3 lines
1.5 MiB
3 lines
1.5 MiB
#/daemon/books.c
|
||
Books (["/doc/manual":(["object":"/domains/default/obj/manual","/doc/manual/chapter39":1164672846,"/doc/manual/chapter38":1164672846,"/doc/manual/chapter37":1164672846,"/doc/manual/chapter36":1164672846,"/doc/manual/chapter35":1164672846,"/doc/manual/chapter34":1164672846,"/doc/manual/chapter33":1164672846,"/doc/manual/chapter32":1164672846,"/doc/manual/chapter31":1164672846,"/doc/manual/chapter30":1164672846,"/doc/manual/chapter19":1164672846,"/doc/manual/chapter18":1164672846,"/doc/manual/chapter17":1164672846,"/doc/manual/chapter16":1164672846,"title":"Untitled","/doc/manual/chapter15":1164672846,"/doc/manual/chapter14":1164672846,"/doc/manual/chapter13":1164672846,"/doc/manual/chapter12":1164672846,"/doc/manual/chapter11":1164672846,"/doc/manual/chapter10":1164672846,"index":" Untitled
Chapter 1: \"Introduction to the Coding Environment\"
Chapter 2: \"The LPC Program\"
Chapter 3: \"LPC Data Types\"
Chapter 4: \"Functions\"
Chapter 5: \"The Basics of Inheritance\"
Chapter 6: \"Variable Handling\"
Chapter 7: \"Flow Control\"
Chapter 8: \"LPC Basics\"
Chapter 9: \"Introduction to Intermediate LPC\"
Chapter 10: \"The LPMud Driver\"
Chapter 11: \"Complex Data Types\"
Chapter 12: \"The LPC Pre-Compiler\"
Chapter 13: \"Advanced String Handling\"
Chapter 14: \"Intermediate Inheritance\"
Chapter 15: \"Debugging\"
Chapter 16: \"Armor\"
Chapter 17: \"Barkeeps\"
Chapter 18: \"Valid climates\"
Chapter 19: \"Doors\"
Chapter 20: \"Items\"
Chapter 21: \"Meals\"
Chapter 22: \"NPCs\"
Chapter 23: \"Properties\"
Chapter 24: \"Quests\"
Chapter 25: \"Rooms\"
Chapter 26: \"Sentients\"
Chapter 27: \"Towns\"
Chapter 28: \"Vendors\"
Chapter 29: \"Weapons\"
Chapter 30: \"The Natural Language Parser\"
Chapter 31: \"Overview of the Quick Creation System\"
Chapter 32: \"QCS: Commands\"
Chapter 33: \"QCS: Creation\"
Chapter 34: \"QCS: Modification of NPC's\"
Chapter 35: \"QCS: Modifying rooms\"
Chapter 36: \"QCS: Modifying weapons\"
Chapter 37: \"QCS: Modifying things and stuff\"
Chapter 38: \"QCS: Adding and deleting\"
Chapter 39: \"QCS: Final notes\"
Chapter 40: \"Useful Creator Commands\"
","reads":([({"chapter 13","chapter thirteen","13",}):"chapter 13 \"Advanced String Handling\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 5: Advanced String Handling
5.1 What a String Is
The LPC Basics textbook taught strings as simple data types. LPC
generally deals with strings in such a matter. The underlying driver
program, however, is written in C, which has no string data type. The
driver in fact sees strings as a complex data type made up of an array of
characters, a simple C data type. LPC, on the other hand does not
recognize a character data type (there may actually be a driver or two out
there which do recognize the character as a data type, but in general not).
The net effect is that there are some array-like things you can do with
strings that you cannot do with other LPC data types.
The first efun regarding strings you should learn is the strlen() efun.
This efun returns the length in characters of an LPC string, and is thus
the string equivalent to sizeof() for arrays. Just from the behaviour of
this efun, you can see that the driver treats a string as if it were made up
of smaller elements. In this chapter, you will learn how to deal with
strings on a more basic level, as characters and sub strings.
5.2 Strings as Character Arrays
You can do nearly anything with strings that you can do with arrays,
except assign values on a character basis. At the most basic, you can
actually refer to character constants by enclosing them in '' (single
quotes). 'a' and \"a\" are therefore very different things in LPC. 'a'
represents a character which cannot be used in assignment statements or
any other operations except comparison evaluations. \"a\" on the other
hand is a string made up of a single character. You can add and subtract
other strings to it and assign it as a value to a variable.
With string variables, you can access the individual characters to run
comparisons against character constants using exactly the same syntax
that is used with arrays. In other words, the statement:
if(str[2] == 'a')
is a valid LPC statement comparing the second character in the str string
to the character 'a'. You have to be very careful that you are not
comparing elements of arrays to characters, nor are you comparing
characters of strings to strings.
LPC also allows you to access several characters together using LPC's
range operator ..:
if(str[0..1] == \"ab\")
In other words, you can look for the string which is formed by the
characters 0 through 1 in the string str. As with arrays, you must be
careful when using indexing or range operators so that you do not try to
reference an index number larger than the last index. Doing so will
result in an error.
Now you can see a couple of similarities between strings and arrays:
1) You may index on both to access the values of individual elements.
a) The individual elements of strings are characters
b) The individual elements of arrays match the data type of the
array.
2) You may operate on a range of values
a) Ex: \"abcdef\"[1..3] is the string \"bcd\"
b) Ex: ({ 1, 2, 3, 4, 5 })[1..3] is the int array ({ 2, 3, 4 })
And of course, you should always keep in mind the fundamental
difference: a string is not made up of a more fundamental LPC data type.
In other words, you may not act on the individual characters by
assigning them values.
5.3 The Efun sscanf()
You cannot do any decent string handling in LPC without using
sscanf(). Without it, you are left trying to play with the full strings
passed by command statements to the command functions. In other
words, you could not handle a command like: \"give sword to leo\", since
you would have no way of separating \"sword to leo\" into its constituent
parts. Commands such as these therefore use this efun in order to use
commands with multiple arguments or to make commands more
\"English-like\".
Most people find the manual entries for sscanf() to be rather difficult
reading. The function does not lend itself well to the format used by
manual entries. As I said above, the function is used to take a string and
break it into usable parts. Technically it is supposed to take a string and
scan it into one or more variables of varying types. Take the example
above:
int give(string str) {
string what, whom;
if(!str) return notify_fail(\"Give what to whom?\\n\");
if(sscanf(str, \"%s to %s\", what, whom) != 2)
return notify_fail(\"Give what to whom?\\n\");
... rest of give code ...
}
The efun sscanf() takes three or more arguments. The first argument is
the string you want scanned. The second argument is called a control
string. The control string is a model which demonstrates in what form
the original string is written, and how it should be divided up. The rest
of the arguments are variables to which you will assign values based
upon the control string.
The control string is made up of three different types of elements: 1)
constants, 2) variable arguments to be scanned, and 3) variable
arguments to be discarded. You must have as many of the variable
arguments in sscanf() as you have elements of type 2 in your control
string. In the above example, the control string was \"%s to %s\", which
is a three element control string made up of one constant part (\" to \"),
and two variable arguments to be scanned (\"%s\"). There were no
variables to be discarded.
The control string basically indicates that the function should find the
string \" to \" in the string str. Whatever comes before that constant will
be placed into the first variable argument as a string. The same thing
will happen to whatever comes after the constant.
Variable elements are noted by a \"%\" sign followed by a code for
decoding them. If the variable element is to be discarded, the \"%\" sign
is followed by the \"*\" as well as the code for decoding the variable.
Common codes for variable element decoding are \"s\" for strings and \"d\"
for integers. In addition, your mudlib may support other conversion
codes, such as \"f\" for float. So in the two examples above, the \"%s\" in
the control string indicates that whatever lies in the original string in the
corresponding place will be scanned into a new variable as a string.
A simple exercise. How would you turn the string \"145\" into an
integer?
Answer:
int x;
sscanf(\"145\", \"%d\", x);
After the sscanf() function, x will equal the integer 145.
Whenever you scan a string against a control string, the function
searches the original string for the first instance of the first constant in
the original string. For example, if your string is \"magic attack 100\" and
you have the following:
int improve(string str) {
string skill;
int x;
if(sscanf(str, \"%s %d\", skill, x) != 2) return 0;
...
}
you would find that you have come up with the wrong return value for
sscanf() (more on the return values later). The control string, \"%s %d\",
is made up of to variables to be scanned and one constant. The constant
is \" \". So the function searches the original string for the first instance
of \" \", placing whatever comes before the \" \" into skill, and trying to
place whatever comes after the \" \" into x. This separates \"magic attack
100\" into the components \"magic\" and \"attack 100\". The function,
however, cannot make heads or tales of \"attack 100\" as an integer, so it
returns 1, meaning that 1 variable value was successfully scanned
(\"magic\" into skill).
Perhaps you guessed from the above examples, but the efun sscanf()
returns an int, which is the number of variables into which values from
the original string were successfully scanned. Some examples with
return values for you to examine:
sscanf(\"swo rd descartes\", \"%s to %s\", str1, str2) return: 0
sscanf(\"swo rd descartes\", \"%s %s\", str1, str2) return: 2
sscanf(\"200 gold to descartes\", \"%d %s to %s\", x, str1, str2) return: 3
sscanf(\"200 gold to descartes\", \"%d %*s to %s\", x, str1) return: 2
where x is an int and str1 and str2 are string
5.4 Summary
LPC strings can be thought of as arrays of characters, yet always
keeping in mind that LPC does not have the character data type (with
most, but not all drivers). Since the character is not a true LPC data
type, you cannot act upon individual characters in an LPC string in the
same manner you would act upon different data types. Noticing the
intimate relationship between strings and arrays nevertheless makes it
easier to understand such concepts as the range operator and indexing on
strings.
There are efuns other than sscanf() which involve advanced string
handling, however, they are not needed nearly as often. You should
check on your mud for man or help files on the efuns: explode(),
implode(), replace_string(), sprintf(). All of these are very valuable
tools, especially if you intend to do coding at the mudlib level.
Copyright (c) George Reese 1993
",({"chapter 11","chapter eleven","11",}):"chapter 11 \"Complex Data Types\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 3: Complex Data Types
3.1 Simple Data Types
In the textbook LPC Basics, you learned about the common, basic LPC
data types: int, string, object, void. Most important you learned that
many operations and functions behave differently based on the data type
of the variables upon which they are operating. Some operators and
functions will even give errors if you use them with the wrong data
types. For example, \"a\"+\"b\" is handled much differently than 1+1.
When you ass \"a\"+\"b\", you are adding \"b\" onto the end of \"a\" to get
\"ab\". On the other hand, when you add 1+1, you do not get 11, you get
2 as you would expect.
I refer to these data types as simple data types, because they atomic in
that they cannot be broken down into smaller component data types.
The object data type is a sort of exception, but you really cannot refer
individually to the components which make it up, so I refer to it as a
simple data type.
This chapter introduces the concept of the complex data type, a data type
which is made up of units of simple data types. LPC has two common
complex data types, both kinds of arrays. First, there is the traditional
array which stores values in consecutive elements accessed by a number
representing which element they are stored in. Second is an associative
array called a mapping. A mapping associates to values together to
allow a more natural access to data.
3.2 The Values NULL and 0
Before getting fully into arrays, there first should be a full understanding
of the concept of NULL versus the concept of 0. In LPC, a null value is
represented by the integer 0. Although the integer 0 and NULL are often
freely interchangeable, this interchangeability often leads to some great
confusion when you get into the realm of complex data types. You may
have even encountered such confusion while using strings.
0 represents a value which for integers means the value you add to
another value yet still retain the value added. This for any addition
operation on any data type, the ZERO value for that data type is the value
that you can add to any other value and get the original value. Thus: A
plus ZERO equals A where A is some value of a given data type and
ZERO is the ZERO value for that data type. This is not any sort of
official mathematical definition. There exists one, but I am not a
mathematician, so I have no idea what the term is. Thus for integers, 0
is the ZERO value since 1 + 0 equals 1.
NULL, on the other hand, is the absence of any value or meaning. The
LPC driver will interpret NULL as an integer 0 if it can make sense of it
in that context. In any context besides integer addition, A plus NULL
causes an error. NULL causes an error because adding valueless fields
in other data types to those data types makes no sense.
Looking at this from another point of view, we can get the ZERO value
for strings by knowing what added to \"a\" will give us \"a\" as a result.
The answer is not 0, but instead \"\". With integers, interchanging NULL
and 0 was acceptable since 0 represents no value with respect to the
integer data type. This interchangeability is not true for other data types,
since their ZERO values do not represent no value. Namely, \"\"
represents a string of no length and is very different from 0.
When you first declare any variable of any type, it has no value. Any
data type except integers therefore must be initialized somehow before
you perform any operation on it. Generally, initialization is done in the
create() function for global variables, or at the top of the local function
for local variables by assigning them some value, often the ZERO value
for that data type. For example, in the following code I want to build a
string with random words:
string build_nonsense() {
string str;
int i;
str = \"\"; /* Here str is initialized to the string
ZERO value */
for(i=0; i<6; i++) {
switch(random(3)+1) {
case 1: str += \"bing\"; break;
case 2: str += \"borg\"; break;
case 3: str += \"foo\"; break;
}
if(i==5) str += \".\\n\";
else str += \" \";
}
return capitalize(str);
}
If we had not initialized the variable str, an error would have resulted
from trying to add a string to a NULL value. Instead, this code first
initializes str to the ZERO value for strings, \"\". After that, it enters a
loop which makes 6 cycles, each time randomly adding one of three
possible words to the string. For all words except the last, an additional
blank character is added. For the last word, a period and a return
character are added. The function then exits the loop, capitalizes the
nonsense string, then exits.
3.3 Arrays in LPC
An array is a powerful complex data type of LPC which allows you to
access multiple values through a single variable. For instance,
Nightmare has an indefinite number of currencies in which players may
do business. Only five of those currencies, however, can be considered
hard currencies. A hard currency for the sake of this example is a
currency which is readily exchangeable for any other hard currency,
whereas a soft currency may only be bought, but not sold. In the bank,
there is a list of hard currencies to allow bank keepers to know which
currencies are in fact hard currencies. With simple data types, we would
have to perform the following nasty operation for every exchange
transaction:
int exchange(string str) {
string from, to;
int amt;
if(!str) return 0;
if(sscanf(str, \"%d %s for %s\", amt, from, to) != 3)
return 0;
if(from != \"platinum\" && from != \"gold\" && from !=
\"silver\" &&
from != \"electrum\" && from != \"copper\") {
notify_fail(\"We do not buy soft currencies!\\n\");
return 0;
}
...
}
With five hard currencies, we have a rather simple example. After all it
took only two lines of code to represent the if statement which filtered
out bad currencies. But what if you had to check against all the names
which cannot be used to make characters in the game? There might be
100 of those; would you want to write a 100 part if statement?
What if you wanted to add a currency to the list of hard currencies? That
means you would have to change every check in the game for hard
currencies to add one more part to the if clauses. Arrays allow you
simple access to groups of related data so that you do not have to deal
with each individual value every time you want to perform a group
operation.
As a constant, an array might look like this:
({ \"platinum\", \"gold\", \"silver\", \"electrum\", \"copper\" })
which is an array of type string. Individual data values in arrays are
called elements, or sometimes members. In code, just as constant
strings are represented by surrounding them with \"\", constant arrays are
represented by being surrounded by ({ }), with individual elements of
the array being separated by a ,.
You may have arrays of any LPC data type, simple or complex. Arrays
made up of mixes of values are called arrays of mixed type. In most
LPC drivers, you declare an array using a throw-back to C language
syntax for arrays. This syntax is often confusing for LPC coders
because the syntax has a meaning in C that simply does not translate into
LPC. Nevertheless, if we wanted an array of type string, we would
declare it in the following manner:
string *arr;
In other words, the data type of the elements it will contain followed by
a space and an asterisk. Remember, however, that this newly declared
string array has a NULL value in it at the time of declaration.
3.4 Using Arrays
You now should understand how to declare and recognize an array in
code. In order to understand how they work in code, let's review the
bank code, this time using arrays:
string *hard_currencies;
int exchange(string str) {
string from, to;
int amt;
if(!str) return 0;
if(sscanf(str, \"%d %s for %s\", amt, from, to) != 3)
return 0;
if(member_array(from, hard_currencies) == -1) {
notify_fail(\"We do not buy soft currencies!\\n\");
return 0;
}
...
}
This code assumes hard_currencies is a global variable and is initialized
in create() as:
hard_currencies = ({ \"platinum\", \"gold\", \"electrum\", \"silver\",
\"copper\" });
Ideally, you would have hard currencies as a #define in a header file for
all objects to use, but #define is a topic for a later chapter.
Once you know what the member_array() efun does, this method
certainly is much easier to read as well as is much more efficient and
easier to code. In fact, you can probably guess what the
member_array() efun does: It tells you if a given value is a member of
the array in question. Specifically here, we want to know if the currency
the player is trying to sell is an element in the hard_curencies array.
What might be confusing to you is, not only does member_array() tell us
if the value is an element in the array, but it in fact tells us which element
of the array the value is.
How does it tell you which element? It is easier to understand arrays if
you think of the array variable as holding a number. In the value above,
for the sake of argument, we will say that hard_currencies holds the
value 179000. This value tells the driver where to look for the array
hard_currencies represents. Thus, hard_currencies points to a place
where the array values may be found. When someone is talking about
the first element of the array, they want the element located at 179000.
When the object needs the value of the second element of the array, it
looks at 179000 + one value, then 179000 plus two values for the third,
and so on. We can therefore access individual elements of an array by
their index, which is the number of values beyond the starting point of
the array we need to look to find the value. For the array
hard_currencies array:
\"platinum\" has an index of 0.
\"gold\" has an index of 1.
\"electrum\" has an index of 2.
\"silver\" has an index of 3.
\"copper\" has an index of 4.
The efun member_array() thus returns the index of the element being
tested if it is in the array, or -1 if it is not in the array. In order to
reference an individual element in an array, you use its index number in
the following manner:
array_name[index_no]
Example:
hard_currencies[3]
where hard_currencies[3] would refer to \"silver\".
So, you now should now several ways in which arrays appear either as
a whole or as individual elements. As a whole, you refer to an array
variable by its name and an array constant by enclosing the array in ({ })
and separating elements by ,. Individually, you refer to array variables
by the array name followed by the element's index number enclosed in
[], and to array constants in the same way you would refer to simple data
types of the same type as the constant. Examples:
Whole arrays:
variable: arr
constant: ({ \"platinum\", \"gold\", \"electrum\", \"silver\", \"copper\" })
Individual members of arrays:
variable: arr[2]
constant: \"electrum\"
You can use these means of reference to do all the things you are used to
doing with other data types. You can assign values, use the values in
operations, pass the values as parameters to functions, and use the
values as return types. It is important to remember that when you are
treating an element alone as an individual, the individual element is not
itself an array (unless you are dealing with an array of arrays). In the
example above, the individual elements are strings. So that:
str = arr[3] + \" and \" + arr[1];
will create str to equal \"silver and gold\". Although this seems simple
enough, many people new to arrays start to run into trouble when trying
to add elements to an array. When you are treating an array as a whole
and you wish to add a new element to it, you must do it by adding
another array.
Note the following example:
string str1, str2;
string *arr;
str1 = \"hi\";
str2 = \"bye\";
/* str1 + str2 equals \"hibye\" */
arr = ({ str1 }) + ({ str2 });
/* arr is equal to ({ str1, str2 }) */
Before going any further, I have to note that this example gives an
extremely horrible way of building an array. You should set it: arr = ({
str1, str2 }). The point of the example, however, is that you must add
like types together. If you try adding an element to an array as the data
type it is, you will get an error. Instead you have to treat it as an array of
a single element.
3.5 Mappings
One of the major advances made in LPMuds since they were created is
the mapping data type. People alternately refer to them as associative
arrays. Practically speaking, a mapping allows you freedom from the
association of a numerical index to a value which arrays require.
Instead, mappings allow you to associate values with indices which
actually have meaning to you, much like a relational database.
In an array of 5 elements, you access those values solely by their integer
indices which cover the range 0 to 4. Imagine going back to the example
of money again. Players have money of different amounts and different
types. In the player object, you need a way to store the types of money
that exist as well as relate them to the amount of that currency type the
player has. The best way to do this with arrays would have been to
store an array of strings representing money types and an array of
integers representing values in the player object. This would result in
CPU-eating ugly code like this:
int query_money(string type) {
int i;
i = member_array(type, currencies);
if(i>-1 && i < sizeof(amounts)) /* sizeof efun
returns # of elements */
return amounts[i];
else return 0;
}
And that is a simple query function. Look at an add function:
void add_money(string type, int amt) {
string *tmp1;
int * tmp2;
int i, x, j, maxj;
i = member_array(type, currencies);
if(i >= sizeof(amounts)) /* corrupt data, we are in
a bad way */
return;
else if(i== -1) {
currencies += ({ type });
amounts += ({ amt });
return;
}
else {
amounts[i] += amt;
if(amounts[i] < 1) {
tmp1 = allocate(sizeof(currencies)-1);
tmp2 = allocate(sizeof(amounts)-1);
for(j=0, x =0, maxj=sizeof(tmp1); j < maxj;
j++) {
if(j==i) x = 1;
tmp1[j] = currencies[j+x];
tmp2[j] = amounts[j+x];
}
currencies = tmp1;
amounts = tmp2;
}
}
}
That is really some nasty code to perform the rather simple concept of
adding some money. First, we figure out if the player has any of that
kind of money, and if so, which element of the currencies array it is.
After that, we have to check to see that the integrity of the currency data
has been maintained. If the index of the type in the currencies array is
greater than the highest index of the amounts array, then we have a
problem since the indices are our only way of relating the two arrays.
Once we know our data is in tact, if the currency type is not currently
held by the player, we simply tack on the type as a new element to the
currencies array and the amount as a new element to the amounts array.
Finally, if it is a currency the player currently has, we just add the
amount to the corresponding index in the amounts array. If the money
gets below 1, meaning having no money of that type, we want to clear
the currency out of memory.
Subtracting an element from an array is no simple matter. Take, for
example, the result of the following:
string *arr;
arr = ({ \"a\", \"b\", \"a\" });
arr -= ({ arr[2] });
What do you think the final value of arr is? Well, it is:
({ \"b\", \"a\" })
Subtracting arr[2] from the original array does not remove the third
element from the array. Instead, it subtracts the value of the third
element of the array from the array. And array subtraction removes the
first instance of the value from the array. Since we do not want to be
forced on counting on the elements of the array as being unique, we are
forced to go through some somersaults to remove the correct element
from both arrays in order to maintain the correspondence of the indices
in the two arrays.
Mappings provide a better way. They allow you to directly associate the
money type with its value. Some people think of mappings as arrays
where you are not restricted to integers as indices. Truth is, mappings
are an entirely different concept in storing aggregate information. Arrays
force you to choose an index which is meaningful to the machine for
locating the appropriate data. The indices tell the machine how many
elements beyond the first value the value you desire can be found. With
mappings, you choose indices which are meaningful to you without
worrying about how that machine locates and stores it.
You may recognize mappings in the following forms:
constant values:
whole: ([ index:value, index:value ]) Ex: ([ \"gold\":10, \"silver\":20 ])
element: 10
variable values:
whole: map (where map is the name of a mapping variable)
element: map[\"gold\"]
So now my monetary functions would look like:
int query_money(string type) { return money[type]; }
void add_money(string type, int amt) {
if(!money[type]) money[type] = amt;
else money[type] += amt;
if(money[type] < 1)
map_delete(money, type); /* this is for
MudOS */
...OR...
money = m_delete(money, type) /* for some
LPMud 3.* varieties */
... OR...
m_delete(money, type); /* for other LPMud 3.*
varieties */
}
Please notice first that the efuns for clearing a mapping element from the
mapping vary from driver to driver. Check with your driver's
documentation for the exact name an syntax of the relevant efun.
As you can see immediately, you do not need to check the integrity of
your data since the values which interest you are inextricably bound to
one another in the mapping. Secondly, getting rid of useless values is a
simple efun call rather than a tricky, CPU-eating loop. Finally, the
query function is made up solely of a return instruction.
You must declare and initialize any mapping before using it.
Declarations look like:
mapping map;
Whereas common initializations look like:
map = ([]);
map = allocate_mapping(10) ...OR... map = m_allocate(10);
map = ([ \"gold\": 20, \"silver\": 15 ]);
As with other data types, there are rules defining how they work in
common operations like addition and subtraction:
([ \"gold\":20, \"silver\":30 ]) + ([ \"electrum\":5 ])
gives:
([\"gold\":20, \"silver\":30, \"electrum\":5])
Although my demonstration shows a continuity of order, there is in fact
no guarantee of the order in which elements of mappings will stored.
Equivalence tests among mappings are therefore not a good thing.
3.6 Summary
Mappings and arrays can be built as complex as you need them to be.
You can have an array of mappings of arrays. Such a thing would be
declared like this:
mapping *map_of_arrs;
which might look like:
({ ([ ind1: ({ valA1, valA2}), ind2: ({valB1, valB2}) ]), ([ indX:
({valX1,valX2}) ]) })
Mappings may use any data type as an index, including objects.
Mapping indices are often referred to as keys as well, a term from
databases. Always keep in mind that with any non-integer data type,
you must first initialize a variable before making use of it in common
operations such as addition and subtraction. In spite of the ease and
dynamics added to LPC coding by mappings and arrays, errors caused
by failing to initialize their values can be the most maddening experience
for people new to these data types. I would venture that a very high
percentage of all errors people experimenting with mappings and arrays
for the first time encounter are one of three error messages:
Indexing on illegal type.
Illegal index.
Bad argument 1 to (+ += - -=) /* insert your favourite operator */
Error messages 1 and 3 are darn near almost always caused by a failure
to initialize the array or mapping in question. Error message 2 is caused
generally when you are trying to use an index in an initialized array
which does not exist. Also, for arrays, often people new to arrays will
get error message 3 because they try to add a single element to an array
by adding the initial array to the single element value instead of adding
an array of the single element to the initial array. Remember, add only
arrays to arrays.
At this point, you should feel comfortable enough with mappings and
arrays to play with them. Expect to encounter the above error messages
a lot when first playing with these. The key to success with mappings is
in debugging all of these errors and seeing exactly what causes wholes
in your programming which allow you to try to work with uninitialized
mappings and arrays. Finally, go back through the basic room code and
look at things like the SetExits() (or the equivalent on your mudlib)
function. Chances are it makes use of mappings. In some instances, it
will use arrays as well for compatibility with mudlib.n.
Copyright (c) George Reese 1993
",({"chapter 24","chapter twenty-four","24",}):"chapter 24 \"Quests\"
Building Quests
from the Nightmare IV LPC Library
written by Descartes of Borg 950716
Unlike previous Nightmare versions, Nightmare IV has no support for
centralized quest administration. This was done under the belief that
coercive questing was among the least favourite features players have
mentioned about the MUDs I have encountered. Nevertheless, the
presence of quests is still an extrememly important part of any MUD.
Since the coercive nature (needing to complete quest X to raise to
level Y) has been removed, other ways to make questing worthwhile need
to be found.
The first, and most obvious, is to properly reward the player with
money, items, and skill and stat points. The other bit of support is
for a title list. Each quest, or accomplishment, is added to a list
of accomplishments the player has. The player may display any of
those at any time as part of their title.
The interface to this is simple:
player_object->AddQuest(string title, string description);
Example:
this_player()->AddQuest(\"the slayer of frogs\",
\"You viciously slayed the evil frogs of Wernmeister that \"
\"threatened the peaceful town with warts and unabated fly murder.\");
In the player's biography, they will see the description along with
the date they accomplished the task. From their title list, they will
now be able to choose this title.
Descartes of Borg
950716
",({"chapter 37","chapter thirty-seven","37",}):"chapter 37 \"QCS: Modifying things and stuff\"
You should have a firm grasp now on how QCS works
in relation to manipulable objects. Let's look at the
settings for a few special kinds of items:
chairs
------
%^GREEN%^modify stool maxsitters 1%^RESET%^
%^GREEN%^modify stool setmaxcarry 200%^RESET%^
beds
----
%^GREEN%^modify sofa maxsitters 2%^RESET%^
%^GREEN%^modify sofa maxliers 1%^RESET%^
%^GREEN%^modify sofa maxcarry 400%^RESET%^
containers
----------
%^GREEN%^modify box canclose 1%^RESET%^
%^GREEN%^modify box closed 1%^RESET%^
%^GREEN%^modify box locked 1%^RESET%^
%^GREEN%^modify box key magic_skeleton_key%^RESET%^
%^GREEN%^modify box maxcarry 200%^RESET%^
%^GREEN%^modify box setmoney gold 15%^RESET%^
tables
------
%^GREEN%^modify altar maxcarry 300%^RESET%^
%^GREEN%^modify altar maxliers 1%^RESET%^
meals/drinks
------------
%^GREEN%^modify burger mealtype food%^RESET%^
%^GREEN%^modify schlitz mealtype alcohol%^RESET%^
%^GREEN%^modify apple mealstrength 10%^RESET%^
books
-----
%^GREEN%^modify journal title The Orc Within%^RESET%^
%^GREEN%^modify journal source /domains/Orcland/etc/books/journal%^RESET%^
Readable things:
----------------
If you want to be able to \"read thing\", for example, \"read sign\":
%^GREEN%^modify sign defaultread This is a message written on the sign.%^RESET%^
If you want to make a thing on a thing readable, as in
\"read inscription on ring\":
%^GREEN%^modify ring item%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^This is an inscription on the ring. Try 'read inscription on ring'%^RESET%^
%^GREEN%^modify ring read%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^So! We, the spear-Danes%^RESET%^
By default, readabale items are readable by anyone, regardless
of the languages they know. If, however, your item should
only be readable by someone who understands the elvish tongue:
%^GREEN%^modify ring language edhellen%^RESET%^
Miscellaneous:
--------------
To make a key have a 50% chance of breaking when it's used:
%^GREEN%^modify golden key disablechance 50%^RESET%^
To make a room or object immune to resets:
%^GREEN%^modify sentry noclean 1%^RESET%^
To make sure there is only one instance of an object or
NPC loaded at any given time:
%^GREEN%^modify tiamat unique 1%^RESET%^
To make a thing or room immune to the QCS (except for this
command):
%^GREEN%^modify workroom nomodify 1%^RESET%^
To specify what kind of vendor should be allowed to traffic
in this item:
%^GREEN%^modify necklace vendortype treasure%^RESET%^
",({"chapter 16","chapter sixteen","16",}):"chapter 16 \"Armor\"
Building Armours
The Nightmare IV LPC Library
written by Descartes of Borg 950430
Armour has changed quite a bit from the days of armour class. The
Nightmare IV LPC Library now uses damage types, which means armour that
is great against one attack may be pathetic against another. In fact,
in building armour, it is important that you keep in mind weaknesses.
Fortunately, armour is by default absolutely pathetic. If you go
making it awesome, chances are that it will not make it through the
approval process. This document is designed to get you started
building armour as well introduce you to the features available to
make unique and interesting armour.
I. Basic Armour
You should be familiar with /doc/build/Items, as armour is just a
special type of item. It therefore has all of the features of regular
items.
The basic armour looks like this:
#include <lib.h> /* see this everywhere */
#include <armour_types.h> /* a listing of armour types */
#include <damage_types.h> /* a listing of damage types */
inherit LIB_ARMOUR; /* the armour inheritable */
static void create() {
armour::create(); /* call create() in armour.c */
SetKeyName(\"rusty helm\");
SetId( ({ \"helm\", \"rusty helm\", \"a rusty helm\" }) );
SetAdjectives( ({ \"rusty\" }) );
SetShort(\"a rusty helm\");
SetLong(\"A rusty helmet which will be better than nothing on your head.\");
SetMass(75);
SetValue(200);
SetDamagePoints(1000);
SetProtection(BLUNT, 4); /* SetProtection() sets the sort of */
SetProtection(BLADE, 3); /* protection for a given damage type */
SetProtection(KNIFE, 3);
SetArmourType(A_HELMET); /* set what kind of armour this is */
}
As you can see, there is very little that you have to do specific to
armour. The only armour specific call you MUST make is
SetArmourType(). Everything else is fluff.
int SetArmourType(int type)
Armour types are found in /include/armour_types.h. The armour type
basically determines where the armour is worn. Each monster,
depending on its race, has for each limb a list of armour types which
may be worn on that limb. For example, most monsters have heads.
Some have two heads. You do not have to worry about this. They know
that they can wear anything that is A_HELMET on their heads. What if
you have something that may not be wearable on all monsters? Like,
for example, you have body armour which should only go on two armed
beings? See SetRestrictLimbs() later. It allows you to restrict
exactly which kinds of limbs can wear the armour.
int SetProtection(int type, int amount);
Without this call, armour is nothing. Just something you wear. This
allows you to make clothes, which may protect against COLD, but do not
do a thing when struck with a sword. Protection is a number between 0
and 100. Refer to approval documentation for details on what levels
are appropriate, as well as for information on mass and value levels.
That's it for the basics!
II. Advanced Function Calls
The Nightmare IV LPC Library armour object is fairly flexible for
allowing you to do interesting things with your armours. In this
section, you will learn about other function calls you can make to
customize your armour.
string *SetRestrictLimbs(string *limbs);
Example:
SetRestrictLimbs( ({ \"right arm\", \"left arm\", \"torso\" }) );
For armours which can only be on certain body configurations, for
example regular armour (A_ARMOUR) should only be worn on people with
the same number of hands, this function allows you to restrict the
armour to being worn only on the limbs you name. If the person trying
to wear the armour does not have one of those limbs, any attempt to
wear fails.
int SetFingers(int num);
Example:
SetFingers(5);
Used for the glove types. If a person has more fingers on the limb on
which they are trying to wear a glove type than the glove has spaces
for, the wear fails.
mixed SetWear(string | function val);
Examples:
SetWear(\"The cloak feels all yucky on you.\");
SetWear( (: CheckArtrell :) );
Allows you to create a special message seen by the person wearing the
item when they wear it if you pass a string. On the other hand, if
you pass a function, it will call that function to see if the person
can wear the item. The function should be of the form:
int WearFunc();
For example:
int CheckArtrell() {
if( (string)this_player()->GetRace() == \"artrell\" ) {
write(\"The rusty helm makes you feel safe.\");
say((string)this_player()->GetName() + \" wears a rusty helm.\");
return 1;
}
else {
write(\"You cannot wear that you bum!\");
return 1;
}
}
III. Function Overrides
The only function of interest that you might want to override is a
function called eventReceiveDamage(). This function is called every
time the armour is hit to see how much of the damage it absorbs. It
looks like this:
int eventReceiveDamage(int type, int strength, int unused, mixed limbs);
This function is called by combat to determine how much damage the
armour absorbs for a given bit of damage being done. It thus should
return how much damage it takes.
You should always at some point call item::eventReceiveDamage() so
that it can do its processing. You do not want to call it, however,
until you determine how much damage you are absorbing unnaturally.
Here is a sample one for an armour that does extra protection for fighters:
int eventReceiveDamage(int type, int strength, int blah, mixed limbs) {
object who_is_wearing;
int x;
if( !(who_is_wearing = environment()) ) /* eek! no one wearing */
return 0;
if( (int)who_is_wearing->ClassMember(\"fighter\") ) /* reduce strength */
x = strength - random(5);
if( x < 1 ) return strength; /* protect against all the damage */
return armour::eventReceiveDamage(type, x, blah, limbs);
}
Keep in mind what eventReceiveDamage() in armour.c is doing. First,
it is modifying the strength of the blow based on the protections you
set with SetProtection(). Then, it is having the armour take damage
based on how much it absorbed. So you need to call
eventReceiveDamage() in armour at the point where you have a value you
want the armour to do its normal stuff with. In the example above, we
wanted to magically protect fighters against a random(5) points of
damage without having the armour take any damage for that. Then if
there is still strength left in the blow, the armour does its normal
protection.
What else can you do with this? Imagine an armour that turns all cold
damage back on the attacker?
int eventReceiveDamage(int type, int strength, int unused, mixed limbs) {
object who_wearing, enemy;
enemy = (object)(who_wearing = previous_object())->GetCurrentEnemy();
if( !enemy || !(type & COLD) )
return armour::eventReceiveDamage(type, strength, unused, limbs);
limbs = enemy->GetTargetLimb(0);
message(\"environment\", \"Your anti-cold throws the frost in your \"
\"enemy's face!\", who_wearing);
message(\"environment\", \"Your cold attack is turned back upon you!\",
enemy);
enemy->eventReceiveDamage(COLD, strength, unused, limbs);
return strength; /* we absorb all of the strength but take no damage */
}
Descartes of Borg
borg@imaginary.com
",({"chapter 27","chapter twenty-seven","27",}):"chapter 27 \"Towns\"
Building Towns
The Nightmare IV LPC Library
written by Descartes of Borg 950429
The Nightmare IV LPC Library contains support for towns, which is in
fact very minimal from the mudlib level. If, however, you wish to
structure your MUD to be centered around the concept as Nightmare LPMud
is, then you need to understand how to build a town. This document
describes the building of towns using the Nightmare IV LPC Library.
I. What Is a Town?
A town is simply a collection of rooms which have the same value set
for Town. If done poorly, this is all it is. If done right, however,
a town becomes the center of the games' social structure. If you
decide to build a town in your area, the first thing you need to do is
isolate it. All towns should be surrounded by vast, vast areas of
wilderness of some sort. This may mean desert, forest, jungle, or
whatever. You may or may not want to have a road which links it to
the rest of civilization.
Rooms are considered \"wilderness\" by default. That is, if you never
set the town in them, they are considered wilderness. To make a room
part of a town, you need to call SetTown() from create() of the room:
SetTown(\"Praxis\");
Capitalize your town name properly.
Next you need to decide how many estates may be built in the room.
Ideally, towns are expanding and changing things. Upper level players
have the ability to build estates in their home towns. Of course, ten
estates in one room is crowded. Generally you should limit the number
of estates to what would logically fit in a given room. For example,
if you are on a road at the edge of town with nothing about, then
allowing two estates makes sense. On the other hand, in the middle of
an intersection of two roads, there is hardly any room for an estate
to be built. To allow estates to be built in a room:
SetProperty(\"estates\", 2);
This allows two estates to be built off of this room.
As stated above, towns are expanding. This is why they should be
situated far apart. Too close together it is hard for them to expand
without changing the overall map of the game. Therefore, when your
town has gotten as full as can be handled, then you simply move to
outlying rooms and make them part of the town by setting their town.
In addition, give them the capacity for estates. Do not forget to
change room descriptions and allow for needed roads!
II. What do I put in towns?
The first section described what is minimally needed for a town from a
code point of view. This section describes what sorts of things you
should put in your towns. Most are optional, however, you do need to
add something called an adventurer's hall. An adventurer's hall is
the default start room for the town for anyone who chooses the town as
their home town. In order to make it their home town, they go to the
adventurer's hall and pay a fee (generally determined by approval) to
move to this town. Until that person builds an estate in the town,
the adventurer's hall is their default starting point.
Beyond that, the only other thing required is a real estate office for
selling estates. This is an inheritable from /lib/sales.c
(LIB_SALES). Approval determines what your local land value is, and
you fill in the descriptions. For information on advanced coding of
sales offices, see the document /doc/build/Sales.
Nothing else is required. Of course, your land value (the amount
people pay to live and build in your town) is determined by the sorts
of services your town offers. No town should offer all services. And
certainly, the services your town offers should reflect the nature of
the region in which you are building. Are you an isolated, small
town? Then few services will be available. Are you a central, large
town? Then a majority of services should be available.
Services include:
shops of different types
bars and pubs
restaurants
libraries for learning languages
class halls
town council rooms
This list will probably expand over time, but it provides a good
starting point for common services.
Descartes of Borg
borg@imaginary.com
",({"chapter 37","chapter thirty-seven","37",}):"chapter 37 \"QCS: Modifying things and stuff\"
You should have a firm grasp now on how QCS works
in relation to manipulable objects. Let's look at the
settings for a few special kinds of items:
chairs
------
%^GREEN%^modify stool maxsitters 1%^RESET%^
%^GREEN%^modify stool setmaxcarry 200%^RESET%^
beds
----
%^GREEN%^modify sofa maxsitters 2%^RESET%^
%^GREEN%^modify sofa maxliers 1%^RESET%^
%^GREEN%^modify sofa maxcarry 400%^RESET%^
containers
----------
%^GREEN%^modify box canclose 1%^RESET%^
%^GREEN%^modify box closed 1%^RESET%^
%^GREEN%^modify box locked 1%^RESET%^
%^GREEN%^modify box key magic_skeleton_key%^RESET%^
%^GREEN%^modify box maxcarry 200%^RESET%^
%^GREEN%^modify box setmoney gold 15%^RESET%^
tables
------
%^GREEN%^modify altar maxcarry 300%^RESET%^
%^GREEN%^modify altar maxliers 1%^RESET%^
meals/drinks
------------
%^GREEN%^modify burger mealtype food%^RESET%^
%^GREEN%^modify schlitz mealtype alcohol%^RESET%^
%^GREEN%^modify apple mealstrength 10%^RESET%^
books
-----
%^GREEN%^modify journal title The Orc Within%^RESET%^
%^GREEN%^modify journal source /domains/Orcland/etc/books/journal%^RESET%^
Readable things:
----------------
If you want to be able to \"read thing\", for example, \"read sign\":
%^GREEN%^modify sign defaultread This is a message written on the sign.%^RESET%^
If you want to make a thing on a thing readable, as in
\"read inscription on ring\":
%^GREEN%^modify ring item%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^This is an inscription on the ring. Try 'read inscription on ring'%^RESET%^
%^GREEN%^modify ring read%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^So! We, the spear-Danes%^RESET%^
By default, readabale items are readable by anyone, regardless
of the languages they know. If, however, your item should
only be readable by someone who understands the elvish tongue:
%^GREEN%^modify ring language edhellen%^RESET%^
Miscellaneous:
--------------
To make a key have a 50% chance of breaking when it's used:
%^GREEN%^modify golden key disablechance 50%^RESET%^
To make a room or object immune to resets:
%^GREEN%^modify sentry noclean 1%^RESET%^
To make sure there is only one instance of an object or
NPC loaded at any given time:
%^GREEN%^modify tiamat unique 1%^RESET%^
To make a thing or room immune to the QCS (except for this
command):
%^GREEN%^modify workroom nomodify 1%^RESET%^
To specify what kind of vendor should be allowed to traffic
in this item:
%^GREEN%^modify necklace vendortype treasure%^RESET%^
",({"chapter 3","chapter three","3",}):"chapter 3 \"LPC Data Types\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 17 june 1993
CHAPTER 3: LPC Data Types
3.1 What you should know by now
LPC object are made up of zero or more variables manipulated by one or
more functions. The order in which these functions appear in code is
irrelevant. The driver uses the LPC code you write by loading copies of
it into memory whenever it is first referenced and additional copies
through cloning. When each object is loaded into memory, all the variables
initially point to no value. The reset() function in compat muds, and
create() in native muds are used to give initial values to variables in
objects. The function for creation is called immediately after the object
is loaded into memory. However, if you are reading this textbook with no
prior programming experience, you may not know what a function is or how
it gets called. And even if you have programming experience, you may
be wondering how the process of functions calling each other gets started
in newly created objects. Before any of these questions get answered,
however, you need to know more about what it is the functions are
manipulating. You therefore should thouroughly come to know the concept
behind LPC data types. Certainly the most boring subject in this manual,
yet it is the most crucial, as 90% of all errors (excepting misplaced
{} and ()) involve the improper usage of LPC data types. So bear through
this important chapter, because it is my feeling that understanding this
chapter alone can help you find coding much, much easier.
3.2 Communicating with the computer
You possibly already know that computers cannot understand the letters
and numbers used by humans. Instead, the \"language\" spoken by computers
consists of an \"alphabet\" of 0's and 1's. Certainly you know computers
do not understand natural human languages. But in fact, they do not
understand the computer languages we write for them either. Computer
languages like BASIC, C, C++, Pascal, etc. are all intermediate
languages. They allow you to structure your thoughts more coherently
for translation into the 0's and 1's of the computer's languages.
There are two methods in which translation is done: compilation and
interpretation. These simply are differences betweem when the
programming language is translated into computer language. With
compiled languages, the programmer writes the code then uses a program
called a compiler to translate the program into the computer's
language. This translation occurs before the program is run. With
interpreted languages however, the process of translation occurs as
the program is being run. Since the translation of the program is
occurring during the time of the program's running in interpreted
languages, interpreted languages make much slower programs than
compiled languages.
The bottom line is, no matter what language you are writing in, at
some point this has to be changed into 0's and 1's which can be
understood by the computer. But the variables which you store in
memory are not simply 0's and 1's. So you have to have a way in
your programming languages of telling the computer whether or not
the 0's and 1's should be treated as decimal numbers or characters or
strings or anything else. You do this through the use of data types.
For example, say you have a variable which you call 'x' and you give
it the decimal whole number value 65. In LPC you would do this through
the statement:
-----
x = 65;
-----
You can later do things like:
_____
write(x+\"\\n\"); /* \\n is symbolically represents a carriage return */
y = x + 5;
-----
The first line allows you to send 65 and a carriage return to someone's screen.
The second line lets you set the value of y to 70.
The problem for the computer is that it does not know what '65' means when
you tell it x = 65;. What you think of 65, it might think of as:
00000000000000000000000001000001
But, also, to the computer, the letter 'A' is represented as:
00000000000000000000000001000001
So, whenever you instruct the computer write(x+\"\\n\");, it must have some
way of knowing that you want to see '65' and not 'A'.
The computer can tell the difference between '65' and 'A' through the use
of data types. A data types simply says what type of data is being stored
by the memory location pointed to by a given variable. Thus, each LPC
variable has a variable type which guides conversions. In the example
given above, you would have had the following line somewhere in the
code *before* the lines shown above:
-----
int x;
-----
This one line tells the driver that whatever value x points to, it will
be used as the data type \"int\", which is short for integer, or whole
number. So you have a basic introduction into the reason why data types
exist. They exist so the driver can make sense of the 0's and 1's that
the computer is storing in memory.
3.3 The data types of LPC
All LPMud drivers have the following data types:
void, status, int, string, object, int *, string *, object *, mixed *
Many drivers, but not all have the following important data types which
are important to discuss:
float, mapping, float *, mapping *
And there are a few drivers with the following rarely used data types
which are not important to discuss:
function, enum, struct, char
3.4 Simple data types
This introductory textbook will deal with the data types void, status,
int, float, string, object, mand mixed. You can find out about the
more complex data types like mappings and arrays in the intermediate
textbook. This chapter deals with the two simplest data types (from the
point of view of the LPC coder), int and string.
An int is any whole number. Thus 1, 42, -17, 0, -10000023 are all type int.
A string is one or more alphanumeric characters. Thus \"a\", \"we are borg\",
\"42\", \"This is a string\" are all strings. Note that strings are always
enclosed in \"\" to allow the driver to distinguish between the int 42 and
the string \"42\" as well as to distinguish between variable names (like x)
and strings by the same names (like \"x\").
When you use a variable in code, you must first let the driver know
what type of data to which that variable points. This process is
called *declaration*. You do this at the beginning of the function
or at the beginning of the object code (outside of functions before all
functions which use it). This is done by placing the name of the data type
before the name of the variable like in the following example:
-----
void add_two_and_two() {
int x;
int y;
x = 2;
y = x + x;
}
-----
Now, this is a complete function. The name of the function is
add_two_and_two(). The function begins with the declaration of an
int variable named x followed by the declaration of an in variable
named y. So now, at this point, the driver now has two variables which
point to NULL values, and it expects what ever values end up there to be
of type int.
A note about the data types void and status:
Void is a trivial data type which points to nothing. It is not used
with respect to variables, but instead with respect to functions. You
will come to understand this better later. For now, you need only
understand that it points to no value.
The data type status is a boolean data type. That is, it can only have
1 or 0 as a value. This is often referred to as being true or false.
3.5 Chapter summary
For variables, the driver needs to know how the 0's and 1's the computer
stores in memory get converted into the forms in which you intend them
to be used. The simplest LPC data types are void, status, int, and string.
You do not user variables of type void, but the data type does come
into play with respect to functions. In addition to being used for
translation from one form to the next, data types are used in determining
what rules the driver uses for such operations as +, -, etc. For example,
in the expression 5+5, the driver knows to add the values of 5 and 5
together to make 10. With strings however, the rules for int addition
make no sense. So instead, with \"a\"+\"b\", it appends \"b\" to the string \"a\"
so that the final string is \"ab\". Errors can thus result if you mistakenly
try to add \"5\"+5. Since int addition makes no sense with strings, the
driver will convert the second 5 to \"5\" and use string addition. The final
result would be \"55\". If you were looking for 10, you would therefore
have ended up with erroneous code. Keep in mind, however, that in most
instances, the driver will not do something so useful as coming up with
\"55\". It comes up with \"55\" cause it has a rule for adding a string
to an int, namely to treat the int as a string. In most cases, if you
use a data type for which an operation or function is not defined
(like if you tried to divide \"this is\" by \"nonsense\", \"this is\"/\"nonsense\"),
the driver will barf and report an error to you.
",({"chapter 36","chapter thirty-six","36",}):"chapter 36 \"QCS: Modifying weapons\"
Remember that the QCS chapters are supposed to be read in
sequence. This is important because as we progress, I will not make
explanations about directives and concepts explained previously.
Weapons are different from rooms and NPC's in that they can
be handled, sold, thrown, etc. They are manipulable objects. As such,
we will see new directives:
%^GREEN%^create weapon hammer%^RESET%^
You may be familiar with this example from the example webpage.
Let's go ahead and plow through the commands:
%^GREEN%^modify weapon id hammer%^RESET%^
%^GREEN%^warhammer%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer name hammer%^RESET%^
%^GREEN%^modify hammer damagetype blunt%^RESET%^
%^GREEN%^modify hammer weapontype blunt%^RESET%^
%^GREEN%^modify hammer mass 700%^RESET%^
%^GREEN%^modify hammer hands 2%^RESET%^
%^GREEN%^modify hammer short a heavy war hammer%^RESET%^
%^GREEN%^modify hammer long This is an extremely large and heavy hammer designed to be wielded in both hands and used to hurt people very badly indeed.%^RESET%^
%^GREEN%^modify hammer adj%^RESET%^
%^GREEN%^large%^RESET%^
%^GREEN%^heavy%^RESET%^
%^GREEN%^war%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer basecost silver 750%^RESET%^
%^GREEN%^about hammer%^RESET%^
Like a room and unlike an NPC, you can also modify the SetItems on
manipulable objects like weapons, so you could do something like this:
%^GREEN%^modify hammer item%^RESET%^
%^GREEN%^shaft%^RESET%^
%^GREEN%^handle%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A thick, reinforced steel shaft with leather bands for a strong grip.%^RESET%^
%^GREEN%^exa shaft on hammer%^RESET%^
",({"chapter 11","chapter eleven","11",}):"chapter 11 \"Complex Data Types\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 3: Complex Data Types
3.1 Simple Data Types
In the textbook LPC Basics, you learned about the common, basic LPC
data types: int, string, object, void. Most important you learned that
many operations and functions behave differently based on the data type
of the variables upon which they are operating. Some operators and
functions will even give errors if you use them with the wrong data
types. For example, \"a\"+\"b\" is handled much differently than 1+1.
When you ass \"a\"+\"b\", you are adding \"b\" onto the end of \"a\" to get
\"ab\". On the other hand, when you add 1+1, you do not get 11, you get
2 as you would expect.
I refer to these data types as simple data types, because they atomic in
that they cannot be broken down into smaller component data types.
The object data type is a sort of exception, but you really cannot refer
individually to the components which make it up, so I refer to it as a
simple data type.
This chapter introduces the concept of the complex data type, a data type
which is made up of units of simple data types. LPC has two common
complex data types, both kinds of arrays. First, there is the traditional
array which stores values in consecutive elements accessed by a number
representing which element they are stored in. Second is an associative
array called a mapping. A mapping associates to values together to
allow a more natural access to data.
3.2 The Values NULL and 0
Before getting fully into arrays, there first should be a full understanding
of the concept of NULL versus the concept of 0. In LPC, a null value is
represented by the integer 0. Although the integer 0 and NULL are often
freely interchangeable, this interchangeability often leads to some great
confusion when you get into the realm of complex data types. You may
have even encountered such confusion while using strings.
0 represents a value which for integers means the value you add to
another value yet still retain the value added. This for any addition
operation on any data type, the ZERO value for that data type is the value
that you can add to any other value and get the original value. Thus: A
plus ZERO equals A where A is some value of a given data type and
ZERO is the ZERO value for that data type. This is not any sort of
official mathematical definition. There exists one, but I am not a
mathematician, so I have no idea what the term is. Thus for integers, 0
is the ZERO value since 1 + 0 equals 1.
NULL, on the other hand, is the absence of any value or meaning. The
LPC driver will interpret NULL as an integer 0 if it can make sense of it
in that context. In any context besides integer addition, A plus NULL
causes an error. NULL causes an error because adding valueless fields
in other data types to those data types makes no sense.
Looking at this from another point of view, we can get the ZERO value
for strings by knowing what added to \"a\" will give us \"a\" as a result.
The answer is not 0, but instead \"\". With integers, interchanging NULL
and 0 was acceptable since 0 represents no value with respect to the
integer data type. This interchangeability is not true for other data types,
since their ZERO values do not represent no value. Namely, \"\"
represents a string of no length and is very different from 0.
When you first declare any variable of any type, it has no value. Any
data type except integers therefore must be initialized somehow before
you perform any operation on it. Generally, initialization is done in the
create() function for global variables, or at the top of the local function
for local variables by assigning them some value, often the ZERO value
for that data type. For example, in the following code I want to build a
string with random words:
string build_nonsense() {
string str;
int i;
str = \"\"; /* Here str is initialized to the string
ZERO value */
for(i=0; i<6; i++) {
switch(random(3)+1) {
case 1: str += \"bing\"; break;
case 2: str += \"borg\"; break;
case 3: str += \"foo\"; break;
}
if(i==5) str += \".\\n\";
else str += \" \";
}
return capitalize(str);
}
If we had not initialized the variable str, an error would have resulted
from trying to add a string to a NULL value. Instead, this code first
initializes str to the ZERO value for strings, \"\". After that, it enters a
loop which makes 6 cycles, each time randomly adding one of three
possible words to the string. For all words except the last, an additional
blank character is added. For the last word, a period and a return
character are added. The function then exits the loop, capitalizes the
nonsense string, then exits.
3.3 Arrays in LPC
An array is a powerful complex data type of LPC which allows you to
access multiple values through a single variable. For instance,
Nightmare has an indefinite number of currencies in which players may
do business. Only five of those currencies, however, can be considered
hard currencies. A hard currency for the sake of this example is a
currency which is readily exchangeable for any other hard currency,
whereas a soft currency may only be bought, but not sold. In the bank,
there is a list of hard currencies to allow bank keepers to know which
currencies are in fact hard currencies. With simple data types, we would
have to perform the following nasty operation for every exchange
transaction:
int exchange(string str) {
string from, to;
int amt;
if(!str) return 0;
if(sscanf(str, \"%d %s for %s\", amt, from, to) != 3)
return 0;
if(from != \"platinum\" && from != \"gold\" && from !=
\"silver\" &&
from != \"electrum\" && from != \"copper\") {
notify_fail(\"We do not buy soft currencies!\\n\");
return 0;
}
...
}
With five hard currencies, we have a rather simple example. After all it
took only two lines of code to represent the if statement which filtered
out bad currencies. But what if you had to check against all the names
which cannot be used to make characters in the game? There might be
100 of those; would you want to write a 100 part if statement?
What if you wanted to add a currency to the list of hard currencies? That
means you would have to change every check in the game for hard
currencies to add one more part to the if clauses. Arrays allow you
simple access to groups of related data so that you do not have to deal
with each individual value every time you want to perform a group
operation.
As a constant, an array might look like this:
({ \"platinum\", \"gold\", \"silver\", \"electrum\", \"copper\" })
which is an array of type string. Individual data values in arrays are
called elements, or sometimes members. In code, just as constant
strings are represented by surrounding them with \"\", constant arrays are
represented by being surrounded by ({ }), with individual elements of
the array being separated by a ,.
You may have arrays of any LPC data type, simple or complex. Arrays
made up of mixes of values are called arrays of mixed type. In most
LPC drivers, you declare an array using a throw-back to C language
syntax for arrays. This syntax is often confusing for LPC coders
because the syntax has a meaning in C that simply does not translate into
LPC. Nevertheless, if we wanted an array of type string, we would
declare it in the following manner:
string *arr;
In other words, the data type of the elements it will contain followed by
a space and an asterisk. Remember, however, that this newly declared
string array has a NULL value in it at the time of declaration.
3.4 Using Arrays
You now should understand how to declare and recognize an array in
code. In order to understand how they work in code, let's review the
bank code, this time using arrays:
string *hard_currencies;
int exchange(string str) {
string from, to;
int amt;
if(!str) return 0;
if(sscanf(str, \"%d %s for %s\", amt, from, to) != 3)
return 0;
if(member_array(from, hard_currencies) == -1) {
notify_fail(\"We do not buy soft currencies!\\n\");
return 0;
}
...
}
This code assumes hard_currencies is a global variable and is initialized
in create() as:
hard_currencies = ({ \"platinum\", \"gold\", \"electrum\", \"silver\",
\"copper\" });
Ideally, you would have hard currencies as a #define in a header file for
all objects to use, but #define is a topic for a later chapter.
Once you know what the member_array() efun does, this method
certainly is much easier to read as well as is much more efficient and
easier to code. In fact, you can probably guess what the
member_array() efun does: It tells you if a given value is a member of
the array in question. Specifically here, we want to know if the currency
the player is trying to sell is an element in the hard_curencies array.
What might be confusing to you is, not only does member_array() tell us
if the value is an element in the array, but it in fact tells us which element
of the array the value is.
How does it tell you which element? It is easier to understand arrays if
you think of the array variable as holding a number. In the value above,
for the sake of argument, we will say that hard_currencies holds the
value 179000. This value tells the driver where to look for the array
hard_currencies represents. Thus, hard_currencies points to a place
where the array values may be found. When someone is talking about
the first element of the array, they want the element located at 179000.
When the object needs the value of the second element of the array, it
looks at 179000 + one value, then 179000 plus two values for the third,
and so on. We can therefore access individual elements of an array by
their index, which is the number of values beyond the starting point of
the array we need to look to find the value. For the array
hard_currencies array:
\"platinum\" has an index of 0.
\"gold\" has an index of 1.
\"electrum\" has an index of 2.
\"silver\" has an index of 3.
\"copper\" has an index of 4.
The efun member_array() thus returns the index of the element being
tested if it is in the array, or -1 if it is not in the array. In order to
reference an individual element in an array, you use its index number in
the following manner:
array_name[index_no]
Example:
hard_currencies[3]
where hard_currencies[3] would refer to \"silver\".
So, you now should now several ways in which arrays appear either as
a whole or as individual elements. As a whole, you refer to an array
variable by its name and an array constant by enclosing the array in ({ })
and separating elements by ,. Individually, you refer to array variables
by the array name followed by the element's index number enclosed in
[], and to array constants in the same way you would refer to simple data
types of the same type as the constant. Examples:
Whole arrays:
variable: arr
constant: ({ \"platinum\", \"gold\", \"electrum\", \"silver\", \"copper\" })
Individual members of arrays:
variable: arr[2]
constant: \"electrum\"
You can use these means of reference to do all the things you are used to
doing with other data types. You can assign values, use the values in
operations, pass the values as parameters to functions, and use the
values as return types. It is important to remember that when you are
treating an element alone as an individual, the individual element is not
itself an array (unless you are dealing with an array of arrays). In the
example above, the individual elements are strings. So that:
str = arr[3] + \" and \" + arr[1];
will create str to equal \"silver and gold\". Although this seems simple
enough, many people new to arrays start to run into trouble when trying
to add elements to an array. When you are treating an array as a whole
and you wish to add a new element to it, you must do it by adding
another array.
Note the following example:
string str1, str2;
string *arr;
str1 = \"hi\";
str2 = \"bye\";
/* str1 + str2 equals \"hibye\" */
arr = ({ str1 }) + ({ str2 });
/* arr is equal to ({ str1, str2 }) */
Before going any further, I have to note that this example gives an
extremely horrible way of building an array. You should set it: arr = ({
str1, str2 }). The point of the example, however, is that you must add
like types together. If you try adding an element to an array as the data
type it is, you will get an error. Instead you have to treat it as an array of
a single element.
3.5 Mappings
One of the major advances made in LPMuds since they were created is
the mapping data type. People alternately refer to them as associative
arrays. Practically speaking, a mapping allows you freedom from the
association of a numerical index to a value which arrays require.
Instead, mappings allow you to associate values with indices which
actually have meaning to you, much like a relational database.
In an array of 5 elements, you access those values solely by their integer
indices which cover the range 0 to 4. Imagine going back to the example
of money again. Players have money of different amounts and different
types. In the player object, you need a way to store the types of money
that exist as well as relate them to the amount of that currency type the
player has. The best way to do this with arrays would have been to
store an array of strings representing money types and an array of
integers representing values in the player object. This would result in
CPU-eating ugly code like this:
int query_money(string type) {
int i;
i = member_array(type, currencies);
if(i>-1 && i < sizeof(amounts)) /* sizeof efun
returns # of elements */
return amounts[i];
else return 0;
}
And that is a simple query function. Look at an add function:
void add_money(string type, int amt) {
string *tmp1;
int * tmp2;
int i, x, j, maxj;
i = member_array(type, currencies);
if(i >= sizeof(amounts)) /* corrupt data, we are in
a bad way */
return;
else if(i== -1) {
currencies += ({ type });
amounts += ({ amt });
return;
}
else {
amounts[i] += amt;
if(amounts[i] < 1) {
tmp1 = allocate(sizeof(currencies)-1);
tmp2 = allocate(sizeof(amounts)-1);
for(j=0, x =0, maxj=sizeof(tmp1); j < maxj;
j++) {
if(j==i) x = 1;
tmp1[j] = currencies[j+x];
tmp2[j] = amounts[j+x];
}
currencies = tmp1;
amounts = tmp2;
}
}
}
That is really some nasty code to perform the rather simple concept of
adding some money. First, we figure out if the player has any of that
kind of money, and if so, which element of the currencies array it is.
After that, we have to check to see that the integrity of the currency data
has been maintained. If the index of the type in the currencies array is
greater than the highest index of the amounts array, then we have a
problem since the indices are our only way of relating the two arrays.
Once we know our data is in tact, if the currency type is not currently
held by the player, we simply tack on the type as a new element to the
currencies array and the amount as a new element to the amounts array.
Finally, if it is a currency the player currently has, we just add the
amount to the corresponding index in the amounts array. If the money
gets below 1, meaning having no money of that type, we want to clear
the currency out of memory.
Subtracting an element from an array is no simple matter. Take, for
example, the result of the following:
string *arr;
arr = ({ \"a\", \"b\", \"a\" });
arr -= ({ arr[2] });
What do you think the final value of arr is? Well, it is:
({ \"b\", \"a\" })
Subtracting arr[2] from the original array does not remove the third
element from the array. Instead, it subtracts the value of the third
element of the array from the array. And array subtraction removes the
first instance of the value from the array. Since we do not want to be
forced on counting on the elements of the array as being unique, we are
forced to go through some somersaults to remove the correct element
from both arrays in order to maintain the correspondence of the indices
in the two arrays.
Mappings provide a better way. They allow you to directly associate the
money type with its value. Some people think of mappings as arrays
where you are not restricted to integers as indices. Truth is, mappings
are an entirely different concept in storing aggregate information. Arrays
force you to choose an index which is meaningful to the machine for
locating the appropriate data. The indices tell the machine how many
elements beyond the first value the value you desire can be found. With
mappings, you choose indices which are meaningful to you without
worrying about how that machine locates and stores it.
You may recognize mappings in the following forms:
constant values:
whole: ([ index:value, index:value ]) Ex: ([ \"gold\":10, \"silver\":20 ])
element: 10
variable values:
whole: map (where map is the name of a mapping variable)
element: map[\"gold\"]
So now my monetary functions would look like:
int query_money(string type) { return money[type]; }
void add_money(string type, int amt) {
if(!money[type]) money[type] = amt;
else money[type] += amt;
if(money[type] < 1)
map_delete(money, type); /* this is for
MudOS */
...OR...
money = m_delete(money, type) /* for some
LPMud 3.* varieties */
... OR...
m_delete(money, type); /* for other LPMud 3.*
varieties */
}
Please notice first that the efuns for clearing a mapping element from the
mapping vary from driver to driver. Check with your driver's
documentation for the exact name an syntax of the relevant efun.
As you can see immediately, you do not need to check the integrity of
your data since the values which interest you are inextricably bound to
one another in the mapping. Secondly, getting rid of useless values is a
simple efun call rather than a tricky, CPU-eating loop. Finally, the
query function is made up solely of a return instruction.
You must declare and initialize any mapping before using it.
Declarations look like:
mapping map;
Whereas common initializations look like:
map = ([]);
map = allocate_mapping(10) ...OR... map = m_allocate(10);
map = ([ \"gold\": 20, \"silver\": 15 ]);
As with other data types, there are rules defining how they work in
common operations like addition and subtraction:
([ \"gold\":20, \"silver\":30 ]) + ([ \"electrum\":5 ])
gives:
([\"gold\":20, \"silver\":30, \"electrum\":5])
Although my demonstration shows a continuity of order, there is in fact
no guarantee of the order in which elements of mappings will stored.
Equivalence tests among mappings are therefore not a good thing.
3.6 Summary
Mappings and arrays can be built as complex as you need them to be.
You can have an array of mappings of arrays. Such a thing would be
declared like this:
mapping *map_of_arrs;
which might look like:
({ ([ ind1: ({ valA1, valA2}), ind2: ({valB1, valB2}) ]), ([ indX:
({valX1,valX2}) ]) })
Mappings may use any data type as an index, including objects.
Mapping indices are often referred to as keys as well, a term from
databases. Always keep in mind that with any non-integer data type,
you must first initialize a variable before making use of it in common
operations such as addition and subtraction. In spite of the ease and
dynamics added to LPC coding by mappings and arrays, errors caused
by failing to initialize their values can be the most maddening experience
for people new to these data types. I would venture that a very high
percentage of all errors people experimenting with mappings and arrays
for the first time encounter are one of three error messages:
Indexing on illegal type.
Illegal index.
Bad argument 1 to (+ += - -=) /* insert your favourite operator */
Error messages 1 and 3 are darn near almost always caused by a failure
to initialize the array or mapping in question. Error message 2 is caused
generally when you are trying to use an index in an initialized array
which does not exist. Also, for arrays, often people new to arrays will
get error message 3 because they try to add a single element to an array
by adding the initial array to the single element value instead of adding
an array of the single element to the initial array. Remember, add only
arrays to arrays.
At this point, you should feel comfortable enough with mappings and
arrays to play with them. Expect to encounter the above error messages
a lot when first playing with these. The key to success with mappings is
in debugging all of these errors and seeing exactly what causes wholes
in your programming which allow you to try to work with uninitialized
mappings and arrays. Finally, go back through the basic room code and
look at things like the SetExits() (or the equivalent on your mudlib)
function. Chances are it makes use of mappings. In some instances, it
will use arrays as well for compatibility with mudlib.n.
Copyright (c) George Reese 1993
",({"chapter 27","chapter twenty-seven","27",}):"chapter 27 \"Towns\"
Building Towns
The Nightmare IV LPC Library
written by Descartes of Borg 950429
The Nightmare IV LPC Library contains support for towns, which is in
fact very minimal from the mudlib level. If, however, you wish to
structure your MUD to be centered around the concept as Nightmare LPMud
is, then you need to understand how to build a town. This document
describes the building of towns using the Nightmare IV LPC Library.
I. What Is a Town?
A town is simply a collection of rooms which have the same value set
for Town. If done poorly, this is all it is. If done right, however,
a town becomes the center of the games' social structure. If you
decide to build a town in your area, the first thing you need to do is
isolate it. All towns should be surrounded by vast, vast areas of
wilderness of some sort. This may mean desert, forest, jungle, or
whatever. You may or may not want to have a road which links it to
the rest of civilization.
Rooms are considered \"wilderness\" by default. That is, if you never
set the town in them, they are considered wilderness. To make a room
part of a town, you need to call SetTown() from create() of the room:
SetTown(\"Praxis\");
Capitalize your town name properly.
Next you need to decide how many estates may be built in the room.
Ideally, towns are expanding and changing things. Upper level players
have the ability to build estates in their home towns. Of course, ten
estates in one room is crowded. Generally you should limit the number
of estates to what would logically fit in a given room. For example,
if you are on a road at the edge of town with nothing about, then
allowing two estates makes sense. On the other hand, in the middle of
an intersection of two roads, there is hardly any room for an estate
to be built. To allow estates to be built in a room:
SetProperty(\"estates\", 2);
This allows two estates to be built off of this room.
As stated above, towns are expanding. This is why they should be
situated far apart. Too close together it is hard for them to expand
without changing the overall map of the game. Therefore, when your
town has gotten as full as can be handled, then you simply move to
outlying rooms and make them part of the town by setting their town.
In addition, give them the capacity for estates. Do not forget to
change room descriptions and allow for needed roads!
II. What do I put in towns?
The first section described what is minimally needed for a town from a
code point of view. This section describes what sorts of things you
should put in your towns. Most are optional, however, you do need to
add something called an adventurer's hall. An adventurer's hall is
the default start room for the town for anyone who chooses the town as
their home town. In order to make it their home town, they go to the
adventurer's hall and pay a fee (generally determined by approval) to
move to this town. Until that person builds an estate in the town,
the adventurer's hall is their default starting point.
Beyond that, the only other thing required is a real estate office for
selling estates. This is an inheritable from /lib/sales.c
(LIB_SALES). Approval determines what your local land value is, and
you fill in the descriptions. For information on advanced coding of
sales offices, see the document /doc/build/Sales.
Nothing else is required. Of course, your land value (the amount
people pay to live and build in your town) is determined by the sorts
of services your town offers. No town should offer all services. And
certainly, the services your town offers should reflect the nature of
the region in which you are building. Are you an isolated, small
town? Then few services will be available. Are you a central, large
town? Then a majority of services should be available.
Services include:
shops of different types
bars and pubs
restaurants
libraries for learning languages
class halls
town council rooms
This list will probably expand over time, but it provides a good
starting point for common services.
Descartes of Borg
borg@imaginary.com
",({"chapter 21","chapter twenty-one","21",}):"chapter 21 \"Meals\"
Building Food and Drink Objects
The Nightmare IV LPC Library
written by Descartes of Borg 950603
This document details the creation of food and drinks using the
Nightmare LPC Library. The creation of barkeeper objects requires you
to be able to build these objects, so make sure you understand what is
going on in here before moving on to barkeepers.
To create food or drink, you inherit from the standard meal object
/lib/meal.c For example:
#include <lib.h>
inherit LIB_MEAL;
You have access to the same functions you have in generic items when
you build food and drinks. In particular, you should be sure to call
the following:
SetKeyName()
SetId()
SetShort()
SetLong()
SetMass()
Note that SetValue() does NOTHING for food and drinks. Value is
automatically determined by the strength of the item.
The following function calls are specific to \"meal\" objects:
int SetMealType(int types);
int SetStrength(int strength);
mixed *SetMealMessages(function f);
OR
mixed *SetMealmessages(string mymsg, string othermsg);
string SetEmptyName(string str);
string SetEmptyShort(string str);
string SetEmptyLong(string str);
string SetEmptyItem(string str);
You must call SetMealType(), SetStrength(), and SetMealMessages().
If you call SetEmptyItem(), you do not need to call the functions
SetEmptyName(), SetEmptyShort(), SetEmptyLong(). On the other hand,
if you do not call SetEmptyItem(), you do need to set the other three.
*****
int SetMealType(int types)
*****
Example: SetMealType(MEAL_FOOD);
For meal objects, you must do:
#include <meal_types.h>
This includes all od the definitions for the meal types in
/include/meal_types.h into your food or drink. You need these
definitions when setting what type of meal object this is. The types
are:
MEAL_FOOD
MEAL_DRINK
MEAL_CAFFEINE
MEAL_ALCOHOL
MEAL_POISON
In general, almost anything you create will be at least either
MEAL_FOOD or MEAL_DRINK. You can add onto it using the | operator.
For example, to make an alcoholic drink:
SetMealType(MEAL_DRINK | MEAL_ALCOHOL);
This makes something a drink and an alcoholic drink. You want to
stick poison in it?
SetMealType(MEAL_DRINK | MEAL_ALCOHOL | MEAL_POISON);
*****
int SetStrength(int x)
*****
Example: SetStrength(20);
This sets how strong your food or drink is. It affects things like
which people can drink or eat it and how much the drink or food costs.
Refer to balance documents to see what is good.
*****
varargs mixed *SetMealMessages(function|string, string)
*****
Examples:
SetMealMessages((: call_other(find_object(\"/some/object\"),\"drink\") :));
SetMealmessages(\"You drink your beer.\", \"$N drinks $P beer.\");
You can pass a single argument, which is a function to be called.
This function will be called after the person has drank or eaten the
meal. It gives you a chance to do some bizarre messaging and such.
If you pass two strings, the first string is used as a message to send
to the player doing the drinking, and the second is what everyone else
sees. To make the message versatile, you can put in the following
place holders:
$N the name of the drinker/eater
$P his/her/its
For example:
$N drinks $P beer.
might resolve to:
Descartes drinks his beer.
*****
string SetEmptyName(string str)
*****
Example: SetEmptyName(\"bottle\");
Sets an id from the empty container of drinks. This need not be set
for food.
*****
string SetEmptyShort(string str)
*****
Example: SetEmptyShort(\"an empty bottle\")
Sets what the short description of the empty container is for anything
that is of type MEAL_DRINK.
*****
string SetEmptyLong(string str)
*****
Example: SetEmptyLong(\"A brown bottle that used to contain beer.\");
Sets the long description for the empty container for drink objects.
*****
string SetEmptyItem(string str)
*****
Example: SetEmptyItem(\"/domains/Praxis/etc/empty_bottle\")
Instead of cloning a generic empty object and setting the other empty
functions, you can create a special empty container which gets given
to the player after they drink a drink object. Not relevant to food.
",({"chapter 23","chapter twenty-three","23",}):"chapter 23 \"Properties\"
Supported Properties
The Nightmare IV LPC Library
written by Descartes of Borg 950429
The Nightmare IV LPC Library allows creators to set dynamic variables in
objects which do not get saved when the object saves. The variables are
called properties. A property is an attribute of an object which is
considered fleeting. This document serves to list the properties
commonly used and their purpose. It is by no means complete, as the
point of having properties is to allow creators to build their own on the
fly.
Note: All properties are 0 by default unless otherwise stated.
Property: light
Values: integer between -6 and 6
Light is a value generally between -6 and 6 which, for rooms,
determines how much light is naturally available in a room in daytime.
For other objects, it determines the degree to which the object is
able to modify the amount of light that exists in the room. If the
room is indoors, the light does not change based on the time of day.
Property: no attack
Values: 1 to prevent attacks, 0 to allow them
Things cannot begin combat from inside a room with this property.
Property: no bump
Values: 1 to prevent bumping, 0 to allow it
If a room, then nothing can be bumped from this room. If a living
thing, then it cannot be bumped.
Property: no steal
Values: 1 to prevent stealing, 0 to allow it
This prevents stealing inside a room with this property.
Property: no magic
Values: 1 to prevent magic, 0 to allow it
This prevents any magic from being used inside the room if set.
Property: no paralyze
Values: 1 prevents paralysis from occurring in a room, 0 allows it
Stops any sort of thing which might cause paralysis from occurring in
a room.
Property: no teleport
Values: 1 if teleporting is prohibited, 0 if allowed
Prevents people from teleporting to or from the room.
Property: no clear
Values: 1 to prevent clearing, 0 to allow it
If set this prevents an avatar from clearing a wilderness room in
order to build a town. Not relevant to rooms in towns.
Property: estates
Values: any non-negative number
Sets the number of estates which can be built in an area. No estates
may be built outside of towns.
Property: magic item
Values: an array of strings describing the magic contained in an object
Allows you to mark specific objects as magic. For example, if a sword
has a magical lighting ability, you might do:
SetProperty(\"magic item\", ({ \"light\" }));
Property: lockpicking tool
Values: any integer marking how well lockpicking is enhanced
When picking a lock, the value of this property is calculated for each
object and added to the overall chance to pick the lock.
Property: keep
Values: the name of whomever the object is kept for
While set, this object may only be picked up by the person whose name
matches the value of this property. If 0, anyone can pick it up
assuming it is normally gettable.
Property: magic hold
Value: any integer
Is subtracted from the chance of success of anyone trying to pick a
lock.
Property: enchantment
Value: any integer
Enchants any object to boost (or degrade) its performance of its
natural functions.
Property: login
Value: a string representing a file name
Sets which room a player should login to at next login if they quit
from the room that has this property. For example, if you have a
treasure room that is protected, and therefore you do not want people
logging into it, you can call:
SetProperty(\"login\", \"/file/name/outside/this/room\");
to have the players login to the room outside.
",({"chapter 29","chapter twenty-nine","29",}):"chapter 29 \"Weapons\"
Building Weapons
The Nightmare IV LPC Library
written by Descartes of Borg 950429
All items in the Nightmare LPC Library (descendants of /lib/item.c)
are weapons. A player can, for example, use a can of spam as a
weapon. However, they are set up as extremely pathetic weapons. This
document describes in detail how to make an object into a real weapon.
I. Basic Stuff
The basic weapon is exactly the same as the basic item. You can do
anything to it that can be done to other items. For details on items,
see /doc/build/Items. The simple weapon should look like this:
#include <lib.h>
#include <damage_types.h>
#include <vendor_types.h>
inherit LIB_ITEM;
static void create() {
item::create();
SetKeyName(\"short sword\");
SetId( ({ \"sword\", \"short sword\", \"a short sword\" }) );
SetAdjectives( ({ \"short\" }) );
SetShort(\"a short sword\");
SetLong(\"A rusty short sword with specs of blood on it.\");
SetVendorType(VT_WEAPON);
SetDamagePoints(1500);
SetClass(12);
SetValue(150);
SetMass(100);
SetWeaponType(\"blade\");
SetDamageType(BLADE);
}
The last part is what differs from regular items. Note the functions:
SetVendorType()
SetClass()
SetWeaponType()
SetDamageType()
The available vendor types can be found by reading the file
/include/vendor_types.h. Similarly, damage types may be found by
reading /include/damage_types.h. The vendor type states what sort of
stores can carry this item. VT_WEAPON should almost ALWAYS be the
vendor type you give for weapons.
SetClass()
The class is the basic weapon strength. It is how much damage gets
done without any modification. This number ranges between 1 and 100,
where 1 is a pathetic weapon (the class for basic items) and 100 is
probably bordering on illegal.
SetWeaponType()
This sets what sort of attack skill the player needs to use this
weapon. The weapon types are:
blade
knife
blunt
projectile
SetDamageType()
Damage types, again, are found in /include/damage_types.h. This sets
what type of damage is done by this weapon to its victims.
II. Wield Functions
mixed SetWield(string | function)
Examples:
SetWield(\"The short sword feels dull as you wield it.\");
SetWield( (: WieldMe :) );
If you pass a string to SetWield(), then the player sees that string
whenever they wield the weapon. If, on the other hand, you pass a
function, then that function will get called just before the weapon is
wielded when the player issues the wield command. The function you
pass should be written in the form of:
int WieldMe();
If the function returns 1, then the player can wield the weapon. If
it returns 0, then the player cannot wield the weapon. Note that if
you have a wield function, you are responsible for all messaging to
the player to let the player know that they can/cannot wield the
weapon. Example:
int WieldMe() {
if( (int)this_player()->ClassMember(\"fighter\") ) {
write(\"The short sword gives you power as you wield it.\");
say((string)this_player()->GetName() + \" wields a short sword.\");
return 1;
}
else {
write(\"You are not worthy of this short sword.\");
return 0;
}
}
III. Modifying Stats and Skills
A common thing people like to do with weapons is temporarily modify a
player's skills. This is done by making use of the function
AddStatBonus() and AddSkillBonus(). Most of the time this is done
through a SetWield() function.
void AddStatBonus(string stat, function f);
void AddSkillBonus(string stat, function f);
Examples:
this_player()->AddStatBonus(\"wisdom\", (: CheckStat :));
this_player()->AddSkillBonus(\"blade attack\", (: CheckSkill :));
The functions then have the format:
int CheckWhatever(string stat_or_skill);
NOTE: You should always check whether the bonus is still in effect.
For example, make sure the weapon is still wielded if it results from
wielding the weapon. For example:
#include <lib.h>
inherit LIB_ITEM;
int DoWield()
int CheckBlade(string skill);
static void create() {
...
SetWield((: DoWield :));
...
}
int DoWield() {
this_player()->AddSkillBonus(\"blade attack\", (: CheckBlade :) );
write(\"You wield the short sword.\");
say((string)this_player()->GetName() + \" wields a short sword.\");
return 1;
}
int CheckBlade(string skill) {
if( !GetWorn() ) {
previous_object()->RemoveSkillBonus(\"blade\", this_object());
return 0;
}
else return 5;
}
In other words, this weapon will give its wielder a blade attack bonus
of 5. Note that you must use previous_object() in CheckBlade() and
NOT this_player() because there is no way of knowing who this_player()
is at the time. You do know, however, that the object calling
CheckBlade() is always the player for whom the skill bonus is made.
Always remember to remove bonuses.
IV. Modifying Hits
The Nightmare IV LPC Library uses an event driven combat system. With
respect to weapons, a round of combat is broken down into the
following events:
1. The person wielding the weapon uses it.
2. If they cannot hit a motionless target, the round ends.
3. If the target dodges the attack, the round ends.
4. eventStrike() is called in the weapon to determine how much damage
the weapon can do.
5. eventReceiveDamage() is called in the target object. This in turn:
a. Calls eventReceiveDamage() in all armour objects, which each:
i. Calls eventReceiveDamage() in the weapon
ii. The weapon wears down a bit
b. The armour wears down a bit
c. The amount of armour damage absorbed is returned
d. The target objects loses health points.
f. The amount of damage done is returned.
6. Skill and stat points are added.
Note the two important functions which get called in weapon.c:
int eventStrike(object ob);
int eventReceiveDamage(int type, int amount, int unused, mixed limbs);
By default, eventStrike() returns the value of GetClass(). However,
you can modify this value by overriding the eventStrike(). For
example:
int eventStrike(object target) {
if( (string)target->GetRace() != \"orc\" ) return item::eventStrike(target);
message(\"environment\", \"The orc slayer makes a nasty sound!\",
environment(target));
return item::eventStrike(target) + random(10);
}
NOTE: You should always use item::eventStrike() rather than hard coded
values since weapon class deteriorates over time.
In this example, a random(10) points of extra damage gets done to
orcs. This would be the orc slayer weapon of ancient fame.
For those familiar with hit functions in the old Nightmare Mudlibs,
this would be roughly equivalent to that.
Another place where you can make things happen is in
eventDeteriorate() which gets called by eventReceieveDamage(). This is
where a weapon wears down from the shock which armour has absorbed
from it. For weapons, there is not much which can be done here, but
this document points it out for the creative who feel they might be able to do
somthing with it.
Descartes of Borg
borg@imaginary.com
",({"chapter 8","chapter eight","8",}):"chapter 8 \"LPC Basics\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 12 july 1993
CHAPTER 8: The data type \"object\"
8.1 Review
You should now be able to do anything so long as you stick to calling
functions within your own object. You should also know, that at the
bare minimum you can get the create() (or reset()) function in your object
called to start just by loading it into memory, and that your reset()
function will be called every now and then so that you may write the
code necessary to refresh your room. Note that neither of these
functions MUST be in your object. The driver checks to see if the
function exists in your object first. If it does not, then it does not
bother. You are also acquainted with the data types void, int, and string.
7.2 Objects as data types
In this chapter you will be acquainted with a more complex data type,
object. An object variable points to a real object loaded into the
driver's memory. You declare it in the same manner as other data types:
object ob;
It differs in that you cannot use +, -, +=, -=, *, or / (what would it
mean to divide a monster by another monster?). And since efuns like
say() and write() only want strings or ints, you cannot write() or
say() them (again, what would it mean to say a monster?).
But you can use them with some other of the most important efuns on any
LPMud.
8.3 The efun: this_object()
This is an efun which returns an object in which the function being executed
exists. In other words, in a file, this_object() refers to the object your
file is in whether the file gets cloned itself or inherted by another file.
It is often useful when you are writing a file which is getting inherited
by another file. Say you are writing your own living.c which gets
inherited by user.c and monster.c, but never used alone. You want to log
the function set_level() it is a player's level being set (but you do not
care if it is a monster.
You might do this:
void set_level(int x) {
if(this_object()->is_player()) log_file(\"levels\", \"foo\\n\");
level = x;
}
Since is_player() is not defined in living.c or anything it inherits,
just saying if(is_player()) will result in an error since the driver
does not find that function in your file or anything it inherits.
this_object() allows you to access functions which may or may not be
present in any final products because your file is inherited by others
without resulting in an error.
8.4 Calling functions in other objects
This of course introduces us to the most important characteristic of
the object data type. It allows us to access functions in other objects.
In previous examples you have been able to find out about a player's level,
reduce the money they have, and how much hp they have.
Calls to functions in other objects may be done in two ways:
object->function(parameters)
call_other(object, \"function\", parameters);
example:
this_player()->add_money(\"silver\", -5);
call_other(this_player(), \"add_money\", \"silver\", -5);
In some (very loose sense), the game is just a chain reaction of function
calls initiated by player commands. When a player initiates a chain of
function calls, that player is the object which is returned by
the efun this_player(). So, since this_player() can change depending
on who initiated the sequence of events, you want to be very careful
as to where you place calls to functions in this_player(). The most common
place you do this is through the last important lfun (we have mentioned
create() and reset()) init().
8.5 The lfun: init()
Any time a living thing encounters an object (enters a new room, or enters
the same room as a certain other object), init() is called in all of
the objects the living being newly encounters. It is at this point
that you can add commands the player can issue in order to act.
Here is a sample init() function in a flower.
void init() {
::init();
add_action(\"smell_flower\", \"smell\");
}
Ito smell_flower(). So you should have smell_flower() look like this:
1 int smell_flower(string str); /* action functions are type int */
2
3 int smell_flower(string str) {
4 if(str != \"flower\") return 0; /* it is not the flower being smelled */
5 write(\"You sniff the flower.\\n\");
6 say((string)this_player()->GetName()+\" smells the flower.\\n\");
7 this_player()->add_hp(random(5));
8 return 1;
9 }
In line 1, we have our function declared.
In line 3, smell_flower() begins. str becomes whatever comes after the
players command (not including the first white space).
In line 4, it checks to see if the player had typed \"smell flower\". If
the player had typed \"smell cheese\", then str would be \"cheese\". If
it is not in fact \"flower\" which is being smelled, then 0 is returned,
letting the driver know that this was not the function which should
have been called. If in fact the player had a piece of cheese as well
which had a smell command to it, the driver would then call the function
for smelling in that object. The driver will keep calling all functions
tied to smell commands until one of them returns 1. If they all return
0, then the player sees \"What?\"
In line 5, the efun write() is called. write() prints the string which
is passed to it to this_player(). So whoever typed the command here
sees \"You sniff the flower.\"
In line 6, the efun say() is called. say() prints the string which is
doing the sniffing, we have to call the GetName() function
in this_player(). That way if the player is invis, it will say
\"Someone\" (or something like that), and it will also be properly
capitalized.
In line 7, we call the add_hp() function in the this_player() object,
since we want to do a little healing for the sniff (Note: do not
code this object on your mud, whoever balances your mud will shoot you).
In line 8, we return control of the game to the driver, returning 1 to
let it know that this was in fact the right function to call.
8.6 Adding objects to your rooms
And now, using the data type object, you can add monsters to your rooms:
void create() {
::create();
SetProperty(\"light\", 3);
set(\"short\", \"Krasna Square\");
set(\"long\", \"Welcome to the Central Square of the town of Praxis.\\n\");
SetExits( ({ \"/domains/standard/hall\" }), ({ \"east\" }) );
}
void reset() {
object ob;
::reset();
if(present(\"guard\")) return; /* Do not want to add a guard if */
ob = new(\"/std/monster\"); /* one is already here */
ob->SetKeyName(\"guard\");
ob->set(\"id\", ({ \"guard\", \"town guard\" }) );
ob->set(\"short\", \"Town guard\");
ob->set(\"long\", \"He guards Praxis from nothingness.\\n\");
ob->SetGender(\"male\");
ob->set_race(\"human\");
ob->set_level(10);
ob->set_alignment(200);
ob->set_humanoid();
ob->set_hp(150);
ob->set_wielding_limbs( ({ \"right hand\", \"left hand\" }) );
ob->eventMove(this_object());
}
Now, this will be wildly different on most muds. Some, as noted before,
in that object so you have a uniquely configured monster object. The
last act in native muds is to call eventMove() in the monster object to move
it to this room (this_object()). In compat muds, you call the efun
move_object() which takes two parameters, the object to be moved, and the
object into which it is being moved.
// CORRECTION: move_object() does not take two arguments in recent
// versions of MudOS. -Crat 24Feb2007
8.7 Chapter summary
At this point, you now have enough knowledge to code some really nice
stuff. Of course, as I have been stressing all along, you really need
to read the documents on building for your mud, as they detail which
functions exist in which types of objects for you to call. No matter
what your knowledge of the mudlib is, you have enough know-how to
give a player extra things to do like sniffing flowers or glue or whatever.
At this point you should get busy coding stuff. But the moment things
even look to become tedious, that means it is time for you to move to
the next level and do more. Right now code yourself a small area.
Make extensive use of the special functions coded in your mud's
room.c (search the docs for obscure ones no one else seems to use).
Add lots o' neat actions. Create weapons which have magic powers which
gradually fade away. All of this you should be able to do now. Once
this becomes routine for you, it will be time to move on to intermediate
stuff. Note that few people actually get to the intermediate stuff.
If you have played at all, you notice there are few areas on the mud
which do what I just told you you should be able to do. It is not
because it is hard, but because there is a lot of arrogance out there
on the part of people who have gotten beyond this point, and very little
communicating of that knowledge. The trick is to push yourself and
think of something you want to do that is impossible. If you ask someone
in the know how to do X, and they say that is impossible, find out
youself how to code it by experimenting.
George Reese
Descartes of Borg
12 july 1993
borg@hebron.connected.com
Descartes@Nightmare (intermud)
Descartes@Igor (not intermud)
",({"chapter 11","chapter eleven","11",}):"chapter 11 \"Complex Data Types\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 3: Complex Data Types
3.1 Simple Data Types
In the textbook LPC Basics, you learned about the common, basic LPC
data types: int, string, object, void. Most important you learned that
many operations and functions behave differently based on the data type
of the variables upon which they are operating. Some operators and
functions will even give errors if you use them with the wrong data
types. For example, \"a\"+\"b\" is handled much differently than 1+1.
When you ass \"a\"+\"b\", you are adding \"b\" onto the end of \"a\" to get
\"ab\". On the other hand, when you add 1+1, you do not get 11, you get
2 as you would expect.
I refer to these data types as simple data types, because they atomic in
that they cannot be broken down into smaller component data types.
The object data type is a sort of exception, but you really cannot refer
individually to the components which make it up, so I refer to it as a
simple data type.
This chapter introduces the concept of the complex data type, a data type
which is made up of units of simple data types. LPC has two common
complex data types, both kinds of arrays. First, there is the traditional
array which stores values in consecutive elements accessed by a number
representing which element they are stored in. Second is an associative
array called a mapping. A mapping associates to values together to
allow a more natural access to data.
3.2 The Values NULL and 0
Before getting fully into arrays, there first should be a full understanding
of the concept of NULL versus the concept of 0. In LPC, a null value is
represented by the integer 0. Although the integer 0 and NULL are often
freely interchangeable, this interchangeability often leads to some great
confusion when you get into the realm of complex data types. You may
have even encountered such confusion while using strings.
0 represents a value which for integers means the value you add to
another value yet still retain the value added. This for any addition
operation on any data type, the ZERO value for that data type is the value
that you can add to any other value and get the original value. Thus: A
plus ZERO equals A where A is some value of a given data type and
ZERO is the ZERO value for that data type. This is not any sort of
official mathematical definition. There exists one, but I am not a
mathematician, so I have no idea what the term is. Thus for integers, 0
is the ZERO value since 1 + 0 equals 1.
NULL, on the other hand, is the absence of any value or meaning. The
LPC driver will interpret NULL as an integer 0 if it can make sense of it
in that context. In any context besides integer addition, A plus NULL
causes an error. NULL causes an error because adding valueless fields
in other data types to those data types makes no sense.
Looking at this from another point of view, we can get the ZERO value
for strings by knowing what added to \"a\" will give us \"a\" as a result.
The answer is not 0, but instead \"\". With integers, interchanging NULL
and 0 was acceptable since 0 represents no value with respect to the
integer data type. This interchangeability is not true for other data types,
since their ZERO values do not represent no value. Namely, \"\"
represents a string of no length and is very different from 0.
When you first declare any variable of any type, it has no value. Any
data type except integers therefore must be initialized somehow before
you perform any operation on it. Generally, initialization is done in the
create() function for global variables, or at the top of the local function
for local variables by assigning them some value, often the ZERO value
for that data type. For example, in the following code I want to build a
string with random words:
string build_nonsense() {
string str;
int i;
str = \"\"; /* Here str is initialized to the string
ZERO value */
for(i=0; i<6; i++) {
switch(random(3)+1) {
case 1: str += \"bing\"; break;
case 2: str += \"borg\"; break;
case 3: str += \"foo\"; break;
}
if(i==5) str += \".\\n\";
else str += \" \";
}
return capitalize(str);
}
If we had not initialized the variable str, an error would have resulted
from trying to add a string to a NULL value. Instead, this code first
initializes str to the ZERO value for strings, \"\". After that, it enters a
loop which makes 6 cycles, each time randomly adding one of three
possible words to the string. For all words except the last, an additional
blank character is added. For the last word, a period and a return
character are added. The function then exits the loop, capitalizes the
nonsense string, then exits.
3.3 Arrays in LPC
An array is a powerful complex data type of LPC which allows you to
access multiple values through a single variable. For instance,
Nightmare has an indefinite number of currencies in which players may
do business. Only five of those currencies, however, can be considered
hard currencies. A hard currency for the sake of this example is a
currency which is readily exchangeable for any other hard currency,
whereas a soft currency may only be bought, but not sold. In the bank,
there is a list of hard currencies to allow bank keepers to know which
currencies are in fact hard currencies. With simple data types, we would
have to perform the following nasty operation for every exchange
transaction:
int exchange(string str) {
string from, to;
int amt;
if(!str) return 0;
if(sscanf(str, \"%d %s for %s\", amt, from, to) != 3)
return 0;
if(from != \"platinum\" && from != \"gold\" && from !=
\"silver\" &&
from != \"electrum\" && from != \"copper\") {
notify_fail(\"We do not buy soft currencies!\\n\");
return 0;
}
...
}
With five hard currencies, we have a rather simple example. After all it
took only two lines of code to represent the if statement which filtered
out bad currencies. But what if you had to check against all the names
which cannot be used to make characters in the game? There might be
100 of those; would you want to write a 100 part if statement?
What if you wanted to add a currency to the list of hard currencies? That
means you would have to change every check in the game for hard
currencies to add one more part to the if clauses. Arrays allow you
simple access to groups of related data so that you do not have to deal
with each individual value every time you want to perform a group
operation.
As a constant, an array might look like this:
({ \"platinum\", \"gold\", \"silver\", \"electrum\", \"copper\" })
which is an array of type string. Individual data values in arrays are
called elements, or sometimes members. In code, just as constant
strings are represented by surrounding them with \"\", constant arrays are
represented by being surrounded by ({ }), with individual elements of
the array being separated by a ,.
You may have arrays of any LPC data type, simple or complex. Arrays
made up of mixes of values are called arrays of mixed type. In most
LPC drivers, you declare an array using a throw-back to C language
syntax for arrays. This syntax is often confusing for LPC coders
because the syntax has a meaning in C that simply does not translate into
LPC. Nevertheless, if we wanted an array of type string, we would
declare it in the following manner:
string *arr;
In other words, the data type of the elements it will contain followed by
a space and an asterisk. Remember, however, that this newly declared
string array has a NULL value in it at the time of declaration.
3.4 Using Arrays
You now should understand how to declare and recognize an array in
code. In order to understand how they work in code, let's review the
bank code, this time using arrays:
string *hard_currencies;
int exchange(string str) {
string from, to;
int amt;
if(!str) return 0;
if(sscanf(str, \"%d %s for %s\", amt, from, to) != 3)
return 0;
if(member_array(from, hard_currencies) == -1) {
notify_fail(\"We do not buy soft currencies!\\n\");
return 0;
}
...
}
This code assumes hard_currencies is a global variable and is initialized
in create() as:
hard_currencies = ({ \"platinum\", \"gold\", \"electrum\", \"silver\",
\"copper\" });
Ideally, you would have hard currencies as a #define in a header file for
all objects to use, but #define is a topic for a later chapter.
Once you know what the member_array() efun does, this method
certainly is much easier to read as well as is much more efficient and
easier to code. In fact, you can probably guess what the
member_array() efun does: It tells you if a given value is a member of
the array in question. Specifically here, we want to know if the currency
the player is trying to sell is an element in the hard_curencies array.
What might be confusing to you is, not only does member_array() tell us
if the value is an element in the array, but it in fact tells us which element
of the array the value is.
How does it tell you which element? It is easier to understand arrays if
you think of the array variable as holding a number. In the value above,
for the sake of argument, we will say that hard_currencies holds the
value 179000. This value tells the driver where to look for the array
hard_currencies represents. Thus, hard_currencies points to a place
where the array values may be found. When someone is talking about
the first element of the array, they want the element located at 179000.
When the object needs the value of the second element of the array, it
looks at 179000 + one value, then 179000 plus two values for the third,
and so on. We can therefore access individual elements of an array by
their index, which is the number of values beyond the starting point of
the array we need to look to find the value. For the array
hard_currencies array:
\"platinum\" has an index of 0.
\"gold\" has an index of 1.
\"electrum\" has an index of 2.
\"silver\" has an index of 3.
\"copper\" has an index of 4.
The efun member_array() thus returns the index of the element being
tested if it is in the array, or -1 if it is not in the array. In order to
reference an individual element in an array, you use its index number in
the following manner:
array_name[index_no]
Example:
hard_currencies[3]
where hard_currencies[3] would refer to \"silver\".
So, you now should now several ways in which arrays appear either as
a whole or as individual elements. As a whole, you refer to an array
variable by its name and an array constant by enclosing the array in ({ })
and separating elements by ,. Individually, you refer to array variables
by the array name followed by the element's index number enclosed in
[], and to array constants in the same way you would refer to simple data
types of the same type as the constant. Examples:
Whole arrays:
variable: arr
constant: ({ \"platinum\", \"gold\", \"electrum\", \"silver\", \"copper\" })
Individual members of arrays:
variable: arr[2]
constant: \"electrum\"
You can use these means of reference to do all the things you are used to
doing with other data types. You can assign values, use the values in
operations, pass the values as parameters to functions, and use the
values as return types. It is important to remember that when you are
treating an element alone as an individual, the individual element is not
itself an array (unless you are dealing with an array of arrays). In the
example above, the individual elements are strings. So that:
str = arr[3] + \" and \" + arr[1];
will create str to equal \"silver and gold\". Although this seems simple
enough, many people new to arrays start to run into trouble when trying
to add elements to an array. When you are treating an array as a whole
and you wish to add a new element to it, you must do it by adding
another array.
Note the following example:
string str1, str2;
string *arr;
str1 = \"hi\";
str2 = \"bye\";
/* str1 + str2 equals \"hibye\" */
arr = ({ str1 }) + ({ str2 });
/* arr is equal to ({ str1, str2 }) */
Before going any further, I have to note that this example gives an
extremely horrible way of building an array. You should set it: arr = ({
str1, str2 }). The point of the example, however, is that you must add
like types together. If you try adding an element to an array as the data
type it is, you will get an error. Instead you have to treat it as an array of
a single element.
3.5 Mappings
One of the major advances made in LPMuds since they were created is
the mapping data type. People alternately refer to them as associative
arrays. Practically speaking, a mapping allows you freedom from the
association of a numerical index to a value which arrays require.
Instead, mappings allow you to associate values with indices which
actually have meaning to you, much like a relational database.
In an array of 5 elements, you access those values solely by their integer
indices which cover the range 0 to 4. Imagine going back to the example
of money again. Players have money of different amounts and different
types. In the player object, you need a way to store the types of money
that exist as well as relate them to the amount of that currency type the
player has. The best way to do this with arrays would have been to
store an array of strings representing money types and an array of
integers representing values in the player object. This would result in
CPU-eating ugly code like this:
int query_money(string type) {
int i;
i = member_array(type, currencies);
if(i>-1 && i < sizeof(amounts)) /* sizeof efun
returns # of elements */
return amounts[i];
else return 0;
}
And that is a simple query function. Look at an add function:
void add_money(string type, int amt) {
string *tmp1;
int * tmp2;
int i, x, j, maxj;
i = member_array(type, currencies);
if(i >= sizeof(amounts)) /* corrupt data, we are in
a bad way */
return;
else if(i== -1) {
currencies += ({ type });
amounts += ({ amt });
return;
}
else {
amounts[i] += amt;
if(amounts[i] < 1) {
tmp1 = allocate(sizeof(currencies)-1);
tmp2 = allocate(sizeof(amounts)-1);
for(j=0, x =0, maxj=sizeof(tmp1); j < maxj;
j++) {
if(j==i) x = 1;
tmp1[j] = currencies[j+x];
tmp2[j] = amounts[j+x];
}
currencies = tmp1;
amounts = tmp2;
}
}
}
That is really some nasty code to perform the rather simple concept of
adding some money. First, we figure out if the player has any of that
kind of money, and if so, which element of the currencies array it is.
After that, we have to check to see that the integrity of the currency data
has been maintained. If the index of the type in the currencies array is
greater than the highest index of the amounts array, then we have a
problem since the indices are our only way of relating the two arrays.
Once we know our data is in tact, if the currency type is not currently
held by the player, we simply tack on the type as a new element to the
currencies array and the amount as a new element to the amounts array.
Finally, if it is a currency the player currently has, we just add the
amount to the corresponding index in the amounts array. If the money
gets below 1, meaning having no money of that type, we want to clear
the currency out of memory.
Subtracting an element from an array is no simple matter. Take, for
example, the result of the following:
string *arr;
arr = ({ \"a\", \"b\", \"a\" });
arr -= ({ arr[2] });
What do you think the final value of arr is? Well, it is:
({ \"b\", \"a\" })
Subtracting arr[2] from the original array does not remove the third
element from the array. Instead, it subtracts the value of the third
element of the array from the array. And array subtraction removes the
first instance of the value from the array. Since we do not want to be
forced on counting on the elements of the array as being unique, we are
forced to go through some somersaults to remove the correct element
from both arrays in order to maintain the correspondence of the indices
in the two arrays.
Mappings provide a better way. They allow you to directly associate the
money type with its value. Some people think of mappings as arrays
where you are not restricted to integers as indices. Truth is, mappings
are an entirely different concept in storing aggregate information. Arrays
force you to choose an index which is meaningful to the machine for
locating the appropriate data. The indices tell the machine how many
elements beyond the first value the value you desire can be found. With
mappings, you choose indices which are meaningful to you without
worrying about how that machine locates and stores it.
You may recognize mappings in the following forms:
constant values:
whole: ([ index:value, index:value ]) Ex: ([ \"gold\":10, \"silver\":20 ])
element: 10
variable values:
whole: map (where map is the name of a mapping variable)
element: map[\"gold\"]
So now my monetary functions would look like:
int query_money(string type) { return money[type]; }
void add_money(string type, int amt) {
if(!money[type]) money[type] = amt;
else money[type] += amt;
if(money[type] < 1)
map_delete(money, type); /* this is for
MudOS */
...OR...
money = m_delete(money, type) /* for some
LPMud 3.* varieties */
... OR...
m_delete(money, type); /* for other LPMud 3.*
varieties */
}
Please notice first that the efuns for clearing a mapping element from the
mapping vary from driver to driver. Check with your driver's
documentation for the exact name an syntax of the relevant efun.
As you can see immediately, you do not need to check the integrity of
your data since the values which interest you are inextricably bound to
one another in the mapping. Secondly, getting rid of useless values is a
simple efun call rather than a tricky, CPU-eating loop. Finally, the
query function is made up solely of a return instruction.
You must declare and initialize any mapping before using it.
Declarations look like:
mapping map;
Whereas common initializations look like:
map = ([]);
map = allocate_mapping(10) ...OR... map = m_allocate(10);
map = ([ \"gold\": 20, \"silver\": 15 ]);
As with other data types, there are rules defining how they work in
common operations like addition and subtraction:
([ \"gold\":20, \"silver\":30 ]) + ([ \"electrum\":5 ])
gives:
([\"gold\":20, \"silver\":30, \"electrum\":5])
Although my demonstration shows a continuity of order, there is in fact
no guarantee of the order in which elements of mappings will stored.
Equivalence tests among mappings are therefore not a good thing.
3.6 Summary
Mappings and arrays can be built as complex as you need them to be.
You can have an array of mappings of arrays. Such a thing would be
declared like this:
mapping *map_of_arrs;
which might look like:
({ ([ ind1: ({ valA1, valA2}), ind2: ({valB1, valB2}) ]), ([ indX:
({valX1,valX2}) ]) })
Mappings may use any data type as an index, including objects.
Mapping indices are often referred to as keys as well, a term from
databases. Always keep in mind that with any non-integer data type,
you must first initialize a variable before making use of it in common
operations such as addition and subtraction. In spite of the ease and
dynamics added to LPC coding by mappings and arrays, errors caused
by failing to initialize their values can be the most maddening experience
for people new to these data types. I would venture that a very high
percentage of all errors people experimenting with mappings and arrays
for the first time encounter are one of three error messages:
Indexing on illegal type.
Illegal index.
Bad argument 1 to (+ += - -=) /* insert your favourite operator */
Error messages 1 and 3 are darn near almost always caused by a failure
to initialize the array or mapping in question. Error message 2 is caused
generally when you are trying to use an index in an initialized array
which does not exist. Also, for arrays, often people new to arrays will
get error message 3 because they try to add a single element to an array
by adding the initial array to the single element value instead of adding
an array of the single element to the initial array. Remember, add only
arrays to arrays.
At this point, you should feel comfortable enough with mappings and
arrays to play with them. Expect to encounter the above error messages
a lot when first playing with these. The key to success with mappings is
in debugging all of these errors and seeing exactly what causes wholes
in your programming which allow you to try to work with uninitialized
mappings and arrays. Finally, go back through the basic room code and
look at things like the SetExits() (or the equivalent on your mudlib)
function. Chances are it makes use of mappings. In some instances, it
will use arrays as well for compatibility with mudlib.n.
Copyright (c) George Reese 1993
",({"chapter 3","chapter three","3",}):"chapter 3 \"LPC Data Types\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 17 june 1993
CHAPTER 3: LPC Data Types
3.1 What you should know by now
LPC object are made up of zero or more variables manipulated by one or
more functions. The order in which these functions appear in code is
irrelevant. The driver uses the LPC code you write by loading copies of
it into memory whenever it is first referenced and additional copies
through cloning. When each object is loaded into memory, all the variables
initially point to no value. The reset() function in compat muds, and
create() in native muds are used to give initial values to variables in
objects. The function for creation is called immediately after the object
is loaded into memory. However, if you are reading this textbook with no
prior programming experience, you may not know what a function is or how
it gets called. And even if you have programming experience, you may
be wondering how the process of functions calling each other gets started
in newly created objects. Before any of these questions get answered,
however, you need to know more about what it is the functions are
manipulating. You therefore should thouroughly come to know the concept
behind LPC data types. Certainly the most boring subject in this manual,
yet it is the most crucial, as 90% of all errors (excepting misplaced
{} and ()) involve the improper usage of LPC data types. So bear through
this important chapter, because it is my feeling that understanding this
chapter alone can help you find coding much, much easier.
3.2 Communicating with the computer
You possibly already know that computers cannot understand the letters
and numbers used by humans. Instead, the \"language\" spoken by computers
consists of an \"alphabet\" of 0's and 1's. Certainly you know computers
do not understand natural human languages. But in fact, they do not
understand the computer languages we write for them either. Computer
languages like BASIC, C, C++, Pascal, etc. are all intermediate
languages. They allow you to structure your thoughts more coherently
for translation into the 0's and 1's of the computer's languages.
There are two methods in which translation is done: compilation and
interpretation. These simply are differences betweem when the
programming language is translated into computer language. With
compiled languages, the programmer writes the code then uses a program
called a compiler to translate the program into the computer's
language. This translation occurs before the program is run. With
interpreted languages however, the process of translation occurs as
the program is being run. Since the translation of the program is
occurring during the time of the program's running in interpreted
languages, interpreted languages make much slower programs than
compiled languages.
The bottom line is, no matter what language you are writing in, at
some point this has to be changed into 0's and 1's which can be
understood by the computer. But the variables which you store in
memory are not simply 0's and 1's. So you have to have a way in
your programming languages of telling the computer whether or not
the 0's and 1's should be treated as decimal numbers or characters or
strings or anything else. You do this through the use of data types.
For example, say you have a variable which you call 'x' and you give
it the decimal whole number value 65. In LPC you would do this through
the statement:
-----
x = 65;
-----
You can later do things like:
_____
write(x+\"\\n\"); /* \\n is symbolically represents a carriage return */
y = x + 5;
-----
The first line allows you to send 65 and a carriage return to someone's screen.
The second line lets you set the value of y to 70.
The problem for the computer is that it does not know what '65' means when
you tell it x = 65;. What you think of 65, it might think of as:
00000000000000000000000001000001
But, also, to the computer, the letter 'A' is represented as:
00000000000000000000000001000001
So, whenever you instruct the computer write(x+\"\\n\");, it must have some
way of knowing that you want to see '65' and not 'A'.
The computer can tell the difference between '65' and 'A' through the use
of data types. A data types simply says what type of data is being stored
by the memory location pointed to by a given variable. Thus, each LPC
variable has a variable type which guides conversions. In the example
given above, you would have had the following line somewhere in the
code *before* the lines shown above:
-----
int x;
-----
This one line tells the driver that whatever value x points to, it will
be used as the data type \"int\", which is short for integer, or whole
number. So you have a basic introduction into the reason why data types
exist. They exist so the driver can make sense of the 0's and 1's that
the computer is storing in memory.
3.3 The data types of LPC
All LPMud drivers have the following data types:
void, status, int, string, object, int *, string *, object *, mixed *
Many drivers, but not all have the following important data types which
are important to discuss:
float, mapping, float *, mapping *
And there are a few drivers with the following rarely used data types
which are not important to discuss:
function, enum, struct, char
3.4 Simple data types
This introductory textbook will deal with the data types void, status,
int, float, string, object, mand mixed. You can find out about the
more complex data types like mappings and arrays in the intermediate
textbook. This chapter deals with the two simplest data types (from the
point of view of the LPC coder), int and string.
An int is any whole number. Thus 1, 42, -17, 0, -10000023 are all type int.
A string is one or more alphanumeric characters. Thus \"a\", \"we are borg\",
\"42\", \"This is a string\" are all strings. Note that strings are always
enclosed in \"\" to allow the driver to distinguish between the int 42 and
the string \"42\" as well as to distinguish between variable names (like x)
and strings by the same names (like \"x\").
When you use a variable in code, you must first let the driver know
what type of data to which that variable points. This process is
called *declaration*. You do this at the beginning of the function
or at the beginning of the object code (outside of functions before all
functions which use it). This is done by placing the name of the data type
before the name of the variable like in the following example:
-----
void add_two_and_two() {
int x;
int y;
x = 2;
y = x + x;
}
-----
Now, this is a complete function. The name of the function is
add_two_and_two(). The function begins with the declaration of an
int variable named x followed by the declaration of an in variable
named y. So now, at this point, the driver now has two variables which
point to NULL values, and it expects what ever values end up there to be
of type int.
A note about the data types void and status:
Void is a trivial data type which points to nothing. It is not used
with respect to variables, but instead with respect to functions. You
will come to understand this better later. For now, you need only
understand that it points to no value.
The data type status is a boolean data type. That is, it can only have
1 or 0 as a value. This is often referred to as being true or false.
3.5 Chapter summary
For variables, the driver needs to know how the 0's and 1's the computer
stores in memory get converted into the forms in which you intend them
to be used. The simplest LPC data types are void, status, int, and string.
You do not user variables of type void, but the data type does come
into play with respect to functions. In addition to being used for
translation from one form to the next, data types are used in determining
what rules the driver uses for such operations as +, -, etc. For example,
in the expression 5+5, the driver knows to add the values of 5 and 5
together to make 10. With strings however, the rules for int addition
make no sense. So instead, with \"a\"+\"b\", it appends \"b\" to the string \"a\"
so that the final string is \"ab\". Errors can thus result if you mistakenly
try to add \"5\"+5. Since int addition makes no sense with strings, the
driver will convert the second 5 to \"5\" and use string addition. The final
result would be \"55\". If you were looking for 10, you would therefore
have ended up with erroneous code. Keep in mind, however, that in most
instances, the driver will not do something so useful as coming up with
\"55\". It comes up with \"55\" cause it has a rule for adding a string
to an int, namely to treat the int as a string. In most cases, if you
use a data type for which an operation or function is not defined
(like if you tried to divide \"this is\" by \"nonsense\", \"this is\"/\"nonsense\"),
the driver will barf and report an error to you.
",({"chapter 12","chapter twelve","12",}):"chapter 12 \"The LPC Pre-Compiler\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 4: The LPC Pre-Compiler
4.1 Review
The previous chapter was quite heavy, so now I will slow down a bit so
you can digest and play with mappings and arrays by taking on the
rather simple topic of the LPC pre-compiler. By this point, however,
you should well understand how the driver interacts with the mudlib and
be able to code objects which use call outs and heart beats. In addition,
you should be coding simple objects which use mappings and arrays,
noting how these data types perform in objects. It is also a good idea to
start looking in detail at the actual mudlib code that makes up your mud.
See if you understand everything which is going on in your mudlibs
room and monster codes. For things you do not understand, ask the
people on your mud designated to answer creator coding questions.
Pre-compiler is actually a bit of a misnomer since LPC code is never
truly compiled. Although this is changing with prototypes of newer
LPC drivers, LPC drivers interpret the LPC code written by creators
rather than compile it into binary format. Nevertheless, the LPC pre-
compiler functions still perform much like pre-compilers for compiled
languages in that pre-compiler directives are interpreted before the driver
even starts to look at object code.
4.2 Pre-compiler Directives
If you do not know what a pre-compiler is, you really do not need to
worry. With respect to LPC, it is basically a process which happens
before the driver begins to interpret LPC code which allows you to
perform actions upon the entire code found in your file. Since the code
is not yet interpreted, the pre-compiler process is involved before the file
exists as an object and before any LPC functions or instructions are ever
examined. The pre-compiler is thus working at the file level, meaning
that it does not deal with any code in inherited files.
The pre-compiler searches a file sent to it for pre-compiler directives.
These are little instructions in the file meant only for the pre-compiler
and are not really part of the LPC language. A pre-compiler directive is
any line in a file beginning with a pound (#) sign. Pre-compiler
directives are generally used to construct what the final code of a file will
look at. The most common pre-compiler directives are:
#define
#undefine
#include
#ifdef
#ifndef
#if
#elseif
#else
#endif
#pragma
Most realm coders on muds use exclusively the directives #define and
#include. The other directives you may see often and should understand
what they mean even if you never use them.
The first pair of directives are:
#define
#undefine
The #define directive sets up a set of characters which will be replaced
any where they exist in the code at precompiler time with their definition.
For example, take:
#define OB_USER \"/std/user\"
This directive has the pre-compiler search the entire file for instances of
OB_USER. Everywhere it sees OB_USER, it replaces with \"/std/user\".
Note that it does not make OB_USER a variable in the code. The LPC
interpreter never sees the OB_USER label. As stated above, the pre-
compiler is a process which takes place before code interpretation. So
what you wrote as:
#define OB_USER \"/std/user\"
void create() {
if(!file_exists(OB_USER+\".c\")) write(\"Merde! No user file!\");
else write(\"Good! User file still exists!\");
}
would arrive at the LPC interpreter as:
void create() {
if(!file_exists(\"/std/user\"+\".c\")) write(\"Merde! No user file!\");
else write(\"Good! User file still exists!\");
}
Simply put, #define just literally replaces the defined label with whatever
follows it. You may also use #define in a special instance where no
value follows. This is called a binary definition. For example:
#define __NIGHTMARE
exists in the config file for the Nightmare Mudlib. This allows for pre-
compiler tests which will be described later in the chapter.
The other pre-compiler directive you are likely to use often is #include.
As the name implies, #include includes the contents of another file right
into the file being pre-compiled at the point in the file where the directive
is placed. Files made for inclusion into other files are often called header
files. They sometimes contain things like #define directives used by
multiple files and function declarations for the file. The traditional file
extension to header files is .h.
Include directives follow one of 2 syntax's:
#include <filename>
#include \"filename\"
If you give the absolute name of the file, then which syntax you use is
irrelevant. How you enclose the file name determines how the pre-
compiler searches for the header files. The pre-compiler first searches in
system include directories for files enclosed in <>. For files enclosed in
\"\", the pre-compiler begins its search in the same directory as the file
going through the pre-compiler. Either way, the pre-compiler will
search the system include directories and the directory of the file for the
header file before giving up. The syntax simply determines the order.
The simplest pre-compiler directive is the #pragma directive. It is
doubtful you will ever use this one. Basically, you follow the directive
with some keyword which is meaningful to your driver. The only
keyword I have ever seen is strict_types, which simply lets the driver
know you want this file interpreted with strict data typing. I doubt you
will ever need to use this, and you may never even see it. I just included
it in the list in the event you do see it so you do not think it is doing
anything truly meaningful.
The final group of pre-compiler directives are the conditional pre-
compiler directives. They allow you to pre-compile the file one way
given the truth value of an expression, otherwise pre-compile the file
another way. This is mostly useful for making code portable among
mudlibs, since putting the m_delete() efun in code on a MudOS mud
would normally cause an error, for example. So you might write the
following:
#ifdef MUDOS
map_delete(map, key);
#else
map = m_delete(map, key);
#endif
which after being passed through the pre-compiler will appear to the
interpreter as:
map_delete(map, key);
on a MudOS mud, and:
map = m_delete(map, key);
on other muds. The interpreter never sees the function call that would
cause it to spam out in error.
Notice that my example made use of a binary definition as described
above. Binary definitions allow you to pass certain code to the
interpreter based on what driver or mudlib you are using, among other
conditions.
4.3 Summary
The pre-compiler is a useful LPC tool for maintaining modularity among
your programs. When you have values that might be subject to change,
but are used widely throughout your files, you might stick all of those
values in a header file as #define statements so that any need to make a
future change will cause you to need to change just the #define directive.
A very good example of where this would be useful would be a header
file called money.h which includes the directive:
#define HARD_CURRENCIES ({ \"gold\", \"platinum\", \"silver\",
\"electrum\", \"copper\" })
so that if ever you wanted to add a new hard currency, you only need
change this directive in order to update all files needing to know what the
hard currencies are.
The LPC pre-compiler also allows you to write code which can be
ported without change among different mudlibs and drivers. Finally,
you should be aware that the pre-compiler only accepts lines ending in
carriage returns. If you want a multiple line pre-compiler directive, you
need to end each incomplete line with a backslash(\\).
Copyright (c) George Reese 1993
",({"chapter 40","chapter forty","40",}):"chapter 40 \"Useful Creator Commands\"
Moving around:
-------------
* To go to your workroom:
%^GREEN%^home%^RESET%^
* To go to someone else's workroom, \"home <person>\", for example:
%^GREEN%^home cratylus%^RESET%^
* To go to the town pub:
%^GREEN%^goto /domains/town/room/tavern%^RESET%^
* Or:
%^GREEN%^cd /domains/town/room%^RESET%^
%^GREEN%^goto tavern%^RESET%^
* To return to where you were before you went somewhere else:
%^GREEN%^return%^RESET%^
* To bring someone to you, \"trans <person>\". For example:
%^GREEN%^trans cratylus%^RESET%^
* To send them back when you're done with them:
%^GREEN%^return cratylus%^RESET%^
Dealing with living beings:
---------------------------
* To force everyone in a room to stop fighting:
%^GREEN%^quell%^RESET%^
* To let them resume combat:
%^GREEN%^unquell%^RESET%^
* To insta-kill a living being, \"zap <thing>\". For example:
%^GREEN%^zap orc%^RESET%^
* To bring to life a player who somehow left the death
room without regenerating, \"resurrect <person>\", For example:
%^GREEN%^resurrect cratylus%^RESET%^
* To make a living being do something you want, \"force <thing>
<command>\". For example:
%^GREEN%^force thief drop towel%^RESET%^
%^GREEN%^force thief go west%^RESET%^
* For complex management of a living being's vital statistics,
skills, health, score, and satiety levels, use the medical
tricorder in the chest in your workroom.
* For a detailed report of a living being's physical status:
%^GREEN%^stat orc%^RESET%^
%^GREEN%^stat cratylus%^RESET%^
Handling objects in general:
---------------------------
* To destroy an object, \"dest <thing>\". Note that the object's
inventory will probably move to the room it was occupying.
For example, if you:
%^GREEN%^dest fighter%^RESET%^
You may find that the room now contains a sword, shield, and
chainmail shirt, but no fighter.
* To reset an object to its original state, \"reload <thing>\". Note
that this also makes the object incorporate any changes you
made to its file. For example:
%^GREEN%^reload fighter%^RESET%^
%^GREEN%^reload here%^RESET%^
* To load a file into memory, \"update file\". This is used when you
have edited an object's file, and want the mud to use the new
stuff you created. For example, if you edited the fighter's file
and wanted to know if it will load properly into memory, you'd
type:
%^GREEN%^update /realms/you/area/npc/fighter.c%^RESET%^
Or:
%^GREEN%^cd /realms/you/area/npc/%^RESET%^
%^GREEN%^update fighter%^RESET%^
If you do not specify an object to update, the mud assumes you
want to update the room you are in. If there is a problem with the
room's code, and it does not load, you will be dropped into the
\"void\".
If the room's code is ok and it updates, anything in the room
that isn't part of its permanent inventory (except for players) will
disappear from the room.
* To make a copy of an object appear, \"clone <file>\". For example:
%^GREEN%^clone /realms/you/area/npc/fighter%^RESET%^
Or:
%^GREEN%^cd /realms/you/area/npc/%^RESET%^
%^GREEN%^clone fighter%^RESET%^
* To know the precise contents of an object, use scan:
%^GREEN%^scan fighter%^RESET%^
%^GREEN%^scan here%^RESET%^
If you want to know not only what the fighter has, but
also what any containers he is carrying have, use the \"-d\" flag:
%^GREEN%^scan -d fighter%^RESET%^
%^GREEN%^scan -d here%^RESET%^
Debugging commands:
--------------------
* elog: This will report back to you the last few lines of
your error log. Usually this is very helpful in nailing down
which lines of a file contain errors. If you are admin, you
may be working on files other than your home dir. If those
files fail to update, you can supply elog with a directory name
to specify where to look for an error report:
%^GREEN%^elog secure%^RESET%^
%^GREEN%^elog cmds%^RESET%^
%^GREEN%^elog lib%^RESET%^
* dbxwhere: provides a list of the chain of messages caught
in your last runtime error.
* dbxframe <number>: Using the list number from dbxwhere,
dbxframe can pinpoint exactly where in that link the error
came from.
%^GREEN%^tail /log/runtime%^RESET%^
%^GREEN%^tail /log/catch%^RESET%^
%^GREEN%^tail /log/player_errors%^RESET%^
miscellaneous useful commands:
-----------------------------
* %^GREEN%^people%^RESET%^: reports who is logged on, what site they logged
in from, and what room they are in.
* %^GREEN%^mudtime%^RESET%^: reports the time of day in the mud (nothing to
do with the time of day anywhere in the real world).
* %^GREEN%^bk <thing or file>%^RESET%^: makes a unique copy of that thing
or file and puts it in /realms/you/bak
*%^GREEN%^ restore <filename>%^RESET%^: copies the last backup of the
filename from your bak/ directory into where it used
to be.
",({"chapter 30","chapter thirty","30",}):"chapter 30 \"The Natural Language Parser\"
The Natural Language Parser
The Problem
A gut reaction to this entire system is to be overwhelmed by its apparent complexity, comparing it to the good-old days of using add_action(). A discussion of the natural language parsing system therefore needs to start by answering the question, \"Why bother?\".
The old way of handling user input was to define commands and tie them to functions using add_action. Each user object kept track which commands it had available to it, and of which functions each command would trigger. The list of commands changed each time the player object (really, any object which had enable_commands() called in it) moved. In addition, each time an object moved inside the player object or left its inventory, this list changed.
This led to two basic problems:
1. The same command might have slightly different uses in different parts of the mud. Or worse, the player could have two similar objects which define the same command in slightly different ways.
2. The complexity of syntax varied based on creator abilities and was limited by CPU.
For example, one creator could have created a rock in their area that added the 'throw' command. This creator is a newbie creator, and thus simply put the following code in their throw_rock() function:
int throw_rock(string str) {
object ob;
if( !str ) return 0;
ob = present(str, this_player());
if( !ob ) {
notify_fail(\"You have no rock!\");
return 0;
}
if( ob != this_object() ) return 0;
if( (int)ob->move(environment(this_player())) != MOVE_OK ) {
write(\"You cannot throw it for some reason.\");
return 1;
}
write(\"You throw the rock.\");
say((string)this_player()->query_cap_name() + \" throws the rock.\");
return 1;
}
In this case, \"throw rock\" will work, but \"throw the granite rock at tommy\" will not. But another creator also defined a throw command in their spear. This creator however, is a good coder and codes their throw_spear() command to handle 'throw the red spear at tommy' as well as 'throw spear'. Explain to a player why both syntaxes only work for the spear, and not for the rock. Then explain to that player why 'throw rock' for a rock built in yet another area yields 'What?'.
An early attempt to get around this problem was the parse_command(). Unfortunately it was buggy spaghetti that was way too complex for anyone to understand. The MudOS attempt to solve this problem is its new natural language command parser. The parser is based on the following assumptions:
All commands should behave in a consistent manner across the mud.
Similar objects should respond as expected to the same command line.
A player should only see 'What?' (or its equivalent) when they make a typo.
It should enable creators to handle the complex command processing required by the above assumption.
Overview of the MudOS System
The MudOS natural language parser is based on a philosophy of centralized command parsing. In other words, creators have no control over which commands exist nor over what syntax rules existing commands follow. Instead, creators are tasked with defining what those commands mean to their objects. Unlike with add_action() where commands are registered to the driver, the MudOS system registers verbs (the command) and rules with the driver. In this example, a simple \"smile\" verb is registered with a single rule, \"at LIV\".
With the old way of doing things, commands were executed either when a player entered in a command or when the command() efun was called. With this new system, a command may be executed at any time via either the parse_sentence() or parse_my_rules() efuns.
When one of those efuns is called, the driver searches through the list of verbs for a verb and rule which matches the command string. In order to do that, however, it needs to make several calls of all the objects involved to determine what the sentence means. For any given command string, the following objects are relevant to the parsing of that command string:
the verb handler
This object contains the functions used to see if the command is valid and to execute it. It is also the one that creates the rules for the verb.
the subject
This is the object from which parse_sentence() is called, or the object mentioned as the first argument in parse_my_rules(). It is the object considered to be executing the command in questin, the logical subject of the sentence.
the master object
This object keeps track of global information, such was what literals (mistakenly referred to as prepositions) exist across the mud. In general, a literal is a preposition.
the direct object
This is not the logical direct object of the sentence. Rather, this is the first object at which the verb action is targetted.
the indirect object
Again, this is not the logical indirect object of the sentence. Rather, it is the second object at which the verb action is targetted. For example, in \"give the book to the elf\", the elf will be both the logical indirect object and the parser indirect object. But if you allow \"give the elf the book\", the elf naturally still remains the logical indirect object, but the book is the indirect object to the parser since it is the second object targetted by the verb (the first being the elf).
Each object involved in he parsing of a sentence, except the subject, is responsible for handling certain driver applies that help the driver in parsing the sentence. The subject, unlike the other objects, is responsible for initiating the command. Although this document treats all of these objects as if they were completely distinct, it is possible to have the same object performing multiple roles for the same command. For example, your subject could also be direct object and verb handler. The next section discusses the objects and the applies they are responsible for in detail.
The Objects
Before studying each object in detail, it is important to keep in mind that each object which can be involved in any role must call parse_init() before doing anything related to verb parsing. The only exception is the master object.
The subject
The subject is simply the initiator of a command. A command is typically initiated by a call to parse_sentence() inside the object's process_input() apply. This example shows how a player object might use parse_sentence() to initiate a command. This efun will return 1 if the command successfully matched a known verb and rule and successfully executed it. If it found the verb in question, but it did not match any rule, then 0 is returned. If it found the verb in question and matched a rule, but the execution of the rule failed, it will return an errorstring describing why it failed. Finally, if no verb matched the command, then -1 is returned.
Take for example a mud with this one rule:
parse_add_rule(\"smile\", \"at LIV\")
The efun parse_sentence() would return the following values for the following command lines:
smile at descartes
Returns: 1
smile happily
Returns: 0
smile at the red box
Returns: \"The Box is not a living thing!\"
eat the red box
Returns: -1
The master object
The master object is responsible for a single apply, parse_command_prepos_list(). This apply returns a list of literal strings which may be used in a rule. A literal string is simply one that appears in the rule exactly as a player types it. In the smile example above, \"at\" is a literal. In most all cases, literals are prepositions, thus the name of the apply.
The verb handler
The verb handler object is responsible for setting up the rules for a verb and handling the test and execution of those rules. This example demonstrates a simple verb handler for the smile verb described above. As you can see, each rule is divided up into three parts:
1. initialization
2. testing
3. execution
The intialization is the call to parse_add_rule(). This associates a rule with a verb. The first argument is the verb (this verb may have spaces in it, like \"look at\") and the second argument is one of the rules being handled by this verb handler. This list defines the valid tokens for a rule.
The testing portion is a \"can\" apply. In testing a rule, the driver calls the applies can_<verb_rule> to determine if the execution of the verb even makes sense in this situation. The test apply is called when the driver has valid arguments to a rule, but it wants to see if those valid arguments make sense right now. For example, you might check a player here to see if they have enough magic points for casting the spell this verb/rule represents. If not, you might return \"You are too tired right now.\". If the rule match up in question makes completely no sense at all, like for example, they tried to throw a house for the (\"throw\", \"OBJ\") rule, you should return 0. The parser will guess well an error message from the situation. In this case, it will have parse_sentence() return \"You cannot throw the thing.\".
Finally execution is where the verb actually happens. You know you have a verb/rule match and you know all the arguments to it are valid. Now it is time to do something. You almost always want to return 1 from this function except in extreme circumstances.
The direct and indirect objects
As stated above, the directness or indirectness of an object has nothing to do with the linguistic meaning of those terms. Instead it has to do with what position in the token list the object takes. The direct object is the first object in the token list, and the indirect object is the second object in the token list.
These objects basically answer the question \"Can I be the direct/indirect object for this verb and rule?\". Like the testing and execution applies for verb handlers, the applies that answer this question may return 1, 0, or an error string. Also like the testing and execution applies for the verb handler, these come in the form of (in)direct_<verb>_<rule>(). This example is from somewhere inside the living object heirarchy. Note the is_living() apply which lets the parser know that the object matches a LIV token.
Inventory visibility
Some objects are subjects, direct objects, or indirect objects of verbs which require access to things in their inventory. For example, living things, bags, chests, etc. all need to allow other things access to their inventories for some commands. Two applies handle this situation:
1. inventory_accessible()
2. inventory_visible()
The first returns 1 if verbs can have access to an object's inventory, the second returns 1 if verbs simply can take into account an object's inventory. An example of the difference might be a glass chest. When closed, you want its inventory to be visible, but not accessible.
It is important to remember that is the return value for any of these special applies, including is_living(), you need to make an explicit call to the parse_refresh() efun. Unless the parse_refresh() efun is called, these special applies are only called once with no guarantee as to when that one call will actually occur.
Creating a New Verb
Currently, the Lima and Nightmare mudlibs use this parser system. Both mudlibs provide inheritable objects which make it simpler to interface with the MudOS parser system. Nightmare specifically has the inheritable LIB_VERB with methods for defining a new verb.
This verb example comes from the Nightmare mudlib. The simple Nightmare verb requires the following steps:
1. Name the verb
2. State the verb rules
3. Name any synonyms
4. Set an error message for display when the command is wrongly used
5. Create help text for the command
Naming the verb is done through the SetVerb() method. You simply specify the name of the verb.
The rules are passed to the SetRules() method. You may specify as many rules as are needed for the verb.
Like rules, synonyms are set as a list for the SetSynonyms() method. A synonym is simply any verb which is exactly synonymous with any possible rule for the verb in question. The player is able to access help for the verb and get error messages for the verb through the verb or any of its synonyms.
The error message is a string displayed to the user when they use the verb in an incorrect manner. For example, if I typed 'eat' when the rule is 'eat OBJ', the error message would be 'Eat what?'.
Finally, like with any object, the help text can be set through the SetHelp() method. Help is very important for verbs.
All of these methods only are able to take care of verb initalization. It is up to the verb creator to give meaning to a new verb. This is done first off by writing can_*() and do_*() applies in the verb handler. These methods should be very simplistic in nature. For example, a can method almost always simply returns 1. A do method generally finds its target and triggers some sort of event in that object. The event does the real command handling.
In addition to can and do applies, you need also to write any direct and indirect applies in approperiate objects. Nightmare centralizes this sort of processing through inheritables geared towards responding to particular verbs. A good example of this is LIB_PRESS which responds to the \"press\" command. Thus any object which should be pressable needs only to inherit this object to become a pressable object.
The can, do, direct, and indirect applies all have the same argument set for the same verb/rule pair, but it is important to know when the parser knows certan things. Take for example the verb/rule \"press OBJ on OBJ\". The parser takes the following actions:
1. Call can_press_obj_on_obj() in verb handler
2. Call direct_press_obj_on_obj() in all accessible and visible objects
3. Call indirect_press_obj_on_obj() in all accessible and visible objects
4. Call do_press_obj_on_obj() in the verb handler
The arguments to all methods called in this process are:
1. object direct_object
2. object indirect_object
3. string direct_object_as_entered_on_command_line
4. string indirect_object_as_entered_on_command_line
But how can can_press_obj_on_obj() know what the direct and indirect objects are if they have not been identified yet? The answer is that it cannot. For the command \"push the button on the wall\", in a room with me and you in it and we carry nothing, the sequence looks like this (return in parens):
1. verb->can_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (1)
2. me->direct_press_obj_on_obj(0, 0, \"the button\", the wall\"); (0)
3. you->direct_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (0)
4. room->direct_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (1)
5. me->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (0)
6. you->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (0)
7. room->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (1)
8. verb->do_press_obj_on_obj(room, room, \"the buton\", \"the wall\"); (1)
This assumes, of course, that the room responds positively with the id's \"button\" and \"wall\".
People familiar with the parser might say, \"Hey, wait, there is a lot more that happens than just that.\" In fact, there are many more possible permutations of this sequence. The most interesting is the ability to simply ignore the difference between prepositions like \"in\" and \"into\" which are often used interchangeably in colloquial speech. For example, if you had \"put OBJ in OBJ\" and \"put OBJ into OBJ\" verb/rules, you could handle them in a single place for each of the applies respectively liek this:
can_put_obj_word_obj()
direct_put_obj_word_obj()
indirect_put_obj_word_obj()
do_put_obj_word_obj()
If the parser found no can_put_obj_in_obj() defined, it then searches for a more generic handler, can_put_obj_word_obj(). In fact the real order it searches for a can handler is:
1. can_put_obj_in_obj()
2. can_put_obj_word_obj()
3. can_put_rule()
4. can_verb_rule()
---------------------
Last example:
static void create() {
parse_init();
parse_add_rule(\"smile\", \"at LIV\");
}
mixed can_smile_at_liv(object target) {
return 1;
}
mixed do_smile_at_liv(object target) {
previous_object()->eventPrint(\"You smile at \" +
(string)target->GetName() + \".\");
target->eventPrint((string)previous_object()->GetName() +
\" smiles at you.\");
return 1;
}
",({"chapter 18","chapter eighteen","18",}):"chapter 18 \"Valid climates\"
indoors
temperate
arid
arctic
tropical
sub-tropical
",({"chapter 31","chapter thirty-one","31",}):"chapter 31 \"Overview of the Quick Creation System\"
First, let me clarify that the QCS is not intended to replace
good coding habits. It is also not designed to handle every possible
need of a builder. The QCS is just a handy tool for making the
most tedious parts of building easier.
The amount of time it takes to hand-code an area of 100 rooms
(a small area, that is), with all the appropriate descriptions and
monsters and weapons and such is just mind-boggling. As a grown-up,
I just don't have time for building stuff line by excruciating line
in raw LPC, because I also need to work, maintain my house, say hello
to my family on occasion, etc.
At the same time, I would need a team of dedicated LPC code
fetishists to make a creation system that covers every last possible
thing you could do in LPC. The QCS is somewhere in between those
two extremes.
Therefore please view the QCS as a quick way to get bulk building
done, and not as a be-all end-all solution. You still need to learn
LPC to do really cool stuff. But to hammer out an area with a quest,
QCS lets you sail through the process of thingmaking with little hassle.
The design philosophy of the system itself involves object files
stored in /secure/modules. These files are inherited by an object which
you need to carry with you in order to use the QCS system. The code
for these creation modules is fairly messy and inelegant, but the
result is clean, indented code that compiles, so let's keep the
mockery to a minimum, shall we?
It is important to keep in mind the QCS isn't an editing system.
It's real live on-line modification, meaning that to modify a thing,
it actually has to be in the same room you are in, or it has to be that
room itself.
Once you modify something, it will typically update, so that if
you change the name of an npc, you're going to need to use the new name
to modify it further.
The next few chapters in this manual are nominally QCS specific,
but in reality this is pretty much my only chance to document some
of the changes in Dead Souls since version 1, so even if you never
intend to use QCS, it's worth poking through these chapters.
- Cratylus @ Frontiers
2 January 2006
",({"chapter 8","chapter eight","8",}):"chapter 8 \"LPC Basics\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 12 july 1993
CHAPTER 8: The data type \"object\"
8.1 Review
You should now be able to do anything so long as you stick to calling
functions within your own object. You should also know, that at the
bare minimum you can get the create() (or reset()) function in your object
called to start just by loading it into memory, and that your reset()
function will be called every now and then so that you may write the
code necessary to refresh your room. Note that neither of these
functions MUST be in your object. The driver checks to see if the
function exists in your object first. If it does not, then it does not
bother. You are also acquainted with the data types void, int, and string.
7.2 Objects as data types
In this chapter you will be acquainted with a more complex data type,
object. An object variable points to a real object loaded into the
driver's memory. You declare it in the same manner as other data types:
object ob;
It differs in that you cannot use +, -, +=, -=, *, or / (what would it
mean to divide a monster by another monster?). And since efuns like
say() and write() only want strings or ints, you cannot write() or
say() them (again, what would it mean to say a monster?).
But you can use them with some other of the most important efuns on any
LPMud.
8.3 The efun: this_object()
This is an efun which returns an object in which the function being executed
exists. In other words, in a file, this_object() refers to the object your
file is in whether the file gets cloned itself or inherted by another file.
It is often useful when you are writing a file which is getting inherited
by another file. Say you are writing your own living.c which gets
inherited by user.c and monster.c, but never used alone. You want to log
the function set_level() it is a player's level being set (but you do not
care if it is a monster.
You might do this:
void set_level(int x) {
if(this_object()->is_player()) log_file(\"levels\", \"foo\\n\");
level = x;
}
Since is_player() is not defined in living.c or anything it inherits,
just saying if(is_player()) will result in an error since the driver
does not find that function in your file or anything it inherits.
this_object() allows you to access functions which may or may not be
present in any final products because your file is inherited by others
without resulting in an error.
8.4 Calling functions in other objects
This of course introduces us to the most important characteristic of
the object data type. It allows us to access functions in other objects.
In previous examples you have been able to find out about a player's level,
reduce the money they have, and how much hp they have.
Calls to functions in other objects may be done in two ways:
object->function(parameters)
call_other(object, \"function\", parameters);
example:
this_player()->add_money(\"silver\", -5);
call_other(this_player(), \"add_money\", \"silver\", -5);
In some (very loose sense), the game is just a chain reaction of function
calls initiated by player commands. When a player initiates a chain of
function calls, that player is the object which is returned by
the efun this_player(). So, since this_player() can change depending
on who initiated the sequence of events, you want to be very careful
as to where you place calls to functions in this_player(). The most common
place you do this is through the last important lfun (we have mentioned
create() and reset()) init().
8.5 The lfun: init()
Any time a living thing encounters an object (enters a new room, or enters
the same room as a certain other object), init() is called in all of
the objects the living being newly encounters. It is at this point
that you can add commands the player can issue in order to act.
Here is a sample init() function in a flower.
void init() {
::init();
add_action(\"smell_flower\", \"smell\");
}
Ito smell_flower(). So you should have smell_flower() look like this:
1 int smell_flower(string str); /* action functions are type int */
2
3 int smell_flower(string str) {
4 if(str != \"flower\") return 0; /* it is not the flower being smelled */
5 write(\"You sniff the flower.\\n\");
6 say((string)this_player()->GetName()+\" smells the flower.\\n\");
7 this_player()->add_hp(random(5));
8 return 1;
9 }
In line 1, we have our function declared.
In line 3, smell_flower() begins. str becomes whatever comes after the
players command (not including the first white space).
In line 4, it checks to see if the player had typed \"smell flower\". If
the player had typed \"smell cheese\", then str would be \"cheese\". If
it is not in fact \"flower\" which is being smelled, then 0 is returned,
letting the driver know that this was not the function which should
have been called. If in fact the player had a piece of cheese as well
which had a smell command to it, the driver would then call the function
for smelling in that object. The driver will keep calling all functions
tied to smell commands until one of them returns 1. If they all return
0, then the player sees \"What?\"
In line 5, the efun write() is called. write() prints the string which
is passed to it to this_player(). So whoever typed the command here
sees \"You sniff the flower.\"
In line 6, the efun say() is called. say() prints the string which is
doing the sniffing, we have to call the GetName() function
in this_player(). That way if the player is invis, it will say
\"Someone\" (or something like that), and it will also be properly
capitalized.
In line 7, we call the add_hp() function in the this_player() object,
since we want to do a little healing for the sniff (Note: do not
code this object on your mud, whoever balances your mud will shoot you).
In line 8, we return control of the game to the driver, returning 1 to
let it know that this was in fact the right function to call.
8.6 Adding objects to your rooms
And now, using the data type object, you can add monsters to your rooms:
void create() {
::create();
SetProperty(\"light\", 3);
set(\"short\", \"Krasna Square\");
set(\"long\", \"Welcome to the Central Square of the town of Praxis.\\n\");
SetExits( ({ \"/domains/standard/hall\" }), ({ \"east\" }) );
}
void reset() {
object ob;
::reset();
if(present(\"guard\")) return; /* Do not want to add a guard if */
ob = new(\"/std/monster\"); /* one is already here */
ob->SetKeyName(\"guard\");
ob->set(\"id\", ({ \"guard\", \"town guard\" }) );
ob->set(\"short\", \"Town guard\");
ob->set(\"long\", \"He guards Praxis from nothingness.\\n\");
ob->SetGender(\"male\");
ob->set_race(\"human\");
ob->set_level(10);
ob->set_alignment(200);
ob->set_humanoid();
ob->set_hp(150);
ob->set_wielding_limbs( ({ \"right hand\", \"left hand\" }) );
ob->eventMove(this_object());
}
Now, this will be wildly different on most muds. Some, as noted before,
in that object so you have a uniquely configured monster object. The
last act in native muds is to call eventMove() in the monster object to move
it to this room (this_object()). In compat muds, you call the efun
move_object() which takes two parameters, the object to be moved, and the
object into which it is being moved.
// CORRECTION: move_object() does not take two arguments in recent
// versions of MudOS. -Crat 24Feb2007
8.7 Chapter summary
At this point, you now have enough knowledge to code some really nice
stuff. Of course, as I have been stressing all along, you really need
to read the documents on building for your mud, as they detail which
functions exist in which types of objects for you to call. No matter
what your knowledge of the mudlib is, you have enough know-how to
give a player extra things to do like sniffing flowers or glue or whatever.
At this point you should get busy coding stuff. But the moment things
even look to become tedious, that means it is time for you to move to
the next level and do more. Right now code yourself a small area.
Make extensive use of the special functions coded in your mud's
room.c (search the docs for obscure ones no one else seems to use).
Add lots o' neat actions. Create weapons which have magic powers which
gradually fade away. All of this you should be able to do now. Once
this becomes routine for you, it will be time to move on to intermediate
stuff. Note that few people actually get to the intermediate stuff.
If you have played at all, you notice there are few areas on the mud
which do what I just told you you should be able to do. It is not
because it is hard, but because there is a lot of arrogance out there
on the part of people who have gotten beyond this point, and very little
communicating of that knowledge. The trick is to push yourself and
think of something you want to do that is impossible. If you ask someone
in the know how to do X, and they say that is impossible, find out
youself how to code it by experimenting.
George Reese
Descartes of Borg
12 july 1993
borg@hebron.connected.com
Descartes@Nightmare (intermud)
Descartes@Igor (not intermud)
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction to the Coding Environment\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 25 may 1993
CHAPTER 1: Introduction to the Coding Environment
1.1 UNIX file structure
LPMuds use basic UNIX commands and its file structure. If you know
UNIX commands already, then note (with a few exceptions) options are
not available to the commands. Like DOS, UNIX is heirarchical. The
root directory of which all directories are sub-directories is called
root(/). And from those sub-directories you may have further
sub-directories. A directory may be referred to in two different ways:
1) by its full name, or absolute name, or 2) by its relative name.
Absolute name refers to the directory's full path starting from / winding
down the directory tree until you name the directory in question. For
example:
/players/descartes/obj/monster
refers to the directory monster which is a sub-directory of obj which
is a sub-directory of descartes which is a sub-directory of players
which is a sudirectory of /.
The relative name refers to the name relative to another directory.
The above example is called monster relative to /players/descartes/obj,
but it is also called obj/monster relative to /players/descartes,
descartes/obj/monster relative to /players, and finally
players/descartes/obj/monster relative to /. You can tell the
difference between absolute names and relative names because absolute
names always start with /. In order to know exactly which directory
is being named by a relative name, you naturally must know what
directory it is relative to.
A directory contains sub-directories and files. LPMuds only use text files
inside the mudlib. Like directories, files have both absolute and
relative names. The most basic relative name is often referred to as the file
name, with the rest of the absolute name being referred to as the path. So,
for the file: /players/descartes/castle.c, castle.c is the file name, and
/players/descartes is the path.
On some muds, a file with a file name beginning with a . (like .plan) is
not visible when you list files with the regular file listing command.
1.2 UNIX Commands
Along with the UNIX file structure, LPMuds use many UNIX commands. Typical
UNIX commands on most muds are:
pwd, cd, ls, rm, mv, cp, mkdir, rmdir, more, head, cat, ed
If you have never before seen UNIX commands, you probably are thinking this
is all nonsense. Well, it is, but you got to use them. Before getting
into what they mean though, first a discussion of current directory.
If you know DOS, then you know what a current working directory is.
At any given point, you are considered to be \"in\" some directory. This
means that any relative file or directory names you give in UNIX commands
are relative to that directory. For example, if my current directory is
/players/descartes and I type \"ed castle.c\" (ed is the command to edit),
then it assumes I mean the file /players/descartes/castle.c
pwd: shows you your current working directory
cd: changes your current working directory. You may give either relative
or absolute path names. With no arguments, it changes to your home
directory.
ls: lists all files in the directory named. If no directory is named,
it lists the files of the current working directory
rm: deletes the file named
mv: renames the file named
cp: copies the file named
mkdir: makes a new directory
rmdir: deletes a directory. All files must have been first removed.
more: pages the file named so that the file appears on your screen one
page at a time.
cat: shows the whole file to you at once
head: shows you the first several lines of a file
tail: shows you the last several lines of a file
ed: allows you to edit a file using the mud editor
1.3 Chapter Summary
UNIX uses a heirarchical file structure with the root of the tree being
named /. Other directories branch off from that root directory and
in turn have their own sub-directories. All directories may contain
directories and files. Directories and files are referred to either
by their absolute name, which always begins with /, or by their relative
name which gives the file's name relative to a particular directory.
In order to get around in the UNIX files structure, you have the
typical UNIX commands for listing files, your current directory, etc.
On your mud, all of the above commands should have detailed help commands
to help you explore exactly what they do. In addition, there should
be a very detailed file on your mud's editor. If you are unfamiliar
with ed, you should go over this convoluted file.
",({"chapter 13","chapter thirteen","13",}):"chapter 13 \"Advanced String Handling\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 5: Advanced String Handling
5.1 What a String Is
The LPC Basics textbook taught strings as simple data types. LPC
generally deals with strings in such a matter. The underlying driver
program, however, is written in C, which has no string data type. The
driver in fact sees strings as a complex data type made up of an array of
characters, a simple C data type. LPC, on the other hand does not
recognize a character data type (there may actually be a driver or two out
there which do recognize the character as a data type, but in general not).
The net effect is that there are some array-like things you can do with
strings that you cannot do with other LPC data types.
The first efun regarding strings you should learn is the strlen() efun.
This efun returns the length in characters of an LPC string, and is thus
the string equivalent to sizeof() for arrays. Just from the behaviour of
this efun, you can see that the driver treats a string as if it were made up
of smaller elements. In this chapter, you will learn how to deal with
strings on a more basic level, as characters and sub strings.
5.2 Strings as Character Arrays
You can do nearly anything with strings that you can do with arrays,
except assign values on a character basis. At the most basic, you can
actually refer to character constants by enclosing them in '' (single
quotes). 'a' and \"a\" are therefore very different things in LPC. 'a'
represents a character which cannot be used in assignment statements or
any other operations except comparison evaluations. \"a\" on the other
hand is a string made up of a single character. You can add and subtract
other strings to it and assign it as a value to a variable.
With string variables, you can access the individual characters to run
comparisons against character constants using exactly the same syntax
that is used with arrays. In other words, the statement:
if(str[2] == 'a')
is a valid LPC statement comparing the second character in the str string
to the character 'a'. You have to be very careful that you are not
comparing elements of arrays to characters, nor are you comparing
characters of strings to strings.
LPC also allows you to access several characters together using LPC's
range operator ..:
if(str[0..1] == \"ab\")
In other words, you can look for the string which is formed by the
characters 0 through 1 in the string str. As with arrays, you must be
careful when using indexing or range operators so that you do not try to
reference an index number larger than the last index. Doing so will
result in an error.
Now you can see a couple of similarities between strings and arrays:
1) You may index on both to access the values of individual elements.
a) The individual elements of strings are characters
b) The individual elements of arrays match the data type of the
array.
2) You may operate on a range of values
a) Ex: \"abcdef\"[1..3] is the string \"bcd\"
b) Ex: ({ 1, 2, 3, 4, 5 })[1..3] is the int array ({ 2, 3, 4 })
And of course, you should always keep in mind the fundamental
difference: a string is not made up of a more fundamental LPC data type.
In other words, you may not act on the individual characters by
assigning them values.
5.3 The Efun sscanf()
You cannot do any decent string handling in LPC without using
sscanf(). Without it, you are left trying to play with the full strings
passed by command statements to the command functions. In other
words, you could not handle a command like: \"give sword to leo\", since
you would have no way of separating \"sword to leo\" into its constituent
parts. Commands such as these therefore use this efun in order to use
commands with multiple arguments or to make commands more
\"English-like\".
Most people find the manual entries for sscanf() to be rather difficult
reading. The function does not lend itself well to the format used by
manual entries. As I said above, the function is used to take a string and
break it into usable parts. Technically it is supposed to take a string and
scan it into one or more variables of varying types. Take the example
above:
int give(string str) {
string what, whom;
if(!str) return notify_fail(\"Give what to whom?\\n\");
if(sscanf(str, \"%s to %s\", what, whom) != 2)
return notify_fail(\"Give what to whom?\\n\");
... rest of give code ...
}
The efun sscanf() takes three or more arguments. The first argument is
the string you want scanned. The second argument is called a control
string. The control string is a model which demonstrates in what form
the original string is written, and how it should be divided up. The rest
of the arguments are variables to which you will assign values based
upon the control string.
The control string is made up of three different types of elements: 1)
constants, 2) variable arguments to be scanned, and 3) variable
arguments to be discarded. You must have as many of the variable
arguments in sscanf() as you have elements of type 2 in your control
string. In the above example, the control string was \"%s to %s\", which
is a three element control string made up of one constant part (\" to \"),
and two variable arguments to be scanned (\"%s\"). There were no
variables to be discarded.
The control string basically indicates that the function should find the
string \" to \" in the string str. Whatever comes before that constant will
be placed into the first variable argument as a string. The same thing
will happen to whatever comes after the constant.
Variable elements are noted by a \"%\" sign followed by a code for
decoding them. If the variable element is to be discarded, the \"%\" sign
is followed by the \"*\" as well as the code for decoding the variable.
Common codes for variable element decoding are \"s\" for strings and \"d\"
for integers. In addition, your mudlib may support other conversion
codes, such as \"f\" for float. So in the two examples above, the \"%s\" in
the control string indicates that whatever lies in the original string in the
corresponding place will be scanned into a new variable as a string.
A simple exercise. How would you turn the string \"145\" into an
integer?
Answer:
int x;
sscanf(\"145\", \"%d\", x);
After the sscanf() function, x will equal the integer 145.
Whenever you scan a string against a control string, the function
searches the original string for the first instance of the first constant in
the original string. For example, if your string is \"magic attack 100\" and
you have the following:
int improve(string str) {
string skill;
int x;
if(sscanf(str, \"%s %d\", skill, x) != 2) return 0;
...
}
you would find that you have come up with the wrong return value for
sscanf() (more on the return values later). The control string, \"%s %d\",
is made up of to variables to be scanned and one constant. The constant
is \" \". So the function searches the original string for the first instance
of \" \", placing whatever comes before the \" \" into skill, and trying to
place whatever comes after the \" \" into x. This separates \"magic attack
100\" into the components \"magic\" and \"attack 100\". The function,
however, cannot make heads or tales of \"attack 100\" as an integer, so it
returns 1, meaning that 1 variable value was successfully scanned
(\"magic\" into skill).
Perhaps you guessed from the above examples, but the efun sscanf()
returns an int, which is the number of variables into which values from
the original string were successfully scanned. Some examples with
return values for you to examine:
sscanf(\"swo rd descartes\", \"%s to %s\", str1, str2) return: 0
sscanf(\"swo rd descartes\", \"%s %s\", str1, str2) return: 2
sscanf(\"200 gold to descartes\", \"%d %s to %s\", x, str1, str2) return: 3
sscanf(\"200 gold to descartes\", \"%d %*s to %s\", x, str1) return: 2
where x is an int and str1 and str2 are string
5.4 Summary
LPC strings can be thought of as arrays of characters, yet always
keeping in mind that LPC does not have the character data type (with
most, but not all drivers). Since the character is not a true LPC data
type, you cannot act upon individual characters in an LPC string in the
same manner you would act upon different data types. Noticing the
intimate relationship between strings and arrays nevertheless makes it
easier to understand such concepts as the range operator and indexing on
strings.
There are efuns other than sscanf() which involve advanced string
handling, however, they are not needed nearly as often. You should
check on your mud for man or help files on the efuns: explode(),
implode(), replace_string(), sprintf(). All of these are very valuable
tools, especially if you intend to do coding at the mudlib level.
Copyright (c) George Reese 1993
",({"chapter 24","chapter twenty-four","24",}):"chapter 24 \"Quests\"
Building Quests
from the Nightmare IV LPC Library
written by Descartes of Borg 950716
Unlike previous Nightmare versions, Nightmare IV has no support for
centralized quest administration. This was done under the belief that
coercive questing was among the least favourite features players have
mentioned about the MUDs I have encountered. Nevertheless, the
presence of quests is still an extrememly important part of any MUD.
Since the coercive nature (needing to complete quest X to raise to
level Y) has been removed, other ways to make questing worthwhile need
to be found.
The first, and most obvious, is to properly reward the player with
money, items, and skill and stat points. The other bit of support is
for a title list. Each quest, or accomplishment, is added to a list
of accomplishments the player has. The player may display any of
those at any time as part of their title.
The interface to this is simple:
player_object->AddQuest(string title, string description);
Example:
this_player()->AddQuest(\"the slayer of frogs\",
\"You viciously slayed the evil frogs of Wernmeister that \"
\"threatened the peaceful town with warts and unabated fly murder.\");
In the player's biography, they will see the description along with
the date they accomplished the task. From their title list, they will
now be able to choose this title.
Descartes of Borg
950716
",({"chapter 19","chapter nineteen","19",}):"chapter 19 \"Doors\"
Creating Doors between Two Rooms
The Nightmare IV LPC Library
created by Descartes of Borg 950419
This document describes how to build door-type objects which link two
rooms. These door-type objects do not need to be doors, but in fact
can be windows or boulders or any other such object. The Nightmare IV
LPC Library door object, unlike the old way of doing doors, is an
object separate from the rooms it connects. In other words, in order
to build a door, you have three objects (just as you would visualize):
two rooms and a door.
The door object is /lib/door.c. To inherit it, #include <lib.h> and
inherit LIB_DOOR;. An example door may be found in
/domains/Examples/etc/door.c as well as the rooms
/domains/Examples/room/doorroom1.c and /domains/Examples/room/doorroom2.c.
Setting up the door object
The first thing you must do is create the door object. You must
visualize this door object just like a door connecting two rooms in
real life. You have a room on each side with a single door with two
sides. Technically, a door object may have any number of sides.
Practically speaking, most people using this object will be using it
as a door, which means it will have two sides.
To create a door object, you simply describe each side of the door.
The easiest way to do this is through the SetSide() function.
mapping SetSide(string side, mapping mp);
Example:
SetSide(\"east\", ([ \"id\" : \"red door\", \"short\" : \"a red door\",
\"long\" : \"A freshly painted red door.\",
\"lockable\" : 0 ]) );
The name of the side is simply the exit used by the room which sees
that side. For example, if in one room the door is at the east exit,
then the side is identified as east. The mapping consists of the
following data:
\"id\"
What a person on that side calls the door. For example, you can have a
door blue on one side and red on the other. On one side, you go east
to go through the door, and from that room the door appears red. The
id for that side might be \"red door\". The id for the other side might
be \"blue door\".
\"short\"
The short description for the door as seen from the side in question.
This can be a function or a string.
\"long\"
The long description for the door as seen from the side in question.
Whether the door is open or not will be added to the long if the long
is a string. This can be either a string or function. If it is a
function, you must specify whether the door is open or close on your
own.
\"lockable\"
0 if the door cannot be locked (and unlocked) from that side, 1 if it
can.
\"keys\"
An array of id's of objects which can be used to unlock it if it is
lockable. Lockable doors do not need keys.
II. Setting up the rooms
After you have called SetItems() and SetExits() in the room
(remembering to set the exit for the exit with the door), call the
function SetDoor().
string SetDoor(string dir, string doorfile);
Example: SetDoor(\"east\", \"/realms/descartes/doors/red_door\");
Sets the exit named to be blocked by a door object when that door
object is closed.
This is all you need to do in the room. Note that the exit name
corresponds to the side name mentioned in the door.
III. Advanced Door Stuff
At this point, you should know how to do the minimum stuff to build a
door. This section goes into detail about door functions and how you
can do advanced things with doors by manipulating door events. This
section has two parts, door data functions and door events.
a. Door Data Functions
*****
SetSide()
*****
mapping SetSide(string side, mapping mp);
As described above.
*****
SetClosed()
*****
static int SetClosed(int x)
Example: SetClosed(1);
This function can only be called from inside the door object.
Generally you use it to set the initial state of the door. If you
want to close the door at any other time, or to close it from another
object, use eventClose() or eventOpen().
*****
SetLocked()
*****
static int SetLocked(int x)
Example: SetLocked(1);
Like SetClosed(), this function should only be used from create()
inside the door object to set the initial state of the door. At other
times, use eventLock() or eventUnlock().
*****
SetLockable()
*****
int SetLockable(string side, int x)
Example: SetLockable(\"east\", 1);
Sets a side as being able to be locked or unlocked. Since it is done
by sides, this means you can have one side not be lockable with the
other side being lockable. The first argument is the side being set
lockable or not lockable, the second argument is 1 for lockable and 0
for not lockable.
*****
SetId()
*****
string SetId(string side, string id)
Example: SetId(\"west\", \"blue door\");
This is not like your traditional SetId() function. Instead, it sets
a single way of identifying the door from a given side. It is what
the player might use to open the door or look at it.
*****
SetShort()
*****
mixed SetShort(string side, string | function desc)
Examples:
SetShort(\"north\", \"a red door\");
SetShort(\"west\", (: GetWestShort :) );
Sets the short description for a given side of a door. If the second
argument is a function, it gets passed as an argument the name of the
side for which the function serves as a description. That function
should return a string. For the above:
string GetWestShort(string dir) {
if( query_night() ) return \"a shadowy door\";
else return \"a red door\";
}
*****
SetLong()
*****
mixed SetLong(string side, string | function desc)
Examples:
SetLong(\"south\", \"An old, dusty door covered in cobwebs.\");
SetLong(\"east\", (: GetEastLong :))
This works much like the SetShort() function, except it handles the
long description. It is important to note that if the second argument
is a string, that the state of the door will be added onto the long
description automatically. In other words \"It is open.\" will appear
as the second line. This will *not* be done if you use a function for
your long description.
*****
SetKeys()
*****
string *SetKeys(string side, string *keys)
Example: SetKeys(\"east\", ({ \"skeleton key\", \"special key\" }));
Builds an array of id's which can be used to unlock the door if it is
lockable from this side. In other words, a person can only unlock the
door if that person has an object which has one of the id's you
specify for its id.
b. Events
*****
eventOpen()
*****
varargs int eventOpen(object by, object agent)
Examples:
\"/realms/descartes/etc/red_door\"->eventOpen(this_object());
int eventOpen(object by, object agent) {
if( query_night() ) return 0; /* Can't open it at night */
else return door::eventOpen(by, agent);
}
The function that actually allows the door to be opened externally.
It returns 1 if the door is successfully opened. It returns 0 if it
fails. The first argument is the room object from which the door is
being opened. The second argument, which is optional, is the living
thing responsible for opening the door.
The first example above is an example of what you might do from
reset() inside a room in order to have the door start open at every
reset.
The second example above is an example of how you might conditionally
prevent the door from opening by overriding the Open event. In this
case, if it is night, you cannot open this door. If it is day, you
can.
*****
eventClose()
*****
varargs int eventClose(object by, object agent)
Example: See eventOpen()
This function works just like eventOpen(), except it does the closing
of the door.
*****
eventLock()
*****
varargs int eventLock(object by, object agent)
Example: see eventOpen()
This function works just like eventOpen(), except that it gets called
for locking the door.
*****
eventUnlock()
*****
varargs int eventUnlock(object by, object agent)
Example: See eventOpen()
This function works just like eventOpen(), except that it gets called
for unlocking the door.
",({"chapter 5","chapter five","5",}):"chapter 5 \"The Basics of Inheritance\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 01 july 1993
CHAPTER 5: The Basics of Inheritance
5.1 Review
You should now understand the basic workings of functions. You should be
able to declare and call one. In addition, you should be able to recognize
function definitions, although, if this is your first experience with LPC,
it is unlikely that you will as yet be able to define your own functions.
There functions form the basic building blocks of LPC objects. Code
in them is executed when another function makes a call to them. In making
a call, input is passed from the calling function into the execution of
the called one. The called function then executes and returns a value
of a certain data type to the calling function. Functions which return
no value are of type void.
After examining your workroom code, it might look something like this
(depending on the mudlib):
-----
inherit \"/std/room\";
void create() {
::create();
SetProperty(\"light\", 2);
SetProperty(\"indoors\", 1);
set(\"short\", \"Descartes' Workroom\");
set(\"long\", \"This is where Descartes works.\\nIt is a cube.\\n\");
SetExits( ({ \"/domains/standard/square\" }), ({ \"square\" }) );
}
-----
If you understand the entire textbook to this point, you should recognize
of the code the following:
1) create() is the definition of a function (hey! he did not declare it)
2) It makes calls to SetProperty(), set(), and SetExits(), none
of which are declared or defined in the code.
3) There is a line at the top that is no variable or function declaration
nor is it a function definition!
This chapter will seek to answer the questions that should be in your head
at this point:
1) Why is there no declaration of create()?
2) Where are the functions SetProperty(), set(), and SetExits() declared
and defined?
3) What the hell is that line at the top of the file?
5.2 Object oriented programming
Inheritance is one of the properties which define true object oriented
programming (OOP). It allows you to create generic code which can be used
in many different ways by many different programs. What a mudlib does is
create these generalized files (objects) which you use to make very specific
objects.
If you had to write the code necessary for you to define the workroom above,
you would have to write about 1000 lines of code to get all the functionality
of the room above. Clearly that is a waste of disk space. In addition,
such code does not interact well with players and other rooms since every
creator is making up his or her own functions to perform the functionality
of a room. Thus, what you might use to write out the room's long description,
GetLong(), another wizard might be calling long(). This is the primary
reason mudlibs are not compatible, since they use different protocols for
object interaction.
OOP overcomes these problems. In the above workroom, you inherit the
functions already defined in a file called \"/std/room.c\". It has all
the functions which are commonly needed by all rooms defined in it. When
you get to make a specific room, you are taking the general functionality
of that room file and making a unique room by adding your own function,
create().
5.3 How inheritance works
As you might have guessed by now, the line:
-----
inherit \"/std/room\";
-----
has you inherit the functionality of the room \"/std/room.c\". By inheriting
the functionality, it means that you can use the functions which have
been declared and defined in the file \"/std/room.c\" In the Nightmare Mudlib,
\"/std/room.c\" has, among other functions, SetProperty(), set(), and
SetExits() declared and defined. In your function create(), you are
making calls to those functions in order to set values you want your
room to start with. These values make your room different from others, yet
able to interact well with other objects in memory.
In actual practice, each mudlib is different, and thus requires you to use
a different set of standard functions, often to do the same thing. It is
therefore beyond the scope of this textbook even to describe what
functions exist and what they do. If your mudlib is well documented,
however, then (probably in /doc/build) you will have tutorials on how
to use the inheritable files to create such objects. These tutorials
should tell you what functions exist, what input they take, the data
type of their output, and what they do.
5.4 Chapter summary
This is far from a complete explanation of the complex subject of inheritance.
The idea here is for you to be able to understand how to use inheritance in
creating your objects. A full discussion will follow in a later textbook.
Right now you should know the following:
1) Each mudlib has a library of generic objects with their own general
functions used by creators through inheritance to make coding objects
easier and to make interaction between objects smoother.
2) The functions in the inheritable files of a mudlib vary from mudlib
to mudlib. There should exist documentation on your mud on how to
use each inheritable file. If you are unaware what functions are
available, then there is simply no way for you to use them. Always
pay special attention to the data types of the input and the data
types of ay output.
3) You inherit the functionality of another object through the line:
-----
inherit \"filename\";
-----
where filename is the name of the file of the object to be inherited.
This line goes at the beginning of your code.
Note:
You may see the syntax ::create() or ::init() or ::reset() in places.
You do not need fully to understand at this point the full nuances of this,
but you should have a clue as to what it is. The \"::\" operator is a way
to call a function specifically in an inherited object (called the scope
resolution operator). For instance, most muds' room.c has a function
called create(). When you inherit room.c and configure it, you are doing
what is called overriding the create() function in room.c. This means
that whenever ANYTHING calls create(), it will call *your* version and not
the one in room.c. However, there may be important stuff in the room.c
version of create(). The :: operator allows you to call the create() in
room.c instead of your create().
An example:
-----
#1
inherit \"/std/room\";
void create() { create(); }
-----
-----
#2
inherit \"/std/room\";
void create() { ::create(); }
-----
Example 1 is a horror. When loaded, the driver calls create(), and then
create() calls create(), which calls create(), which calls create()...
In other words, all create() does is keep calling itself until the driver
detects a too deep recursion and exits.
Example 2 is basically just a waste of RAM, as it is no different from room.c
functionally. With it, the driver calls its create(), which in turn calls
::create(), the create() in room.c. Otherwise it is functionally
exactly the same as room.c.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Variable Handling\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: july 5 1993
CHAPTER 6: Variable Handling
6.1 Review
By now you should be able to code some simple objects using your muds standard
object library. Inheritance allows you to use functions defined in those
objects without having to go and define yourself. In addition,
you should know how to declare your own functions. This
chapter will teach you about the basic elements of LPC which will allow you to
define your own functions using the manipulation of variables.
6.2 Values and objects
Basically, what makes objects on the mud different are two things:
1) Some have different functions
2) All have different values
Now, all player objects have the same functions. They are therefore
differentiated by the values they hold. For instance, the player
named \"Forlock\" is different from \"Descartes\" *at least* in that they
have different values for the variable true_name, those being
\"descartes\" and \"forlock\".
Therefore, changes in the game involve changes in the values of the objects
in the game. Functions are used to name specific process for manipulating
values. For instance, the create() function is the function whose
process is specifically to initialize the values of an object.
Within a function, it is specifically things called instructions which are
responsible for the direct manipulation of variables.
6.3 Local and global variables
Like variables in most programming language, LPC variables may be declared
as variables \"local\" to a specific function, or \"globally\" available
to all functions. Local variables are declared inside the function which
will use them. No other function knows about their existence, since
the values are only stored in memory while that function is being executed.
A global variable is available to any function which comes after its
declaration in the object code. Since global variables take up RAM for
the entire existence of the object, you should use them only when
you need a value stored for the entire existence of the object.
Have a look at the following 2 bits of code:
-----
int x;
int query_x() { return x; }
void set_x(int y) { x = y; }
-----
-----
void set_x(int y) {
int x;
x = y;
write(\"x is set to x\"+x+\" and will now be forgotten.\\n\");
}
-----
In the first example, x is declared outside of any functions, and therefore
will be available to any function declared after it. In that example,
x is a global variable.
In the second example, x is declared inside the function set_x(). It
only exists while the function set_x() is being executed. Afterwards,
it ceases to exist. In that example, x is a local variable.
6.4 Manipulating the values of variables
Instructions to the driver are used to manipulate the values of variables.
An example of an instruction would be:
-----
x = 5;
-----
The above instruction is self-explanatory. It assigns to the variable
x the value 5. However, there are some important concepts in involved
in that instruction which are involved in instructions in general.
The first involves the concept of an expression. An expression is
any series of symbols which have a value. In the above instruction,
the variable x is assigned the value of the expression 5. Constant
values are the simplest forms in which expressions can be put. A constant
is a value that never changes like the int 5 or the string \"hello\".
The last concept is the concept of an operator. In the above example,
the assignment operator = is used.
There are however many more operators in LPC, and expressions can get
quite complex. If we go up one level of complexity, we get:
-----
y = 5;
x = y +2;
-----
The first instruction uses the assignment operator to assign the value
of the constant expression 5 to the variable y. The second one
uses the assignment operator to assign to x the value of the expression
(y+2) which uses the addition operator to come up with a value which
is the sum of the value of y and the value of the constant expression 2.
Sound like a lot of hot air?
In another manner of speaking, operators can be used to form complex
expressions. In the above example, there are two expressions in the
one instruction x = y + 2;:
1) the expression y+2
2) the expression x = y + 2
As stated before, all expressions have a value. The expression
y+2 has the value of the sum of y and 2 (here, 7);
The expression x = y + 2 *also* has the value of 7.
So operators have to important tasks:
1) They *may* act upon input like a function
2) They evaluate as having a value themselves.
Now, not all operators do what 1 does. The = operators does act upon
the value of 7 on its right by assigning that value to x. The operator
+ however does nothing. They both, however, have their own values.
6.5 Complex expressions
As you may have noticed above, the expression x = 5 *itself* has a value
of 5. In fact, since LPC operators themselves have value as expressions,
they cal allow you to write some really convoluted looking nonsense like:
i = ( (x=sizeof(tmp=users())) ? --x : sizeof(tmp=children(\"/std/monster\"))-1)
which says basically:
assing to tmp the array returned by the efun users(), then assign to x
the value equal to the number of elements to that array. If the value
of the expression assigning the value to x is true (not 0), then assign
x by 1 and assign the value of x-1 to i. If x is false though,
then set tmp to the array returned by the efun children(), and then
assign to i the value of the number of members in the array tmp -1.
Would you ever use the above statement? I doubt it. However you might
see or use expressions similar to it, since the ability to consolidate
so much information into one single line helps to speed up the execution of
your code. A more often used version of this property of LPC operators
would be something like:
x = sizeof(tmp = users());
while(i--) write((string)tmp[i]->GetKeyName()+\"\\n\");
instead of writing something like:
tmp = users();
x = sizeof(tmp);
for(i=0; i<x; i++) write((string)tmp[i]->GetKeyName()+\"\\n\");
Things like for(), while(), arrays and such will be explained later.
But the first bit of code is more concise and it executed faster.
NOTE: A detailed description of all basic LPC operators follows the chapter
summary.
6.6 Chapter Summary
You now know how to declare variables and understand the difference between
declaring and using them globally or locally. Once you become familiar
with your driver's efuns, you can display those values in many different
ways. In addition, through the LPC operators, you know how to change
and evaluate the values contained in variables. This is useful of course
in that it allows you to do something like count how many apples have
been picked from a tree, so that once all apples have been picked, no
players can pick more. Unfortunately, you do not know how to have
code executed in anything other than a linera fashion. In other words,
hold off on that apple until the next chapter, cause you do not know
how to check if the apples picked is equal to the number of apples in the
tree. You also do not know about the special function init() where you
give new commands to players. But you are almost ready to code a nice,
fairly complex area.
6.7 LPC operators
This section contains a detailed listing of the simpler LPC operators,
including what they do to the values they use (if anything) and the value
that they have.
The operators described here are:
= + - * / % += -= *= /= %=
-- ++ == != > < >= <= ! && ||
-> ? :
Those operators are all described in a rather dry manner below, but it is best
to at least look at each one, since some may not behave *exactly* as
you think. But it should make a rather good reference guide.
= assignment operator:
example: x = 5;
value: the value of the variable on the *left* after its function is done
explanation: It takes the value of any expression on the *right* and
assigns it to the variable on the *left*. Note that you must use
a single variable on the left, as you cannot assign values to
constants or complex expressions.
+ addition operator:
example: x + 7
value: The sum of the value on the left and the value on the right
exaplanation: It takes the value of the expression on the right and
adds it to the value of the expression on the left. For values
of type int, this means the numerical sum. For strings,
it means that the value on the right is stuck onto the value on
the left (\"ab\" is the value of \"a\"+\"b\"). This operator does not
modify any of the original values (i.e. the variable x from
above retains its old value).
- subtraction operator:
example: x - 7
value: the value of the expression on the left reduced by the right
explanation: Same characteristics as addition, except it subtracts.
With strings: \"a\" is the value of \"ab\" - \"b\"
* multiplication operator:
example: x*7
value and explanation: same as with adding and subtracting except
this one performs the math of multiplication
/ division operator:
example: x/7
value and explanation: see above
+= additive assignment operator:
example: x += 5
value: the same as x + 5
exaplanation: It takes the value of the variable on the left
and the value of the expression on the right, adds them together
and assigns the sum to the variable on the left.
example: if x = 2... x += 5 assigns the value
7 to the variable x. The whole expression
has the value of 7.
-= subtraction assignment operator
example: x-=7
value: the value of the left value reduced by the right value
examplanation: The same as += except for subtraction.
*= multiplicative assignment operator
example: x *= 7
value: the value of the left value multiplied by the right
explanation: Similar to -= and += except for addition.
/= division assignment operator
example: x /= 7
value: the value of the variable on the left divided by the right value
explanation: similar to above, except with division
++ post/pre-increment operators
examples: i++ or ++i
values:
i++ has the value of i
++i has the value of i+1
explanation: ++ changes the value of i by increasing it by 1.
However, the value of the expression depends on where you
place the ++. ++i is the pre-increment operator. This means
that it performs the increment *before* giving a value.
i++ is the post-ncrement operator. It evalutes before incrementing
i. What is the point? Well, it does not much matter to you at
this point, but you should recognize what it means.
-- post/pre-decrement operators
examples: i-- or --i
values:
i-- the value of i
--i the value of i reduced by 1
explanation: like ++ except for subtraction
== equality operator
example: x == 5
value: true or false (not 0 or 0)
explanation: it does nothing to either value, but
it returns true if the 2 values are the same.
It returns false if they are not equal.
!= inequality operator
example: x != 5
value: true or false
explanation returns true if the left expression is not equal to the right
expression. It returns fals if they are equal
> greater than operator
example: x > 5
value: true or false
explanation: true only if x has a value greater than 5
false if the value is equal or less
< less than operator
>= greater than or equal to operator
<= less than or equal to operator
examples: x < y x >= y x <= y
values: true or false
explanation: similar as to > except
< true if left is less than right
>= true if left is greater than *or equal to* right
<= true if the left is less than *or equal to* the right
&& logical and operator
|| logical or operator
examples: x && y x || y
values: true or false
explanation: If the right value and left value are non-zero, && is true.
If either are false, then && is false.
For ||, only one of the values must be true for it to evaluate
as true. It is only false if both values indeed
are false
! negation operator
example: !x
value: true or false
explanation: If x is true, then !x is false
If x is false, !x is true.
A pair of more complicated ones that are here just for the sake of being
here. Do not worry if they utterly confuse you.
-> the call other operator
example: this_player()->GetKeyName()
value: The value returned by the function being called
explanation: It calls the function which is on the right in the object
on the left side of the operator. The left expression *must* be
an object, and the right expression *must* be the name of a function.
If not such function exists in the object, it will return 0 (or
more correctly, undefined).
? : conditional operator
example: x ? y : z
values: in the above example, if x is try, the value is y
if x is false, the value of the expression is z
explanation: If the leftmost value is true, it will give the expression as
a whole the value of the middle expression. Else, it will give the
expression as a whole the value of the rightmost expression.
A note on equality: A very nasty error people make that is VERY difficult
to debug is the error of placing = where you mean ==. Since
operators return values, they both make sense when being evaluated.
In other words, no error occurs. But they have very different values. For example:
if(x == 5) if(x = 5)
The value of x == 5 is true if the value of x is 5, false othewise.
The value of x = 5 is 5 (and therefore always true).
The if statement is looking for the expression in () to be either true or false,
so if you had = and meant ==, you would end up with an expression that is
always true. And you would pull your hair out trying to figure out
why things were not happening like they should :)
",({"chapter 30","chapter thirty","30",}):"chapter 30 \"The Natural Language Parser\"
The Natural Language Parser
The Problem
A gut reaction to this entire system is to be overwhelmed by its apparent complexity, comparing it to the good-old days of using add_action(). A discussion of the natural language parsing system therefore needs to start by answering the question, \"Why bother?\".
The old way of handling user input was to define commands and tie them to functions using add_action. Each user object kept track which commands it had available to it, and of which functions each command would trigger. The list of commands changed each time the player object (really, any object which had enable_commands() called in it) moved. In addition, each time an object moved inside the player object or left its inventory, this list changed.
This led to two basic problems:
1. The same command might have slightly different uses in different parts of the mud. Or worse, the player could have two similar objects which define the same command in slightly different ways.
2. The complexity of syntax varied based on creator abilities and was limited by CPU.
For example, one creator could have created a rock in their area that added the 'throw' command. This creator is a newbie creator, and thus simply put the following code in their throw_rock() function:
int throw_rock(string str) {
object ob;
if( !str ) return 0;
ob = present(str, this_player());
if( !ob ) {
notify_fail(\"You have no rock!\");
return 0;
}
if( ob != this_object() ) return 0;
if( (int)ob->move(environment(this_player())) != MOVE_OK ) {
write(\"You cannot throw it for some reason.\");
return 1;
}
write(\"You throw the rock.\");
say((string)this_player()->query_cap_name() + \" throws the rock.\");
return 1;
}
In this case, \"throw rock\" will work, but \"throw the granite rock at tommy\" will not. But another creator also defined a throw command in their spear. This creator however, is a good coder and codes their throw_spear() command to handle 'throw the red spear at tommy' as well as 'throw spear'. Explain to a player why both syntaxes only work for the spear, and not for the rock. Then explain to that player why 'throw rock' for a rock built in yet another area yields 'What?'.
An early attempt to get around this problem was the parse_command(). Unfortunately it was buggy spaghetti that was way too complex for anyone to understand. The MudOS attempt to solve this problem is its new natural language command parser. The parser is based on the following assumptions:
All commands should behave in a consistent manner across the mud.
Similar objects should respond as expected to the same command line.
A player should only see 'What?' (or its equivalent) when they make a typo.
It should enable creators to handle the complex command processing required by the above assumption.
Overview of the MudOS System
The MudOS natural language parser is based on a philosophy of centralized command parsing. In other words, creators have no control over which commands exist nor over what syntax rules existing commands follow. Instead, creators are tasked with defining what those commands mean to their objects. Unlike with add_action() where commands are registered to the driver, the MudOS system registers verbs (the command) and rules with the driver. In this example, a simple \"smile\" verb is registered with a single rule, \"at LIV\".
With the old way of doing things, commands were executed either when a player entered in a command or when the command() efun was called. With this new system, a command may be executed at any time via either the parse_sentence() or parse_my_rules() efuns.
When one of those efuns is called, the driver searches through the list of verbs for a verb and rule which matches the command string. In order to do that, however, it needs to make several calls of all the objects involved to determine what the sentence means. For any given command string, the following objects are relevant to the parsing of that command string:
the verb handler
This object contains the functions used to see if the command is valid and to execute it. It is also the one that creates the rules for the verb.
the subject
This is the object from which parse_sentence() is called, or the object mentioned as the first argument in parse_my_rules(). It is the object considered to be executing the command in questin, the logical subject of the sentence.
the master object
This object keeps track of global information, such was what literals (mistakenly referred to as prepositions) exist across the mud. In general, a literal is a preposition.
the direct object
This is not the logical direct object of the sentence. Rather, this is the first object at which the verb action is targetted.
the indirect object
Again, this is not the logical indirect object of the sentence. Rather, it is the second object at which the verb action is targetted. For example, in \"give the book to the elf\", the elf will be both the logical indirect object and the parser indirect object. But if you allow \"give the elf the book\", the elf naturally still remains the logical indirect object, but the book is the indirect object to the parser since it is the second object targetted by the verb (the first being the elf).
Each object involved in he parsing of a sentence, except the subject, is responsible for handling certain driver applies that help the driver in parsing the sentence. The subject, unlike the other objects, is responsible for initiating the command. Although this document treats all of these objects as if they were completely distinct, it is possible to have the same object performing multiple roles for the same command. For example, your subject could also be direct object and verb handler. The next section discusses the objects and the applies they are responsible for in detail.
The Objects
Before studying each object in detail, it is important to keep in mind that each object which can be involved in any role must call parse_init() before doing anything related to verb parsing. The only exception is the master object.
The subject
The subject is simply the initiator of a command. A command is typically initiated by a call to parse_sentence() inside the object's process_input() apply. This example shows how a player object might use parse_sentence() to initiate a command. This efun will return 1 if the command successfully matched a known verb and rule and successfully executed it. If it found the verb in question, but it did not match any rule, then 0 is returned. If it found the verb in question and matched a rule, but the execution of the rule failed, it will return an errorstring describing why it failed. Finally, if no verb matched the command, then -1 is returned.
Take for example a mud with this one rule:
parse_add_rule(\"smile\", \"at LIV\")
The efun parse_sentence() would return the following values for the following command lines:
smile at descartes
Returns: 1
smile happily
Returns: 0
smile at the red box
Returns: \"The Box is not a living thing!\"
eat the red box
Returns: -1
The master object
The master object is responsible for a single apply, parse_command_prepos_list(). This apply returns a list of literal strings which may be used in a rule. A literal string is simply one that appears in the rule exactly as a player types it. In the smile example above, \"at\" is a literal. In most all cases, literals are prepositions, thus the name of the apply.
The verb handler
The verb handler object is responsible for setting up the rules for a verb and handling the test and execution of those rules. This example demonstrates a simple verb handler for the smile verb described above. As you can see, each rule is divided up into three parts:
1. initialization
2. testing
3. execution
The intialization is the call to parse_add_rule(). This associates a rule with a verb. The first argument is the verb (this verb may have spaces in it, like \"look at\") and the second argument is one of the rules being handled by this verb handler. This list defines the valid tokens for a rule.
The testing portion is a \"can\" apply. In testing a rule, the driver calls the applies can_<verb_rule> to determine if the execution of the verb even makes sense in this situation. The test apply is called when the driver has valid arguments to a rule, but it wants to see if those valid arguments make sense right now. For example, you might check a player here to see if they have enough magic points for casting the spell this verb/rule represents. If not, you might return \"You are too tired right now.\". If the rule match up in question makes completely no sense at all, like for example, they tried to throw a house for the (\"throw\", \"OBJ\") rule, you should return 0. The parser will guess well an error message from the situation. In this case, it will have parse_sentence() return \"You cannot throw the thing.\".
Finally execution is where the verb actually happens. You know you have a verb/rule match and you know all the arguments to it are valid. Now it is time to do something. You almost always want to return 1 from this function except in extreme circumstances.
The direct and indirect objects
As stated above, the directness or indirectness of an object has nothing to do with the linguistic meaning of those terms. Instead it has to do with what position in the token list the object takes. The direct object is the first object in the token list, and the indirect object is the second object in the token list.
These objects basically answer the question \"Can I be the direct/indirect object for this verb and rule?\". Like the testing and execution applies for verb handlers, the applies that answer this question may return 1, 0, or an error string. Also like the testing and execution applies for the verb handler, these come in the form of (in)direct_<verb>_<rule>(). This example is from somewhere inside the living object heirarchy. Note the is_living() apply which lets the parser know that the object matches a LIV token.
Inventory visibility
Some objects are subjects, direct objects, or indirect objects of verbs which require access to things in their inventory. For example, living things, bags, chests, etc. all need to allow other things access to their inventories for some commands. Two applies handle this situation:
1. inventory_accessible()
2. inventory_visible()
The first returns 1 if verbs can have access to an object's inventory, the second returns 1 if verbs simply can take into account an object's inventory. An example of the difference might be a glass chest. When closed, you want its inventory to be visible, but not accessible.
It is important to remember that is the return value for any of these special applies, including is_living(), you need to make an explicit call to the parse_refresh() efun. Unless the parse_refresh() efun is called, these special applies are only called once with no guarantee as to when that one call will actually occur.
Creating a New Verb
Currently, the Lima and Nightmare mudlibs use this parser system. Both mudlibs provide inheritable objects which make it simpler to interface with the MudOS parser system. Nightmare specifically has the inheritable LIB_VERB with methods for defining a new verb.
This verb example comes from the Nightmare mudlib. The simple Nightmare verb requires the following steps:
1. Name the verb
2. State the verb rules
3. Name any synonyms
4. Set an error message for display when the command is wrongly used
5. Create help text for the command
Naming the verb is done through the SetVerb() method. You simply specify the name of the verb.
The rules are passed to the SetRules() method. You may specify as many rules as are needed for the verb.
Like rules, synonyms are set as a list for the SetSynonyms() method. A synonym is simply any verb which is exactly synonymous with any possible rule for the verb in question. The player is able to access help for the verb and get error messages for the verb through the verb or any of its synonyms.
The error message is a string displayed to the user when they use the verb in an incorrect manner. For example, if I typed 'eat' when the rule is 'eat OBJ', the error message would be 'Eat what?'.
Finally, like with any object, the help text can be set through the SetHelp() method. Help is very important for verbs.
All of these methods only are able to take care of verb initalization. It is up to the verb creator to give meaning to a new verb. This is done first off by writing can_*() and do_*() applies in the verb handler. These methods should be very simplistic in nature. For example, a can method almost always simply returns 1. A do method generally finds its target and triggers some sort of event in that object. The event does the real command handling.
In addition to can and do applies, you need also to write any direct and indirect applies in approperiate objects. Nightmare centralizes this sort of processing through inheritables geared towards responding to particular verbs. A good example of this is LIB_PRESS which responds to the \"press\" command. Thus any object which should be pressable needs only to inherit this object to become a pressable object.
The can, do, direct, and indirect applies all have the same argument set for the same verb/rule pair, but it is important to know when the parser knows certan things. Take for example the verb/rule \"press OBJ on OBJ\". The parser takes the following actions:
1. Call can_press_obj_on_obj() in verb handler
2. Call direct_press_obj_on_obj() in all accessible and visible objects
3. Call indirect_press_obj_on_obj() in all accessible and visible objects
4. Call do_press_obj_on_obj() in the verb handler
The arguments to all methods called in this process are:
1. object direct_object
2. object indirect_object
3. string direct_object_as_entered_on_command_line
4. string indirect_object_as_entered_on_command_line
But how can can_press_obj_on_obj() know what the direct and indirect objects are if they have not been identified yet? The answer is that it cannot. For the command \"push the button on the wall\", in a room with me and you in it and we carry nothing, the sequence looks like this (return in parens):
1. verb->can_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (1)
2. me->direct_press_obj_on_obj(0, 0, \"the button\", the wall\"); (0)
3. you->direct_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (0)
4. room->direct_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (1)
5. me->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (0)
6. you->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (0)
7. room->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (1)
8. verb->do_press_obj_on_obj(room, room, \"the buton\", \"the wall\"); (1)
This assumes, of course, that the room responds positively with the id's \"button\" and \"wall\".
People familiar with the parser might say, \"Hey, wait, there is a lot more that happens than just that.\" In fact, there are many more possible permutations of this sequence. The most interesting is the ability to simply ignore the difference between prepositions like \"in\" and \"into\" which are often used interchangeably in colloquial speech. For example, if you had \"put OBJ in OBJ\" and \"put OBJ into OBJ\" verb/rules, you could handle them in a single place for each of the applies respectively liek this:
can_put_obj_word_obj()
direct_put_obj_word_obj()
indirect_put_obj_word_obj()
do_put_obj_word_obj()
If the parser found no can_put_obj_in_obj() defined, it then searches for a more generic handler, can_put_obj_word_obj(). In fact the real order it searches for a can handler is:
1. can_put_obj_in_obj()
2. can_put_obj_word_obj()
3. can_put_rule()
4. can_verb_rule()
---------------------
Last example:
static void create() {
parse_init();
parse_add_rule(\"smile\", \"at LIV\");
}
mixed can_smile_at_liv(object target) {
return 1;
}
mixed do_smile_at_liv(object target) {
previous_object()->eventPrint(\"You smile at \" +
(string)target->GetName() + \".\");
target->eventPrint((string)previous_object()->GetName() +
\" smiles at you.\");
return 1;
}
",({"chapter 3","chapter three","3",}):"chapter 3 \"LPC Data Types\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 17 june 1993
CHAPTER 3: LPC Data Types
3.1 What you should know by now
LPC object are made up of zero or more variables manipulated by one or
more functions. The order in which these functions appear in code is
irrelevant. The driver uses the LPC code you write by loading copies of
it into memory whenever it is first referenced and additional copies
through cloning. When each object is loaded into memory, all the variables
initially point to no value. The reset() function in compat muds, and
create() in native muds are used to give initial values to variables in
objects. The function for creation is called immediately after the object
is loaded into memory. However, if you are reading this textbook with no
prior programming experience, you may not know what a function is or how
it gets called. And even if you have programming experience, you may
be wondering how the process of functions calling each other gets started
in newly created objects. Before any of these questions get answered,
however, you need to know more about what it is the functions are
manipulating. You therefore should thouroughly come to know the concept
behind LPC data types. Certainly the most boring subject in this manual,
yet it is the most crucial, as 90% of all errors (excepting misplaced
{} and ()) involve the improper usage of LPC data types. So bear through
this important chapter, because it is my feeling that understanding this
chapter alone can help you find coding much, much easier.
3.2 Communicating with the computer
You possibly already know that computers cannot understand the letters
and numbers used by humans. Instead, the \"language\" spoken by computers
consists of an \"alphabet\" of 0's and 1's. Certainly you know computers
do not understand natural human languages. But in fact, they do not
understand the computer languages we write for them either. Computer
languages like BASIC, C, C++, Pascal, etc. are all intermediate
languages. They allow you to structure your thoughts more coherently
for translation into the 0's and 1's of the computer's languages.
There are two methods in which translation is done: compilation and
interpretation. These simply are differences betweem when the
programming language is translated into computer language. With
compiled languages, the programmer writes the code then uses a program
called a compiler to translate the program into the computer's
language. This translation occurs before the program is run. With
interpreted languages however, the process of translation occurs as
the program is being run. Since the translation of the program is
occurring during the time of the program's running in interpreted
languages, interpreted languages make much slower programs than
compiled languages.
The bottom line is, no matter what language you are writing in, at
some point this has to be changed into 0's and 1's which can be
understood by the computer. But the variables which you store in
memory are not simply 0's and 1's. So you have to have a way in
your programming languages of telling the computer whether or not
the 0's and 1's should be treated as decimal numbers or characters or
strings or anything else. You do this through the use of data types.
For example, say you have a variable which you call 'x' and you give
it the decimal whole number value 65. In LPC you would do this through
the statement:
-----
x = 65;
-----
You can later do things like:
_____
write(x+\"\\n\"); /* \\n is symbolically represents a carriage return */
y = x + 5;
-----
The first line allows you to send 65 and a carriage return to someone's screen.
The second line lets you set the value of y to 70.
The problem for the computer is that it does not know what '65' means when
you tell it x = 65;. What you think of 65, it might think of as:
00000000000000000000000001000001
But, also, to the computer, the letter 'A' is represented as:
00000000000000000000000001000001
So, whenever you instruct the computer write(x+\"\\n\");, it must have some
way of knowing that you want to see '65' and not 'A'.
The computer can tell the difference between '65' and 'A' through the use
of data types. A data types simply says what type of data is being stored
by the memory location pointed to by a given variable. Thus, each LPC
variable has a variable type which guides conversions. In the example
given above, you would have had the following line somewhere in the
code *before* the lines shown above:
-----
int x;
-----
This one line tells the driver that whatever value x points to, it will
be used as the data type \"int\", which is short for integer, or whole
number. So you have a basic introduction into the reason why data types
exist. They exist so the driver can make sense of the 0's and 1's that
the computer is storing in memory.
3.3 The data types of LPC
All LPMud drivers have the following data types:
void, status, int, string, object, int *, string *, object *, mixed *
Many drivers, but not all have the following important data types which
are important to discuss:
float, mapping, float *, mapping *
And there are a few drivers with the following rarely used data types
which are not important to discuss:
function, enum, struct, char
3.4 Simple data types
This introductory textbook will deal with the data types void, status,
int, float, string, object, mand mixed. You can find out about the
more complex data types like mappings and arrays in the intermediate
textbook. This chapter deals with the two simplest data types (from the
point of view of the LPC coder), int and string.
An int is any whole number. Thus 1, 42, -17, 0, -10000023 are all type int.
A string is one or more alphanumeric characters. Thus \"a\", \"we are borg\",
\"42\", \"This is a string\" are all strings. Note that strings are always
enclosed in \"\" to allow the driver to distinguish between the int 42 and
the string \"42\" as well as to distinguish between variable names (like x)
and strings by the same names (like \"x\").
When you use a variable in code, you must first let the driver know
what type of data to which that variable points. This process is
called *declaration*. You do this at the beginning of the function
or at the beginning of the object code (outside of functions before all
functions which use it). This is done by placing the name of the data type
before the name of the variable like in the following example:
-----
void add_two_and_two() {
int x;
int y;
x = 2;
y = x + x;
}
-----
Now, this is a complete function. The name of the function is
add_two_and_two(). The function begins with the declaration of an
int variable named x followed by the declaration of an in variable
named y. So now, at this point, the driver now has two variables which
point to NULL values, and it expects what ever values end up there to be
of type int.
A note about the data types void and status:
Void is a trivial data type which points to nothing. It is not used
with respect to variables, but instead with respect to functions. You
will come to understand this better later. For now, you need only
understand that it points to no value.
The data type status is a boolean data type. That is, it can only have
1 or 0 as a value. This is often referred to as being true or false.
3.5 Chapter summary
For variables, the driver needs to know how the 0's and 1's the computer
stores in memory get converted into the forms in which you intend them
to be used. The simplest LPC data types are void, status, int, and string.
You do not user variables of type void, but the data type does come
into play with respect to functions. In addition to being used for
translation from one form to the next, data types are used in determining
what rules the driver uses for such operations as +, -, etc. For example,
in the expression 5+5, the driver knows to add the values of 5 and 5
together to make 10. With strings however, the rules for int addition
make no sense. So instead, with \"a\"+\"b\", it appends \"b\" to the string \"a\"
so that the final string is \"ab\". Errors can thus result if you mistakenly
try to add \"5\"+5. Since int addition makes no sense with strings, the
driver will convert the second 5 to \"5\" and use string addition. The final
result would be \"55\". If you were looking for 10, you would therefore
have ended up with erroneous code. Keep in mind, however, that in most
instances, the driver will not do something so useful as coming up with
\"55\". It comes up with \"55\" cause it has a rule for adding a string
to an int, namely to treat the int as a string. In most cases, if you
use a data type for which an operation or function is not defined
(like if you tried to divide \"this is\" by \"nonsense\", \"this is\"/\"nonsense\"),
the driver will barf and report an error to you.
",({"chapter 18","chapter eighteen","18",}):"chapter 18 \"Valid climates\"
indoors
temperate
arid
arctic
tropical
sub-tropical
",({"chapter 35","chapter thirty-five","35",}):"chapter 35 \"QCS: Modifying rooms\"
Suppose you are in your sample room and you issued the
command:
%^GREEN%^create room south testroom1%^RESET%^
You then travel south and see that you are in a room that is
almost exactly like the sample room except for the exits. Well,
probably you don't want to have a mud with nothing but identical
rooms, so let's modify it:
%^GREEN%^modify here short Test Room One%^RESET%^
%^GREEN%^modify here long This is the first test room. The walls are rather blank.%^RESET%^
%^GREEN%^modify here climate indoors%^RESET%^
%^GREEN%^modify here light 30%^RESET%^
Ok, so far so good. Standard interior. However, a good mud has
rooms with details. Let's add some detail to this room.
I've omitted the system output for clarity. This is just what you
would input.
%^GREEN%^modify here item%^RESET%^
%^GREEN%^wall%^RESET%^
%^GREEN%^walls%^RESET%^
%^GREEN%^blank wall%^RESET%^
%^GREEN%^blank walls%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^These are just blank walls.%^RESET%^
Let's review what we've done here:
1) You issued the modify command specifying your current room as the
target, and the SetItems directive as the argument.
2) You entered a query session, and were asked to enter each element
of the item's key.
3) You entered a single dot to indicate you were done entering
key elements.
4) You entered the value for the key, which is the description of the
item.
The result of all this is that now you can issue these commands:
%^GREEN%^exa wall%^RESET%^
%^GREEN%^look at blank walls%^RESET%^
%^GREEN%^examine walls%^RESET%^
And the output will be:
These are just blank walls.
Let's add a floor while we're at it:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^floor%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A floor like any other.%^RESET%^
In this case, you didn't feel like adding extra synonyms for \"floor\",
so you entered the final dot rather than entering another key element.
Then you added the description, and now if you \"exa floor\", you'll get
that description.
\"about here\" will display to you the file you have modified.
Well, that's enough fun with indoor rooms. There's not much more
to them. Let's go outdoors now:
%^GREEN%^create room south exterior_room%^RESET%^
%^GREEN%^create door south test_door%^RESET%^
%^GREEN%^open door%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^modify here short a small lawn%^RESET%^
%^GREEN%^modify here daylong A small, well groomed lawn on a lovely sunny day. There is a small building north of here.%^RESET%^
%^GREEN%^modify here nightlong This is a small lawn. Stars twinkle in the night sky above, and some light is coming from a small building to the north.%^RESET%^
%^GREEN%^modify here daylight 30%^RESET%^
%^GREEN%^modify here nightlight 20%^RESET%^
%^GREEN%^modify here light delete%^RESET%^
%^GREEN%^modify here long delete%^RESET%^
%^GREEN%^modify here items delete%^RESET%^
%^GREEN%^modify here items%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^small building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A small building, rather ramshackle as if hastily put together.%^RESET%^
%^GREEN%^modify here climate temperate%^RESET%^
Ok! A few new things here. A neat thing about outdoor rooms is that
typically they are subject to the time of day. A SetClimate directive
that indicates an exterior environment causes the room to receive
messages about the sun setting, rising, etc.
The SetDayLong and SetNightLong directives allow you to more
sensibly describe the area depending on the time of day. To avoid
confusion, I deleted the SetLong directive. It is not mandatory to
have different day and night descriptions, but players appreciate the
effort.
It is also possible to have differing ambient light levels depending
on the time of day, so we've added SetDayLight and SetNightLight, and
we deleted the SetAmbientLight directive.
Let's continue to add detail:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Healthy, well groomed and freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You can smell the refreshing scent of freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Yep, it's got that new lawn smell.%^RESET%^
%^GREEN%^modify here listen%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Sounds like someone's fumbling about in there, making a mess. New creators can be so noisy.%^RESET%^
%^GREEN%^modify here item%^RESET%^
%^GREEN%^garden%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You may enter the garden from here.%^RESET%^
%^GREEN%^create room garden garden_room%^RESET%^
You now have a room with lots of charm and detail. You can \"smell grass\"
and \"listen to small building\", if you like. Neat, huh? But there's something
very important to keep in mind:
Enters, listens and smells don't work properly if there is no item defined
for that smell. For example, if you want to be able to listen to the sea,
you must \"modify here item\" and add a \"sea\" item. Otherwise, \"listen
to the sea\" will respond with \"There is no sea here.\"
The only exception to this rule is the \"default\" smell.
Enters behave similarly. If you want to be able to \"enter\" something,
you'll need to create the corresponding item first, as in the example above.
You can use the SetProperties directive to make the room
conform to some presets, like:
%^GREEN%^modify here property no attack 1%^RESET%^
Read chapter 23 in the Creator's Manual for details on room properties.
Also, please note that indoor rooms can also have differing
descriptions and light levels for night and day. It's just that
indoor rooms don't get notification of daytime changes.
Finally, the SetTown directive allows the room to participate in
area-wide events, and is useful for security purposes as well:
%^GREEN%^modify here town MyTown%^RESET%^
Notes on room filenames:
-----------------------
By default, a filename without a leading path creates a room
in your area room directory, which in my case would be
\"/realms/cratylus/area/room\". However, you can specify a different
location for the new room.
To create a room in your current working directory:
%^GREEN%^create room east ./newroom%^RESET%^
To create a room in a specific directory:
%^GREEN%^create room east /realms/cratylus/testrooms/newroom%^RESET%^
",({"chapter 26","chapter twenty-six","26",}):"chapter 26 \"Sentients\"
Building Sentient Non-Player Characters
The Nightmare IV Object Library
written by Descartes of Borg 951127
One thing most everyone wants to see are monsters that react more
intelligently to user input. The fact is, however, that most monsters
in the game only need a small, basic behaviour set. Nevertheless, in
order to make an area interesting, there should be some monsters which
stand out as unique and purposeful. The problem about building such
monsters is that they use a lot of processing time.
In order to make sure most monsters which do not need such
intelligence do not waste processing time on such activities, the
Nightmare Object Library separates non-player characters into two
classes: dumb monsters, which are basic mindless automata and
sentients, monsters which react more intelligently to their
environment.
This document describes sentients. Before looking at this document,
it is highly recommended that you be familiar with the document
/doc/build/NPC which details non-player characters. Sentients are
non-player characters, so everthing which applies to non-player
characters also applies to sentients.
*****
Currently, a few basic behaviours distinguish sentients from normal
npcs. Those behaviours are the ability to intelligently move about
the mud and to react to user speech. Nightmare thus provides the
following functions to allow you to easily have an sentient enact
those behaviours:
mapping SetTalkResponses(mapping mp);
mixed AddTalkResponse(string str, mixed val);
int RemoveTalkResponse(string str);
mapping SetCommandResponses(mapping mp);
mixed AddCommandResponse(string str, mixed val);
int RemoveCommandResponse(string str);
varargs int SetWander(int speed, string *path, int recurse);
string *SetWanderPath(string *path);
int SetWanderRecurse(int x);
int SetWanderSpeed(int x);
*****
Making NPCs react to user speech
You may want to have NPCs react to things players say. To that end,
the following functions exist:
mapping SetTalkResponses(mapping mp);
mixed AddTalkResponse(string str, mixed val);
int RemoveTalkResponse(string str);
Function: mapping SetTalkResponses(mapping mp)
Example: SetTalkResponses( ([ \"square\" : \"The square is east of here.\",
\"house\" : \"Isn't that an ugly house?\" ]) );
This function allows you to set a list of responses to given phrases.
For example, if you put this code in a sentient and a player said
\"Where is the square?\" or \"Your frog is certainly square.\", your NPC
would have said \"The square is east of here.\". Note therefore that
the NPC is only looking for the keys you place in there. You could
have restricted it to \"where is the square\" instead of \"square\", but
then someone asking \"Where's the square\" would be missed.
Also note that phrases should be in lower case. It will match to
upper case words automatically.
Finally, you can either give a string or a function as the match to a
phrase. If the match is a string, the NPC simply says the string in
the NPC's native tongue. If, however, the match is a function, that
function will get called.
*****
Function: mixed AddTalkResponse(string str, mixed val);
Example: AddTalkResponse(\"in the house\", (: HouseFunc :));
Matches an individual phrase to a string or function. As with
SetTalkResponses(), if the match is a string, the NPC simply says the
string in response to the phrase. If it is a function, that function
gets called.
*****
Function: int RemoveTalkResponse(string str);
Example: RemoveTalkResponse(\"house\");
Removes the previous set or added talk response from the NPC.
*****
Making NPCs react to user directives
Nightmare supports a special command, the \"ask\" command. A player may
use the ask command to ask an NPC to perform a certain task. For
example, \"ask the healer to mend my right leg\". There is a special
event in NPC's which responds to this called eventAsk(). In order to
make responding to this easier, however, Nightmare has the
CommandResponse functions. The command response functions allow NPC's
to respond based on commands, like \"mend\".
*****
Function: mapping SetCommandResponses(mapping mp);
Example: SetCommandResponses( ([ \"heal\", \"I cannot heal people\" ]) );
Allows you to match commands to either strings or functions. Matched
functions get called with the command as the first argument, and
command arguments as the second argument. For example, if you had:
SetCommandResponses(\"give\", (: give :));
Your give() function would get called with \"give\" as the first
argument and \"me the sword\" as the second argument in response to a
player issuing the command \"ask the monster to give me the sword\".
*****
Function: mixed AddCommandResponse(string str, mixed val);
Example: AddCommandResponse(\"give\", (: give :));
This allows you to add to the list of commands to which the NPC
responds. The NPC responds to those commands as outlined for
SetCommandResponses().
*****
Function: int RemoveCommandResponse(string str);
Example: RemoveCommandResponse(\"give\")
Removes a previously set command response.
*****
Making NPCs move about the game intelligently
A sticky subject on most muds is that of wandering monsters. When
done poorly, they can waste resources to a great degree. Nightmare,
however, works to avoid wasting resources while getting the most out
of allowing monsters to move about.
Nightmare supports two types of wandering monsters: those which have
pre-determined paths and others which are true wanderers. True
wanderers, those who simply randomly choose paths are subject to the
following restrictions:
They may not move into rooms not yet loaded in memory.
They will not try to open closed doors.
The first restriction is the most important to note. This means that
the NPC will not wander into rooms that have not been recently visited
by some player. This avoids the problem NPCs cause on many muds of
uselessly loading rooms that only the monster will ever see.
Monsters given specific paths to wander are not subject to the above
restrictions. Of course, they cannot wander through closed doors.
But you can make part of their path to open a closed door. In
addition, since such monsters have very specific sets of rooms into
which they can travel, they are not in danger of needlessly loading a
zillion rooms.
*****
Function: varargs int SetWander(int speed, string *path, int recurse);
Examples:
SetWander(5);
SetWander(5, ({ \"go north\", \"open door\", \"enter hut\", \"go west\" }));
SetWander(5, ({ \"go north\", \"open door\", \"enter hut\", \"go west\",
\"go south\" }), 1);
This is the function you will almost always use in create() to make a
sentient wander. Only one of the three possible arguments is
mandatory, that being the speed. The speed is simply the number of
heart beats between attempts to move. Thus, the higher the number,
the slower the movement of the monster.
The second argument, if given, is a list of commands which will be
executed in order by the monster. If it is not given, the monster
will be assumed to be a true wanderer. In other words, the first time
the monster tries to wander, the monster will \"go north\". The second
time, he will \"open door\". The third, he will \"enter hut\", etc.
The third argument is either 1 or 0. If 1, that means once the
monster has completed the path, it will use the first command in the
list the next time it tries to wander. If 0, it will cease to issue
commands once it has cycled through the list.
You might note that between the time the above monster opens the door
and enters the hut, somebody could come along and shut the door. How
can you deal with that? You could do:
SetWander(5, ({ \"go north\", ({ \"open door\", \"enter hut\" }) }));
You will notice here that the second member of the command array is
itself an array instead of a string. In that case, all members of
that array get executed as part of that wander. In this case it helps
make sure no one closes the door between when the monster tries to
open it and when it tries to pass through the door.
For even more flexibility, you can make elements of the array into
functions. Instead of executing a command in a wander turn, the
function you provide instead gets called. For example:
SetWander(5, ({ \"go north\", (: kill_anyone :), \"go south\" }), 1);
Where the function kill_anyone() has the monster kill any players in
that room. Thus, this monster sits in its room and occasionally pops
its head one room to the north to kill anyone sitting there.
*****
Function: string *SetWanderPath(string *path);
Example: SetWanderPath(({ \"go north\", \"go south\" }))
Allows you to set the monster's wander path independent of other
settings. The wander path will never get executed, however, unless
the monster's wander speed is greater than 0.
*****
Function: int SetWanderRecurse(int x);
Example: SetWanderRecurse(1);
Allows you to make the monster's wander path recurse independent of
other settings. This is meaningless, however, unless the monster's
wander speed is greater than 0 and a wander path is set for it.
*****
Function: int SetWanderSpeed(int x);
Example: SetWanderSpeed(5);
Allows you to set the monster's wander speed independent of other
settings. This is NOT the same as SetWander(5). SetWander() will
clear out any previous wander path and wander recurse settings. This
function has no effect on the monster's wander path or wander recurse.
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Flow Control\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 10 july 1993
CHAPTER 7: Flow Control
7.1 Review of variables
Variables may be manipulated by assigning or changing values with the
expressions =, +=, -=, ++, --. Those expressions may be combined with
the expressions -, +, *, /, %. However, so far, you have only been
shown how to use a function to do these in a linear way. For example:
int hello(int x) {
x--;
write(\"Hello, x is \"+x+\".\\n\");
return x;
}
is a function you should know how to write and understand. But what
if you wanted to write the value of x only if x = 1? Or what if
you wanted it to keep writing x over and over until x = 1 before
returning? LPC uses flow control in exactly the same way as C and C++.
7.2 The LPC flow control statements
LPC uses the following expressions:
if(expression) instruction;
if(expression) instruction;
else instruction;
if(expression) instruction;
else if(expression) instruction;
else instruction;
while(expression) instruction;
do { instruction; } while(expression);
switch(expression) {
case (expression): instruction; break;
default: instruction;
}
Before we discuss these, first something on what is meant by expression and
instruction. An expression is anything with a value like a variable,
a comparison (like x>5, where if x is 6 or more, the value is 1, else the
value is 0), or an assignment(like x += 2). An instruction can be any
single line of lpc code like a function call, a value assignment or
modification, etc.
You should know also the operators &&, ||, ==, !=, and !. These are the
logical operators. They return a nonzero value when true, and 0 when false.
Make note of the values of the following expressions:
(1 && 1) value: 1 (1 and 1)
(1 && 0) value: 0 (1 and 0)
(1 || 0) value: 1 (1 or 0)
(1 == 1) value: 1 (1 is equal to 1)
(1 != 1) value: 0 (1 is not equal to 1)
(!1) value: 0 (not 1)
(!0) value: 1 (not 0)
In expressions using &&, if the value of the first item being compared
is 0, the second is never tested even. When using ||, if the first is
true (1), then the second is not tested.
7.3 if()
The first expression to look at that alters flow control is if(). Take
a look at the following example:
1 void reset() {
2 int x;
3
4 ::reset();
5 x = random(10);
6 if(x > 50) SetSearch_func(\"floorboards\", \"search_floor\");
7 }
The line numbers are for reference only.
In line 2, of course we declare a variable of type int called x. Line 3
is aethetic whitespace to clearly show where the declarations end and the
function code begins. The variable x is only available to the function
reset().
Line 4 makes a call to the room.c version of reset().
Line 5 uses the driver efun random() to return a random number between
0 and the parameter minus 1. So here we are looking for a number between
0 and 99.
In line 6, we test the value of the expression (x>50) to see if it is true
or false. If it is true, then it makes a call to the room.c function
SetSearch_func(). If it is false, the call to SetSearch_func() is never
executed.
In line 7, the function returns driver control to the calling function
(the driver itself in this case) without returning any value.
If you had wanted to execute multiple instructions instead of just the one,
you would have done it in the following manner:
if(x>50) {
SetSearch_func(\"floorboards\", \"search_floor\");
if(!present(\"beggar\", this_object())) make_beggar();
}
Notice the {} encapsulate the instructions to be executed if the test
expression is true. In the example, again we call the room.c function
which sets a function (search_floor()) that you will later define yourself
to be called when the player types \"search floorboards\" (NOTE: This is
highly mudlib dependent. Nightmare mudlibs have this function call.
Others may have something similar, while others may not have this feature
under any name). Next, there is another if() expression that tests the
truth of the expression (!present(\"beggar\",this_object())). The ! in the
test expression changes the truth of the expression which follows it. In
this case, it changes the truth of the efun present(), which will return
the object that is a beggar if it is in the room (this_object()), or it
will return 0 if there is no beggar in the room. So if there is a beggar
still living in the room, (present(\"beggar\", this_object())) will have
a value equal to the beggar object (data type object), otherwise it will
be 0. The ! will change a 0 to a 1, or any nonzero value (like the
beggar object) to a 0. Therefore, the expression
(!present(\"beggar\", this_object())) is true if there is no beggar in the
room, and false if there is. So, if there is no beggar in the room,
then it calls the function you define in your room code that makes a
new beggar and puts it in the room. (If there is a beggar in the room,
we do not want to add yet another one :))
Of course, if()'s often comes with ands or buts :). In LPC, the formal
reading of the if() statement is:
if(expression) { set of intructions }
else if(expression) { set of instructions }
else { set of instructions }
This means:
If expression is true, then do these instructions.
Otherise, if this second expression is true, do this second set.
And if none of those were true, then do this last set.
You can have if() alone:
if(x>5) write(\"Foo,\\n\");
with an else if():
if(x > 5) write(\"X is greater than 5.\\n\");
else if(x >2) write(\"X is less than 6, but greater than 2.\\n\");
with an else:
if(x>5) write(\"X is greater than 5.\\n\");
else write(\"X is less than 6.\\n\");
or the whole lot of them as listed above. You can have any number of
else if()'s in the expression, but you must have one and only one
if() and at most one else. Of course, as with the beggar example,
you may nest if() statements inside if() instructions. (For example,
if(x>5) {
if(x==7) write(\"Lucky number!\\n\");
else write(\"Roll again.\\n\");
}
else write(\"You lose.\\n\");
7.4 The statements: while() and do {} while()
Prototype:
while(expression) { set of instructions }
do { set of instructions } while(expression);
These allow you to create a set of instructions which continue to
execute so long as some expression is true. Suppose you wanted to
set a variable equal to a player's level and keep subtracting random
amounts of either money or hp from a player until that variable equals
0 (so that player's of higher levels would lose more). You might do it
this way:
1 int x;
2
3 x = (int)this_player()->query_level(); /* this has yet to be explained */
4 while(x > 0) {
5 if(random(2)) this_player()->add_money(\"silver\", -random(50));
6 else this_player()->add_hp(-(random(10));
7 x--;
8 }
The expression this_player()->query_level() calIn line 4, we start a loop that executes so long as x is greater than 0.
Another way we could have done this line would be:
while(x) {
The problem with that would be if we later made a change to the funtion
y anywhere between 0 and 49 coins.
In line 6, if instead it returns 0, we call the add_hp() function in the
player which reduces the player's hit points anywhere between 0 and 9 hp.
In line 7, we reduce x by 1.
At line 8, the execution comes to the end of the while() instructions and
goes back up to line 4 to see if x is still greater than 0. This
loop will keep executing until x is finally less than 1.
You might, however, want to test an expression *after* you execute some
instructions. For instance, in the above, if you wanted to execute
the instructions at least once for everyone, even if their level is
below the test level:
int x;
x = (int)this_player()->query_level();
do {
if(random(2)) this_player()->add_money(\"silver\", -random(50));
else this_player()->add_hp(-random(10));
x--;
} while(x > 0);
This is a rather bizarre example, being as few muds have level 0 players.
And even still, you could have done it using the original loop with
a different test. Nevertheless, it is intended to show how a do{} while()
works. As you see, instead of initiating the test at the beginning of the
loop (which would immediately exclude some values of x), it tests after
the loop has been executed. This assures that the instructions of the loop
get executed at least one time, no matter what x is.
7.5 for() loops
Prototype:
for(initialize values ; test expression ; instruction) { instructions }
initialize values:
This allows you to set starting values of variables which will be used
in the loop. This part is optional.
test expression:
Same as the expression in if() and while(). The loop is executed
as long as this expression (or expressions) is true. You must have a
test expression.
instruction:
An expression (or expressions) which is to be executed at the end of each
loop. This is optional.
Note:
for(;expression;) {}
IS EXACTLY THE SAME AS
while(expression) {}
Example:
1 int x;
2
3 for(x= (int)this_player()->query_level(); x>0; x--) {
4 if(random(2)) this_player()->add_money(\"silver\", -random(50));
5 else this_player()->add_hp(-random(10));
6 }
This for() loop behaves EXACTLY like the while() example.
Additionally, if you wanted to initialize 2 variables:
for(x=0, y=random(20); x<y; x++) { write(x+\"\\n\"); }
Here, we initialize 2 variables, x and y, and we separate them by a
comma. You can do the same with any of the 3 parts of the for()
expression.
7.6 The statement: switch()
Prototype:
switch(expression) {
case constant: instructions
case constant: instructions
...
case constant: instructions
default: instructions
}
This is functionally much like if() expressions, and much nicer to the
CPU, however most rarely used because it looks so damn complicated.
But it is not.
First off, the expression is not a test. The cases are tests. A English
sounding way to read:
1 int x;
2
3 x = random(5);
4 switch(x) {
5 case 1: write(\"X is 1.\\n\");
6 case 2: x++;
7 default: x--;
8 }
9 write(x+\"\\n\");
is:
set variable x to a random number between 0 and 4.
In case 1 of variable x write its value add 1 to it and subtract 1.
In case 2 of variable x, add 1 to its value and then subtract 1.
In other cases subtract 1.
Write the value of x.
switch(x) basically tells the driver that the variable x is the value
we are trying to match to a case.
Once the driver finds a case which matches, that case *and all following
cases* will be acted upon. You may break out of the switch statement
as well as any other flow control statement with a break instruction in
order only to execute a single case. But that will be explained later.
The default statement is one that will be executed for any value of
x so long as the switch() flow has not been broken. You may use any
data type in a switch statement:
string name;
name = (string)this_player()->GetKeyName();
switch(name) {
case \"descartes\": write(\"You borg.\\n\");
case \"flamme\":
case \"forlock\":
case \"shadowwolf\": write(\"You are a Nightmare head arch.\\n\");
default: write(\"You exist.\\n\");
}
For me, I would see:
You borg.
You are a Nightmare head arch.
You exist.
Flamme, Forlock, or Shadowwolf would see:
You are a Nightmare head arch.
You exist.
Everyone else would see:
You exist.
7.7 Altering the flow of functions and flow control statements
The following instructions:
return continue break
alter the natural flow of things as described above.
First of all,
return
no matter where it occurs in a function, will cease the execution of that
function and return control to the function which called the one the
return statement is in. If the function is NOT of type void, then a
value must follow the return statement, and that value must be of a
type matching the function. An absolute value function would look
like this:
int absolute_value(int x) {
if(x>-1) return x;
else return -x;
}
In the second line, the function ceases execution and returns to the calling
function because the desired value has been found if x is a positive
number.
continue is most often used in for() and while statements. It serves
to stop the execution of the current loop and send the execution back
to the beginning of the loop. For instance, say you wanted to avoid
division by 0:
x= 4;
while( x > -5) {
x--
if(!x) continue;
write((100/x)+\"\\n\");
}
write(\"Done.\\n\")
You would see the following output:
33
50
100
-100
-50
-33
-25
Done.
To avoid an error, it checks in each loop to make sure x is not 0.
If x is zero, then it starts back with the test expression without
finishing its current loop.
In a for() expression
for(x=3; x>-5; x--) {
if(!x) continue;
write((100/x)+\"\\n\");
}
write(\"Done.\\n\");
It works much the same way. Note this gives exactly the same output
as before. At x=1, it tests to see if x is zero, it is not, so it
writes 100/x, then goes back to the top, subtracts one from x, checks to
see if it is zero again, and it is zero, so it goes back to the top
and subtracts 1 again.
break
This one ceases the function of a flow control statement. No matter
where you are in the statement, the control of the program will go
to the end of the loop. So, if in the above examples, we had
used break instead of continue, the output would have looked like this:
33
50
100
Done.
continue is most often used with the for() and while() statements.
break however is mostly used with switch()
switch(name) {
case \"descartes\": write(\"You are borg.\\n\"); break;
case \"flamme\": write(\"You are flamme.\\n\"); break;
case \"forlock\": write(\"You are forlock.\\n\"); break;
case \"shadowwolf\": write(\"You are shadowwolf.\\n\"); break;
default: write(\"You will be assimilated.\\n\");
}
This functions just like:
if(name == \"descartes\") write(\"You are borg.\\n\");
else if(name == \"flamme\") write(\"You are flamme.\\n\");
else if(name == \"forlock\") write(\"You are forlock.\\n\");
else if(name == \"shadowwolf\") write(\"You are shadowwolf.\\n\");
else write(\"You will be assimilated.\\n\");
except the switch statement is much better on the CPU.
If any of these are placed in nested statements, then they alter the
flow of the most immediate statement.
7.8 Chapter summary
This chapter covered one hell of a lot, but it was stuff that needed to
be seen all at once. You should now completely understand if() for()
while() do{} while() and switch(), as well as how to alter their flow
using return, continue, and break. Effeciency says if it can be done in
a natural way using switch() instead of a lot of if() else if()'s, then
by all means do it. You were also introduced to the idea of calling
functions in other objects. That however, is a topic to be detailed later.
You now should be completely at ease writing simple rooms (if you have
read your mudlib's room building document), simple monsters, and
other sorts of simple objects.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction to the Coding Environment\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 25 may 1993
CHAPTER 1: Introduction to the Coding Environment
1.1 UNIX file structure
LPMuds use basic UNIX commands and its file structure. If you know
UNIX commands already, then note (with a few exceptions) options are
not available to the commands. Like DOS, UNIX is heirarchical. The
root directory of which all directories are sub-directories is called
root(/). And from those sub-directories you may have further
sub-directories. A directory may be referred to in two different ways:
1) by its full name, or absolute name, or 2) by its relative name.
Absolute name refers to the directory's full path starting from / winding
down the directory tree until you name the directory in question. For
example:
/players/descartes/obj/monster
refers to the directory monster which is a sub-directory of obj which
is a sub-directory of descartes which is a sub-directory of players
which is a sudirectory of /.
The relative name refers to the name relative to another directory.
The above example is called monster relative to /players/descartes/obj,
but it is also called obj/monster relative to /players/descartes,
descartes/obj/monster relative to /players, and finally
players/descartes/obj/monster relative to /. You can tell the
difference between absolute names and relative names because absolute
names always start with /. In order to know exactly which directory
is being named by a relative name, you naturally must know what
directory it is relative to.
A directory contains sub-directories and files. LPMuds only use text files
inside the mudlib. Like directories, files have both absolute and
relative names. The most basic relative name is often referred to as the file
name, with the rest of the absolute name being referred to as the path. So,
for the file: /players/descartes/castle.c, castle.c is the file name, and
/players/descartes is the path.
On some muds, a file with a file name beginning with a . (like .plan) is
not visible when you list files with the regular file listing command.
1.2 UNIX Commands
Along with the UNIX file structure, LPMuds use many UNIX commands. Typical
UNIX commands on most muds are:
pwd, cd, ls, rm, mv, cp, mkdir, rmdir, more, head, cat, ed
If you have never before seen UNIX commands, you probably are thinking this
is all nonsense. Well, it is, but you got to use them. Before getting
into what they mean though, first a discussion of current directory.
If you know DOS, then you know what a current working directory is.
At any given point, you are considered to be \"in\" some directory. This
means that any relative file or directory names you give in UNIX commands
are relative to that directory. For example, if my current directory is
/players/descartes and I type \"ed castle.c\" (ed is the command to edit),
then it assumes I mean the file /players/descartes/castle.c
pwd: shows you your current working directory
cd: changes your current working directory. You may give either relative
or absolute path names. With no arguments, it changes to your home
directory.
ls: lists all files in the directory named. If no directory is named,
it lists the files of the current working directory
rm: deletes the file named
mv: renames the file named
cp: copies the file named
mkdir: makes a new directory
rmdir: deletes a directory. All files must have been first removed.
more: pages the file named so that the file appears on your screen one
page at a time.
cat: shows the whole file to you at once
head: shows you the first several lines of a file
tail: shows you the last several lines of a file
ed: allows you to edit a file using the mud editor
1.3 Chapter Summary
UNIX uses a heirarchical file structure with the root of the tree being
named /. Other directories branch off from that root directory and
in turn have their own sub-directories. All directories may contain
directories and files. Directories and files are referred to either
by their absolute name, which always begins with /, or by their relative
name which gives the file's name relative to a particular directory.
In order to get around in the UNIX files structure, you have the
typical UNIX commands for listing files, your current directory, etc.
On your mud, all of the above commands should have detailed help commands
to help you explore exactly what they do. In addition, there should
be a very detailed file on your mud's editor. If you are unfamiliar
with ed, you should go over this convoluted file.
",({"chapter 15","chapter fifteen","15",}):"chapter 15 \"Debugging\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 7: Debugging
7.1 Types of Errors
By now, you have likely run into errors here, there, and everywhere. In
general, there are three sorts of errors you might see: compile time
errors, run time errors, and malfunctioning code. On most muds you
will find a personal file where your compile time errors are logged. For
the most part, this file can be found either in your home directory as the
file named \"log\" or \".log\", or somewhere in the directory \"/log\" as a file
with your name.. In addition, muds tend to keep a log of run time errors
which occur while the mud is up. Again, this is generally found in
\"/log\". On MudOS muds it is called \"debug.log\". On other muds it may
be called something different like \"lpmud.log\". Ask your administrators
where compile time and run time errors are each logged if you do not
already know.
Compile time errors are errors which occur when the driver tries to load
an object into memory. If, when the driver is trying to load an object
into memory, it encounters things which it simply does not understand
with respect to what you wrote, it will fail to load it into memory and log
why it could not load the object into your personal error log. The most
common compile time errors are typos, missing or extra (), {}. [], or \"\",
and failure to declare properly functions and variables used by the
object.
Run time errors occur when something wrong happens to an object in
memory while it is executing a statement. For example, the driver
cannot tell whether the statement \"x/y\" will be valid in all circumstances.
In fact, it is a valid LPC expression. Yet, if the value of y is 0, then a
run time error will occur since you cannot divide by 0. When the driver
runs across an error during the execution of a function, it aborts
execution of the function and logs an error to the game's run time error
log. It will also show the error to this_player(), if defined, if the player
is a creator, or it will show \"What?\" to players. Most common causes
for run time errors are bad values and trying to perform operations with
data types for which those operations are not defined.
The most insideous type of error, however, is plain malfunctioning
code. These errors do not log, since the driver never really realizes that
anything is wrong. In short, this error happens when you think the code
says one thing, but in fact it says another thing. People too often
encounter this bug and automatically insist that it must be a mudlib or
driver bug. Everyone makes all types of errors though, and more often
than not when code is not functioning the way you should, it will be
because you misread it.
7.2 Debugging Compile Time Errors
Compile time errors are certainly the most common and simplest bugs to
debug. New coders often get frustrated by them due to the obscure
nature of some error messages. Nevertheless, once a person becomes
used to the error messages generated by their driver, debugging compile
time errors becomes utterly routine.
In your error log, the driver will tell you the type of error and on which
line it finally noticed there was an error. Note that this is not on which
line the actual error necessarily exists. The most common compile time
error, besides the typo, is the missing or superfluous parentheses,
brackets, braces, or quotes. Yet this error is the one that most baffles
new coders, since the driver will not notice the missing or extra piece
until well after the original. Take for example the following code:
1 int test(string str) {
2 int x;
3 for(x =0; x<10; x++)
4 write(x+\"\\n\");
5 }
6 write(\"Done.\\n\");
7 }
Depending on what you intended, the actual error here is either at line 3
(meaning you are missing a {) or at line 5 (meaing you have an extra }).
Nevertheless, the driver will report that it found an error when it gets to
line 6. The actual driver message may vary from driver to driver, but no
matter which driver, you will see an error on line 6, since the } in line 5
is interpreted as ending the function test(). At line 6, the driver sees that
you have a write() sitting outside any function definition, and thus
reports an error. Generally, the driver will also go on to report that it
found an error at line 7 in the form of an extra }.
The secret to debugging these is coding style. Having closing } match
up vertically with the clauses they close out helps you see where you are
missing them when you are debugging code. Similarly, when using
multiple sets of parentheses, space out different groups like this:
if( (x=sizeof(who=users()) > ( (y+z)/(a-b) + (-(random(7))) ) )
As you can see, the parentheses for the for() statement, are spaced out
from the rest of the statement. In addition, individual sub-groups are
spaced so they can easily be sorted out in the event of an error.
Once you have a coding style which aids in picking these out, you learn
which error messages tend to indicate this sort of error. When
debugging this sort of error, you then view a section of code before and
after the line in question. In most all cases, you will catch the bug right
off.
Another common compile time error is where the driver reports an
unknown identifier. Generally, typos and failure to declare variables
causes this sort of error. Fortunately, the error log will almost always
tell you exactly where the error is. So when debugging it, enter the
editor and find the line in question. If the problem is with a variable and
is not a typo, make sure you declared it properly. On the other hand, if
it is a typo, simply fix it!
One thing to beware of, however, is that this error will sometimes be
reported in conjunction with a missing parentheses, brackets, or braces
type error. In these situations, your problem with an unknown identifier
is often bogus. The driver misreads the way the {} or whatever are
setup, and thus gets variable declarations confused. Therefore make
sure all other compile time errors are corrected before bothering with
these types of errors.
In the same class with the above error, is the general syntax error. The
driver generates this error when it simply fails to understand what you
said. Again, this is often caused by typos, but can also be caused by not
properly understanding the syntax of a certain feature like writing a for()
statement: for(x=0, x<10, x++). If you get an error like this which is
not a syntax error, try reviewing the syntax of the statement in which the
error is occurring.
7.3 Debugging Run Time Errors
Run time errors are much more complex than their compile time
counterparts. Fortunately these errors do get logged, though many
creators do not realise or they do not know where to look. The error log
for run time errors are also generally much more detailed than compile
time errors, meaning that you can trace the history of the execution train
from where it started to where it went wrong. You therefore can setup
debugging traps using precompiler statements much easier using these
logs. Run time errors, however, tend to result from using more
complex codign techniques than beginners tend to use, which means you
are left with errors which are generally more complicated than simple
compile time errors.
Run time errors almost always result from misusing LPC data types.
Most commonly, trying to do call others using object variables which are
NULL, indexing on mapping, array, or string variables which are
NULL, or passing bad arguments to functions. We will look at a real
run time error log from Nightmare:
Bad argument 1 to explode()
program: bin/system/_grep.c, object: bin/system/_grep
line 32
' cmd_hook' in ' std/living.c' ('
std/user#4002')line 83
' cmd_grep' in ' bin/system/_grep.c' ('
bin/system/_grep')line 32
Bad argument 2 to message()
program: adm/obj/simul_efun.c, object: adm/obj/simul_efun
line 34
' cmd_hook' in ' std/living.c' ('
std/user#4957')line 83
' cmd_look' in ' bin/mortal/_look.c' ('
bin/mortal/_look')line 23
' examine_object' in ' bin/mortal/_look.c' ('
bin/mortal/_look')line 78
' write' in 'adm/obj/simul_efun.c' ('
adm/obj/simul_efun')line 34
Bad argument 1 to call_other()
program: bin/system/_clone.c, object: bin/system/_clone
line 25
' cmd_hook' in ' std/living.c' ('
std/user#3734')line 83
' cmd_clone' in ' bin/system/_clone.c' ('
bin/system/_clone')line 25
Illegal index
program: std/monster.c, object:
wizards/zaknaifen/spy#7205 line 76
' heart_beat' in ' std/monster.c'
('wizards/zaknaifen/spy#7205')line
76
All of the errors, except the last one, involve passing a bad argument to a
function. The first bug, involves passing a bad first arument to the efun
explode(). This efun expects a string as its first argment. In debugging
these kinds of errors, we would therefore go to line 32 in
/bin/system/_grep.c and check to see what the data type of the first
argument being passed in fact is. In this particular case, the value being
passed should be a string.
If for some reason I has actually passed something else, I would be done
debugging at that point and fix it simply by making sure that I was
passing a string. This situation is more complex. I now need to trace
the actual values contained by the variable being passed to explode, so
that I can see what it is the explode() efun sees that it is being passed.
The line is question is this:
borg[files[i]] = regexp(explode(read_file(files[i]), \"\\n\"), exp);
where files is an array for strings, i is an integer, and borg is a mapping.
So clearly we need to find out what the value of read_file(files[i]) is.
Well, this efun returns a string unless the file in question does not exist,
the object in question does not have read access to the file in question, or
the file in question is an empty file, in which cases the function will
return NULL. Clearly, our problem is that one of these events must
have happened. In order to see which, we need to look at files[i].
Examining the code, the files array gets its value through the get_dir()
efun. This returns all the files in a directory if the object has read access
to the directory. Therefore the problem is neither lack of access or non-
existent files. The file which caused this error then must have been an
empty file. And, in fact, that is exactly what caused this error. To
debug that, we would pass files through the filter() efun and make
sure that only files with a file size greater than 0 were allowed into the
array.
The key to debugging a run time error is therefore knowing exactly what
the values of all variables in question are at the exact moment where the
bug created. When reading your run time log, be careful to separate the
object from the file in which the bug occurred. For example, the
indexing error above came about in the object /wizards/zaknaifen/spy,
but the error occured while running a function in /std/monster.c, which
the object inherited.
7.4 Malfunctioning Code
The nastiest problem to deal with is when your code does not behave the
way you intended it to behave. The object loads fine, and it produces no
run time errors, but things simply do not happen the way they should.
Since the driver does not see a problem with this type of code, no logs
are produced. You therefore need to go through the code line by line
and figure out what is happening.
Step 1: Locate the last line of code you knew successfully executed
Step 2: Locate the first line of code where you know things are going
wrong
Step 3: Examine the flow of the code from the known successful point to
the first known unsuccessful point.
More often than not, these problems occurr when you are using if()
statements and not accounting for all possibilities. For example:
int cmd(string tmp) {
if(stringp(tmp)) return do_a()
else if(intp(tmp)) return do_b()
return 1;
}
In this code, we find that it compiles and runs fine. Problem is nothing
happens when it is executed. We know for sure that the cmd() function
is getting executed, so we can start there. We also know that a value of
1 is in fact being returned, since we do not see \"What?\" when we enter
the command. Immediately, we can see that for some reason the
variable tmp has a value other than string or int. As it turns out, we
issued the command without parameters, so tmp was NULL and failed
all tests.
The above example is rather simplistic, bordering on silly.
Nevertheless, it gives you an idea of how to examine the flow of the
code when debugging malfunctioning code. Other tools are available as
well to help in debugging code. The most important tool is the use of
the precompiler to debug code. With the code above, we have a clause
checking for integers being passed to cmd(). When we type \"cmd 10\",
we are expecting do_b() to execute. We need to see what the value of
tmp is before we get into the loop:
#define DEBUG
int cmd(string tmp) {
#ifdef DEBUG
write(tmp);
#endif
if(stringp(tmp)) return do_a();
else if(intp(tmp)) return do_b();
else return 1;
}
We find out immediately upon issuing the command, that tmp has a
value of \"10\". Looking back at the code, we slap ourselves silly,
forgetting that we have to change command arguments to integers using
sscanf() before evaluating them as integers.
7.5 Summary
The key to debugging any LPC problem is always being aware of what
the values of your variables are at any given step in your code. LPC
execution reduces on the simplest level to changes in variable values, so
bad values are what causes bad things to happen once code has been
loaded into memory. If you get errors about bad arguments to
functions, more likely than not you are passing a NULL value to a
function for that argument. This happens most often with objects, since
people will do one of the following:
1) use a value that was set to an object that has since destructed
2) use the return value of this_player() when there is no this_player()
3) use the return value of this_object() just after this_object() was
destructed
In addition, people will often run into errors involving illegal indexing or
indexing on illegal types. Most often, this is because the mapping or
array in question was not initialized, and therefore cannot be indexed.
The key is to know exactly what the full value of the array or mapping
should be at the point in question. In addition, watch for using index
numbers larger than the size of given arrays
Finally, make use of the precompiler to temporarly throw out code, or
introduce code which will show you the values of variables. The
precompiler makes it easy to get rid of debugging code quickly once you
are done. You can simply remove the DEBUG define when you are
done.
Copyright (c) George Reese 1993
",({"chapter 25","chapter twenty-five","25",}):"chapter 25 \"Rooms\"
Building Rooms
The Nightmare IV LPC Library
written by Descartes of Borg 950420
This document details how to build rooms using the Nightmare IV LPC
Library's inheritable room object. This document is divided into
simple room building and complex room building. The first part
teaches you about basic rooms. The second part tells you what
features are there to allow you to do creative things with the room object.
************************************************
Part 1: Basic Room Building
************************************************
I. The Simple Room
The simple room minimally looks like this:
#include <lib.h>
inherit LIB_ROOM;
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"indoors\");
SetShort(\"an empty room\");
SetLong(\"An empty room with no exits leading anywhere. It is \"
\"completely barren with nothing to describe.\");
}
#include <lib.h>
This first line is one you need in any object. It defines the exact
location of objects which you inherit. In this case, the object is
LIB_ROOM. It is currently located at /lib/room.c. If we wanted to
change that location, however, we could do it easily since you only
reference LIB_ROOM. So lib.h is a file that says that LIB_ROOM is
\"/lib/room\".
inherit LIB_ROOM;
The third line, the inherit line, says that this object will inherit
/lib/room.c.
static void create() {
The fifth line begins the meat of any object you will write. This is
the beginning of a function. This one is called create(). If you are
curious, the static means no one can use the call command to call the
function. Do not worry about that too much, however, as it is always
something you put there for the create() function.
The \"void\" part simply says that you are returning no value from the
function. See the LPC Basics textbook for more information on
functions.
room::create();
Inside the create() function are the calls which define what the
object will do. The first call calls the function create() in
/lib/room.c, the object you just inherited. /lib/room.c has its own
create() function which does some things needed in order to set up
your object. You need to make sure it gets called through this line.
SetProperty(\"light\", 2);
This sets the light in the room to be 2. In outdoors rooms, this is
the average light during day time. In indoor rooms, it is the average
light all the time.
SetClimate(\"indoors\")
Every room has a climate. An indoors room, among other things, is not
affected by weather or the time of day.
SetShort(\"an empty room\")
This is the description that the player sees when in brief mode. In
addition, in brief mode, obvious exit abbreviations are automatically
added. This is done through the SetObviousExits() function described
later. However, the short should be phrased in such a way that it
makes sense from something like a scry command which would say
something like: \"You see Descartes in an empty room.\"
SetLong(\"An empty room with no exits leading anywhere. It is \"
\"completely barren with nothing to describe.\");
This sets the long description seen by the player when in verbose
mode. Note that items in the room as well as scents and sounds are
added to what the player sees automatically.
That's it! You now have a room which no one can leave!
II. Adding items
Approval on any decent MUD will eat you for lunch if you do not
describe your items. This is likely the most tedious part of area
building, however, it is also the part that largely makes the
difference between a dull area and a fun one. You must be sure to
make it so that anything a player might logically want to see in
detail in a room is described in detail. For example, say you have
the following long description for a room:
You are in Monument Square, once known as Krasna Square. The two main
roads of Praxis intersect here, where all of Nightmare's people gather
in joy and sorrow. The road running north and south is called Centre
Path, while Boc La Road is the name of the road running east and west.
A magnificent monument rises above the square.
You should have descriptions for the following items placed in your
room:
square, monument, monument square, krasna square, roads, road,
intersection, people, centre path, boc la road, magnificent monument
How to do this with a minimum of hassle:
SetItems( ([ ({ \"square\", \"monument square\", \"krasna square\" }) :
\"The central square of Praxis where citizens and adventurers \"
\"gather to chat and trade. Formerly known as Krasna Square, \"
\"is now known as Monument Square as thanks to those who helped \"
\"to build the town\",
({ \"monument\", \"magnificent monument\" }) : \"A giant monolith \"
\"rising above Monument Square\",
({ \"intersection\", \"road\", \"roads\" }) : \"The two main roads of Praxis \"
\"intersect in Monument Square. The one to the north and south \"
\"is called Centre Path, while the other is Boc La Road.\",
({ \"people\", \"adventurers\", \"citizens\" }) : \"A varied group of \"
\"people from countless realms hanging about talking and trading.\",
\"centre path\" : \"The main road leading north to the North Forest \"
\"from Praxis, and south to the sea.\",
\"boc la road\" : \"The main east-west road through Praxis, going \"
\"east towards the jungle, and west towards the Daroq Mountains.\" ]) );
That may seem like a mouthful, but it is easier to break down into
smaller points and see what is going on. The SetItems() prototype
looks like this:
mapping SetItems(mapping items);
That means it accepts a data type called a mapping as the argument and
returns a new mapping of items. A mapping is a special data type in
LPC that allows you to associate two values together, for example, to
associate an item with its description. For example, above we wanted
to associate the items \"monument\" and \"magnificent monument\" with the
description \"A giant monolith rising above Monument Square\". To do
that, a mapping looks like this:
([ value1 : assoc_value1 ])
where assoc_value1 is the value associated with value1. In this case,
we might have something like:
([ \"monument\" : \"A giant monolith rising above Monument Square.\" ])
But, we also wanted to associate \"magnificent monument\" with this
description. One way, which is perfectly legitimate, would be:
([ \"monument\" : \"A giant monolith rising above Monument Square\",
\"magnificent monument\" : \"A giant monolith rising above Monument Square\" ])
But that would be damned annoying, especially with long descriptions
or things with a lot of synonyms. You can therefore group values
which have the same description together using array notation:
({ value1, value2, value3 })
And thus, make that mapping look like:
([ ({ \"monument\", \"magnificent monument\" }) : \"A giant monolith rising \"
\"above Monument Square.\" ])
To complete setting the items, you simply add other item/description
pairs separated by commas:
([ ({ \"monument\", \"monument square\" }) : \"A giant monolith rising \"
\"above Monument Square.\",
\"house\" : \"A little white house with white picket fences.\" ])
Mappings are a rather difficult concept to grasp, but once grasped
they are very powerful. You should take a look at some sample code
from /domains/Examples/room to get a good idea of what proper code
looks like. In addition, there is a chapter in Intermediate LPC
dedicated to the concept. Finally, you can always mail
borg@imaginary.com to ask questions.
III. Adding Exits and Enters
If you understand the section above, exits and enters are simple.
They too use mappings, but less complicated ones:
SetExits( ([ \"north\" : \"/domains/Praxis/n_centre1\",
\"south\" : \"/domains/Praxis/s_centre1\",
\"east\" : \"/domains/Praxis/e_boc_la1\",
\"west\" : \"/domains/Praxis/w_boc_la1\" ]) );
SetEnters( ([ \"hall\" : \"/domains/Praxis/town_hall\",
\"pub\" : \"/domains/Praxis/pub\" ]) );
With an exit mapping, you simply match the direction to the room to
which it leads. With an enter mapping, you match a thing being
entered with the room to which it leads.
Unlike other LPC Libraries, the Nightmare IV LPC Library distinguishes
between the concept of motion towards and motion into. Motion towards
is exemplified by the \"go\" command, which is affected by SetExits().
For example, to go east, you type \"go east\". You are simply going
towards the east (Note that \"go east\" is by default aliased to \"e\").
Motion into is exemplified by the \"enter\" command, which is affected
by SetEnters(). Enter marks anything you enter into, for example a
building or bushes or the like. In the above example, a player would
issue the command \"enter pub\" to enter the pub.
IV. Adding Objects
If you want to add physical objects into your room, you use the
SetInventory() function. For example, if you wanted to place a balrog
in the room:
SetInventory(([ \"/domains/Praxis/npc/balrog\" : 1 ]);
Every reset, the room will then check to see if any balrogs are in the
room. If no balrogs are in the room it will clone 1. Again, this is
another function using a mapping. In this case it is associating the
file name of an object with how many of that object should be in the
room at every reset. If you wanted 5 balrogs in the room, you would
have changed the 1 to 5.
V. Adding Smells, Listens, and Searches
The functions:
SetSmell()
SetSearch()
SetListen()
All work identically to the SetItems() function. That is they match
things you can smell, listen, search to descriptions which the player
sees when they smell, listen, search the item.
For example:
SetSmell( ([ \"monument\" : \"It smells of obsidian.\",
\"road\" : \"It smells dusty.\",
({ \"pub\", \"bar\" }) : \"It smells of alcohol.\" ]) );
If a player types:
\"smell monument\"
then they see
\"It smells of obsidian.\"
One unique thing about these three functions, however, is that you can
use the special thing \"default\" to set a smell, listen, or search that
occurs when no object is specified. For example,
SetSmell(([ \"default\" : \"It really stinks here.\" ]) );
Will have the player see \"It really stinks here.\" when they simply
type \"smell\". In addition, this is the smell the player sees when
they simply walk into a room.
VI. Miscellaneous stuff
SetObviousExits(\"n, s, e\")
Sets an obvious exits string which gets seen in brief mode and by
newbies in verbose mode. Generally, this should consist of the
abbreviations for the room's obvious exits only.
SetTown(\"Praxis\")
For rooms which are considered part of a town, you must specify that
they are part of the town through this function. In this example, the
room is set to be in the town of Praxis. See the document
/doc/build/Towns for more information on towns.
SetDayLong(\"The sky lights up the endless fields of wheat which stand \"
\"before you.\");
SetNightLong(\"You are standing in a pitch black field of wheat.\");
Instead of using SetLong(), you can call both of these functions to
give different long descriptions for day and night.
SetGravity(2.0)
This makes things in the room twice as heavy as normal.
SetDoor(\"east\", \"/domains/Praxis/doors/red_door\");
Sets a door to the east which is the file
\"/domains/Praxis/doors/red_door.c\". You should have an exit to the
east, and you should do this AFTER you have called SetItems(). See
the document /doc/build/Doors for detailed information on door
building.
VII. Summary
Here is a room that uses everything described above:
#include <lib.h>
inherit LIB_ROOM;
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"temperate\");
SetTown(\"Praxis\");
SetShort(\"a peaceful park\");
SetDayLong(\"The light of the sun shines down upon an open field \"
\"in the middle of Praxis known as Kronos Park. In spite \"
\"of the time of day, no one is around. East Boc La \"
\"Road is to the south.\");
SetNightLong(\"Kronos Park is a poorly lit haven for rogues in the \"
\"cover of night. It is safest to head back south \"
\"towards the lights of East Boc La Road\");
SetItems( ([ ({ \"field\", \"park\" }) : \"A wide open park in the \"
\"center of Praxis.\" ]) );
SetSearch( ([ \"field\" : \"You get dirt all over your hands.\" ]) );
SetSmell( ([ \"default\" : \"You smell grass after a fresh rain.\",
\"dirt\" : \"It smells like... dirt!\" ]) );
SetExits( ([ \"south\" : \"/domains/Praxis/e_boc_la3\" ]) );
SetInventory( ([ \"/domains/Praxis/npc/rogue\" : 2 ]) );
}
************************************************
Part 2: Advanced Room Building
************************************************
I. Functionals
MudOS has a data type called a functional. Most room functions take a
functional as an argument instead of a string. What this does is
allow you to specify a function to get called in order to determine
the value rather than set it as a string which cannot be changed. For
example, if you wanted to set a long description that varied depending the
status of a door:
#include <lib.h>
inherit LIB_ROOM;
string CheckDoor(string useless);
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"indoors\");
SetShort(\"an indoor room with a door\");
SetLong( (: CheckDoor :) );
SetExits( ([ \"east\" : \"/domains/Praxis/east_room\" ]) );
SetDoor(\"east\", \"/domains/Praxis/doors/red_door\");
}
string CheckDoor(string useless) {
string tmp;
tmp = \"You are in a plain indoor room with a door. \";
if( (int)\"/domains/Praxis/doors/red_door\"->GetOpen() )
tmp += \"The door is open.\";
else tmp += \"The door is closed.\";
return tmp;
}
In this example, a function called CheckDoor() was written to
determine exactly what the long description should be. This is done
because in create(), you have no idea what the status of the door will
be from moment to moment. Using a function, you can therefore
determine what the long description is at the time it is needed.
Functionals can reference any function anywhere on the MUD, including
efuns. See /doc/lpc/data_types/functionals for details on them. For
the sake of this document however, you note a functional using smileys
:).
(: CheckDoor :) means the function CheckDoor() in this object. You
can also specify function in other objects, for example:
(: call_other, this_player(), \"GetName\" :) would refer to GetName() in
the person who was this_player() AT THE TIME THE FUNCTIONAL WAS
CREATED.
Notice at the top of the file that CheckDoor() was prototyped. You
must prototype any function you reference inside your objects. The
expression (: CheckDoor :) constitutes as a reference, and thus makes
you need to prototype the function.
The rest of this portion describes individual function calls using
functionals. The functional prototype part is how your functional
should be declared.:
SetShort(string | function)
Functional prototype: string ShortFunc();
Example: SetShort( (: MyShort :) );
If you pass it a function, then this function gets called to determine
the short description. The function should return a string which will
be used as the short description.
SetLong(string | function)
Functional prototype: string LongFunc(string unused)
Example: SetLong( (: MyLong :) );
This function should return a string which will be used as the long
description for the room. The argument \"unused\" is just that, unused
in this context. It is something used for other objects.
SetItems(mapping mp);
Functional prototype: string ItemFunc(string item);
Example: SetItems( ([ \"house\" : (: LookHouse :) ]) );
This function should return a string to be used for the item
description. The argument is passed the name of the item being looked
at, so you can use the same function for multiple items.
SetSearch(mapping mp)
Alternate: SetSearch(string item, string | function desc)
Functional prototype: string SearchFunc(string item);
Examples: SetSearch( ([ \"grass\" : (: SearchGrass :) ]) );
SetSearch(\"grass\", (: SearchGrass :));
Note that there are two forms to SetSearch(), useful depending on how
many searches you are setting at once. If you have a search function,
then that function should return a string which is what they will see.
The argument passed is the item being searched.
SetSmell()
SetListem()
see SetSearch()
II. Advanced Exits
SetExits() is fairly straight forward. However, there exists another
function for exits called AddExit(). It allows you to add one exit at
a time (useful if say a player searches and finds a new exit) as well
as give functional power to exits. The prototype for AddExit() is:
varargs mapping AddExit(string dir, string dest, function pre, function post);
The varargs part of the prototype simply means you can call it using
less than the full number of arguments specified. In this case, the
minimum call is:
AddExit(\"east\", \"/domains/Praxis/square\");
The last two arguments are called pre-exit functions and post exit
functions. The pre-exit function gets called when a player issues a
command to leave the room, but before the player is allowed to leave.
Depending on the return value of the function, the player is allowed
or denied the right to leave. For example:
AddExit(\"north\", \"/domains/Praxis/square\", (: PreExit :));
int PreExit(string dir) {
if( !avatarp(this_player()) ) {
write(\"You are too lowly to go that way!\");
return 0;
}
else return 1;
}
In other words, if the player is an avatar, they can go north.
Otherwise they cannot. The prototype is:
int PreExit(string dir);
where the return value is 1 or 0 for can or cannot leave, and the
argument dir is the direction in which the player is exiting.
Post exit functions work a little differently since it makes no sense
to prevent someone from leaving once they have left. The prototype
looks like:
void PostExit(string dir);
This simply allows you to do processing once the player is gone. If
you wish a post exit without a pre exit, then:
AddExit(\"north\", \"/domains/Praxis/square\"\", 0, (: PostExit :));
Enters work exactly the same way.
Please read about the events CanReceive() and CanRelease(), as those
may be more appropriate places to do what you want. Remember, this
only prevents a player from using the \"go\" command to go in that
direction. CanReceive() in the other room would be better if your
desire is to keep non-avatars out of the square at any cost.
III. Other Functions
AddExit()
RemoveExit()
AddEnter()
RemoveEnter()
RemoveSearch()
RemoveSmell()
RemoveListen()
AddItem()
RemoveItem()
All of the above Remove*() functions take a single string argument
specifying what it is that is being removed. For example:
RemoveExit(\"east\")
removes the exit to the east.
AddItem(string item, mixed val)
Adds a single item. Val can be a string or function.
Descartes of Borg
borg@imaginary.com
",({"chapter 2","chapter two","2",}):"chapter 2 \"The LPC Program\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 16 june 1993
CHAPTER 2: The LPC Program
2.1 About programs
The title of this chapter of the textbook is actually poorly named, since
one does not write programs in LPC. An LPC coder instead writes *objects*.
What is the difference? Well, for our purposes now, the difference is
in the way the file is executed. When you \"run\" a program, execution
begins at a definite place in the program. In other words, there
is a place in all programs that is noted as the beginning where program
execution starts. In addition, programs have definite end points,
so that when execution reaches that point, the execution of the program
terminates. So, in short, execution of a program runs from a definite
beginning point through to a definite end point. This is not so with
LPC objects.
With muds, LPC objects are simply distinct parts of the C program which
is running the game (the driver). In other words, execution of the mud
program begins and ends in the driver. But the driver in fact does
very little in the way of creating the world you know when you play
a mud. Instead, the driver relies heavily on the code created in LPC,
executing lines of the objects in the mud as needed. LPC objects thus
have no place that is necessarily the beginning point, nor do they
have a definite ending point.
Like other programming languages, an LPC \"program\" may be made up of
one or more files. For an LPC object to get executed, it simple
needs to be loaded into the driver's memory. The driver will call lines
from the object as it needs according to a structure which will be
defined throughout this textbook. The important thing you need to
understand at this point is that there is no \"beginning\" to an LPC
object in terms of execution, and there is no \"end\".
2.2 Driver-mudlib interaction
As I have mentioned earlier, the driver is the C program that runs on
the host machine. It connects you into the game and processes LPC code.
Note that this is one theory of mud programming, and not necessarily
better than others. It could be that the entire game is written in C.
Such a game would be much faster, but it would be less flexible in
that wizards could not add things to the game while it was running. This
is the theory behind DikuMUDs. Instead, LPMUDs run on the theory that
the driver should in no define the nature of the game, that the nature
of the game is to be decided by the individuals involved, and that
you should be able to add to the game *as it is being played*. This
is why LPMUDs make use of the LPC programming language. It allows
you to define the nature of the game in LPC for the driver to read and
execute as needed. It is also a much simpler language to understand
than C, thus making the process of world creation open to a greater
number of people.
Once you have written a file in LPC (assuming it is corrent LPC ), it justs
sits there on the host machine's hard drive until something in the game
makes reference to it. When something in the game finally does make
reference to the object, a copy of the file is loaded into memory and
a special *function* of that object is called in order to initialize
the values of the variables in the object. Now, do not be concerned
if that last sentence went right over your head, since someone brand
new to programming would not know what the hell a function or a variable
is. The important thing to understand right now is that a copy of the
object file is taken by the driver from the machine's hard drive and
stored into memory (since it is a copy, multiple versions of that
object may exist). You will later understand what a function is, what
a variable is, and exactly how it is something in the game made reference
to your object.
2.3 Loading an object into memory
Although there is no particular place in an object code that must exist
in order for the driver to begin executing it, there is a place for which
the driver will search in order to initialize the object. On compat
drivers, it is the function called reset(). On native muds it is the
function called create().
LPC objects are made up of variables (values which can change) and
functions which are used to manipulate those variables. Functions
manipulate variables through the use of LPC grammatical structures,
which include calling other functions, using externally defined
functions (efuns), and basic LPC expressions and flow control
mechanisms.
Does that sound convoluted? First lets start with a variable. A
variable might be something like: level. It can \"vary\" from sitation
to situation in value, and different things use the value of the player's
level to make different things happen. For instance, if you are a
level 19 player, the value of the variable level will be 19. Now
if your mud is on the old LPMud 2.4.5 system where levels 1-19 are
players and 20+ are wizards, things can ask for your level value to
see if you can perform wizard type actions. Basically, each object
in LPC is a pile of variables with values which change over time.
Things happen to these objects based on what values its variables
hold. Often, then things that happen cause the variables to change.
So, whenever an object in LPC is referenced by another object currently
in memory, the driver searches to see what places for values the
object has (but they have no values yet). Once that is done, the driver
calls a function in the object called reset() or create() (depending
on your driver) which will set up the starting values for the object's
variables. It is thus through *calls* to *functions* that variable
values get manipulated.
But create() or reset() is NOT the starting place of LPC code, although
it is where most LPC code execution does begin. The fact is, those
functions need not exist. If your object does just fine with its
starting values all being NULL pointers (meaning, for our purposes
here, 0), then you do not need a create() or reset() function. Thus
the first bit of execution of the object's code may begin somewhere
completely different.
Now we get to what this chapter is all about. The question: What
consists a complete LPC object? Well, an LPC object is simply
one or more functions grouped together manipulating 0 or more
variables. The order in which functions are placed in an object
relative to one another is irrelevant. In other words:
-----
void init() { add_action(\"smile\", \"smile\"); }
void create() { return; }
int smile(string str) { return 0; }
-----
is exactly the same as:
-----
void create() { return; }
int smile(string str) { return 0; }
void init() { add_action(\"smile\", \"smile\"); }
_____
Also important to note, the object containing only:
-----
void nonsense() {}
-----
is a valid, but trivial object, although it probably would not interact
properly with other objects on your mud since such an object has no
weight, is invisible, etc..
2.4 Chapter summary
LPC code has no beginning point or ending point, since LPC code is used
to create objects to be used by the driver program rather than create
individual programs. LPC objects consist of one or more functions whose
order in the code is irrelevant, as well as of zero or more variables whose
values are manipulated inside those functions. LPC objects simply sit
on the host machine's hard driver until referenced by another object in
the game (in other words, they do not really exist). Once the object
is referenced, it is loaded into the machine's memory with empty
values for the variables. The function reset() in compat muds or
create() in native muds is called in that object if it exists to allow
the variables to take on initial values. Other functions in the object
are used by the driver and other objects in the game to allow interaction
among objects and the manipulation of the LPC variables.
A note on reset() and create():
create() is only used by muds in native mode (see the textbook Introduction
for more information on native mode vs. compat mode). It is only used
to initialize newly referenced objects.
reset() is used by both muds in compat mode and native mode. In compat
mode, reset() performs two functions. First, it is used to initialize
newly referenced objects. In addition, however, compat mode muds use
reset() to \"reset\" the object. In other words, return it to its initial
state of affairs. This allows monsters to regenerate in a room and doors
to start back in the shut position, etc.. Native mode muds use reset()
to perform the second function (as its name implies).
So there are two important things which happen in LP style muds which
cause the driver to make calls to functions in objects. The first is
the creation of the object. At this time, the driver calls a function
to initalize the values in the object. For compat mode muds, this
is performed by the function named reset() (with an argument of 0,
more on this later though). For muds running in native mode, this is
performed by the function create().
The second is the returning of the room to some base state of affairs.
This base set of affairs may or may not be different from the initial
state of affairs, and certainly you would not want to take up time
doing redundant things (like resetting variables that never change).
Compat mode muds nevertheless use the same function that was used to
create the object to reset it, that being reset(). Native mode muds,
who use create() to create the room, instead use reset() to reset it.
All is not lost in compat mode though, as there is a way to tell the
difference between creation and resetting. For reset purposes, the
driver passes either 1 or the reset number as an argument to reset()
in compat mode. Now this is meaningless to you now, but just keep in
mind that you can in fact tell the difference in compat mode. Also
keep in mind that the argment in the creation use of reset is 0 and
the argument in the reset use is a nonzero number.
",({"chapter 15","chapter fifteen","15",}):"chapter 15 \"Debugging\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 7: Debugging
7.1 Types of Errors
By now, you have likely run into errors here, there, and everywhere. In
general, there are three sorts of errors you might see: compile time
errors, run time errors, and malfunctioning code. On most muds you
will find a personal file where your compile time errors are logged. For
the most part, this file can be found either in your home directory as the
file named \"log\" or \".log\", or somewhere in the directory \"/log\" as a file
with your name.. In addition, muds tend to keep a log of run time errors
which occur while the mud is up. Again, this is generally found in
\"/log\". On MudOS muds it is called \"debug.log\". On other muds it may
be called something different like \"lpmud.log\". Ask your administrators
where compile time and run time errors are each logged if you do not
already know.
Compile time errors are errors which occur when the driver tries to load
an object into memory. If, when the driver is trying to load an object
into memory, it encounters things which it simply does not understand
with respect to what you wrote, it will fail to load it into memory and log
why it could not load the object into your personal error log. The most
common compile time errors are typos, missing or extra (), {}. [], or \"\",
and failure to declare properly functions and variables used by the
object.
Run time errors occur when something wrong happens to an object in
memory while it is executing a statement. For example, the driver
cannot tell whether the statement \"x/y\" will be valid in all circumstances.
In fact, it is a valid LPC expression. Yet, if the value of y is 0, then a
run time error will occur since you cannot divide by 0. When the driver
runs across an error during the execution of a function, it aborts
execution of the function and logs an error to the game's run time error
log. It will also show the error to this_player(), if defined, if the player
is a creator, or it will show \"What?\" to players. Most common causes
for run time errors are bad values and trying to perform operations with
data types for which those operations are not defined.
The most insideous type of error, however, is plain malfunctioning
code. These errors do not log, since the driver never really realizes that
anything is wrong. In short, this error happens when you think the code
says one thing, but in fact it says another thing. People too often
encounter this bug and automatically insist that it must be a mudlib or
driver bug. Everyone makes all types of errors though, and more often
than not when code is not functioning the way you should, it will be
because you misread it.
7.2 Debugging Compile Time Errors
Compile time errors are certainly the most common and simplest bugs to
debug. New coders often get frustrated by them due to the obscure
nature of some error messages. Nevertheless, once a person becomes
used to the error messages generated by their driver, debugging compile
time errors becomes utterly routine.
In your error log, the driver will tell you the type of error and on which
line it finally noticed there was an error. Note that this is not on which
line the actual error necessarily exists. The most common compile time
error, besides the typo, is the missing or superfluous parentheses,
brackets, braces, or quotes. Yet this error is the one that most baffles
new coders, since the driver will not notice the missing or extra piece
until well after the original. Take for example the following code:
1 int test(string str) {
2 int x;
3 for(x =0; x<10; x++)
4 write(x+\"\\n\");
5 }
6 write(\"Done.\\n\");
7 }
Depending on what you intended, the actual error here is either at line 3
(meaning you are missing a {) or at line 5 (meaing you have an extra }).
Nevertheless, the driver will report that it found an error when it gets to
line 6. The actual driver message may vary from driver to driver, but no
matter which driver, you will see an error on line 6, since the } in line 5
is interpreted as ending the function test(). At line 6, the driver sees that
you have a write() sitting outside any function definition, and thus
reports an error. Generally, the driver will also go on to report that it
found an error at line 7 in the form of an extra }.
The secret to debugging these is coding style. Having closing } match
up vertically with the clauses they close out helps you see where you are
missing them when you are debugging code. Similarly, when using
multiple sets of parentheses, space out different groups like this:
if( (x=sizeof(who=users()) > ( (y+z)/(a-b) + (-(random(7))) ) )
As you can see, the parentheses for the for() statement, are spaced out
from the rest of the statement. In addition, individual sub-groups are
spaced so they can easily be sorted out in the event of an error.
Once you have a coding style which aids in picking these out, you learn
which error messages tend to indicate this sort of error. When
debugging this sort of error, you then view a section of code before and
after the line in question. In most all cases, you will catch the bug right
off.
Another common compile time error is where the driver reports an
unknown identifier. Generally, typos and failure to declare variables
causes this sort of error. Fortunately, the error log will almost always
tell you exactly where the error is. So when debugging it, enter the
editor and find the line in question. If the problem is with a variable and
is not a typo, make sure you declared it properly. On the other hand, if
it is a typo, simply fix it!
One thing to beware of, however, is that this error will sometimes be
reported in conjunction with a missing parentheses, brackets, or braces
type error. In these situations, your problem with an unknown identifier
is often bogus. The driver misreads the way the {} or whatever are
setup, and thus gets variable declarations confused. Therefore make
sure all other compile time errors are corrected before bothering with
these types of errors.
In the same class with the above error, is the general syntax error. The
driver generates this error when it simply fails to understand what you
said. Again, this is often caused by typos, but can also be caused by not
properly understanding the syntax of a certain feature like writing a for()
statement: for(x=0, x<10, x++). If you get an error like this which is
not a syntax error, try reviewing the syntax of the statement in which the
error is occurring.
7.3 Debugging Run Time Errors
Run time errors are much more complex than their compile time
counterparts. Fortunately these errors do get logged, though many
creators do not realise or they do not know where to look. The error log
for run time errors are also generally much more detailed than compile
time errors, meaning that you can trace the history of the execution train
from where it started to where it went wrong. You therefore can setup
debugging traps using precompiler statements much easier using these
logs. Run time errors, however, tend to result from using more
complex codign techniques than beginners tend to use, which means you
are left with errors which are generally more complicated than simple
compile time errors.
Run time errors almost always result from misusing LPC data types.
Most commonly, trying to do call others using object variables which are
NULL, indexing on mapping, array, or string variables which are
NULL, or passing bad arguments to functions. We will look at a real
run time error log from Nightmare:
Bad argument 1 to explode()
program: bin/system/_grep.c, object: bin/system/_grep
line 32
' cmd_hook' in ' std/living.c' ('
std/user#4002')line 83
' cmd_grep' in ' bin/system/_grep.c' ('
bin/system/_grep')line 32
Bad argument 2 to message()
program: adm/obj/simul_efun.c, object: adm/obj/simul_efun
line 34
' cmd_hook' in ' std/living.c' ('
std/user#4957')line 83
' cmd_look' in ' bin/mortal/_look.c' ('
bin/mortal/_look')line 23
' examine_object' in ' bin/mortal/_look.c' ('
bin/mortal/_look')line 78
' write' in 'adm/obj/simul_efun.c' ('
adm/obj/simul_efun')line 34
Bad argument 1 to call_other()
program: bin/system/_clone.c, object: bin/system/_clone
line 25
' cmd_hook' in ' std/living.c' ('
std/user#3734')line 83
' cmd_clone' in ' bin/system/_clone.c' ('
bin/system/_clone')line 25
Illegal index
program: std/monster.c, object:
wizards/zaknaifen/spy#7205 line 76
' heart_beat' in ' std/monster.c'
('wizards/zaknaifen/spy#7205')line
76
All of the errors, except the last one, involve passing a bad argument to a
function. The first bug, involves passing a bad first arument to the efun
explode(). This efun expects a string as its first argment. In debugging
these kinds of errors, we would therefore go to line 32 in
/bin/system/_grep.c and check to see what the data type of the first
argument being passed in fact is. In this particular case, the value being
passed should be a string.
If for some reason I has actually passed something else, I would be done
debugging at that point and fix it simply by making sure that I was
passing a string. This situation is more complex. I now need to trace
the actual values contained by the variable being passed to explode, so
that I can see what it is the explode() efun sees that it is being passed.
The line is question is this:
borg[files[i]] = regexp(explode(read_file(files[i]), \"\\n\"), exp);
where files is an array for strings, i is an integer, and borg is a mapping.
So clearly we need to find out what the value of read_file(files[i]) is.
Well, this efun returns a string unless the file in question does not exist,
the object in question does not have read access to the file in question, or
the file in question is an empty file, in which cases the function will
return NULL. Clearly, our problem is that one of these events must
have happened. In order to see which, we need to look at files[i].
Examining the code, the files array gets its value through the get_dir()
efun. This returns all the files in a directory if the object has read access
to the directory. Therefore the problem is neither lack of access or non-
existent files. The file which caused this error then must have been an
empty file. And, in fact, that is exactly what caused this error. To
debug that, we would pass files through the filter() efun and make
sure that only files with a file size greater than 0 were allowed into the
array.
The key to debugging a run time error is therefore knowing exactly what
the values of all variables in question are at the exact moment where the
bug created. When reading your run time log, be careful to separate the
object from the file in which the bug occurred. For example, the
indexing error above came about in the object /wizards/zaknaifen/spy,
but the error occured while running a function in /std/monster.c, which
the object inherited.
7.4 Malfunctioning Code
The nastiest problem to deal with is when your code does not behave the
way you intended it to behave. The object loads fine, and it produces no
run time errors, but things simply do not happen the way they should.
Since the driver does not see a problem with this type of code, no logs
are produced. You therefore need to go through the code line by line
and figure out what is happening.
Step 1: Locate the last line of code you knew successfully executed
Step 2: Locate the first line of code where you know things are going
wrong
Step 3: Examine the flow of the code from the known successful point to
the first known unsuccessful point.
More often than not, these problems occurr when you are using if()
statements and not accounting for all possibilities. For example:
int cmd(string tmp) {
if(stringp(tmp)) return do_a()
else if(intp(tmp)) return do_b()
return 1;
}
In this code, we find that it compiles and runs fine. Problem is nothing
happens when it is executed. We know for sure that the cmd() function
is getting executed, so we can start there. We also know that a value of
1 is in fact being returned, since we do not see \"What?\" when we enter
the command. Immediately, we can see that for some reason the
variable tmp has a value other than string or int. As it turns out, we
issued the command without parameters, so tmp was NULL and failed
all tests.
The above example is rather simplistic, bordering on silly.
Nevertheless, it gives you an idea of how to examine the flow of the
code when debugging malfunctioning code. Other tools are available as
well to help in debugging code. The most important tool is the use of
the precompiler to debug code. With the code above, we have a clause
checking for integers being passed to cmd(). When we type \"cmd 10\",
we are expecting do_b() to execute. We need to see what the value of
tmp is before we get into the loop:
#define DEBUG
int cmd(string tmp) {
#ifdef DEBUG
write(tmp);
#endif
if(stringp(tmp)) return do_a();
else if(intp(tmp)) return do_b();
else return 1;
}
We find out immediately upon issuing the command, that tmp has a
value of \"10\". Looking back at the code, we slap ourselves silly,
forgetting that we have to change command arguments to integers using
sscanf() before evaluating them as integers.
7.5 Summary
The key to debugging any LPC problem is always being aware of what
the values of your variables are at any given step in your code. LPC
execution reduces on the simplest level to changes in variable values, so
bad values are what causes bad things to happen once code has been
loaded into memory. If you get errors about bad arguments to
functions, more likely than not you are passing a NULL value to a
function for that argument. This happens most often with objects, since
people will do one of the following:
1) use a value that was set to an object that has since destructed
2) use the return value of this_player() when there is no this_player()
3) use the return value of this_object() just after this_object() was
destructed
In addition, people will often run into errors involving illegal indexing or
indexing on illegal types. Most often, this is because the mapping or
array in question was not initialized, and therefore cannot be indexed.
The key is to know exactly what the full value of the array or mapping
should be at the point in question. In addition, watch for using index
numbers larger than the size of given arrays
Finally, make use of the precompiler to temporarly throw out code, or
introduce code which will show you the values of variables. The
precompiler makes it easy to get rid of debugging code quickly once you
are done. You can simply remove the DEBUG define when you are
done.
Copyright (c) George Reese 1993
",({"chapter 4","chapter four","4",}):"chapter 4 \"Functions\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 22 june 1993
CHAPTER 4: Functions
4.1 Review
By this point, you should be aware that LPC objects consist of functions
which manipulate variables. The functions manipulate variables when they
are executed, and they get executed through *calls* to those functions.
The order in which the functions are placed in a file does not matter.
Inside a function, the variables get manipulated. They are stored in
computer memory and used by the computer as 0's and 1's which
get translated to and from useable output and input through a device
called data typing. String data types tell the driver that the
data should appear to you and come from you in the form of alphanumeric
characters. Variables of type int are represented to you as whole
number values. Type status is represented to you as either 1 or 0.
And finally type void has no value to you or the machine, and is not
really used with variable data types.
4.2 What is a function?
Like math functions, LPC functions take input and return output.
Languages like Pascal distinguish between the concept of proceedure abd
the concept of function. LPC does not, however, it is useful to
understand this distinction. What Pascal calls a proceedure, LPC
calls a function of type void. In other words, a proceedure, or function
of type void returns no output. What Pascal calls a function differs
in that it does return output. In LPC, the most trivial, correct
function is:
-----
void do_nothing() { }
-----
This function accepts no input, performs no instructions, and returns no
value.
There are three parts to every properly written LPC function:
1) The declaration
2) The definition
3) The call
Like with variables, functions must be declared. This will allow the
driver to know 1) what type of data the function is returning as output,
and 2) how many input(s) and of what type those input(s) are. The
more common word for input is parameters.
A function declaration therefore consists of:
type name(parameter1, parameter2, ..., parameterN);
The declaration of a function called drink_water() which accepts a string as
input and an int as output would thus look like this:
-----
int drink_water(string str);
-----
where str is the name of the input as it will be used inside the function.
The function definition is the code which describes what the function actually
does with the input sent to it.
The call is any place in other functions which invokes the execution of the
function in question. For two functions write_vals() and add(), you thus
might have the following bit of code:
-----
/* First, function declarations. They usually appear at the beginning
of object code.
*/
void write_vals();
int add(int x, int y);
/* Next, the definition of the function write_vals(). We assume that
this function is going to be called from outside the object
*/
void write_vals() {
int x;
/*N Now we assign x the value of the output of add() through a call */
x = add(2, 2);
write(x+\"\\n\");
}
/* Finally, the definition of add() */
int add(int x, int y) {
return (x + y);
}
-----
Remember, it does not matter which function definition appears first in the
code. This is because functions are not executed consecutively. Instead,
functions are executed as called. The only requirement is that the
declaration of a function appear before its definition and before the
definition of any function which makes a call to it.
4.3 Efuns
Perhaps you have heard people refer to efuns. They are externally defined
functions. Namely, they are defined by the mud driver. If you have
played around at all with coding in LPC, you have probably found some
expressions you were told to use like this_player(), write(), say(),
this_object(), etc. look a lot like functions. That is because they are
efuns. The value of efuns is that they are much faster than LPC functions,
since they already exist in the binary form the computer understands.
In the function write_vals() above, two functions calls were made. The first was to
the functions add(), which you declared and defined. The second call, however,
was to a function called write(), and efun. The driver has already declared
and defined this function for you. You needs only to make calls to it.
Efuns are created to hanldle common, every day function calls, to handle
input/output to the internet sockets, and other matters difficult to be
dealt with in LPC. They are written in C in the game driver and compiled
along with the driver before the mud comes up, making them much faster
in execution. But for your purposes, efun calls are just like calls
made to your functions. Still, it is important to know two things of any
efun: 1) what return type does it have, and 2) what parameters of what
types does it take.
Information on efuns such as input parameters and return types is often
found in a directory called /doc/efun on your mud. I cannot
detail efuns here, because efuns vary from driver to driver. However,
you can often access this information using the commands \"man\" or \"help\"
depending on your mudlib. For instance, the command \"man write\" would
give you information on the write efun. But if all else fails,
\"more /doc/efun/write\" should work.
By looking it up, you will find write is declared as follows:
-----
void write(string);
-----
This tells you an appropriate call to write expects no return value and
passes a single parameter of type string.
4.4 Defining your own functions
Although ordering your functions within the file does not matter, ordering
the code which defines a function is most important. Once a function
has been called, function code is executed in the order it appears
in the function definition. In write_vals() above, the instruction:
-----
x = add(2, 2);
-----
Must come before the write() efun call if you want to see the appropriate
value of x used in write().
With respect to values returned by function, this is done through the \"return\"
instruction followed by a value of the same data type as the function. In
add() above, the instruction is \"return (x+y);\", where the value of (x+y)
is the value returned to write_vals() and assigned to x. On a more
general level, \"return\" halts the execution of a function and returns
code execution to the function which called that function. In addition,
it returns to the calling function the value of any expression that follows.
To stop the execution of a function of type void out of order, use
\"return\"; without any value following. Once again, remember, the data
type of the value of any expression returned using \"return\" MUST be the
same as the data type of the function itself.
4.5 Chapter Summary
The files which define LPC objects are made of of functions. Functions, in
turn, are made up of three parts:
1) The declaration
2) The definition
3) The call
Function declarations generally appear at the top of the file before any
defintions, although the requirement is that the declaration must appear
before the function definition and before the definition of any function
which calls it.
Function definitions may appear in the file in any order so long as they
come after their declaration. In addition, you may not define one function
inside another function.
Function calls appear inside the definition of other functions where you
want the code to begin execution of your function. They may also appear
within the definition of the function itself, but this is not recommended
for new coders, as it can easily lead to infinite loops.
The function definition consists of the following in this order:
1) function return type
2) function name
3) opening ( followed by a parameter list and a closing )
4) an opening { instructing the driver that execution begins here
5) declarations of any variables to be used only in that function
6) instructions, expressions, and calls to other functions as needed
7) a closing } stating that the function code ends here and, if no
\"return\" instruction has been given at this point (type void functions
only), execution returns to the calling function as if a r\"return\"
instruction was given
The trivial function would thus be:
-----
void do_nothing() {}
-----
since this function does not accept any input, perform any instructions, or
return any output.
Any function which is not of type void MUST return a value of a data type
matching the function's data type.
Each driver has a set of functions already defined for you called efuns
These you need neither need to declare nor define since it has already
been done for you. Furthermore, execution of these functions is faster
than the execution of your functions since efuns are in the driver.
In addition, each mudlib has special functions like efuns in that they
are already defined and declared for you, but different in that they
are defined in the mudlib and in LPC. They are called simul_efuns, or
simulated efuns. You can find out all about each of these as they are
listed in the /doc/efun directory on most muds. In addition many
muds have a command called \"man\" or a \"help\" command which allows you
simply to call up the info files on them.
Note on style:
Some drivers may not require you to declare your functions, and some
may not require you to specify the return type of the function in its
definition. Regardless of this fact, you should never omit this information
for the following reasons:
1) It is easier for other people (and you at later dates) to read your
code and understand what is meant. This is particularly useful
for debugging, where a large portion of errors (outside of misplaced
parentheses and brackets) involve problems with data types (Ever
gotten \"Bad arg 1 to foo() line 32\"?).
2) It is simply considered good coding form.
",({"chapter 35","chapter thirty-five","35",}):"chapter 35 \"QCS: Modifying rooms\"
Suppose you are in your sample room and you issued the
command:
%^GREEN%^create room south testroom1%^RESET%^
You then travel south and see that you are in a room that is
almost exactly like the sample room except for the exits. Well,
probably you don't want to have a mud with nothing but identical
rooms, so let's modify it:
%^GREEN%^modify here short Test Room One%^RESET%^
%^GREEN%^modify here long This is the first test room. The walls are rather blank.%^RESET%^
%^GREEN%^modify here climate indoors%^RESET%^
%^GREEN%^modify here light 30%^RESET%^
Ok, so far so good. Standard interior. However, a good mud has
rooms with details. Let's add some detail to this room.
I've omitted the system output for clarity. This is just what you
would input.
%^GREEN%^modify here item%^RESET%^
%^GREEN%^wall%^RESET%^
%^GREEN%^walls%^RESET%^
%^GREEN%^blank wall%^RESET%^
%^GREEN%^blank walls%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^These are just blank walls.%^RESET%^
Let's review what we've done here:
1) You issued the modify command specifying your current room as the
target, and the SetItems directive as the argument.
2) You entered a query session, and were asked to enter each element
of the item's key.
3) You entered a single dot to indicate you were done entering
key elements.
4) You entered the value for the key, which is the description of the
item.
The result of all this is that now you can issue these commands:
%^GREEN%^exa wall%^RESET%^
%^GREEN%^look at blank walls%^RESET%^
%^GREEN%^examine walls%^RESET%^
And the output will be:
These are just blank walls.
Let's add a floor while we're at it:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^floor%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A floor like any other.%^RESET%^
In this case, you didn't feel like adding extra synonyms for \"floor\",
so you entered the final dot rather than entering another key element.
Then you added the description, and now if you \"exa floor\", you'll get
that description.
\"about here\" will display to you the file you have modified.
Well, that's enough fun with indoor rooms. There's not much more
to them. Let's go outdoors now:
%^GREEN%^create room south exterior_room%^RESET%^
%^GREEN%^create door south test_door%^RESET%^
%^GREEN%^open door%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^modify here short a small lawn%^RESET%^
%^GREEN%^modify here daylong A small, well groomed lawn on a lovely sunny day. There is a small building north of here.%^RESET%^
%^GREEN%^modify here nightlong This is a small lawn. Stars twinkle in the night sky above, and some light is coming from a small building to the north.%^RESET%^
%^GREEN%^modify here daylight 30%^RESET%^
%^GREEN%^modify here nightlight 20%^RESET%^
%^GREEN%^modify here light delete%^RESET%^
%^GREEN%^modify here long delete%^RESET%^
%^GREEN%^modify here items delete%^RESET%^
%^GREEN%^modify here items%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^small building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A small building, rather ramshackle as if hastily put together.%^RESET%^
%^GREEN%^modify here climate temperate%^RESET%^
Ok! A few new things here. A neat thing about outdoor rooms is that
typically they are subject to the time of day. A SetClimate directive
that indicates an exterior environment causes the room to receive
messages about the sun setting, rising, etc.
The SetDayLong and SetNightLong directives allow you to more
sensibly describe the area depending on the time of day. To avoid
confusion, I deleted the SetLong directive. It is not mandatory to
have different day and night descriptions, but players appreciate the
effort.
It is also possible to have differing ambient light levels depending
on the time of day, so we've added SetDayLight and SetNightLight, and
we deleted the SetAmbientLight directive.
Let's continue to add detail:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Healthy, well groomed and freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You can smell the refreshing scent of freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Yep, it's got that new lawn smell.%^RESET%^
%^GREEN%^modify here listen%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Sounds like someone's fumbling about in there, making a mess. New creators can be so noisy.%^RESET%^
%^GREEN%^modify here item%^RESET%^
%^GREEN%^garden%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You may enter the garden from here.%^RESET%^
%^GREEN%^create room garden garden_room%^RESET%^
You now have a room with lots of charm and detail. You can \"smell grass\"
and \"listen to small building\", if you like. Neat, huh? But there's something
very important to keep in mind:
Enters, listens and smells don't work properly if there is no item defined
for that smell. For example, if you want to be able to listen to the sea,
you must \"modify here item\" and add a \"sea\" item. Otherwise, \"listen
to the sea\" will respond with \"There is no sea here.\"
The only exception to this rule is the \"default\" smell.
Enters behave similarly. If you want to be able to \"enter\" something,
you'll need to create the corresponding item first, as in the example above.
You can use the SetProperties directive to make the room
conform to some presets, like:
%^GREEN%^modify here property no attack 1%^RESET%^
Read chapter 23 in the Creator's Manual for details on room properties.
Also, please note that indoor rooms can also have differing
descriptions and light levels for night and day. It's just that
indoor rooms don't get notification of daytime changes.
Finally, the SetTown directive allows the room to participate in
area-wide events, and is useful for security purposes as well:
%^GREEN%^modify here town MyTown%^RESET%^
Notes on room filenames:
-----------------------
By default, a filename without a leading path creates a room
in your area room directory, which in my case would be
\"/realms/cratylus/area/room\". However, you can specify a different
location for the new room.
To create a room in your current working directory:
%^GREEN%^create room east ./newroom%^RESET%^
To create a room in a specific directory:
%^GREEN%^create room east /realms/cratylus/testrooms/newroom%^RESET%^
",({"chapter 29","chapter twenty-nine","29",}):"chapter 29 \"Weapons\"
Building Weapons
The Nightmare IV LPC Library
written by Descartes of Borg 950429
All items in the Nightmare LPC Library (descendants of /lib/item.c)
are weapons. A player can, for example, use a can of spam as a
weapon. However, they are set up as extremely pathetic weapons. This
document describes in detail how to make an object into a real weapon.
I. Basic Stuff
The basic weapon is exactly the same as the basic item. You can do
anything to it that can be done to other items. For details on items,
see /doc/build/Items. The simple weapon should look like this:
#include <lib.h>
#include <damage_types.h>
#include <vendor_types.h>
inherit LIB_ITEM;
static void create() {
item::create();
SetKeyName(\"short sword\");
SetId( ({ \"sword\", \"short sword\", \"a short sword\" }) );
SetAdjectives( ({ \"short\" }) );
SetShort(\"a short sword\");
SetLong(\"A rusty short sword with specs of blood on it.\");
SetVendorType(VT_WEAPON);
SetDamagePoints(1500);
SetClass(12);
SetValue(150);
SetMass(100);
SetWeaponType(\"blade\");
SetDamageType(BLADE);
}
The last part is what differs from regular items. Note the functions:
SetVendorType()
SetClass()
SetWeaponType()
SetDamageType()
The available vendor types can be found by reading the file
/include/vendor_types.h. Similarly, damage types may be found by
reading /include/damage_types.h. The vendor type states what sort of
stores can carry this item. VT_WEAPON should almost ALWAYS be the
vendor type you give for weapons.
SetClass()
The class is the basic weapon strength. It is how much damage gets
done without any modification. This number ranges between 1 and 100,
where 1 is a pathetic weapon (the class for basic items) and 100 is
probably bordering on illegal.
SetWeaponType()
This sets what sort of attack skill the player needs to use this
weapon. The weapon types are:
blade
knife
blunt
projectile
SetDamageType()
Damage types, again, are found in /include/damage_types.h. This sets
what type of damage is done by this weapon to its victims.
II. Wield Functions
mixed SetWield(string | function)
Examples:
SetWield(\"The short sword feels dull as you wield it.\");
SetWield( (: WieldMe :) );
If you pass a string to SetWield(), then the player sees that string
whenever they wield the weapon. If, on the other hand, you pass a
function, then that function will get called just before the weapon is
wielded when the player issues the wield command. The function you
pass should be written in the form of:
int WieldMe();
If the function returns 1, then the player can wield the weapon. If
it returns 0, then the player cannot wield the weapon. Note that if
you have a wield function, you are responsible for all messaging to
the player to let the player know that they can/cannot wield the
weapon. Example:
int WieldMe() {
if( (int)this_player()->ClassMember(\"fighter\") ) {
write(\"The short sword gives you power as you wield it.\");
say((string)this_player()->GetName() + \" wields a short sword.\");
return 1;
}
else {
write(\"You are not worthy of this short sword.\");
return 0;
}
}
III. Modifying Stats and Skills
A common thing people like to do with weapons is temporarily modify a
player's skills. This is done by making use of the function
AddStatBonus() and AddSkillBonus(). Most of the time this is done
through a SetWield() function.
void AddStatBonus(string stat, function f);
void AddSkillBonus(string stat, function f);
Examples:
this_player()->AddStatBonus(\"wisdom\", (: CheckStat :));
this_player()->AddSkillBonus(\"blade attack\", (: CheckSkill :));
The functions then have the format:
int CheckWhatever(string stat_or_skill);
NOTE: You should always check whether the bonus is still in effect.
For example, make sure the weapon is still wielded if it results from
wielding the weapon. For example:
#include <lib.h>
inherit LIB_ITEM;
int DoWield()
int CheckBlade(string skill);
static void create() {
...
SetWield((: DoWield :));
...
}
int DoWield() {
this_player()->AddSkillBonus(\"blade attack\", (: CheckBlade :) );
write(\"You wield the short sword.\");
say((string)this_player()->GetName() + \" wields a short sword.\");
return 1;
}
int CheckBlade(string skill) {
if( !GetWorn() ) {
previous_object()->RemoveSkillBonus(\"blade\", this_object());
return 0;
}
else return 5;
}
In other words, this weapon will give its wielder a blade attack bonus
of 5. Note that you must use previous_object() in CheckBlade() and
NOT this_player() because there is no way of knowing who this_player()
is at the time. You do know, however, that the object calling
CheckBlade() is always the player for whom the skill bonus is made.
Always remember to remove bonuses.
IV. Modifying Hits
The Nightmare IV LPC Library uses an event driven combat system. With
respect to weapons, a round of combat is broken down into the
following events:
1. The person wielding the weapon uses it.
2. If they cannot hit a motionless target, the round ends.
3. If the target dodges the attack, the round ends.
4. eventStrike() is called in the weapon to determine how much damage
the weapon can do.
5. eventReceiveDamage() is called in the target object. This in turn:
a. Calls eventReceiveDamage() in all armour objects, which each:
i. Calls eventReceiveDamage() in the weapon
ii. The weapon wears down a bit
b. The armour wears down a bit
c. The amount of armour damage absorbed is returned
d. The target objects loses health points.
f. The amount of damage done is returned.
6. Skill and stat points are added.
Note the two important functions which get called in weapon.c:
int eventStrike(object ob);
int eventReceiveDamage(int type, int amount, int unused, mixed limbs);
By default, eventStrike() returns the value of GetClass(). However,
you can modify this value by overriding the eventStrike(). For
example:
int eventStrike(object target) {
if( (string)target->GetRace() != \"orc\" ) return item::eventStrike(target);
message(\"environment\", \"The orc slayer makes a nasty sound!\",
environment(target));
return item::eventStrike(target) + random(10);
}
NOTE: You should always use item::eventStrike() rather than hard coded
values since weapon class deteriorates over time.
In this example, a random(10) points of extra damage gets done to
orcs. This would be the orc slayer weapon of ancient fame.
For those familiar with hit functions in the old Nightmare Mudlibs,
this would be roughly equivalent to that.
Another place where you can make things happen is in
eventDeteriorate() which gets called by eventReceieveDamage(). This is
where a weapon wears down from the shock which armour has absorbed
from it. For weapons, there is not much which can be done here, but
this document points it out for the creative who feel they might be able to do
somthing with it.
Descartes of Borg
borg@imaginary.com
",({"chapter 10","chapter ten","10",}):"chapter 10 \"The LPMud Driver\"
Intermediate LPC
Descartes of Borg
Novermber 1993
Chapter 2: The LPMud Driver
2.1 Review of Basic Driver/Mudlib Interaction
In the LPC Basics textbook, you learned a lot about the way the mudlib
works, specifically in relation to objects you code in order to build your
realm. Not much was discussed about the interaction between the
mudlib and the driver. You should know, however, that the driver
does the following:
1) When an object is first loaded into memory, the driver will call
create() in native muds and reset() in compat muds. A creator
uses create() or reset() to give initial values to the object.
2) At an interval setup by the game administrator, the driver calls the
function reset(). This allows the object to regenerate monsters and
such. Notice that in a compat mud, the same function is used to set up
initial values as is used to reset the room.
3) Any time a living object comes in contact with an object of any sort,
the driver calls init() in the newly encountered object. This allows
newly encountered objects to give living objects commands to execute
through the add_action() efun, as well as perform other actions which
should happen whenever a living thing encounters a given object.
4) The driver defines a set of functions known as efuns which are
available to all objects in the game. Examples of commonly used efuns
are: this_player(), this_object(), write(), say(), etc.
2.2 The Driver Cycle
The driver is a C program which runs the game. Its basic functions are
to accept connections from the outside world so people can login,
interpret the LPC code which defines LPC objects and how they
function in the game, and accept user input and call the appropriate LPC
functions which match the event. In its most simplest essence, it is an
unending loop.
Once the game has booted up and is properly functioning (the boot up
process will be discussed in a future, advanced LPC textbook), the
driver enters a loop which does not terminate until the shutdown() efun
is legally called or a bug causes the driver program to crash. First off,
the driver handles any new incoming connections and passes control of
the connection to a login object. After that, the driver puts together a
table of commands which have been entered by users since the last cycle
of the driver. After the command table is assembled, all messages
scheduled to be sent to the connection from the last driver cycle are sent
out to the user. At this point, the driver goes through the table of
commands to be executed and executes each set of commands each
object has stored there. The driver ends its cycle by calling the function
heart_beat() in every object with a heart_beat() set and finally
performing all pending call outs. This chapter will not deal with the
handling of connections, but instead will focus on how the driver
handles user commands and heartbeats and call outs.
2.3 User Commands
As noted in section 1.2, the driver stores a list of commands for each
user to be executed each cycle. The commands list has the name of the
living object performing the command, the object which gave the living
object that command, and the function which is to be executed in order
to perform the command. The driver refers to the object which typed in
the command as the command giver. It is the command giver which
gets returned as this_player() in most cases.
The driver starts at the top of the list of living objects with pending
commands, and successively performs each command it typed by calling
the function associated with the command and passing any arguments
the command giver gave as arguments to the function. As the driver
starts with the commands issued by a new living object, the command
giver variable is changed to be equal to the new living object, so that
during the sequence of functions initiated by that command, the efun
this_player() returns the object which issued the command.
Let's look at the command buffer for an example player. Since the
execution of his last command, Bozo has typed \"north\" and \"tell
descartes when is the next reboot\". The command \"north\" is associated
with the function \"Do_Move()\" in the room Bozo is in (the command
\"north\" is automatically setup by the SetExits() efun in that room). The
command \"tell\" is not specifically listed as a command for the player,
however, in the player object there is a function called \"cmd_hook()\"
which is associated with the command \"\", which matches any possible
user input.
Once the driver gets down to Bozo, the command giver variable is set to
the object which is Bozo. Then, seeing Bozo typed \"north\" and the
function \"north\" is associated with, the driver calls Bozo's_Room-
>Do_Move(0). An argument of 0 is passed to the function since Bozo
only typed the command \"north\" with no arguments. The room
naturally calls some functions it needs, all the while such that the efun
this_player() returns the object which is Bozo. Eventually, the room
object will call eventMoveLiving() in Bozo, which in turn calls the
move_object() efun. This efun is responsible for changing an object's
environment.
When the environment of an object changes, the commands available to
it from objects in its previous environment as well as from its previous
environment are removed from the object. Once that is done, the driver
calls the efun init() in the new environment as well as in each object in
the new environment. During each of these calls to init(), the object
Bozo is still the command giver. Thus all add_action() efuns from this
move will apply to Bozo. Once all those calls are done, control passes
back from the move_object() efun to the eventMoveLiving() lfun in Bozo.
eventMoveLiving() returns control back to Do_Move() in the old room,
which returns 1 to signify to the driver that the command action was
successful. If the Do_Move() function had returned 0 for some reason,
the driver would have written \"What?\" (or whatever your driver's
default bad command message is) to Bozo.
Once the first command returns 1, the driver proceeds on to Bozo's
second command, following much the same structure. Note that with
\"tell descartes when is the next reboot\", the driver passes \"descartes
when is the next reboot\" to the function associated with tell. That
function in turn has to decide what to do with that argument. After that
command returns either 1 or 0, the driver then proceeds on to the next
living object with commands pending, and so on until all living objects
with pending commands have had their commands performed.
2.4 The Efuns set_heart_beat() and call_out()
Once all commands are performed for objects with commands pending,
the driver then proceeds to call the heart_beat() function in all objects
listed with the driver as having heartbeats. Whenever an object calls the
efun set_heart_beat() with a non-zero argument (depending on your
driver, what non-zero number may be important, but in most cases you
call it with the int 1). The efun set_heart_beat() adds the object which
calls set_heart_beat() to the list of objects with heartbeats. If you call it
with an argument of 0, then it removes the object from the list of objects
with heartbeats.
The most common use for heartbeats in the mudlib is to heal players and
monsters and perform combat. Once the driver has finished dealing with
the command list, it goes through the heartbeat list calling heart_beat() in
each object in the list. So for a player, for example, the driver will call
heart_beat() in the player which will:
1) age the player
2) heal the player according to a heal rate
3) check to see if there are any hunted, hunting, or attacking objects
around
4) perform an attack if step 3 returns true.
5) any other things which need to happen automatically roughly every
second
Note that the more objects which have heartbeats, the more processing
which has to happen every cycle the mud is up. Objects with heartbeats
are thus known as the major hog of CPU time on muds.
The call_out() efun is used to perform timed function calls which do not
need to happen as often as heartbeats, or which just happen once. Call
outs let you specify the function in an object you want called. The
general formula for call outs is:
call_out(func, time, args);
The third argument specifying arguments is optional. The first argument
is a string representing the name of the function to be called. The second
argument is how many seconds should pass before the function gets
called.
Practically speaking, when an object calls call_out(), it is added to a list
of objects with pending call outs with the amount of time of the call out
and the name of the function to be called. Each cycle of the driver, the
time is counted down until it becomes time for the function to be called.
When the time comes, the driver removes the object from the list of
objects with pending call outs and performs the call to the call out
function, passing any special args originally specified by the call out
function.
If you want a to remove a pending call before it occurs, you need to use
the remove_call_out() efun, passing the name of the function being
called out. The driver will remove the next pending call out to that
function. This means you may have some ambiguity if more than one
call out is pending for the same function.
In order to make a call out cyclical, you must reissue the call_out() efun
in the function you called out, since the driver automatically removes the
function from the call out table when a call out is performed. Example:
void foo() { call_out(\"hello\", 10); }
void hello() { call_out(\"hello\", 10); }
will set up hello() to be called every 10 seconds after foo() is first called.
There are several things to be careful about here. First, you must watch
to make sure you do not structure your call outs to be recursive in any
unintended fashion. Second, compare what a set_heart_beat() does
when compared directly to what call_out() does.
set_heart_beat():
a) Adds this_object() to a table listing objects with heartbeats.
b) The function heart_beat() in this_object() gets called every single
driver cycle.
call_out():
a) Adds this_object(), the name of a function in this_object(), a time
delay, and a set of arguments to a table listing functions with pending
call outs.
b) The function named is called only once, and that call comes after the
specified delay.
As you can see, there is a much greater memory overhead associated
with call outs for part (a), yet that there is a much greater CPU overhead
associated with heartbeats as shown in part (b), assuming that the delay
for the call out is greater than a single driver cycle.
Clearly, you do not want to be issuing 1 second call outs, for then you
get the worst of both worlds. Similarly, you do not want to be having
heart beats in objects that can perform the same functions with call outs
of a greater duration than 1 second. I personally have heard much talk
about at what point you should use a call out over a heartbeat. What I
have mostly heard is that for single calls or for cycles of a duration
greater than 10 seconds, it is best to use a call out. For repetitive calls of
durations less than 10 seconds, you are better off using heartbeats. I do
not know if this is true, but I do not think following this can do any
harm.
2.5 Summary
Basic to a more in depth understanding of LPC is and understanding of
the way in which the driver interacts with the mudlib. You should now
understand the order in which the driver performs functions, as well as a
more detailed knowledge of the efuns this_player(), add_action(), and
move_object() and the lfun init(). In addition to this building upon
knowledge you got from the LPC Basics textbook, this chapter has
introduced call outs and heartbeats and the manner in which the driver
handles them. You should now have a basic understanding of call outs
and heartbeats such that you can experiment with them in your realm
code.
Copyright (c) George Reese 1993
",({"chapter 29","chapter twenty-nine","29",}):"chapter 29 \"Weapons\"
Building Weapons
The Nightmare IV LPC Library
written by Descartes of Borg 950429
All items in the Nightmare LPC Library (descendants of /lib/item.c)
are weapons. A player can, for example, use a can of spam as a
weapon. However, they are set up as extremely pathetic weapons. This
document describes in detail how to make an object into a real weapon.
I. Basic Stuff
The basic weapon is exactly the same as the basic item. You can do
anything to it that can be done to other items. For details on items,
see /doc/build/Items. The simple weapon should look like this:
#include <lib.h>
#include <damage_types.h>
#include <vendor_types.h>
inherit LIB_ITEM;
static void create() {
item::create();
SetKeyName(\"short sword\");
SetId( ({ \"sword\", \"short sword\", \"a short sword\" }) );
SetAdjectives( ({ \"short\" }) );
SetShort(\"a short sword\");
SetLong(\"A rusty short sword with specs of blood on it.\");
SetVendorType(VT_WEAPON);
SetDamagePoints(1500);
SetClass(12);
SetValue(150);
SetMass(100);
SetWeaponType(\"blade\");
SetDamageType(BLADE);
}
The last part is what differs from regular items. Note the functions:
SetVendorType()
SetClass()
SetWeaponType()
SetDamageType()
The available vendor types can be found by reading the file
/include/vendor_types.h. Similarly, damage types may be found by
reading /include/damage_types.h. The vendor type states what sort of
stores can carry this item. VT_WEAPON should almost ALWAYS be the
vendor type you give for weapons.
SetClass()
The class is the basic weapon strength. It is how much damage gets
done without any modification. This number ranges between 1 and 100,
where 1 is a pathetic weapon (the class for basic items) and 100 is
probably bordering on illegal.
SetWeaponType()
This sets what sort of attack skill the player needs to use this
weapon. The weapon types are:
blade
knife
blunt
projectile
SetDamageType()
Damage types, again, are found in /include/damage_types.h. This sets
what type of damage is done by this weapon to its victims.
II. Wield Functions
mixed SetWield(string | function)
Examples:
SetWield(\"The short sword feels dull as you wield it.\");
SetWield( (: WieldMe :) );
If you pass a string to SetWield(), then the player sees that string
whenever they wield the weapon. If, on the other hand, you pass a
function, then that function will get called just before the weapon is
wielded when the player issues the wield command. The function you
pass should be written in the form of:
int WieldMe();
If the function returns 1, then the player can wield the weapon. If
it returns 0, then the player cannot wield the weapon. Note that if
you have a wield function, you are responsible for all messaging to
the player to let the player know that they can/cannot wield the
weapon. Example:
int WieldMe() {
if( (int)this_player()->ClassMember(\"fighter\") ) {
write(\"The short sword gives you power as you wield it.\");
say((string)this_player()->GetName() + \" wields a short sword.\");
return 1;
}
else {
write(\"You are not worthy of this short sword.\");
return 0;
}
}
III. Modifying Stats and Skills
A common thing people like to do with weapons is temporarily modify a
player's skills. This is done by making use of the function
AddStatBonus() and AddSkillBonus(). Most of the time this is done
through a SetWield() function.
void AddStatBonus(string stat, function f);
void AddSkillBonus(string stat, function f);
Examples:
this_player()->AddStatBonus(\"wisdom\", (: CheckStat :));
this_player()->AddSkillBonus(\"blade attack\", (: CheckSkill :));
The functions then have the format:
int CheckWhatever(string stat_or_skill);
NOTE: You should always check whether the bonus is still in effect.
For example, make sure the weapon is still wielded if it results from
wielding the weapon. For example:
#include <lib.h>
inherit LIB_ITEM;
int DoWield()
int CheckBlade(string skill);
static void create() {
...
SetWield((: DoWield :));
...
}
int DoWield() {
this_player()->AddSkillBonus(\"blade attack\", (: CheckBlade :) );
write(\"You wield the short sword.\");
say((string)this_player()->GetName() + \" wields a short sword.\");
return 1;
}
int CheckBlade(string skill) {
if( !GetWorn() ) {
previous_object()->RemoveSkillBonus(\"blade\", this_object());
return 0;
}
else return 5;
}
In other words, this weapon will give its wielder a blade attack bonus
of 5. Note that you must use previous_object() in CheckBlade() and
NOT this_player() because there is no way of knowing who this_player()
is at the time. You do know, however, that the object calling
CheckBlade() is always the player for whom the skill bonus is made.
Always remember to remove bonuses.
IV. Modifying Hits
The Nightmare IV LPC Library uses an event driven combat system. With
respect to weapons, a round of combat is broken down into the
following events:
1. The person wielding the weapon uses it.
2. If they cannot hit a motionless target, the round ends.
3. If the target dodges the attack, the round ends.
4. eventStrike() is called in the weapon to determine how much damage
the weapon can do.
5. eventReceiveDamage() is called in the target object. This in turn:
a. Calls eventReceiveDamage() in all armour objects, which each:
i. Calls eventReceiveDamage() in the weapon
ii. The weapon wears down a bit
b. The armour wears down a bit
c. The amount of armour damage absorbed is returned
d. The target objects loses health points.
f. The amount of damage done is returned.
6. Skill and stat points are added.
Note the two important functions which get called in weapon.c:
int eventStrike(object ob);
int eventReceiveDamage(int type, int amount, int unused, mixed limbs);
By default, eventStrike() returns the value of GetClass(). However,
you can modify this value by overriding the eventStrike(). For
example:
int eventStrike(object target) {
if( (string)target->GetRace() != \"orc\" ) return item::eventStrike(target);
message(\"environment\", \"The orc slayer makes a nasty sound!\",
environment(target));
return item::eventStrike(target) + random(10);
}
NOTE: You should always use item::eventStrike() rather than hard coded
values since weapon class deteriorates over time.
In this example, a random(10) points of extra damage gets done to
orcs. This would be the orc slayer weapon of ancient fame.
For those familiar with hit functions in the old Nightmare Mudlibs,
this would be roughly equivalent to that.
Another place where you can make things happen is in
eventDeteriorate() which gets called by eventReceieveDamage(). This is
where a weapon wears down from the shock which armour has absorbed
from it. For weapons, there is not much which can be done here, but
this document points it out for the creative who feel they might be able to do
somthing with it.
Descartes of Borg
borg@imaginary.com
",({"chapter 38","chapter thirty-eight","38",}):"chapter 38 \"QCS: Adding and deleting\"
Rooms, containers and NPC's all are capable of holding
items, and often it is convenient to have them already holding certain
items upon creation.
The SetInventory directive in an object's file provides us with
a list of that object's \"permanent inventory\". To modify an object's
inventory, we use the add and delete commands.
Let's say that we want our cowboy to be wielding a
hammer when he appears...
%^GREEN%^clone cowboy%^RESET%^
%^GREEN%^cd ../weap%^RESET%^
%^GREEN%^clone hammer%^RESET%^
%^GREEN%^add hammer to cowboy%^RESET%^
%^GREEN%^wield hammer%^RESET%^
Believe it or not, it's that simple. The add command will ask
you a question after you issue it. What it wants to know is if you
want the NPC to do anything special when the hammer appears on him.
In this case, yes, we wanted him to wield it.
However, if you want to add something to an NPC and don't
have anything interesting for him to do with it, respond to the
question with the number of items that you want to appear. For
example, if I want the cowboy to be carrying a key:
%^GREEN%^cd ../obj%^RESET%^
%^GREEN%^clone key%^RESET%^
%^GREEN%^add key to cowboy%^RESET%^
%^GREEN%^1%^RESET%^
And that's it. Now if I want the cowboy to be a permanent
resident of this room:
%^GREEN%^add cowboy%^RESET%^
%^GREEN%^1%^RESET%^
The add command understands that if you don't specify a
target argument, you must mean the room. You can also be specific:
%^GREEN%^add cowboy to room%^RESET%^
%^GREEN%^1%^RESET%^
The delete command works the opposite way. It removes items from
an object's permanent inventory:
%^GREEN%^delete hammer from cowboy%^RESET%^
%^GREEN%^delete cowboy%^RESET%^
The delete command is also the way to get rid of rooms. You won't
be removing the file from disk, you'll just be deleting that exit
from the room:
%^GREEN%^delete exit garden%^RESET%^
NOTE: When you delete an exit, only the connection from your room to
the other room is removed. The connection from the other room to
your room remains. This is not a bug, it's a feature. There are plenty
of circumstances where one-way travel is desirable.
",({"chapter 23","chapter twenty-three","23",}):"chapter 23 \"Properties\"
Supported Properties
The Nightmare IV LPC Library
written by Descartes of Borg 950429
The Nightmare IV LPC Library allows creators to set dynamic variables in
objects which do not get saved when the object saves. The variables are
called properties. A property is an attribute of an object which is
considered fleeting. This document serves to list the properties
commonly used and their purpose. It is by no means complete, as the
point of having properties is to allow creators to build their own on the
fly.
Note: All properties are 0 by default unless otherwise stated.
Property: light
Values: integer between -6 and 6
Light is a value generally between -6 and 6 which, for rooms,
determines how much light is naturally available in a room in daytime.
For other objects, it determines the degree to which the object is
able to modify the amount of light that exists in the room. If the
room is indoors, the light does not change based on the time of day.
Property: no attack
Values: 1 to prevent attacks, 0 to allow them
Things cannot begin combat from inside a room with this property.
Property: no bump
Values: 1 to prevent bumping, 0 to allow it
If a room, then nothing can be bumped from this room. If a living
thing, then it cannot be bumped.
Property: no steal
Values: 1 to prevent stealing, 0 to allow it
This prevents stealing inside a room with this property.
Property: no magic
Values: 1 to prevent magic, 0 to allow it
This prevents any magic from being used inside the room if set.
Property: no paralyze
Values: 1 prevents paralysis from occurring in a room, 0 allows it
Stops any sort of thing which might cause paralysis from occurring in
a room.
Property: no teleport
Values: 1 if teleporting is prohibited, 0 if allowed
Prevents people from teleporting to or from the room.
Property: no clear
Values: 1 to prevent clearing, 0 to allow it
If set this prevents an avatar from clearing a wilderness room in
order to build a town. Not relevant to rooms in towns.
Property: estates
Values: any non-negative number
Sets the number of estates which can be built in an area. No estates
may be built outside of towns.
Property: magic item
Values: an array of strings describing the magic contained in an object
Allows you to mark specific objects as magic. For example, if a sword
has a magical lighting ability, you might do:
SetProperty(\"magic item\", ({ \"light\" }));
Property: lockpicking tool
Values: any integer marking how well lockpicking is enhanced
When picking a lock, the value of this property is calculated for each
object and added to the overall chance to pick the lock.
Property: keep
Values: the name of whomever the object is kept for
While set, this object may only be picked up by the person whose name
matches the value of this property. If 0, anyone can pick it up
assuming it is normally gettable.
Property: magic hold
Value: any integer
Is subtracted from the chance of success of anyone trying to pick a
lock.
Property: enchantment
Value: any integer
Enchants any object to boost (or degrade) its performance of its
natural functions.
Property: login
Value: a string representing a file name
Sets which room a player should login to at next login if they quit
from the room that has this property. For example, if you have a
treasure room that is protected, and therefore you do not want people
logging into it, you can call:
SetProperty(\"login\", \"/file/name/outside/this/room\");
to have the players login to the room outside.
",({"chapter 31","chapter thirty-one","31",}):"chapter 31 \"Overview of the Quick Creation System\"
First, let me clarify that the QCS is not intended to replace
good coding habits. It is also not designed to handle every possible
need of a builder. The QCS is just a handy tool for making the
most tedious parts of building easier.
The amount of time it takes to hand-code an area of 100 rooms
(a small area, that is), with all the appropriate descriptions and
monsters and weapons and such is just mind-boggling. As a grown-up,
I just don't have time for building stuff line by excruciating line
in raw LPC, because I also need to work, maintain my house, say hello
to my family on occasion, etc.
At the same time, I would need a team of dedicated LPC code
fetishists to make a creation system that covers every last possible
thing you could do in LPC. The QCS is somewhere in between those
two extremes.
Therefore please view the QCS as a quick way to get bulk building
done, and not as a be-all end-all solution. You still need to learn
LPC to do really cool stuff. But to hammer out an area with a quest,
QCS lets you sail through the process of thingmaking with little hassle.
The design philosophy of the system itself involves object files
stored in /secure/modules. These files are inherited by an object which
you need to carry with you in order to use the QCS system. The code
for these creation modules is fairly messy and inelegant, but the
result is clean, indented code that compiles, so let's keep the
mockery to a minimum, shall we?
It is important to keep in mind the QCS isn't an editing system.
It's real live on-line modification, meaning that to modify a thing,
it actually has to be in the same room you are in, or it has to be that
room itself.
Once you modify something, it will typically update, so that if
you change the name of an npc, you're going to need to use the new name
to modify it further.
The next few chapters in this manual are nominally QCS specific,
but in reality this is pretty much my only chance to document some
of the changes in Dead Souls since version 1, so even if you never
intend to use QCS, it's worth poking through these chapters.
- Cratylus @ Frontiers
2 January 2006
",({"chapter 38","chapter thirty-eight","38",}):"chapter 38 \"QCS: Adding and deleting\"
Rooms, containers and NPC's all are capable of holding
items, and often it is convenient to have them already holding certain
items upon creation.
The SetInventory directive in an object's file provides us with
a list of that object's \"permanent inventory\". To modify an object's
inventory, we use the add and delete commands.
Let's say that we want our cowboy to be wielding a
hammer when he appears...
%^GREEN%^clone cowboy%^RESET%^
%^GREEN%^cd ../weap%^RESET%^
%^GREEN%^clone hammer%^RESET%^
%^GREEN%^add hammer to cowboy%^RESET%^
%^GREEN%^wield hammer%^RESET%^
Believe it or not, it's that simple. The add command will ask
you a question after you issue it. What it wants to know is if you
want the NPC to do anything special when the hammer appears on him.
In this case, yes, we wanted him to wield it.
However, if you want to add something to an NPC and don't
have anything interesting for him to do with it, respond to the
question with the number of items that you want to appear. For
example, if I want the cowboy to be carrying a key:
%^GREEN%^cd ../obj%^RESET%^
%^GREEN%^clone key%^RESET%^
%^GREEN%^add key to cowboy%^RESET%^
%^GREEN%^1%^RESET%^
And that's it. Now if I want the cowboy to be a permanent
resident of this room:
%^GREEN%^add cowboy%^RESET%^
%^GREEN%^1%^RESET%^
The add command understands that if you don't specify a
target argument, you must mean the room. You can also be specific:
%^GREEN%^add cowboy to room%^RESET%^
%^GREEN%^1%^RESET%^
The delete command works the opposite way. It removes items from
an object's permanent inventory:
%^GREEN%^delete hammer from cowboy%^RESET%^
%^GREEN%^delete cowboy%^RESET%^
The delete command is also the way to get rid of rooms. You won't
be removing the file from disk, you'll just be deleting that exit
from the room:
%^GREEN%^delete exit garden%^RESET%^
NOTE: When you delete an exit, only the connection from your room to
the other room is removed. The connection from the other room to
your room remains. This is not a bug, it's a feature. There are plenty
of circumstances where one-way travel is desirable.
",({"chapter 20","chapter twenty","20",}):"chapter 20 \"Items\"
Building Any Item
The Nightmare IV LPC Library
written by Descartes of Borg 950430
Each object you build has a certain common make-up no matter what type
of object it is. This is because all of your objects actually are
based upon the same object, the object called /lib/object.c. That
object contains functions such as SetShort() and SetLong() which you
use in almost every single object you will build. This document
details how to set up any possible object you will use. The only
exceptions will be for rooms, which do not have key names or id's.
Beyond that, most of the every day objects you will code like armours,
weapons, drinks, foods, etc. all derive from a common object called
/lib/item.c. This document attempts to detail what is involved in
building any object on the MUD through /lib/item.c and its ancestor
/lib/object.c.
This document is in three sections
I. List of Mandatory Function Calls
II. Basic Functions
III. Extras
IV. Events
** *************** List of Mandatory Function Calls ************** **
SetKeyName(\"red bag\");
SetId( ({ \"bag\", \"red bag\" }) );
SetAdjectives( ({ \"red\" }) );
SetShort(\"a red bag\");
SetLong(\"A small red bag with no distinguishing marks.\");
SetMass(90);
SetValue(50);
SetVendorType(VT_BAG);
You also need to include vendor_types.h.
** *************** Basic Functions *************** **
*****
SetKeyName()
*****
string SetKeyName(string key_name);
Example: SetKeyName(\"red bag\");
Notes:
Mandatory for all objects except rooms.
Not used for rooms.
The key name is the central name by which the object is referred to in
sentences where no article is required. For example, the sentence
\"You pick up your red bag\" makes use of the key name to complete the
sentence. This is much like the short description, except the short
description will include an article. For this object, SetShort(\"a red
bag\") would be used.
*****
SetId()
*****
string *SetId(string *id);
Example: SetId( ({ \"bag\", \"red bag\" }) );
Notes:
Mandatory for all objects except rooms.
Not used in rooms.
Must be all lower case.
The id is an array of strings by which the object may be referred to by a
player. For example, if the player wants to get this bag, the player
can type \"get bag\" or \"get red bag\". The id is used purely for
identification purposes, so if you have something you need to sneak in
a unique way of identifying it, you may add an id only you know about.
*****
SetAdjectives()
*****
string *SetAdjectives(string *adjs);
Example: SetAdjectives( ({ \"red\" }) );
Notes:
Planned for future use in Zork style command parsing.
Not used in rooms.
The adjectives are descriptive terms used to describe the object.
This is not currently being used, however, it will be part of the new
style command parsing we will be building. This will allow the player
to type things like \"get the red one\" and pick up the red bag. Even
though it is not used, it is requested you place it in your objects to
make upgrading down the road a simpler task.
*****
SetShort()
*****
string SetShort(string description | function desc_func);
Examples:
SetShort(\"a red bag\");
SetShort((: DescribeBag :));
The short description is a brief description of the object. Only
names and proper nouns should be capitalized, the rest should be lower
case, as if it were appearing in the middle of a sentence. In rooms,
the player sees the short description when in brief mode and when they
glance at the room. For objects, the player sees the short when it is
described in the room or in their inventory.
If you pass a function instead of a string, then that function is used
to create the description. You can use this to do something like make
the object change its short description depending on who is looking at
it. The function that you build should therefore return a string
that will be used as the short description. For example...
string DescribeBag() {
if( query_night() ) return \"a bag\";
else return \"a red bag\";
}
*****
SetLong()
*****
string SetLong(string description | function desc_func);
Examples:
SetLong(\"A red bag with no markings on it whatsoever.\");
SetLong((: BagLong :));
Creates a verbose way to present the object to the player. You should
be much more descriptive than I have been in the example. Being a
text game, descriptions are 90% of what make the game. The more
creative you are with your descriptions, the more interesting the game
is to players. The long description of a room is seen by players in
verbose mode and when the player uses the \"look\" command. For
objects, the long description is seen when the player looks at the
object. Functions work in exactly the same fashion as short
functions.
*****
SetMass()
*****
int SetMass(int mass);
Example: SetMass(100);
Notes:
Mandatory for all visible objects.
Not needed for non-tangible objects and rooms.
Sets the mass for the object. In conjunction with the gravity of the
room it is in, this works to determine the weight of the object.
*****
SetValue()
*****
int SetValue(int value);
Example: SetValue(50);
Notes:
Mandatory for all sellable objects.
Not used in rooms.
Sets the base economic value of an object. This has no meaning in any
currencies, and in fact the actual value in any given currency may
vary.
*****
SetVendorType()
*****
int SetVendorType(int vt);
Example: SetVendorType(VT_BAG);
Note:
Mandatory for all objects except rooms.
Preset to VT_ARMOUR for objects which inherit LIB_ARMOUR.
Preset to VT_TREASURE for objects which inherit LIB_ITEM.
Preset to VT_LIGHT for objects which inherit LIB_LIGHT.
Not valid for room objects.
Values are found in /include/vendor_types.h.
You must do:
#include <vendor_types.h>
to use the VT_* macros (i.e. VT_ARMOUR, VT_TREASURE, VT_WEAPON).
The vendor type determines which shops will buy the item. For
example, things with VT_BAG as the vendor type can be bought and sold
in bag stores. For items which cross the line, for example a flaming
sword, you can combine vendor types in the following manner:
SetVendorType(VT_WEAPON | VT_LIGHT);
*****
SetDamagePoints()
*****
int SetDamagePoints(int pts);
Example: SetDamagePoints(500)
Sets the amount of damage an object can take before descreasing in
value. With armours and weapons, damage is taken quite often. Damage
is more rare with other kinds of objects. With this example object
which has 500 damage points, whenever 500 points has been done to it,
its value is cut in half and eventDeteriorate() is called for the
object. See the events section on using eventDeteriorate(). The
points are then reset to 500 and damage is done from that.
** *************** Extras *************** **
*****
SetProperty()
*****
mixed SetProperty(string property, mixed value);
Example: SetProperty(\"no pick\", 1);
Allows you to store information in an object which may not have been
intended by the designer of the object, or which is fleeting in
nature. See /doc/build/Properties for a list of common properties.
*****
SetProperties()
*****
mapping SetProperties(mapping props);
Example: SetProperties( ([ \"light\" : 1, \"no attack\" : 1 ]) );
Allows you to set any properties you want all in one shot.
*****
SetDestroyOnSell()
*****
int SetDestroyOnSell(int true_or_false);
Example: SetDestroyOnSell(1);
For mundane objects, or objects which should not be resold, allows you
to set it so that the object gets destroyed when sold instead of
allowing it to be resold.
*****
SetPreventGet()
*****
mixed SetPreventGet(mixed val);
Examples:
SetPreventGet(\"You cannot get that!\");
SetPreventGet( (: check_get :) );
Allows you to make an object un-gettable by a player. If you pass a
string, the player will see that string any time they try to get the
item. If you pass a function, that function will be called to see if
you want to allow the get. Your function gets the person trying to get
the object as an argument:
int check_get(object who) {
if( (int)who->GetRave() == \"ogre\" ) {
message(\"my_action\", \"Ogres cannot get this thing!\", who);
return 0;
}
else return 1;
}
*****
SetPreventPut()
*****
mixed SetPreventPut(mixed val);
Examples:
SetPreventPut(\"You cannot put that in there!\");
SetPreventPut( (: check_put :) );
The same as SetPreventGet(), except this is used when the object is
being put into another object.
*****
SetPreventDrop()
*****
mixed SetPreventDrop(mixed val);
Examples:
SetPreventDrop(\"You cannot drop that!\");
SetPreventDrop( (: check_drop :) );
The same as SetPreventGet(), except this is used when a player tries
to drop the object.
** *************** General Events ************** **
*****
eventDeteriorate()
*****
void eventDeteriorate(int type);
Example: ob->eventDeteriorate(COLD);
Notes:
Damage types can be found in /include/damage_types.h
This function gets called periodically in objects whenever they wear
down a bit. The type passed to the function is the type of damage
which triggered the deterioration.
*****
eventMove()
*****
int eventMove(mixed dest);
Example:
ob->eventMove(this_player());
ob->eventMove(\"/domains/Praxis/square\");
The eventMove event is called in an object when it is being moved from
one place to the next. You can either pass it the file name of a room
to which it should be moved or an object into which it should be
moved. It will return true if the object gets moved, false if it
cannot move for some reason. For objects which are being dropped,
gotten, or put, it is generally a good idea to check CanDrop(),
CanClose(), or CanGet() for the object in question since eventMove()
does not know the context of the move and therefore will allow a drop
since it does not check CanDrop().
*****
eventReceiveDamage()
*****
varargs int eventReceiveDamage(int type, int amount, int unused, mixed limbs);
Example: ob->eventReceiveDamage(BLUNT, 30, 0, \"right hand\");
This function gets called in an object whenever any damage is done to
it. Most frequently this gets called in monsters and armour. In
armour you can use it to modify the amount of damage which gets done.
The return value of this function is the amount of damage done to the
object. For example, if you have a piece of armour that absorbs 5 of
the 30 points listed above, then you return 5.
NOTE:
For monsters there is an extra arg at the front called
agent. The agent is the being responsible for doing
the damage. It may be zero if something like the weather
is causing the damage. It looks like:
varargs int eventReceiveDamage(object agent, int type, int strength,
int internal, mixed limbs);
For more detailed information, see /doc/build/NPC.
",({"chapter 10","chapter ten","10",}):"chapter 10 \"The LPMud Driver\"
Intermediate LPC
Descartes of Borg
Novermber 1993
Chapter 2: The LPMud Driver
2.1 Review of Basic Driver/Mudlib Interaction
In the LPC Basics textbook, you learned a lot about the way the mudlib
works, specifically in relation to objects you code in order to build your
realm. Not much was discussed about the interaction between the
mudlib and the driver. You should know, however, that the driver
does the following:
1) When an object is first loaded into memory, the driver will call
create() in native muds and reset() in compat muds. A creator
uses create() or reset() to give initial values to the object.
2) At an interval setup by the game administrator, the driver calls the
function reset(). This allows the object to regenerate monsters and
such. Notice that in a compat mud, the same function is used to set up
initial values as is used to reset the room.
3) Any time a living object comes in contact with an object of any sort,
the driver calls init() in the newly encountered object. This allows
newly encountered objects to give living objects commands to execute
through the add_action() efun, as well as perform other actions which
should happen whenever a living thing encounters a given object.
4) The driver defines a set of functions known as efuns which are
available to all objects in the game. Examples of commonly used efuns
are: this_player(), this_object(), write(), say(), etc.
2.2 The Driver Cycle
The driver is a C program which runs the game. Its basic functions are
to accept connections from the outside world so people can login,
interpret the LPC code which defines LPC objects and how they
function in the game, and accept user input and call the appropriate LPC
functions which match the event. In its most simplest essence, it is an
unending loop.
Once the game has booted up and is properly functioning (the boot up
process will be discussed in a future, advanced LPC textbook), the
driver enters a loop which does not terminate until the shutdown() efun
is legally called or a bug causes the driver program to crash. First off,
the driver handles any new incoming connections and passes control of
the connection to a login object. After that, the driver puts together a
table of commands which have been entered by users since the last cycle
of the driver. After the command table is assembled, all messages
scheduled to be sent to the connection from the last driver cycle are sent
out to the user. At this point, the driver goes through the table of
commands to be executed and executes each set of commands each
object has stored there. The driver ends its cycle by calling the function
heart_beat() in every object with a heart_beat() set and finally
performing all pending call outs. This chapter will not deal with the
handling of connections, but instead will focus on how the driver
handles user commands and heartbeats and call outs.
2.3 User Commands
As noted in section 1.2, the driver stores a list of commands for each
user to be executed each cycle. The commands list has the name of the
living object performing the command, the object which gave the living
object that command, and the function which is to be executed in order
to perform the command. The driver refers to the object which typed in
the command as the command giver. It is the command giver which
gets returned as this_player() in most cases.
The driver starts at the top of the list of living objects with pending
commands, and successively performs each command it typed by calling
the function associated with the command and passing any arguments
the command giver gave as arguments to the function. As the driver
starts with the commands issued by a new living object, the command
giver variable is changed to be equal to the new living object, so that
during the sequence of functions initiated by that command, the efun
this_player() returns the object which issued the command.
Let's look at the command buffer for an example player. Since the
execution of his last command, Bozo has typed \"north\" and \"tell
descartes when is the next reboot\". The command \"north\" is associated
with the function \"Do_Move()\" in the room Bozo is in (the command
\"north\" is automatically setup by the SetExits() efun in that room). The
command \"tell\" is not specifically listed as a command for the player,
however, in the player object there is a function called \"cmd_hook()\"
which is associated with the command \"\", which matches any possible
user input.
Once the driver gets down to Bozo, the command giver variable is set to
the object which is Bozo. Then, seeing Bozo typed \"north\" and the
function \"north\" is associated with, the driver calls Bozo's_Room-
>Do_Move(0). An argument of 0 is passed to the function since Bozo
only typed the command \"north\" with no arguments. The room
naturally calls some functions it needs, all the while such that the efun
this_player() returns the object which is Bozo. Eventually, the room
object will call eventMoveLiving() in Bozo, which in turn calls the
move_object() efun. This efun is responsible for changing an object's
environment.
When the environment of an object changes, the commands available to
it from objects in its previous environment as well as from its previous
environment are removed from the object. Once that is done, the driver
calls the efun init() in the new environment as well as in each object in
the new environment. During each of these calls to init(), the object
Bozo is still the command giver. Thus all add_action() efuns from this
move will apply to Bozo. Once all those calls are done, control passes
back from the move_object() efun to the eventMoveLiving() lfun in Bozo.
eventMoveLiving() returns control back to Do_Move() in the old room,
which returns 1 to signify to the driver that the command action was
successful. If the Do_Move() function had returned 0 for some reason,
the driver would have written \"What?\" (or whatever your driver's
default bad command message is) to Bozo.
Once the first command returns 1, the driver proceeds on to Bozo's
second command, following much the same structure. Note that with
\"tell descartes when is the next reboot\", the driver passes \"descartes
when is the next reboot\" to the function associated with tell. That
function in turn has to decide what to do with that argument. After that
command returns either 1 or 0, the driver then proceeds on to the next
living object with commands pending, and so on until all living objects
with pending commands have had their commands performed.
2.4 The Efuns set_heart_beat() and call_out()
Once all commands are performed for objects with commands pending,
the driver then proceeds to call the heart_beat() function in all objects
listed with the driver as having heartbeats. Whenever an object calls the
efun set_heart_beat() with a non-zero argument (depending on your
driver, what non-zero number may be important, but in most cases you
call it with the int 1). The efun set_heart_beat() adds the object which
calls set_heart_beat() to the list of objects with heartbeats. If you call it
with an argument of 0, then it removes the object from the list of objects
with heartbeats.
The most common use for heartbeats in the mudlib is to heal players and
monsters and perform combat. Once the driver has finished dealing with
the command list, it goes through the heartbeat list calling heart_beat() in
each object in the list. So for a player, for example, the driver will call
heart_beat() in the player which will:
1) age the player
2) heal the player according to a heal rate
3) check to see if there are any hunted, hunting, or attacking objects
around
4) perform an attack if step 3 returns true.
5) any other things which need to happen automatically roughly every
second
Note that the more objects which have heartbeats, the more processing
which has to happen every cycle the mud is up. Objects with heartbeats
are thus known as the major hog of CPU time on muds.
The call_out() efun is used to perform timed function calls which do not
need to happen as often as heartbeats, or which just happen once. Call
outs let you specify the function in an object you want called. The
general formula for call outs is:
call_out(func, time, args);
The third argument specifying arguments is optional. The first argument
is a string representing the name of the function to be called. The second
argument is how many seconds should pass before the function gets
called.
Practically speaking, when an object calls call_out(), it is added to a list
of objects with pending call outs with the amount of time of the call out
and the name of the function to be called. Each cycle of the driver, the
time is counted down until it becomes time for the function to be called.
When the time comes, the driver removes the object from the list of
objects with pending call outs and performs the call to the call out
function, passing any special args originally specified by the call out
function.
If you want a to remove a pending call before it occurs, you need to use
the remove_call_out() efun, passing the name of the function being
called out. The driver will remove the next pending call out to that
function. This means you may have some ambiguity if more than one
call out is pending for the same function.
In order to make a call out cyclical, you must reissue the call_out() efun
in the function you called out, since the driver automatically removes the
function from the call out table when a call out is performed. Example:
void foo() { call_out(\"hello\", 10); }
void hello() { call_out(\"hello\", 10); }
will set up hello() to be called every 10 seconds after foo() is first called.
There are several things to be careful about here. First, you must watch
to make sure you do not structure your call outs to be recursive in any
unintended fashion. Second, compare what a set_heart_beat() does
when compared directly to what call_out() does.
set_heart_beat():
a) Adds this_object() to a table listing objects with heartbeats.
b) The function heart_beat() in this_object() gets called every single
driver cycle.
call_out():
a) Adds this_object(), the name of a function in this_object(), a time
delay, and a set of arguments to a table listing functions with pending
call outs.
b) The function named is called only once, and that call comes after the
specified delay.
As you can see, there is a much greater memory overhead associated
with call outs for part (a), yet that there is a much greater CPU overhead
associated with heartbeats as shown in part (b), assuming that the delay
for the call out is greater than a single driver cycle.
Clearly, you do not want to be issuing 1 second call outs, for then you
get the worst of both worlds. Similarly, you do not want to be having
heart beats in objects that can perform the same functions with call outs
of a greater duration than 1 second. I personally have heard much talk
about at what point you should use a call out over a heartbeat. What I
have mostly heard is that for single calls or for cycles of a duration
greater than 10 seconds, it is best to use a call out. For repetitive calls of
durations less than 10 seconds, you are better off using heartbeats. I do
not know if this is true, but I do not think following this can do any
harm.
2.5 Summary
Basic to a more in depth understanding of LPC is and understanding of
the way in which the driver interacts with the mudlib. You should now
understand the order in which the driver performs functions, as well as a
more detailed knowledge of the efuns this_player(), add_action(), and
move_object() and the lfun init(). In addition to this building upon
knowledge you got from the LPC Basics textbook, this chapter has
introduced call outs and heartbeats and the manner in which the driver
handles them. You should now have a basic understanding of call outs
and heartbeats such that you can experiment with them in your realm
code.
Copyright (c) George Reese 1993
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction to the Coding Environment\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 25 may 1993
CHAPTER 1: Introduction to the Coding Environment
1.1 UNIX file structure
LPMuds use basic UNIX commands and its file structure. If you know
UNIX commands already, then note (with a few exceptions) options are
not available to the commands. Like DOS, UNIX is heirarchical. The
root directory of which all directories are sub-directories is called
root(/). And from those sub-directories you may have further
sub-directories. A directory may be referred to in two different ways:
1) by its full name, or absolute name, or 2) by its relative name.
Absolute name refers to the directory's full path starting from / winding
down the directory tree until you name the directory in question. For
example:
/players/descartes/obj/monster
refers to the directory monster which is a sub-directory of obj which
is a sub-directory of descartes which is a sub-directory of players
which is a sudirectory of /.
The relative name refers to the name relative to another directory.
The above example is called monster relative to /players/descartes/obj,
but it is also called obj/monster relative to /players/descartes,
descartes/obj/monster relative to /players, and finally
players/descartes/obj/monster relative to /. You can tell the
difference between absolute names and relative names because absolute
names always start with /. In order to know exactly which directory
is being named by a relative name, you naturally must know what
directory it is relative to.
A directory contains sub-directories and files. LPMuds only use text files
inside the mudlib. Like directories, files have both absolute and
relative names. The most basic relative name is often referred to as the file
name, with the rest of the absolute name being referred to as the path. So,
for the file: /players/descartes/castle.c, castle.c is the file name, and
/players/descartes is the path.
On some muds, a file with a file name beginning with a . (like .plan) is
not visible when you list files with the regular file listing command.
1.2 UNIX Commands
Along with the UNIX file structure, LPMuds use many UNIX commands. Typical
UNIX commands on most muds are:
pwd, cd, ls, rm, mv, cp, mkdir, rmdir, more, head, cat, ed
If you have never before seen UNIX commands, you probably are thinking this
is all nonsense. Well, it is, but you got to use them. Before getting
into what they mean though, first a discussion of current directory.
If you know DOS, then you know what a current working directory is.
At any given point, you are considered to be \"in\" some directory. This
means that any relative file or directory names you give in UNIX commands
are relative to that directory. For example, if my current directory is
/players/descartes and I type \"ed castle.c\" (ed is the command to edit),
then it assumes I mean the file /players/descartes/castle.c
pwd: shows you your current working directory
cd: changes your current working directory. You may give either relative
or absolute path names. With no arguments, it changes to your home
directory.
ls: lists all files in the directory named. If no directory is named,
it lists the files of the current working directory
rm: deletes the file named
mv: renames the file named
cp: copies the file named
mkdir: makes a new directory
rmdir: deletes a directory. All files must have been first removed.
more: pages the file named so that the file appears on your screen one
page at a time.
cat: shows the whole file to you at once
head: shows you the first several lines of a file
tail: shows you the last several lines of a file
ed: allows you to edit a file using the mud editor
1.3 Chapter Summary
UNIX uses a heirarchical file structure with the root of the tree being
named /. Other directories branch off from that root directory and
in turn have their own sub-directories. All directories may contain
directories and files. Directories and files are referred to either
by their absolute name, which always begins with /, or by their relative
name which gives the file's name relative to a particular directory.
In order to get around in the UNIX files structure, you have the
typical UNIX commands for listing files, your current directory, etc.
On your mud, all of the above commands should have detailed help commands
to help you explore exactly what they do. In addition, there should
be a very detailed file on your mud's editor. If you are unfamiliar
with ed, you should go over this convoluted file.
",({"chapter 14","chapter fourteen","14",}):"chapter 14 \"Intermediate Inheritance\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 6: Intermediate Inheritance
6.1 Basics of Inheritance
In the textbook LPC Basics, you learned how it is the mudlib maintains
consistency amoung mud objects through inheritance. Inheritance
allows the mud administrators to code the basic functions and such that
all mudlib objects, or all mudlib objects of a certain type must have so
that you can concentrate on creating the functions which make these
objects different. When you build a room, or a weapon, or a monster,
you are taking a set of functions already written for you and inheriting
them into your object. In this way, all objects on the mud can count on
other objects to behave in a certain manner. For instance, player objects
can rely on the fact that all room objects will have a function in them
called GetLong() which describes the room. Inheritance thus keeps
you from having to worry about what the function GetLong() should
look like.
Naturally, this textbook tries to go beyond this fundamental knowledge
of inheritance to give the coder a better undertstanding of how
inheritance works in LPC programming. Without getting into detail that
the advanced domain coder/beginner mudlib coder simply does not yet
need, this chapter will try to explain exactly what happens when you
inherit an object.
6.2 Cloning and Inheritance
Whenever a file is referenced for the first time as an object (as opposed
to reading the contents of the file), the game tries to load the file into
memory and create an object. If the object is successfully loaded into
memory, it becomes as master copy. Master copies of objects may be
cloned but not used as actual game objects. The master copy is used to
support any clone objects in the game.
The master copy is the source of one of the controversies of mud LPC
coding, that is whether to clone or inherit. With rooms, there is no
question of what you wish to do, since there should only be one instance
of each room object in the game. So you generally use inheritance in
creating rooms. Many mud administrators, including myself, however
encourage creators to clone the standard monster object and configure it
from inside room objects instead of keeping monsters in separate files
which inherit the standard monster object.
As I stated above, each time a file is referenced to create an object, a
master copy is loaded into memory. When you do something like:
void reset() {
object ob;
ob = new(\"/std/monster\");
/* clone_object(\"/std/monster\") some places */
ob->SetKeyName(\"foo monster\");
... rest of monster config code followed by moving
it to the room ...
}
the driver searches to see if their is a master object called \"/std/monster\".
If not, it creates one. If it does exist, or after it has been created, the
driver then creates a clone object called \"/std/monster#<number>\". If
this is the first time \"/std/monster\" is being referenced, in effect, two
objects are being created: the master object and the cloned instance.
On the other hand, let's say you did all your configuring in the create()
of a special monster file which inherits \"/std/monster\". Instead of
cloning the standard monster object from your room, you clone your
monster file. If the standard monster has not been loaded, it gets loaded
since your monster inherits it. In addition, a master copy of your file
gets loaded into memory. Finally, a clone of your monster is created
and moved into the room, for a total of three objects added to the game.
Note that you cannot make use of the master copy easily to get around
this. If, for example, you were to do:
\"/realms/descartes/my_monster\"->eventMove(this_object());
instead of
new(\"/realms/descartes/my_monster\")->eventMove(this_object());
you would not be able to modify the file \"my_monster.c\" and update it,
since the update command destroys the current master version of an
object. On some mudlibs it also loads the new version into memory.
Imagine the look on a player's face when their monster disappears in
mid-combat cause you updated the file!
Cloning is therefore a useful too when you plan on doing just that-
cloning. If you are doing nothing special to a monster which cannot be
done through a few call others, then you will save the mud from getting
loaded with useless master copies. Inheritance, however, is useful if
you plan to add functionality to an object (write your own functions) or
if you have a single configuration that gets used over and over again
(you have an army of orc guards all the same, so you write a special orc
file and clone it).
6.3 Inside Inheritance
When objects A and B inherit object C, all three objects have their own
set of data sharing one set of function definitions from object C. In
addition, A and B will have separate functions definitions which were
entered separately into their code. For the sake of example throughout
the rest of the chapter, we will use the following code. Do not be
disturbed if, at this point, some of the code makes no sense:
STD_ITEM C
private string name, cap_name, short, long;
private int setup;
void SetKeyName(string str)
nomask string GetKeyName();
private int query_setup();
static void unsetup();
void SetShort(string str);
string GetShort();
void SetLong(string str);
string GetLong();
void SetKeyName(string str) {
if(!query_setup()) {
name = str;
setup = 1;
}
nomask string GetKeyName() { return name; }
private query_setup() { return setup; }
static void unsetup() { setup = 0; }
string GetName() {
return (name ? capitalize(name) : \"\"); }
}
void SetShort(string str) { short = str; }
string GetShort() { return short; }
void SetLong(string str) { long = str; }
string GetLong() { return str; }
void create() { seteuid(getuid()); }
STD_ITEM B
inherit \"/std/objectc\";
private int wc;
void set_wc(int wc);
int query_wc();
int wieldweapon(string str);
void create() { ::create(); }
void init() {
if(environment(this_object()) == this_player())
add_action(\"wieldweapon\", \"wield\");
}
void set_wc(int x) { wc = x; }
int query_wc() { return wc; }
int wieldweapon(string str) {
... code for wielding the weapon ...
}
STD_ITEM A
inherit \"/std/objectc\";
int ghost;
void create() { ::create(); }
void change_name(string str) {
if(!((int)this_object()->is_player())) unsetup();
SetKeyName(str);
}
string GetName() {
if(ghost) return \"A ghost\";
else return ::GetName();
}
As you can see, object C is inherited both by object A and object B.
Object C is a representation of a much oversimplified base object, with B
being an equally oversimplified weapon and A being an equally
simplified living object. Only one copy of each function is retained in
memory, even though we have here three objects using the functions.
There are of course, three instances of the variables from Object C in
memory, with one instance of the variables of Object A and Object B in
memory. Each object thus gets its own data.
6.4 Function and Variable Labels
Notice that many of the functions above are proceeded with labels which
have not yet appeared in either this text or the beginner text, the labels
static, private, and nomask. These labels define special priveledges
which an object may have to its data and member functions. Functions
you have used up to this point have the default label public. This is
default to such a degree, some drivers do not support the labeling.
A public variable is available to any object down the inheritance tree
from the object in which the variable is declared. Public variables in
object C may be accessed by both objects A and B. Similarly, public
functions may be called by any object down the inheritance tree from the
object in which they are declared.
The opposite of public is of course private. A private variable or
function may only be referenced from inside the object which declares it.
If object A or B tried to make any reference to any of the variables in
object C, an error would result, since the variables are said to be out of
scope, or not available to inheriting classes due to their private labels.
Functions, however, provide a unique challenge which variables do not.
External objects in LPC have the ability to call functions in other objects
through call others. The private label does not protect against call
others.
To protect against call others, functions use the label static. A function
which is static may only be called from inside the complete object or
from the game driver. By complete object, I mean object A can call
static functions in the object C it inherits. The static only protects against
external call others. In addition, this_object()->foo() is considered an
internal call as far as the static label goes.
Since variables cannot be referenced externally, there is no need for an
equivalent label for them. Somewhere along the line, someone decided
to muddy up the waters and use the static label with variables to have a
completely separate meaning. What is even more maddening is that this
label has nothing to do with what it means in the C programming
language. A static variable is simply a variable that does not get saved to
file through the efun save_object() and does not get restored through
restore_object(). Go figure.
In general, it is good practice to have private variables with public
functions, using query_*() functions to access the values of inherited
variables, and set_*(), add_*(), and other such functions to change
those values. In realm coding this is not something one really has to
worry a lot about. As a matter of fact, in realm coding you do not have
to know much of anything which is in this chapter. To be come a really
good realm coder, however, you have to be able to read the mudlib
code. And mudlib code is full of these labels. So you should work
around with these labels until you can read code and understand why it
is written that way and what it means to objects which inherit the code.
The final label is nomask, and it deals with a property of inheritance
which allows you to rewrite functions which have already been defined.
For example, you can see above that object A rewrote the function
GetName(). A rewrite of function is called overriding the
function. The most common override of a function would be in a case
like this, where a condition peculiar to our object (object A) needs to
happen on a call ot the function under certain circumstances. Putting test
code into object C just so object A can be a ghost is plain silly. So
instead, we override GetName() in object A, testing to see if the
object is a ghost. If so, we change what happens when another object
queries for the cap name. If it is not a ghost, then we want the regular
object behaviour to happen. We therefore use the scope resolution
operator (::) to call the inherited version of the GetName()
function and return its value.
A nomask function is one which cannot be overridden either through
inheritance or through shadowing. Shadowing is a sort of backwards
inheritance which will be detailed in the advanced LPC textbook. In the
example above, neither object A nor object B (nor any other object for
that matter) can override GetKeyName(). Since we want to use
GetKeyName() as a unique identifier of objects, we don't want people
faking us through shadowing or inheritance. The function therefore gets
the nomask label.
6.5 Summary
Through inheritance, a coder may make user of functions defined in
other objects in order to reduce the tedium of producing masses of
similar objects and to increase the consistency of object behaviour across
mudlib objects. LPC inheritance allows objects maximum priveledges in
defining how their data can be accessed by external objects as well as
objects inheriting them. This data security is maintained through the
keywords, nomask, private, and static.
In addition, a coder is able to change the functionality of non-protected
functions by overriding them. Even in the process of overriding a
function, however, an object may access the original function through
the scope resolution operator.
Copyright (c) George Reese 1993
",({"chapter 18","chapter eighteen","18",}):"chapter 18 \"Valid climates\"
indoors
temperate
arid
arctic
tropical
sub-tropical
",({"chapter 2","chapter two","2",}):"chapter 2 \"The LPC Program\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 16 june 1993
CHAPTER 2: The LPC Program
2.1 About programs
The title of this chapter of the textbook is actually poorly named, since
one does not write programs in LPC. An LPC coder instead writes *objects*.
What is the difference? Well, for our purposes now, the difference is
in the way the file is executed. When you \"run\" a program, execution
begins at a definite place in the program. In other words, there
is a place in all programs that is noted as the beginning where program
execution starts. In addition, programs have definite end points,
so that when execution reaches that point, the execution of the program
terminates. So, in short, execution of a program runs from a definite
beginning point through to a definite end point. This is not so with
LPC objects.
With muds, LPC objects are simply distinct parts of the C program which
is running the game (the driver). In other words, execution of the mud
program begins and ends in the driver. But the driver in fact does
very little in the way of creating the world you know when you play
a mud. Instead, the driver relies heavily on the code created in LPC,
executing lines of the objects in the mud as needed. LPC objects thus
have no place that is necessarily the beginning point, nor do they
have a definite ending point.
Like other programming languages, an LPC \"program\" may be made up of
one or more files. For an LPC object to get executed, it simple
needs to be loaded into the driver's memory. The driver will call lines
from the object as it needs according to a structure which will be
defined throughout this textbook. The important thing you need to
understand at this point is that there is no \"beginning\" to an LPC
object in terms of execution, and there is no \"end\".
2.2 Driver-mudlib interaction
As I have mentioned earlier, the driver is the C program that runs on
the host machine. It connects you into the game and processes LPC code.
Note that this is one theory of mud programming, and not necessarily
better than others. It could be that the entire game is written in C.
Such a game would be much faster, but it would be less flexible in
that wizards could not add things to the game while it was running. This
is the theory behind DikuMUDs. Instead, LPMUDs run on the theory that
the driver should in no define the nature of the game, that the nature
of the game is to be decided by the individuals involved, and that
you should be able to add to the game *as it is being played*. This
is why LPMUDs make use of the LPC programming language. It allows
you to define the nature of the game in LPC for the driver to read and
execute as needed. It is also a much simpler language to understand
than C, thus making the process of world creation open to a greater
number of people.
Once you have written a file in LPC (assuming it is corrent LPC ), it justs
sits there on the host machine's hard drive until something in the game
makes reference to it. When something in the game finally does make
reference to the object, a copy of the file is loaded into memory and
a special *function* of that object is called in order to initialize
the values of the variables in the object. Now, do not be concerned
if that last sentence went right over your head, since someone brand
new to programming would not know what the hell a function or a variable
is. The important thing to understand right now is that a copy of the
object file is taken by the driver from the machine's hard drive and
stored into memory (since it is a copy, multiple versions of that
object may exist). You will later understand what a function is, what
a variable is, and exactly how it is something in the game made reference
to your object.
2.3 Loading an object into memory
Although there is no particular place in an object code that must exist
in order for the driver to begin executing it, there is a place for which
the driver will search in order to initialize the object. On compat
drivers, it is the function called reset(). On native muds it is the
function called create().
LPC objects are made up of variables (values which can change) and
functions which are used to manipulate those variables. Functions
manipulate variables through the use of LPC grammatical structures,
which include calling other functions, using externally defined
functions (efuns), and basic LPC expressions and flow control
mechanisms.
Does that sound convoluted? First lets start with a variable. A
variable might be something like: level. It can \"vary\" from sitation
to situation in value, and different things use the value of the player's
level to make different things happen. For instance, if you are a
level 19 player, the value of the variable level will be 19. Now
if your mud is on the old LPMud 2.4.5 system where levels 1-19 are
players and 20+ are wizards, things can ask for your level value to
see if you can perform wizard type actions. Basically, each object
in LPC is a pile of variables with values which change over time.
Things happen to these objects based on what values its variables
hold. Often, then things that happen cause the variables to change.
So, whenever an object in LPC is referenced by another object currently
in memory, the driver searches to see what places for values the
object has (but they have no values yet). Once that is done, the driver
calls a function in the object called reset() or create() (depending
on your driver) which will set up the starting values for the object's
variables. It is thus through *calls* to *functions* that variable
values get manipulated.
But create() or reset() is NOT the starting place of LPC code, although
it is where most LPC code execution does begin. The fact is, those
functions need not exist. If your object does just fine with its
starting values all being NULL pointers (meaning, for our purposes
here, 0), then you do not need a create() or reset() function. Thus
the first bit of execution of the object's code may begin somewhere
completely different.
Now we get to what this chapter is all about. The question: What
consists a complete LPC object? Well, an LPC object is simply
one or more functions grouped together manipulating 0 or more
variables. The order in which functions are placed in an object
relative to one another is irrelevant. In other words:
-----
void init() { add_action(\"smile\", \"smile\"); }
void create() { return; }
int smile(string str) { return 0; }
-----
is exactly the same as:
-----
void create() { return; }
int smile(string str) { return 0; }
void init() { add_action(\"smile\", \"smile\"); }
_____
Also important to note, the object containing only:
-----
void nonsense() {}
-----
is a valid, but trivial object, although it probably would not interact
properly with other objects on your mud since such an object has no
weight, is invisible, etc..
2.4 Chapter summary
LPC code has no beginning point or ending point, since LPC code is used
to create objects to be used by the driver program rather than create
individual programs. LPC objects consist of one or more functions whose
order in the code is irrelevant, as well as of zero or more variables whose
values are manipulated inside those functions. LPC objects simply sit
on the host machine's hard driver until referenced by another object in
the game (in other words, they do not really exist). Once the object
is referenced, it is loaded into the machine's memory with empty
values for the variables. The function reset() in compat muds or
create() in native muds is called in that object if it exists to allow
the variables to take on initial values. Other functions in the object
are used by the driver and other objects in the game to allow interaction
among objects and the manipulation of the LPC variables.
A note on reset() and create():
create() is only used by muds in native mode (see the textbook Introduction
for more information on native mode vs. compat mode). It is only used
to initialize newly referenced objects.
reset() is used by both muds in compat mode and native mode. In compat
mode, reset() performs two functions. First, it is used to initialize
newly referenced objects. In addition, however, compat mode muds use
reset() to \"reset\" the object. In other words, return it to its initial
state of affairs. This allows monsters to regenerate in a room and doors
to start back in the shut position, etc.. Native mode muds use reset()
to perform the second function (as its name implies).
So there are two important things which happen in LP style muds which
cause the driver to make calls to functions in objects. The first is
the creation of the object. At this time, the driver calls a function
to initalize the values in the object. For compat mode muds, this
is performed by the function named reset() (with an argument of 0,
more on this later though). For muds running in native mode, this is
performed by the function create().
The second is the returning of the room to some base state of affairs.
This base set of affairs may or may not be different from the initial
state of affairs, and certainly you would not want to take up time
doing redundant things (like resetting variables that never change).
Compat mode muds nevertheless use the same function that was used to
create the object to reset it, that being reset(). Native mode muds,
who use create() to create the room, instead use reset() to reset it.
All is not lost in compat mode though, as there is a way to tell the
difference between creation and resetting. For reset purposes, the
driver passes either 1 or the reset number as an argument to reset()
in compat mode. Now this is meaningless to you now, but just keep in
mind that you can in fact tell the difference in compat mode. Also
keep in mind that the argment in the creation use of reset is 0 and
the argument in the reset use is a nonzero number.
",({"chapter 16","chapter sixteen","16",}):"chapter 16 \"Armor\"
Building Armours
The Nightmare IV LPC Library
written by Descartes of Borg 950430
Armour has changed quite a bit from the days of armour class. The
Nightmare IV LPC Library now uses damage types, which means armour that
is great against one attack may be pathetic against another. In fact,
in building armour, it is important that you keep in mind weaknesses.
Fortunately, armour is by default absolutely pathetic. If you go
making it awesome, chances are that it will not make it through the
approval process. This document is designed to get you started
building armour as well introduce you to the features available to
make unique and interesting armour.
I. Basic Armour
You should be familiar with /doc/build/Items, as armour is just a
special type of item. It therefore has all of the features of regular
items.
The basic armour looks like this:
#include <lib.h> /* see this everywhere */
#include <armour_types.h> /* a listing of armour types */
#include <damage_types.h> /* a listing of damage types */
inherit LIB_ARMOUR; /* the armour inheritable */
static void create() {
armour::create(); /* call create() in armour.c */
SetKeyName(\"rusty helm\");
SetId( ({ \"helm\", \"rusty helm\", \"a rusty helm\" }) );
SetAdjectives( ({ \"rusty\" }) );
SetShort(\"a rusty helm\");
SetLong(\"A rusty helmet which will be better than nothing on your head.\");
SetMass(75);
SetValue(200);
SetDamagePoints(1000);
SetProtection(BLUNT, 4); /* SetProtection() sets the sort of */
SetProtection(BLADE, 3); /* protection for a given damage type */
SetProtection(KNIFE, 3);
SetArmourType(A_HELMET); /* set what kind of armour this is */
}
As you can see, there is very little that you have to do specific to
armour. The only armour specific call you MUST make is
SetArmourType(). Everything else is fluff.
int SetArmourType(int type)
Armour types are found in /include/armour_types.h. The armour type
basically determines where the armour is worn. Each monster,
depending on its race, has for each limb a list of armour types which
may be worn on that limb. For example, most monsters have heads.
Some have two heads. You do not have to worry about this. They know
that they can wear anything that is A_HELMET on their heads. What if
you have something that may not be wearable on all monsters? Like,
for example, you have body armour which should only go on two armed
beings? See SetRestrictLimbs() later. It allows you to restrict
exactly which kinds of limbs can wear the armour.
int SetProtection(int type, int amount);
Without this call, armour is nothing. Just something you wear. This
allows you to make clothes, which may protect against COLD, but do not
do a thing when struck with a sword. Protection is a number between 0
and 100. Refer to approval documentation for details on what levels
are appropriate, as well as for information on mass and value levels.
That's it for the basics!
II. Advanced Function Calls
The Nightmare IV LPC Library armour object is fairly flexible for
allowing you to do interesting things with your armours. In this
section, you will learn about other function calls you can make to
customize your armour.
string *SetRestrictLimbs(string *limbs);
Example:
SetRestrictLimbs( ({ \"right arm\", \"left arm\", \"torso\" }) );
For armours which can only be on certain body configurations, for
example regular armour (A_ARMOUR) should only be worn on people with
the same number of hands, this function allows you to restrict the
armour to being worn only on the limbs you name. If the person trying
to wear the armour does not have one of those limbs, any attempt to
wear fails.
int SetFingers(int num);
Example:
SetFingers(5);
Used for the glove types. If a person has more fingers on the limb on
which they are trying to wear a glove type than the glove has spaces
for, the wear fails.
mixed SetWear(string | function val);
Examples:
SetWear(\"The cloak feels all yucky on you.\");
SetWear( (: CheckArtrell :) );
Allows you to create a special message seen by the person wearing the
item when they wear it if you pass a string. On the other hand, if
you pass a function, it will call that function to see if the person
can wear the item. The function should be of the form:
int WearFunc();
For example:
int CheckArtrell() {
if( (string)this_player()->GetRace() == \"artrell\" ) {
write(\"The rusty helm makes you feel safe.\");
say((string)this_player()->GetName() + \" wears a rusty helm.\");
return 1;
}
else {
write(\"You cannot wear that you bum!\");
return 1;
}
}
III. Function Overrides
The only function of interest that you might want to override is a
function called eventReceiveDamage(). This function is called every
time the armour is hit to see how much of the damage it absorbs. It
looks like this:
int eventReceiveDamage(int type, int strength, int unused, mixed limbs);
This function is called by combat to determine how much damage the
armour absorbs for a given bit of damage being done. It thus should
return how much damage it takes.
You should always at some point call item::eventReceiveDamage() so
that it can do its processing. You do not want to call it, however,
until you determine how much damage you are absorbing unnaturally.
Here is a sample one for an armour that does extra protection for fighters:
int eventReceiveDamage(int type, int strength, int blah, mixed limbs) {
object who_is_wearing;
int x;
if( !(who_is_wearing = environment()) ) /* eek! no one wearing */
return 0;
if( (int)who_is_wearing->ClassMember(\"fighter\") ) /* reduce strength */
x = strength - random(5);
if( x < 1 ) return strength; /* protect against all the damage */
return armour::eventReceiveDamage(type, x, blah, limbs);
}
Keep in mind what eventReceiveDamage() in armour.c is doing. First,
it is modifying the strength of the blow based on the protections you
set with SetProtection(). Then, it is having the armour take damage
based on how much it absorbed. So you need to call
eventReceiveDamage() in armour at the point where you have a value you
want the armour to do its normal stuff with. In the example above, we
wanted to magically protect fighters against a random(5) points of
damage without having the armour take any damage for that. Then if
there is still strength left in the blow, the armour does its normal
protection.
What else can you do with this? Imagine an armour that turns all cold
damage back on the attacker?
int eventReceiveDamage(int type, int strength, int unused, mixed limbs) {
object who_wearing, enemy;
enemy = (object)(who_wearing = previous_object())->GetCurrentEnemy();
if( !enemy || !(type & COLD) )
return armour::eventReceiveDamage(type, strength, unused, limbs);
limbs = enemy->GetTargetLimb(0);
message(\"environment\", \"Your anti-cold throws the frost in your \"
\"enemy's face!\", who_wearing);
message(\"environment\", \"Your cold attack is turned back upon you!\",
enemy);
enemy->eventReceiveDamage(COLD, strength, unused, limbs);
return strength; /* we absorb all of the strength but take no damage */
}
Descartes of Borg
borg@imaginary.com
",({"chapter 12","chapter twelve","12",}):"chapter 12 \"The LPC Pre-Compiler\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 4: The LPC Pre-Compiler
4.1 Review
The previous chapter was quite heavy, so now I will slow down a bit so
you can digest and play with mappings and arrays by taking on the
rather simple topic of the LPC pre-compiler. By this point, however,
you should well understand how the driver interacts with the mudlib and
be able to code objects which use call outs and heart beats. In addition,
you should be coding simple objects which use mappings and arrays,
noting how these data types perform in objects. It is also a good idea to
start looking in detail at the actual mudlib code that makes up your mud.
See if you understand everything which is going on in your mudlibs
room and monster codes. For things you do not understand, ask the
people on your mud designated to answer creator coding questions.
Pre-compiler is actually a bit of a misnomer since LPC code is never
truly compiled. Although this is changing with prototypes of newer
LPC drivers, LPC drivers interpret the LPC code written by creators
rather than compile it into binary format. Nevertheless, the LPC pre-
compiler functions still perform much like pre-compilers for compiled
languages in that pre-compiler directives are interpreted before the driver
even starts to look at object code.
4.2 Pre-compiler Directives
If you do not know what a pre-compiler is, you really do not need to
worry. With respect to LPC, it is basically a process which happens
before the driver begins to interpret LPC code which allows you to
perform actions upon the entire code found in your file. Since the code
is not yet interpreted, the pre-compiler process is involved before the file
exists as an object and before any LPC functions or instructions are ever
examined. The pre-compiler is thus working at the file level, meaning
that it does not deal with any code in inherited files.
The pre-compiler searches a file sent to it for pre-compiler directives.
These are little instructions in the file meant only for the pre-compiler
and are not really part of the LPC language. A pre-compiler directive is
any line in a file beginning with a pound (#) sign. Pre-compiler
directives are generally used to construct what the final code of a file will
look at. The most common pre-compiler directives are:
#define
#undefine
#include
#ifdef
#ifndef
#if
#elseif
#else
#endif
#pragma
Most realm coders on muds use exclusively the directives #define and
#include. The other directives you may see often and should understand
what they mean even if you never use them.
The first pair of directives are:
#define
#undefine
The #define directive sets up a set of characters which will be replaced
any where they exist in the code at precompiler time with their definition.
For example, take:
#define OB_USER \"/std/user\"
This directive has the pre-compiler search the entire file for instances of
OB_USER. Everywhere it sees OB_USER, it replaces with \"/std/user\".
Note that it does not make OB_USER a variable in the code. The LPC
interpreter never sees the OB_USER label. As stated above, the pre-
compiler is a process which takes place before code interpretation. So
what you wrote as:
#define OB_USER \"/std/user\"
void create() {
if(!file_exists(OB_USER+\".c\")) write(\"Merde! No user file!\");
else write(\"Good! User file still exists!\");
}
would arrive at the LPC interpreter as:
void create() {
if(!file_exists(\"/std/user\"+\".c\")) write(\"Merde! No user file!\");
else write(\"Good! User file still exists!\");
}
Simply put, #define just literally replaces the defined label with whatever
follows it. You may also use #define in a special instance where no
value follows. This is called a binary definition. For example:
#define __NIGHTMARE
exists in the config file for the Nightmare Mudlib. This allows for pre-
compiler tests which will be described later in the chapter.
The other pre-compiler directive you are likely to use often is #include.
As the name implies, #include includes the contents of another file right
into the file being pre-compiled at the point in the file where the directive
is placed. Files made for inclusion into other files are often called header
files. They sometimes contain things like #define directives used by
multiple files and function declarations for the file. The traditional file
extension to header files is .h.
Include directives follow one of 2 syntax's:
#include <filename>
#include \"filename\"
If you give the absolute name of the file, then which syntax you use is
irrelevant. How you enclose the file name determines how the pre-
compiler searches for the header files. The pre-compiler first searches in
system include directories for files enclosed in <>. For files enclosed in
\"\", the pre-compiler begins its search in the same directory as the file
going through the pre-compiler. Either way, the pre-compiler will
search the system include directories and the directory of the file for the
header file before giving up. The syntax simply determines the order.
The simplest pre-compiler directive is the #pragma directive. It is
doubtful you will ever use this one. Basically, you follow the directive
with some keyword which is meaningful to your driver. The only
keyword I have ever seen is strict_types, which simply lets the driver
know you want this file interpreted with strict data typing. I doubt you
will ever need to use this, and you may never even see it. I just included
it in the list in the event you do see it so you do not think it is doing
anything truly meaningful.
The final group of pre-compiler directives are the conditional pre-
compiler directives. They allow you to pre-compile the file one way
given the truth value of an expression, otherwise pre-compile the file
another way. This is mostly useful for making code portable among
mudlibs, since putting the m_delete() efun in code on a MudOS mud
would normally cause an error, for example. So you might write the
following:
#ifdef MUDOS
map_delete(map, key);
#else
map = m_delete(map, key);
#endif
which after being passed through the pre-compiler will appear to the
interpreter as:
map_delete(map, key);
on a MudOS mud, and:
map = m_delete(map, key);
on other muds. The interpreter never sees the function call that would
cause it to spam out in error.
Notice that my example made use of a binary definition as described
above. Binary definitions allow you to pass certain code to the
interpreter based on what driver or mudlib you are using, among other
conditions.
4.3 Summary
The pre-compiler is a useful LPC tool for maintaining modularity among
your programs. When you have values that might be subject to change,
but are used widely throughout your files, you might stick all of those
values in a header file as #define statements so that any need to make a
future change will cause you to need to change just the #define directive.
A very good example of where this would be useful would be a header
file called money.h which includes the directive:
#define HARD_CURRENCIES ({ \"gold\", \"platinum\", \"silver\",
\"electrum\", \"copper\" })
so that if ever you wanted to add a new hard currency, you only need
change this directive in order to update all files needing to know what the
hard currencies are.
The LPC pre-compiler also allows you to write code which can be
ported without change among different mudlibs and drivers. Finally,
you should be aware that the pre-compiler only accepts lines ending in
carriage returns. If you want a multiple line pre-compiler directive, you
need to end each incomplete line with a backslash(\\).
Copyright (c) George Reese 1993
",({"chapter 40","chapter forty","40",}):"chapter 40 \"Useful Creator Commands\"
Moving around:
-------------
* To go to your workroom:
%^GREEN%^home%^RESET%^
* To go to someone else's workroom, \"home <person>\", for example:
%^GREEN%^home cratylus%^RESET%^
* To go to the town pub:
%^GREEN%^goto /domains/town/room/tavern%^RESET%^
* Or:
%^GREEN%^cd /domains/town/room%^RESET%^
%^GREEN%^goto tavern%^RESET%^
* To return to where you were before you went somewhere else:
%^GREEN%^return%^RESET%^
* To bring someone to you, \"trans <person>\". For example:
%^GREEN%^trans cratylus%^RESET%^
* To send them back when you're done with them:
%^GREEN%^return cratylus%^RESET%^
Dealing with living beings:
---------------------------
* To force everyone in a room to stop fighting:
%^GREEN%^quell%^RESET%^
* To let them resume combat:
%^GREEN%^unquell%^RESET%^
* To insta-kill a living being, \"zap <thing>\". For example:
%^GREEN%^zap orc%^RESET%^
* To bring to life a player who somehow left the death
room without regenerating, \"resurrect <person>\", For example:
%^GREEN%^resurrect cratylus%^RESET%^
* To make a living being do something you want, \"force <thing>
<command>\". For example:
%^GREEN%^force thief drop towel%^RESET%^
%^GREEN%^force thief go west%^RESET%^
* For complex management of a living being's vital statistics,
skills, health, score, and satiety levels, use the medical
tricorder in the chest in your workroom.
* For a detailed report of a living being's physical status:
%^GREEN%^stat orc%^RESET%^
%^GREEN%^stat cratylus%^RESET%^
Handling objects in general:
---------------------------
* To destroy an object, \"dest <thing>\". Note that the object's
inventory will probably move to the room it was occupying.
For example, if you:
%^GREEN%^dest fighter%^RESET%^
You may find that the room now contains a sword, shield, and
chainmail shirt, but no fighter.
* To reset an object to its original state, \"reload <thing>\". Note
that this also makes the object incorporate any changes you
made to its file. For example:
%^GREEN%^reload fighter%^RESET%^
%^GREEN%^reload here%^RESET%^
* To load a file into memory, \"update file\". This is used when you
have edited an object's file, and want the mud to use the new
stuff you created. For example, if you edited the fighter's file
and wanted to know if it will load properly into memory, you'd
type:
%^GREEN%^update /realms/you/area/npc/fighter.c%^RESET%^
Or:
%^GREEN%^cd /realms/you/area/npc/%^RESET%^
%^GREEN%^update fighter%^RESET%^
If you do not specify an object to update, the mud assumes you
want to update the room you are in. If there is a problem with the
room's code, and it does not load, you will be dropped into the
\"void\".
If the room's code is ok and it updates, anything in the room
that isn't part of its permanent inventory (except for players) will
disappear from the room.
* To make a copy of an object appear, \"clone <file>\". For example:
%^GREEN%^clone /realms/you/area/npc/fighter%^RESET%^
Or:
%^GREEN%^cd /realms/you/area/npc/%^RESET%^
%^GREEN%^clone fighter%^RESET%^
* To know the precise contents of an object, use scan:
%^GREEN%^scan fighter%^RESET%^
%^GREEN%^scan here%^RESET%^
If you want to know not only what the fighter has, but
also what any containers he is carrying have, use the \"-d\" flag:
%^GREEN%^scan -d fighter%^RESET%^
%^GREEN%^scan -d here%^RESET%^
Debugging commands:
--------------------
* elog: This will report back to you the last few lines of
your error log. Usually this is very helpful in nailing down
which lines of a file contain errors. If you are admin, you
may be working on files other than your home dir. If those
files fail to update, you can supply elog with a directory name
to specify where to look for an error report:
%^GREEN%^elog secure%^RESET%^
%^GREEN%^elog cmds%^RESET%^
%^GREEN%^elog lib%^RESET%^
* dbxwhere: provides a list of the chain of messages caught
in your last runtime error.
* dbxframe <number>: Using the list number from dbxwhere,
dbxframe can pinpoint exactly where in that link the error
came from.
%^GREEN%^tail /log/runtime%^RESET%^
%^GREEN%^tail /log/catch%^RESET%^
%^GREEN%^tail /log/player_errors%^RESET%^
miscellaneous useful commands:
-----------------------------
* %^GREEN%^people%^RESET%^: reports who is logged on, what site they logged
in from, and what room they are in.
* %^GREEN%^mudtime%^RESET%^: reports the time of day in the mud (nothing to
do with the time of day anywhere in the real world).
* %^GREEN%^bk <thing or file>%^RESET%^: makes a unique copy of that thing
or file and puts it in /realms/you/bak
*%^GREEN%^ restore <filename>%^RESET%^: copies the last backup of the
filename from your bak/ directory into where it used
to be.
",({"chapter 14","chapter fourteen","14",}):"chapter 14 \"Intermediate Inheritance\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 6: Intermediate Inheritance
6.1 Basics of Inheritance
In the textbook LPC Basics, you learned how it is the mudlib maintains
consistency amoung mud objects through inheritance. Inheritance
allows the mud administrators to code the basic functions and such that
all mudlib objects, or all mudlib objects of a certain type must have so
that you can concentrate on creating the functions which make these
objects different. When you build a room, or a weapon, or a monster,
you are taking a set of functions already written for you and inheriting
them into your object. In this way, all objects on the mud can count on
other objects to behave in a certain manner. For instance, player objects
can rely on the fact that all room objects will have a function in them
called GetLong() which describes the room. Inheritance thus keeps
you from having to worry about what the function GetLong() should
look like.
Naturally, this textbook tries to go beyond this fundamental knowledge
of inheritance to give the coder a better undertstanding of how
inheritance works in LPC programming. Without getting into detail that
the advanced domain coder/beginner mudlib coder simply does not yet
need, this chapter will try to explain exactly what happens when you
inherit an object.
6.2 Cloning and Inheritance
Whenever a file is referenced for the first time as an object (as opposed
to reading the contents of the file), the game tries to load the file into
memory and create an object. If the object is successfully loaded into
memory, it becomes as master copy. Master copies of objects may be
cloned but not used as actual game objects. The master copy is used to
support any clone objects in the game.
The master copy is the source of one of the controversies of mud LPC
coding, that is whether to clone or inherit. With rooms, there is no
question of what you wish to do, since there should only be one instance
of each room object in the game. So you generally use inheritance in
creating rooms. Many mud administrators, including myself, however
encourage creators to clone the standard monster object and configure it
from inside room objects instead of keeping monsters in separate files
which inherit the standard monster object.
As I stated above, each time a file is referenced to create an object, a
master copy is loaded into memory. When you do something like:
void reset() {
object ob;
ob = new(\"/std/monster\");
/* clone_object(\"/std/monster\") some places */
ob->SetKeyName(\"foo monster\");
... rest of monster config code followed by moving
it to the room ...
}
the driver searches to see if their is a master object called \"/std/monster\".
If not, it creates one. If it does exist, or after it has been created, the
driver then creates a clone object called \"/std/monster#<number>\". If
this is the first time \"/std/monster\" is being referenced, in effect, two
objects are being created: the master object and the cloned instance.
On the other hand, let's say you did all your configuring in the create()
of a special monster file which inherits \"/std/monster\". Instead of
cloning the standard monster object from your room, you clone your
monster file. If the standard monster has not been loaded, it gets loaded
since your monster inherits it. In addition, a master copy of your file
gets loaded into memory. Finally, a clone of your monster is created
and moved into the room, for a total of three objects added to the game.
Note that you cannot make use of the master copy easily to get around
this. If, for example, you were to do:
\"/realms/descartes/my_monster\"->eventMove(this_object());
instead of
new(\"/realms/descartes/my_monster\")->eventMove(this_object());
you would not be able to modify the file \"my_monster.c\" and update it,
since the update command destroys the current master version of an
object. On some mudlibs it also loads the new version into memory.
Imagine the look on a player's face when their monster disappears in
mid-combat cause you updated the file!
Cloning is therefore a useful too when you plan on doing just that-
cloning. If you are doing nothing special to a monster which cannot be
done through a few call others, then you will save the mud from getting
loaded with useless master copies. Inheritance, however, is useful if
you plan to add functionality to an object (write your own functions) or
if you have a single configuration that gets used over and over again
(you have an army of orc guards all the same, so you write a special orc
file and clone it).
6.3 Inside Inheritance
When objects A and B inherit object C, all three objects have their own
set of data sharing one set of function definitions from object C. In
addition, A and B will have separate functions definitions which were
entered separately into their code. For the sake of example throughout
the rest of the chapter, we will use the following code. Do not be
disturbed if, at this point, some of the code makes no sense:
STD_ITEM C
private string name, cap_name, short, long;
private int setup;
void SetKeyName(string str)
nomask string GetKeyName();
private int query_setup();
static void unsetup();
void SetShort(string str);
string GetShort();
void SetLong(string str);
string GetLong();
void SetKeyName(string str) {
if(!query_setup()) {
name = str;
setup = 1;
}
nomask string GetKeyName() { return name; }
private query_setup() { return setup; }
static void unsetup() { setup = 0; }
string GetName() {
return (name ? capitalize(name) : \"\"); }
}
void SetShort(string str) { short = str; }
string GetShort() { return short; }
void SetLong(string str) { long = str; }
string GetLong() { return str; }
void create() { seteuid(getuid()); }
STD_ITEM B
inherit \"/std/objectc\";
private int wc;
void set_wc(int wc);
int query_wc();
int wieldweapon(string str);
void create() { ::create(); }
void init() {
if(environment(this_object()) == this_player())
add_action(\"wieldweapon\", \"wield\");
}
void set_wc(int x) { wc = x; }
int query_wc() { return wc; }
int wieldweapon(string str) {
... code for wielding the weapon ...
}
STD_ITEM A
inherit \"/std/objectc\";
int ghost;
void create() { ::create(); }
void change_name(string str) {
if(!((int)this_object()->is_player())) unsetup();
SetKeyName(str);
}
string GetName() {
if(ghost) return \"A ghost\";
else return ::GetName();
}
As you can see, object C is inherited both by object A and object B.
Object C is a representation of a much oversimplified base object, with B
being an equally oversimplified weapon and A being an equally
simplified living object. Only one copy of each function is retained in
memory, even though we have here three objects using the functions.
There are of course, three instances of the variables from Object C in
memory, with one instance of the variables of Object A and Object B in
memory. Each object thus gets its own data.
6.4 Function and Variable Labels
Notice that many of the functions above are proceeded with labels which
have not yet appeared in either this text or the beginner text, the labels
static, private, and nomask. These labels define special priveledges
which an object may have to its data and member functions. Functions
you have used up to this point have the default label public. This is
default to such a degree, some drivers do not support the labeling.
A public variable is available to any object down the inheritance tree
from the object in which the variable is declared. Public variables in
object C may be accessed by both objects A and B. Similarly, public
functions may be called by any object down the inheritance tree from the
object in which they are declared.
The opposite of public is of course private. A private variable or
function may only be referenced from inside the object which declares it.
If object A or B tried to make any reference to any of the variables in
object C, an error would result, since the variables are said to be out of
scope, or not available to inheriting classes due to their private labels.
Functions, however, provide a unique challenge which variables do not.
External objects in LPC have the ability to call functions in other objects
through call others. The private label does not protect against call
others.
To protect against call others, functions use the label static. A function
which is static may only be called from inside the complete object or
from the game driver. By complete object, I mean object A can call
static functions in the object C it inherits. The static only protects against
external call others. In addition, this_object()->foo() is considered an
internal call as far as the static label goes.
Since variables cannot be referenced externally, there is no need for an
equivalent label for them. Somewhere along the line, someone decided
to muddy up the waters and use the static label with variables to have a
completely separate meaning. What is even more maddening is that this
label has nothing to do with what it means in the C programming
language. A static variable is simply a variable that does not get saved to
file through the efun save_object() and does not get restored through
restore_object(). Go figure.
In general, it is good practice to have private variables with public
functions, using query_*() functions to access the values of inherited
variables, and set_*(), add_*(), and other such functions to change
those values. In realm coding this is not something one really has to
worry a lot about. As a matter of fact, in realm coding you do not have
to know much of anything which is in this chapter. To be come a really
good realm coder, however, you have to be able to read the mudlib
code. And mudlib code is full of these labels. So you should work
around with these labels until you can read code and understand why it
is written that way and what it means to objects which inherit the code.
The final label is nomask, and it deals with a property of inheritance
which allows you to rewrite functions which have already been defined.
For example, you can see above that object A rewrote the function
GetName(). A rewrite of function is called overriding the
function. The most common override of a function would be in a case
like this, where a condition peculiar to our object (object A) needs to
happen on a call ot the function under certain circumstances. Putting test
code into object C just so object A can be a ghost is plain silly. So
instead, we override GetName() in object A, testing to see if the
object is a ghost. If so, we change what happens when another object
queries for the cap name. If it is not a ghost, then we want the regular
object behaviour to happen. We therefore use the scope resolution
operator (::) to call the inherited version of the GetName()
function and return its value.
A nomask function is one which cannot be overridden either through
inheritance or through shadowing. Shadowing is a sort of backwards
inheritance which will be detailed in the advanced LPC textbook. In the
example above, neither object A nor object B (nor any other object for
that matter) can override GetKeyName(). Since we want to use
GetKeyName() as a unique identifier of objects, we don't want people
faking us through shadowing or inheritance. The function therefore gets
the nomask label.
6.5 Summary
Through inheritance, a coder may make user of functions defined in
other objects in order to reduce the tedium of producing masses of
similar objects and to increase the consistency of object behaviour across
mudlib objects. LPC inheritance allows objects maximum priveledges in
defining how their data can be accessed by external objects as well as
objects inheriting them. This data security is maintained through the
keywords, nomask, private, and static.
In addition, a coder is able to change the functionality of non-protected
functions by overriding them. Even in the process of overriding a
function, however, an object may access the original function through
the scope resolution operator.
Copyright (c) George Reese 1993
",({"chapter 36","chapter thirty-six","36",}):"chapter 36 \"QCS: Modifying weapons\"
Remember that the QCS chapters are supposed to be read in
sequence. This is important because as we progress, I will not make
explanations about directives and concepts explained previously.
Weapons are different from rooms and NPC's in that they can
be handled, sold, thrown, etc. They are manipulable objects. As such,
we will see new directives:
%^GREEN%^create weapon hammer%^RESET%^
You may be familiar with this example from the example webpage.
Let's go ahead and plow through the commands:
%^GREEN%^modify weapon id hammer%^RESET%^
%^GREEN%^warhammer%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer name hammer%^RESET%^
%^GREEN%^modify hammer damagetype blunt%^RESET%^
%^GREEN%^modify hammer weapontype blunt%^RESET%^
%^GREEN%^modify hammer mass 700%^RESET%^
%^GREEN%^modify hammer hands 2%^RESET%^
%^GREEN%^modify hammer short a heavy war hammer%^RESET%^
%^GREEN%^modify hammer long This is an extremely large and heavy hammer designed to be wielded in both hands and used to hurt people very badly indeed.%^RESET%^
%^GREEN%^modify hammer adj%^RESET%^
%^GREEN%^large%^RESET%^
%^GREEN%^heavy%^RESET%^
%^GREEN%^war%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer basecost silver 750%^RESET%^
%^GREEN%^about hammer%^RESET%^
Like a room and unlike an NPC, you can also modify the SetItems on
manipulable objects like weapons, so you could do something like this:
%^GREEN%^modify hammer item%^RESET%^
%^GREEN%^shaft%^RESET%^
%^GREEN%^handle%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A thick, reinforced steel shaft with leather bands for a strong grip.%^RESET%^
%^GREEN%^exa shaft on hammer%^RESET%^
",({"chapter 15","chapter fifteen","15",}):"chapter 15 \"Debugging\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 7: Debugging
7.1 Types of Errors
By now, you have likely run into errors here, there, and everywhere. In
general, there are three sorts of errors you might see: compile time
errors, run time errors, and malfunctioning code. On most muds you
will find a personal file where your compile time errors are logged. For
the most part, this file can be found either in your home directory as the
file named \"log\" or \".log\", or somewhere in the directory \"/log\" as a file
with your name.. In addition, muds tend to keep a log of run time errors
which occur while the mud is up. Again, this is generally found in
\"/log\". On MudOS muds it is called \"debug.log\". On other muds it may
be called something different like \"lpmud.log\". Ask your administrators
where compile time and run time errors are each logged if you do not
already know.
Compile time errors are errors which occur when the driver tries to load
an object into memory. If, when the driver is trying to load an object
into memory, it encounters things which it simply does not understand
with respect to what you wrote, it will fail to load it into memory and log
why it could not load the object into your personal error log. The most
common compile time errors are typos, missing or extra (), {}. [], or \"\",
and failure to declare properly functions and variables used by the
object.
Run time errors occur when something wrong happens to an object in
memory while it is executing a statement. For example, the driver
cannot tell whether the statement \"x/y\" will be valid in all circumstances.
In fact, it is a valid LPC expression. Yet, if the value of y is 0, then a
run time error will occur since you cannot divide by 0. When the driver
runs across an error during the execution of a function, it aborts
execution of the function and logs an error to the game's run time error
log. It will also show the error to this_player(), if defined, if the player
is a creator, or it will show \"What?\" to players. Most common causes
for run time errors are bad values and trying to perform operations with
data types for which those operations are not defined.
The most insideous type of error, however, is plain malfunctioning
code. These errors do not log, since the driver never really realizes that
anything is wrong. In short, this error happens when you think the code
says one thing, but in fact it says another thing. People too often
encounter this bug and automatically insist that it must be a mudlib or
driver bug. Everyone makes all types of errors though, and more often
than not when code is not functioning the way you should, it will be
because you misread it.
7.2 Debugging Compile Time Errors
Compile time errors are certainly the most common and simplest bugs to
debug. New coders often get frustrated by them due to the obscure
nature of some error messages. Nevertheless, once a person becomes
used to the error messages generated by their driver, debugging compile
time errors becomes utterly routine.
In your error log, the driver will tell you the type of error and on which
line it finally noticed there was an error. Note that this is not on which
line the actual error necessarily exists. The most common compile time
error, besides the typo, is the missing or superfluous parentheses,
brackets, braces, or quotes. Yet this error is the one that most baffles
new coders, since the driver will not notice the missing or extra piece
until well after the original. Take for example the following code:
1 int test(string str) {
2 int x;
3 for(x =0; x<10; x++)
4 write(x+\"\\n\");
5 }
6 write(\"Done.\\n\");
7 }
Depending on what you intended, the actual error here is either at line 3
(meaning you are missing a {) or at line 5 (meaing you have an extra }).
Nevertheless, the driver will report that it found an error when it gets to
line 6. The actual driver message may vary from driver to driver, but no
matter which driver, you will see an error on line 6, since the } in line 5
is interpreted as ending the function test(). At line 6, the driver sees that
you have a write() sitting outside any function definition, and thus
reports an error. Generally, the driver will also go on to report that it
found an error at line 7 in the form of an extra }.
The secret to debugging these is coding style. Having closing } match
up vertically with the clauses they close out helps you see where you are
missing them when you are debugging code. Similarly, when using
multiple sets of parentheses, space out different groups like this:
if( (x=sizeof(who=users()) > ( (y+z)/(a-b) + (-(random(7))) ) )
As you can see, the parentheses for the for() statement, are spaced out
from the rest of the statement. In addition, individual sub-groups are
spaced so they can easily be sorted out in the event of an error.
Once you have a coding style which aids in picking these out, you learn
which error messages tend to indicate this sort of error. When
debugging this sort of error, you then view a section of code before and
after the line in question. In most all cases, you will catch the bug right
off.
Another common compile time error is where the driver reports an
unknown identifier. Generally, typos and failure to declare variables
causes this sort of error. Fortunately, the error log will almost always
tell you exactly where the error is. So when debugging it, enter the
editor and find the line in question. If the problem is with a variable and
is not a typo, make sure you declared it properly. On the other hand, if
it is a typo, simply fix it!
One thing to beware of, however, is that this error will sometimes be
reported in conjunction with a missing parentheses, brackets, or braces
type error. In these situations, your problem with an unknown identifier
is often bogus. The driver misreads the way the {} or whatever are
setup, and thus gets variable declarations confused. Therefore make
sure all other compile time errors are corrected before bothering with
these types of errors.
In the same class with the above error, is the general syntax error. The
driver generates this error when it simply fails to understand what you
said. Again, this is often caused by typos, but can also be caused by not
properly understanding the syntax of a certain feature like writing a for()
statement: for(x=0, x<10, x++). If you get an error like this which is
not a syntax error, try reviewing the syntax of the statement in which the
error is occurring.
7.3 Debugging Run Time Errors
Run time errors are much more complex than their compile time
counterparts. Fortunately these errors do get logged, though many
creators do not realise or they do not know where to look. The error log
for run time errors are also generally much more detailed than compile
time errors, meaning that you can trace the history of the execution train
from where it started to where it went wrong. You therefore can setup
debugging traps using precompiler statements much easier using these
logs. Run time errors, however, tend to result from using more
complex codign techniques than beginners tend to use, which means you
are left with errors which are generally more complicated than simple
compile time errors.
Run time errors almost always result from misusing LPC data types.
Most commonly, trying to do call others using object variables which are
NULL, indexing on mapping, array, or string variables which are
NULL, or passing bad arguments to functions. We will look at a real
run time error log from Nightmare:
Bad argument 1 to explode()
program: bin/system/_grep.c, object: bin/system/_grep
line 32
' cmd_hook' in ' std/living.c' ('
std/user#4002')line 83
' cmd_grep' in ' bin/system/_grep.c' ('
bin/system/_grep')line 32
Bad argument 2 to message()
program: adm/obj/simul_efun.c, object: adm/obj/simul_efun
line 34
' cmd_hook' in ' std/living.c' ('
std/user#4957')line 83
' cmd_look' in ' bin/mortal/_look.c' ('
bin/mortal/_look')line 23
' examine_object' in ' bin/mortal/_look.c' ('
bin/mortal/_look')line 78
' write' in 'adm/obj/simul_efun.c' ('
adm/obj/simul_efun')line 34
Bad argument 1 to call_other()
program: bin/system/_clone.c, object: bin/system/_clone
line 25
' cmd_hook' in ' std/living.c' ('
std/user#3734')line 83
' cmd_clone' in ' bin/system/_clone.c' ('
bin/system/_clone')line 25
Illegal index
program: std/monster.c, object:
wizards/zaknaifen/spy#7205 line 76
' heart_beat' in ' std/monster.c'
('wizards/zaknaifen/spy#7205')line
76
All of the errors, except the last one, involve passing a bad argument to a
function. The first bug, involves passing a bad first arument to the efun
explode(). This efun expects a string as its first argment. In debugging
these kinds of errors, we would therefore go to line 32 in
/bin/system/_grep.c and check to see what the data type of the first
argument being passed in fact is. In this particular case, the value being
passed should be a string.
If for some reason I has actually passed something else, I would be done
debugging at that point and fix it simply by making sure that I was
passing a string. This situation is more complex. I now need to trace
the actual values contained by the variable being passed to explode, so
that I can see what it is the explode() efun sees that it is being passed.
The line is question is this:
borg[files[i]] = regexp(explode(read_file(files[i]), \"\\n\"), exp);
where files is an array for strings, i is an integer, and borg is a mapping.
So clearly we need to find out what the value of read_file(files[i]) is.
Well, this efun returns a string unless the file in question does not exist,
the object in question does not have read access to the file in question, or
the file in question is an empty file, in which cases the function will
return NULL. Clearly, our problem is that one of these events must
have happened. In order to see which, we need to look at files[i].
Examining the code, the files array gets its value through the get_dir()
efun. This returns all the files in a directory if the object has read access
to the directory. Therefore the problem is neither lack of access or non-
existent files. The file which caused this error then must have been an
empty file. And, in fact, that is exactly what caused this error. To
debug that, we would pass files through the filter() efun and make
sure that only files with a file size greater than 0 were allowed into the
array.
The key to debugging a run time error is therefore knowing exactly what
the values of all variables in question are at the exact moment where the
bug created. When reading your run time log, be careful to separate the
object from the file in which the bug occurred. For example, the
indexing error above came about in the object /wizards/zaknaifen/spy,
but the error occured while running a function in /std/monster.c, which
the object inherited.
7.4 Malfunctioning Code
The nastiest problem to deal with is when your code does not behave the
way you intended it to behave. The object loads fine, and it produces no
run time errors, but things simply do not happen the way they should.
Since the driver does not see a problem with this type of code, no logs
are produced. You therefore need to go through the code line by line
and figure out what is happening.
Step 1: Locate the last line of code you knew successfully executed
Step 2: Locate the first line of code where you know things are going
wrong
Step 3: Examine the flow of the code from the known successful point to
the first known unsuccessful point.
More often than not, these problems occurr when you are using if()
statements and not accounting for all possibilities. For example:
int cmd(string tmp) {
if(stringp(tmp)) return do_a()
else if(intp(tmp)) return do_b()
return 1;
}
In this code, we find that it compiles and runs fine. Problem is nothing
happens when it is executed. We know for sure that the cmd() function
is getting executed, so we can start there. We also know that a value of
1 is in fact being returned, since we do not see \"What?\" when we enter
the command. Immediately, we can see that for some reason the
variable tmp has a value other than string or int. As it turns out, we
issued the command without parameters, so tmp was NULL and failed
all tests.
The above example is rather simplistic, bordering on silly.
Nevertheless, it gives you an idea of how to examine the flow of the
code when debugging malfunctioning code. Other tools are available as
well to help in debugging code. The most important tool is the use of
the precompiler to debug code. With the code above, we have a clause
checking for integers being passed to cmd(). When we type \"cmd 10\",
we are expecting do_b() to execute. We need to see what the value of
tmp is before we get into the loop:
#define DEBUG
int cmd(string tmp) {
#ifdef DEBUG
write(tmp);
#endif
if(stringp(tmp)) return do_a();
else if(intp(tmp)) return do_b();
else return 1;
}
We find out immediately upon issuing the command, that tmp has a
value of \"10\". Looking back at the code, we slap ourselves silly,
forgetting that we have to change command arguments to integers using
sscanf() before evaluating them as integers.
7.5 Summary
The key to debugging any LPC problem is always being aware of what
the values of your variables are at any given step in your code. LPC
execution reduces on the simplest level to changes in variable values, so
bad values are what causes bad things to happen once code has been
loaded into memory. If you get errors about bad arguments to
functions, more likely than not you are passing a NULL value to a
function for that argument. This happens most often with objects, since
people will do one of the following:
1) use a value that was set to an object that has since destructed
2) use the return value of this_player() when there is no this_player()
3) use the return value of this_object() just after this_object() was
destructed
In addition, people will often run into errors involving illegal indexing or
indexing on illegal types. Most often, this is because the mapping or
array in question was not initialized, and therefore cannot be indexed.
The key is to know exactly what the full value of the array or mapping
should be at the point in question. In addition, watch for using index
numbers larger than the size of given arrays
Finally, make use of the precompiler to temporarly throw out code, or
introduce code which will show you the values of variables. The
precompiler makes it easy to get rid of debugging code quickly once you
are done. You can simply remove the DEBUG define when you are
done.
Copyright (c) George Reese 1993
",({"chapter 34","chapter thirty-four","34",}):"chapter 34 \"QCS: Modification of NPC's\"
In the previous chapter we learned how to make a generic object.
Now that we have it, what to do with it?
It's important to keep in mind that the generic thing now in
front of you isn't just a clone from a template file. If the command
you used was \"create npc cowboy\", there is now a file (probably)
called /realms/you/area/npc/cowboy.c that contains the code for the
creature in front of you.
However, this poor beast has the most uninteresting of features.
It is in fact so boring that it responds only to its generic type.
Such that \"examine cowboy\" or \"kill tex\" won't work. You'll need to
\"look at npc\".
Accordingly, any modification commands need to be made referring
to the new thing with an it responds to, until such a time as you
change the id to suit your tastes. Let's carry on with the example
of our generic npc. To make a cowboy out of him, we can either
change his name, or his id, or both. Let's start with his name:
%^GREEN%^modify npc name cowboy%^RESET%^
This makes the SetKeyName() directive in cowboy.c use \"cowboy\"
as its argument, effectively allowing you to address this npc as \"cowboy\"
from now on. Now you can \"look at cowboy\" with some results.
Obviously our NPC isn't *just* a cowboy. He's also a human, a dude,
and his name is Tex. How do we make him respond to all of these nouns?
%^GREEN%^modify cowboy id%^RESET%^
You'll notice there are no arguments following the word \"id\". Setting
a thing's id is different from most other settings. If you'll think back
to the LPC datatypes chapter of this manual (you did read the LPC
chapters, didn't you?) you'll remember that some information about
objects is in the form of strings (\"cowboy\"), some is in the form of
integers (the cowboy's health points, for example) and some is in the
form of arrays, which are a group of data points. In this example we
want the cowboy's id to be an array, because of the many ways we might
want to address him. Therefore, we want the SetId directive in his
file to look like this:
SetId( ({\"human\", \"dude\", \"tex\" }) );
You might think that QCS should be able to accept multiple values
like this on a line, perhaps with \"modify cowboy id human dude tex\" as
the command.
But what if you want this npc to be a \"Boy Named Sue\"? How would
you accommodate id's that contain spaces? In designing QCS, I considered
having escape characters to allow for such things, but ultimately
reasoned that this was just way too much mess. Instead, SetId, and
other directives that take arrays as arguments, are handled by entering
a query session. Below is an example of what it might look like. The
example is necessarily messy because I am including both the queries
and the responses.
---------------------------------------------------
> %^GREEN%^modify npc name cowboy%^RESET%^
Indenting file...
\"/tmp/indent.1136206130.tmp.dat\" 15 lines 420 bytes
Exit from ed.
> %^GREEN%^modify cowboy id%^RESET%^
This setting takes multiple values. If you have no more values to enter, then
enter a dot on a blank line. To cancel, enter a single q on a blank line.
You may now enter the next value. So far, it is blank.
If you're done entering values, enter a dot on a blank line.
%^GREEN%^dude%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^human%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^tex%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^boy named sue%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\",
\"boy named sue\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^.%^RESET%^
Entries complete. Final array is: ({ \"dude\", \"human\", \"tex\", \"boy named sue\" })
Indenting file...
\"/tmp/indent.1136206156.tmp.dat\" 19 lines 459 bytes
Exit from ed.
/open/1136206138: Ok
/realms/cratylus/area/npc/cowboy: Ok
SetId modification complete.
> %^GREEN%^exa tex%^RESET%^
Other than being human, this npc is entirely unremarkable.
The male human is in top condition.
---------------------------------------------------
If you were now to examine Tex's code (with the command \"about tex\")
you'd see that his SetId directive now looks like this:
SetId( ({\"dude\", \"human\", \"tex\", \"boy named sue\"}) );
Other NPC features take arrays also. SetAdjectives is one. You
might enter this (mud output omitted for clarity):
%^GREEN%^modify tex adjectives%^RESET%^
%^GREEN%^dusty%^RESET%^
%^GREEN%^hardy%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^look at dusty cowboy%^RESET%^
There are two other directives that require queries. Things and
NPC's can be looked at, but they can also be smelled and listened
to, if you add SetSmell and SetListen. The syntax is:
%^GREEN%^modify tex smell%^RESET%^
And you will then be asked a question about keys and mappings.
Understanding mappings is important, but for now you just need to
understand that you are being asked *two* separate questions:
1) What on the cowboy is being smelled/listened to?
2) What is the smell/sound?
What this means is that your input will look something like
this:
%^GREEN%^modify tex smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex smells of sweat and manure.
What happens is this:
- You enter the modify command.
- You enter the word \"default\" to indicate this is Tex's general smell.
- You enter a dot to indicate that you are done specifying what
part of Tex is being smelled.
- You then specify the smell.
This may seem odd until you realize you can also add smells/listens to
parts of things. Not on NPC's, though. We'll look at this more closely in
later chapters. For now, just use the syntax as shown above. For adding
a listen to the cowboy, it works the same way:
%^GREEN%^modify tex listen%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex seems to be humming a jaunty melody.
Other features of an NPC do not take arrays, so one-line commands
will do. For example:
%^GREEN%^modify cowboy long This is a cowboy who calls himself Tex, but is in fact a Boy Named Sue.%^RESET%^
%^GREEN%^modify cowboy short a cowboy%^RESET%^
%^GREEN%^modify cowboy level 5%^RESET%^
%^GREEN%^modify cowboy class fighter%^RESET%^
%^GREEN%^modify tex currency gold 1%^RESET%^
%^GREEN%^modify tex currency silver 12%^RESET%^
%^GREEN%^modify tex skill bargaining 5%^RESET%^
%^GREEN%^modify tex skill projectile attack 7%^RESET%^
%^GREEN%^modify tex stat strength 33%^RESET%^
%^GREEN%^modify tex property nice guy 1%^RESET%^
%^GREEN%^modify tex healthpoints 150%^RESET%^
%^GREEN%^modify tex maxhealthpoints 170%^RESET%^
%^GREEN%^modify tex melee 0%^RESET%^
%^GREEN%^modify tex unique 1%^RESET%^
If you now issue the \"about tex\" command you will see that all
the changes you made have been put into the file.
You may have noticed the \"melee\" keyword. Dead Souls 2 NPC's come
in various shapes and sizes, and some of them shouldn't wield weapons. A
wolf with a battle axe would be a strange sight indeed. However, the
default combat system makes unarmed creatures extremely vulnerable in
combat.
To make an NPC combat-capable without weapons, use the new SetMelee
directive. SetMelee(1) makes the NPC capable of proper unarmed combat.
SetMelee(0) makes the NPC a weak opponent if unarmed.
NPC's will generally try to bite during unarmed combat. If this
is beneath the capability or dignity of your NPC, you can prevent
this with:
%^GREEN%^modify tex canbite 0%^RESET%^
If your NPC should try to escape when the battle isn't going
his way, the wimpy settings should do:
%^GREEN%^modify tex wimpy 30%^RESET%^
%^GREEN%^modify tex wimpycommand climb ladder%^RESET%^
If you don't specify a wimpy command, Tex will leave the
room through a random exit. In this case, when Tex's health
is down to 30% and he is in combat, he will try to climb a ladder.
Some NPC's are designed to travel about. To enable this
feature, use the wanderspeed directive:
%^GREEN%^modify tex wanderspeed 5%^RESET%^
If you want him to travel more quickly, use a
lower number. By default, wandering NPC's only wander in rooms
that have already been loaded into memory. They avoid loading
rooms because loading a bunch of rooms that only the NPC
will ever see is a waste of your mud's resources.
However, if you *do* want your NPC to wander in an
unrestricted manner, regardless of whether a room is loaded,
use the permitload directive:
%^GREEN%^modify tex permitload 1%^RESET%^
By default, NPC's stand up when they can. This is
so that if they collapse during combat, they try to
get back up once they are able to do so.
If you prefer that your NPC maintain some other
posture, you can set that posture, then disable
autostanding like this:
%^GREEN%^modify tex posture lying%^RESET%^
%^GREEN%^modify tex autostand 0%^RESET%^
If he's especially lazy, you can have him take
a nap this way:
%^GREEN%^modify tex sleeping 10%^RESET%^
Which will have him wake up after about a minute.
However, note that if you've disabled autostanding,
he will remain lying down after he wakes up.
If the NPC should be hostile, that is, he should
attack any creatures that it sees enter a room,
SetEncounter should do it:
%^GREEN%^modify tex encounter 100%^RESET%^
This means that if the creature it sees has a
charisma score of less than 100 (which should pretty
much be always true), Tex will try to kill it. You
can do some fancy stuff with SetEncounter, such
as only attacking orcs, or actually doing something
friendly, but to do so you can't use QCS. Read
the NPC and Sentients chapter in the Creator's
Manual for details on how to code such stuff.
If the NPC is a golem or other such non-biological
creature, it may be useful to specify what they are
made of. The SetComposition setting for a clay
golem might look like this:
%^GREEN%^modify golem composition clay%^RESET%^
If it happens to be a golem that does not believe
in violence as a solution to problems, you can
make refuse to hurt others with the following:
%^GREEN%^modify golem pacifist 1%^RESET%^
Vendors:
-------
Vendors are a special kind of NPC that can sell stuff.
Along with the standard NPC settings, vendors have
the following:
SetStorageRoom specifies where the vendor's stock
is stored. If a valid room is specified, anything
in that room can be sold by the vendor.
SetLocalCurrency specifies the type of currency,
such as gold or silver, that the vendor accepts.
SetMaxItems is the maximum number of items in
the storeroom that the vendor is permitted to sell.
SetVendorType specifies the kind of stuff the vendor can
trade in. \"all\" allows him to buy and sell whatever.
But if he is a weapon vendor, he can't trade in armor,
etc. See /include/vendor_types.h for the available
vendor types. To have a \"multi-type\" vendor, you'll have to
code it by hand. The result of that looks something
like this:
SetVendorType( VT_TREASURE | VT_ARMOR | VT_HERBS );
Barkeeps:
--------
Like vendors, barkeeps sell stuff, but they are
limited to selling food and drink.
Unlike vendors, barkeeps have no limitation on
the amount of stuff they can sell. They also do
not have a storeroom. The stuff they can sell is
specified in their SetMenu directive, like this:
%^GREEN%^clone woody%^RESET%^
You clone a generic barkeep (/realms/temujin/area/npc/woody.c).
%^GREEN%^modify woody menu%^RESET%^
If you don't understand these questions, type the letter q on a blank line and hit enter.
Please enter the first key element for this mapping:
%^GREEN%^bourbon%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^whiskey%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^.%^RESET%^
Please enter the value for key ({ \"bourbon\", \"whiskey\" }):
%^GREEN%^/domains/town/meals/bourbon%^RESET%^
Barkeeps also have the SetLocalCurrency directive
to specify the currency they accept.
",({"chapter 21","chapter twenty-one","21",}):"chapter 21 \"Meals\"
Building Food and Drink Objects
The Nightmare IV LPC Library
written by Descartes of Borg 950603
This document details the creation of food and drinks using the
Nightmare LPC Library. The creation of barkeeper objects requires you
to be able to build these objects, so make sure you understand what is
going on in here before moving on to barkeepers.
To create food or drink, you inherit from the standard meal object
/lib/meal.c For example:
#include <lib.h>
inherit LIB_MEAL;
You have access to the same functions you have in generic items when
you build food and drinks. In particular, you should be sure to call
the following:
SetKeyName()
SetId()
SetShort()
SetLong()
SetMass()
Note that SetValue() does NOTHING for food and drinks. Value is
automatically determined by the strength of the item.
The following function calls are specific to \"meal\" objects:
int SetMealType(int types);
int SetStrength(int strength);
mixed *SetMealMessages(function f);
OR
mixed *SetMealmessages(string mymsg, string othermsg);
string SetEmptyName(string str);
string SetEmptyShort(string str);
string SetEmptyLong(string str);
string SetEmptyItem(string str);
You must call SetMealType(), SetStrength(), and SetMealMessages().
If you call SetEmptyItem(), you do not need to call the functions
SetEmptyName(), SetEmptyShort(), SetEmptyLong(). On the other hand,
if you do not call SetEmptyItem(), you do need to set the other three.
*****
int SetMealType(int types)
*****
Example: SetMealType(MEAL_FOOD);
For meal objects, you must do:
#include <meal_types.h>
This includes all od the definitions for the meal types in
/include/meal_types.h into your food or drink. You need these
definitions when setting what type of meal object this is. The types
are:
MEAL_FOOD
MEAL_DRINK
MEAL_CAFFEINE
MEAL_ALCOHOL
MEAL_POISON
In general, almost anything you create will be at least either
MEAL_FOOD or MEAL_DRINK. You can add onto it using the | operator.
For example, to make an alcoholic drink:
SetMealType(MEAL_DRINK | MEAL_ALCOHOL);
This makes something a drink and an alcoholic drink. You want to
stick poison in it?
SetMealType(MEAL_DRINK | MEAL_ALCOHOL | MEAL_POISON);
*****
int SetStrength(int x)
*****
Example: SetStrength(20);
This sets how strong your food or drink is. It affects things like
which people can drink or eat it and how much the drink or food costs.
Refer to balance documents to see what is good.
*****
varargs mixed *SetMealMessages(function|string, string)
*****
Examples:
SetMealMessages((: call_other(find_object(\"/some/object\"),\"drink\") :));
SetMealmessages(\"You drink your beer.\", \"$N drinks $P beer.\");
You can pass a single argument, which is a function to be called.
This function will be called after the person has drank or eaten the
meal. It gives you a chance to do some bizarre messaging and such.
If you pass two strings, the first string is used as a message to send
to the player doing the drinking, and the second is what everyone else
sees. To make the message versatile, you can put in the following
place holders:
$N the name of the drinker/eater
$P his/her/its
For example:
$N drinks $P beer.
might resolve to:
Descartes drinks his beer.
*****
string SetEmptyName(string str)
*****
Example: SetEmptyName(\"bottle\");
Sets an id from the empty container of drinks. This need not be set
for food.
*****
string SetEmptyShort(string str)
*****
Example: SetEmptyShort(\"an empty bottle\")
Sets what the short description of the empty container is for anything
that is of type MEAL_DRINK.
*****
string SetEmptyLong(string str)
*****
Example: SetEmptyLong(\"A brown bottle that used to contain beer.\");
Sets the long description for the empty container for drink objects.
*****
string SetEmptyItem(string str)
*****
Example: SetEmptyItem(\"/domains/Praxis/etc/empty_bottle\")
Instead of cloning a generic empty object and setting the other empty
functions, you can create a special empty container which gets given
to the player after they drink a drink object. Not relevant to food.
",({"chapter 22","chapter twenty-two","22",}):"chapter 22 \"NPCs\"
Building Non-Player Characters
The Nightmare IV Object Library
written by Descartes of Borg 951201
This document outlines the creation of non-player characters (NPC's).
On other muds, NPC's are sometimes referred to as monsters. Like the
rooms document, this document is divided up into two sections: basic
NPC building and complex NPC building. NPC's are living things which
inherit all the behaviours of living things. Documentation on living
specific functionality may be found in /doc/build/Livings.
************************************************
Part 1: Basic NPC Building
************************************************
*****
I. The simplest NPC
*****
#include <lib.h>
inherit LIB_NPC;
static void create() {
npc::create();
SetKeyName(\"praxis peasant\");
SetId( ({ \"peasant\", \"praxis peasant\" }) );
SetShort(\"a local peasant\");
SetLong(\"Dirty and totally disheveled, this poor inhabitant of Praxis \"
\"still somehow maintains an air of dignity that nothing can \"
\"break.\");
SetLevel(1);
SetRace(\"elf\");
SetClass(\"fighter\");
SetGender(\"male\");
}
There are two things you should note. The first is that an NPC is
also a general object, meaning that you have available to you all the
things you can do with general objects, like setting descriptions and
ID's. The second is that a basic NPC does not require a heck of a lot
more. I will cover the NPC specific functions here.
SetLevel(1)
SetRace(\"elf\")
SetClass(\"fighter\")
Level, race, and class are the three most important settings in any
NPC. Together they determine how powerful the NPC is. You are
absolutely required to set a level and a race. For those who
absolutely do not want to give the NPC a class, you do not have to.
But, you must instead manually set the NPC's skill levels, which is
described in the second part of this document. In general, however,
you always want to set the class.
Together, the class and race and level determine which skills and
stats are considered important for the monster, and how good at those
skills and stats the monster is. The order in which you call these
functions is irrelevant, as everything is recalculated any time one of
the above changes.
Also, note that SetRace() may only be called with a race listed in the
mraces command with simple NPC's. If you wish to build an NPC with a
unique race, you need to do some limb manipulation, which is described
in the advanced section.
SetGender(\"male\")
While not required, you will normally want to give an NPC a gender.
The default is neutral. However, in this world, very little is
neuter. Your choices for this function are male, female, and neuter.
*****
II. Other NPC Configuration Functions
*****
Function: int SetMorality(int amount);
Example: SetMorality(100);
This is a number between -2000 and 2000 which determines the morality
of an individual with respect to good and evil. -2000 is absolute
evil, and 2000 is absolute good. The actions of players determine
their morality, and often those actions are relative to a target.
Thus killing an evil being can be considered good, while killing a bad
one evil.
Function: int SetUnique(int x);
Example: SetUnique(1)
Marks the NPC as a unique monster. This allows the room which clones
your NPC to use the negative values to SetInventory() (see
/doc/build/Rooms) to make sure the NPC only gets cloned every few
days.
************************************************
Part 2: Advanced NPC Building
************************************************
*****
I. Functions
*****
You may use these functions to make your NPC's a bit more interesting
than the simple variety.
Function: void SetAction(int chance, mixed val);
Examples: SetAction(5, (: DoSomething :));
SetAction(5, ({ \"!smile\", \"!frown\" }));
SetAction(5, ({ \"The peasant looks unhappy.\" }));
Sets something to randomly happen every few heart beats while the NPC
is not in combat. In the above examples, the NPC has a 5% chance each
heart beat of performing the action you provided with the second
argument. The action can be a call to a function, a list of potential
commands, or a list of strings to be echoed to the room.
If you pass a function, that function will be called each time an
action is supposed to occur. If you pass a list of strings, one of
those strings will be randomly chosen as the target action for this
heart beat. If the chosen string begins with a !, it is treated as a
command. Otherwise, it is simply echoed to the room. Note that you
can mix commands and echo strings.
*****
Function: void SetCombatAction(int chance, mixed val);
Examples: SetCombatAction(5, (: DoSomething :));
SetCombatAction(5, ({ \"!missile\", \"!fireball\" }));
SetAction(5, ({ \"The peasant looks angry.\" }));
This function works exactly the same as SetAction(), except that these
actions only get triggered while the NPC is in combat. This is the
best place to have the NPC cast spells.
*****
Function: varargs void SetCurrency(mixed val, int amount);
Examples: SetCurrency(\"gold\", 100);
SetCurrency( ([ \"gold\" : 100, \"electrum\" : 1000 ]) );
This function allows you to set how much money an NPC is carrying.
The first syntax allows you to set one currency at a time. The second
allows you to set multiple currencies at once. Not that if you use
the second syntax, it will blow away any currencies the NPC might
already be carrying.
*****
Function: mixed SetDie(mixed val);
Examples: SetDie(\"The black knight bleeds on you as he drops dead.\");
SetDie((: CheckDie :));
If you pass a string, that string will be echoed as the NPC's death
message when it dies. If you pass a function, that function gets
called with the agent doing the killing, if any, as an argument. For
example, with the above example, the function that you write:
int CheckDie(object killer);
gets called. If you return 1, the NPC goes on to die. If you return
0, the NPC does not die. In the event you prevent death, you need to
make some arrangements with the NPC's health points and such to make
sure it really is still alive.
*****
Function: mixed SetEncounter(mixed val);
Examples: SetEncounter(40);
SetEncounter( (: CheckDwarf :) );
SetEncounter( ({ str1, str2 }) );
This allows you to set up e behaviour for an NPC upon encountering
another living thing. Note that this behaviour occurrs for both
players and other NPC's. Using the first syntax, the NPC will simply
attack any other living thing with a charisma less than 40. The
second syntax calls the function you specify. You may have it do any
number of things, however, you must also return a 1 or a 0 from that
function. A 1 means that after the function is called, the NPC should
initiate combat against the thing it just encountered. A 0 means
carry on as usual.
Finally, the third syntax is likely to be used in places other than
the create() funciton in the NPC. This syntax lets you set a list
names which are simply enemies to the NPC. More likely, you will be
using AddEncounter() and RemoveEncounter() for this.
*****
Function: string *AddEncounter(string name);
Example: AddEncounter((string)this_player()->GetKeyName());
Adds a name to the list of names an NPC will attack on sight.
*****
Function: string *RemoveEncounter(string name);
Example: RemoveEncounter((string)this_player()->GetKeyName());
Removes a name from the list of names an NPC will attack on sight.
*****
Function: SetInventory(mapping inventory);
Examples:
SetInventory( ([ \"/domains/Praxis/weapon/sword\" : \"wield sword\" ]) );
SetInventory( ([ \"/domains/Praxix/etc/ruby\" : 1 ]) );
SetInventory( ([ \"/domains/Praxis/etc/emerald\" : -10 ]) );
This functions behaves almost identically to SetInventory() for rooms
(see /doc/build/Rooms). The big difference is that you may pass a
string in addition to a number as the value for any item you want in
the inventory. In the first example above, that string is the command
the NPC issues when the sword is cloned into its inventory. In other
words, if you want an NPC to do something special with an item it has
in its inventory, in this case wield the sword, you pass the command
string as the value instead of a number.
Note that this means only one of such item will be cloned, and it
cannot be unique.
*****
II. Events
*****
The following events exist in NPC's. You should have a good grasp of
function overriding before overriding these functions.
Event: varargs int eventDie(object target);
This event is triggered any time the NPC is killed. The event returns
1 if the NPC dies, 0 if it fails to die, and -1 on error. If you
intend to allow the NPC to die, you should call npc::eventDie(target)
and make sure it returns 1.
*****
Event: int eventFollow(object dest, int chance);
This event is triggered whenever an NPC is following another living
thing and that thing leaves the room. Returnung 1 means that the NPC
successfully followed the other being, and 0 means the NPC did not.
*****
3. Manipulating limbs
*****
The basic set of limbs an NPC gets is generally set when you set its
race. You can get a list of supported NPC races through the mraces
command. Occassionally, however, you may want to create NPCs of
unique races, or with unique body structures. Or perhaps you want a
human whose right hand is already amputated. This section deals with
doing those things.
Amputating a limb is simple. Call RemoveLimb(\"limb\"). Note that that
is not useful for removing a limb that should not be there. Instead,
it is used for amputating a limb that looks amputated.
If, on the other hand, you wish to remove a limb which simply should
not have been there in the first place, call DestLimb(\"limb\").
The most simple case of actual limb manipulation, however, is to
change the basic structure of an individual NPC around for some
reason. For example, perhaps you wanted to add a tail to a human.
For this, you use the AddLimb() function.
Function: varargs int AddLimb(string limb, string parent, int class, int *armours);
Examples: AddLimb(\"tail\", \"torso\", 4)
AddLimb(\"head\", \"torso\", 1, ({ A_HELMET, A_VISOR, A_AMULET }));
This function adds a new limb to the NPC's body. The first argument
is the name of the limb to be added. The second argument is the name
of the limb to which it is attached. The third argument is the limb
class. Limb class is a number between 1 and 5. The lower the number,
the harder the limb is to remove. A limb class of 1 also means that
removal of the limb is fatal. The fourth, optional argument is a list
of armour types which may be worn on that limb.
In some cases, you may wish to create a new race from scratch. This
requires adding every single limb manually. You first call SetRace()
with a special second argument to note that you are creating a new
race:
SetRace(\"womble\", 1);
Then, you add the limbs for that race one by one. Make sure you call
SetRace() first.
",({"chapter 35","chapter thirty-five","35",}):"chapter 35 \"QCS: Modifying rooms\"
Suppose you are in your sample room and you issued the
command:
%^GREEN%^create room south testroom1%^RESET%^
You then travel south and see that you are in a room that is
almost exactly like the sample room except for the exits. Well,
probably you don't want to have a mud with nothing but identical
rooms, so let's modify it:
%^GREEN%^modify here short Test Room One%^RESET%^
%^GREEN%^modify here long This is the first test room. The walls are rather blank.%^RESET%^
%^GREEN%^modify here climate indoors%^RESET%^
%^GREEN%^modify here light 30%^RESET%^
Ok, so far so good. Standard interior. However, a good mud has
rooms with details. Let's add some detail to this room.
I've omitted the system output for clarity. This is just what you
would input.
%^GREEN%^modify here item%^RESET%^
%^GREEN%^wall%^RESET%^
%^GREEN%^walls%^RESET%^
%^GREEN%^blank wall%^RESET%^
%^GREEN%^blank walls%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^These are just blank walls.%^RESET%^
Let's review what we've done here:
1) You issued the modify command specifying your current room as the
target, and the SetItems directive as the argument.
2) You entered a query session, and were asked to enter each element
of the item's key.
3) You entered a single dot to indicate you were done entering
key elements.
4) You entered the value for the key, which is the description of the
item.
The result of all this is that now you can issue these commands:
%^GREEN%^exa wall%^RESET%^
%^GREEN%^look at blank walls%^RESET%^
%^GREEN%^examine walls%^RESET%^
And the output will be:
These are just blank walls.
Let's add a floor while we're at it:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^floor%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A floor like any other.%^RESET%^
In this case, you didn't feel like adding extra synonyms for \"floor\",
so you entered the final dot rather than entering another key element.
Then you added the description, and now if you \"exa floor\", you'll get
that description.
\"about here\" will display to you the file you have modified.
Well, that's enough fun with indoor rooms. There's not much more
to them. Let's go outdoors now:
%^GREEN%^create room south exterior_room%^RESET%^
%^GREEN%^create door south test_door%^RESET%^
%^GREEN%^open door%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^modify here short a small lawn%^RESET%^
%^GREEN%^modify here daylong A small, well groomed lawn on a lovely sunny day. There is a small building north of here.%^RESET%^
%^GREEN%^modify here nightlong This is a small lawn. Stars twinkle in the night sky above, and some light is coming from a small building to the north.%^RESET%^
%^GREEN%^modify here daylight 30%^RESET%^
%^GREEN%^modify here nightlight 20%^RESET%^
%^GREEN%^modify here light delete%^RESET%^
%^GREEN%^modify here long delete%^RESET%^
%^GREEN%^modify here items delete%^RESET%^
%^GREEN%^modify here items%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^small building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A small building, rather ramshackle as if hastily put together.%^RESET%^
%^GREEN%^modify here climate temperate%^RESET%^
Ok! A few new things here. A neat thing about outdoor rooms is that
typically they are subject to the time of day. A SetClimate directive
that indicates an exterior environment causes the room to receive
messages about the sun setting, rising, etc.
The SetDayLong and SetNightLong directives allow you to more
sensibly describe the area depending on the time of day. To avoid
confusion, I deleted the SetLong directive. It is not mandatory to
have different day and night descriptions, but players appreciate the
effort.
It is also possible to have differing ambient light levels depending
on the time of day, so we've added SetDayLight and SetNightLight, and
we deleted the SetAmbientLight directive.
Let's continue to add detail:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Healthy, well groomed and freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You can smell the refreshing scent of freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Yep, it's got that new lawn smell.%^RESET%^
%^GREEN%^modify here listen%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Sounds like someone's fumbling about in there, making a mess. New creators can be so noisy.%^RESET%^
%^GREEN%^modify here item%^RESET%^
%^GREEN%^garden%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You may enter the garden from here.%^RESET%^
%^GREEN%^create room garden garden_room%^RESET%^
You now have a room with lots of charm and detail. You can \"smell grass\"
and \"listen to small building\", if you like. Neat, huh? But there's something
very important to keep in mind:
Enters, listens and smells don't work properly if there is no item defined
for that smell. For example, if you want to be able to listen to the sea,
you must \"modify here item\" and add a \"sea\" item. Otherwise, \"listen
to the sea\" will respond with \"There is no sea here.\"
The only exception to this rule is the \"default\" smell.
Enters behave similarly. If you want to be able to \"enter\" something,
you'll need to create the corresponding item first, as in the example above.
You can use the SetProperties directive to make the room
conform to some presets, like:
%^GREEN%^modify here property no attack 1%^RESET%^
Read chapter 23 in the Creator's Manual for details on room properties.
Also, please note that indoor rooms can also have differing
descriptions and light levels for night and day. It's just that
indoor rooms don't get notification of daytime changes.
Finally, the SetTown directive allows the room to participate in
area-wide events, and is useful for security purposes as well:
%^GREEN%^modify here town MyTown%^RESET%^
Notes on room filenames:
-----------------------
By default, a filename without a leading path creates a room
in your area room directory, which in my case would be
\"/realms/cratylus/area/room\". However, you can specify a different
location for the new room.
To create a room in your current working directory:
%^GREEN%^create room east ./newroom%^RESET%^
To create a room in a specific directory:
%^GREEN%^create room east /realms/cratylus/testrooms/newroom%^RESET%^
",({"chapter 25","chapter twenty-five","25",}):"chapter 25 \"Rooms\"
Building Rooms
The Nightmare IV LPC Library
written by Descartes of Borg 950420
This document details how to build rooms using the Nightmare IV LPC
Library's inheritable room object. This document is divided into
simple room building and complex room building. The first part
teaches you about basic rooms. The second part tells you what
features are there to allow you to do creative things with the room object.
************************************************
Part 1: Basic Room Building
************************************************
I. The Simple Room
The simple room minimally looks like this:
#include <lib.h>
inherit LIB_ROOM;
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"indoors\");
SetShort(\"an empty room\");
SetLong(\"An empty room with no exits leading anywhere. It is \"
\"completely barren with nothing to describe.\");
}
#include <lib.h>
This first line is one you need in any object. It defines the exact
location of objects which you inherit. In this case, the object is
LIB_ROOM. It is currently located at /lib/room.c. If we wanted to
change that location, however, we could do it easily since you only
reference LIB_ROOM. So lib.h is a file that says that LIB_ROOM is
\"/lib/room\".
inherit LIB_ROOM;
The third line, the inherit line, says that this object will inherit
/lib/room.c.
static void create() {
The fifth line begins the meat of any object you will write. This is
the beginning of a function. This one is called create(). If you are
curious, the static means no one can use the call command to call the
function. Do not worry about that too much, however, as it is always
something you put there for the create() function.
The \"void\" part simply says that you are returning no value from the
function. See the LPC Basics textbook for more information on
functions.
room::create();
Inside the create() function are the calls which define what the
object will do. The first call calls the function create() in
/lib/room.c, the object you just inherited. /lib/room.c has its own
create() function which does some things needed in order to set up
your object. You need to make sure it gets called through this line.
SetProperty(\"light\", 2);
This sets the light in the room to be 2. In outdoors rooms, this is
the average light during day time. In indoor rooms, it is the average
light all the time.
SetClimate(\"indoors\")
Every room has a climate. An indoors room, among other things, is not
affected by weather or the time of day.
SetShort(\"an empty room\")
This is the description that the player sees when in brief mode. In
addition, in brief mode, obvious exit abbreviations are automatically
added. This is done through the SetObviousExits() function described
later. However, the short should be phrased in such a way that it
makes sense from something like a scry command which would say
something like: \"You see Descartes in an empty room.\"
SetLong(\"An empty room with no exits leading anywhere. It is \"
\"completely barren with nothing to describe.\");
This sets the long description seen by the player when in verbose
mode. Note that items in the room as well as scents and sounds are
added to what the player sees automatically.
That's it! You now have a room which no one can leave!
II. Adding items
Approval on any decent MUD will eat you for lunch if you do not
describe your items. This is likely the most tedious part of area
building, however, it is also the part that largely makes the
difference between a dull area and a fun one. You must be sure to
make it so that anything a player might logically want to see in
detail in a room is described in detail. For example, say you have
the following long description for a room:
You are in Monument Square, once known as Krasna Square. The two main
roads of Praxis intersect here, where all of Nightmare's people gather
in joy and sorrow. The road running north and south is called Centre
Path, while Boc La Road is the name of the road running east and west.
A magnificent monument rises above the square.
You should have descriptions for the following items placed in your
room:
square, monument, monument square, krasna square, roads, road,
intersection, people, centre path, boc la road, magnificent monument
How to do this with a minimum of hassle:
SetItems( ([ ({ \"square\", \"monument square\", \"krasna square\" }) :
\"The central square of Praxis where citizens and adventurers \"
\"gather to chat and trade. Formerly known as Krasna Square, \"
\"is now known as Monument Square as thanks to those who helped \"
\"to build the town\",
({ \"monument\", \"magnificent monument\" }) : \"A giant monolith \"
\"rising above Monument Square\",
({ \"intersection\", \"road\", \"roads\" }) : \"The two main roads of Praxis \"
\"intersect in Monument Square. The one to the north and south \"
\"is called Centre Path, while the other is Boc La Road.\",
({ \"people\", \"adventurers\", \"citizens\" }) : \"A varied group of \"
\"people from countless realms hanging about talking and trading.\",
\"centre path\" : \"The main road leading north to the North Forest \"
\"from Praxis, and south to the sea.\",
\"boc la road\" : \"The main east-west road through Praxis, going \"
\"east towards the jungle, and west towards the Daroq Mountains.\" ]) );
That may seem like a mouthful, but it is easier to break down into
smaller points and see what is going on. The SetItems() prototype
looks like this:
mapping SetItems(mapping items);
That means it accepts a data type called a mapping as the argument and
returns a new mapping of items. A mapping is a special data type in
LPC that allows you to associate two values together, for example, to
associate an item with its description. For example, above we wanted
to associate the items \"monument\" and \"magnificent monument\" with the
description \"A giant monolith rising above Monument Square\". To do
that, a mapping looks like this:
([ value1 : assoc_value1 ])
where assoc_value1 is the value associated with value1. In this case,
we might have something like:
([ \"monument\" : \"A giant monolith rising above Monument Square.\" ])
But, we also wanted to associate \"magnificent monument\" with this
description. One way, which is perfectly legitimate, would be:
([ \"monument\" : \"A giant monolith rising above Monument Square\",
\"magnificent monument\" : \"A giant monolith rising above Monument Square\" ])
But that would be damned annoying, especially with long descriptions
or things with a lot of synonyms. You can therefore group values
which have the same description together using array notation:
({ value1, value2, value3 })
And thus, make that mapping look like:
([ ({ \"monument\", \"magnificent monument\" }) : \"A giant monolith rising \"
\"above Monument Square.\" ])
To complete setting the items, you simply add other item/description
pairs separated by commas:
([ ({ \"monument\", \"monument square\" }) : \"A giant monolith rising \"
\"above Monument Square.\",
\"house\" : \"A little white house with white picket fences.\" ])
Mappings are a rather difficult concept to grasp, but once grasped
they are very powerful. You should take a look at some sample code
from /domains/Examples/room to get a good idea of what proper code
looks like. In addition, there is a chapter in Intermediate LPC
dedicated to the concept. Finally, you can always mail
borg@imaginary.com to ask questions.
III. Adding Exits and Enters
If you understand the section above, exits and enters are simple.
They too use mappings, but less complicated ones:
SetExits( ([ \"north\" : \"/domains/Praxis/n_centre1\",
\"south\" : \"/domains/Praxis/s_centre1\",
\"east\" : \"/domains/Praxis/e_boc_la1\",
\"west\" : \"/domains/Praxis/w_boc_la1\" ]) );
SetEnters( ([ \"hall\" : \"/domains/Praxis/town_hall\",
\"pub\" : \"/domains/Praxis/pub\" ]) );
With an exit mapping, you simply match the direction to the room to
which it leads. With an enter mapping, you match a thing being
entered with the room to which it leads.
Unlike other LPC Libraries, the Nightmare IV LPC Library distinguishes
between the concept of motion towards and motion into. Motion towards
is exemplified by the \"go\" command, which is affected by SetExits().
For example, to go east, you type \"go east\". You are simply going
towards the east (Note that \"go east\" is by default aliased to \"e\").
Motion into is exemplified by the \"enter\" command, which is affected
by SetEnters(). Enter marks anything you enter into, for example a
building or bushes or the like. In the above example, a player would
issue the command \"enter pub\" to enter the pub.
IV. Adding Objects
If you want to add physical objects into your room, you use the
SetInventory() function. For example, if you wanted to place a balrog
in the room:
SetInventory(([ \"/domains/Praxis/npc/balrog\" : 1 ]);
Every reset, the room will then check to see if any balrogs are in the
room. If no balrogs are in the room it will clone 1. Again, this is
another function using a mapping. In this case it is associating the
file name of an object with how many of that object should be in the
room at every reset. If you wanted 5 balrogs in the room, you would
have changed the 1 to 5.
V. Adding Smells, Listens, and Searches
The functions:
SetSmell()
SetSearch()
SetListen()
All work identically to the SetItems() function. That is they match
things you can smell, listen, search to descriptions which the player
sees when they smell, listen, search the item.
For example:
SetSmell( ([ \"monument\" : \"It smells of obsidian.\",
\"road\" : \"It smells dusty.\",
({ \"pub\", \"bar\" }) : \"It smells of alcohol.\" ]) );
If a player types:
\"smell monument\"
then they see
\"It smells of obsidian.\"
One unique thing about these three functions, however, is that you can
use the special thing \"default\" to set a smell, listen, or search that
occurs when no object is specified. For example,
SetSmell(([ \"default\" : \"It really stinks here.\" ]) );
Will have the player see \"It really stinks here.\" when they simply
type \"smell\". In addition, this is the smell the player sees when
they simply walk into a room.
VI. Miscellaneous stuff
SetObviousExits(\"n, s, e\")
Sets an obvious exits string which gets seen in brief mode and by
newbies in verbose mode. Generally, this should consist of the
abbreviations for the room's obvious exits only.
SetTown(\"Praxis\")
For rooms which are considered part of a town, you must specify that
they are part of the town through this function. In this example, the
room is set to be in the town of Praxis. See the document
/doc/build/Towns for more information on towns.
SetDayLong(\"The sky lights up the endless fields of wheat which stand \"
\"before you.\");
SetNightLong(\"You are standing in a pitch black field of wheat.\");
Instead of using SetLong(), you can call both of these functions to
give different long descriptions for day and night.
SetGravity(2.0)
This makes things in the room twice as heavy as normal.
SetDoor(\"east\", \"/domains/Praxis/doors/red_door\");
Sets a door to the east which is the file
\"/domains/Praxis/doors/red_door.c\". You should have an exit to the
east, and you should do this AFTER you have called SetItems(). See
the document /doc/build/Doors for detailed information on door
building.
VII. Summary
Here is a room that uses everything described above:
#include <lib.h>
inherit LIB_ROOM;
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"temperate\");
SetTown(\"Praxis\");
SetShort(\"a peaceful park\");
SetDayLong(\"The light of the sun shines down upon an open field \"
\"in the middle of Praxis known as Kronos Park. In spite \"
\"of the time of day, no one is around. East Boc La \"
\"Road is to the south.\");
SetNightLong(\"Kronos Park is a poorly lit haven for rogues in the \"
\"cover of night. It is safest to head back south \"
\"towards the lights of East Boc La Road\");
SetItems( ([ ({ \"field\", \"park\" }) : \"A wide open park in the \"
\"center of Praxis.\" ]) );
SetSearch( ([ \"field\" : \"You get dirt all over your hands.\" ]) );
SetSmell( ([ \"default\" : \"You smell grass after a fresh rain.\",
\"dirt\" : \"It smells like... dirt!\" ]) );
SetExits( ([ \"south\" : \"/domains/Praxis/e_boc_la3\" ]) );
SetInventory( ([ \"/domains/Praxis/npc/rogue\" : 2 ]) );
}
************************************************
Part 2: Advanced Room Building
************************************************
I. Functionals
MudOS has a data type called a functional. Most room functions take a
functional as an argument instead of a string. What this does is
allow you to specify a function to get called in order to determine
the value rather than set it as a string which cannot be changed. For
example, if you wanted to set a long description that varied depending the
status of a door:
#include <lib.h>
inherit LIB_ROOM;
string CheckDoor(string useless);
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"indoors\");
SetShort(\"an indoor room with a door\");
SetLong( (: CheckDoor :) );
SetExits( ([ \"east\" : \"/domains/Praxis/east_room\" ]) );
SetDoor(\"east\", \"/domains/Praxis/doors/red_door\");
}
string CheckDoor(string useless) {
string tmp;
tmp = \"You are in a plain indoor room with a door. \";
if( (int)\"/domains/Praxis/doors/red_door\"->GetOpen() )
tmp += \"The door is open.\";
else tmp += \"The door is closed.\";
return tmp;
}
In this example, a function called CheckDoor() was written to
determine exactly what the long description should be. This is done
because in create(), you have no idea what the status of the door will
be from moment to moment. Using a function, you can therefore
determine what the long description is at the time it is needed.
Functionals can reference any function anywhere on the MUD, including
efuns. See /doc/lpc/data_types/functionals for details on them. For
the sake of this document however, you note a functional using smileys
:).
(: CheckDoor :) means the function CheckDoor() in this object. You
can also specify function in other objects, for example:
(: call_other, this_player(), \"GetName\" :) would refer to GetName() in
the person who was this_player() AT THE TIME THE FUNCTIONAL WAS
CREATED.
Notice at the top of the file that CheckDoor() was prototyped. You
must prototype any function you reference inside your objects. The
expression (: CheckDoor :) constitutes as a reference, and thus makes
you need to prototype the function.
The rest of this portion describes individual function calls using
functionals. The functional prototype part is how your functional
should be declared.:
SetShort(string | function)
Functional prototype: string ShortFunc();
Example: SetShort( (: MyShort :) );
If you pass it a function, then this function gets called to determine
the short description. The function should return a string which will
be used as the short description.
SetLong(string | function)
Functional prototype: string LongFunc(string unused)
Example: SetLong( (: MyLong :) );
This function should return a string which will be used as the long
description for the room. The argument \"unused\" is just that, unused
in this context. It is something used for other objects.
SetItems(mapping mp);
Functional prototype: string ItemFunc(string item);
Example: SetItems( ([ \"house\" : (: LookHouse :) ]) );
This function should return a string to be used for the item
description. The argument is passed the name of the item being looked
at, so you can use the same function for multiple items.
SetSearch(mapping mp)
Alternate: SetSearch(string item, string | function desc)
Functional prototype: string SearchFunc(string item);
Examples: SetSearch( ([ \"grass\" : (: SearchGrass :) ]) );
SetSearch(\"grass\", (: SearchGrass :));
Note that there are two forms to SetSearch(), useful depending on how
many searches you are setting at once. If you have a search function,
then that function should return a string which is what they will see.
The argument passed is the item being searched.
SetSmell()
SetListem()
see SetSearch()
II. Advanced Exits
SetExits() is fairly straight forward. However, there exists another
function for exits called AddExit(). It allows you to add one exit at
a time (useful if say a player searches and finds a new exit) as well
as give functional power to exits. The prototype for AddExit() is:
varargs mapping AddExit(string dir, string dest, function pre, function post);
The varargs part of the prototype simply means you can call it using
less than the full number of arguments specified. In this case, the
minimum call is:
AddExit(\"east\", \"/domains/Praxis/square\");
The last two arguments are called pre-exit functions and post exit
functions. The pre-exit function gets called when a player issues a
command to leave the room, but before the player is allowed to leave.
Depending on the return value of the function, the player is allowed
or denied the right to leave. For example:
AddExit(\"north\", \"/domains/Praxis/square\", (: PreExit :));
int PreExit(string dir) {
if( !avatarp(this_player()) ) {
write(\"You are too lowly to go that way!\");
return 0;
}
else return 1;
}
In other words, if the player is an avatar, they can go north.
Otherwise they cannot. The prototype is:
int PreExit(string dir);
where the return value is 1 or 0 for can or cannot leave, and the
argument dir is the direction in which the player is exiting.
Post exit functions work a little differently since it makes no sense
to prevent someone from leaving once they have left. The prototype
looks like:
void PostExit(string dir);
This simply allows you to do processing once the player is gone. If
you wish a post exit without a pre exit, then:
AddExit(\"north\", \"/domains/Praxis/square\"\", 0, (: PostExit :));
Enters work exactly the same way.
Please read about the events CanReceive() and CanRelease(), as those
may be more appropriate places to do what you want. Remember, this
only prevents a player from using the \"go\" command to go in that
direction. CanReceive() in the other room would be better if your
desire is to keep non-avatars out of the square at any cost.
III. Other Functions
AddExit()
RemoveExit()
AddEnter()
RemoveEnter()
RemoveSearch()
RemoveSmell()
RemoveListen()
AddItem()
RemoveItem()
All of the above Remove*() functions take a single string argument
specifying what it is that is being removed. For example:
RemoveExit(\"east\")
removes the exit to the east.
AddItem(string item, mixed val)
Adds a single item. Val can be a string or function.
Descartes of Borg
borg@imaginary.com
",({"chapter 19","chapter nineteen","19",}):"chapter 19 \"Doors\"
Creating Doors between Two Rooms
The Nightmare IV LPC Library
created by Descartes of Borg 950419
This document describes how to build door-type objects which link two
rooms. These door-type objects do not need to be doors, but in fact
can be windows or boulders or any other such object. The Nightmare IV
LPC Library door object, unlike the old way of doing doors, is an
object separate from the rooms it connects. In other words, in order
to build a door, you have three objects (just as you would visualize):
two rooms and a door.
The door object is /lib/door.c. To inherit it, #include <lib.h> and
inherit LIB_DOOR;. An example door may be found in
/domains/Examples/etc/door.c as well as the rooms
/domains/Examples/room/doorroom1.c and /domains/Examples/room/doorroom2.c.
Setting up the door object
The first thing you must do is create the door object. You must
visualize this door object just like a door connecting two rooms in
real life. You have a room on each side with a single door with two
sides. Technically, a door object may have any number of sides.
Practically speaking, most people using this object will be using it
as a door, which means it will have two sides.
To create a door object, you simply describe each side of the door.
The easiest way to do this is through the SetSide() function.
mapping SetSide(string side, mapping mp);
Example:
SetSide(\"east\", ([ \"id\" : \"red door\", \"short\" : \"a red door\",
\"long\" : \"A freshly painted red door.\",
\"lockable\" : 0 ]) );
The name of the side is simply the exit used by the room which sees
that side. For example, if in one room the door is at the east exit,
then the side is identified as east. The mapping consists of the
following data:
\"id\"
What a person on that side calls the door. For example, you can have a
door blue on one side and red on the other. On one side, you go east
to go through the door, and from that room the door appears red. The
id for that side might be \"red door\". The id for the other side might
be \"blue door\".
\"short\"
The short description for the door as seen from the side in question.
This can be a function or a string.
\"long\"
The long description for the door as seen from the side in question.
Whether the door is open or not will be added to the long if the long
is a string. This can be either a string or function. If it is a
function, you must specify whether the door is open or close on your
own.
\"lockable\"
0 if the door cannot be locked (and unlocked) from that side, 1 if it
can.
\"keys\"
An array of id's of objects which can be used to unlock it if it is
lockable. Lockable doors do not need keys.
II. Setting up the rooms
After you have called SetItems() and SetExits() in the room
(remembering to set the exit for the exit with the door), call the
function SetDoor().
string SetDoor(string dir, string doorfile);
Example: SetDoor(\"east\", \"/realms/descartes/doors/red_door\");
Sets the exit named to be blocked by a door object when that door
object is closed.
This is all you need to do in the room. Note that the exit name
corresponds to the side name mentioned in the door.
III. Advanced Door Stuff
At this point, you should know how to do the minimum stuff to build a
door. This section goes into detail about door functions and how you
can do advanced things with doors by manipulating door events. This
section has two parts, door data functions and door events.
a. Door Data Functions
*****
SetSide()
*****
mapping SetSide(string side, mapping mp);
As described above.
*****
SetClosed()
*****
static int SetClosed(int x)
Example: SetClosed(1);
This function can only be called from inside the door object.
Generally you use it to set the initial state of the door. If you
want to close the door at any other time, or to close it from another
object, use eventClose() or eventOpen().
*****
SetLocked()
*****
static int SetLocked(int x)
Example: SetLocked(1);
Like SetClosed(), this function should only be used from create()
inside the door object to set the initial state of the door. At other
times, use eventLock() or eventUnlock().
*****
SetLockable()
*****
int SetLockable(string side, int x)
Example: SetLockable(\"east\", 1);
Sets a side as being able to be locked or unlocked. Since it is done
by sides, this means you can have one side not be lockable with the
other side being lockable. The first argument is the side being set
lockable or not lockable, the second argument is 1 for lockable and 0
for not lockable.
*****
SetId()
*****
string SetId(string side, string id)
Example: SetId(\"west\", \"blue door\");
This is not like your traditional SetId() function. Instead, it sets
a single way of identifying the door from a given side. It is what
the player might use to open the door or look at it.
*****
SetShort()
*****
mixed SetShort(string side, string | function desc)
Examples:
SetShort(\"north\", \"a red door\");
SetShort(\"west\", (: GetWestShort :) );
Sets the short description for a given side of a door. If the second
argument is a function, it gets passed as an argument the name of the
side for which the function serves as a description. That function
should return a string. For the above:
string GetWestShort(string dir) {
if( query_night() ) return \"a shadowy door\";
else return \"a red door\";
}
*****
SetLong()
*****
mixed SetLong(string side, string | function desc)
Examples:
SetLong(\"south\", \"An old, dusty door covered in cobwebs.\");
SetLong(\"east\", (: GetEastLong :))
This works much like the SetShort() function, except it handles the
long description. It is important to note that if the second argument
is a string, that the state of the door will be added onto the long
description automatically. In other words \"It is open.\" will appear
as the second line. This will *not* be done if you use a function for
your long description.
*****
SetKeys()
*****
string *SetKeys(string side, string *keys)
Example: SetKeys(\"east\", ({ \"skeleton key\", \"special key\" }));
Builds an array of id's which can be used to unlock the door if it is
lockable from this side. In other words, a person can only unlock the
door if that person has an object which has one of the id's you
specify for its id.
b. Events
*****
eventOpen()
*****
varargs int eventOpen(object by, object agent)
Examples:
\"/realms/descartes/etc/red_door\"->eventOpen(this_object());
int eventOpen(object by, object agent) {
if( query_night() ) return 0; /* Can't open it at night */
else return door::eventOpen(by, agent);
}
The function that actually allows the door to be opened externally.
It returns 1 if the door is successfully opened. It returns 0 if it
fails. The first argument is the room object from which the door is
being opened. The second argument, which is optional, is the living
thing responsible for opening the door.
The first example above is an example of what you might do from
reset() inside a room in order to have the door start open at every
reset.
The second example above is an example of how you might conditionally
prevent the door from opening by overriding the Open event. In this
case, if it is night, you cannot open this door. If it is day, you
can.
*****
eventClose()
*****
varargs int eventClose(object by, object agent)
Example: See eventOpen()
This function works just like eventOpen(), except it does the closing
of the door.
*****
eventLock()
*****
varargs int eventLock(object by, object agent)
Example: see eventOpen()
This function works just like eventOpen(), except that it gets called
for locking the door.
*****
eventUnlock()
*****
varargs int eventUnlock(object by, object agent)
Example: See eventOpen()
This function works just like eventOpen(), except that it gets called
for unlocking the door.
",({"chapter 33","chapter thirty-three","33",}):"chapter 33 \"QCS: Creation\"
Creation breaks down into three categories. \"Things\", rooms, and
doors. Let's look at things first:
Thing creation:
---------------
In this category we're including NPC's, weapon, armor, unspecial items,
tables, furniture, books and containers. Basically things that actually show
up somewhere. A door is a very special sort of object and does not fall
under this category.
To make a new thing, the syntax looks like this:
create THING FILENAME
The THING is the category of item we are creating. The FILENAME is
the name of the file that will contain this item's configuration data.
You may enter an absolute path, or you may simply enter a filename. If
you enter a filename, QCS will attempt to figure out the best place
for this file. If your current working directory has a name QCS understands
as being compatible with the type of object you are making (for example,
your cwd is /realms/you/npc and you are making an NPC) it will use that.
Otherwise it will search parent and children directories for a
directory name it understands this way. Finally, if it can find no
compatible directories for your file near your cwd, it will put it in
the appropriate directory in your home area (in this case,
/realms/you/area/npc ).
Avoid a relative path. It probably won't work the way you think.
When the command completes, FILENAME will be a file containing the
data for your generic thing, and a copy of that generic thing will appear
in the room you are in.
If, for example, you entered:
%^GREEN%^create npc cowboy%^RESET%^
The room you are in will contain a generic NPC which *does not*
answer to the id of \"cowboy\". This NPC is just a generic NPC whose
filename is (probably) /realms/you/npc/cowboy.c and isn't yet a real
cowboy. You'll need to use the \"modify\" command to make a proper
cowboy out of him.
Room creation
-------------
Naturally, if you create a room, a new room will not appear inside
your current environment. Instead, the syntax of the \"create\" command
is different when you want to create a new room.
You may have noticed that you can't use the \"create\" command to make
a new room adjacent to your workroom. This is for your protection and
my sanity. Files which contain the directive \"SetNoModify(1)\" are
immune to QCS manipulation.
Rooms like your workroom, the default start room, the void room, etc,
are set nomodify. This is because if you screw it up, you will be
sorry, and I just don't want to hear it.
So, suppose then that you're in your sample room (one room east
of your workroom) and you want to make a new room. You might issue
the following command:
%^GREEN%^create room south testroom1%^RESET%^
What this does is copy the room you are in (in this case,
/realms/you/area/sample_room.c) to a new location (perhaps
/realms/you/area/testroom1.c). But the neat thing is, this new room
does not have the same exits as the room you are in. The new room
has just one exit, leading back to where you are.
The net effect of all this is that when you issue this command,
you make a new room in the direction you specify, and this
new room looks just like the room you're in, only the exits are such
that you can travel back and forth between the rooms.
Door creation
-------------
Doors are funny things. In Dead Souls, they aren't objects in the
conventional sense of a thing which occupies a room you're in. Rather,
they are really daemons which attach to adjoining rooms. If that doesn't
make sense to you, don't worry. You're not alone.
The syntax for door creation is much like that for room creation.
After you create that room south of your sample room, you can now create
a door between them:
%^GREEN%^create door south sample_door%^RESET%^
This brings into existence a generic door (closed by default) which
is between the two rooms.
",({"chapter 27","chapter twenty-seven","27",}):"chapter 27 \"Towns\"
Building Towns
The Nightmare IV LPC Library
written by Descartes of Borg 950429
The Nightmare IV LPC Library contains support for towns, which is in
fact very minimal from the mudlib level. If, however, you wish to
structure your MUD to be centered around the concept as Nightmare LPMud
is, then you need to understand how to build a town. This document
describes the building of towns using the Nightmare IV LPC Library.
I. What Is a Town?
A town is simply a collection of rooms which have the same value set
for Town. If done poorly, this is all it is. If done right, however,
a town becomes the center of the games' social structure. If you
decide to build a town in your area, the first thing you need to do is
isolate it. All towns should be surrounded by vast, vast areas of
wilderness of some sort. This may mean desert, forest, jungle, or
whatever. You may or may not want to have a road which links it to
the rest of civilization.
Rooms are considered \"wilderness\" by default. That is, if you never
set the town in them, they are considered wilderness. To make a room
part of a town, you need to call SetTown() from create() of the room:
SetTown(\"Praxis\");
Capitalize your town name properly.
Next you need to decide how many estates may be built in the room.
Ideally, towns are expanding and changing things. Upper level players
have the ability to build estates in their home towns. Of course, ten
estates in one room is crowded. Generally you should limit the number
of estates to what would logically fit in a given room. For example,
if you are on a road at the edge of town with nothing about, then
allowing two estates makes sense. On the other hand, in the middle of
an intersection of two roads, there is hardly any room for an estate
to be built. To allow estates to be built in a room:
SetProperty(\"estates\", 2);
This allows two estates to be built off of this room.
As stated above, towns are expanding. This is why they should be
situated far apart. Too close together it is hard for them to expand
without changing the overall map of the game. Therefore, when your
town has gotten as full as can be handled, then you simply move to
outlying rooms and make them part of the town by setting their town.
In addition, give them the capacity for estates. Do not forget to
change room descriptions and allow for needed roads!
II. What do I put in towns?
The first section described what is minimally needed for a town from a
code point of view. This section describes what sorts of things you
should put in your towns. Most are optional, however, you do need to
add something called an adventurer's hall. An adventurer's hall is
the default start room for the town for anyone who chooses the town as
their home town. In order to make it their home town, they go to the
adventurer's hall and pay a fee (generally determined by approval) to
move to this town. Until that person builds an estate in the town,
the adventurer's hall is their default starting point.
Beyond that, the only other thing required is a real estate office for
selling estates. This is an inheritable from /lib/sales.c
(LIB_SALES). Approval determines what your local land value is, and
you fill in the descriptions. For information on advanced coding of
sales offices, see the document /doc/build/Sales.
Nothing else is required. Of course, your land value (the amount
people pay to live and build in your town) is determined by the sorts
of services your town offers. No town should offer all services. And
certainly, the services your town offers should reflect the nature of
the region in which you are building. Are you an isolated, small
town? Then few services will be available. Are you a central, large
town? Then a majority of services should be available.
Services include:
shops of different types
bars and pubs
restaurants
libraries for learning languages
class halls
town council rooms
This list will probably expand over time, but it provides a good
starting point for common services.
Descartes of Borg
borg@imaginary.com
",({"chapter 28","chapter twenty-eight","28",}):"chapter 28 \"Vendors\"
Building Store Vendors
The Nightmare IV LPC Library
written by Descartes of Borg 950528
This document details the creation of vendor objects, NPC's which buy
and sell items. Note that vendors are NPC's, so everything in the
document on building NPC's applies to vendors. It is recommended that
you be completely familiar with that document before moving on to this
one.
Building vendors is actually quite simple, with very little required
beyond the NPC requirements. In fact, only the following function
calls are unique to vendors:
string SetLocalCurrency(string currency);
string SetStorageRoom(string room);
int SetMaxItems(int num);
int SetVendorType(int vt);
One special note, however, is that the skill \"bargaining\" is extremely
important to vendors. Namely, the higher the bargaining, the harder
it is for players to get decent prices.
*****
string SetLocalCurrency(string curr);
*****
Example: SetLocalCurrency(\"electrum\");
Sets the currency which the vendor will use for doing business. The
currencies should be approved by the approval team.
*****
string SetStorageRoom(string room);
*****
Example: SetStorageRoom(\"/domains/Praxis/horace_storage\");
Identifies the file name of the room in which the vendor will be
storing items for sale. This room should never be accessible to
players.
*****
int SetMaxItems(int num);
*****
Example: SetMaxItems(60);
Sets the maximum number of items a vendor can keep in storage at any
given time. Refer to approval documentation for proper numbers for
this.
*****
int SetVendorType(int type);
*****
Examples:
SetVendorType(VT_WEAPON);
SetVendorType(VT_ARMOUR | VT_WEAPON);
Sets which types of items a vendor will buy and sell. A list of all
vendor types is in /include/vendor_types.h. You may allow a vendor to
sell multiple types using the | operator.
",({"chapter 32","chapter thirty-two","32",}):"chapter 32 \"QCS: Commands\"
What with muds being text only, QCS has no fancy windowing system.
Using a menu-driven creation system was ruled out quickly due to the
vast complexity of the menus that would be required. Instead, QCS
relies on a few powerful commands.
create
This is the command that gets the ball rolling. This command
is what lets you bring a new thing into existence. The things you can
create can be seen by typing \"help create\". Examples are rooms, weapons,
doors, and so on. We will be reviewing each of those in later chapters.
When you issue this command a generic version of the item you
wish to create appears (or, in the case of a room, appears in the
direction you specify). Once that generic copy materializes, you can
change it to suit your needs using the \"modify\" command.
modify
I tend to regard this command as the heart and soul of QCS. It's
this tool that lets you make your world your own. Your new generic
things are not useful or fun until you modify them. A \"generic weapon\"
isn't very interesting, but a \"mithrilite poleaxe\" might be just the
thing to deal with a pesky dragon.
add
Creatures, rooms, and containers are capable of storing other
things. Once you make an ogre, you may want to give him a hammer to
wield. After you make that hammer, you use the add command to let
the ogre have that wepon in his permanent inventory.
delete
On the other hand, you may be tired of that ogre after a while. If
he is a part of the permanent inventory of a room, you can use the
delete command to remove him permanently. Or if you'd rather he have
a Kill-O-Zap Frogstar blaster rather than a hammer, get rid of the
hammer in his inventory with this command.
copy
This is a room-specific command. Rather than write multiple,
nearly identical rooms for large areas, you can use the copy command to
make the room you are almost exactly like any other room you choose,
except for the exits, which remain the same. Handy for big forests,
cell-blocks, twisty mazes of little passages, etc.
initfix
If a thing isn't working right, try to initfix it. \"init()\" is
an object function that many items need in order to work properly. If
you've run into something that is behaving unexpectedly, run initfix on
it. The trouble just might clear up.
",({"chapter 26","chapter twenty-six","26",}):"chapter 26 \"Sentients\"
Building Sentient Non-Player Characters
The Nightmare IV Object Library
written by Descartes of Borg 951127
One thing most everyone wants to see are monsters that react more
intelligently to user input. The fact is, however, that most monsters
in the game only need a small, basic behaviour set. Nevertheless, in
order to make an area interesting, there should be some monsters which
stand out as unique and purposeful. The problem about building such
monsters is that they use a lot of processing time.
In order to make sure most monsters which do not need such
intelligence do not waste processing time on such activities, the
Nightmare Object Library separates non-player characters into two
classes: dumb monsters, which are basic mindless automata and
sentients, monsters which react more intelligently to their
environment.
This document describes sentients. Before looking at this document,
it is highly recommended that you be familiar with the document
/doc/build/NPC which details non-player characters. Sentients are
non-player characters, so everthing which applies to non-player
characters also applies to sentients.
*****
Currently, a few basic behaviours distinguish sentients from normal
npcs. Those behaviours are the ability to intelligently move about
the mud and to react to user speech. Nightmare thus provides the
following functions to allow you to easily have an sentient enact
those behaviours:
mapping SetTalkResponses(mapping mp);
mixed AddTalkResponse(string str, mixed val);
int RemoveTalkResponse(string str);
mapping SetCommandResponses(mapping mp);
mixed AddCommandResponse(string str, mixed val);
int RemoveCommandResponse(string str);
varargs int SetWander(int speed, string *path, int recurse);
string *SetWanderPath(string *path);
int SetWanderRecurse(int x);
int SetWanderSpeed(int x);
*****
Making NPCs react to user speech
You may want to have NPCs react to things players say. To that end,
the following functions exist:
mapping SetTalkResponses(mapping mp);
mixed AddTalkResponse(string str, mixed val);
int RemoveTalkResponse(string str);
Function: mapping SetTalkResponses(mapping mp)
Example: SetTalkResponses( ([ \"square\" : \"The square is east of here.\",
\"house\" : \"Isn't that an ugly house?\" ]) );
This function allows you to set a list of responses to given phrases.
For example, if you put this code in a sentient and a player said
\"Where is the square?\" or \"Your frog is certainly square.\", your NPC
would have said \"The square is east of here.\". Note therefore that
the NPC is only looking for the keys you place in there. You could
have restricted it to \"where is the square\" instead of \"square\", but
then someone asking \"Where's the square\" would be missed.
Also note that phrases should be in lower case. It will match to
upper case words automatically.
Finally, you can either give a string or a function as the match to a
phrase. If the match is a string, the NPC simply says the string in
the NPC's native tongue. If, however, the match is a function, that
function will get called.
*****
Function: mixed AddTalkResponse(string str, mixed val);
Example: AddTalkResponse(\"in the house\", (: HouseFunc :));
Matches an individual phrase to a string or function. As with
SetTalkResponses(), if the match is a string, the NPC simply says the
string in response to the phrase. If it is a function, that function
gets called.
*****
Function: int RemoveTalkResponse(string str);
Example: RemoveTalkResponse(\"house\");
Removes the previous set or added talk response from the NPC.
*****
Making NPCs react to user directives
Nightmare supports a special command, the \"ask\" command. A player may
use the ask command to ask an NPC to perform a certain task. For
example, \"ask the healer to mend my right leg\". There is a special
event in NPC's which responds to this called eventAsk(). In order to
make responding to this easier, however, Nightmare has the
CommandResponse functions. The command response functions allow NPC's
to respond based on commands, like \"mend\".
*****
Function: mapping SetCommandResponses(mapping mp);
Example: SetCommandResponses( ([ \"heal\", \"I cannot heal people\" ]) );
Allows you to match commands to either strings or functions. Matched
functions get called with the command as the first argument, and
command arguments as the second argument. For example, if you had:
SetCommandResponses(\"give\", (: give :));
Your give() function would get called with \"give\" as the first
argument and \"me the sword\" as the second argument in response to a
player issuing the command \"ask the monster to give me the sword\".
*****
Function: mixed AddCommandResponse(string str, mixed val);
Example: AddCommandResponse(\"give\", (: give :));
This allows you to add to the list of commands to which the NPC
responds. The NPC responds to those commands as outlined for
SetCommandResponses().
*****
Function: int RemoveCommandResponse(string str);
Example: RemoveCommandResponse(\"give\")
Removes a previously set command response.
*****
Making NPCs move about the game intelligently
A sticky subject on most muds is that of wandering monsters. When
done poorly, they can waste resources to a great degree. Nightmare,
however, works to avoid wasting resources while getting the most out
of allowing monsters to move about.
Nightmare supports two types of wandering monsters: those which have
pre-determined paths and others which are true wanderers. True
wanderers, those who simply randomly choose paths are subject to the
following restrictions:
They may not move into rooms not yet loaded in memory.
They will not try to open closed doors.
The first restriction is the most important to note. This means that
the NPC will not wander into rooms that have not been recently visited
by some player. This avoids the problem NPCs cause on many muds of
uselessly loading rooms that only the monster will ever see.
Monsters given specific paths to wander are not subject to the above
restrictions. Of course, they cannot wander through closed doors.
But you can make part of their path to open a closed door. In
addition, since such monsters have very specific sets of rooms into
which they can travel, they are not in danger of needlessly loading a
zillion rooms.
*****
Function: varargs int SetWander(int speed, string *path, int recurse);
Examples:
SetWander(5);
SetWander(5, ({ \"go north\", \"open door\", \"enter hut\", \"go west\" }));
SetWander(5, ({ \"go north\", \"open door\", \"enter hut\", \"go west\",
\"go south\" }), 1);
This is the function you will almost always use in create() to make a
sentient wander. Only one of the three possible arguments is
mandatory, that being the speed. The speed is simply the number of
heart beats between attempts to move. Thus, the higher the number,
the slower the movement of the monster.
The second argument, if given, is a list of commands which will be
executed in order by the monster. If it is not given, the monster
will be assumed to be a true wanderer. In other words, the first time
the monster tries to wander, the monster will \"go north\". The second
time, he will \"open door\". The third, he will \"enter hut\", etc.
The third argument is either 1 or 0. If 1, that means once the
monster has completed the path, it will use the first command in the
list the next time it tries to wander. If 0, it will cease to issue
commands once it has cycled through the list.
You might note that between the time the above monster opens the door
and enters the hut, somebody could come along and shut the door. How
can you deal with that? You could do:
SetWander(5, ({ \"go north\", ({ \"open door\", \"enter hut\" }) }));
You will notice here that the second member of the command array is
itself an array instead of a string. In that case, all members of
that array get executed as part of that wander. In this case it helps
make sure no one closes the door between when the monster tries to
open it and when it tries to pass through the door.
For even more flexibility, you can make elements of the array into
functions. Instead of executing a command in a wander turn, the
function you provide instead gets called. For example:
SetWander(5, ({ \"go north\", (: kill_anyone :), \"go south\" }), 1);
Where the function kill_anyone() has the monster kill any players in
that room. Thus, this monster sits in its room and occasionally pops
its head one room to the north to kill anyone sitting there.
*****
Function: string *SetWanderPath(string *path);
Example: SetWanderPath(({ \"go north\", \"go south\" }))
Allows you to set the monster's wander path independent of other
settings. The wander path will never get executed, however, unless
the monster's wander speed is greater than 0.
*****
Function: int SetWanderRecurse(int x);
Example: SetWanderRecurse(1);
Allows you to make the monster's wander path recurse independent of
other settings. This is meaningless, however, unless the monster's
wander speed is greater than 0 and a wander path is set for it.
*****
Function: int SetWanderSpeed(int x);
Example: SetWanderSpeed(5);
Allows you to set the monster's wander speed independent of other
settings. This is NOT the same as SetWander(5). SetWander() will
clear out any previous wander path and wander recurse settings. This
function has no effect on the monster's wander path or wander recurse.
",({"chapter 5","chapter five","5",}):"chapter 5 \"The Basics of Inheritance\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 01 july 1993
CHAPTER 5: The Basics of Inheritance
5.1 Review
You should now understand the basic workings of functions. You should be
able to declare and call one. In addition, you should be able to recognize
function definitions, although, if this is your first experience with LPC,
it is unlikely that you will as yet be able to define your own functions.
There functions form the basic building blocks of LPC objects. Code
in them is executed when another function makes a call to them. In making
a call, input is passed from the calling function into the execution of
the called one. The called function then executes and returns a value
of a certain data type to the calling function. Functions which return
no value are of type void.
After examining your workroom code, it might look something like this
(depending on the mudlib):
-----
inherit \"/std/room\";
void create() {
::create();
SetProperty(\"light\", 2);
SetProperty(\"indoors\", 1);
set(\"short\", \"Descartes' Workroom\");
set(\"long\", \"This is where Descartes works.\\nIt is a cube.\\n\");
SetExits( ({ \"/domains/standard/square\" }), ({ \"square\" }) );
}
-----
If you understand the entire textbook to this point, you should recognize
of the code the following:
1) create() is the definition of a function (hey! he did not declare it)
2) It makes calls to SetProperty(), set(), and SetExits(), none
of which are declared or defined in the code.
3) There is a line at the top that is no variable or function declaration
nor is it a function definition!
This chapter will seek to answer the questions that should be in your head
at this point:
1) Why is there no declaration of create()?
2) Where are the functions SetProperty(), set(), and SetExits() declared
and defined?
3) What the hell is that line at the top of the file?
5.2 Object oriented programming
Inheritance is one of the properties which define true object oriented
programming (OOP). It allows you to create generic code which can be used
in many different ways by many different programs. What a mudlib does is
create these generalized files (objects) which you use to make very specific
objects.
If you had to write the code necessary for you to define the workroom above,
you would have to write about 1000 lines of code to get all the functionality
of the room above. Clearly that is a waste of disk space. In addition,
such code does not interact well with players and other rooms since every
creator is making up his or her own functions to perform the functionality
of a room. Thus, what you might use to write out the room's long description,
GetLong(), another wizard might be calling long(). This is the primary
reason mudlibs are not compatible, since they use different protocols for
object interaction.
OOP overcomes these problems. In the above workroom, you inherit the
functions already defined in a file called \"/std/room.c\". It has all
the functions which are commonly needed by all rooms defined in it. When
you get to make a specific room, you are taking the general functionality
of that room file and making a unique room by adding your own function,
create().
5.3 How inheritance works
As you might have guessed by now, the line:
-----
inherit \"/std/room\";
-----
has you inherit the functionality of the room \"/std/room.c\". By inheriting
the functionality, it means that you can use the functions which have
been declared and defined in the file \"/std/room.c\" In the Nightmare Mudlib,
\"/std/room.c\" has, among other functions, SetProperty(), set(), and
SetExits() declared and defined. In your function create(), you are
making calls to those functions in order to set values you want your
room to start with. These values make your room different from others, yet
able to interact well with other objects in memory.
In actual practice, each mudlib is different, and thus requires you to use
a different set of standard functions, often to do the same thing. It is
therefore beyond the scope of this textbook even to describe what
functions exist and what they do. If your mudlib is well documented,
however, then (probably in /doc/build) you will have tutorials on how
to use the inheritable files to create such objects. These tutorials
should tell you what functions exist, what input they take, the data
type of their output, and what they do.
5.4 Chapter summary
This is far from a complete explanation of the complex subject of inheritance.
The idea here is for you to be able to understand how to use inheritance in
creating your objects. A full discussion will follow in a later textbook.
Right now you should know the following:
1) Each mudlib has a library of generic objects with their own general
functions used by creators through inheritance to make coding objects
easier and to make interaction between objects smoother.
2) The functions in the inheritable files of a mudlib vary from mudlib
to mudlib. There should exist documentation on your mud on how to
use each inheritable file. If you are unaware what functions are
available, then there is simply no way for you to use them. Always
pay special attention to the data types of the input and the data
types of ay output.
3) You inherit the functionality of another object through the line:
-----
inherit \"filename\";
-----
where filename is the name of the file of the object to be inherited.
This line goes at the beginning of your code.
Note:
You may see the syntax ::create() or ::init() or ::reset() in places.
You do not need fully to understand at this point the full nuances of this,
but you should have a clue as to what it is. The \"::\" operator is a way
to call a function specifically in an inherited object (called the scope
resolution operator). For instance, most muds' room.c has a function
called create(). When you inherit room.c and configure it, you are doing
what is called overriding the create() function in room.c. This means
that whenever ANYTHING calls create(), it will call *your* version and not
the one in room.c. However, there may be important stuff in the room.c
version of create(). The :: operator allows you to call the create() in
room.c instead of your create().
An example:
-----
#1
inherit \"/std/room\";
void create() { create(); }
-----
-----
#2
inherit \"/std/room\";
void create() { ::create(); }
-----
Example 1 is a horror. When loaded, the driver calls create(), and then
create() calls create(), which calls create(), which calls create()...
In other words, all create() does is keep calling itself until the driver
detects a too deep recursion and exits.
Example 2 is basically just a waste of RAM, as it is no different from room.c
functionally. With it, the driver calls its create(), which in turn calls
::create(), the create() in room.c. Otherwise it is functionally
exactly the same as room.c.
",({"chapter 30","chapter thirty","30",}):"chapter 30 \"The Natural Language Parser\"
The Natural Language Parser
The Problem
A gut reaction to this entire system is to be overwhelmed by its apparent complexity, comparing it to the good-old days of using add_action(). A discussion of the natural language parsing system therefore needs to start by answering the question, \"Why bother?\".
The old way of handling user input was to define commands and tie them to functions using add_action. Each user object kept track which commands it had available to it, and of which functions each command would trigger. The list of commands changed each time the player object (really, any object which had enable_commands() called in it) moved. In addition, each time an object moved inside the player object or left its inventory, this list changed.
This led to two basic problems:
1. The same command might have slightly different uses in different parts of the mud. Or worse, the player could have two similar objects which define the same command in slightly different ways.
2. The complexity of syntax varied based on creator abilities and was limited by CPU.
For example, one creator could have created a rock in their area that added the 'throw' command. This creator is a newbie creator, and thus simply put the following code in their throw_rock() function:
int throw_rock(string str) {
object ob;
if( !str ) return 0;
ob = present(str, this_player());
if( !ob ) {
notify_fail(\"You have no rock!\");
return 0;
}
if( ob != this_object() ) return 0;
if( (int)ob->move(environment(this_player())) != MOVE_OK ) {
write(\"You cannot throw it for some reason.\");
return 1;
}
write(\"You throw the rock.\");
say((string)this_player()->query_cap_name() + \" throws the rock.\");
return 1;
}
In this case, \"throw rock\" will work, but \"throw the granite rock at tommy\" will not. But another creator also defined a throw command in their spear. This creator however, is a good coder and codes their throw_spear() command to handle 'throw the red spear at tommy' as well as 'throw spear'. Explain to a player why both syntaxes only work for the spear, and not for the rock. Then explain to that player why 'throw rock' for a rock built in yet another area yields 'What?'.
An early attempt to get around this problem was the parse_command(). Unfortunately it was buggy spaghetti that was way too complex for anyone to understand. The MudOS attempt to solve this problem is its new natural language command parser. The parser is based on the following assumptions:
All commands should behave in a consistent manner across the mud.
Similar objects should respond as expected to the same command line.
A player should only see 'What?' (or its equivalent) when they make a typo.
It should enable creators to handle the complex command processing required by the above assumption.
Overview of the MudOS System
The MudOS natural language parser is based on a philosophy of centralized command parsing. In other words, creators have no control over which commands exist nor over what syntax rules existing commands follow. Instead, creators are tasked with defining what those commands mean to their objects. Unlike with add_action() where commands are registered to the driver, the MudOS system registers verbs (the command) and rules with the driver. In this example, a simple \"smile\" verb is registered with a single rule, \"at LIV\".
With the old way of doing things, commands were executed either when a player entered in a command or when the command() efun was called. With this new system, a command may be executed at any time via either the parse_sentence() or parse_my_rules() efuns.
When one of those efuns is called, the driver searches through the list of verbs for a verb and rule which matches the command string. In order to do that, however, it needs to make several calls of all the objects involved to determine what the sentence means. For any given command string, the following objects are relevant to the parsing of that command string:
the verb handler
This object contains the functions used to see if the command is valid and to execute it. It is also the one that creates the rules for the verb.
the subject
This is the object from which parse_sentence() is called, or the object mentioned as the first argument in parse_my_rules(). It is the object considered to be executing the command in questin, the logical subject of the sentence.
the master object
This object keeps track of global information, such was what literals (mistakenly referred to as prepositions) exist across the mud. In general, a literal is a preposition.
the direct object
This is not the logical direct object of the sentence. Rather, this is the first object at which the verb action is targetted.
the indirect object
Again, this is not the logical indirect object of the sentence. Rather, it is the second object at which the verb action is targetted. For example, in \"give the book to the elf\", the elf will be both the logical indirect object and the parser indirect object. But if you allow \"give the elf the book\", the elf naturally still remains the logical indirect object, but the book is the indirect object to the parser since it is the second object targetted by the verb (the first being the elf).
Each object involved in he parsing of a sentence, except the subject, is responsible for handling certain driver applies that help the driver in parsing the sentence. The subject, unlike the other objects, is responsible for initiating the command. Although this document treats all of these objects as if they were completely distinct, it is possible to have the same object performing multiple roles for the same command. For example, your subject could also be direct object and verb handler. The next section discusses the objects and the applies they are responsible for in detail.
The Objects
Before studying each object in detail, it is important to keep in mind that each object which can be involved in any role must call parse_init() before doing anything related to verb parsing. The only exception is the master object.
The subject
The subject is simply the initiator of a command. A command is typically initiated by a call to parse_sentence() inside the object's process_input() apply. This example shows how a player object might use parse_sentence() to initiate a command. This efun will return 1 if the command successfully matched a known verb and rule and successfully executed it. If it found the verb in question, but it did not match any rule, then 0 is returned. If it found the verb in question and matched a rule, but the execution of the rule failed, it will return an errorstring describing why it failed. Finally, if no verb matched the command, then -1 is returned.
Take for example a mud with this one rule:
parse_add_rule(\"smile\", \"at LIV\")
The efun parse_sentence() would return the following values for the following command lines:
smile at descartes
Returns: 1
smile happily
Returns: 0
smile at the red box
Returns: \"The Box is not a living thing!\"
eat the red box
Returns: -1
The master object
The master object is responsible for a single apply, parse_command_prepos_list(). This apply returns a list of literal strings which may be used in a rule. A literal string is simply one that appears in the rule exactly as a player types it. In the smile example above, \"at\" is a literal. In most all cases, literals are prepositions, thus the name of the apply.
The verb handler
The verb handler object is responsible for setting up the rules for a verb and handling the test and execution of those rules. This example demonstrates a simple verb handler for the smile verb described above. As you can see, each rule is divided up into three parts:
1. initialization
2. testing
3. execution
The intialization is the call to parse_add_rule(). This associates a rule with a verb. The first argument is the verb (this verb may have spaces in it, like \"look at\") and the second argument is one of the rules being handled by this verb handler. This list defines the valid tokens for a rule.
The testing portion is a \"can\" apply. In testing a rule, the driver calls the applies can_<verb_rule> to determine if the execution of the verb even makes sense in this situation. The test apply is called when the driver has valid arguments to a rule, but it wants to see if those valid arguments make sense right now. For example, you might check a player here to see if they have enough magic points for casting the spell this verb/rule represents. If not, you might return \"You are too tired right now.\". If the rule match up in question makes completely no sense at all, like for example, they tried to throw a house for the (\"throw\", \"OBJ\") rule, you should return 0. The parser will guess well an error message from the situation. In this case, it will have parse_sentence() return \"You cannot throw the thing.\".
Finally execution is where the verb actually happens. You know you have a verb/rule match and you know all the arguments to it are valid. Now it is time to do something. You almost always want to return 1 from this function except in extreme circumstances.
The direct and indirect objects
As stated above, the directness or indirectness of an object has nothing to do with the linguistic meaning of those terms. Instead it has to do with what position in the token list the object takes. The direct object is the first object in the token list, and the indirect object is the second object in the token list.
These objects basically answer the question \"Can I be the direct/indirect object for this verb and rule?\". Like the testing and execution applies for verb handlers, the applies that answer this question may return 1, 0, or an error string. Also like the testing and execution applies for the verb handler, these come in the form of (in)direct_<verb>_<rule>(). This example is from somewhere inside the living object heirarchy. Note the is_living() apply which lets the parser know that the object matches a LIV token.
Inventory visibility
Some objects are subjects, direct objects, or indirect objects of verbs which require access to things in their inventory. For example, living things, bags, chests, etc. all need to allow other things access to their inventories for some commands. Two applies handle this situation:
1. inventory_accessible()
2. inventory_visible()
The first returns 1 if verbs can have access to an object's inventory, the second returns 1 if verbs simply can take into account an object's inventory. An example of the difference might be a glass chest. When closed, you want its inventory to be visible, but not accessible.
It is important to remember that is the return value for any of these special applies, including is_living(), you need to make an explicit call to the parse_refresh() efun. Unless the parse_refresh() efun is called, these special applies are only called once with no guarantee as to when that one call will actually occur.
Creating a New Verb
Currently, the Lima and Nightmare mudlibs use this parser system. Both mudlibs provide inheritable objects which make it simpler to interface with the MudOS parser system. Nightmare specifically has the inheritable LIB_VERB with methods for defining a new verb.
This verb example comes from the Nightmare mudlib. The simple Nightmare verb requires the following steps:
1. Name the verb
2. State the verb rules
3. Name any synonyms
4. Set an error message for display when the command is wrongly used
5. Create help text for the command
Naming the verb is done through the SetVerb() method. You simply specify the name of the verb.
The rules are passed to the SetRules() method. You may specify as many rules as are needed for the verb.
Like rules, synonyms are set as a list for the SetSynonyms() method. A synonym is simply any verb which is exactly synonymous with any possible rule for the verb in question. The player is able to access help for the verb and get error messages for the verb through the verb or any of its synonyms.
The error message is a string displayed to the user when they use the verb in an incorrect manner. For example, if I typed 'eat' when the rule is 'eat OBJ', the error message would be 'Eat what?'.
Finally, like with any object, the help text can be set through the SetHelp() method. Help is very important for verbs.
All of these methods only are able to take care of verb initalization. It is up to the verb creator to give meaning to a new verb. This is done first off by writing can_*() and do_*() applies in the verb handler. These methods should be very simplistic in nature. For example, a can method almost always simply returns 1. A do method generally finds its target and triggers some sort of event in that object. The event does the real command handling.
In addition to can and do applies, you need also to write any direct and indirect applies in approperiate objects. Nightmare centralizes this sort of processing through inheritables geared towards responding to particular verbs. A good example of this is LIB_PRESS which responds to the \"press\" command. Thus any object which should be pressable needs only to inherit this object to become a pressable object.
The can, do, direct, and indirect applies all have the same argument set for the same verb/rule pair, but it is important to know when the parser knows certan things. Take for example the verb/rule \"press OBJ on OBJ\". The parser takes the following actions:
1. Call can_press_obj_on_obj() in verb handler
2. Call direct_press_obj_on_obj() in all accessible and visible objects
3. Call indirect_press_obj_on_obj() in all accessible and visible objects
4. Call do_press_obj_on_obj() in the verb handler
The arguments to all methods called in this process are:
1. object direct_object
2. object indirect_object
3. string direct_object_as_entered_on_command_line
4. string indirect_object_as_entered_on_command_line
But how can can_press_obj_on_obj() know what the direct and indirect objects are if they have not been identified yet? The answer is that it cannot. For the command \"push the button on the wall\", in a room with me and you in it and we carry nothing, the sequence looks like this (return in parens):
1. verb->can_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (1)
2. me->direct_press_obj_on_obj(0, 0, \"the button\", the wall\"); (0)
3. you->direct_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (0)
4. room->direct_press_obj_on_obj(0, 0, \"the button\", \"the wall\"); (1)
5. me->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (0)
6. you->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (0)
7. room->indirect_press_obj_on_obj(room, 0, \"the button\", \"the wall\"); (1)
8. verb->do_press_obj_on_obj(room, room, \"the buton\", \"the wall\"); (1)
This assumes, of course, that the room responds positively with the id's \"button\" and \"wall\".
People familiar with the parser might say, \"Hey, wait, there is a lot more that happens than just that.\" In fact, there are many more possible permutations of this sequence. The most interesting is the ability to simply ignore the difference between prepositions like \"in\" and \"into\" which are often used interchangeably in colloquial speech. For example, if you had \"put OBJ in OBJ\" and \"put OBJ into OBJ\" verb/rules, you could handle them in a single place for each of the applies respectively liek this:
can_put_obj_word_obj()
direct_put_obj_word_obj()
indirect_put_obj_word_obj()
do_put_obj_word_obj()
If the parser found no can_put_obj_in_obj() defined, it then searches for a more generic handler, can_put_obj_word_obj(). In fact the real order it searches for a can handler is:
1. can_put_obj_in_obj()
2. can_put_obj_word_obj()
3. can_put_rule()
4. can_verb_rule()
---------------------
Last example:
static void create() {
parse_init();
parse_add_rule(\"smile\", \"at LIV\");
}
mixed can_smile_at_liv(object target) {
return 1;
}
mixed do_smile_at_liv(object target) {
previous_object()->eventPrint(\"You smile at \" +
(string)target->GetName() + \".\");
target->eventPrint((string)previous_object()->GetName() +
\" smiles at you.\");
return 1;
}
",({"chapter 22","chapter twenty-two","22",}):"chapter 22 \"NPCs\"
Building Non-Player Characters
The Nightmare IV Object Library
written by Descartes of Borg 951201
This document outlines the creation of non-player characters (NPC's).
On other muds, NPC's are sometimes referred to as monsters. Like the
rooms document, this document is divided up into two sections: basic
NPC building and complex NPC building. NPC's are living things which
inherit all the behaviours of living things. Documentation on living
specific functionality may be found in /doc/build/Livings.
************************************************
Part 1: Basic NPC Building
************************************************
*****
I. The simplest NPC
*****
#include <lib.h>
inherit LIB_NPC;
static void create() {
npc::create();
SetKeyName(\"praxis peasant\");
SetId( ({ \"peasant\", \"praxis peasant\" }) );
SetShort(\"a local peasant\");
SetLong(\"Dirty and totally disheveled, this poor inhabitant of Praxis \"
\"still somehow maintains an air of dignity that nothing can \"
\"break.\");
SetLevel(1);
SetRace(\"elf\");
SetClass(\"fighter\");
SetGender(\"male\");
}
There are two things you should note. The first is that an NPC is
also a general object, meaning that you have available to you all the
things you can do with general objects, like setting descriptions and
ID's. The second is that a basic NPC does not require a heck of a lot
more. I will cover the NPC specific functions here.
SetLevel(1)
SetRace(\"elf\")
SetClass(\"fighter\")
Level, race, and class are the three most important settings in any
NPC. Together they determine how powerful the NPC is. You are
absolutely required to set a level and a race. For those who
absolutely do not want to give the NPC a class, you do not have to.
But, you must instead manually set the NPC's skill levels, which is
described in the second part of this document. In general, however,
you always want to set the class.
Together, the class and race and level determine which skills and
stats are considered important for the monster, and how good at those
skills and stats the monster is. The order in which you call these
functions is irrelevant, as everything is recalculated any time one of
the above changes.
Also, note that SetRace() may only be called with a race listed in the
mraces command with simple NPC's. If you wish to build an NPC with a
unique race, you need to do some limb manipulation, which is described
in the advanced section.
SetGender(\"male\")
While not required, you will normally want to give an NPC a gender.
The default is neutral. However, in this world, very little is
neuter. Your choices for this function are male, female, and neuter.
*****
II. Other NPC Configuration Functions
*****
Function: int SetMorality(int amount);
Example: SetMorality(100);
This is a number between -2000 and 2000 which determines the morality
of an individual with respect to good and evil. -2000 is absolute
evil, and 2000 is absolute good. The actions of players determine
their morality, and often those actions are relative to a target.
Thus killing an evil being can be considered good, while killing a bad
one evil.
Function: int SetUnique(int x);
Example: SetUnique(1)
Marks the NPC as a unique monster. This allows the room which clones
your NPC to use the negative values to SetInventory() (see
/doc/build/Rooms) to make sure the NPC only gets cloned every few
days.
************************************************
Part 2: Advanced NPC Building
************************************************
*****
I. Functions
*****
You may use these functions to make your NPC's a bit more interesting
than the simple variety.
Function: void SetAction(int chance, mixed val);
Examples: SetAction(5, (: DoSomething :));
SetAction(5, ({ \"!smile\", \"!frown\" }));
SetAction(5, ({ \"The peasant looks unhappy.\" }));
Sets something to randomly happen every few heart beats while the NPC
is not in combat. In the above examples, the NPC has a 5% chance each
heart beat of performing the action you provided with the second
argument. The action can be a call to a function, a list of potential
commands, or a list of strings to be echoed to the room.
If you pass a function, that function will be called each time an
action is supposed to occur. If you pass a list of strings, one of
those strings will be randomly chosen as the target action for this
heart beat. If the chosen string begins with a !, it is treated as a
command. Otherwise, it is simply echoed to the room. Note that you
can mix commands and echo strings.
*****
Function: void SetCombatAction(int chance, mixed val);
Examples: SetCombatAction(5, (: DoSomething :));
SetCombatAction(5, ({ \"!missile\", \"!fireball\" }));
SetAction(5, ({ \"The peasant looks angry.\" }));
This function works exactly the same as SetAction(), except that these
actions only get triggered while the NPC is in combat. This is the
best place to have the NPC cast spells.
*****
Function: varargs void SetCurrency(mixed val, int amount);
Examples: SetCurrency(\"gold\", 100);
SetCurrency( ([ \"gold\" : 100, \"electrum\" : 1000 ]) );
This function allows you to set how much money an NPC is carrying.
The first syntax allows you to set one currency at a time. The second
allows you to set multiple currencies at once. Not that if you use
the second syntax, it will blow away any currencies the NPC might
already be carrying.
*****
Function: mixed SetDie(mixed val);
Examples: SetDie(\"The black knight bleeds on you as he drops dead.\");
SetDie((: CheckDie :));
If you pass a string, that string will be echoed as the NPC's death
message when it dies. If you pass a function, that function gets
called with the agent doing the killing, if any, as an argument. For
example, with the above example, the function that you write:
int CheckDie(object killer);
gets called. If you return 1, the NPC goes on to die. If you return
0, the NPC does not die. In the event you prevent death, you need to
make some arrangements with the NPC's health points and such to make
sure it really is still alive.
*****
Function: mixed SetEncounter(mixed val);
Examples: SetEncounter(40);
SetEncounter( (: CheckDwarf :) );
SetEncounter( ({ str1, str2 }) );
This allows you to set up e behaviour for an NPC upon encountering
another living thing. Note that this behaviour occurrs for both
players and other NPC's. Using the first syntax, the NPC will simply
attack any other living thing with a charisma less than 40. The
second syntax calls the function you specify. You may have it do any
number of things, however, you must also return a 1 or a 0 from that
function. A 1 means that after the function is called, the NPC should
initiate combat against the thing it just encountered. A 0 means
carry on as usual.
Finally, the third syntax is likely to be used in places other than
the create() funciton in the NPC. This syntax lets you set a list
names which are simply enemies to the NPC. More likely, you will be
using AddEncounter() and RemoveEncounter() for this.
*****
Function: string *AddEncounter(string name);
Example: AddEncounter((string)this_player()->GetKeyName());
Adds a name to the list of names an NPC will attack on sight.
*****
Function: string *RemoveEncounter(string name);
Example: RemoveEncounter((string)this_player()->GetKeyName());
Removes a name from the list of names an NPC will attack on sight.
*****
Function: SetInventory(mapping inventory);
Examples:
SetInventory( ([ \"/domains/Praxis/weapon/sword\" : \"wield sword\" ]) );
SetInventory( ([ \"/domains/Praxix/etc/ruby\" : 1 ]) );
SetInventory( ([ \"/domains/Praxis/etc/emerald\" : -10 ]) );
This functions behaves almost identically to SetInventory() for rooms
(see /doc/build/Rooms). The big difference is that you may pass a
string in addition to a number as the value for any item you want in
the inventory. In the first example above, that string is the command
the NPC issues when the sword is cloned into its inventory. In other
words, if you want an NPC to do something special with an item it has
in its inventory, in this case wield the sword, you pass the command
string as the value instead of a number.
Note that this means only one of such item will be cloned, and it
cannot be unique.
*****
II. Events
*****
The following events exist in NPC's. You should have a good grasp of
function overriding before overriding these functions.
Event: varargs int eventDie(object target);
This event is triggered any time the NPC is killed. The event returns
1 if the NPC dies, 0 if it fails to die, and -1 on error. If you
intend to allow the NPC to die, you should call npc::eventDie(target)
and make sure it returns 1.
*****
Event: int eventFollow(object dest, int chance);
This event is triggered whenever an NPC is following another living
thing and that thing leaves the room. Returnung 1 means that the NPC
successfully followed the other being, and 0 means the NPC did not.
*****
3. Manipulating limbs
*****
The basic set of limbs an NPC gets is generally set when you set its
race. You can get a list of supported NPC races through the mraces
command. Occassionally, however, you may want to create NPCs of
unique races, or with unique body structures. Or perhaps you want a
human whose right hand is already amputated. This section deals with
doing those things.
Amputating a limb is simple. Call RemoveLimb(\"limb\"). Note that that
is not useful for removing a limb that should not be there. Instead,
it is used for amputating a limb that looks amputated.
If, on the other hand, you wish to remove a limb which simply should
not have been there in the first place, call DestLimb(\"limb\").
The most simple case of actual limb manipulation, however, is to
change the basic structure of an individual NPC around for some
reason. For example, perhaps you wanted to add a tail to a human.
For this, you use the AddLimb() function.
Function: varargs int AddLimb(string limb, string parent, int class, int *armours);
Examples: AddLimb(\"tail\", \"torso\", 4)
AddLimb(\"head\", \"torso\", 1, ({ A_HELMET, A_VISOR, A_AMULET }));
This function adds a new limb to the NPC's body. The first argument
is the name of the limb to be added. The second argument is the name
of the limb to which it is attached. The third argument is the limb
class. Limb class is a number between 1 and 5. The lower the number,
the harder the limb is to remove. A limb class of 1 also means that
removal of the limb is fatal. The fourth, optional argument is a list
of armour types which may be worn on that limb.
In some cases, you may wish to create a new race from scratch. This
requires adding every single limb manually. You first call SetRace()
with a special second argument to note that you are creating a new
race:
SetRace(\"womble\", 1);
Then, you add the limbs for that race one by one. Make sure you call
SetRace() first.
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Flow Control\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 10 july 1993
CHAPTER 7: Flow Control
7.1 Review of variables
Variables may be manipulated by assigning or changing values with the
expressions =, +=, -=, ++, --. Those expressions may be combined with
the expressions -, +, *, /, %. However, so far, you have only been
shown how to use a function to do these in a linear way. For example:
int hello(int x) {
x--;
write(\"Hello, x is \"+x+\".\\n\");
return x;
}
is a function you should know how to write and understand. But what
if you wanted to write the value of x only if x = 1? Or what if
you wanted it to keep writing x over and over until x = 1 before
returning? LPC uses flow control in exactly the same way as C and C++.
7.2 The LPC flow control statements
LPC uses the following expressions:
if(expression) instruction;
if(expression) instruction;
else instruction;
if(expression) instruction;
else if(expression) instruction;
else instruction;
while(expression) instruction;
do { instruction; } while(expression);
switch(expression) {
case (expression): instruction; break;
default: instruction;
}
Before we discuss these, first something on what is meant by expression and
instruction. An expression is anything with a value like a variable,
a comparison (like x>5, where if x is 6 or more, the value is 1, else the
value is 0), or an assignment(like x += 2). An instruction can be any
single line of lpc code like a function call, a value assignment or
modification, etc.
You should know also the operators &&, ||, ==, !=, and !. These are the
logical operators. They return a nonzero value when true, and 0 when false.
Make note of the values of the following expressions:
(1 && 1) value: 1 (1 and 1)
(1 && 0) value: 0 (1 and 0)
(1 || 0) value: 1 (1 or 0)
(1 == 1) value: 1 (1 is equal to 1)
(1 != 1) value: 0 (1 is not equal to 1)
(!1) value: 0 (not 1)
(!0) value: 1 (not 0)
In expressions using &&, if the value of the first item being compared
is 0, the second is never tested even. When using ||, if the first is
true (1), then the second is not tested.
7.3 if()
The first expression to look at that alters flow control is if(). Take
a look at the following example:
1 void reset() {
2 int x;
3
4 ::reset();
5 x = random(10);
6 if(x > 50) SetSearch_func(\"floorboards\", \"search_floor\");
7 }
The line numbers are for reference only.
In line 2, of course we declare a variable of type int called x. Line 3
is aethetic whitespace to clearly show where the declarations end and the
function code begins. The variable x is only available to the function
reset().
Line 4 makes a call to the room.c version of reset().
Line 5 uses the driver efun random() to return a random number between
0 and the parameter minus 1. So here we are looking for a number between
0 and 99.
In line 6, we test the value of the expression (x>50) to see if it is true
or false. If it is true, then it makes a call to the room.c function
SetSearch_func(). If it is false, the call to SetSearch_func() is never
executed.
In line 7, the function returns driver control to the calling function
(the driver itself in this case) without returning any value.
If you had wanted to execute multiple instructions instead of just the one,
you would have done it in the following manner:
if(x>50) {
SetSearch_func(\"floorboards\", \"search_floor\");
if(!present(\"beggar\", this_object())) make_beggar();
}
Notice the {} encapsulate the instructions to be executed if the test
expression is true. In the example, again we call the room.c function
which sets a function (search_floor()) that you will later define yourself
to be called when the player types \"search floorboards\" (NOTE: This is
highly mudlib dependent. Nightmare mudlibs have this function call.
Others may have something similar, while others may not have this feature
under any name). Next, there is another if() expression that tests the
truth of the expression (!present(\"beggar\",this_object())). The ! in the
test expression changes the truth of the expression which follows it. In
this case, it changes the truth of the efun present(), which will return
the object that is a beggar if it is in the room (this_object()), or it
will return 0 if there is no beggar in the room. So if there is a beggar
still living in the room, (present(\"beggar\", this_object())) will have
a value equal to the beggar object (data type object), otherwise it will
be 0. The ! will change a 0 to a 1, or any nonzero value (like the
beggar object) to a 0. Therefore, the expression
(!present(\"beggar\", this_object())) is true if there is no beggar in the
room, and false if there is. So, if there is no beggar in the room,
then it calls the function you define in your room code that makes a
new beggar and puts it in the room. (If there is a beggar in the room,
we do not want to add yet another one :))
Of course, if()'s often comes with ands or buts :). In LPC, the formal
reading of the if() statement is:
if(expression) { set of intructions }
else if(expression) { set of instructions }
else { set of instructions }
This means:
If expression is true, then do these instructions.
Otherise, if this second expression is true, do this second set.
And if none of those were true, then do this last set.
You can have if() alone:
if(x>5) write(\"Foo,\\n\");
with an else if():
if(x > 5) write(\"X is greater than 5.\\n\");
else if(x >2) write(\"X is less than 6, but greater than 2.\\n\");
with an else:
if(x>5) write(\"X is greater than 5.\\n\");
else write(\"X is less than 6.\\n\");
or the whole lot of them as listed above. You can have any number of
else if()'s in the expression, but you must have one and only one
if() and at most one else. Of course, as with the beggar example,
you may nest if() statements inside if() instructions. (For example,
if(x>5) {
if(x==7) write(\"Lucky number!\\n\");
else write(\"Roll again.\\n\");
}
else write(\"You lose.\\n\");
7.4 The statements: while() and do {} while()
Prototype:
while(expression) { set of instructions }
do { set of instructions } while(expression);
These allow you to create a set of instructions which continue to
execute so long as some expression is true. Suppose you wanted to
set a variable equal to a player's level and keep subtracting random
amounts of either money or hp from a player until that variable equals
0 (so that player's of higher levels would lose more). You might do it
this way:
1 int x;
2
3 x = (int)this_player()->query_level(); /* this has yet to be explained */
4 while(x > 0) {
5 if(random(2)) this_player()->add_money(\"silver\", -random(50));
6 else this_player()->add_hp(-(random(10));
7 x--;
8 }
The expression this_player()->query_level() calIn line 4, we start a loop that executes so long as x is greater than 0.
Another way we could have done this line would be:
while(x) {
The problem with that would be if we later made a change to the funtion
y anywhere between 0 and 49 coins.
In line 6, if instead it returns 0, we call the add_hp() function in the
player which reduces the player's hit points anywhere between 0 and 9 hp.
In line 7, we reduce x by 1.
At line 8, the execution comes to the end of the while() instructions and
goes back up to line 4 to see if x is still greater than 0. This
loop will keep executing until x is finally less than 1.
You might, however, want to test an expression *after* you execute some
instructions. For instance, in the above, if you wanted to execute
the instructions at least once for everyone, even if their level is
below the test level:
int x;
x = (int)this_player()->query_level();
do {
if(random(2)) this_player()->add_money(\"silver\", -random(50));
else this_player()->add_hp(-random(10));
x--;
} while(x > 0);
This is a rather bizarre example, being as few muds have level 0 players.
And even still, you could have done it using the original loop with
a different test. Nevertheless, it is intended to show how a do{} while()
works. As you see, instead of initiating the test at the beginning of the
loop (which would immediately exclude some values of x), it tests after
the loop has been executed. This assures that the instructions of the loop
get executed at least one time, no matter what x is.
7.5 for() loops
Prototype:
for(initialize values ; test expression ; instruction) { instructions }
initialize values:
This allows you to set starting values of variables which will be used
in the loop. This part is optional.
test expression:
Same as the expression in if() and while(). The loop is executed
as long as this expression (or expressions) is true. You must have a
test expression.
instruction:
An expression (or expressions) which is to be executed at the end of each
loop. This is optional.
Note:
for(;expression;) {}
IS EXACTLY THE SAME AS
while(expression) {}
Example:
1 int x;
2
3 for(x= (int)this_player()->query_level(); x>0; x--) {
4 if(random(2)) this_player()->add_money(\"silver\", -random(50));
5 else this_player()->add_hp(-random(10));
6 }
This for() loop behaves EXACTLY like the while() example.
Additionally, if you wanted to initialize 2 variables:
for(x=0, y=random(20); x<y; x++) { write(x+\"\\n\"); }
Here, we initialize 2 variables, x and y, and we separate them by a
comma. You can do the same with any of the 3 parts of the for()
expression.
7.6 The statement: switch()
Prototype:
switch(expression) {
case constant: instructions
case constant: instructions
...
case constant: instructions
default: instructions
}
This is functionally much like if() expressions, and much nicer to the
CPU, however most rarely used because it looks so damn complicated.
But it is not.
First off, the expression is not a test. The cases are tests. A English
sounding way to read:
1 int x;
2
3 x = random(5);
4 switch(x) {
5 case 1: write(\"X is 1.\\n\");
6 case 2: x++;
7 default: x--;
8 }
9 write(x+\"\\n\");
is:
set variable x to a random number between 0 and 4.
In case 1 of variable x write its value add 1 to it and subtract 1.
In case 2 of variable x, add 1 to its value and then subtract 1.
In other cases subtract 1.
Write the value of x.
switch(x) basically tells the driver that the variable x is the value
we are trying to match to a case.
Once the driver finds a case which matches, that case *and all following
cases* will be acted upon. You may break out of the switch statement
as well as any other flow control statement with a break instruction in
order only to execute a single case. But that will be explained later.
The default statement is one that will be executed for any value of
x so long as the switch() flow has not been broken. You may use any
data type in a switch statement:
string name;
name = (string)this_player()->GetKeyName();
switch(name) {
case \"descartes\": write(\"You borg.\\n\");
case \"flamme\":
case \"forlock\":
case \"shadowwolf\": write(\"You are a Nightmare head arch.\\n\");
default: write(\"You exist.\\n\");
}
For me, I would see:
You borg.
You are a Nightmare head arch.
You exist.
Flamme, Forlock, or Shadowwolf would see:
You are a Nightmare head arch.
You exist.
Everyone else would see:
You exist.
7.7 Altering the flow of functions and flow control statements
The following instructions:
return continue break
alter the natural flow of things as described above.
First of all,
return
no matter where it occurs in a function, will cease the execution of that
function and return control to the function which called the one the
return statement is in. If the function is NOT of type void, then a
value must follow the return statement, and that value must be of a
type matching the function. An absolute value function would look
like this:
int absolute_value(int x) {
if(x>-1) return x;
else return -x;
}
In the second line, the function ceases execution and returns to the calling
function because the desired value has been found if x is a positive
number.
continue is most often used in for() and while statements. It serves
to stop the execution of the current loop and send the execution back
to the beginning of the loop. For instance, say you wanted to avoid
division by 0:
x= 4;
while( x > -5) {
x--
if(!x) continue;
write((100/x)+\"\\n\");
}
write(\"Done.\\n\")
You would see the following output:
33
50
100
-100
-50
-33
-25
Done.
To avoid an error, it checks in each loop to make sure x is not 0.
If x is zero, then it starts back with the test expression without
finishing its current loop.
In a for() expression
for(x=3; x>-5; x--) {
if(!x) continue;
write((100/x)+\"\\n\");
}
write(\"Done.\\n\");
It works much the same way. Note this gives exactly the same output
as before. At x=1, it tests to see if x is zero, it is not, so it
writes 100/x, then goes back to the top, subtracts one from x, checks to
see if it is zero again, and it is zero, so it goes back to the top
and subtracts 1 again.
break
This one ceases the function of a flow control statement. No matter
where you are in the statement, the control of the program will go
to the end of the loop. So, if in the above examples, we had
used break instead of continue, the output would have looked like this:
33
50
100
Done.
continue is most often used with the for() and while() statements.
break however is mostly used with switch()
switch(name) {
case \"descartes\": write(\"You are borg.\\n\"); break;
case \"flamme\": write(\"You are flamme.\\n\"); break;
case \"forlock\": write(\"You are forlock.\\n\"); break;
case \"shadowwolf\": write(\"You are shadowwolf.\\n\"); break;
default: write(\"You will be assimilated.\\n\");
}
This functions just like:
if(name == \"descartes\") write(\"You are borg.\\n\");
else if(name == \"flamme\") write(\"You are flamme.\\n\");
else if(name == \"forlock\") write(\"You are forlock.\\n\");
else if(name == \"shadowwolf\") write(\"You are shadowwolf.\\n\");
else write(\"You will be assimilated.\\n\");
except the switch statement is much better on the CPU.
If any of these are placed in nested statements, then they alter the
flow of the most immediate statement.
7.8 Chapter summary
This chapter covered one hell of a lot, but it was stuff that needed to
be seen all at once. You should now completely understand if() for()
while() do{} while() and switch(), as well as how to alter their flow
using return, continue, and break. Effeciency says if it can be done in
a natural way using switch() instead of a lot of if() else if()'s, then
by all means do it. You were also introduced to the idea of calling
functions in other objects. That however, is a topic to be detailed later.
You now should be completely at ease writing simple rooms (if you have
read your mudlib's room building document), simple monsters, and
other sorts of simple objects.
",({"chapter 39","chapter thirty-nine","39",}):"chapter 39 \"QCS: Final notes\"
* Remember that QCS commands work on objects, not files. To
load a file into memory, use the update command. To reload an
already existing object, like a cloned orc or a book, use
the reload command. To use modify, delete, and add, you have
to specify a cloned object that is on you or in your environment.
I think you're getting the idea of how this works. Here's
an example of armor creation:
%^GREEN%^create armor jeans%^RESET%^
%^GREEN%^modify jeans id%^RESET%^
%^GREEN%^pants%^RESET%^
%^GREEN%^trousers%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify jeans short a pair of denim jeans%^RESET%^
%^GREEN%^modify jeans long Worn jeans, frayed and stained.%^RESET%^
%^GREEN%^modify jeans adj%^RESET%^
%^GREEN%^pair of%^RESET%^
%^GREEN%^denim%^RESET%^
%^GREEN%^frayed%^RESET%^
%^GREEN%^worn%^RESET%^
%^GREEN%^stained%^RESET%^
%^GREEN%^.%^RESET%^
To know what directives QCS can change on an object, type:
%^GREEN%^help modify%^RESET%^
This provides a list of modifiable things and the directives
that can be modified on them.
Ultimately the Quick Creation System generates LPC code, so
you'll want to review the earlier chapters of this handbook to
get a base of understanding of the code that comprises your new
creations.
Some notes and tips:
* The SetNoCondition directive makes it so an item does not report
its physical status when examined. Weapons and armor wear down in
combat, and most objects let you know their condition when you
examine them. However, in some cases (a sandwich for example)
this is inappropriate, so the SetNoCondition directive may be
useful.
* Doors aren't like normal objects. They have to be modified *twice*.
Once for each side of the door. If this sounds unnecessarily
tedious, remember that a door leading south is also a door
leading north from the other room.
* Doors generally are not visible in the same way that regular
objects are. To make a door especially obvious and noticeable,
do something like:
%^GREEN%^modify door sethiddendoor 0%^RESET%^
* SetCurrency is for adding money to NPC's. SetMoney is for adding
money to non-living containers (bags, etc).
* Item subtypes are listed in /include. To know what kinds of vendors
are available, for example, look in /include/vendor_types.h
* Books need a \"source directory\", which must contain one file per
chapter. The SetSource for this manual, for example, is /doc/manual
The first line of each file must follow the same format as
the files you see in /doc/manual
* SetObviousExits is usually no longer needed: rooms report
obvious exits automatically. However, if you don't want an exit
to show up by default, use the SetObviousExits directive
to specify only those that you want seen. This will override the
room's default exit display.
",({"chapter 25","chapter twenty-five","25",}):"chapter 25 \"Rooms\"
Building Rooms
The Nightmare IV LPC Library
written by Descartes of Borg 950420
This document details how to build rooms using the Nightmare IV LPC
Library's inheritable room object. This document is divided into
simple room building and complex room building. The first part
teaches you about basic rooms. The second part tells you what
features are there to allow you to do creative things with the room object.
************************************************
Part 1: Basic Room Building
************************************************
I. The Simple Room
The simple room minimally looks like this:
#include <lib.h>
inherit LIB_ROOM;
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"indoors\");
SetShort(\"an empty room\");
SetLong(\"An empty room with no exits leading anywhere. It is \"
\"completely barren with nothing to describe.\");
}
#include <lib.h>
This first line is one you need in any object. It defines the exact
location of objects which you inherit. In this case, the object is
LIB_ROOM. It is currently located at /lib/room.c. If we wanted to
change that location, however, we could do it easily since you only
reference LIB_ROOM. So lib.h is a file that says that LIB_ROOM is
\"/lib/room\".
inherit LIB_ROOM;
The third line, the inherit line, says that this object will inherit
/lib/room.c.
static void create() {
The fifth line begins the meat of any object you will write. This is
the beginning of a function. This one is called create(). If you are
curious, the static means no one can use the call command to call the
function. Do not worry about that too much, however, as it is always
something you put there for the create() function.
The \"void\" part simply says that you are returning no value from the
function. See the LPC Basics textbook for more information on
functions.
room::create();
Inside the create() function are the calls which define what the
object will do. The first call calls the function create() in
/lib/room.c, the object you just inherited. /lib/room.c has its own
create() function which does some things needed in order to set up
your object. You need to make sure it gets called through this line.
SetProperty(\"light\", 2);
This sets the light in the room to be 2. In outdoors rooms, this is
the average light during day time. In indoor rooms, it is the average
light all the time.
SetClimate(\"indoors\")
Every room has a climate. An indoors room, among other things, is not
affected by weather or the time of day.
SetShort(\"an empty room\")
This is the description that the player sees when in brief mode. In
addition, in brief mode, obvious exit abbreviations are automatically
added. This is done through the SetObviousExits() function described
later. However, the short should be phrased in such a way that it
makes sense from something like a scry command which would say
something like: \"You see Descartes in an empty room.\"
SetLong(\"An empty room with no exits leading anywhere. It is \"
\"completely barren with nothing to describe.\");
This sets the long description seen by the player when in verbose
mode. Note that items in the room as well as scents and sounds are
added to what the player sees automatically.
That's it! You now have a room which no one can leave!
II. Adding items
Approval on any decent MUD will eat you for lunch if you do not
describe your items. This is likely the most tedious part of area
building, however, it is also the part that largely makes the
difference between a dull area and a fun one. You must be sure to
make it so that anything a player might logically want to see in
detail in a room is described in detail. For example, say you have
the following long description for a room:
You are in Monument Square, once known as Krasna Square. The two main
roads of Praxis intersect here, where all of Nightmare's people gather
in joy and sorrow. The road running north and south is called Centre
Path, while Boc La Road is the name of the road running east and west.
A magnificent monument rises above the square.
You should have descriptions for the following items placed in your
room:
square, monument, monument square, krasna square, roads, road,
intersection, people, centre path, boc la road, magnificent monument
How to do this with a minimum of hassle:
SetItems( ([ ({ \"square\", \"monument square\", \"krasna square\" }) :
\"The central square of Praxis where citizens and adventurers \"
\"gather to chat and trade. Formerly known as Krasna Square, \"
\"is now known as Monument Square as thanks to those who helped \"
\"to build the town\",
({ \"monument\", \"magnificent monument\" }) : \"A giant monolith \"
\"rising above Monument Square\",
({ \"intersection\", \"road\", \"roads\" }) : \"The two main roads of Praxis \"
\"intersect in Monument Square. The one to the north and south \"
\"is called Centre Path, while the other is Boc La Road.\",
({ \"people\", \"adventurers\", \"citizens\" }) : \"A varied group of \"
\"people from countless realms hanging about talking and trading.\",
\"centre path\" : \"The main road leading north to the North Forest \"
\"from Praxis, and south to the sea.\",
\"boc la road\" : \"The main east-west road through Praxis, going \"
\"east towards the jungle, and west towards the Daroq Mountains.\" ]) );
That may seem like a mouthful, but it is easier to break down into
smaller points and see what is going on. The SetItems() prototype
looks like this:
mapping SetItems(mapping items);
That means it accepts a data type called a mapping as the argument and
returns a new mapping of items. A mapping is a special data type in
LPC that allows you to associate two values together, for example, to
associate an item with its description. For example, above we wanted
to associate the items \"monument\" and \"magnificent monument\" with the
description \"A giant monolith rising above Monument Square\". To do
that, a mapping looks like this:
([ value1 : assoc_value1 ])
where assoc_value1 is the value associated with value1. In this case,
we might have something like:
([ \"monument\" : \"A giant monolith rising above Monument Square.\" ])
But, we also wanted to associate \"magnificent monument\" with this
description. One way, which is perfectly legitimate, would be:
([ \"monument\" : \"A giant monolith rising above Monument Square\",
\"magnificent monument\" : \"A giant monolith rising above Monument Square\" ])
But that would be damned annoying, especially with long descriptions
or things with a lot of synonyms. You can therefore group values
which have the same description together using array notation:
({ value1, value2, value3 })
And thus, make that mapping look like:
([ ({ \"monument\", \"magnificent monument\" }) : \"A giant monolith rising \"
\"above Monument Square.\" ])
To complete setting the items, you simply add other item/description
pairs separated by commas:
([ ({ \"monument\", \"monument square\" }) : \"A giant monolith rising \"
\"above Monument Square.\",
\"house\" : \"A little white house with white picket fences.\" ])
Mappings are a rather difficult concept to grasp, but once grasped
they are very powerful. You should take a look at some sample code
from /domains/Examples/room to get a good idea of what proper code
looks like. In addition, there is a chapter in Intermediate LPC
dedicated to the concept. Finally, you can always mail
borg@imaginary.com to ask questions.
III. Adding Exits and Enters
If you understand the section above, exits and enters are simple.
They too use mappings, but less complicated ones:
SetExits( ([ \"north\" : \"/domains/Praxis/n_centre1\",
\"south\" : \"/domains/Praxis/s_centre1\",
\"east\" : \"/domains/Praxis/e_boc_la1\",
\"west\" : \"/domains/Praxis/w_boc_la1\" ]) );
SetEnters( ([ \"hall\" : \"/domains/Praxis/town_hall\",
\"pub\" : \"/domains/Praxis/pub\" ]) );
With an exit mapping, you simply match the direction to the room to
which it leads. With an enter mapping, you match a thing being
entered with the room to which it leads.
Unlike other LPC Libraries, the Nightmare IV LPC Library distinguishes
between the concept of motion towards and motion into. Motion towards
is exemplified by the \"go\" command, which is affected by SetExits().
For example, to go east, you type \"go east\". You are simply going
towards the east (Note that \"go east\" is by default aliased to \"e\").
Motion into is exemplified by the \"enter\" command, which is affected
by SetEnters(). Enter marks anything you enter into, for example a
building or bushes or the like. In the above example, a player would
issue the command \"enter pub\" to enter the pub.
IV. Adding Objects
If you want to add physical objects into your room, you use the
SetInventory() function. For example, if you wanted to place a balrog
in the room:
SetInventory(([ \"/domains/Praxis/npc/balrog\" : 1 ]);
Every reset, the room will then check to see if any balrogs are in the
room. If no balrogs are in the room it will clone 1. Again, this is
another function using a mapping. In this case it is associating the
file name of an object with how many of that object should be in the
room at every reset. If you wanted 5 balrogs in the room, you would
have changed the 1 to 5.
V. Adding Smells, Listens, and Searches
The functions:
SetSmell()
SetSearch()
SetListen()
All work identically to the SetItems() function. That is they match
things you can smell, listen, search to descriptions which the player
sees when they smell, listen, search the item.
For example:
SetSmell( ([ \"monument\" : \"It smells of obsidian.\",
\"road\" : \"It smells dusty.\",
({ \"pub\", \"bar\" }) : \"It smells of alcohol.\" ]) );
If a player types:
\"smell monument\"
then they see
\"It smells of obsidian.\"
One unique thing about these three functions, however, is that you can
use the special thing \"default\" to set a smell, listen, or search that
occurs when no object is specified. For example,
SetSmell(([ \"default\" : \"It really stinks here.\" ]) );
Will have the player see \"It really stinks here.\" when they simply
type \"smell\". In addition, this is the smell the player sees when
they simply walk into a room.
VI. Miscellaneous stuff
SetObviousExits(\"n, s, e\")
Sets an obvious exits string which gets seen in brief mode and by
newbies in verbose mode. Generally, this should consist of the
abbreviations for the room's obvious exits only.
SetTown(\"Praxis\")
For rooms which are considered part of a town, you must specify that
they are part of the town through this function. In this example, the
room is set to be in the town of Praxis. See the document
/doc/build/Towns for more information on towns.
SetDayLong(\"The sky lights up the endless fields of wheat which stand \"
\"before you.\");
SetNightLong(\"You are standing in a pitch black field of wheat.\");
Instead of using SetLong(), you can call both of these functions to
give different long descriptions for day and night.
SetGravity(2.0)
This makes things in the room twice as heavy as normal.
SetDoor(\"east\", \"/domains/Praxis/doors/red_door\");
Sets a door to the east which is the file
\"/domains/Praxis/doors/red_door.c\". You should have an exit to the
east, and you should do this AFTER you have called SetItems(). See
the document /doc/build/Doors for detailed information on door
building.
VII. Summary
Here is a room that uses everything described above:
#include <lib.h>
inherit LIB_ROOM;
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"temperate\");
SetTown(\"Praxis\");
SetShort(\"a peaceful park\");
SetDayLong(\"The light of the sun shines down upon an open field \"
\"in the middle of Praxis known as Kronos Park. In spite \"
\"of the time of day, no one is around. East Boc La \"
\"Road is to the south.\");
SetNightLong(\"Kronos Park is a poorly lit haven for rogues in the \"
\"cover of night. It is safest to head back south \"
\"towards the lights of East Boc La Road\");
SetItems( ([ ({ \"field\", \"park\" }) : \"A wide open park in the \"
\"center of Praxis.\" ]) );
SetSearch( ([ \"field\" : \"You get dirt all over your hands.\" ]) );
SetSmell( ([ \"default\" : \"You smell grass after a fresh rain.\",
\"dirt\" : \"It smells like... dirt!\" ]) );
SetExits( ([ \"south\" : \"/domains/Praxis/e_boc_la3\" ]) );
SetInventory( ([ \"/domains/Praxis/npc/rogue\" : 2 ]) );
}
************************************************
Part 2: Advanced Room Building
************************************************
I. Functionals
MudOS has a data type called a functional. Most room functions take a
functional as an argument instead of a string. What this does is
allow you to specify a function to get called in order to determine
the value rather than set it as a string which cannot be changed. For
example, if you wanted to set a long description that varied depending the
status of a door:
#include <lib.h>
inherit LIB_ROOM;
string CheckDoor(string useless);
static void create() {
room::create();
SetProperty(\"light\", 2);
SetClimate(\"indoors\");
SetShort(\"an indoor room with a door\");
SetLong( (: CheckDoor :) );
SetExits( ([ \"east\" : \"/domains/Praxis/east_room\" ]) );
SetDoor(\"east\", \"/domains/Praxis/doors/red_door\");
}
string CheckDoor(string useless) {
string tmp;
tmp = \"You are in a plain indoor room with a door. \";
if( (int)\"/domains/Praxis/doors/red_door\"->GetOpen() )
tmp += \"The door is open.\";
else tmp += \"The door is closed.\";
return tmp;
}
In this example, a function called CheckDoor() was written to
determine exactly what the long description should be. This is done
because in create(), you have no idea what the status of the door will
be from moment to moment. Using a function, you can therefore
determine what the long description is at the time it is needed.
Functionals can reference any function anywhere on the MUD, including
efuns. See /doc/lpc/data_types/functionals for details on them. For
the sake of this document however, you note a functional using smileys
:).
(: CheckDoor :) means the function CheckDoor() in this object. You
can also specify function in other objects, for example:
(: call_other, this_player(), \"GetName\" :) would refer to GetName() in
the person who was this_player() AT THE TIME THE FUNCTIONAL WAS
CREATED.
Notice at the top of the file that CheckDoor() was prototyped. You
must prototype any function you reference inside your objects. The
expression (: CheckDoor :) constitutes as a reference, and thus makes
you need to prototype the function.
The rest of this portion describes individual function calls using
functionals. The functional prototype part is how your functional
should be declared.:
SetShort(string | function)
Functional prototype: string ShortFunc();
Example: SetShort( (: MyShort :) );
If you pass it a function, then this function gets called to determine
the short description. The function should return a string which will
be used as the short description.
SetLong(string | function)
Functional prototype: string LongFunc(string unused)
Example: SetLong( (: MyLong :) );
This function should return a string which will be used as the long
description for the room. The argument \"unused\" is just that, unused
in this context. It is something used for other objects.
SetItems(mapping mp);
Functional prototype: string ItemFunc(string item);
Example: SetItems( ([ \"house\" : (: LookHouse :) ]) );
This function should return a string to be used for the item
description. The argument is passed the name of the item being looked
at, so you can use the same function for multiple items.
SetSearch(mapping mp)
Alternate: SetSearch(string item, string | function desc)
Functional prototype: string SearchFunc(string item);
Examples: SetSearch( ([ \"grass\" : (: SearchGrass :) ]) );
SetSearch(\"grass\", (: SearchGrass :));
Note that there are two forms to SetSearch(), useful depending on how
many searches you are setting at once. If you have a search function,
then that function should return a string which is what they will see.
The argument passed is the item being searched.
SetSmell()
SetListem()
see SetSearch()
II. Advanced Exits
SetExits() is fairly straight forward. However, there exists another
function for exits called AddExit(). It allows you to add one exit at
a time (useful if say a player searches and finds a new exit) as well
as give functional power to exits. The prototype for AddExit() is:
varargs mapping AddExit(string dir, string dest, function pre, function post);
The varargs part of the prototype simply means you can call it using
less than the full number of arguments specified. In this case, the
minimum call is:
AddExit(\"east\", \"/domains/Praxis/square\");
The last two arguments are called pre-exit functions and post exit
functions. The pre-exit function gets called when a player issues a
command to leave the room, but before the player is allowed to leave.
Depending on the return value of the function, the player is allowed
or denied the right to leave. For example:
AddExit(\"north\", \"/domains/Praxis/square\", (: PreExit :));
int PreExit(string dir) {
if( !avatarp(this_player()) ) {
write(\"You are too lowly to go that way!\");
return 0;
}
else return 1;
}
In other words, if the player is an avatar, they can go north.
Otherwise they cannot. The prototype is:
int PreExit(string dir);
where the return value is 1 or 0 for can or cannot leave, and the
argument dir is the direction in which the player is exiting.
Post exit functions work a little differently since it makes no sense
to prevent someone from leaving once they have left. The prototype
looks like:
void PostExit(string dir);
This simply allows you to do processing once the player is gone. If
you wish a post exit without a pre exit, then:
AddExit(\"north\", \"/domains/Praxis/square\"\", 0, (: PostExit :));
Enters work exactly the same way.
Please read about the events CanReceive() and CanRelease(), as those
may be more appropriate places to do what you want. Remember, this
only prevents a player from using the \"go\" command to go in that
direction. CanReceive() in the other room would be better if your
desire is to keep non-avatars out of the square at any cost.
III. Other Functions
AddExit()
RemoveExit()
AddEnter()
RemoveEnter()
RemoveSearch()
RemoveSmell()
RemoveListen()
AddItem()
RemoveItem()
All of the above Remove*() functions take a single string argument
specifying what it is that is being removed. For example:
RemoveExit(\"east\")
removes the exit to the east.
AddItem(string item, mixed val)
Adds a single item. Val can be a string or function.
Descartes of Borg
borg@imaginary.com
",({"chapter 39","chapter thirty-nine","39",}):"chapter 39 \"QCS: Final notes\"
* Remember that QCS commands work on objects, not files. To
load a file into memory, use the update command. To reload an
already existing object, like a cloned orc or a book, use
the reload command. To use modify, delete, and add, you have
to specify a cloned object that is on you or in your environment.
I think you're getting the idea of how this works. Here's
an example of armor creation:
%^GREEN%^create armor jeans%^RESET%^
%^GREEN%^modify jeans id%^RESET%^
%^GREEN%^pants%^RESET%^
%^GREEN%^trousers%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify jeans short a pair of denim jeans%^RESET%^
%^GREEN%^modify jeans long Worn jeans, frayed and stained.%^RESET%^
%^GREEN%^modify jeans adj%^RESET%^
%^GREEN%^pair of%^RESET%^
%^GREEN%^denim%^RESET%^
%^GREEN%^frayed%^RESET%^
%^GREEN%^worn%^RESET%^
%^GREEN%^stained%^RESET%^
%^GREEN%^.%^RESET%^
To know what directives QCS can change on an object, type:
%^GREEN%^help modify%^RESET%^
This provides a list of modifiable things and the directives
that can be modified on them.
Ultimately the Quick Creation System generates LPC code, so
you'll want to review the earlier chapters of this handbook to
get a base of understanding of the code that comprises your new
creations.
Some notes and tips:
* The SetNoCondition directive makes it so an item does not report
its physical status when examined. Weapons and armor wear down in
combat, and most objects let you know their condition when you
examine them. However, in some cases (a sandwich for example)
this is inappropriate, so the SetNoCondition directive may be
useful.
* Doors aren't like normal objects. They have to be modified *twice*.
Once for each side of the door. If this sounds unnecessarily
tedious, remember that a door leading south is also a door
leading north from the other room.
* Doors generally are not visible in the same way that regular
objects are. To make a door especially obvious and noticeable,
do something like:
%^GREEN%^modify door sethiddendoor 0%^RESET%^
* SetCurrency is for adding money to NPC's. SetMoney is for adding
money to non-living containers (bags, etc).
* Item subtypes are listed in /include. To know what kinds of vendors
are available, for example, look in /include/vendor_types.h
* Books need a \"source directory\", which must contain one file per
chapter. The SetSource for this manual, for example, is /doc/manual
The first line of each file must follow the same format as
the files you see in /doc/manual
* SetObviousExits is usually no longer needed: rooms report
obvious exits automatically. However, if you don't want an exit
to show up by default, use the SetObviousExits directive
to specify only those that you want seen. This will override the
room's default exit display.
",({"chapter 9","chapter nine","9",}):"chapter 9 \"Introduction to Intermediate LPC\"
Intermediate LPC
Descartes of Borg
Novermber 1993
Chapter 1: Introduction
1.1 LPC Basics
Anyone reading this textbook should either have read the textbook LPC
Basics or be familiar enough with mud realm coding such that not only are
they capable of building rooms and other such objects involved in area
coding, but they also have a good idea of what is going on when the code
they write is executing. If you do not feel you are at this point, then go
back and read LPC Basics before continuing. If you do so, you will find
that what you read here will be much more meaningful to you.
1.2 Goals of This Textbook
The introductory textbook was meant to take people new to LPC from
knowing nothing to being able to code a nice realm on any LPMud. There
is naturally much more to LPC and to LPMud building, however, than
building rooms, armours, monsters, and weapons. As you get into more
complicated concepts like guilds, or desire to do more involved things with
your realm, you will find the concepts detailed in LPC Basics to be lacking
in support for these projects. Intermediate LPC is designed to take you
beyond the simple realm building process into a full knowledge of LPC for
functioning as a realm builder on an LPMud. The task of mudlib building
itself is left to a later text. After reading this textbook and working through
it by experimenting with actual code, the reader should be able to code game
objects to fit any design or idea they have in mind, so long as I have been
successful.
1.3 An Overview
What more is there? Well many of you are quite aware that LPC supports
mappings and arrays and have been asking me why those were not detailed
in LPC Basics. I felt that those concepts were beyond the scope of what I
was trying to do with that textbook and were more fitting to this textbook.
But new tools are all fine and dandy, what matters, however, is what you
can do with those tools. The goal of LPC Basics was to get you to building
quality LPMud realms. Mappings and arrays are not necessary to do that.
The goal of this book is to allow you to code any idea you might want to
code in your area. That ability requires the knowledge of mappings and
arrays.
Any idea you want to code in an LPMud is possible. LPC is a language
which is amazingly well suited to this task. All that prevents you from
coding your ideas is your knowledge of LPC or an inadequate mudlib or
your mud<75>s theme or administrative policies. This textbook cannot make
the mudlib you are working with any better, and it cannot change the mud
theme or the mud<75>s administrative policies. Never once think that LPC is
incapable of doing what you want to do. If your idea is prevented by
administrative policies or themes, then it is simply not an idea for your
current mud. If the mudlib is inadequate, talk to the people in charge of
your mudlib about what can be done at the mudlib level to facilitate it. You
would be surprised by what is actually in the mudlib you did not know
about. More important, after reading this textbook, you should be able to
read all of the mudlib code in your mud<75>s mudlib and understand what is
going on at each line in the mudlib code. You may not as yet be able to
reproduce that code on your own, but at least you can understand what is
going on at the mudlib level.
This textbook starts out with a discussion about what the LPMud driver is
doing. One nice thing about this textbook, in general it is completely driver
and mudlib independent (excepting for the Dworkin Game Driver). The
chapter on the game driver does not get into actual implementation, but
instead deals with what all game drivers basically do in order to run the
mud.
Next I discuss those magic topics everyone wants to know more about,
arrays and mappings. Mappings may be simultaneously the easiest and
most difficult data type to understand. Since they are sort of complex arrays
in a loose sense, you really need to understand arrays before discussing
them. All the same, once you understand them, they are much easier than
arrays to use in real situations. At any rate, spend most of your time
working with that chapter, because it is probably the most difficult, yet most
useful chapter in the book.
After that follows a brief chapter on the LPC pre-compiler, a tool you can
use for sorting out how your code will look before it gets sent to the
compiler. Despite my horrid intro to it here, this chapter is perhaps the
easiest chapter in the textbook. I put it after the mappings and arrays
chapter for exactly that reason.
Strings are re-introduced next, going into more detail with how you can do
such things as advanced command handling by breaking up strings. Once
you understand arrays fairly well, this chapter should be really simple.
The next chapter is the second most important in the book. It may be the
most important if you ever intend to go beyond the intermediate stage and
dive into mudlib coding. That chapter involves the complex ideas behind
LPC inheritance. Since the goal of this textbook is not to teach mudlib
programming, the chapter is not a detailed discussion on object oriented
programming. Understanding this chapter, however, will give you some
good insights into what is involved with object oriented programming, as
well as allow you to build more complex objects by overriding functions
and defining your own base classes.
Finally, the textbook ends with a simple discussion of code debugging.
This is not an essential chapter, but instead it is meant as more of an
auxiliary supplement to what the knowledge you have accumulated so far.
1.4 Not Appearing in This Textbook
Perhaps what might appear to some as the most glaring omission of this
textbook is largely a political omission, shadows. Never have I ever
encountered an example of where a shadow was either the best or most
effecient manner of doing anything. It does not follow from that, however,
that there are no uses for shadows. My reasoning for omitting shadows
from this textbook is that the learner is best served by learning the concepts
in this textbook first and having spent time with them before dealing with
the subject of shadows. In that way, I feel the person learning LPC will be
better capable of judging the merits of using a shadow down the road. I
will discuss shadows in a future textbook.
If you are someone who uses shadows some or a lot, please do not take the
above paragraph as a personal attack. There may be some perfectly valid
uses for shadows somewhere which I have yet to encounter. Nevertheless,
they are not the ideal way to accomplish any given task, and therefore they
are not considered for the purposes of this textbook an intermediate coding
tool.
I have also omitted discussions of security and object oriented
programming. Both are quite obviously mudlib issues. Many people,
however, might take exception with my leaving out a discussion of object
oriented programming. I chose to leave that for a later text, since most area
builders code for the creativity, not for the computer science theory. In both
the intermediate and beginner textbooks, I have chosen only to discuss
theory where it is directly applicable to practical LPC programming. For
people who are starting out green in LPC and want to code the next great
mudlib, perhaps theory would be more useful. But for the purposes of this
book, a discussion of object oriented programming is simply a snoozer. I
do plan to get heavy into theory with the next textbook.
1.5 Summary
LPC is not difficult to learn. It is a language which, although pathetic
compared to any other language for performing most computer language
tasks, is incredibly powerful and unequalled for the tasks of building an
area in MUD type games. For the beginner, it allows you to easily jump in
and code useful objects without even knowing what you are doing. For the
intermediate person, it allows you to turn any idea you have into textual
virtual reality. And for the advanced person, it<69>s object oriented features
can allow you to build one of the most popular games on the internet. What
you can do is simply limited by how much you know. And learning more
does not require a computer science degree.
Copyright (c) George Reese 1993
",({"chapter 31","chapter thirty-one","31",}):"chapter 31 \"Overview of the Quick Creation System\"
First, let me clarify that the QCS is not intended to replace
good coding habits. It is also not designed to handle every possible
need of a builder. The QCS is just a handy tool for making the
most tedious parts of building easier.
The amount of time it takes to hand-code an area of 100 rooms
(a small area, that is), with all the appropriate descriptions and
monsters and weapons and such is just mind-boggling. As a grown-up,
I just don't have time for building stuff line by excruciating line
in raw LPC, because I also need to work, maintain my house, say hello
to my family on occasion, etc.
At the same time, I would need a team of dedicated LPC code
fetishists to make a creation system that covers every last possible
thing you could do in LPC. The QCS is somewhere in between those
two extremes.
Therefore please view the QCS as a quick way to get bulk building
done, and not as a be-all end-all solution. You still need to learn
LPC to do really cool stuff. But to hammer out an area with a quest,
QCS lets you sail through the process of thingmaking with little hassle.
The design philosophy of the system itself involves object files
stored in /secure/modules. These files are inherited by an object which
you need to carry with you in order to use the QCS system. The code
for these creation modules is fairly messy and inelegant, but the
result is clean, indented code that compiles, so let's keep the
mockery to a minimum, shall we?
It is important to keep in mind the QCS isn't an editing system.
It's real live on-line modification, meaning that to modify a thing,
it actually has to be in the same room you are in, or it has to be that
room itself.
Once you modify something, it will typically update, so that if
you change the name of an npc, you're going to need to use the new name
to modify it further.
The next few chapters in this manual are nominally QCS specific,
but in reality this is pretty much my only chance to document some
of the changes in Dead Souls since version 1, so even if you never
intend to use QCS, it's worth poking through these chapters.
- Cratylus @ Frontiers
2 January 2006
",({"chapter 4","chapter four","4",}):"chapter 4 \"Functions\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 22 june 1993
CHAPTER 4: Functions
4.1 Review
By this point, you should be aware that LPC objects consist of functions
which manipulate variables. The functions manipulate variables when they
are executed, and they get executed through *calls* to those functions.
The order in which the functions are placed in a file does not matter.
Inside a function, the variables get manipulated. They are stored in
computer memory and used by the computer as 0's and 1's which
get translated to and from useable output and input through a device
called data typing. String data types tell the driver that the
data should appear to you and come from you in the form of alphanumeric
characters. Variables of type int are represented to you as whole
number values. Type status is represented to you as either 1 or 0.
And finally type void has no value to you or the machine, and is not
really used with variable data types.
4.2 What is a function?
Like math functions, LPC functions take input and return output.
Languages like Pascal distinguish between the concept of proceedure abd
the concept of function. LPC does not, however, it is useful to
understand this distinction. What Pascal calls a proceedure, LPC
calls a function of type void. In other words, a proceedure, or function
of type void returns no output. What Pascal calls a function differs
in that it does return output. In LPC, the most trivial, correct
function is:
-----
void do_nothing() { }
-----
This function accepts no input, performs no instructions, and returns no
value.
There are three parts to every properly written LPC function:
1) The declaration
2) The definition
3) The call
Like with variables, functions must be declared. This will allow the
driver to know 1) what type of data the function is returning as output,
and 2) how many input(s) and of what type those input(s) are. The
more common word for input is parameters.
A function declaration therefore consists of:
type name(parameter1, parameter2, ..., parameterN);
The declaration of a function called drink_water() which accepts a string as
input and an int as output would thus look like this:
-----
int drink_water(string str);
-----
where str is the name of the input as it will be used inside the function.
The function definition is the code which describes what the function actually
does with the input sent to it.
The call is any place in other functions which invokes the execution of the
function in question. For two functions write_vals() and add(), you thus
might have the following bit of code:
-----
/* First, function declarations. They usually appear at the beginning
of object code.
*/
void write_vals();
int add(int x, int y);
/* Next, the definition of the function write_vals(). We assume that
this function is going to be called from outside the object
*/
void write_vals() {
int x;
/*N Now we assign x the value of the output of add() through a call */
x = add(2, 2);
write(x+\"\\n\");
}
/* Finally, the definition of add() */
int add(int x, int y) {
return (x + y);
}
-----
Remember, it does not matter which function definition appears first in the
code. This is because functions are not executed consecutively. Instead,
functions are executed as called. The only requirement is that the
declaration of a function appear before its definition and before the
definition of any function which makes a call to it.
4.3 Efuns
Perhaps you have heard people refer to efuns. They are externally defined
functions. Namely, they are defined by the mud driver. If you have
played around at all with coding in LPC, you have probably found some
expressions you were told to use like this_player(), write(), say(),
this_object(), etc. look a lot like functions. That is because they are
efuns. The value of efuns is that they are much faster than LPC functions,
since they already exist in the binary form the computer understands.
In the function write_vals() above, two functions calls were made. The first was to
the functions add(), which you declared and defined. The second call, however,
was to a function called write(), and efun. The driver has already declared
and defined this function for you. You needs only to make calls to it.
Efuns are created to hanldle common, every day function calls, to handle
input/output to the internet sockets, and other matters difficult to be
dealt with in LPC. They are written in C in the game driver and compiled
along with the driver before the mud comes up, making them much faster
in execution. But for your purposes, efun calls are just like calls
made to your functions. Still, it is important to know two things of any
efun: 1) what return type does it have, and 2) what parameters of what
types does it take.
Information on efuns such as input parameters and return types is often
found in a directory called /doc/efun on your mud. I cannot
detail efuns here, because efuns vary from driver to driver. However,
you can often access this information using the commands \"man\" or \"help\"
depending on your mudlib. For instance, the command \"man write\" would
give you information on the write efun. But if all else fails,
\"more /doc/efun/write\" should work.
By looking it up, you will find write is declared as follows:
-----
void write(string);
-----
This tells you an appropriate call to write expects no return value and
passes a single parameter of type string.
4.4 Defining your own functions
Although ordering your functions within the file does not matter, ordering
the code which defines a function is most important. Once a function
has been called, function code is executed in the order it appears
in the function definition. In write_vals() above, the instruction:
-----
x = add(2, 2);
-----
Must come before the write() efun call if you want to see the appropriate
value of x used in write().
With respect to values returned by function, this is done through the \"return\"
instruction followed by a value of the same data type as the function. In
add() above, the instruction is \"return (x+y);\", where the value of (x+y)
is the value returned to write_vals() and assigned to x. On a more
general level, \"return\" halts the execution of a function and returns
code execution to the function which called that function. In addition,
it returns to the calling function the value of any expression that follows.
To stop the execution of a function of type void out of order, use
\"return\"; without any value following. Once again, remember, the data
type of the value of any expression returned using \"return\" MUST be the
same as the data type of the function itself.
4.5 Chapter Summary
The files which define LPC objects are made of of functions. Functions, in
turn, are made up of three parts:
1) The declaration
2) The definition
3) The call
Function declarations generally appear at the top of the file before any
defintions, although the requirement is that the declaration must appear
before the function definition and before the definition of any function
which calls it.
Function definitions may appear in the file in any order so long as they
come after their declaration. In addition, you may not define one function
inside another function.
Function calls appear inside the definition of other functions where you
want the code to begin execution of your function. They may also appear
within the definition of the function itself, but this is not recommended
for new coders, as it can easily lead to infinite loops.
The function definition consists of the following in this order:
1) function return type
2) function name
3) opening ( followed by a parameter list and a closing )
4) an opening { instructing the driver that execution begins here
5) declarations of any variables to be used only in that function
6) instructions, expressions, and calls to other functions as needed
7) a closing } stating that the function code ends here and, if no
\"return\" instruction has been given at this point (type void functions
only), execution returns to the calling function as if a r\"return\"
instruction was given
The trivial function would thus be:
-----
void do_nothing() {}
-----
since this function does not accept any input, perform any instructions, or
return any output.
Any function which is not of type void MUST return a value of a data type
matching the function's data type.
Each driver has a set of functions already defined for you called efuns
These you need neither need to declare nor define since it has already
been done for you. Furthermore, execution of these functions is faster
than the execution of your functions since efuns are in the driver.
In addition, each mudlib has special functions like efuns in that they
are already defined and declared for you, but different in that they
are defined in the mudlib and in LPC. They are called simul_efuns, or
simulated efuns. You can find out all about each of these as they are
listed in the /doc/efun directory on most muds. In addition many
muds have a command called \"man\" or a \"help\" command which allows you
simply to call up the info files on them.
Note on style:
Some drivers may not require you to declare your functions, and some
may not require you to specify the return type of the function in its
definition. Regardless of this fact, you should never omit this information
for the following reasons:
1) It is easier for other people (and you at later dates) to read your
code and understand what is meant. This is particularly useful
for debugging, where a large portion of errors (outside of misplaced
parentheses and brackets) involve problems with data types (Ever
gotten \"Bad arg 1 to foo() line 32\"?).
2) It is simply considered good coding form.
",({"chapter 23","chapter twenty-three","23",}):"chapter 23 \"Properties\"
Supported Properties
The Nightmare IV LPC Library
written by Descartes of Borg 950429
The Nightmare IV LPC Library allows creators to set dynamic variables in
objects which do not get saved when the object saves. The variables are
called properties. A property is an attribute of an object which is
considered fleeting. This document serves to list the properties
commonly used and their purpose. It is by no means complete, as the
point of having properties is to allow creators to build their own on the
fly.
Note: All properties are 0 by default unless otherwise stated.
Property: light
Values: integer between -6 and 6
Light is a value generally between -6 and 6 which, for rooms,
determines how much light is naturally available in a room in daytime.
For other objects, it determines the degree to which the object is
able to modify the amount of light that exists in the room. If the
room is indoors, the light does not change based on the time of day.
Property: no attack
Values: 1 to prevent attacks, 0 to allow them
Things cannot begin combat from inside a room with this property.
Property: no bump
Values: 1 to prevent bumping, 0 to allow it
If a room, then nothing can be bumped from this room. If a living
thing, then it cannot be bumped.
Property: no steal
Values: 1 to prevent stealing, 0 to allow it
This prevents stealing inside a room with this property.
Property: no magic
Values: 1 to prevent magic, 0 to allow it
This prevents any magic from being used inside the room if set.
Property: no paralyze
Values: 1 prevents paralysis from occurring in a room, 0 allows it
Stops any sort of thing which might cause paralysis from occurring in
a room.
Property: no teleport
Values: 1 if teleporting is prohibited, 0 if allowed
Prevents people from teleporting to or from the room.
Property: no clear
Values: 1 to prevent clearing, 0 to allow it
If set this prevents an avatar from clearing a wilderness room in
order to build a town. Not relevant to rooms in towns.
Property: estates
Values: any non-negative number
Sets the number of estates which can be built in an area. No estates
may be built outside of towns.
Property: magic item
Values: an array of strings describing the magic contained in an object
Allows you to mark specific objects as magic. For example, if a sword
has a magical lighting ability, you might do:
SetProperty(\"magic item\", ({ \"light\" }));
Property: lockpicking tool
Values: any integer marking how well lockpicking is enhanced
When picking a lock, the value of this property is calculated for each
object and added to the overall chance to pick the lock.
Property: keep
Values: the name of whomever the object is kept for
While set, this object may only be picked up by the person whose name
matches the value of this property. If 0, anyone can pick it up
assuming it is normally gettable.
Property: magic hold
Value: any integer
Is subtracted from the chance of success of anyone trying to pick a
lock.
Property: enchantment
Value: any integer
Enchants any object to boost (or degrade) its performance of its
natural functions.
Property: login
Value: a string representing a file name
Sets which room a player should login to at next login if they quit
from the room that has this property. For example, if you have a
treasure room that is protected, and therefore you do not want people
logging into it, you can call:
SetProperty(\"login\", \"/file/name/outside/this/room\");
to have the players login to the room outside.
",({"chapter 28","chapter twenty-eight","28",}):"chapter 28 \"Vendors\"
Building Store Vendors
The Nightmare IV LPC Library
written by Descartes of Borg 950528
This document details the creation of vendor objects, NPC's which buy
and sell items. Note that vendors are NPC's, so everything in the
document on building NPC's applies to vendors. It is recommended that
you be completely familiar with that document before moving on to this
one.
Building vendors is actually quite simple, with very little required
beyond the NPC requirements. In fact, only the following function
calls are unique to vendors:
string SetLocalCurrency(string currency);
string SetStorageRoom(string room);
int SetMaxItems(int num);
int SetVendorType(int vt);
One special note, however, is that the skill \"bargaining\" is extremely
important to vendors. Namely, the higher the bargaining, the harder
it is for players to get decent prices.
*****
string SetLocalCurrency(string curr);
*****
Example: SetLocalCurrency(\"electrum\");
Sets the currency which the vendor will use for doing business. The
currencies should be approved by the approval team.
*****
string SetStorageRoom(string room);
*****
Example: SetStorageRoom(\"/domains/Praxis/horace_storage\");
Identifies the file name of the room in which the vendor will be
storing items for sale. This room should never be accessible to
players.
*****
int SetMaxItems(int num);
*****
Example: SetMaxItems(60);
Sets the maximum number of items a vendor can keep in storage at any
given time. Refer to approval documentation for proper numbers for
this.
*****
int SetVendorType(int type);
*****
Examples:
SetVendorType(VT_WEAPON);
SetVendorType(VT_ARMOUR | VT_WEAPON);
Sets which types of items a vendor will buy and sell. A list of all
vendor types is in /include/vendor_types.h. You may allow a vendor to
sell multiple types using the | operator.
",({"chapter 8","chapter eight","8",}):"chapter 8 \"LPC Basics\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 12 july 1993
CHAPTER 8: The data type \"object\"
8.1 Review
You should now be able to do anything so long as you stick to calling
functions within your own object. You should also know, that at the
bare minimum you can get the create() (or reset()) function in your object
called to start just by loading it into memory, and that your reset()
function will be called every now and then so that you may write the
code necessary to refresh your room. Note that neither of these
functions MUST be in your object. The driver checks to see if the
function exists in your object first. If it does not, then it does not
bother. You are also acquainted with the data types void, int, and string.
7.2 Objects as data types
In this chapter you will be acquainted with a more complex data type,
object. An object variable points to a real object loaded into the
driver's memory. You declare it in the same manner as other data types:
object ob;
It differs in that you cannot use +, -, +=, -=, *, or / (what would it
mean to divide a monster by another monster?). And since efuns like
say() and write() only want strings or ints, you cannot write() or
say() them (again, what would it mean to say a monster?).
But you can use them with some other of the most important efuns on any
LPMud.
8.3 The efun: this_object()
This is an efun which returns an object in which the function being executed
exists. In other words, in a file, this_object() refers to the object your
file is in whether the file gets cloned itself or inherted by another file.
It is often useful when you are writing a file which is getting inherited
by another file. Say you are writing your own living.c which gets
inherited by user.c and monster.c, but never used alone. You want to log
the function set_level() it is a player's level being set (but you do not
care if it is a monster.
You might do this:
void set_level(int x) {
if(this_object()->is_player()) log_file(\"levels\", \"foo\\n\");
level = x;
}
Since is_player() is not defined in living.c or anything it inherits,
just saying if(is_player()) will result in an error since the driver
does not find that function in your file or anything it inherits.
this_object() allows you to access functions which may or may not be
present in any final products because your file is inherited by others
without resulting in an error.
8.4 Calling functions in other objects
This of course introduces us to the most important characteristic of
the object data type. It allows us to access functions in other objects.
In previous examples you have been able to find out about a player's level,
reduce the money they have, and how much hp they have.
Calls to functions in other objects may be done in two ways:
object->function(parameters)
call_other(object, \"function\", parameters);
example:
this_player()->add_money(\"silver\", -5);
call_other(this_player(), \"add_money\", \"silver\", -5);
In some (very loose sense), the game is just a chain reaction of function
calls initiated by player commands. When a player initiates a chain of
function calls, that player is the object which is returned by
the efun this_player(). So, since this_player() can change depending
on who initiated the sequence of events, you want to be very careful
as to where you place calls to functions in this_player(). The most common
place you do this is through the last important lfun (we have mentioned
create() and reset()) init().
8.5 The lfun: init()
Any time a living thing encounters an object (enters a new room, or enters
the same room as a certain other object), init() is called in all of
the objects the living being newly encounters. It is at this point
that you can add commands the player can issue in order to act.
Here is a sample init() function in a flower.
void init() {
::init();
add_action(\"smell_flower\", \"smell\");
}
Ito smell_flower(). So you should have smell_flower() look like this:
1 int smell_flower(string str); /* action functions are type int */
2
3 int smell_flower(string str) {
4 if(str != \"flower\") return 0; /* it is not the flower being smelled */
5 write(\"You sniff the flower.\\n\");
6 say((string)this_player()->GetName()+\" smells the flower.\\n\");
7 this_player()->add_hp(random(5));
8 return 1;
9 }
In line 1, we have our function declared.
In line 3, smell_flower() begins. str becomes whatever comes after the
players command (not including the first white space).
In line 4, it checks to see if the player had typed \"smell flower\". If
the player had typed \"smell cheese\", then str would be \"cheese\". If
it is not in fact \"flower\" which is being smelled, then 0 is returned,
letting the driver know that this was not the function which should
have been called. If in fact the player had a piece of cheese as well
which had a smell command to it, the driver would then call the function
for smelling in that object. The driver will keep calling all functions
tied to smell commands until one of them returns 1. If they all return
0, then the player sees \"What?\"
In line 5, the efun write() is called. write() prints the string which
is passed to it to this_player(). So whoever typed the command here
sees \"You sniff the flower.\"
In line 6, the efun say() is called. say() prints the string which is
doing the sniffing, we have to call the GetName() function
in this_player(). That way if the player is invis, it will say
\"Someone\" (or something like that), and it will also be properly
capitalized.
In line 7, we call the add_hp() function in the this_player() object,
since we want to do a little healing for the sniff (Note: do not
code this object on your mud, whoever balances your mud will shoot you).
In line 8, we return control of the game to the driver, returning 1 to
let it know that this was in fact the right function to call.
8.6 Adding objects to your rooms
And now, using the data type object, you can add monsters to your rooms:
void create() {
::create();
SetProperty(\"light\", 3);
set(\"short\", \"Krasna Square\");
set(\"long\", \"Welcome to the Central Square of the town of Praxis.\\n\");
SetExits( ({ \"/domains/standard/hall\" }), ({ \"east\" }) );
}
void reset() {
object ob;
::reset();
if(present(\"guard\")) return; /* Do not want to add a guard if */
ob = new(\"/std/monster\"); /* one is already here */
ob->SetKeyName(\"guard\");
ob->set(\"id\", ({ \"guard\", \"town guard\" }) );
ob->set(\"short\", \"Town guard\");
ob->set(\"long\", \"He guards Praxis from nothingness.\\n\");
ob->SetGender(\"male\");
ob->set_race(\"human\");
ob->set_level(10);
ob->set_alignment(200);
ob->set_humanoid();
ob->set_hp(150);
ob->set_wielding_limbs( ({ \"right hand\", \"left hand\" }) );
ob->eventMove(this_object());
}
Now, this will be wildly different on most muds. Some, as noted before,
in that object so you have a uniquely configured monster object. The
last act in native muds is to call eventMove() in the monster object to move
it to this room (this_object()). In compat muds, you call the efun
move_object() which takes two parameters, the object to be moved, and the
object into which it is being moved.
// CORRECTION: move_object() does not take two arguments in recent
// versions of MudOS. -Crat 24Feb2007
8.7 Chapter summary
At this point, you now have enough knowledge to code some really nice
stuff. Of course, as I have been stressing all along, you really need
to read the documents on building for your mud, as they detail which
functions exist in which types of objects for you to call. No matter
what your knowledge of the mudlib is, you have enough know-how to
give a player extra things to do like sniffing flowers or glue or whatever.
At this point you should get busy coding stuff. But the moment things
even look to become tedious, that means it is time for you to move to
the next level and do more. Right now code yourself a small area.
Make extensive use of the special functions coded in your mud's
room.c (search the docs for obscure ones no one else seems to use).
Add lots o' neat actions. Create weapons which have magic powers which
gradually fade away. All of this you should be able to do now. Once
this becomes routine for you, it will be time to move on to intermediate
stuff. Note that few people actually get to the intermediate stuff.
If you have played at all, you notice there are few areas on the mud
which do what I just told you you should be able to do. It is not
because it is hard, but because there is a lot of arrogance out there
on the part of people who have gotten beyond this point, and very little
communicating of that knowledge. The trick is to push yourself and
think of something you want to do that is impossible. If you ask someone
in the know how to do X, and they say that is impossible, find out
youself how to code it by experimenting.
George Reese
Descartes of Borg
12 july 1993
borg@hebron.connected.com
Descartes@Nightmare (intermud)
Descartes@Igor (not intermud)
",({"chapter 16","chapter sixteen","16",}):"chapter 16 \"Armor\"
Building Armours
The Nightmare IV LPC Library
written by Descartes of Borg 950430
Armour has changed quite a bit from the days of armour class. The
Nightmare IV LPC Library now uses damage types, which means armour that
is great against one attack may be pathetic against another. In fact,
in building armour, it is important that you keep in mind weaknesses.
Fortunately, armour is by default absolutely pathetic. If you go
making it awesome, chances are that it will not make it through the
approval process. This document is designed to get you started
building armour as well introduce you to the features available to
make unique and interesting armour.
I. Basic Armour
You should be familiar with /doc/build/Items, as armour is just a
special type of item. It therefore has all of the features of regular
items.
The basic armour looks like this:
#include <lib.h> /* see this everywhere */
#include <armour_types.h> /* a listing of armour types */
#include <damage_types.h> /* a listing of damage types */
inherit LIB_ARMOUR; /* the armour inheritable */
static void create() {
armour::create(); /* call create() in armour.c */
SetKeyName(\"rusty helm\");
SetId( ({ \"helm\", \"rusty helm\", \"a rusty helm\" }) );
SetAdjectives( ({ \"rusty\" }) );
SetShort(\"a rusty helm\");
SetLong(\"A rusty helmet which will be better than nothing on your head.\");
SetMass(75);
SetValue(200);
SetDamagePoints(1000);
SetProtection(BLUNT, 4); /* SetProtection() sets the sort of */
SetProtection(BLADE, 3); /* protection for a given damage type */
SetProtection(KNIFE, 3);
SetArmourType(A_HELMET); /* set what kind of armour this is */
}
As you can see, there is very little that you have to do specific to
armour. The only armour specific call you MUST make is
SetArmourType(). Everything else is fluff.
int SetArmourType(int type)
Armour types are found in /include/armour_types.h. The armour type
basically determines where the armour is worn. Each monster,
depending on its race, has for each limb a list of armour types which
may be worn on that limb. For example, most monsters have heads.
Some have two heads. You do not have to worry about this. They know
that they can wear anything that is A_HELMET on their heads. What if
you have something that may not be wearable on all monsters? Like,
for example, you have body armour which should only go on two armed
beings? See SetRestrictLimbs() later. It allows you to restrict
exactly which kinds of limbs can wear the armour.
int SetProtection(int type, int amount);
Without this call, armour is nothing. Just something you wear. This
allows you to make clothes, which may protect against COLD, but do not
do a thing when struck with a sword. Protection is a number between 0
and 100. Refer to approval documentation for details on what levels
are appropriate, as well as for information on mass and value levels.
That's it for the basics!
II. Advanced Function Calls
The Nightmare IV LPC Library armour object is fairly flexible for
allowing you to do interesting things with your armours. In this
section, you will learn about other function calls you can make to
customize your armour.
string *SetRestrictLimbs(string *limbs);
Example:
SetRestrictLimbs( ({ \"right arm\", \"left arm\", \"torso\" }) );
For armours which can only be on certain body configurations, for
example regular armour (A_ARMOUR) should only be worn on people with
the same number of hands, this function allows you to restrict the
armour to being worn only on the limbs you name. If the person trying
to wear the armour does not have one of those limbs, any attempt to
wear fails.
int SetFingers(int num);
Example:
SetFingers(5);
Used for the glove types. If a person has more fingers on the limb on
which they are trying to wear a glove type than the glove has spaces
for, the wear fails.
mixed SetWear(string | function val);
Examples:
SetWear(\"The cloak feels all yucky on you.\");
SetWear( (: CheckArtrell :) );
Allows you to create a special message seen by the person wearing the
item when they wear it if you pass a string. On the other hand, if
you pass a function, it will call that function to see if the person
can wear the item. The function should be of the form:
int WearFunc();
For example:
int CheckArtrell() {
if( (string)this_player()->GetRace() == \"artrell\" ) {
write(\"The rusty helm makes you feel safe.\");
say((string)this_player()->GetName() + \" wears a rusty helm.\");
return 1;
}
else {
write(\"You cannot wear that you bum!\");
return 1;
}
}
III. Function Overrides
The only function of interest that you might want to override is a
function called eventReceiveDamage(). This function is called every
time the armour is hit to see how much of the damage it absorbs. It
looks like this:
int eventReceiveDamage(int type, int strength, int unused, mixed limbs);
This function is called by combat to determine how much damage the
armour absorbs for a given bit of damage being done. It thus should
return how much damage it takes.
You should always at some point call item::eventReceiveDamage() so
that it can do its processing. You do not want to call it, however,
until you determine how much damage you are absorbing unnaturally.
Here is a sample one for an armour that does extra protection for fighters:
int eventReceiveDamage(int type, int strength, int blah, mixed limbs) {
object who_is_wearing;
int x;
if( !(who_is_wearing = environment()) ) /* eek! no one wearing */
return 0;
if( (int)who_is_wearing->ClassMember(\"fighter\") ) /* reduce strength */
x = strength - random(5);
if( x < 1 ) return strength; /* protect against all the damage */
return armour::eventReceiveDamage(type, x, blah, limbs);
}
Keep in mind what eventReceiveDamage() in armour.c is doing. First,
it is modifying the strength of the blow based on the protections you
set with SetProtection(). Then, it is having the armour take damage
based on how much it absorbed. So you need to call
eventReceiveDamage() in armour at the point where you have a value you
want the armour to do its normal stuff with. In the example above, we
wanted to magically protect fighters against a random(5) points of
damage without having the armour take any damage for that. Then if
there is still strength left in the blow, the armour does its normal
protection.
What else can you do with this? Imagine an armour that turns all cold
damage back on the attacker?
int eventReceiveDamage(int type, int strength, int unused, mixed limbs) {
object who_wearing, enemy;
enemy = (object)(who_wearing = previous_object())->GetCurrentEnemy();
if( !enemy || !(type & COLD) )
return armour::eventReceiveDamage(type, strength, unused, limbs);
limbs = enemy->GetTargetLimb(0);
message(\"environment\", \"Your anti-cold throws the frost in your \"
\"enemy's face!\", who_wearing);
message(\"environment\", \"Your cold attack is turned back upon you!\",
enemy);
enemy->eventReceiveDamage(COLD, strength, unused, limbs);
return strength; /* we absorb all of the strength but take no damage */
}
Descartes of Borg
borg@imaginary.com
",({"chapter 37","chapter thirty-seven","37",}):"chapter 37 \"QCS: Modifying things and stuff\"
You should have a firm grasp now on how QCS works
in relation to manipulable objects. Let's look at the
settings for a few special kinds of items:
chairs
------
%^GREEN%^modify stool maxsitters 1%^RESET%^
%^GREEN%^modify stool setmaxcarry 200%^RESET%^
beds
----
%^GREEN%^modify sofa maxsitters 2%^RESET%^
%^GREEN%^modify sofa maxliers 1%^RESET%^
%^GREEN%^modify sofa maxcarry 400%^RESET%^
containers
----------
%^GREEN%^modify box canclose 1%^RESET%^
%^GREEN%^modify box closed 1%^RESET%^
%^GREEN%^modify box locked 1%^RESET%^
%^GREEN%^modify box key magic_skeleton_key%^RESET%^
%^GREEN%^modify box maxcarry 200%^RESET%^
%^GREEN%^modify box setmoney gold 15%^RESET%^
tables
------
%^GREEN%^modify altar maxcarry 300%^RESET%^
%^GREEN%^modify altar maxliers 1%^RESET%^
meals/drinks
------------
%^GREEN%^modify burger mealtype food%^RESET%^
%^GREEN%^modify schlitz mealtype alcohol%^RESET%^
%^GREEN%^modify apple mealstrength 10%^RESET%^
books
-----
%^GREEN%^modify journal title The Orc Within%^RESET%^
%^GREEN%^modify journal source /domains/Orcland/etc/books/journal%^RESET%^
Readable things:
----------------
If you want to be able to \"read thing\", for example, \"read sign\":
%^GREEN%^modify sign defaultread This is a message written on the sign.%^RESET%^
If you want to make a thing on a thing readable, as in
\"read inscription on ring\":
%^GREEN%^modify ring item%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^This is an inscription on the ring. Try 'read inscription on ring'%^RESET%^
%^GREEN%^modify ring read%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^So! We, the spear-Danes%^RESET%^
By default, readabale items are readable by anyone, regardless
of the languages they know. If, however, your item should
only be readable by someone who understands the elvish tongue:
%^GREEN%^modify ring language edhellen%^RESET%^
Miscellaneous:
--------------
To make a key have a 50% chance of breaking when it's used:
%^GREEN%^modify golden key disablechance 50%^RESET%^
To make a room or object immune to resets:
%^GREEN%^modify sentry noclean 1%^RESET%^
To make sure there is only one instance of an object or
NPC loaded at any given time:
%^GREEN%^modify tiamat unique 1%^RESET%^
To make a thing or room immune to the QCS (except for this
command):
%^GREEN%^modify workroom nomodify 1%^RESET%^
To specify what kind of vendor should be allowed to traffic
in this item:
%^GREEN%^modify necklace vendortype treasure%^RESET%^
",({"chapter 33","chapter thirty-three","33",}):"chapter 33 \"QCS: Creation\"
Creation breaks down into three categories. \"Things\", rooms, and
doors. Let's look at things first:
Thing creation:
---------------
In this category we're including NPC's, weapon, armor, unspecial items,
tables, furniture, books and containers. Basically things that actually show
up somewhere. A door is a very special sort of object and does not fall
under this category.
To make a new thing, the syntax looks like this:
create THING FILENAME
The THING is the category of item we are creating. The FILENAME is
the name of the file that will contain this item's configuration data.
You may enter an absolute path, or you may simply enter a filename. If
you enter a filename, QCS will attempt to figure out the best place
for this file. If your current working directory has a name QCS understands
as being compatible with the type of object you are making (for example,
your cwd is /realms/you/npc and you are making an NPC) it will use that.
Otherwise it will search parent and children directories for a
directory name it understands this way. Finally, if it can find no
compatible directories for your file near your cwd, it will put it in
the appropriate directory in your home area (in this case,
/realms/you/area/npc ).
Avoid a relative path. It probably won't work the way you think.
When the command completes, FILENAME will be a file containing the
data for your generic thing, and a copy of that generic thing will appear
in the room you are in.
If, for example, you entered:
%^GREEN%^create npc cowboy%^RESET%^
The room you are in will contain a generic NPC which *does not*
answer to the id of \"cowboy\". This NPC is just a generic NPC whose
filename is (probably) /realms/you/npc/cowboy.c and isn't yet a real
cowboy. You'll need to use the \"modify\" command to make a proper
cowboy out of him.
Room creation
-------------
Naturally, if you create a room, a new room will not appear inside
your current environment. Instead, the syntax of the \"create\" command
is different when you want to create a new room.
You may have noticed that you can't use the \"create\" command to make
a new room adjacent to your workroom. This is for your protection and
my sanity. Files which contain the directive \"SetNoModify(1)\" are
immune to QCS manipulation.
Rooms like your workroom, the default start room, the void room, etc,
are set nomodify. This is because if you screw it up, you will be
sorry, and I just don't want to hear it.
So, suppose then that you're in your sample room (one room east
of your workroom) and you want to make a new room. You might issue
the following command:
%^GREEN%^create room south testroom1%^RESET%^
What this does is copy the room you are in (in this case,
/realms/you/area/sample_room.c) to a new location (perhaps
/realms/you/area/testroom1.c). But the neat thing is, this new room
does not have the same exits as the room you are in. The new room
has just one exit, leading back to where you are.
The net effect of all this is that when you issue this command,
you make a new room in the direction you specify, and this
new room looks just like the room you're in, only the exits are such
that you can travel back and forth between the rooms.
Door creation
-------------
Doors are funny things. In Dead Souls, they aren't objects in the
conventional sense of a thing which occupies a room you're in. Rather,
they are really daemons which attach to adjoining rooms. If that doesn't
make sense to you, don't worry. You're not alone.
The syntax for door creation is much like that for room creation.
After you create that room south of your sample room, you can now create
a door between them:
%^GREEN%^create door south sample_door%^RESET%^
This brings into existence a generic door (closed by default) which
is between the two rooms.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Variable Handling\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: july 5 1993
CHAPTER 6: Variable Handling
6.1 Review
By now you should be able to code some simple objects using your muds standard
object library. Inheritance allows you to use functions defined in those
objects without having to go and define yourself. In addition,
you should know how to declare your own functions. This
chapter will teach you about the basic elements of LPC which will allow you to
define your own functions using the manipulation of variables.
6.2 Values and objects
Basically, what makes objects on the mud different are two things:
1) Some have different functions
2) All have different values
Now, all player objects have the same functions. They are therefore
differentiated by the values they hold. For instance, the player
named \"Forlock\" is different from \"Descartes\" *at least* in that they
have different values for the variable true_name, those being
\"descartes\" and \"forlock\".
Therefore, changes in the game involve changes in the values of the objects
in the game. Functions are used to name specific process for manipulating
values. For instance, the create() function is the function whose
process is specifically to initialize the values of an object.
Within a function, it is specifically things called instructions which are
responsible for the direct manipulation of variables.
6.3 Local and global variables
Like variables in most programming language, LPC variables may be declared
as variables \"local\" to a specific function, or \"globally\" available
to all functions. Local variables are declared inside the function which
will use them. No other function knows about their existence, since
the values are only stored in memory while that function is being executed.
A global variable is available to any function which comes after its
declaration in the object code. Since global variables take up RAM for
the entire existence of the object, you should use them only when
you need a value stored for the entire existence of the object.
Have a look at the following 2 bits of code:
-----
int x;
int query_x() { return x; }
void set_x(int y) { x = y; }
-----
-----
void set_x(int y) {
int x;
x = y;
write(\"x is set to x\"+x+\" and will now be forgotten.\\n\");
}
-----
In the first example, x is declared outside of any functions, and therefore
will be available to any function declared after it. In that example,
x is a global variable.
In the second example, x is declared inside the function set_x(). It
only exists while the function set_x() is being executed. Afterwards,
it ceases to exist. In that example, x is a local variable.
6.4 Manipulating the values of variables
Instructions to the driver are used to manipulate the values of variables.
An example of an instruction would be:
-----
x = 5;
-----
The above instruction is self-explanatory. It assigns to the variable
x the value 5. However, there are some important concepts in involved
in that instruction which are involved in instructions in general.
The first involves the concept of an expression. An expression is
any series of symbols which have a value. In the above instruction,
the variable x is assigned the value of the expression 5. Constant
values are the simplest forms in which expressions can be put. A constant
is a value that never changes like the int 5 or the string \"hello\".
The last concept is the concept of an operator. In the above example,
the assignment operator = is used.
There are however many more operators in LPC, and expressions can get
quite complex. If we go up one level of complexity, we get:
-----
y = 5;
x = y +2;
-----
The first instruction uses the assignment operator to assign the value
of the constant expression 5 to the variable y. The second one
uses the assignment operator to assign to x the value of the expression
(y+2) which uses the addition operator to come up with a value which
is the sum of the value of y and the value of the constant expression 2.
Sound like a lot of hot air?
In another manner of speaking, operators can be used to form complex
expressions. In the above example, there are two expressions in the
one instruction x = y + 2;:
1) the expression y+2
2) the expression x = y + 2
As stated before, all expressions have a value. The expression
y+2 has the value of the sum of y and 2 (here, 7);
The expression x = y + 2 *also* has the value of 7.
So operators have to important tasks:
1) They *may* act upon input like a function
2) They evaluate as having a value themselves.
Now, not all operators do what 1 does. The = operators does act upon
the value of 7 on its right by assigning that value to x. The operator
+ however does nothing. They both, however, have their own values.
6.5 Complex expressions
As you may have noticed above, the expression x = 5 *itself* has a value
of 5. In fact, since LPC operators themselves have value as expressions,
they cal allow you to write some really convoluted looking nonsense like:
i = ( (x=sizeof(tmp=users())) ? --x : sizeof(tmp=children(\"/std/monster\"))-1)
which says basically:
assing to tmp the array returned by the efun users(), then assign to x
the value equal to the number of elements to that array. If the value
of the expression assigning the value to x is true (not 0), then assign
x by 1 and assign the value of x-1 to i. If x is false though,
then set tmp to the array returned by the efun children(), and then
assign to i the value of the number of members in the array tmp -1.
Would you ever use the above statement? I doubt it. However you might
see or use expressions similar to it, since the ability to consolidate
so much information into one single line helps to speed up the execution of
your code. A more often used version of this property of LPC operators
would be something like:
x = sizeof(tmp = users());
while(i--) write((string)tmp[i]->GetKeyName()+\"\\n\");
instead of writing something like:
tmp = users();
x = sizeof(tmp);
for(i=0; i<x; i++) write((string)tmp[i]->GetKeyName()+\"\\n\");
Things like for(), while(), arrays and such will be explained later.
But the first bit of code is more concise and it executed faster.
NOTE: A detailed description of all basic LPC operators follows the chapter
summary.
6.6 Chapter Summary
You now know how to declare variables and understand the difference between
declaring and using them globally or locally. Once you become familiar
with your driver's efuns, you can display those values in many different
ways. In addition, through the LPC operators, you know how to change
and evaluate the values contained in variables. This is useful of course
in that it allows you to do something like count how many apples have
been picked from a tree, so that once all apples have been picked, no
players can pick more. Unfortunately, you do not know how to have
code executed in anything other than a linera fashion. In other words,
hold off on that apple until the next chapter, cause you do not know
how to check if the apples picked is equal to the number of apples in the
tree. You also do not know about the special function init() where you
give new commands to players. But you are almost ready to code a nice,
fairly complex area.
6.7 LPC operators
This section contains a detailed listing of the simpler LPC operators,
including what they do to the values they use (if anything) and the value
that they have.
The operators described here are:
= + - * / % += -= *= /= %=
-- ++ == != > < >= <= ! && ||
-> ? :
Those operators are all described in a rather dry manner below, but it is best
to at least look at each one, since some may not behave *exactly* as
you think. But it should make a rather good reference guide.
= assignment operator:
example: x = 5;
value: the value of the variable on the *left* after its function is done
explanation: It takes the value of any expression on the *right* and
assigns it to the variable on the *left*. Note that you must use
a single variable on the left, as you cannot assign values to
constants or complex expressions.
+ addition operator:
example: x + 7
value: The sum of the value on the left and the value on the right
exaplanation: It takes the value of the expression on the right and
adds it to the value of the expression on the left. For values
of type int, this means the numerical sum. For strings,
it means that the value on the right is stuck onto the value on
the left (\"ab\" is the value of \"a\"+\"b\"). This operator does not
modify any of the original values (i.e. the variable x from
above retains its old value).
- subtraction operator:
example: x - 7
value: the value of the expression on the left reduced by the right
explanation: Same characteristics as addition, except it subtracts.
With strings: \"a\" is the value of \"ab\" - \"b\"
* multiplication operator:
example: x*7
value and explanation: same as with adding and subtracting except
this one performs the math of multiplication
/ division operator:
example: x/7
value and explanation: see above
+= additive assignment operator:
example: x += 5
value: the same as x + 5
exaplanation: It takes the value of the variable on the left
and the value of the expression on the right, adds them together
and assigns the sum to the variable on the left.
example: if x = 2... x += 5 assigns the value
7 to the variable x. The whole expression
has the value of 7.
-= subtraction assignment operator
example: x-=7
value: the value of the left value reduced by the right value
examplanation: The same as += except for subtraction.
*= multiplicative assignment operator
example: x *= 7
value: the value of the left value multiplied by the right
explanation: Similar to -= and += except for addition.
/= division assignment operator
example: x /= 7
value: the value of the variable on the left divided by the right value
explanation: similar to above, except with division
++ post/pre-increment operators
examples: i++ or ++i
values:
i++ has the value of i
++i has the value of i+1
explanation: ++ changes the value of i by increasing it by 1.
However, the value of the expression depends on where you
place the ++. ++i is the pre-increment operator. This means
that it performs the increment *before* giving a value.
i++ is the post-ncrement operator. It evalutes before incrementing
i. What is the point? Well, it does not much matter to you at
this point, but you should recognize what it means.
-- post/pre-decrement operators
examples: i-- or --i
values:
i-- the value of i
--i the value of i reduced by 1
explanation: like ++ except for subtraction
== equality operator
example: x == 5
value: true or false (not 0 or 0)
explanation: it does nothing to either value, but
it returns true if the 2 values are the same.
It returns false if they are not equal.
!= inequality operator
example: x != 5
value: true or false
explanation returns true if the left expression is not equal to the right
expression. It returns fals if they are equal
> greater than operator
example: x > 5
value: true or false
explanation: true only if x has a value greater than 5
false if the value is equal or less
< less than operator
>= greater than or equal to operator
<= less than or equal to operator
examples: x < y x >= y x <= y
values: true or false
explanation: similar as to > except
< true if left is less than right
>= true if left is greater than *or equal to* right
<= true if the left is less than *or equal to* the right
&& logical and operator
|| logical or operator
examples: x && y x || y
values: true or false
explanation: If the right value and left value are non-zero, && is true.
If either are false, then && is false.
For ||, only one of the values must be true for it to evaluate
as true. It is only false if both values indeed
are false
! negation operator
example: !x
value: true or false
explanation: If x is true, then !x is false
If x is false, !x is true.
A pair of more complicated ones that are here just for the sake of being
here. Do not worry if they utterly confuse you.
-> the call other operator
example: this_player()->GetKeyName()
value: The value returned by the function being called
explanation: It calls the function which is on the right in the object
on the left side of the operator. The left expression *must* be
an object, and the right expression *must* be the name of a function.
If not such function exists in the object, it will return 0 (or
more correctly, undefined).
? : conditional operator
example: x ? y : z
values: in the above example, if x is try, the value is y
if x is false, the value of the expression is z
explanation: If the leftmost value is true, it will give the expression as
a whole the value of the middle expression. Else, it will give the
expression as a whole the value of the rightmost expression.
A note on equality: A very nasty error people make that is VERY difficult
to debug is the error of placing = where you mean ==. Since
operators return values, they both make sense when being evaluated.
In other words, no error occurs. But they have very different values. For example:
if(x == 5) if(x = 5)
The value of x == 5 is true if the value of x is 5, false othewise.
The value of x = 5 is 5 (and therefore always true).
The if statement is looking for the expression in () to be either true or false,
so if you had = and meant ==, you would end up with an expression that is
always true. And you would pull your hair out trying to figure out
why things were not happening like they should :)
",({"chapter 34","chapter thirty-four","34",}):"chapter 34 \"QCS: Modification of NPC's\"
In the previous chapter we learned how to make a generic object.
Now that we have it, what to do with it?
It's important to keep in mind that the generic thing now in
front of you isn't just a clone from a template file. If the command
you used was \"create npc cowboy\", there is now a file (probably)
called /realms/you/area/npc/cowboy.c that contains the code for the
creature in front of you.
However, this poor beast has the most uninteresting of features.
It is in fact so boring that it responds only to its generic type.
Such that \"examine cowboy\" or \"kill tex\" won't work. You'll need to
\"look at npc\".
Accordingly, any modification commands need to be made referring
to the new thing with an it responds to, until such a time as you
change the id to suit your tastes. Let's carry on with the example
of our generic npc. To make a cowboy out of him, we can either
change his name, or his id, or both. Let's start with his name:
%^GREEN%^modify npc name cowboy%^RESET%^
This makes the SetKeyName() directive in cowboy.c use \"cowboy\"
as its argument, effectively allowing you to address this npc as \"cowboy\"
from now on. Now you can \"look at cowboy\" with some results.
Obviously our NPC isn't *just* a cowboy. He's also a human, a dude,
and his name is Tex. How do we make him respond to all of these nouns?
%^GREEN%^modify cowboy id%^RESET%^
You'll notice there are no arguments following the word \"id\". Setting
a thing's id is different from most other settings. If you'll think back
to the LPC datatypes chapter of this manual (you did read the LPC
chapters, didn't you?) you'll remember that some information about
objects is in the form of strings (\"cowboy\"), some is in the form of
integers (the cowboy's health points, for example) and some is in the
form of arrays, which are a group of data points. In this example we
want the cowboy's id to be an array, because of the many ways we might
want to address him. Therefore, we want the SetId directive in his
file to look like this:
SetId( ({\"human\", \"dude\", \"tex\" }) );
You might think that QCS should be able to accept multiple values
like this on a line, perhaps with \"modify cowboy id human dude tex\" as
the command.
But what if you want this npc to be a \"Boy Named Sue\"? How would
you accommodate id's that contain spaces? In designing QCS, I considered
having escape characters to allow for such things, but ultimately
reasoned that this was just way too much mess. Instead, SetId, and
other directives that take arrays as arguments, are handled by entering
a query session. Below is an example of what it might look like. The
example is necessarily messy because I am including both the queries
and the responses.
---------------------------------------------------
> %^GREEN%^modify npc name cowboy%^RESET%^
Indenting file...
\"/tmp/indent.1136206130.tmp.dat\" 15 lines 420 bytes
Exit from ed.
> %^GREEN%^modify cowboy id%^RESET%^
This setting takes multiple values. If you have no more values to enter, then
enter a dot on a blank line. To cancel, enter a single q on a blank line.
You may now enter the next value. So far, it is blank.
If you're done entering values, enter a dot on a blank line.
%^GREEN%^dude%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^human%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^tex%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^boy named sue%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\",
\"boy named sue\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^.%^RESET%^
Entries complete. Final array is: ({ \"dude\", \"human\", \"tex\", \"boy named sue\" })
Indenting file...
\"/tmp/indent.1136206156.tmp.dat\" 19 lines 459 bytes
Exit from ed.
/open/1136206138: Ok
/realms/cratylus/area/npc/cowboy: Ok
SetId modification complete.
> %^GREEN%^exa tex%^RESET%^
Other than being human, this npc is entirely unremarkable.
The male human is in top condition.
---------------------------------------------------
If you were now to examine Tex's code (with the command \"about tex\")
you'd see that his SetId directive now looks like this:
SetId( ({\"dude\", \"human\", \"tex\", \"boy named sue\"}) );
Other NPC features take arrays also. SetAdjectives is one. You
might enter this (mud output omitted for clarity):
%^GREEN%^modify tex adjectives%^RESET%^
%^GREEN%^dusty%^RESET%^
%^GREEN%^hardy%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^look at dusty cowboy%^RESET%^
There are two other directives that require queries. Things and
NPC's can be looked at, but they can also be smelled and listened
to, if you add SetSmell and SetListen. The syntax is:
%^GREEN%^modify tex smell%^RESET%^
And you will then be asked a question about keys and mappings.
Understanding mappings is important, but for now you just need to
understand that you are being asked *two* separate questions:
1) What on the cowboy is being smelled/listened to?
2) What is the smell/sound?
What this means is that your input will look something like
this:
%^GREEN%^modify tex smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex smells of sweat and manure.
What happens is this:
- You enter the modify command.
- You enter the word \"default\" to indicate this is Tex's general smell.
- You enter a dot to indicate that you are done specifying what
part of Tex is being smelled.
- You then specify the smell.
This may seem odd until you realize you can also add smells/listens to
parts of things. Not on NPC's, though. We'll look at this more closely in
later chapters. For now, just use the syntax as shown above. For adding
a listen to the cowboy, it works the same way:
%^GREEN%^modify tex listen%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex seems to be humming a jaunty melody.
Other features of an NPC do not take arrays, so one-line commands
will do. For example:
%^GREEN%^modify cowboy long This is a cowboy who calls himself Tex, but is in fact a Boy Named Sue.%^RESET%^
%^GREEN%^modify cowboy short a cowboy%^RESET%^
%^GREEN%^modify cowboy level 5%^RESET%^
%^GREEN%^modify cowboy class fighter%^RESET%^
%^GREEN%^modify tex currency gold 1%^RESET%^
%^GREEN%^modify tex currency silver 12%^RESET%^
%^GREEN%^modify tex skill bargaining 5%^RESET%^
%^GREEN%^modify tex skill projectile attack 7%^RESET%^
%^GREEN%^modify tex stat strength 33%^RESET%^
%^GREEN%^modify tex property nice guy 1%^RESET%^
%^GREEN%^modify tex healthpoints 150%^RESET%^
%^GREEN%^modify tex maxhealthpoints 170%^RESET%^
%^GREEN%^modify tex melee 0%^RESET%^
%^GREEN%^modify tex unique 1%^RESET%^
If you now issue the \"about tex\" command you will see that all
the changes you made have been put into the file.
You may have noticed the \"melee\" keyword. Dead Souls 2 NPC's come
in various shapes and sizes, and some of them shouldn't wield weapons. A
wolf with a battle axe would be a strange sight indeed. However, the
default combat system makes unarmed creatures extremely vulnerable in
combat.
To make an NPC combat-capable without weapons, use the new SetMelee
directive. SetMelee(1) makes the NPC capable of proper unarmed combat.
SetMelee(0) makes the NPC a weak opponent if unarmed.
NPC's will generally try to bite during unarmed combat. If this
is beneath the capability or dignity of your NPC, you can prevent
this with:
%^GREEN%^modify tex canbite 0%^RESET%^
If your NPC should try to escape when the battle isn't going
his way, the wimpy settings should do:
%^GREEN%^modify tex wimpy 30%^RESET%^
%^GREEN%^modify tex wimpycommand climb ladder%^RESET%^
If you don't specify a wimpy command, Tex will leave the
room through a random exit. In this case, when Tex's health
is down to 30% and he is in combat, he will try to climb a ladder.
Some NPC's are designed to travel about. To enable this
feature, use the wanderspeed directive:
%^GREEN%^modify tex wanderspeed 5%^RESET%^
If you want him to travel more quickly, use a
lower number. By default, wandering NPC's only wander in rooms
that have already been loaded into memory. They avoid loading
rooms because loading a bunch of rooms that only the NPC
will ever see is a waste of your mud's resources.
However, if you *do* want your NPC to wander in an
unrestricted manner, regardless of whether a room is loaded,
use the permitload directive:
%^GREEN%^modify tex permitload 1%^RESET%^
By default, NPC's stand up when they can. This is
so that if they collapse during combat, they try to
get back up once they are able to do so.
If you prefer that your NPC maintain some other
posture, you can set that posture, then disable
autostanding like this:
%^GREEN%^modify tex posture lying%^RESET%^
%^GREEN%^modify tex autostand 0%^RESET%^
If he's especially lazy, you can have him take
a nap this way:
%^GREEN%^modify tex sleeping 10%^RESET%^
Which will have him wake up after about a minute.
However, note that if you've disabled autostanding,
he will remain lying down after he wakes up.
If the NPC should be hostile, that is, he should
attack any creatures that it sees enter a room,
SetEncounter should do it:
%^GREEN%^modify tex encounter 100%^RESET%^
This means that if the creature it sees has a
charisma score of less than 100 (which should pretty
much be always true), Tex will try to kill it. You
can do some fancy stuff with SetEncounter, such
as only attacking orcs, or actually doing something
friendly, but to do so you can't use QCS. Read
the NPC and Sentients chapter in the Creator's
Manual for details on how to code such stuff.
If the NPC is a golem or other such non-biological
creature, it may be useful to specify what they are
made of. The SetComposition setting for a clay
golem might look like this:
%^GREEN%^modify golem composition clay%^RESET%^
If it happens to be a golem that does not believe
in violence as a solution to problems, you can
make refuse to hurt others with the following:
%^GREEN%^modify golem pacifist 1%^RESET%^
Vendors:
-------
Vendors are a special kind of NPC that can sell stuff.
Along with the standard NPC settings, vendors have
the following:
SetStorageRoom specifies where the vendor's stock
is stored. If a valid room is specified, anything
in that room can be sold by the vendor.
SetLocalCurrency specifies the type of currency,
such as gold or silver, that the vendor accepts.
SetMaxItems is the maximum number of items in
the storeroom that the vendor is permitted to sell.
SetVendorType specifies the kind of stuff the vendor can
trade in. \"all\" allows him to buy and sell whatever.
But if he is a weapon vendor, he can't trade in armor,
etc. See /include/vendor_types.h for the available
vendor types. To have a \"multi-type\" vendor, you'll have to
code it by hand. The result of that looks something
like this:
SetVendorType( VT_TREASURE | VT_ARMOR | VT_HERBS );
Barkeeps:
--------
Like vendors, barkeeps sell stuff, but they are
limited to selling food and drink.
Unlike vendors, barkeeps have no limitation on
the amount of stuff they can sell. They also do
not have a storeroom. The stuff they can sell is
specified in their SetMenu directive, like this:
%^GREEN%^clone woody%^RESET%^
You clone a generic barkeep (/realms/temujin/area/npc/woody.c).
%^GREEN%^modify woody menu%^RESET%^
If you don't understand these questions, type the letter q on a blank line and hit enter.
Please enter the first key element for this mapping:
%^GREEN%^bourbon%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^whiskey%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^.%^RESET%^
Please enter the value for key ({ \"bourbon\", \"whiskey\" }):
%^GREEN%^/domains/town/meals/bourbon%^RESET%^
Barkeeps also have the SetLocalCurrency directive
to specify the currency they accept.
",({"chapter 33","chapter thirty-three","33",}):"chapter 33 \"QCS: Creation\"
Creation breaks down into three categories. \"Things\", rooms, and
doors. Let's look at things first:
Thing creation:
---------------
In this category we're including NPC's, weapon, armor, unspecial items,
tables, furniture, books and containers. Basically things that actually show
up somewhere. A door is a very special sort of object and does not fall
under this category.
To make a new thing, the syntax looks like this:
create THING FILENAME
The THING is the category of item we are creating. The FILENAME is
the name of the file that will contain this item's configuration data.
You may enter an absolute path, or you may simply enter a filename. If
you enter a filename, QCS will attempt to figure out the best place
for this file. If your current working directory has a name QCS understands
as being compatible with the type of object you are making (for example,
your cwd is /realms/you/npc and you are making an NPC) it will use that.
Otherwise it will search parent and children directories for a
directory name it understands this way. Finally, if it can find no
compatible directories for your file near your cwd, it will put it in
the appropriate directory in your home area (in this case,
/realms/you/area/npc ).
Avoid a relative path. It probably won't work the way you think.
When the command completes, FILENAME will be a file containing the
data for your generic thing, and a copy of that generic thing will appear
in the room you are in.
If, for example, you entered:
%^GREEN%^create npc cowboy%^RESET%^
The room you are in will contain a generic NPC which *does not*
answer to the id of \"cowboy\". This NPC is just a generic NPC whose
filename is (probably) /realms/you/npc/cowboy.c and isn't yet a real
cowboy. You'll need to use the \"modify\" command to make a proper
cowboy out of him.
Room creation
-------------
Naturally, if you create a room, a new room will not appear inside
your current environment. Instead, the syntax of the \"create\" command
is different when you want to create a new room.
You may have noticed that you can't use the \"create\" command to make
a new room adjacent to your workroom. This is for your protection and
my sanity. Files which contain the directive \"SetNoModify(1)\" are
immune to QCS manipulation.
Rooms like your workroom, the default start room, the void room, etc,
are set nomodify. This is because if you screw it up, you will be
sorry, and I just don't want to hear it.
So, suppose then that you're in your sample room (one room east
of your workroom) and you want to make a new room. You might issue
the following command:
%^GREEN%^create room south testroom1%^RESET%^
What this does is copy the room you are in (in this case,
/realms/you/area/sample_room.c) to a new location (perhaps
/realms/you/area/testroom1.c). But the neat thing is, this new room
does not have the same exits as the room you are in. The new room
has just one exit, leading back to where you are.
The net effect of all this is that when you issue this command,
you make a new room in the direction you specify, and this
new room looks just like the room you're in, only the exits are such
that you can travel back and forth between the rooms.
Door creation
-------------
Doors are funny things. In Dead Souls, they aren't objects in the
conventional sense of a thing which occupies a room you're in. Rather,
they are really daemons which attach to adjoining rooms. If that doesn't
make sense to you, don't worry. You're not alone.
The syntax for door creation is much like that for room creation.
After you create that room south of your sample room, you can now create
a door between them:
%^GREEN%^create door south sample_door%^RESET%^
This brings into existence a generic door (closed by default) which
is between the two rooms.
",({"chapter 9","chapter nine","9",}):"chapter 9 \"Introduction to Intermediate LPC\"
Intermediate LPC
Descartes of Borg
Novermber 1993
Chapter 1: Introduction
1.1 LPC Basics
Anyone reading this textbook should either have read the textbook LPC
Basics or be familiar enough with mud realm coding such that not only are
they capable of building rooms and other such objects involved in area
coding, but they also have a good idea of what is going on when the code
they write is executing. If you do not feel you are at this point, then go
back and read LPC Basics before continuing. If you do so, you will find
that what you read here will be much more meaningful to you.
1.2 Goals of This Textbook
The introductory textbook was meant to take people new to LPC from
knowing nothing to being able to code a nice realm on any LPMud. There
is naturally much more to LPC and to LPMud building, however, than
building rooms, armours, monsters, and weapons. As you get into more
complicated concepts like guilds, or desire to do more involved things with
your realm, you will find the concepts detailed in LPC Basics to be lacking
in support for these projects. Intermediate LPC is designed to take you
beyond the simple realm building process into a full knowledge of LPC for
functioning as a realm builder on an LPMud. The task of mudlib building
itself is left to a later text. After reading this textbook and working through
it by experimenting with actual code, the reader should be able to code game
objects to fit any design or idea they have in mind, so long as I have been
successful.
1.3 An Overview
What more is there? Well many of you are quite aware that LPC supports
mappings and arrays and have been asking me why those were not detailed
in LPC Basics. I felt that those concepts were beyond the scope of what I
was trying to do with that textbook and were more fitting to this textbook.
But new tools are all fine and dandy, what matters, however, is what you
can do with those tools. The goal of LPC Basics was to get you to building
quality LPMud realms. Mappings and arrays are not necessary to do that.
The goal of this book is to allow you to code any idea you might want to
code in your area. That ability requires the knowledge of mappings and
arrays.
Any idea you want to code in an LPMud is possible. LPC is a language
which is amazingly well suited to this task. All that prevents you from
coding your ideas is your knowledge of LPC or an inadequate mudlib or
your mud<75>s theme or administrative policies. This textbook cannot make
the mudlib you are working with any better, and it cannot change the mud
theme or the mud<75>s administrative policies. Never once think that LPC is
incapable of doing what you want to do. If your idea is prevented by
administrative policies or themes, then it is simply not an idea for your
current mud. If the mudlib is inadequate, talk to the people in charge of
your mudlib about what can be done at the mudlib level to facilitate it. You
would be surprised by what is actually in the mudlib you did not know
about. More important, after reading this textbook, you should be able to
read all of the mudlib code in your mud<75>s mudlib and understand what is
going on at each line in the mudlib code. You may not as yet be able to
reproduce that code on your own, but at least you can understand what is
going on at the mudlib level.
This textbook starts out with a discussion about what the LPMud driver is
doing. One nice thing about this textbook, in general it is completely driver
and mudlib independent (excepting for the Dworkin Game Driver). The
chapter on the game driver does not get into actual implementation, but
instead deals with what all game drivers basically do in order to run the
mud.
Next I discuss those magic topics everyone wants to know more about,
arrays and mappings. Mappings may be simultaneously the easiest and
most difficult data type to understand. Since they are sort of complex arrays
in a loose sense, you really need to understand arrays before discussing
them. All the same, once you understand them, they are much easier than
arrays to use in real situations. At any rate, spend most of your time
working with that chapter, because it is probably the most difficult, yet most
useful chapter in the book.
After that follows a brief chapter on the LPC pre-compiler, a tool you can
use for sorting out how your code will look before it gets sent to the
compiler. Despite my horrid intro to it here, this chapter is perhaps the
easiest chapter in the textbook. I put it after the mappings and arrays
chapter for exactly that reason.
Strings are re-introduced next, going into more detail with how you can do
such things as advanced command handling by breaking up strings. Once
you understand arrays fairly well, this chapter should be really simple.
The next chapter is the second most important in the book. It may be the
most important if you ever intend to go beyond the intermediate stage and
dive into mudlib coding. That chapter involves the complex ideas behind
LPC inheritance. Since the goal of this textbook is not to teach mudlib
programming, the chapter is not a detailed discussion on object oriented
programming. Understanding this chapter, however, will give you some
good insights into what is involved with object oriented programming, as
well as allow you to build more complex objects by overriding functions
and defining your own base classes.
Finally, the textbook ends with a simple discussion of code debugging.
This is not an essential chapter, but instead it is meant as more of an
auxiliary supplement to what the knowledge you have accumulated so far.
1.4 Not Appearing in This Textbook
Perhaps what might appear to some as the most glaring omission of this
textbook is largely a political omission, shadows. Never have I ever
encountered an example of where a shadow was either the best or most
effecient manner of doing anything. It does not follow from that, however,
that there are no uses for shadows. My reasoning for omitting shadows
from this textbook is that the learner is best served by learning the concepts
in this textbook first and having spent time with them before dealing with
the subject of shadows. In that way, I feel the person learning LPC will be
better capable of judging the merits of using a shadow down the road. I
will discuss shadows in a future textbook.
If you are someone who uses shadows some or a lot, please do not take the
above paragraph as a personal attack. There may be some perfectly valid
uses for shadows somewhere which I have yet to encounter. Nevertheless,
they are not the ideal way to accomplish any given task, and therefore they
are not considered for the purposes of this textbook an intermediate coding
tool.
I have also omitted discussions of security and object oriented
programming. Both are quite obviously mudlib issues. Many people,
however, might take exception with my leaving out a discussion of object
oriented programming. I chose to leave that for a later text, since most area
builders code for the creativity, not for the computer science theory. In both
the intermediate and beginner textbooks, I have chosen only to discuss
theory where it is directly applicable to practical LPC programming. For
people who are starting out green in LPC and want to code the next great
mudlib, perhaps theory would be more useful. But for the purposes of this
book, a discussion of object oriented programming is simply a snoozer. I
do plan to get heavy into theory with the next textbook.
1.5 Summary
LPC is not difficult to learn. It is a language which, although pathetic
compared to any other language for performing most computer language
tasks, is incredibly powerful and unequalled for the tasks of building an
area in MUD type games. For the beginner, it allows you to easily jump in
and code useful objects without even knowing what you are doing. For the
intermediate person, it allows you to turn any idea you have into textual
virtual reality. And for the advanced person, it<69>s object oriented features
can allow you to build one of the most popular games on the internet. What
you can do is simply limited by how much you know. And learning more
does not require a computer science degree.
Copyright (c) George Reese 1993
",({"chapter 21","chapter twenty-one","21",}):"chapter 21 \"Meals\"
Building Food and Drink Objects
The Nightmare IV LPC Library
written by Descartes of Borg 950603
This document details the creation of food and drinks using the
Nightmare LPC Library. The creation of barkeeper objects requires you
to be able to build these objects, so make sure you understand what is
going on in here before moving on to barkeepers.
To create food or drink, you inherit from the standard meal object
/lib/meal.c For example:
#include <lib.h>
inherit LIB_MEAL;
You have access to the same functions you have in generic items when
you build food and drinks. In particular, you should be sure to call
the following:
SetKeyName()
SetId()
SetShort()
SetLong()
SetMass()
Note that SetValue() does NOTHING for food and drinks. Value is
automatically determined by the strength of the item.
The following function calls are specific to \"meal\" objects:
int SetMealType(int types);
int SetStrength(int strength);
mixed *SetMealMessages(function f);
OR
mixed *SetMealmessages(string mymsg, string othermsg);
string SetEmptyName(string str);
string SetEmptyShort(string str);
string SetEmptyLong(string str);
string SetEmptyItem(string str);
You must call SetMealType(), SetStrength(), and SetMealMessages().
If you call SetEmptyItem(), you do not need to call the functions
SetEmptyName(), SetEmptyShort(), SetEmptyLong(). On the other hand,
if you do not call SetEmptyItem(), you do need to set the other three.
*****
int SetMealType(int types)
*****
Example: SetMealType(MEAL_FOOD);
For meal objects, you must do:
#include <meal_types.h>
This includes all od the definitions for the meal types in
/include/meal_types.h into your food or drink. You need these
definitions when setting what type of meal object this is. The types
are:
MEAL_FOOD
MEAL_DRINK
MEAL_CAFFEINE
MEAL_ALCOHOL
MEAL_POISON
In general, almost anything you create will be at least either
MEAL_FOOD or MEAL_DRINK. You can add onto it using the | operator.
For example, to make an alcoholic drink:
SetMealType(MEAL_DRINK | MEAL_ALCOHOL);
This makes something a drink and an alcoholic drink. You want to
stick poison in it?
SetMealType(MEAL_DRINK | MEAL_ALCOHOL | MEAL_POISON);
*****
int SetStrength(int x)
*****
Example: SetStrength(20);
This sets how strong your food or drink is. It affects things like
which people can drink or eat it and how much the drink or food costs.
Refer to balance documents to see what is good.
*****
varargs mixed *SetMealMessages(function|string, string)
*****
Examples:
SetMealMessages((: call_other(find_object(\"/some/object\"),\"drink\") :));
SetMealmessages(\"You drink your beer.\", \"$N drinks $P beer.\");
You can pass a single argument, which is a function to be called.
This function will be called after the person has drank or eaten the
meal. It gives you a chance to do some bizarre messaging and such.
If you pass two strings, the first string is used as a message to send
to the player doing the drinking, and the second is what everyone else
sees. To make the message versatile, you can put in the following
place holders:
$N the name of the drinker/eater
$P his/her/its
For example:
$N drinks $P beer.
might resolve to:
Descartes drinks his beer.
*****
string SetEmptyName(string str)
*****
Example: SetEmptyName(\"bottle\");
Sets an id from the empty container of drinks. This need not be set
for food.
*****
string SetEmptyShort(string str)
*****
Example: SetEmptyShort(\"an empty bottle\")
Sets what the short description of the empty container is for anything
that is of type MEAL_DRINK.
*****
string SetEmptyLong(string str)
*****
Example: SetEmptyLong(\"A brown bottle that used to contain beer.\");
Sets the long description for the empty container for drink objects.
*****
string SetEmptyItem(string str)
*****
Example: SetEmptyItem(\"/domains/Praxis/etc/empty_bottle\")
Instead of cloning a generic empty object and setting the other empty
functions, you can create a special empty container which gets given
to the player after they drink a drink object. Not relevant to food.
",({"chapter 12","chapter twelve","12",}):"chapter 12 \"The LPC Pre-Compiler\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 4: The LPC Pre-Compiler
4.1 Review
The previous chapter was quite heavy, so now I will slow down a bit so
you can digest and play with mappings and arrays by taking on the
rather simple topic of the LPC pre-compiler. By this point, however,
you should well understand how the driver interacts with the mudlib and
be able to code objects which use call outs and heart beats. In addition,
you should be coding simple objects which use mappings and arrays,
noting how these data types perform in objects. It is also a good idea to
start looking in detail at the actual mudlib code that makes up your mud.
See if you understand everything which is going on in your mudlibs
room and monster codes. For things you do not understand, ask the
people on your mud designated to answer creator coding questions.
Pre-compiler is actually a bit of a misnomer since LPC code is never
truly compiled. Although this is changing with prototypes of newer
LPC drivers, LPC drivers interpret the LPC code written by creators
rather than compile it into binary format. Nevertheless, the LPC pre-
compiler functions still perform much like pre-compilers for compiled
languages in that pre-compiler directives are interpreted before the driver
even starts to look at object code.
4.2 Pre-compiler Directives
If you do not know what a pre-compiler is, you really do not need to
worry. With respect to LPC, it is basically a process which happens
before the driver begins to interpret LPC code which allows you to
perform actions upon the entire code found in your file. Since the code
is not yet interpreted, the pre-compiler process is involved before the file
exists as an object and before any LPC functions or instructions are ever
examined. The pre-compiler is thus working at the file level, meaning
that it does not deal with any code in inherited files.
The pre-compiler searches a file sent to it for pre-compiler directives.
These are little instructions in the file meant only for the pre-compiler
and are not really part of the LPC language. A pre-compiler directive is
any line in a file beginning with a pound (#) sign. Pre-compiler
directives are generally used to construct what the final code of a file will
look at. The most common pre-compiler directives are:
#define
#undefine
#include
#ifdef
#ifndef
#if
#elseif
#else
#endif
#pragma
Most realm coders on muds use exclusively the directives #define and
#include. The other directives you may see often and should understand
what they mean even if you never use them.
The first pair of directives are:
#define
#undefine
The #define directive sets up a set of characters which will be replaced
any where they exist in the code at precompiler time with their definition.
For example, take:
#define OB_USER \"/std/user\"
This directive has the pre-compiler search the entire file for instances of
OB_USER. Everywhere it sees OB_USER, it replaces with \"/std/user\".
Note that it does not make OB_USER a variable in the code. The LPC
interpreter never sees the OB_USER label. As stated above, the pre-
compiler is a process which takes place before code interpretation. So
what you wrote as:
#define OB_USER \"/std/user\"
void create() {
if(!file_exists(OB_USER+\".c\")) write(\"Merde! No user file!\");
else write(\"Good! User file still exists!\");
}
would arrive at the LPC interpreter as:
void create() {
if(!file_exists(\"/std/user\"+\".c\")) write(\"Merde! No user file!\");
else write(\"Good! User file still exists!\");
}
Simply put, #define just literally replaces the defined label with whatever
follows it. You may also use #define in a special instance where no
value follows. This is called a binary definition. For example:
#define __NIGHTMARE
exists in the config file for the Nightmare Mudlib. This allows for pre-
compiler tests which will be described later in the chapter.
The other pre-compiler directive you are likely to use often is #include.
As the name implies, #include includes the contents of another file right
into the file being pre-compiled at the point in the file where the directive
is placed. Files made for inclusion into other files are often called header
files. They sometimes contain things like #define directives used by
multiple files and function declarations for the file. The traditional file
extension to header files is .h.
Include directives follow one of 2 syntax's:
#include <filename>
#include \"filename\"
If you give the absolute name of the file, then which syntax you use is
irrelevant. How you enclose the file name determines how the pre-
compiler searches for the header files. The pre-compiler first searches in
system include directories for files enclosed in <>. For files enclosed in
\"\", the pre-compiler begins its search in the same directory as the file
going through the pre-compiler. Either way, the pre-compiler will
search the system include directories and the directory of the file for the
header file before giving up. The syntax simply determines the order.
The simplest pre-compiler directive is the #pragma directive. It is
doubtful you will ever use this one. Basically, you follow the directive
with some keyword which is meaningful to your driver. The only
keyword I have ever seen is strict_types, which simply lets the driver
know you want this file interpreted with strict data typing. I doubt you
will ever need to use this, and you may never even see it. I just included
it in the list in the event you do see it so you do not think it is doing
anything truly meaningful.
The final group of pre-compiler directives are the conditional pre-
compiler directives. They allow you to pre-compile the file one way
given the truth value of an expression, otherwise pre-compile the file
another way. This is mostly useful for making code portable among
mudlibs, since putting the m_delete() efun in code on a MudOS mud
would normally cause an error, for example. So you might write the
following:
#ifdef MUDOS
map_delete(map, key);
#else
map = m_delete(map, key);
#endif
which after being passed through the pre-compiler will appear to the
interpreter as:
map_delete(map, key);
on a MudOS mud, and:
map = m_delete(map, key);
on other muds. The interpreter never sees the function call that would
cause it to spam out in error.
Notice that my example made use of a binary definition as described
above. Binary definitions allow you to pass certain code to the
interpreter based on what driver or mudlib you are using, among other
conditions.
4.3 Summary
The pre-compiler is a useful LPC tool for maintaining modularity among
your programs. When you have values that might be subject to change,
but are used widely throughout your files, you might stick all of those
values in a header file as #define statements so that any need to make a
future change will cause you to need to change just the #define directive.
A very good example of where this would be useful would be a header
file called money.h which includes the directive:
#define HARD_CURRENCIES ({ \"gold\", \"platinum\", \"silver\",
\"electrum\", \"copper\" })
so that if ever you wanted to add a new hard currency, you only need
change this directive in order to update all files needing to know what the
hard currencies are.
The LPC pre-compiler also allows you to write code which can be
ported without change among different mudlibs and drivers. Finally,
you should be aware that the pre-compiler only accepts lines ending in
carriage returns. If you want a multiple line pre-compiler directive, you
need to end each incomplete line with a backslash(\\).
Copyright (c) George Reese 1993
",({"chapter 39","chapter thirty-nine","39",}):"chapter 39 \"QCS: Final notes\"
* Remember that QCS commands work on objects, not files. To
load a file into memory, use the update command. To reload an
already existing object, like a cloned orc or a book, use
the reload command. To use modify, delete, and add, you have
to specify a cloned object that is on you or in your environment.
I think you're getting the idea of how this works. Here's
an example of armor creation:
%^GREEN%^create armor jeans%^RESET%^
%^GREEN%^modify jeans id%^RESET%^
%^GREEN%^pants%^RESET%^
%^GREEN%^trousers%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify jeans short a pair of denim jeans%^RESET%^
%^GREEN%^modify jeans long Worn jeans, frayed and stained.%^RESET%^
%^GREEN%^modify jeans adj%^RESET%^
%^GREEN%^pair of%^RESET%^
%^GREEN%^denim%^RESET%^
%^GREEN%^frayed%^RESET%^
%^GREEN%^worn%^RESET%^
%^GREEN%^stained%^RESET%^
%^GREEN%^.%^RESET%^
To know what directives QCS can change on an object, type:
%^GREEN%^help modify%^RESET%^
This provides a list of modifiable things and the directives
that can be modified on them.
Ultimately the Quick Creation System generates LPC code, so
you'll want to review the earlier chapters of this handbook to
get a base of understanding of the code that comprises your new
creations.
Some notes and tips:
* The SetNoCondition directive makes it so an item does not report
its physical status when examined. Weapons and armor wear down in
combat, and most objects let you know their condition when you
examine them. However, in some cases (a sandwich for example)
this is inappropriate, so the SetNoCondition directive may be
useful.
* Doors aren't like normal objects. They have to be modified *twice*.
Once for each side of the door. If this sounds unnecessarily
tedious, remember that a door leading south is also a door
leading north from the other room.
* Doors generally are not visible in the same way that regular
objects are. To make a door especially obvious and noticeable,
do something like:
%^GREEN%^modify door sethiddendoor 0%^RESET%^
* SetCurrency is for adding money to NPC's. SetMoney is for adding
money to non-living containers (bags, etc).
* Item subtypes are listed in /include. To know what kinds of vendors
are available, for example, look in /include/vendor_types.h
* Books need a \"source directory\", which must contain one file per
chapter. The SetSource for this manual, for example, is /doc/manual
The first line of each file must follow the same format as
the files you see in /doc/manual
* SetObviousExits is usually no longer needed: rooms report
obvious exits automatically. However, if you don't want an exit
to show up by default, use the SetObviousExits directive
to specify only those that you want seen. This will override the
room's default exit display.
",({"chapter 20","chapter twenty","20",}):"chapter 20 \"Items\"
Building Any Item
The Nightmare IV LPC Library
written by Descartes of Borg 950430
Each object you build has a certain common make-up no matter what type
of object it is. This is because all of your objects actually are
based upon the same object, the object called /lib/object.c. That
object contains functions such as SetShort() and SetLong() which you
use in almost every single object you will build. This document
details how to set up any possible object you will use. The only
exceptions will be for rooms, which do not have key names or id's.
Beyond that, most of the every day objects you will code like armours,
weapons, drinks, foods, etc. all derive from a common object called
/lib/item.c. This document attempts to detail what is involved in
building any object on the MUD through /lib/item.c and its ancestor
/lib/object.c.
This document is in three sections
I. List of Mandatory Function Calls
II. Basic Functions
III. Extras
IV. Events
** *************** List of Mandatory Function Calls ************** **
SetKeyName(\"red bag\");
SetId( ({ \"bag\", \"red bag\" }) );
SetAdjectives( ({ \"red\" }) );
SetShort(\"a red bag\");
SetLong(\"A small red bag with no distinguishing marks.\");
SetMass(90);
SetValue(50);
SetVendorType(VT_BAG);
You also need to include vendor_types.h.
** *************** Basic Functions *************** **
*****
SetKeyName()
*****
string SetKeyName(string key_name);
Example: SetKeyName(\"red bag\");
Notes:
Mandatory for all objects except rooms.
Not used for rooms.
The key name is the central name by which the object is referred to in
sentences where no article is required. For example, the sentence
\"You pick up your red bag\" makes use of the key name to complete the
sentence. This is much like the short description, except the short
description will include an article. For this object, SetShort(\"a red
bag\") would be used.
*****
SetId()
*****
string *SetId(string *id);
Example: SetId( ({ \"bag\", \"red bag\" }) );
Notes:
Mandatory for all objects except rooms.
Not used in rooms.
Must be all lower case.
The id is an array of strings by which the object may be referred to by a
player. For example, if the player wants to get this bag, the player
can type \"get bag\" or \"get red bag\". The id is used purely for
identification purposes, so if you have something you need to sneak in
a unique way of identifying it, you may add an id only you know about.
*****
SetAdjectives()
*****
string *SetAdjectives(string *adjs);
Example: SetAdjectives( ({ \"red\" }) );
Notes:
Planned for future use in Zork style command parsing.
Not used in rooms.
The adjectives are descriptive terms used to describe the object.
This is not currently being used, however, it will be part of the new
style command parsing we will be building. This will allow the player
to type things like \"get the red one\" and pick up the red bag. Even
though it is not used, it is requested you place it in your objects to
make upgrading down the road a simpler task.
*****
SetShort()
*****
string SetShort(string description | function desc_func);
Examples:
SetShort(\"a red bag\");
SetShort((: DescribeBag :));
The short description is a brief description of the object. Only
names and proper nouns should be capitalized, the rest should be lower
case, as if it were appearing in the middle of a sentence. In rooms,
the player sees the short description when in brief mode and when they
glance at the room. For objects, the player sees the short when it is
described in the room or in their inventory.
If you pass a function instead of a string, then that function is used
to create the description. You can use this to do something like make
the object change its short description depending on who is looking at
it. The function that you build should therefore return a string
that will be used as the short description. For example...
string DescribeBag() {
if( query_night() ) return \"a bag\";
else return \"a red bag\";
}
*****
SetLong()
*****
string SetLong(string description | function desc_func);
Examples:
SetLong(\"A red bag with no markings on it whatsoever.\");
SetLong((: BagLong :));
Creates a verbose way to present the object to the player. You should
be much more descriptive than I have been in the example. Being a
text game, descriptions are 90% of what make the game. The more
creative you are with your descriptions, the more interesting the game
is to players. The long description of a room is seen by players in
verbose mode and when the player uses the \"look\" command. For
objects, the long description is seen when the player looks at the
object. Functions work in exactly the same fashion as short
functions.
*****
SetMass()
*****
int SetMass(int mass);
Example: SetMass(100);
Notes:
Mandatory for all visible objects.
Not needed for non-tangible objects and rooms.
Sets the mass for the object. In conjunction with the gravity of the
room it is in, this works to determine the weight of the object.
*****
SetValue()
*****
int SetValue(int value);
Example: SetValue(50);
Notes:
Mandatory for all sellable objects.
Not used in rooms.
Sets the base economic value of an object. This has no meaning in any
currencies, and in fact the actual value in any given currency may
vary.
*****
SetVendorType()
*****
int SetVendorType(int vt);
Example: SetVendorType(VT_BAG);
Note:
Mandatory for all objects except rooms.
Preset to VT_ARMOUR for objects which inherit LIB_ARMOUR.
Preset to VT_TREASURE for objects which inherit LIB_ITEM.
Preset to VT_LIGHT for objects which inherit LIB_LIGHT.
Not valid for room objects.
Values are found in /include/vendor_types.h.
You must do:
#include <vendor_types.h>
to use the VT_* macros (i.e. VT_ARMOUR, VT_TREASURE, VT_WEAPON).
The vendor type determines which shops will buy the item. For
example, things with VT_BAG as the vendor type can be bought and sold
in bag stores. For items which cross the line, for example a flaming
sword, you can combine vendor types in the following manner:
SetVendorType(VT_WEAPON | VT_LIGHT);
*****
SetDamagePoints()
*****
int SetDamagePoints(int pts);
Example: SetDamagePoints(500)
Sets the amount of damage an object can take before descreasing in
value. With armours and weapons, damage is taken quite often. Damage
is more rare with other kinds of objects. With this example object
which has 500 damage points, whenever 500 points has been done to it,
its value is cut in half and eventDeteriorate() is called for the
object. See the events section on using eventDeteriorate(). The
points are then reset to 500 and damage is done from that.
** *************** Extras *************** **
*****
SetProperty()
*****
mixed SetProperty(string property, mixed value);
Example: SetProperty(\"no pick\", 1);
Allows you to store information in an object which may not have been
intended by the designer of the object, or which is fleeting in
nature. See /doc/build/Properties for a list of common properties.
*****
SetProperties()
*****
mapping SetProperties(mapping props);
Example: SetProperties( ([ \"light\" : 1, \"no attack\" : 1 ]) );
Allows you to set any properties you want all in one shot.
*****
SetDestroyOnSell()
*****
int SetDestroyOnSell(int true_or_false);
Example: SetDestroyOnSell(1);
For mundane objects, or objects which should not be resold, allows you
to set it so that the object gets destroyed when sold instead of
allowing it to be resold.
*****
SetPreventGet()
*****
mixed SetPreventGet(mixed val);
Examples:
SetPreventGet(\"You cannot get that!\");
SetPreventGet( (: check_get :) );
Allows you to make an object un-gettable by a player. If you pass a
string, the player will see that string any time they try to get the
item. If you pass a function, that function will be called to see if
you want to allow the get. Your function gets the person trying to get
the object as an argument:
int check_get(object who) {
if( (int)who->GetRave() == \"ogre\" ) {
message(\"my_action\", \"Ogres cannot get this thing!\", who);
return 0;
}
else return 1;
}
*****
SetPreventPut()
*****
mixed SetPreventPut(mixed val);
Examples:
SetPreventPut(\"You cannot put that in there!\");
SetPreventPut( (: check_put :) );
The same as SetPreventGet(), except this is used when the object is
being put into another object.
*****
SetPreventDrop()
*****
mixed SetPreventDrop(mixed val);
Examples:
SetPreventDrop(\"You cannot drop that!\");
SetPreventDrop( (: check_drop :) );
The same as SetPreventGet(), except this is used when a player tries
to drop the object.
** *************** General Events ************** **
*****
eventDeteriorate()
*****
void eventDeteriorate(int type);
Example: ob->eventDeteriorate(COLD);
Notes:
Damage types can be found in /include/damage_types.h
This function gets called periodically in objects whenever they wear
down a bit. The type passed to the function is the type of damage
which triggered the deterioration.
*****
eventMove()
*****
int eventMove(mixed dest);
Example:
ob->eventMove(this_player());
ob->eventMove(\"/domains/Praxis/square\");
The eventMove event is called in an object when it is being moved from
one place to the next. You can either pass it the file name of a room
to which it should be moved or an object into which it should be
moved. It will return true if the object gets moved, false if it
cannot move for some reason. For objects which are being dropped,
gotten, or put, it is generally a good idea to check CanDrop(),
CanClose(), or CanGet() for the object in question since eventMove()
does not know the context of the move and therefore will allow a drop
since it does not check CanDrop().
*****
eventReceiveDamage()
*****
varargs int eventReceiveDamage(int type, int amount, int unused, mixed limbs);
Example: ob->eventReceiveDamage(BLUNT, 30, 0, \"right hand\");
This function gets called in an object whenever any damage is done to
it. Most frequently this gets called in monsters and armour. In
armour you can use it to modify the amount of damage which gets done.
The return value of this function is the amount of damage done to the
object. For example, if you have a piece of armour that absorbs 5 of
the 30 points listed above, then you return 5.
NOTE:
For monsters there is an extra arg at the front called
agent. The agent is the being responsible for doing
the damage. It may be zero if something like the weather
is causing the damage. It looks like:
varargs int eventReceiveDamage(object agent, int type, int strength,
int internal, mixed limbs);
For more detailed information, see /doc/build/NPC.
",({"chapter 38","chapter thirty-eight","38",}):"chapter 38 \"QCS: Adding and deleting\"
Rooms, containers and NPC's all are capable of holding
items, and often it is convenient to have them already holding certain
items upon creation.
The SetInventory directive in an object's file provides us with
a list of that object's \"permanent inventory\". To modify an object's
inventory, we use the add and delete commands.
Let's say that we want our cowboy to be wielding a
hammer when he appears...
%^GREEN%^clone cowboy%^RESET%^
%^GREEN%^cd ../weap%^RESET%^
%^GREEN%^clone hammer%^RESET%^
%^GREEN%^add hammer to cowboy%^RESET%^
%^GREEN%^wield hammer%^RESET%^
Believe it or not, it's that simple. The add command will ask
you a question after you issue it. What it wants to know is if you
want the NPC to do anything special when the hammer appears on him.
In this case, yes, we wanted him to wield it.
However, if you want to add something to an NPC and don't
have anything interesting for him to do with it, respond to the
question with the number of items that you want to appear. For
example, if I want the cowboy to be carrying a key:
%^GREEN%^cd ../obj%^RESET%^
%^GREEN%^clone key%^RESET%^
%^GREEN%^add key to cowboy%^RESET%^
%^GREEN%^1%^RESET%^
And that's it. Now if I want the cowboy to be a permanent
resident of this room:
%^GREEN%^add cowboy%^RESET%^
%^GREEN%^1%^RESET%^
The add command understands that if you don't specify a
target argument, you must mean the room. You can also be specific:
%^GREEN%^add cowboy to room%^RESET%^
%^GREEN%^1%^RESET%^
The delete command works the opposite way. It removes items from
an object's permanent inventory:
%^GREEN%^delete hammer from cowboy%^RESET%^
%^GREEN%^delete cowboy%^RESET%^
The delete command is also the way to get rid of rooms. You won't
be removing the file from disk, you'll just be deleting that exit
from the room:
%^GREEN%^delete exit garden%^RESET%^
NOTE: When you delete an exit, only the connection from your room to
the other room is removed. The connection from the other room to
your room remains. This is not a bug, it's a feature. There are plenty
of circumstances where one-way travel is desirable.
",({"chapter 26","chapter twenty-six","26",}):"chapter 26 \"Sentients\"
Building Sentient Non-Player Characters
The Nightmare IV Object Library
written by Descartes of Borg 951127
One thing most everyone wants to see are monsters that react more
intelligently to user input. The fact is, however, that most monsters
in the game only need a small, basic behaviour set. Nevertheless, in
order to make an area interesting, there should be some monsters which
stand out as unique and purposeful. The problem about building such
monsters is that they use a lot of processing time.
In order to make sure most monsters which do not need such
intelligence do not waste processing time on such activities, the
Nightmare Object Library separates non-player characters into two
classes: dumb monsters, which are basic mindless automata and
sentients, monsters which react more intelligently to their
environment.
This document describes sentients. Before looking at this document,
it is highly recommended that you be familiar with the document
/doc/build/NPC which details non-player characters. Sentients are
non-player characters, so everthing which applies to non-player
characters also applies to sentients.
*****
Currently, a few basic behaviours distinguish sentients from normal
npcs. Those behaviours are the ability to intelligently move about
the mud and to react to user speech. Nightmare thus provides the
following functions to allow you to easily have an sentient enact
those behaviours:
mapping SetTalkResponses(mapping mp);
mixed AddTalkResponse(string str, mixed val);
int RemoveTalkResponse(string str);
mapping SetCommandResponses(mapping mp);
mixed AddCommandResponse(string str, mixed val);
int RemoveCommandResponse(string str);
varargs int SetWander(int speed, string *path, int recurse);
string *SetWanderPath(string *path);
int SetWanderRecurse(int x);
int SetWanderSpeed(int x);
*****
Making NPCs react to user speech
You may want to have NPCs react to things players say. To that end,
the following functions exist:
mapping SetTalkResponses(mapping mp);
mixed AddTalkResponse(string str, mixed val);
int RemoveTalkResponse(string str);
Function: mapping SetTalkResponses(mapping mp)
Example: SetTalkResponses( ([ \"square\" : \"The square is east of here.\",
\"house\" : \"Isn't that an ugly house?\" ]) );
This function allows you to set a list of responses to given phrases.
For example, if you put this code in a sentient and a player said
\"Where is the square?\" or \"Your frog is certainly square.\", your NPC
would have said \"The square is east of here.\". Note therefore that
the NPC is only looking for the keys you place in there. You could
have restricted it to \"where is the square\" instead of \"square\", but
then someone asking \"Where's the square\" would be missed.
Also note that phrases should be in lower case. It will match to
upper case words automatically.
Finally, you can either give a string or a function as the match to a
phrase. If the match is a string, the NPC simply says the string in
the NPC's native tongue. If, however, the match is a function, that
function will get called.
*****
Function: mixed AddTalkResponse(string str, mixed val);
Example: AddTalkResponse(\"in the house\", (: HouseFunc :));
Matches an individual phrase to a string or function. As with
SetTalkResponses(), if the match is a string, the NPC simply says the
string in response to the phrase. If it is a function, that function
gets called.
*****
Function: int RemoveTalkResponse(string str);
Example: RemoveTalkResponse(\"house\");
Removes the previous set or added talk response from the NPC.
*****
Making NPCs react to user directives
Nightmare supports a special command, the \"ask\" command. A player may
use the ask command to ask an NPC to perform a certain task. For
example, \"ask the healer to mend my right leg\". There is a special
event in NPC's which responds to this called eventAsk(). In order to
make responding to this easier, however, Nightmare has the
CommandResponse functions. The command response functions allow NPC's
to respond based on commands, like \"mend\".
*****
Function: mapping SetCommandResponses(mapping mp);
Example: SetCommandResponses( ([ \"heal\", \"I cannot heal people\" ]) );
Allows you to match commands to either strings or functions. Matched
functions get called with the command as the first argument, and
command arguments as the second argument. For example, if you had:
SetCommandResponses(\"give\", (: give :));
Your give() function would get called with \"give\" as the first
argument and \"me the sword\" as the second argument in response to a
player issuing the command \"ask the monster to give me the sword\".
*****
Function: mixed AddCommandResponse(string str, mixed val);
Example: AddCommandResponse(\"give\", (: give :));
This allows you to add to the list of commands to which the NPC
responds. The NPC responds to those commands as outlined for
SetCommandResponses().
*****
Function: int RemoveCommandResponse(string str);
Example: RemoveCommandResponse(\"give\")
Removes a previously set command response.
*****
Making NPCs move about the game intelligently
A sticky subject on most muds is that of wandering monsters. When
done poorly, they can waste resources to a great degree. Nightmare,
however, works to avoid wasting resources while getting the most out
of allowing monsters to move about.
Nightmare supports two types of wandering monsters: those which have
pre-determined paths and others which are true wanderers. True
wanderers, those who simply randomly choose paths are subject to the
following restrictions:
They may not move into rooms not yet loaded in memory.
They will not try to open closed doors.
The first restriction is the most important to note. This means that
the NPC will not wander into rooms that have not been recently visited
by some player. This avoids the problem NPCs cause on many muds of
uselessly loading rooms that only the monster will ever see.
Monsters given specific paths to wander are not subject to the above
restrictions. Of course, they cannot wander through closed doors.
But you can make part of their path to open a closed door. In
addition, since such monsters have very specific sets of rooms into
which they can travel, they are not in danger of needlessly loading a
zillion rooms.
*****
Function: varargs int SetWander(int speed, string *path, int recurse);
Examples:
SetWander(5);
SetWander(5, ({ \"go north\", \"open door\", \"enter hut\", \"go west\" }));
SetWander(5, ({ \"go north\", \"open door\", \"enter hut\", \"go west\",
\"go south\" }), 1);
This is the function you will almost always use in create() to make a
sentient wander. Only one of the three possible arguments is
mandatory, that being the speed. The speed is simply the number of
heart beats between attempts to move. Thus, the higher the number,
the slower the movement of the monster.
The second argument, if given, is a list of commands which will be
executed in order by the monster. If it is not given, the monster
will be assumed to be a true wanderer. In other words, the first time
the monster tries to wander, the monster will \"go north\". The second
time, he will \"open door\". The third, he will \"enter hut\", etc.
The third argument is either 1 or 0. If 1, that means once the
monster has completed the path, it will use the first command in the
list the next time it tries to wander. If 0, it will cease to issue
commands once it has cycled through the list.
You might note that between the time the above monster opens the door
and enters the hut, somebody could come along and shut the door. How
can you deal with that? You could do:
SetWander(5, ({ \"go north\", ({ \"open door\", \"enter hut\" }) }));
You will notice here that the second member of the command array is
itself an array instead of a string. In that case, all members of
that array get executed as part of that wander. In this case it helps
make sure no one closes the door between when the monster tries to
open it and when it tries to pass through the door.
For even more flexibility, you can make elements of the array into
functions. Instead of executing a command in a wander turn, the
function you provide instead gets called. For example:
SetWander(5, ({ \"go north\", (: kill_anyone :), \"go south\" }), 1);
Where the function kill_anyone() has the monster kill any players in
that room. Thus, this monster sits in its room and occasionally pops
its head one room to the north to kill anyone sitting there.
*****
Function: string *SetWanderPath(string *path);
Example: SetWanderPath(({ \"go north\", \"go south\" }))
Allows you to set the monster's wander path independent of other
settings. The wander path will never get executed, however, unless
the monster's wander speed is greater than 0.
*****
Function: int SetWanderRecurse(int x);
Example: SetWanderRecurse(1);
Allows you to make the monster's wander path recurse independent of
other settings. This is meaningless, however, unless the monster's
wander speed is greater than 0 and a wander path is set for it.
*****
Function: int SetWanderSpeed(int x);
Example: SetWanderSpeed(5);
Allows you to set the monster's wander speed independent of other
settings. This is NOT the same as SetWander(5). SetWander() will
clear out any previous wander path and wander recurse settings. This
function has no effect on the monster's wander path or wander recurse.
",({"chapter 32","chapter thirty-two","32",}):"chapter 32 \"QCS: Commands\"
What with muds being text only, QCS has no fancy windowing system.
Using a menu-driven creation system was ruled out quickly due to the
vast complexity of the menus that would be required. Instead, QCS
relies on a few powerful commands.
create
This is the command that gets the ball rolling. This command
is what lets you bring a new thing into existence. The things you can
create can be seen by typing \"help create\". Examples are rooms, weapons,
doors, and so on. We will be reviewing each of those in later chapters.
When you issue this command a generic version of the item you
wish to create appears (or, in the case of a room, appears in the
direction you specify). Once that generic copy materializes, you can
change it to suit your needs using the \"modify\" command.
modify
I tend to regard this command as the heart and soul of QCS. It's
this tool that lets you make your world your own. Your new generic
things are not useful or fun until you modify them. A \"generic weapon\"
isn't very interesting, but a \"mithrilite poleaxe\" might be just the
thing to deal with a pesky dragon.
add
Creatures, rooms, and containers are capable of storing other
things. Once you make an ogre, you may want to give him a hammer to
wield. After you make that hammer, you use the add command to let
the ogre have that wepon in his permanent inventory.
delete
On the other hand, you may be tired of that ogre after a while. If
he is a part of the permanent inventory of a room, you can use the
delete command to remove him permanently. Or if you'd rather he have
a Kill-O-Zap Frogstar blaster rather than a hammer, get rid of the
hammer in his inventory with this command.
copy
This is a room-specific command. Rather than write multiple,
nearly identical rooms for large areas, you can use the copy command to
make the room you are almost exactly like any other room you choose,
except for the exits, which remain the same. Handy for big forests,
cell-blocks, twisty mazes of little passages, etc.
initfix
If a thing isn't working right, try to initfix it. \"init()\" is
an object function that many items need in order to work properly. If
you've run into something that is behaving unexpectedly, run initfix on
it. The trouble just might clear up.
",({"chapter 14","chapter fourteen","14",}):"chapter 14 \"Intermediate Inheritance\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 6: Intermediate Inheritance
6.1 Basics of Inheritance
In the textbook LPC Basics, you learned how it is the mudlib maintains
consistency amoung mud objects through inheritance. Inheritance
allows the mud administrators to code the basic functions and such that
all mudlib objects, or all mudlib objects of a certain type must have so
that you can concentrate on creating the functions which make these
objects different. When you build a room, or a weapon, or a monster,
you are taking a set of functions already written for you and inheriting
them into your object. In this way, all objects on the mud can count on
other objects to behave in a certain manner. For instance, player objects
can rely on the fact that all room objects will have a function in them
called GetLong() which describes the room. Inheritance thus keeps
you from having to worry about what the function GetLong() should
look like.
Naturally, this textbook tries to go beyond this fundamental knowledge
of inheritance to give the coder a better undertstanding of how
inheritance works in LPC programming. Without getting into detail that
the advanced domain coder/beginner mudlib coder simply does not yet
need, this chapter will try to explain exactly what happens when you
inherit an object.
6.2 Cloning and Inheritance
Whenever a file is referenced for the first time as an object (as opposed
to reading the contents of the file), the game tries to load the file into
memory and create an object. If the object is successfully loaded into
memory, it becomes as master copy. Master copies of objects may be
cloned but not used as actual game objects. The master copy is used to
support any clone objects in the game.
The master copy is the source of one of the controversies of mud LPC
coding, that is whether to clone or inherit. With rooms, there is no
question of what you wish to do, since there should only be one instance
of each room object in the game. So you generally use inheritance in
creating rooms. Many mud administrators, including myself, however
encourage creators to clone the standard monster object and configure it
from inside room objects instead of keeping monsters in separate files
which inherit the standard monster object.
As I stated above, each time a file is referenced to create an object, a
master copy is loaded into memory. When you do something like:
void reset() {
object ob;
ob = new(\"/std/monster\");
/* clone_object(\"/std/monster\") some places */
ob->SetKeyName(\"foo monster\");
... rest of monster config code followed by moving
it to the room ...
}
the driver searches to see if their is a master object called \"/std/monster\".
If not, it creates one. If it does exist, or after it has been created, the
driver then creates a clone object called \"/std/monster#<number>\". If
this is the first time \"/std/monster\" is being referenced, in effect, two
objects are being created: the master object and the cloned instance.
On the other hand, let's say you did all your configuring in the create()
of a special monster file which inherits \"/std/monster\". Instead of
cloning the standard monster object from your room, you clone your
monster file. If the standard monster has not been loaded, it gets loaded
since your monster inherits it. In addition, a master copy of your file
gets loaded into memory. Finally, a clone of your monster is created
and moved into the room, for a total of three objects added to the game.
Note that you cannot make use of the master copy easily to get around
this. If, for example, you were to do:
\"/realms/descartes/my_monster\"->eventMove(this_object());
instead of
new(\"/realms/descartes/my_monster\")->eventMove(this_object());
you would not be able to modify the file \"my_monster.c\" and update it,
since the update command destroys the current master version of an
object. On some mudlibs it also loads the new version into memory.
Imagine the look on a player's face when their monster disappears in
mid-combat cause you updated the file!
Cloning is therefore a useful too when you plan on doing just that-
cloning. If you are doing nothing special to a monster which cannot be
done through a few call others, then you will save the mud from getting
loaded with useless master copies. Inheritance, however, is useful if
you plan to add functionality to an object (write your own functions) or
if you have a single configuration that gets used over and over again
(you have an army of orc guards all the same, so you write a special orc
file and clone it).
6.3 Inside Inheritance
When objects A and B inherit object C, all three objects have their own
set of data sharing one set of function definitions from object C. In
addition, A and B will have separate functions definitions which were
entered separately into their code. For the sake of example throughout
the rest of the chapter, we will use the following code. Do not be
disturbed if, at this point, some of the code makes no sense:
STD_ITEM C
private string name, cap_name, short, long;
private int setup;
void SetKeyName(string str)
nomask string GetKeyName();
private int query_setup();
static void unsetup();
void SetShort(string str);
string GetShort();
void SetLong(string str);
string GetLong();
void SetKeyName(string str) {
if(!query_setup()) {
name = str;
setup = 1;
}
nomask string GetKeyName() { return name; }
private query_setup() { return setup; }
static void unsetup() { setup = 0; }
string GetName() {
return (name ? capitalize(name) : \"\"); }
}
void SetShort(string str) { short = str; }
string GetShort() { return short; }
void SetLong(string str) { long = str; }
string GetLong() { return str; }
void create() { seteuid(getuid()); }
STD_ITEM B
inherit \"/std/objectc\";
private int wc;
void set_wc(int wc);
int query_wc();
int wieldweapon(string str);
void create() { ::create(); }
void init() {
if(environment(this_object()) == this_player())
add_action(\"wieldweapon\", \"wield\");
}
void set_wc(int x) { wc = x; }
int query_wc() { return wc; }
int wieldweapon(string str) {
... code for wielding the weapon ...
}
STD_ITEM A
inherit \"/std/objectc\";
int ghost;
void create() { ::create(); }
void change_name(string str) {
if(!((int)this_object()->is_player())) unsetup();
SetKeyName(str);
}
string GetName() {
if(ghost) return \"A ghost\";
else return ::GetName();
}
As you can see, object C is inherited both by object A and object B.
Object C is a representation of a much oversimplified base object, with B
being an equally oversimplified weapon and A being an equally
simplified living object. Only one copy of each function is retained in
memory, even though we have here three objects using the functions.
There are of course, three instances of the variables from Object C in
memory, with one instance of the variables of Object A and Object B in
memory. Each object thus gets its own data.
6.4 Function and Variable Labels
Notice that many of the functions above are proceeded with labels which
have not yet appeared in either this text or the beginner text, the labels
static, private, and nomask. These labels define special priveledges
which an object may have to its data and member functions. Functions
you have used up to this point have the default label public. This is
default to such a degree, some drivers do not support the labeling.
A public variable is available to any object down the inheritance tree
from the object in which the variable is declared. Public variables in
object C may be accessed by both objects A and B. Similarly, public
functions may be called by any object down the inheritance tree from the
object in which they are declared.
The opposite of public is of course private. A private variable or
function may only be referenced from inside the object which declares it.
If object A or B tried to make any reference to any of the variables in
object C, an error would result, since the variables are said to be out of
scope, or not available to inheriting classes due to their private labels.
Functions, however, provide a unique challenge which variables do not.
External objects in LPC have the ability to call functions in other objects
through call others. The private label does not protect against call
others.
To protect against call others, functions use the label static. A function
which is static may only be called from inside the complete object or
from the game driver. By complete object, I mean object A can call
static functions in the object C it inherits. The static only protects against
external call others. In addition, this_object()->foo() is considered an
internal call as far as the static label goes.
Since variables cannot be referenced externally, there is no need for an
equivalent label for them. Somewhere along the line, someone decided
to muddy up the waters and use the static label with variables to have a
completely separate meaning. What is even more maddening is that this
label has nothing to do with what it means in the C programming
language. A static variable is simply a variable that does not get saved to
file through the efun save_object() and does not get restored through
restore_object(). Go figure.
In general, it is good practice to have private variables with public
functions, using query_*() functions to access the values of inherited
variables, and set_*(), add_*(), and other such functions to change
those values. In realm coding this is not something one really has to
worry a lot about. As a matter of fact, in realm coding you do not have
to know much of anything which is in this chapter. To be come a really
good realm coder, however, you have to be able to read the mudlib
code. And mudlib code is full of these labels. So you should work
around with these labels until you can read code and understand why it
is written that way and what it means to objects which inherit the code.
The final label is nomask, and it deals with a property of inheritance
which allows you to rewrite functions which have already been defined.
For example, you can see above that object A rewrote the function
GetName(). A rewrite of function is called overriding the
function. The most common override of a function would be in a case
like this, where a condition peculiar to our object (object A) needs to
happen on a call ot the function under certain circumstances. Putting test
code into object C just so object A can be a ghost is plain silly. So
instead, we override GetName() in object A, testing to see if the
object is a ghost. If so, we change what happens when another object
queries for the cap name. If it is not a ghost, then we want the regular
object behaviour to happen. We therefore use the scope resolution
operator (::) to call the inherited version of the GetName()
function and return its value.
A nomask function is one which cannot be overridden either through
inheritance or through shadowing. Shadowing is a sort of backwards
inheritance which will be detailed in the advanced LPC textbook. In the
example above, neither object A nor object B (nor any other object for
that matter) can override GetKeyName(). Since we want to use
GetKeyName() as a unique identifier of objects, we don't want people
faking us through shadowing or inheritance. The function therefore gets
the nomask label.
6.5 Summary
Through inheritance, a coder may make user of functions defined in
other objects in order to reduce the tedium of producing masses of
similar objects and to increase the consistency of object behaviour across
mudlib objects. LPC inheritance allows objects maximum priveledges in
defining how their data can be accessed by external objects as well as
objects inheriting them. This data security is maintained through the
keywords, nomask, private, and static.
In addition, a coder is able to change the functionality of non-protected
functions by overriding them. Even in the process of overriding a
function, however, an object may access the original function through
the scope resolution operator.
Copyright (c) George Reese 1993
",({"chapter 24","chapter twenty-four","24",}):"chapter 24 \"Quests\"
Building Quests
from the Nightmare IV LPC Library
written by Descartes of Borg 950716
Unlike previous Nightmare versions, Nightmare IV has no support for
centralized quest administration. This was done under the belief that
coercive questing was among the least favourite features players have
mentioned about the MUDs I have encountered. Nevertheless, the
presence of quests is still an extrememly important part of any MUD.
Since the coercive nature (needing to complete quest X to raise to
level Y) has been removed, other ways to make questing worthwhile need
to be found.
The first, and most obvious, is to properly reward the player with
money, items, and skill and stat points. The other bit of support is
for a title list. Each quest, or accomplishment, is added to a list
of accomplishments the player has. The player may display any of
those at any time as part of their title.
The interface to this is simple:
player_object->AddQuest(string title, string description);
Example:
this_player()->AddQuest(\"the slayer of frogs\",
\"You viciously slayed the evil frogs of Wernmeister that \"
\"threatened the peaceful town with warts and unabated fly murder.\");
In the player's biography, they will see the description along with
the date they accomplished the task. From their title list, they will
now be able to choose this title.
Descartes of Borg
950716
",({"chapter 19","chapter nineteen","19",}):"chapter 19 \"Doors\"
Creating Doors between Two Rooms
The Nightmare IV LPC Library
created by Descartes of Borg 950419
This document describes how to build door-type objects which link two
rooms. These door-type objects do not need to be doors, but in fact
can be windows or boulders or any other such object. The Nightmare IV
LPC Library door object, unlike the old way of doing doors, is an
object separate from the rooms it connects. In other words, in order
to build a door, you have three objects (just as you would visualize):
two rooms and a door.
The door object is /lib/door.c. To inherit it, #include <lib.h> and
inherit LIB_DOOR;. An example door may be found in
/domains/Examples/etc/door.c as well as the rooms
/domains/Examples/room/doorroom1.c and /domains/Examples/room/doorroom2.c.
Setting up the door object
The first thing you must do is create the door object. You must
visualize this door object just like a door connecting two rooms in
real life. You have a room on each side with a single door with two
sides. Technically, a door object may have any number of sides.
Practically speaking, most people using this object will be using it
as a door, which means it will have two sides.
To create a door object, you simply describe each side of the door.
The easiest way to do this is through the SetSide() function.
mapping SetSide(string side, mapping mp);
Example:
SetSide(\"east\", ([ \"id\" : \"red door\", \"short\" : \"a red door\",
\"long\" : \"A freshly painted red door.\",
\"lockable\" : 0 ]) );
The name of the side is simply the exit used by the room which sees
that side. For example, if in one room the door is at the east exit,
then the side is identified as east. The mapping consists of the
following data:
\"id\"
What a person on that side calls the door. For example, you can have a
door blue on one side and red on the other. On one side, you go east
to go through the door, and from that room the door appears red. The
id for that side might be \"red door\". The id for the other side might
be \"blue door\".
\"short\"
The short description for the door as seen from the side in question.
This can be a function or a string.
\"long\"
The long description for the door as seen from the side in question.
Whether the door is open or not will be added to the long if the long
is a string. This can be either a string or function. If it is a
function, you must specify whether the door is open or close on your
own.
\"lockable\"
0 if the door cannot be locked (and unlocked) from that side, 1 if it
can.
\"keys\"
An array of id's of objects which can be used to unlock it if it is
lockable. Lockable doors do not need keys.
II. Setting up the rooms
After you have called SetItems() and SetExits() in the room
(remembering to set the exit for the exit with the door), call the
function SetDoor().
string SetDoor(string dir, string doorfile);
Example: SetDoor(\"east\", \"/realms/descartes/doors/red_door\");
Sets the exit named to be blocked by a door object when that door
object is closed.
This is all you need to do in the room. Note that the exit name
corresponds to the side name mentioned in the door.
III. Advanced Door Stuff
At this point, you should know how to do the minimum stuff to build a
door. This section goes into detail about door functions and how you
can do advanced things with doors by manipulating door events. This
section has two parts, door data functions and door events.
a. Door Data Functions
*****
SetSide()
*****
mapping SetSide(string side, mapping mp);
As described above.
*****
SetClosed()
*****
static int SetClosed(int x)
Example: SetClosed(1);
This function can only be called from inside the door object.
Generally you use it to set the initial state of the door. If you
want to close the door at any other time, or to close it from another
object, use eventClose() or eventOpen().
*****
SetLocked()
*****
static int SetLocked(int x)
Example: SetLocked(1);
Like SetClosed(), this function should only be used from create()
inside the door object to set the initial state of the door. At other
times, use eventLock() or eventUnlock().
*****
SetLockable()
*****
int SetLockable(string side, int x)
Example: SetLockable(\"east\", 1);
Sets a side as being able to be locked or unlocked. Since it is done
by sides, this means you can have one side not be lockable with the
other side being lockable. The first argument is the side being set
lockable or not lockable, the second argument is 1 for lockable and 0
for not lockable.
*****
SetId()
*****
string SetId(string side, string id)
Example: SetId(\"west\", \"blue door\");
This is not like your traditional SetId() function. Instead, it sets
a single way of identifying the door from a given side. It is what
the player might use to open the door or look at it.
*****
SetShort()
*****
mixed SetShort(string side, string | function desc)
Examples:
SetShort(\"north\", \"a red door\");
SetShort(\"west\", (: GetWestShort :) );
Sets the short description for a given side of a door. If the second
argument is a function, it gets passed as an argument the name of the
side for which the function serves as a description. That function
should return a string. For the above:
string GetWestShort(string dir) {
if( query_night() ) return \"a shadowy door\";
else return \"a red door\";
}
*****
SetLong()
*****
mixed SetLong(string side, string | function desc)
Examples:
SetLong(\"south\", \"An old, dusty door covered in cobwebs.\");
SetLong(\"east\", (: GetEastLong :))
This works much like the SetShort() function, except it handles the
long description. It is important to note that if the second argument
is a string, that the state of the door will be added onto the long
description automatically. In other words \"It is open.\" will appear
as the second line. This will *not* be done if you use a function for
your long description.
*****
SetKeys()
*****
string *SetKeys(string side, string *keys)
Example: SetKeys(\"east\", ({ \"skeleton key\", \"special key\" }));
Builds an array of id's which can be used to unlock the door if it is
lockable from this side. In other words, a person can only unlock the
door if that person has an object which has one of the id's you
specify for its id.
b. Events
*****
eventOpen()
*****
varargs int eventOpen(object by, object agent)
Examples:
\"/realms/descartes/etc/red_door\"->eventOpen(this_object());
int eventOpen(object by, object agent) {
if( query_night() ) return 0; /* Can't open it at night */
else return door::eventOpen(by, agent);
}
The function that actually allows the door to be opened externally.
It returns 1 if the door is successfully opened. It returns 0 if it
fails. The first argument is the room object from which the door is
being opened. The second argument, which is optional, is the living
thing responsible for opening the door.
The first example above is an example of what you might do from
reset() inside a room in order to have the door start open at every
reset.
The second example above is an example of how you might conditionally
prevent the door from opening by overriding the Open event. In this
case, if it is night, you cannot open this door. If it is day, you
can.
*****
eventClose()
*****
varargs int eventClose(object by, object agent)
Example: See eventOpen()
This function works just like eventOpen(), except it does the closing
of the door.
*****
eventLock()
*****
varargs int eventLock(object by, object agent)
Example: see eventOpen()
This function works just like eventOpen(), except that it gets called
for locking the door.
*****
eventUnlock()
*****
varargs int eventUnlock(object by, object agent)
Example: See eventOpen()
This function works just like eventOpen(), except that it gets called
for unlocking the door.
",({"chapter 36","chapter thirty-six","36",}):"chapter 36 \"QCS: Modifying weapons\"
Remember that the QCS chapters are supposed to be read in
sequence. This is important because as we progress, I will not make
explanations about directives and concepts explained previously.
Weapons are different from rooms and NPC's in that they can
be handled, sold, thrown, etc. They are manipulable objects. As such,
we will see new directives:
%^GREEN%^create weapon hammer%^RESET%^
You may be familiar with this example from the example webpage.
Let's go ahead and plow through the commands:
%^GREEN%^modify weapon id hammer%^RESET%^
%^GREEN%^warhammer%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer name hammer%^RESET%^
%^GREEN%^modify hammer damagetype blunt%^RESET%^
%^GREEN%^modify hammer weapontype blunt%^RESET%^
%^GREEN%^modify hammer mass 700%^RESET%^
%^GREEN%^modify hammer hands 2%^RESET%^
%^GREEN%^modify hammer short a heavy war hammer%^RESET%^
%^GREEN%^modify hammer long This is an extremely large and heavy hammer designed to be wielded in both hands and used to hurt people very badly indeed.%^RESET%^
%^GREEN%^modify hammer adj%^RESET%^
%^GREEN%^large%^RESET%^
%^GREEN%^heavy%^RESET%^
%^GREEN%^war%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer basecost silver 750%^RESET%^
%^GREEN%^about hammer%^RESET%^
Like a room and unlike an NPC, you can also modify the SetItems on
manipulable objects like weapons, so you could do something like this:
%^GREEN%^modify hammer item%^RESET%^
%^GREEN%^shaft%^RESET%^
%^GREEN%^handle%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A thick, reinforced steel shaft with leather bands for a strong grip.%^RESET%^
%^GREEN%^exa shaft on hammer%^RESET%^
",({"chapter 34","chapter thirty-four","34",}):"chapter 34 \"QCS: Modification of NPC's\"
In the previous chapter we learned how to make a generic object.
Now that we have it, what to do with it?
It's important to keep in mind that the generic thing now in
front of you isn't just a clone from a template file. If the command
you used was \"create npc cowboy\", there is now a file (probably)
called /realms/you/area/npc/cowboy.c that contains the code for the
creature in front of you.
However, this poor beast has the most uninteresting of features.
It is in fact so boring that it responds only to its generic type.
Such that \"examine cowboy\" or \"kill tex\" won't work. You'll need to
\"look at npc\".
Accordingly, any modification commands need to be made referring
to the new thing with an it responds to, until such a time as you
change the id to suit your tastes. Let's carry on with the example
of our generic npc. To make a cowboy out of him, we can either
change his name, or his id, or both. Let's start with his name:
%^GREEN%^modify npc name cowboy%^RESET%^
This makes the SetKeyName() directive in cowboy.c use \"cowboy\"
as its argument, effectively allowing you to address this npc as \"cowboy\"
from now on. Now you can \"look at cowboy\" with some results.
Obviously our NPC isn't *just* a cowboy. He's also a human, a dude,
and his name is Tex. How do we make him respond to all of these nouns?
%^GREEN%^modify cowboy id%^RESET%^
You'll notice there are no arguments following the word \"id\". Setting
a thing's id is different from most other settings. If you'll think back
to the LPC datatypes chapter of this manual (you did read the LPC
chapters, didn't you?) you'll remember that some information about
objects is in the form of strings (\"cowboy\"), some is in the form of
integers (the cowboy's health points, for example) and some is in the
form of arrays, which are a group of data points. In this example we
want the cowboy's id to be an array, because of the many ways we might
want to address him. Therefore, we want the SetId directive in his
file to look like this:
SetId( ({\"human\", \"dude\", \"tex\" }) );
You might think that QCS should be able to accept multiple values
like this on a line, perhaps with \"modify cowboy id human dude tex\" as
the command.
But what if you want this npc to be a \"Boy Named Sue\"? How would
you accommodate id's that contain spaces? In designing QCS, I considered
having escape characters to allow for such things, but ultimately
reasoned that this was just way too much mess. Instead, SetId, and
other directives that take arrays as arguments, are handled by entering
a query session. Below is an example of what it might look like. The
example is necessarily messy because I am including both the queries
and the responses.
---------------------------------------------------
> %^GREEN%^modify npc name cowboy%^RESET%^
Indenting file...
\"/tmp/indent.1136206130.tmp.dat\" 15 lines 420 bytes
Exit from ed.
> %^GREEN%^modify cowboy id%^RESET%^
This setting takes multiple values. If you have no more values to enter, then
enter a dot on a blank line. To cancel, enter a single q on a blank line.
You may now enter the next value. So far, it is blank.
If you're done entering values, enter a dot on a blank line.
%^GREEN%^dude%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^human%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^tex%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^boy named sue%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\",
\"boy named sue\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^.%^RESET%^
Entries complete. Final array is: ({ \"dude\", \"human\", \"tex\", \"boy named sue\" })
Indenting file...
\"/tmp/indent.1136206156.tmp.dat\" 19 lines 459 bytes
Exit from ed.
/open/1136206138: Ok
/realms/cratylus/area/npc/cowboy: Ok
SetId modification complete.
> %^GREEN%^exa tex%^RESET%^
Other than being human, this npc is entirely unremarkable.
The male human is in top condition.
---------------------------------------------------
If you were now to examine Tex's code (with the command \"about tex\")
you'd see that his SetId directive now looks like this:
SetId( ({\"dude\", \"human\", \"tex\", \"boy named sue\"}) );
Other NPC features take arrays also. SetAdjectives is one. You
might enter this (mud output omitted for clarity):
%^GREEN%^modify tex adjectives%^RESET%^
%^GREEN%^dusty%^RESET%^
%^GREEN%^hardy%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^look at dusty cowboy%^RESET%^
There are two other directives that require queries. Things and
NPC's can be looked at, but they can also be smelled and listened
to, if you add SetSmell and SetListen. The syntax is:
%^GREEN%^modify tex smell%^RESET%^
And you will then be asked a question about keys and mappings.
Understanding mappings is important, but for now you just need to
understand that you are being asked *two* separate questions:
1) What on the cowboy is being smelled/listened to?
2) What is the smell/sound?
What this means is that your input will look something like
this:
%^GREEN%^modify tex smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex smells of sweat and manure.
What happens is this:
- You enter the modify command.
- You enter the word \"default\" to indicate this is Tex's general smell.
- You enter a dot to indicate that you are done specifying what
part of Tex is being smelled.
- You then specify the smell.
This may seem odd until you realize you can also add smells/listens to
parts of things. Not on NPC's, though. We'll look at this more closely in
later chapters. For now, just use the syntax as shown above. For adding
a listen to the cowboy, it works the same way:
%^GREEN%^modify tex listen%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex seems to be humming a jaunty melody.
Other features of an NPC do not take arrays, so one-line commands
will do. For example:
%^GREEN%^modify cowboy long This is a cowboy who calls himself Tex, but is in fact a Boy Named Sue.%^RESET%^
%^GREEN%^modify cowboy short a cowboy%^RESET%^
%^GREEN%^modify cowboy level 5%^RESET%^
%^GREEN%^modify cowboy class fighter%^RESET%^
%^GREEN%^modify tex currency gold 1%^RESET%^
%^GREEN%^modify tex currency silver 12%^RESET%^
%^GREEN%^modify tex skill bargaining 5%^RESET%^
%^GREEN%^modify tex skill projectile attack 7%^RESET%^
%^GREEN%^modify tex stat strength 33%^RESET%^
%^GREEN%^modify tex property nice guy 1%^RESET%^
%^GREEN%^modify tex healthpoints 150%^RESET%^
%^GREEN%^modify tex maxhealthpoints 170%^RESET%^
%^GREEN%^modify tex melee 0%^RESET%^
%^GREEN%^modify tex unique 1%^RESET%^
If you now issue the \"about tex\" command you will see that all
the changes you made have been put into the file.
You may have noticed the \"melee\" keyword. Dead Souls 2 NPC's come
in various shapes and sizes, and some of them shouldn't wield weapons. A
wolf with a battle axe would be a strange sight indeed. However, the
default combat system makes unarmed creatures extremely vulnerable in
combat.
To make an NPC combat-capable without weapons, use the new SetMelee
directive. SetMelee(1) makes the NPC capable of proper unarmed combat.
SetMelee(0) makes the NPC a weak opponent if unarmed.
NPC's will generally try to bite during unarmed combat. If this
is beneath the capability or dignity of your NPC, you can prevent
this with:
%^GREEN%^modify tex canbite 0%^RESET%^
If your NPC should try to escape when the battle isn't going
his way, the wimpy settings should do:
%^GREEN%^modify tex wimpy 30%^RESET%^
%^GREEN%^modify tex wimpycommand climb ladder%^RESET%^
If you don't specify a wimpy command, Tex will leave the
room through a random exit. In this case, when Tex's health
is down to 30% and he is in combat, he will try to climb a ladder.
Some NPC's are designed to travel about. To enable this
feature, use the wanderspeed directive:
%^GREEN%^modify tex wanderspeed 5%^RESET%^
If you want him to travel more quickly, use a
lower number. By default, wandering NPC's only wander in rooms
that have already been loaded into memory. They avoid loading
rooms because loading a bunch of rooms that only the NPC
will ever see is a waste of your mud's resources.
However, if you *do* want your NPC to wander in an
unrestricted manner, regardless of whether a room is loaded,
use the permitload directive:
%^GREEN%^modify tex permitload 1%^RESET%^
By default, NPC's stand up when they can. This is
so that if they collapse during combat, they try to
get back up once they are able to do so.
If you prefer that your NPC maintain some other
posture, you can set that posture, then disable
autostanding like this:
%^GREEN%^modify tex posture lying%^RESET%^
%^GREEN%^modify tex autostand 0%^RESET%^
If he's especially lazy, you can have him take
a nap this way:
%^GREEN%^modify tex sleeping 10%^RESET%^
Which will have him wake up after about a minute.
However, note that if you've disabled autostanding,
he will remain lying down after he wakes up.
If the NPC should be hostile, that is, he should
attack any creatures that it sees enter a room,
SetEncounter should do it:
%^GREEN%^modify tex encounter 100%^RESET%^
This means that if the creature it sees has a
charisma score of less than 100 (which should pretty
much be always true), Tex will try to kill it. You
can do some fancy stuff with SetEncounter, such
as only attacking orcs, or actually doing something
friendly, but to do so you can't use QCS. Read
the NPC and Sentients chapter in the Creator's
Manual for details on how to code such stuff.
If the NPC is a golem or other such non-biological
creature, it may be useful to specify what they are
made of. The SetComposition setting for a clay
golem might look like this:
%^GREEN%^modify golem composition clay%^RESET%^
If it happens to be a golem that does not believe
in violence as a solution to problems, you can
make refuse to hurt others with the following:
%^GREEN%^modify golem pacifist 1%^RESET%^
Vendors:
-------
Vendors are a special kind of NPC that can sell stuff.
Along with the standard NPC settings, vendors have
the following:
SetStorageRoom specifies where the vendor's stock
is stored. If a valid room is specified, anything
in that room can be sold by the vendor.
SetLocalCurrency specifies the type of currency,
such as gold or silver, that the vendor accepts.
SetMaxItems is the maximum number of items in
the storeroom that the vendor is permitted to sell.
SetVendorType specifies the kind of stuff the vendor can
trade in. \"all\" allows him to buy and sell whatever.
But if he is a weapon vendor, he can't trade in armor,
etc. See /include/vendor_types.h for the available
vendor types. To have a \"multi-type\" vendor, you'll have to
code it by hand. The result of that looks something
like this:
SetVendorType( VT_TREASURE | VT_ARMOR | VT_HERBS );
Barkeeps:
--------
Like vendors, barkeeps sell stuff, but they are
limited to selling food and drink.
Unlike vendors, barkeeps have no limitation on
the amount of stuff they can sell. They also do
not have a storeroom. The stuff they can sell is
specified in their SetMenu directive, like this:
%^GREEN%^clone woody%^RESET%^
You clone a generic barkeep (/realms/temujin/area/npc/woody.c).
%^GREEN%^modify woody menu%^RESET%^
If you don't understand these questions, type the letter q on a blank line and hit enter.
Please enter the first key element for this mapping:
%^GREEN%^bourbon%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^whiskey%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^.%^RESET%^
Please enter the value for key ({ \"bourbon\", \"whiskey\" }):
%^GREEN%^/domains/town/meals/bourbon%^RESET%^
Barkeeps also have the SetLocalCurrency directive
to specify the currency they accept.
",({"chapter 28","chapter twenty-eight","28",}):"chapter 28 \"Vendors\"
Building Store Vendors
The Nightmare IV LPC Library
written by Descartes of Borg 950528
This document details the creation of vendor objects, NPC's which buy
and sell items. Note that vendors are NPC's, so everything in the
document on building NPC's applies to vendors. It is recommended that
you be completely familiar with that document before moving on to this
one.
Building vendors is actually quite simple, with very little required
beyond the NPC requirements. In fact, only the following function
calls are unique to vendors:
string SetLocalCurrency(string currency);
string SetStorageRoom(string room);
int SetMaxItems(int num);
int SetVendorType(int vt);
One special note, however, is that the skill \"bargaining\" is extremely
important to vendors. Namely, the higher the bargaining, the harder
it is for players to get decent prices.
*****
string SetLocalCurrency(string curr);
*****
Example: SetLocalCurrency(\"electrum\");
Sets the currency which the vendor will use for doing business. The
currencies should be approved by the approval team.
*****
string SetStorageRoom(string room);
*****
Example: SetStorageRoom(\"/domains/Praxis/horace_storage\");
Identifies the file name of the room in which the vendor will be
storing items for sale. This room should never be accessible to
players.
*****
int SetMaxItems(int num);
*****
Example: SetMaxItems(60);
Sets the maximum number of items a vendor can keep in storage at any
given time. Refer to approval documentation for proper numbers for
this.
*****
int SetVendorType(int type);
*****
Examples:
SetVendorType(VT_WEAPON);
SetVendorType(VT_ARMOUR | VT_WEAPON);
Sets which types of items a vendor will buy and sell. A list of all
vendor types is in /include/vendor_types.h. You may allow a vendor to
sell multiple types using the | operator.
",({"chapter 17","chapter seventeen","17",}):"chapter 17 \"Barkeeps\"
Building Food and Drink Sellers
The Nightmare IV LPC Library
written by Descartes of Borg 950528
This document details the building of barkeeps, waiters, and other
such people who sell food and drink. Barkeeps are NPC's, and
therefore everythign which applies to NPC's applies to barkeeps.
To build a barkeep, you should inherit LIB_BARKEEP.
Beyond the functions specific to NPC's barkeeps also make use of the
following functions:
mapping SetMenuItems(mapping menu);
mapping AddMenuItem(string item, string file);
mapping RemoveMenuItem(string item);
mapping GetMenuItems();
string SetLocalCurrency(string curr);
When building a barkeep, you must add some mechanism in the room in
which the barkeep is placed for people to view a list of things for
sale.
*****
mapping SetMenuItems(mapping menu);
*****
Example: SetMenuItems( ([ \"coffee\" : \"/realms/descartes/coffee\" ]) );
Sets which menu items are found in which file. This is a mapping with
the name of the item as a key and the file in which it is located as
the value.
*****
mapping AddMenuItem(string item, string file);
*****
Example: AddMenuItem(\"lobster\", \"/realms/descartes/lobster\");
Adds one menu item at a time to the list of menu items.
*****
mapping RemoveMenuItem(string item);
*****
Example: RemoveMenuItem(\"coffee\");
Removes the named item from the menu.
*****
mapping GetMenuItems();
*****
Returns all the menu items for this barkeep. Useful in building your
menu list.
*****
string SetLocalCurrency(string curr);
*****
Example: SetLocalCurrency(\"khucha\");
Sets the currency in which the barkeep does business.
",({"chapter 10","chapter ten","10",}):"chapter 10 \"The LPMud Driver\"
Intermediate LPC
Descartes of Borg
Novermber 1993
Chapter 2: The LPMud Driver
2.1 Review of Basic Driver/Mudlib Interaction
In the LPC Basics textbook, you learned a lot about the way the mudlib
works, specifically in relation to objects you code in order to build your
realm. Not much was discussed about the interaction between the
mudlib and the driver. You should know, however, that the driver
does the following:
1) When an object is first loaded into memory, the driver will call
create() in native muds and reset() in compat muds. A creator
uses create() or reset() to give initial values to the object.
2) At an interval setup by the game administrator, the driver calls the
function reset(). This allows the object to regenerate monsters and
such. Notice that in a compat mud, the same function is used to set up
initial values as is used to reset the room.
3) Any time a living object comes in contact with an object of any sort,
the driver calls init() in the newly encountered object. This allows
newly encountered objects to give living objects commands to execute
through the add_action() efun, as well as perform other actions which
should happen whenever a living thing encounters a given object.
4) The driver defines a set of functions known as efuns which are
available to all objects in the game. Examples of commonly used efuns
are: this_player(), this_object(), write(), say(), etc.
2.2 The Driver Cycle
The driver is a C program which runs the game. Its basic functions are
to accept connections from the outside world so people can login,
interpret the LPC code which defines LPC objects and how they
function in the game, and accept user input and call the appropriate LPC
functions which match the event. In its most simplest essence, it is an
unending loop.
Once the game has booted up and is properly functioning (the boot up
process will be discussed in a future, advanced LPC textbook), the
driver enters a loop which does not terminate until the shutdown() efun
is legally called or a bug causes the driver program to crash. First off,
the driver handles any new incoming connections and passes control of
the connection to a login object. After that, the driver puts together a
table of commands which have been entered by users since the last cycle
of the driver. After the command table is assembled, all messages
scheduled to be sent to the connection from the last driver cycle are sent
out to the user. At this point, the driver goes through the table of
commands to be executed and executes each set of commands each
object has stored there. The driver ends its cycle by calling the function
heart_beat() in every object with a heart_beat() set and finally
performing all pending call outs. This chapter will not deal with the
handling of connections, but instead will focus on how the driver
handles user commands and heartbeats and call outs.
2.3 User Commands
As noted in section 1.2, the driver stores a list of commands for each
user to be executed each cycle. The commands list has the name of the
living object performing the command, the object which gave the living
object that command, and the function which is to be executed in order
to perform the command. The driver refers to the object which typed in
the command as the command giver. It is the command giver which
gets returned as this_player() in most cases.
The driver starts at the top of the list of living objects with pending
commands, and successively performs each command it typed by calling
the function associated with the command and passing any arguments
the command giver gave as arguments to the function. As the driver
starts with the commands issued by a new living object, the command
giver variable is changed to be equal to the new living object, so that
during the sequence of functions initiated by that command, the efun
this_player() returns the object which issued the command.
Let's look at the command buffer for an example player. Since the
execution of his last command, Bozo has typed \"north\" and \"tell
descartes when is the next reboot\". The command \"north\" is associated
with the function \"Do_Move()\" in the room Bozo is in (the command
\"north\" is automatically setup by the SetExits() efun in that room). The
command \"tell\" is not specifically listed as a command for the player,
however, in the player object there is a function called \"cmd_hook()\"
which is associated with the command \"\", which matches any possible
user input.
Once the driver gets down to Bozo, the command giver variable is set to
the object which is Bozo. Then, seeing Bozo typed \"north\" and the
function \"north\" is associated with, the driver calls Bozo's_Room-
>Do_Move(0). An argument of 0 is passed to the function since Bozo
only typed the command \"north\" with no arguments. The room
naturally calls some functions it needs, all the while such that the efun
this_player() returns the object which is Bozo. Eventually, the room
object will call eventMoveLiving() in Bozo, which in turn calls the
move_object() efun. This efun is responsible for changing an object's
environment.
When the environment of an object changes, the commands available to
it from objects in its previous environment as well as from its previous
environment are removed from the object. Once that is done, the driver
calls the efun init() in the new environment as well as in each object in
the new environment. During each of these calls to init(), the object
Bozo is still the command giver. Thus all add_action() efuns from this
move will apply to Bozo. Once all those calls are done, control passes
back from the move_object() efun to the eventMoveLiving() lfun in Bozo.
eventMoveLiving() returns control back to Do_Move() in the old room,
which returns 1 to signify to the driver that the command action was
successful. If the Do_Move() function had returned 0 for some reason,
the driver would have written \"What?\" (or whatever your driver's
default bad command message is) to Bozo.
Once the first command returns 1, the driver proceeds on to Bozo's
second command, following much the same structure. Note that with
\"tell descartes when is the next reboot\", the driver passes \"descartes
when is the next reboot\" to the function associated with tell. That
function in turn has to decide what to do with that argument. After that
command returns either 1 or 0, the driver then proceeds on to the next
living object with commands pending, and so on until all living objects
with pending commands have had their commands performed.
2.4 The Efuns set_heart_beat() and call_out()
Once all commands are performed for objects with commands pending,
the driver then proceeds to call the heart_beat() function in all objects
listed with the driver as having heartbeats. Whenever an object calls the
efun set_heart_beat() with a non-zero argument (depending on your
driver, what non-zero number may be important, but in most cases you
call it with the int 1). The efun set_heart_beat() adds the object which
calls set_heart_beat() to the list of objects with heartbeats. If you call it
with an argument of 0, then it removes the object from the list of objects
with heartbeats.
The most common use for heartbeats in the mudlib is to heal players and
monsters and perform combat. Once the driver has finished dealing with
the command list, it goes through the heartbeat list calling heart_beat() in
each object in the list. So for a player, for example, the driver will call
heart_beat() in the player which will:
1) age the player
2) heal the player according to a heal rate
3) check to see if there are any hunted, hunting, or attacking objects
around
4) perform an attack if step 3 returns true.
5) any other things which need to happen automatically roughly every
second
Note that the more objects which have heartbeats, the more processing
which has to happen every cycle the mud is up. Objects with heartbeats
are thus known as the major hog of CPU time on muds.
The call_out() efun is used to perform timed function calls which do not
need to happen as often as heartbeats, or which just happen once. Call
outs let you specify the function in an object you want called. The
general formula for call outs is:
call_out(func, time, args);
The third argument specifying arguments is optional. The first argument
is a string representing the name of the function to be called. The second
argument is how many seconds should pass before the function gets
called.
Practically speaking, when an object calls call_out(), it is added to a list
of objects with pending call outs with the amount of time of the call out
and the name of the function to be called. Each cycle of the driver, the
time is counted down until it becomes time for the function to be called.
When the time comes, the driver removes the object from the list of
objects with pending call outs and performs the call to the call out
function, passing any special args originally specified by the call out
function.
If you want a to remove a pending call before it occurs, you need to use
the remove_call_out() efun, passing the name of the function being
called out. The driver will remove the next pending call out to that
function. This means you may have some ambiguity if more than one
call out is pending for the same function.
In order to make a call out cyclical, you must reissue the call_out() efun
in the function you called out, since the driver automatically removes the
function from the call out table when a call out is performed. Example:
void foo() { call_out(\"hello\", 10); }
void hello() { call_out(\"hello\", 10); }
will set up hello() to be called every 10 seconds after foo() is first called.
There are several things to be careful about here. First, you must watch
to make sure you do not structure your call outs to be recursive in any
unintended fashion. Second, compare what a set_heart_beat() does
when compared directly to what call_out() does.
set_heart_beat():
a) Adds this_object() to a table listing objects with heartbeats.
b) The function heart_beat() in this_object() gets called every single
driver cycle.
call_out():
a) Adds this_object(), the name of a function in this_object(), a time
delay, and a set of arguments to a table listing functions with pending
call outs.
b) The function named is called only once, and that call comes after the
specified delay.
As you can see, there is a much greater memory overhead associated
with call outs for part (a), yet that there is a much greater CPU overhead
associated with heartbeats as shown in part (b), assuming that the delay
for the call out is greater than a single driver cycle.
Clearly, you do not want to be issuing 1 second call outs, for then you
get the worst of both worlds. Similarly, you do not want to be having
heart beats in objects that can perform the same functions with call outs
of a greater duration than 1 second. I personally have heard much talk
about at what point you should use a call out over a heartbeat. What I
have mostly heard is that for single calls or for cycles of a duration
greater than 10 seconds, it is best to use a call out. For repetitive calls of
durations less than 10 seconds, you are better off using heartbeats. I do
not know if this is true, but I do not think following this can do any
harm.
2.5 Summary
Basic to a more in depth understanding of LPC is and understanding of
the way in which the driver interacts with the mudlib. You should now
understand the order in which the driver performs functions, as well as a
more detailed knowledge of the efuns this_player(), add_action(), and
move_object() and the lfun init(). In addition to this building upon
knowledge you got from the LPC Basics textbook, this chapter has
introduced call outs and heartbeats and the manner in which the driver
handles them. You should now have a basic understanding of call outs
and heartbeats such that you can experiment with them in your realm
code.
Copyright (c) George Reese 1993
",({"chapter 22","chapter twenty-two","22",}):"chapter 22 \"NPCs\"
Building Non-Player Characters
The Nightmare IV Object Library
written by Descartes of Borg 951201
This document outlines the creation of non-player characters (NPC's).
On other muds, NPC's are sometimes referred to as monsters. Like the
rooms document, this document is divided up into two sections: basic
NPC building and complex NPC building. NPC's are living things which
inherit all the behaviours of living things. Documentation on living
specific functionality may be found in /doc/build/Livings.
************************************************
Part 1: Basic NPC Building
************************************************
*****
I. The simplest NPC
*****
#include <lib.h>
inherit LIB_NPC;
static void create() {
npc::create();
SetKeyName(\"praxis peasant\");
SetId( ({ \"peasant\", \"praxis peasant\" }) );
SetShort(\"a local peasant\");
SetLong(\"Dirty and totally disheveled, this poor inhabitant of Praxis \"
\"still somehow maintains an air of dignity that nothing can \"
\"break.\");
SetLevel(1);
SetRace(\"elf\");
SetClass(\"fighter\");
SetGender(\"male\");
}
There are two things you should note. The first is that an NPC is
also a general object, meaning that you have available to you all the
things you can do with general objects, like setting descriptions and
ID's. The second is that a basic NPC does not require a heck of a lot
more. I will cover the NPC specific functions here.
SetLevel(1)
SetRace(\"elf\")
SetClass(\"fighter\")
Level, race, and class are the three most important settings in any
NPC. Together they determine how powerful the NPC is. You are
absolutely required to set a level and a race. For those who
absolutely do not want to give the NPC a class, you do not have to.
But, you must instead manually set the NPC's skill levels, which is
described in the second part of this document. In general, however,
you always want to set the class.
Together, the class and race and level determine which skills and
stats are considered important for the monster, and how good at those
skills and stats the monster is. The order in which you call these
functions is irrelevant, as everything is recalculated any time one of
the above changes.
Also, note that SetRace() may only be called with a race listed in the
mraces command with simple NPC's. If you wish to build an NPC with a
unique race, you need to do some limb manipulation, which is described
in the advanced section.
SetGender(\"male\")
While not required, you will normally want to give an NPC a gender.
The default is neutral. However, in this world, very little is
neuter. Your choices for this function are male, female, and neuter.
*****
II. Other NPC Configuration Functions
*****
Function: int SetMorality(int amount);
Example: SetMorality(100);
This is a number between -2000 and 2000 which determines the morality
of an individual with respect to good and evil. -2000 is absolute
evil, and 2000 is absolute good. The actions of players determine
their morality, and often those actions are relative to a target.
Thus killing an evil being can be considered good, while killing a bad
one evil.
Function: int SetUnique(int x);
Example: SetUnique(1)
Marks the NPC as a unique monster. This allows the room which clones
your NPC to use the negative values to SetInventory() (see
/doc/build/Rooms) to make sure the NPC only gets cloned every few
days.
************************************************
Part 2: Advanced NPC Building
************************************************
*****
I. Functions
*****
You may use these functions to make your NPC's a bit more interesting
than the simple variety.
Function: void SetAction(int chance, mixed val);
Examples: SetAction(5, (: DoSomething :));
SetAction(5, ({ \"!smile\", \"!frown\" }));
SetAction(5, ({ \"The peasant looks unhappy.\" }));
Sets something to randomly happen every few heart beats while the NPC
is not in combat. In the above examples, the NPC has a 5% chance each
heart beat of performing the action you provided with the second
argument. The action can be a call to a function, a list of potential
commands, or a list of strings to be echoed to the room.
If you pass a function, that function will be called each time an
action is supposed to occur. If you pass a list of strings, one of
those strings will be randomly chosen as the target action for this
heart beat. If the chosen string begins with a !, it is treated as a
command. Otherwise, it is simply echoed to the room. Note that you
can mix commands and echo strings.
*****
Function: void SetCombatAction(int chance, mixed val);
Examples: SetCombatAction(5, (: DoSomething :));
SetCombatAction(5, ({ \"!missile\", \"!fireball\" }));
SetAction(5, ({ \"The peasant looks angry.\" }));
This function works exactly the same as SetAction(), except that these
actions only get triggered while the NPC is in combat. This is the
best place to have the NPC cast spells.
*****
Function: varargs void SetCurrency(mixed val, int amount);
Examples: SetCurrency(\"gold\", 100);
SetCurrency( ([ \"gold\" : 100, \"electrum\" : 1000 ]) );
This function allows you to set how much money an NPC is carrying.
The first syntax allows you to set one currency at a time. The second
allows you to set multiple currencies at once. Not that if you use
the second syntax, it will blow away any currencies the NPC might
already be carrying.
*****
Function: mixed SetDie(mixed val);
Examples: SetDie(\"The black knight bleeds on you as he drops dead.\");
SetDie((: CheckDie :));
If you pass a string, that string will be echoed as the NPC's death
message when it dies. If you pass a function, that function gets
called with the agent doing the killing, if any, as an argument. For
example, with the above example, the function that you write:
int CheckDie(object killer);
gets called. If you return 1, the NPC goes on to die. If you return
0, the NPC does not die. In the event you prevent death, you need to
make some arrangements with the NPC's health points and such to make
sure it really is still alive.
*****
Function: mixed SetEncounter(mixed val);
Examples: SetEncounter(40);
SetEncounter( (: CheckDwarf :) );
SetEncounter( ({ str1, str2 }) );
This allows you to set up e behaviour for an NPC upon encountering
another living thing. Note that this behaviour occurrs for both
players and other NPC's. Using the first syntax, the NPC will simply
attack any other living thing with a charisma less than 40. The
second syntax calls the function you specify. You may have it do any
number of things, however, you must also return a 1 or a 0 from that
function. A 1 means that after the function is called, the NPC should
initiate combat against the thing it just encountered. A 0 means
carry on as usual.
Finally, the third syntax is likely to be used in places other than
the create() funciton in the NPC. This syntax lets you set a list
names which are simply enemies to the NPC. More likely, you will be
using AddEncounter() and RemoveEncounter() for this.
*****
Function: string *AddEncounter(string name);
Example: AddEncounter((string)this_player()->GetKeyName());
Adds a name to the list of names an NPC will attack on sight.
*****
Function: string *RemoveEncounter(string name);
Example: RemoveEncounter((string)this_player()->GetKeyName());
Removes a name from the list of names an NPC will attack on sight.
*****
Function: SetInventory(mapping inventory);
Examples:
SetInventory( ([ \"/domains/Praxis/weapon/sword\" : \"wield sword\" ]) );
SetInventory( ([ \"/domains/Praxix/etc/ruby\" : 1 ]) );
SetInventory( ([ \"/domains/Praxis/etc/emerald\" : -10 ]) );
This functions behaves almost identically to SetInventory() for rooms
(see /doc/build/Rooms). The big difference is that you may pass a
string in addition to a number as the value for any item you want in
the inventory. In the first example above, that string is the command
the NPC issues when the sword is cloned into its inventory. In other
words, if you want an NPC to do something special with an item it has
in its inventory, in this case wield the sword, you pass the command
string as the value instead of a number.
Note that this means only one of such item will be cloned, and it
cannot be unique.
*****
II. Events
*****
The following events exist in NPC's. You should have a good grasp of
function overriding before overriding these functions.
Event: varargs int eventDie(object target);
This event is triggered any time the NPC is killed. The event returns
1 if the NPC dies, 0 if it fails to die, and -1 on error. If you
intend to allow the NPC to die, you should call npc::eventDie(target)
and make sure it returns 1.
*****
Event: int eventFollow(object dest, int chance);
This event is triggered whenever an NPC is following another living
thing and that thing leaves the room. Returnung 1 means that the NPC
successfully followed the other being, and 0 means the NPC did not.
*****
3. Manipulating limbs
*****
The basic set of limbs an NPC gets is generally set when you set its
race. You can get a list of supported NPC races through the mraces
command. Occassionally, however, you may want to create NPCs of
unique races, or with unique body structures. Or perhaps you want a
human whose right hand is already amputated. This section deals with
doing those things.
Amputating a limb is simple. Call RemoveLimb(\"limb\"). Note that that
is not useful for removing a limb that should not be there. Instead,
it is used for amputating a limb that looks amputated.
If, on the other hand, you wish to remove a limb which simply should
not have been there in the first place, call DestLimb(\"limb\").
The most simple case of actual limb manipulation, however, is to
change the basic structure of an individual NPC around for some
reason. For example, perhaps you wanted to add a tail to a human.
For this, you use the AddLimb() function.
Function: varargs int AddLimb(string limb, string parent, int class, int *armours);
Examples: AddLimb(\"tail\", \"torso\", 4)
AddLimb(\"head\", \"torso\", 1, ({ A_HELMET, A_VISOR, A_AMULET }));
This function adds a new limb to the NPC's body. The first argument
is the name of the limb to be added. The second argument is the name
of the limb to which it is attached. The third argument is the limb
class. Limb class is a number between 1 and 5. The lower the number,
the harder the limb is to remove. A limb class of 1 also means that
removal of the limb is fatal. The fourth, optional argument is a list
of armour types which may be worn on that limb.
In some cases, you may wish to create a new race from scratch. This
requires adding every single limb manually. You first call SetRace()
with a special second argument to note that you are creating a new
race:
SetRace(\"womble\", 1);
Then, you add the limbs for that race one by one. Make sure you call
SetRace() first.
",({"chapter 40","chapter forty","40",}):"chapter 40 \"Useful Creator Commands\"
Moving around:
-------------
* To go to your workroom:
%^GREEN%^home%^RESET%^
* To go to someone else's workroom, \"home <person>\", for example:
%^GREEN%^home cratylus%^RESET%^
* To go to the town pub:
%^GREEN%^goto /domains/town/room/tavern%^RESET%^
* Or:
%^GREEN%^cd /domains/town/room%^RESET%^
%^GREEN%^goto tavern%^RESET%^
* To return to where you were before you went somewhere else:
%^GREEN%^return%^RESET%^
* To bring someone to you, \"trans <person>\". For example:
%^GREEN%^trans cratylus%^RESET%^
* To send them back when you're done with them:
%^GREEN%^return cratylus%^RESET%^
Dealing with living beings:
---------------------------
* To force everyone in a room to stop fighting:
%^GREEN%^quell%^RESET%^
* To let them resume combat:
%^GREEN%^unquell%^RESET%^
* To insta-kill a living being, \"zap <thing>\". For example:
%^GREEN%^zap orc%^RESET%^
* To bring to life a player who somehow left the death
room without regenerating, \"resurrect <person>\", For example:
%^GREEN%^resurrect cratylus%^RESET%^
* To make a living being do something you want, \"force <thing>
<command>\". For example:
%^GREEN%^force thief drop towel%^RESET%^
%^GREEN%^force thief go west%^RESET%^
* For complex management of a living being's vital statistics,
skills, health, score, and satiety levels, use the medical
tricorder in the chest in your workroom.
* For a detailed report of a living being's physical status:
%^GREEN%^stat orc%^RESET%^
%^GREEN%^stat cratylus%^RESET%^
Handling objects in general:
---------------------------
* To destroy an object, \"dest <thing>\". Note that the object's
inventory will probably move to the room it was occupying.
For example, if you:
%^GREEN%^dest fighter%^RESET%^
You may find that the room now contains a sword, shield, and
chainmail shirt, but no fighter.
* To reset an object to its original state, \"reload <thing>\". Note
that this also makes the object incorporate any changes you
made to its file. For example:
%^GREEN%^reload fighter%^RESET%^
%^GREEN%^reload here%^RESET%^
* To load a file into memory, \"update file\". This is used when you
have edited an object's file, and want the mud to use the new
stuff you created. For example, if you edited the fighter's file
and wanted to know if it will load properly into memory, you'd
type:
%^GREEN%^update /realms/you/area/npc/fighter.c%^RESET%^
Or:
%^GREEN%^cd /realms/you/area/npc/%^RESET%^
%^GREEN%^update fighter%^RESET%^
If you do not specify an object to update, the mud assumes you
want to update the room you are in. If there is a problem with the
room's code, and it does not load, you will be dropped into the
\"void\".
If the room's code is ok and it updates, anything in the room
that isn't part of its permanent inventory (except for players) will
disappear from the room.
* To make a copy of an object appear, \"clone <file>\". For example:
%^GREEN%^clone /realms/you/area/npc/fighter%^RESET%^
Or:
%^GREEN%^cd /realms/you/area/npc/%^RESET%^
%^GREEN%^clone fighter%^RESET%^
* To know the precise contents of an object, use scan:
%^GREEN%^scan fighter%^RESET%^
%^GREEN%^scan here%^RESET%^
If you want to know not only what the fighter has, but
also what any containers he is carrying have, use the \"-d\" flag:
%^GREEN%^scan -d fighter%^RESET%^
%^GREEN%^scan -d here%^RESET%^
Debugging commands:
--------------------
* elog: This will report back to you the last few lines of
your error log. Usually this is very helpful in nailing down
which lines of a file contain errors. If you are admin, you
may be working on files other than your home dir. If those
files fail to update, you can supply elog with a directory name
to specify where to look for an error report:
%^GREEN%^elog secure%^RESET%^
%^GREEN%^elog cmds%^RESET%^
%^GREEN%^elog lib%^RESET%^
* dbxwhere: provides a list of the chain of messages caught
in your last runtime error.
* dbxframe <number>: Using the list number from dbxwhere,
dbxframe can pinpoint exactly where in that link the error
came from.
%^GREEN%^tail /log/runtime%^RESET%^
%^GREEN%^tail /log/catch%^RESET%^
%^GREEN%^tail /log/player_errors%^RESET%^
miscellaneous useful commands:
-----------------------------
* %^GREEN%^people%^RESET%^: reports who is logged on, what site they logged
in from, and what room they are in.
* %^GREEN%^mudtime%^RESET%^: reports the time of day in the mud (nothing to
do with the time of day anywhere in the real world).
* %^GREEN%^bk <thing or file>%^RESET%^: makes a unique copy of that thing
or file and puts it in /realms/you/bak
*%^GREEN%^ restore <filename>%^RESET%^: copies the last backup of the
filename from your bak/ directory into where it used
to be.
",({"chapter 2","chapter two","2",}):"chapter 2 \"The LPC Program\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 16 june 1993
CHAPTER 2: The LPC Program
2.1 About programs
The title of this chapter of the textbook is actually poorly named, since
one does not write programs in LPC. An LPC coder instead writes *objects*.
What is the difference? Well, for our purposes now, the difference is
in the way the file is executed. When you \"run\" a program, execution
begins at a definite place in the program. In other words, there
is a place in all programs that is noted as the beginning where program
execution starts. In addition, programs have definite end points,
so that when execution reaches that point, the execution of the program
terminates. So, in short, execution of a program runs from a definite
beginning point through to a definite end point. This is not so with
LPC objects.
With muds, LPC objects are simply distinct parts of the C program which
is running the game (the driver). In other words, execution of the mud
program begins and ends in the driver. But the driver in fact does
very little in the way of creating the world you know when you play
a mud. Instead, the driver relies heavily on the code created in LPC,
executing lines of the objects in the mud as needed. LPC objects thus
have no place that is necessarily the beginning point, nor do they
have a definite ending point.
Like other programming languages, an LPC \"program\" may be made up of
one or more files. For an LPC object to get executed, it simple
needs to be loaded into the driver's memory. The driver will call lines
from the object as it needs according to a structure which will be
defined throughout this textbook. The important thing you need to
understand at this point is that there is no \"beginning\" to an LPC
object in terms of execution, and there is no \"end\".
2.2 Driver-mudlib interaction
As I have mentioned earlier, the driver is the C program that runs on
the host machine. It connects you into the game and processes LPC code.
Note that this is one theory of mud programming, and not necessarily
better than others. It could be that the entire game is written in C.
Such a game would be much faster, but it would be less flexible in
that wizards could not add things to the game while it was running. This
is the theory behind DikuMUDs. Instead, LPMUDs run on the theory that
the driver should in no define the nature of the game, that the nature
of the game is to be decided by the individuals involved, and that
you should be able to add to the game *as it is being played*. This
is why LPMUDs make use of the LPC programming language. It allows
you to define the nature of the game in LPC for the driver to read and
execute as needed. It is also a much simpler language to understand
than C, thus making the process of world creation open to a greater
number of people.
Once you have written a file in LPC (assuming it is corrent LPC ), it justs
sits there on the host machine's hard drive until something in the game
makes reference to it. When something in the game finally does make
reference to the object, a copy of the file is loaded into memory and
a special *function* of that object is called in order to initialize
the values of the variables in the object. Now, do not be concerned
if that last sentence went right over your head, since someone brand
new to programming would not know what the hell a function or a variable
is. The important thing to understand right now is that a copy of the
object file is taken by the driver from the machine's hard drive and
stored into memory (since it is a copy, multiple versions of that
object may exist). You will later understand what a function is, what
a variable is, and exactly how it is something in the game made reference
to your object.
2.3 Loading an object into memory
Although there is no particular place in an object code that must exist
in order for the driver to begin executing it, there is a place for which
the driver will search in order to initialize the object. On compat
drivers, it is the function called reset(). On native muds it is the
function called create().
LPC objects are made up of variables (values which can change) and
functions which are used to manipulate those variables. Functions
manipulate variables through the use of LPC grammatical structures,
which include calling other functions, using externally defined
functions (efuns), and basic LPC expressions and flow control
mechanisms.
Does that sound convoluted? First lets start with a variable. A
variable might be something like: level. It can \"vary\" from sitation
to situation in value, and different things use the value of the player's
level to make different things happen. For instance, if you are a
level 19 player, the value of the variable level will be 19. Now
if your mud is on the old LPMud 2.4.5 system where levels 1-19 are
players and 20+ are wizards, things can ask for your level value to
see if you can perform wizard type actions. Basically, each object
in LPC is a pile of variables with values which change over time.
Things happen to these objects based on what values its variables
hold. Often, then things that happen cause the variables to change.
So, whenever an object in LPC is referenced by another object currently
in memory, the driver searches to see what places for values the
object has (but they have no values yet). Once that is done, the driver
calls a function in the object called reset() or create() (depending
on your driver) which will set up the starting values for the object's
variables. It is thus through *calls* to *functions* that variable
values get manipulated.
But create() or reset() is NOT the starting place of LPC code, although
it is where most LPC code execution does begin. The fact is, those
functions need not exist. If your object does just fine with its
starting values all being NULL pointers (meaning, for our purposes
here, 0), then you do not need a create() or reset() function. Thus
the first bit of execution of the object's code may begin somewhere
completely different.
Now we get to what this chapter is all about. The question: What
consists a complete LPC object? Well, an LPC object is simply
one or more functions grouped together manipulating 0 or more
variables. The order in which functions are placed in an object
relative to one another is irrelevant. In other words:
-----
void init() { add_action(\"smile\", \"smile\"); }
void create() { return; }
int smile(string str) { return 0; }
-----
is exactly the same as:
-----
void create() { return; }
int smile(string str) { return 0; }
void init() { add_action(\"smile\", \"smile\"); }
_____
Also important to note, the object containing only:
-----
void nonsense() {}
-----
is a valid, but trivial object, although it probably would not interact
properly with other objects on your mud since such an object has no
weight, is invisible, etc..
2.4 Chapter summary
LPC code has no beginning point or ending point, since LPC code is used
to create objects to be used by the driver program rather than create
individual programs. LPC objects consist of one or more functions whose
order in the code is irrelevant, as well as of zero or more variables whose
values are manipulated inside those functions. LPC objects simply sit
on the host machine's hard driver until referenced by another object in
the game (in other words, they do not really exist). Once the object
is referenced, it is loaded into the machine's memory with empty
values for the variables. The function reset() in compat muds or
create() in native muds is called in that object if it exists to allow
the variables to take on initial values. Other functions in the object
are used by the driver and other objects in the game to allow interaction
among objects and the manipulation of the LPC variables.
A note on reset() and create():
create() is only used by muds in native mode (see the textbook Introduction
for more information on native mode vs. compat mode). It is only used
to initialize newly referenced objects.
reset() is used by both muds in compat mode and native mode. In compat
mode, reset() performs two functions. First, it is used to initialize
newly referenced objects. In addition, however, compat mode muds use
reset() to \"reset\" the object. In other words, return it to its initial
state of affairs. This allows monsters to regenerate in a room and doors
to start back in the shut position, etc.. Native mode muds use reset()
to perform the second function (as its name implies).
So there are two important things which happen in LP style muds which
cause the driver to make calls to functions in objects. The first is
the creation of the object. At this time, the driver calls a function
to initalize the values in the object. For compat mode muds, this
is performed by the function named reset() (with an argument of 0,
more on this later though). For muds running in native mode, this is
performed by the function create().
The second is the returning of the room to some base state of affairs.
This base set of affairs may or may not be different from the initial
state of affairs, and certainly you would not want to take up time
doing redundant things (like resetting variables that never change).
Compat mode muds nevertheless use the same function that was used to
create the object to reset it, that being reset(). Native mode muds,
who use create() to create the room, instead use reset() to reset it.
All is not lost in compat mode though, as there is a way to tell the
difference between creation and resetting. For reset purposes, the
driver passes either 1 or the reset number as an argument to reset()
in compat mode. Now this is meaningless to you now, but just keep in
mind that you can in fact tell the difference in compat mode. Also
keep in mind that the argment in the creation use of reset is 0 and
the argument in the reset use is a nonzero number.
",({"chapter 32","chapter thirty-two","32",}):"chapter 32 \"QCS: Commands\"
What with muds being text only, QCS has no fancy windowing system.
Using a menu-driven creation system was ruled out quickly due to the
vast complexity of the menus that would be required. Instead, QCS
relies on a few powerful commands.
create
This is the command that gets the ball rolling. This command
is what lets you bring a new thing into existence. The things you can
create can be seen by typing \"help create\". Examples are rooms, weapons,
doors, and so on. We will be reviewing each of those in later chapters.
When you issue this command a generic version of the item you
wish to create appears (or, in the case of a room, appears in the
direction you specify). Once that generic copy materializes, you can
change it to suit your needs using the \"modify\" command.
modify
I tend to regard this command as the heart and soul of QCS. It's
this tool that lets you make your world your own. Your new generic
things are not useful or fun until you modify them. A \"generic weapon\"
isn't very interesting, but a \"mithrilite poleaxe\" might be just the
thing to deal with a pesky dragon.
add
Creatures, rooms, and containers are capable of storing other
things. Once you make an ogre, you may want to give him a hammer to
wield. After you make that hammer, you use the add command to let
the ogre have that wepon in his permanent inventory.
delete
On the other hand, you may be tired of that ogre after a while. If
he is a part of the permanent inventory of a room, you can use the
delete command to remove him permanently. Or if you'd rather he have
a Kill-O-Zap Frogstar blaster rather than a hammer, get rid of the
hammer in his inventory with this command.
copy
This is a room-specific command. Rather than write multiple,
nearly identical rooms for large areas, you can use the copy command to
make the room you are almost exactly like any other room you choose,
except for the exits, which remain the same. Handy for big forests,
cell-blocks, twisty mazes of little passages, etc.
initfix
If a thing isn't working right, try to initfix it. \"init()\" is
an object function that many items need in order to work properly. If
you've run into something that is behaving unexpectedly, run initfix on
it. The trouble just might clear up.
",({"chapter 13","chapter thirteen","13",}):"chapter 13 \"Advanced String Handling\"
Intermediate LPC
Descartes of Borg
November 1993
Chapter 5: Advanced String Handling
5.1 What a String Is
The LPC Basics textbook taught strings as simple data types. LPC
generally deals with strings in such a matter. The underlying driver
program, however, is written in C, which has no string data type. The
driver in fact sees strings as a complex data type made up of an array of
characters, a simple C data type. LPC, on the other hand does not
recognize a character data type (there may actually be a driver or two out
there which do recognize the character as a data type, but in general not).
The net effect is that there are some array-like things you can do with
strings that you cannot do with other LPC data types.
The first efun regarding strings you should learn is the strlen() efun.
This efun returns the length in characters of an LPC string, and is thus
the string equivalent to sizeof() for arrays. Just from the behaviour of
this efun, you can see that the driver treats a string as if it were made up
of smaller elements. In this chapter, you will learn how to deal with
strings on a more basic level, as characters and sub strings.
5.2 Strings as Character Arrays
You can do nearly anything with strings that you can do with arrays,
except assign values on a character basis. At the most basic, you can
actually refer to character constants by enclosing them in '' (single
quotes). 'a' and \"a\" are therefore very different things in LPC. 'a'
represents a character which cannot be used in assignment statements or
any other operations except comparison evaluations. \"a\" on the other
hand is a string made up of a single character. You can add and subtract
other strings to it and assign it as a value to a variable.
With string variables, you can access the individual characters to run
comparisons against character constants using exactly the same syntax
that is used with arrays. In other words, the statement:
if(str[2] == 'a')
is a valid LPC statement comparing the second character in the str string
to the character 'a'. You have to be very careful that you are not
comparing elements of arrays to characters, nor are you comparing
characters of strings to strings.
LPC also allows you to access several characters together using LPC's
range operator ..:
if(str[0..1] == \"ab\")
In other words, you can look for the string which is formed by the
characters 0 through 1 in the string str. As with arrays, you must be
careful when using indexing or range operators so that you do not try to
reference an index number larger than the last index. Doing so will
result in an error.
Now you can see a couple of similarities between strings and arrays:
1) You may index on both to access the values of individual elements.
a) The individual elements of strings are characters
b) The individual elements of arrays match the data type of the
array.
2) You may operate on a range of values
a) Ex: \"abcdef\"[1..3] is the string \"bcd\"
b) Ex: ({ 1, 2, 3, 4, 5 })[1..3] is the int array ({ 2, 3, 4 })
And of course, you should always keep in mind the fundamental
difference: a string is not made up of a more fundamental LPC data type.
In other words, you may not act on the individual characters by
assigning them values.
5.3 The Efun sscanf()
You cannot do any decent string handling in LPC without using
sscanf(). Without it, you are left trying to play with the full strings
passed by command statements to the command functions. In other
words, you could not handle a command like: \"give sword to leo\", since
you would have no way of separating \"sword to leo\" into its constituent
parts. Commands such as these therefore use this efun in order to use
commands with multiple arguments or to make commands more
\"English-like\".
Most people find the manual entries for sscanf() to be rather difficult
reading. The function does not lend itself well to the format used by
manual entries. As I said above, the function is used to take a string and
break it into usable parts. Technically it is supposed to take a string and
scan it into one or more variables of varying types. Take the example
above:
int give(string str) {
string what, whom;
if(!str) return notify_fail(\"Give what to whom?\\n\");
if(sscanf(str, \"%s to %s\", what, whom) != 2)
return notify_fail(\"Give what to whom?\\n\");
... rest of give code ...
}
The efun sscanf() takes three or more arguments. The first argument is
the string you want scanned. The second argument is called a control
string. The control string is a model which demonstrates in what form
the original string is written, and how it should be divided up. The rest
of the arguments are variables to which you will assign values based
upon the control string.
The control string is made up of three different types of elements: 1)
constants, 2) variable arguments to be scanned, and 3) variable
arguments to be discarded. You must have as many of the variable
arguments in sscanf() as you have elements of type 2 in your control
string. In the above example, the control string was \"%s to %s\", which
is a three element control string made up of one constant part (\" to \"),
and two variable arguments to be scanned (\"%s\"). There were no
variables to be discarded.
The control string basically indicates that the function should find the
string \" to \" in the string str. Whatever comes before that constant will
be placed into the first variable argument as a string. The same thing
will happen to whatever comes after the constant.
Variable elements are noted by a \"%\" sign followed by a code for
decoding them. If the variable element is to be discarded, the \"%\" sign
is followed by the \"*\" as well as the code for decoding the variable.
Common codes for variable element decoding are \"s\" for strings and \"d\"
for integers. In addition, your mudlib may support other conversion
codes, such as \"f\" for float. So in the two examples above, the \"%s\" in
the control string indicates that whatever lies in the original string in the
corresponding place will be scanned into a new variable as a string.
A simple exercise. How would you turn the string \"145\" into an
integer?
Answer:
int x;
sscanf(\"145\", \"%d\", x);
After the sscanf() function, x will equal the integer 145.
Whenever you scan a string against a control string, the function
searches the original string for the first instance of the first constant in
the original string. For example, if your string is \"magic attack 100\" and
you have the following:
int improve(string str) {
string skill;
int x;
if(sscanf(str, \"%s %d\", skill, x) != 2) return 0;
...
}
you would find that you have come up with the wrong return value for
sscanf() (more on the return values later). The control string, \"%s %d\",
is made up of to variables to be scanned and one constant. The constant
is \" \". So the function searches the original string for the first instance
of \" \", placing whatever comes before the \" \" into skill, and trying to
place whatever comes after the \" \" into x. This separates \"magic attack
100\" into the components \"magic\" and \"attack 100\". The function,
however, cannot make heads or tales of \"attack 100\" as an integer, so it
returns 1, meaning that 1 variable value was successfully scanned
(\"magic\" into skill).
Perhaps you guessed from the above examples, but the efun sscanf()
returns an int, which is the number of variables into which values from
the original string were successfully scanned. Some examples with
return values for you to examine:
sscanf(\"swo rd descartes\", \"%s to %s\", str1, str2) return: 0
sscanf(\"swo rd descartes\", \"%s %s\", str1, str2) return: 2
sscanf(\"200 gold to descartes\", \"%d %s to %s\", x, str1, str2) return: 3
sscanf(\"200 gold to descartes\", \"%d %*s to %s\", x, str1) return: 2
where x is an int and str1 and str2 are string
5.4 Summary
LPC strings can be thought of as arrays of characters, yet always
keeping in mind that LPC does not have the character data type (with
most, but not all drivers). Since the character is not a true LPC data
type, you cannot act upon individual characters in an LPC string in the
same manner you would act upon different data types. Noticing the
intimate relationship between strings and arrays nevertheless makes it
easier to understand such concepts as the range operator and indexing on
strings.
There are efuns other than sscanf() which involve advanced string
handling, however, they are not needed nearly as often. You should
check on your mud for man or help files on the efuns: explode(),
implode(), replace_string(), sprintf(). All of these are very valuable
tools, especially if you intend to do coding at the mudlib level.
Copyright (c) George Reese 1993
",({"chapter 6","chapter six","6",}):"chapter 6 \"Variable Handling\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: july 5 1993
CHAPTER 6: Variable Handling
6.1 Review
By now you should be able to code some simple objects using your muds standard
object library. Inheritance allows you to use functions defined in those
objects without having to go and define yourself. In addition,
you should know how to declare your own functions. This
chapter will teach you about the basic elements of LPC which will allow you to
define your own functions using the manipulation of variables.
6.2 Values and objects
Basically, what makes objects on the mud different are two things:
1) Some have different functions
2) All have different values
Now, all player objects have the same functions. They are therefore
differentiated by the values they hold. For instance, the player
named \"Forlock\" is different from \"Descartes\" *at least* in that they
have different values for the variable true_name, those being
\"descartes\" and \"forlock\".
Therefore, changes in the game involve changes in the values of the objects
in the game. Functions are used to name specific process for manipulating
values. For instance, the create() function is the function whose
process is specifically to initialize the values of an object.
Within a function, it is specifically things called instructions which are
responsible for the direct manipulation of variables.
6.3 Local and global variables
Like variables in most programming language, LPC variables may be declared
as variables \"local\" to a specific function, or \"globally\" available
to all functions. Local variables are declared inside the function which
will use them. No other function knows about their existence, since
the values are only stored in memory while that function is being executed.
A global variable is available to any function which comes after its
declaration in the object code. Since global variables take up RAM for
the entire existence of the object, you should use them only when
you need a value stored for the entire existence of the object.
Have a look at the following 2 bits of code:
-----
int x;
int query_x() { return x; }
void set_x(int y) { x = y; }
-----
-----
void set_x(int y) {
int x;
x = y;
write(\"x is set to x\"+x+\" and will now be forgotten.\\n\");
}
-----
In the first example, x is declared outside of any functions, and therefore
will be available to any function declared after it. In that example,
x is a global variable.
In the second example, x is declared inside the function set_x(). It
only exists while the function set_x() is being executed. Afterwards,
it ceases to exist. In that example, x is a local variable.
6.4 Manipulating the values of variables
Instructions to the driver are used to manipulate the values of variables.
An example of an instruction would be:
-----
x = 5;
-----
The above instruction is self-explanatory. It assigns to the variable
x the value 5. However, there are some important concepts in involved
in that instruction which are involved in instructions in general.
The first involves the concept of an expression. An expression is
any series of symbols which have a value. In the above instruction,
the variable x is assigned the value of the expression 5. Constant
values are the simplest forms in which expressions can be put. A constant
is a value that never changes like the int 5 or the string \"hello\".
The last concept is the concept of an operator. In the above example,
the assignment operator = is used.
There are however many more operators in LPC, and expressions can get
quite complex. If we go up one level of complexity, we get:
-----
y = 5;
x = y +2;
-----
The first instruction uses the assignment operator to assign the value
of the constant expression 5 to the variable y. The second one
uses the assignment operator to assign to x the value of the expression
(y+2) which uses the addition operator to come up with a value which
is the sum of the value of y and the value of the constant expression 2.
Sound like a lot of hot air?
In another manner of speaking, operators can be used to form complex
expressions. In the above example, there are two expressions in the
one instruction x = y + 2;:
1) the expression y+2
2) the expression x = y + 2
As stated before, all expressions have a value. The expression
y+2 has the value of the sum of y and 2 (here, 7);
The expression x = y + 2 *also* has the value of 7.
So operators have to important tasks:
1) They *may* act upon input like a function
2) They evaluate as having a value themselves.
Now, not all operators do what 1 does. The = operators does act upon
the value of 7 on its right by assigning that value to x. The operator
+ however does nothing. They both, however, have their own values.
6.5 Complex expressions
As you may have noticed above, the expression x = 5 *itself* has a value
of 5. In fact, since LPC operators themselves have value as expressions,
they cal allow you to write some really convoluted looking nonsense like:
i = ( (x=sizeof(tmp=users())) ? --x : sizeof(tmp=children(\"/std/monster\"))-1)
which says basically:
assing to tmp the array returned by the efun users(), then assign to x
the value equal to the number of elements to that array. If the value
of the expression assigning the value to x is true (not 0), then assign
x by 1 and assign the value of x-1 to i. If x is false though,
then set tmp to the array returned by the efun children(), and then
assign to i the value of the number of members in the array tmp -1.
Would you ever use the above statement? I doubt it. However you might
see or use expressions similar to it, since the ability to consolidate
so much information into one single line helps to speed up the execution of
your code. A more often used version of this property of LPC operators
would be something like:
x = sizeof(tmp = users());
while(i--) write((string)tmp[i]->GetKeyName()+\"\\n\");
instead of writing something like:
tmp = users();
x = sizeof(tmp);
for(i=0; i<x; i++) write((string)tmp[i]->GetKeyName()+\"\\n\");
Things like for(), while(), arrays and such will be explained later.
But the first bit of code is more concise and it executed faster.
NOTE: A detailed description of all basic LPC operators follows the chapter
summary.
6.6 Chapter Summary
You now know how to declare variables and understand the difference between
declaring and using them globally or locally. Once you become familiar
with your driver's efuns, you can display those values in many different
ways. In addition, through the LPC operators, you know how to change
and evaluate the values contained in variables. This is useful of course
in that it allows you to do something like count how many apples have
been picked from a tree, so that once all apples have been picked, no
players can pick more. Unfortunately, you do not know how to have
code executed in anything other than a linera fashion. In other words,
hold off on that apple until the next chapter, cause you do not know
how to check if the apples picked is equal to the number of apples in the
tree. You also do not know about the special function init() where you
give new commands to players. But you are almost ready to code a nice,
fairly complex area.
6.7 LPC operators
This section contains a detailed listing of the simpler LPC operators,
including what they do to the values they use (if anything) and the value
that they have.
The operators described here are:
= + - * / % += -= *= /= %=
-- ++ == != > < >= <= ! && ||
-> ? :
Those operators are all described in a rather dry manner below, but it is best
to at least look at each one, since some may not behave *exactly* as
you think. But it should make a rather good reference guide.
= assignment operator:
example: x = 5;
value: the value of the variable on the *left* after its function is done
explanation: It takes the value of any expression on the *right* and
assigns it to the variable on the *left*. Note that you must use
a single variable on the left, as you cannot assign values to
constants or complex expressions.
+ addition operator:
example: x + 7
value: The sum of the value on the left and the value on the right
exaplanation: It takes the value of the expression on the right and
adds it to the value of the expression on the left. For values
of type int, this means the numerical sum. For strings,
it means that the value on the right is stuck onto the value on
the left (\"ab\" is the value of \"a\"+\"b\"). This operator does not
modify any of the original values (i.e. the variable x from
above retains its old value).
- subtraction operator:
example: x - 7
value: the value of the expression on the left reduced by the right
explanation: Same characteristics as addition, except it subtracts.
With strings: \"a\" is the value of \"ab\" - \"b\"
* multiplication operator:
example: x*7
value and explanation: same as with adding and subtracting except
this one performs the math of multiplication
/ division operator:
example: x/7
value and explanation: see above
+= additive assignment operator:
example: x += 5
value: the same as x + 5
exaplanation: It takes the value of the variable on the left
and the value of the expression on the right, adds them together
and assigns the sum to the variable on the left.
example: if x = 2... x += 5 assigns the value
7 to the variable x. The whole expression
has the value of 7.
-= subtraction assignment operator
example: x-=7
value: the value of the left value reduced by the right value
examplanation: The same as += except for subtraction.
*= multiplicative assignment operator
example: x *= 7
value: the value of the left value multiplied by the right
explanation: Similar to -= and += except for addition.
/= division assignment operator
example: x /= 7
value: the value of the variable on the left divided by the right value
explanation: similar to above, except with division
++ post/pre-increment operators
examples: i++ or ++i
values:
i++ has the value of i
++i has the value of i+1
explanation: ++ changes the value of i by increasing it by 1.
However, the value of the expression depends on where you
place the ++. ++i is the pre-increment operator. This means
that it performs the increment *before* giving a value.
i++ is the post-ncrement operator. It evalutes before incrementing
i. What is the point? Well, it does not much matter to you at
this point, but you should recognize what it means.
-- post/pre-decrement operators
examples: i-- or --i
values:
i-- the value of i
--i the value of i reduced by 1
explanation: like ++ except for subtraction
== equality operator
example: x == 5
value: true or false (not 0 or 0)
explanation: it does nothing to either value, but
it returns true if the 2 values are the same.
It returns false if they are not equal.
!= inequality operator
example: x != 5
value: true or false
explanation returns true if the left expression is not equal to the right
expression. It returns fals if they are equal
> greater than operator
example: x > 5
value: true or false
explanation: true only if x has a value greater than 5
false if the value is equal or less
< less than operator
>= greater than or equal to operator
<= less than or equal to operator
examples: x < y x >= y x <= y
values: true or false
explanation: similar as to > except
< true if left is less than right
>= true if left is greater than *or equal to* right
<= true if the left is less than *or equal to* the right
&& logical and operator
|| logical or operator
examples: x && y x || y
values: true or false
explanation: If the right value and left value are non-zero, && is true.
If either are false, then && is false.
For ||, only one of the values must be true for it to evaluate
as true. It is only false if both values indeed
are false
! negation operator
example: !x
value: true or false
explanation: If x is true, then !x is false
If x is false, !x is true.
A pair of more complicated ones that are here just for the sake of being
here. Do not worry if they utterly confuse you.
-> the call other operator
example: this_player()->GetKeyName()
value: The value returned by the function being called
explanation: It calls the function which is on the right in the object
on the left side of the operator. The left expression *must* be
an object, and the right expression *must* be the name of a function.
If not such function exists in the object, it will return 0 (or
more correctly, undefined).
? : conditional operator
example: x ? y : z
values: in the above example, if x is try, the value is y
if x is false, the value of the expression is z
explanation: If the leftmost value is true, it will give the expression as
a whole the value of the middle expression. Else, it will give the
expression as a whole the value of the rightmost expression.
A note on equality: A very nasty error people make that is VERY difficult
to debug is the error of placing = where you mean ==. Since
operators return values, they both make sense when being evaluated.
In other words, no error occurs. But they have very different values. For example:
if(x == 5) if(x = 5)
The value of x == 5 is true if the value of x is 5, false othewise.
The value of x = 5 is 5 (and therefore always true).
The if statement is looking for the expression in () to be either true or false,
so if you had = and meant ==, you would end up with an expression that is
always true. And you would pull your hair out trying to figure out
why things were not happening like they should :)
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Flow Control\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 10 july 1993
CHAPTER 7: Flow Control
7.1 Review of variables
Variables may be manipulated by assigning or changing values with the
expressions =, +=, -=, ++, --. Those expressions may be combined with
the expressions -, +, *, /, %. However, so far, you have only been
shown how to use a function to do these in a linear way. For example:
int hello(int x) {
x--;
write(\"Hello, x is \"+x+\".\\n\");
return x;
}
is a function you should know how to write and understand. But what
if you wanted to write the value of x only if x = 1? Or what if
you wanted it to keep writing x over and over until x = 1 before
returning? LPC uses flow control in exactly the same way as C and C++.
7.2 The LPC flow control statements
LPC uses the following expressions:
if(expression) instruction;
if(expression) instruction;
else instruction;
if(expression) instruction;
else if(expression) instruction;
else instruction;
while(expression) instruction;
do { instruction; } while(expression);
switch(expression) {
case (expression): instruction; break;
default: instruction;
}
Before we discuss these, first something on what is meant by expression and
instruction. An expression is anything with a value like a variable,
a comparison (like x>5, where if x is 6 or more, the value is 1, else the
value is 0), or an assignment(like x += 2). An instruction can be any
single line of lpc code like a function call, a value assignment or
modification, etc.
You should know also the operators &&, ||, ==, !=, and !. These are the
logical operators. They return a nonzero value when true, and 0 when false.
Make note of the values of the following expressions:
(1 && 1) value: 1 (1 and 1)
(1 && 0) value: 0 (1 and 0)
(1 || 0) value: 1 (1 or 0)
(1 == 1) value: 1 (1 is equal to 1)
(1 != 1) value: 0 (1 is not equal to 1)
(!1) value: 0 (not 1)
(!0) value: 1 (not 0)
In expressions using &&, if the value of the first item being compared
is 0, the second is never tested even. When using ||, if the first is
true (1), then the second is not tested.
7.3 if()
The first expression to look at that alters flow control is if(). Take
a look at the following example:
1 void reset() {
2 int x;
3
4 ::reset();
5 x = random(10);
6 if(x > 50) SetSearch_func(\"floorboards\", \"search_floor\");
7 }
The line numbers are for reference only.
In line 2, of course we declare a variable of type int called x. Line 3
is aethetic whitespace to clearly show where the declarations end and the
function code begins. The variable x is only available to the function
reset().
Line 4 makes a call to the room.c version of reset().
Line 5 uses the driver efun random() to return a random number between
0 and the parameter minus 1. So here we are looking for a number between
0 and 99.
In line 6, we test the value of the expression (x>50) to see if it is true
or false. If it is true, then it makes a call to the room.c function
SetSearch_func(). If it is false, the call to SetSearch_func() is never
executed.
In line 7, the function returns driver control to the calling function
(the driver itself in this case) without returning any value.
If you had wanted to execute multiple instructions instead of just the one,
you would have done it in the following manner:
if(x>50) {
SetSearch_func(\"floorboards\", \"search_floor\");
if(!present(\"beggar\", this_object())) make_beggar();
}
Notice the {} encapsulate the instructions to be executed if the test
expression is true. In the example, again we call the room.c function
which sets a function (search_floor()) that you will later define yourself
to be called when the player types \"search floorboards\" (NOTE: This is
highly mudlib dependent. Nightmare mudlibs have this function call.
Others may have something similar, while others may not have this feature
under any name). Next, there is another if() expression that tests the
truth of the expression (!present(\"beggar\",this_object())). The ! in the
test expression changes the truth of the expression which follows it. In
this case, it changes the truth of the efun present(), which will return
the object that is a beggar if it is in the room (this_object()), or it
will return 0 if there is no beggar in the room. So if there is a beggar
still living in the room, (present(\"beggar\", this_object())) will have
a value equal to the beggar object (data type object), otherwise it will
be 0. The ! will change a 0 to a 1, or any nonzero value (like the
beggar object) to a 0. Therefore, the expression
(!present(\"beggar\", this_object())) is true if there is no beggar in the
room, and false if there is. So, if there is no beggar in the room,
then it calls the function you define in your room code that makes a
new beggar and puts it in the room. (If there is a beggar in the room,
we do not want to add yet another one :))
Of course, if()'s often comes with ands or buts :). In LPC, the formal
reading of the if() statement is:
if(expression) { set of intructions }
else if(expression) { set of instructions }
else { set of instructions }
This means:
If expression is true, then do these instructions.
Otherise, if this second expression is true, do this second set.
And if none of those were true, then do this last set.
You can have if() alone:
if(x>5) write(\"Foo,\\n\");
with an else if():
if(x > 5) write(\"X is greater than 5.\\n\");
else if(x >2) write(\"X is less than 6, but greater than 2.\\n\");
with an else:
if(x>5) write(\"X is greater than 5.\\n\");
else write(\"X is less than 6.\\n\");
or the whole lot of them as listed above. You can have any number of
else if()'s in the expression, but you must have one and only one
if() and at most one else. Of course, as with the beggar example,
you may nest if() statements inside if() instructions. (For example,
if(x>5) {
if(x==7) write(\"Lucky number!\\n\");
else write(\"Roll again.\\n\");
}
else write(\"You lose.\\n\");
7.4 The statements: while() and do {} while()
Prototype:
while(expression) { set of instructions }
do { set of instructions } while(expression);
These allow you to create a set of instructions which continue to
execute so long as some expression is true. Suppose you wanted to
set a variable equal to a player's level and keep subtracting random
amounts of either money or hp from a player until that variable equals
0 (so that player's of higher levels would lose more). You might do it
this way:
1 int x;
2
3 x = (int)this_player()->query_level(); /* this has yet to be explained */
4 while(x > 0) {
5 if(random(2)) this_player()->add_money(\"silver\", -random(50));
6 else this_player()->add_hp(-(random(10));
7 x--;
8 }
The expression this_player()->query_level() calIn line 4, we start a loop that executes so long as x is greater than 0.
Another way we could have done this line would be:
while(x) {
The problem with that would be if we later made a change to the funtion
y anywhere between 0 and 49 coins.
In line 6, if instead it returns 0, we call the add_hp() function in the
player which reduces the player's hit points anywhere between 0 and 9 hp.
In line 7, we reduce x by 1.
At line 8, the execution comes to the end of the while() instructions and
goes back up to line 4 to see if x is still greater than 0. This
loop will keep executing until x is finally less than 1.
You might, however, want to test an expression *after* you execute some
instructions. For instance, in the above, if you wanted to execute
the instructions at least once for everyone, even if their level is
below the test level:
int x;
x = (int)this_player()->query_level();
do {
if(random(2)) this_player()->add_money(\"silver\", -random(50));
else this_player()->add_hp(-random(10));
x--;
} while(x > 0);
This is a rather bizarre example, being as few muds have level 0 players.
And even still, you could have done it using the original loop with
a different test. Nevertheless, it is intended to show how a do{} while()
works. As you see, instead of initiating the test at the beginning of the
loop (which would immediately exclude some values of x), it tests after
the loop has been executed. This assures that the instructions of the loop
get executed at least one time, no matter what x is.
7.5 for() loops
Prototype:
for(initialize values ; test expression ; instruction) { instructions }
initialize values:
This allows you to set starting values of variables which will be used
in the loop. This part is optional.
test expression:
Same as the expression in if() and while(). The loop is executed
as long as this expression (or expressions) is true. You must have a
test expression.
instruction:
An expression (or expressions) which is to be executed at the end of each
loop. This is optional.
Note:
for(;expression;) {}
IS EXACTLY THE SAME AS
while(expression) {}
Example:
1 int x;
2
3 for(x= (int)this_player()->query_level(); x>0; x--) {
4 if(random(2)) this_player()->add_money(\"silver\", -random(50));
5 else this_player()->add_hp(-random(10));
6 }
This for() loop behaves EXACTLY like the while() example.
Additionally, if you wanted to initialize 2 variables:
for(x=0, y=random(20); x<y; x++) { write(x+\"\\n\"); }
Here, we initialize 2 variables, x and y, and we separate them by a
comma. You can do the same with any of the 3 parts of the for()
expression.
7.6 The statement: switch()
Prototype:
switch(expression) {
case constant: instructions
case constant: instructions
...
case constant: instructions
default: instructions
}
This is functionally much like if() expressions, and much nicer to the
CPU, however most rarely used because it looks so damn complicated.
But it is not.
First off, the expression is not a test. The cases are tests. A English
sounding way to read:
1 int x;
2
3 x = random(5);
4 switch(x) {
5 case 1: write(\"X is 1.\\n\");
6 case 2: x++;
7 default: x--;
8 }
9 write(x+\"\\n\");
is:
set variable x to a random number between 0 and 4.
In case 1 of variable x write its value add 1 to it and subtract 1.
In case 2 of variable x, add 1 to its value and then subtract 1.
In other cases subtract 1.
Write the value of x.
switch(x) basically tells the driver that the variable x is the value
we are trying to match to a case.
Once the driver finds a case which matches, that case *and all following
cases* will be acted upon. You may break out of the switch statement
as well as any other flow control statement with a break instruction in
order only to execute a single case. But that will be explained later.
The default statement is one that will be executed for any value of
x so long as the switch() flow has not been broken. You may use any
data type in a switch statement:
string name;
name = (string)this_player()->GetKeyName();
switch(name) {
case \"descartes\": write(\"You borg.\\n\");
case \"flamme\":
case \"forlock\":
case \"shadowwolf\": write(\"You are a Nightmare head arch.\\n\");
default: write(\"You exist.\\n\");
}
For me, I would see:
You borg.
You are a Nightmare head arch.
You exist.
Flamme, Forlock, or Shadowwolf would see:
You are a Nightmare head arch.
You exist.
Everyone else would see:
You exist.
7.7 Altering the flow of functions and flow control statements
The following instructions:
return continue break
alter the natural flow of things as described above.
First of all,
return
no matter where it occurs in a function, will cease the execution of that
function and return control to the function which called the one the
return statement is in. If the function is NOT of type void, then a
value must follow the return statement, and that value must be of a
type matching the function. An absolute value function would look
like this:
int absolute_value(int x) {
if(x>-1) return x;
else return -x;
}
In the second line, the function ceases execution and returns to the calling
function because the desired value has been found if x is a positive
number.
continue is most often used in for() and while statements. It serves
to stop the execution of the current loop and send the execution back
to the beginning of the loop. For instance, say you wanted to avoid
division by 0:
x= 4;
while( x > -5) {
x--
if(!x) continue;
write((100/x)+\"\\n\");
}
write(\"Done.\\n\")
You would see the following output:
33
50
100
-100
-50
-33
-25
Done.
To avoid an error, it checks in each loop to make sure x is not 0.
If x is zero, then it starts back with the test expression without
finishing its current loop.
In a for() expression
for(x=3; x>-5; x--) {
if(!x) continue;
write((100/x)+\"\\n\");
}
write(\"Done.\\n\");
It works much the same way. Note this gives exactly the same output
as before. At x=1, it tests to see if x is zero, it is not, so it
writes 100/x, then goes back to the top, subtracts one from x, checks to
see if it is zero again, and it is zero, so it goes back to the top
and subtracts 1 again.
break
This one ceases the function of a flow control statement. No matter
where you are in the statement, the control of the program will go
to the end of the loop. So, if in the above examples, we had
used break instead of continue, the output would have looked like this:
33
50
100
Done.
continue is most often used with the for() and while() statements.
break however is mostly used with switch()
switch(name) {
case \"descartes\": write(\"You are borg.\\n\"); break;
case \"flamme\": write(\"You are flamme.\\n\"); break;
case \"forlock\": write(\"You are forlock.\\n\"); break;
case \"shadowwolf\": write(\"You are shadowwolf.\\n\"); break;
default: write(\"You will be assimilated.\\n\");
}
This functions just like:
if(name == \"descartes\") write(\"You are borg.\\n\");
else if(name == \"flamme\") write(\"You are flamme.\\n\");
else if(name == \"forlock\") write(\"You are forlock.\\n\");
else if(name == \"shadowwolf\") write(\"You are shadowwolf.\\n\");
else write(\"You will be assimilated.\\n\");
except the switch statement is much better on the CPU.
If any of these are placed in nested statements, then they alter the
flow of the most immediate statement.
7.8 Chapter summary
This chapter covered one hell of a lot, but it was stuff that needed to
be seen all at once. You should now completely understand if() for()
while() do{} while() and switch(), as well as how to alter their flow
using return, continue, and break. Effeciency says if it can be done in
a natural way using switch() instead of a lot of if() else if()'s, then
by all means do it. You were also introduced to the idea of calling
functions in other objects. That however, is a topic to be detailed later.
You now should be completely at ease writing simple rooms (if you have
read your mudlib's room building document), simple monsters, and
other sorts of simple objects.
",({"chapter 4","chapter four","4",}):"chapter 4 \"Functions\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 22 june 1993
CHAPTER 4: Functions
4.1 Review
By this point, you should be aware that LPC objects consist of functions
which manipulate variables. The functions manipulate variables when they
are executed, and they get executed through *calls* to those functions.
The order in which the functions are placed in a file does not matter.
Inside a function, the variables get manipulated. They are stored in
computer memory and used by the computer as 0's and 1's which
get translated to and from useable output and input through a device
called data typing. String data types tell the driver that the
data should appear to you and come from you in the form of alphanumeric
characters. Variables of type int are represented to you as whole
number values. Type status is represented to you as either 1 or 0.
And finally type void has no value to you or the machine, and is not
really used with variable data types.
4.2 What is a function?
Like math functions, LPC functions take input and return output.
Languages like Pascal distinguish between the concept of proceedure abd
the concept of function. LPC does not, however, it is useful to
understand this distinction. What Pascal calls a proceedure, LPC
calls a function of type void. In other words, a proceedure, or function
of type void returns no output. What Pascal calls a function differs
in that it does return output. In LPC, the most trivial, correct
function is:
-----
void do_nothing() { }
-----
This function accepts no input, performs no instructions, and returns no
value.
There are three parts to every properly written LPC function:
1) The declaration
2) The definition
3) The call
Like with variables, functions must be declared. This will allow the
driver to know 1) what type of data the function is returning as output,
and 2) how many input(s) and of what type those input(s) are. The
more common word for input is parameters.
A function declaration therefore consists of:
type name(parameter1, parameter2, ..., parameterN);
The declaration of a function called drink_water() which accepts a string as
input and an int as output would thus look like this:
-----
int drink_water(string str);
-----
where str is the name of the input as it will be used inside the function.
The function definition is the code which describes what the function actually
does with the input sent to it.
The call is any place in other functions which invokes the execution of the
function in question. For two functions write_vals() and add(), you thus
might have the following bit of code:
-----
/* First, function declarations. They usually appear at the beginning
of object code.
*/
void write_vals();
int add(int x, int y);
/* Next, the definition of the function write_vals(). We assume that
this function is going to be called from outside the object
*/
void write_vals() {
int x;
/*N Now we assign x the value of the output of add() through a call */
x = add(2, 2);
write(x+\"\\n\");
}
/* Finally, the definition of add() */
int add(int x, int y) {
return (x + y);
}
-----
Remember, it does not matter which function definition appears first in the
code. This is because functions are not executed consecutively. Instead,
functions are executed as called. The only requirement is that the
declaration of a function appear before its definition and before the
definition of any function which makes a call to it.
4.3 Efuns
Perhaps you have heard people refer to efuns. They are externally defined
functions. Namely, they are defined by the mud driver. If you have
played around at all with coding in LPC, you have probably found some
expressions you were told to use like this_player(), write(), say(),
this_object(), etc. look a lot like functions. That is because they are
efuns. The value of efuns is that they are much faster than LPC functions,
since they already exist in the binary form the computer understands.
In the function write_vals() above, two functions calls were made. The first was to
the functions add(), which you declared and defined. The second call, however,
was to a function called write(), and efun. The driver has already declared
and defined this function for you. You needs only to make calls to it.
Efuns are created to hanldle common, every day function calls, to handle
input/output to the internet sockets, and other matters difficult to be
dealt with in LPC. They are written in C in the game driver and compiled
along with the driver before the mud comes up, making them much faster
in execution. But for your purposes, efun calls are just like calls
made to your functions. Still, it is important to know two things of any
efun: 1) what return type does it have, and 2) what parameters of what
types does it take.
Information on efuns such as input parameters and return types is often
found in a directory called /doc/efun on your mud. I cannot
detail efuns here, because efuns vary from driver to driver. However,
you can often access this information using the commands \"man\" or \"help\"
depending on your mudlib. For instance, the command \"man write\" would
give you information on the write efun. But if all else fails,
\"more /doc/efun/write\" should work.
By looking it up, you will find write is declared as follows:
-----
void write(string);
-----
This tells you an appropriate call to write expects no return value and
passes a single parameter of type string.
4.4 Defining your own functions
Although ordering your functions within the file does not matter, ordering
the code which defines a function is most important. Once a function
has been called, function code is executed in the order it appears
in the function definition. In write_vals() above, the instruction:
-----
x = add(2, 2);
-----
Must come before the write() efun call if you want to see the appropriate
value of x used in write().
With respect to values returned by function, this is done through the \"return\"
instruction followed by a value of the same data type as the function. In
add() above, the instruction is \"return (x+y);\", where the value of (x+y)
is the value returned to write_vals() and assigned to x. On a more
general level, \"return\" halts the execution of a function and returns
code execution to the function which called that function. In addition,
it returns to the calling function the value of any expression that follows.
To stop the execution of a function of type void out of order, use
\"return\"; without any value following. Once again, remember, the data
type of the value of any expression returned using \"return\" MUST be the
same as the data type of the function itself.
4.5 Chapter Summary
The files which define LPC objects are made of of functions. Functions, in
turn, are made up of three parts:
1) The declaration
2) The definition
3) The call
Function declarations generally appear at the top of the file before any
defintions, although the requirement is that the declaration must appear
before the function definition and before the definition of any function
which calls it.
Function definitions may appear in the file in any order so long as they
come after their declaration. In addition, you may not define one function
inside another function.
Function calls appear inside the definition of other functions where you
want the code to begin execution of your function. They may also appear
within the definition of the function itself, but this is not recommended
for new coders, as it can easily lead to infinite loops.
The function definition consists of the following in this order:
1) function return type
2) function name
3) opening ( followed by a parameter list and a closing )
4) an opening { instructing the driver that execution begins here
5) declarations of any variables to be used only in that function
6) instructions, expressions, and calls to other functions as needed
7) a closing } stating that the function code ends here and, if no
\"return\" instruction has been given at this point (type void functions
only), execution returns to the calling function as if a r\"return\"
instruction was given
The trivial function would thus be:
-----
void do_nothing() {}
-----
since this function does not accept any input, perform any instructions, or
return any output.
Any function which is not of type void MUST return a value of a data type
matching the function's data type.
Each driver has a set of functions already defined for you called efuns
These you need neither need to declare nor define since it has already
been done for you. Furthermore, execution of these functions is faster
than the execution of your functions since efuns are in the driver.
In addition, each mudlib has special functions like efuns in that they
are already defined and declared for you, but different in that they
are defined in the mudlib and in LPC. They are called simul_efuns, or
simulated efuns. You can find out all about each of these as they are
listed in the /doc/efun directory on most muds. In addition many
muds have a command called \"man\" or a \"help\" command which allows you
simply to call up the info files on them.
Note on style:
Some drivers may not require you to declare your functions, and some
may not require you to specify the return type of the function in its
definition. Regardless of this fact, you should never omit this information
for the following reasons:
1) It is easier for other people (and you at later dates) to read your
code and understand what is meant. This is particularly useful
for debugging, where a large portion of errors (outside of misplaced
parentheses and brackets) involve problems with data types (Ever
gotten \"Bad arg 1 to foo() line 32\"?).
2) It is simply considered good coding form.
",({"chapter 5","chapter five","5",}):"chapter 5 \"The Basics of Inheritance\"
LPC Basics
Written by Descartes of Borg
first edition: 23 april 1993
second edition: 01 july 1993
CHAPTER 5: The Basics of Inheritance
5.1 Review
You should now understand the basic workings of functions. You should be
able to declare and call one. In addition, you should be able to recognize
function definitions, although, if this is your first experience with LPC,
it is unlikely that you will as yet be able to define your own functions.
There functions form the basic building blocks of LPC objects. Code
in them is executed when another function makes a call to them. In making
a call, input is passed from the calling function into the execution of
the called one. The called function then executes and returns a value
of a certain data type to the calling function. Functions which return
no value are of type void.
After examining your workroom code, it might look something like this
(depending on the mudlib):
-----
inherit \"/std/room\";
void create() {
::create();
SetProperty(\"light\", 2);
SetProperty(\"indoors\", 1);
set(\"short\", \"Descartes' Workroom\");
set(\"long\", \"This is where Descartes works.\\nIt is a cube.\\n\");
SetExits( ({ \"/domains/standard/square\" }), ({ \"square\" }) );
}
-----
If you understand the entire textbook to this point, you should recognize
of the code the following:
1) create() is the definition of a function (hey! he did not declare it)
2) It makes calls to SetProperty(), set(), and SetExits(), none
of which are declared or defined in the code.
3) There is a line at the top that is no variable or function declaration
nor is it a function definition!
This chapter will seek to answer the questions that should be in your head
at this point:
1) Why is there no declaration of create()?
2) Where are the functions SetProperty(), set(), and SetExits() declared
and defined?
3) What the hell is that line at the top of the file?
5.2 Object oriented programming
Inheritance is one of the properties which define true object oriented
programming (OOP). It allows you to create generic code which can be used
in many different ways by many different programs. What a mudlib does is
create these generalized files (objects) which you use to make very specific
objects.
If you had to write the code necessary for you to define the workroom above,
you would have to write about 1000 lines of code to get all the functionality
of the room above. Clearly that is a waste of disk space. In addition,
such code does not interact well with players and other rooms since every
creator is making up his or her own functions to perform the functionality
of a room. Thus, what you might use to write out the room's long description,
GetLong(), another wizard might be calling long(). This is the primary
reason mudlibs are not compatible, since they use different protocols for
object interaction.
OOP overcomes these problems. In the above workroom, you inherit the
functions already defined in a file called \"/std/room.c\". It has all
the functions which are commonly needed by all rooms defined in it. When
you get to make a specific room, you are taking the general functionality
of that room file and making a unique room by adding your own function,
create().
5.3 How inheritance works
As you might have guessed by now, the line:
-----
inherit \"/std/room\";
-----
has you inherit the functionality of the room \"/std/room.c\". By inheriting
the functionality, it means that you can use the functions which have
been declared and defined in the file \"/std/room.c\" In the Nightmare Mudlib,
\"/std/room.c\" has, among other functions, SetProperty(), set(), and
SetExits() declared and defined. In your function create(), you are
making calls to those functions in order to set values you want your
room to start with. These values make your room different from others, yet
able to interact well with other objects in memory.
In actual practice, each mudlib is different, and thus requires you to use
a different set of standard functions, often to do the same thing. It is
therefore beyond the scope of this textbook even to describe what
functions exist and what they do. If your mudlib is well documented,
however, then (probably in /doc/build) you will have tutorials on how
to use the inheritable files to create such objects. These tutorials
should tell you what functions exist, what input they take, the data
type of their output, and what they do.
5.4 Chapter summary
This is far from a complete explanation of the complex subject of inheritance.
The idea here is for you to be able to understand how to use inheritance in
creating your objects. A full discussion will follow in a later textbook.
Right now you should know the following:
1) Each mudlib has a library of generic objects with their own general
functions used by creators through inheritance to make coding objects
easier and to make interaction between objects smoother.
2) The functions in the inheritable files of a mudlib vary from mudlib
to mudlib. There should exist documentation on your mud on how to
use each inheritable file. If you are unaware what functions are
available, then there is simply no way for you to use them. Always
pay special attention to the data types of the input and the data
types of ay output.
3) You inherit the functionality of another object through the line:
-----
inherit \"filename\";
-----
where filename is the name of the file of the object to be inherited.
This line goes at the beginning of your code.
Note:
You may see the syntax ::create() or ::init() or ::reset() in places.
You do not need fully to understand at this point the full nuances of this,
but you should have a clue as to what it is. The \"::\" operator is a way
to call a function specifically in an inherited object (called the scope
resolution operator). For instance, most muds' room.c has a function
called create(). When you inherit room.c and configure it, you are doing
what is called overriding the create() function in room.c. This means
that whenever ANYTHING calls create(), it will call *your* version and not
the one in room.c. However, there may be important stuff in the room.c
version of create(). The :: operator allows you to call the create() in
room.c instead of your create().
An example:
-----
#1
inherit \"/std/room\";
void create() { create(); }
-----
-----
#2
inherit \"/std/room\";
void create() { ::create(); }
-----
Example 1 is a horror. When loaded, the driver calls create(), and then
create() calls create(), which calls create(), which calls create()...
In other words, all create() does is keep calling itself until the driver
detects a too deep recursion and exits.
Example 2 is basically just a waste of RAM, as it is no different from room.c
functionally. With it, the driver calls its create(), which in turn calls
::create(), the create() in room.c. Otherwise it is functionally
exactly the same as room.c.
",({"chapter 17","chapter seventeen","17",}):"chapter 17 \"Barkeeps\"
Building Food and Drink Sellers
The Nightmare IV LPC Library
written by Descartes of Borg 950528
This document details the building of barkeeps, waiters, and other
such people who sell food and drink. Barkeeps are NPC's, and
therefore everythign which applies to NPC's applies to barkeeps.
To build a barkeep, you should inherit LIB_BARKEEP.
Beyond the functions specific to NPC's barkeeps also make use of the
following functions:
mapping SetMenuItems(mapping menu);
mapping AddMenuItem(string item, string file);
mapping RemoveMenuItem(string item);
mapping GetMenuItems();
string SetLocalCurrency(string curr);
When building a barkeep, you must add some mechanism in the room in
which the barkeep is placed for people to view a list of things for
sale.
*****
mapping SetMenuItems(mapping menu);
*****
Example: SetMenuItems( ([ \"coffee\" : \"/realms/descartes/coffee\" ]) );
Sets which menu items are found in which file. This is a mapping with
the name of the item as a key and the file in which it is located as
the value.
*****
mapping AddMenuItem(string item, string file);
*****
Example: AddMenuItem(\"lobster\", \"/realms/descartes/lobster\");
Adds one menu item at a time to the list of menu items.
*****
mapping RemoveMenuItem(string item);
*****
Example: RemoveMenuItem(\"coffee\");
Removes the named item from the menu.
*****
mapping GetMenuItems();
*****
Returns all the menu items for this barkeep. Useful in building your
menu list.
*****
string SetLocalCurrency(string curr);
*****
Example: SetLocalCurrency(\"khucha\");
Sets the currency in which the barkeep does business.
",({"chapter 20","chapter twenty","20",}):"chapter 20 \"Items\"
Building Any Item
The Nightmare IV LPC Library
written by Descartes of Borg 950430
Each object you build has a certain common make-up no matter what type
of object it is. This is because all of your objects actually are
based upon the same object, the object called /lib/object.c. That
object contains functions such as SetShort() and SetLong() which you
use in almost every single object you will build. This document
details how to set up any possible object you will use. The only
exceptions will be for rooms, which do not have key names or id's.
Beyond that, most of the every day objects you will code like armours,
weapons, drinks, foods, etc. all derive from a common object called
/lib/item.c. This document attempts to detail what is involved in
building any object on the MUD through /lib/item.c and its ancestor
/lib/object.c.
This document is in three sections
I. List of Mandatory Function Calls
II. Basic Functions
III. Extras
IV. Events
** *************** List of Mandatory Function Calls ************** **
SetKeyName(\"red bag\");
SetId( ({ \"bag\", \"red bag\" }) );
SetAdjectives( ({ \"red\" }) );
SetShort(\"a red bag\");
SetLong(\"A small red bag with no distinguishing marks.\");
SetMass(90);
SetValue(50);
SetVendorType(VT_BAG);
You also need to include vendor_types.h.
** *************** Basic Functions *************** **
*****
SetKeyName()
*****
string SetKeyName(string key_name);
Example: SetKeyName(\"red bag\");
Notes:
Mandatory for all objects except rooms.
Not used for rooms.
The key name is the central name by which the object is referred to in
sentences where no article is required. For example, the sentence
\"You pick up your red bag\" makes use of the key name to complete the
sentence. This is much like the short description, except the short
description will include an article. For this object, SetShort(\"a red
bag\") would be used.
*****
SetId()
*****
string *SetId(string *id);
Example: SetId( ({ \"bag\", \"red bag\" }) );
Notes:
Mandatory for all objects except rooms.
Not used in rooms.
Must be all lower case.
The id is an array of strings by which the object may be referred to by a
player. For example, if the player wants to get this bag, the player
can type \"get bag\" or \"get red bag\". The id is used purely for
identification purposes, so if you have something you need to sneak in
a unique way of identifying it, you may add an id only you know about.
*****
SetAdjectives()
*****
string *SetAdjectives(string *adjs);
Example: SetAdjectives( ({ \"red\" }) );
Notes:
Planned for future use in Zork style command parsing.
Not used in rooms.
The adjectives are descriptive terms used to describe the object.
This is not currently being used, however, it will be part of the new
style command parsing we will be building. This will allow the player
to type things like \"get the red one\" and pick up the red bag. Even
though it is not used, it is requested you place it in your objects to
make upgrading down the road a simpler task.
*****
SetShort()
*****
string SetShort(string description | function desc_func);
Examples:
SetShort(\"a red bag\");
SetShort((: DescribeBag :));
The short description is a brief description of the object. Only
names and proper nouns should be capitalized, the rest should be lower
case, as if it were appearing in the middle of a sentence. In rooms,
the player sees the short description when in brief mode and when they
glance at the room. For objects, the player sees the short when it is
described in the room or in their inventory.
If you pass a function instead of a string, then that function is used
to create the description. You can use this to do something like make
the object change its short description depending on who is looking at
it. The function that you build should therefore return a string
that will be used as the short description. For example...
string DescribeBag() {
if( query_night() ) return \"a bag\";
else return \"a red bag\";
}
*****
SetLong()
*****
string SetLong(string description | function desc_func);
Examples:
SetLong(\"A red bag with no markings on it whatsoever.\");
SetLong((: BagLong :));
Creates a verbose way to present the object to the player. You should
be much more descriptive than I have been in the example. Being a
text game, descriptions are 90% of what make the game. The more
creative you are with your descriptions, the more interesting the game
is to players. The long description of a room is seen by players in
verbose mode and when the player uses the \"look\" command. For
objects, the long description is seen when the player looks at the
object. Functions work in exactly the same fashion as short
functions.
*****
SetMass()
*****
int SetMass(int mass);
Example: SetMass(100);
Notes:
Mandatory for all visible objects.
Not needed for non-tangible objects and rooms.
Sets the mass for the object. In conjunction with the gravity of the
room it is in, this works to determine the weight of the object.
*****
SetValue()
*****
int SetValue(int value);
Example: SetValue(50);
Notes:
Mandatory for all sellable objects.
Not used in rooms.
Sets the base economic value of an object. This has no meaning in any
currencies, and in fact the actual value in any given currency may
vary.
*****
SetVendorType()
*****
int SetVendorType(int vt);
Example: SetVendorType(VT_BAG);
Note:
Mandatory for all objects except rooms.
Preset to VT_ARMOUR for objects which inherit LIB_ARMOUR.
Preset to VT_TREASURE for objects which inherit LIB_ITEM.
Preset to VT_LIGHT for objects which inherit LIB_LIGHT.
Not valid for room objects.
Values are found in /include/vendor_types.h.
You must do:
#include <vendor_types.h>
to use the VT_* macros (i.e. VT_ARMOUR, VT_TREASURE, VT_WEAPON).
The vendor type determines which shops will buy the item. For
example, things with VT_BAG as the vendor type can be bought and sold
in bag stores. For items which cross the line, for example a flaming
sword, you can combine vendor types in the following manner:
SetVendorType(VT_WEAPON | VT_LIGHT);
*****
SetDamagePoints()
*****
int SetDamagePoints(int pts);
Example: SetDamagePoints(500)
Sets the amount of damage an object can take before descreasing in
value. With armours and weapons, damage is taken quite often. Damage
is more rare with other kinds of objects. With this example object
which has 500 damage points, whenever 500 points has been done to it,
its value is cut in half and eventDeteriorate() is called for the
object. See the events section on using eventDeteriorate(). The
points are then reset to 500 and damage is done from that.
** *************** Extras *************** **
*****
SetProperty()
*****
mixed SetProperty(string property, mixed value);
Example: SetProperty(\"no pick\", 1);
Allows you to store information in an object which may not have been
intended by the designer of the object, or which is fleeting in
nature. See /doc/build/Properties for a list of common properties.
*****
SetProperties()
*****
mapping SetProperties(mapping props);
Example: SetProperties( ([ \"light\" : 1, \"no attack\" : 1 ]) );
Allows you to set any properties you want all in one shot.
*****
SetDestroyOnSell()
*****
int SetDestroyOnSell(int true_or_false);
Example: SetDestroyOnSell(1);
For mundane objects, or objects which should not be resold, allows you
to set it so that the object gets destroyed when sold instead of
allowing it to be resold.
*****
SetPreventGet()
*****
mixed SetPreventGet(mixed val);
Examples:
SetPreventGet(\"You cannot get that!\");
SetPreventGet( (: check_get :) );
Allows you to make an object un-gettable by a player. If you pass a
string, the player will see that string any time they try to get the
item. If you pass a function, that function will be called to see if
you want to allow the get. Your function gets the person trying to get
the object as an argument:
int check_get(object who) {
if( (int)who->GetRave() == \"ogre\" ) {
message(\"my_action\", \"Ogres cannot get this thing!\", who);
return 0;
}
else return 1;
}
*****
SetPreventPut()
*****
mixed SetPreventPut(mixed val);
Examples:
SetPreventPut(\"You cannot put that in there!\");
SetPreventPut( (: check_put :) );
The same as SetPreventGet(), except this is used when the object is
being put into another object.
*****
SetPreventDrop()
*****
mixed SetPreventDrop(mixed val);
Examples:
SetPreventDrop(\"You cannot drop that!\");
SetPreventDrop( (: check_drop :) );
The same as SetPreventGet(), except this is used when a player tries
to drop the object.
** *************** General Events ************** **
*****
eventDeteriorate()
*****
void eventDeteriorate(int type);
Example: ob->eventDeteriorate(COLD);
Notes:
Damage types can be found in /include/damage_types.h
This function gets called periodically in objects whenever they wear
down a bit. The type passed to the function is the type of damage
which triggered the deterioration.
*****
eventMove()
*****
int eventMove(mixed dest);
Example:
ob->eventMove(this_player());
ob->eventMove(\"/domains/Praxis/square\");
The eventMove event is called in an object when it is being moved from
one place to the next. You can either pass it the file name of a room
to which it should be moved or an object into which it should be
moved. It will return true if the object gets moved, false if it
cannot move for some reason. For objects which are being dropped,
gotten, or put, it is generally a good idea to check CanDrop(),
CanClose(), or CanGet() for the object in question since eventMove()
does not know the context of the move and therefore will allow a drop
since it does not check CanDrop().
*****
eventReceiveDamage()
*****
varargs int eventReceiveDamage(int type, int amount, int unused, mixed limbs);
Example: ob->eventReceiveDamage(BLUNT, 30, 0, \"right hand\");
This function gets called in an object whenever any damage is done to
it. Most frequently this gets called in monsters and armour. In
armour you can use it to modify the amount of damage which gets done.
The return value of this function is the amount of damage done to the
object. For example, if you have a piece of armour that absorbs 5 of
the 30 points listed above, then you return 5.
NOTE:
For monsters there is an extra arg at the front called
agent. The agent is the being responsible for doing
the damage. It may be zero if something like the weather
is causing the damage. It looks like:
varargs int eventReceiveDamage(object agent, int type, int strength,
int internal, mixed limbs);
For more detailed information, see /doc/build/NPC.
",({"chapter 17","chapter seventeen","17",}):"chapter 17 \"Barkeeps\"
Building Food and Drink Sellers
The Nightmare IV LPC Library
written by Descartes of Borg 950528
This document details the building of barkeeps, waiters, and other
such people who sell food and drink. Barkeeps are NPC's, and
therefore everythign which applies to NPC's applies to barkeeps.
To build a barkeep, you should inherit LIB_BARKEEP.
Beyond the functions specific to NPC's barkeeps also make use of the
following functions:
mapping SetMenuItems(mapping menu);
mapping AddMenuItem(string item, string file);
mapping RemoveMenuItem(string item);
mapping GetMenuItems();
string SetLocalCurrency(string curr);
When building a barkeep, you must add some mechanism in the room in
which the barkeep is placed for people to view a list of things for
sale.
*****
mapping SetMenuItems(mapping menu);
*****
Example: SetMenuItems( ([ \"coffee\" : \"/realms/descartes/coffee\" ]) );
Sets which menu items are found in which file. This is a mapping with
the name of the item as a key and the file in which it is located as
the value.
*****
mapping AddMenuItem(string item, string file);
*****
Example: AddMenuItem(\"lobster\", \"/realms/descartes/lobster\");
Adds one menu item at a time to the list of menu items.
*****
mapping RemoveMenuItem(string item);
*****
Example: RemoveMenuItem(\"coffee\");
Removes the named item from the menu.
*****
mapping GetMenuItems();
*****
Returns all the menu items for this barkeep. Useful in building your
menu list.
*****
string SetLocalCurrency(string curr);
*****
Example: SetLocalCurrency(\"khucha\");
Sets the currency in which the barkeep does business.
",({"chapter 9","chapter nine","9",}):"chapter 9 \"Introduction to Intermediate LPC\"
Intermediate LPC
Descartes of Borg
Novermber 1993
Chapter 1: Introduction
1.1 LPC Basics
Anyone reading this textbook should either have read the textbook LPC
Basics or be familiar enough with mud realm coding such that not only are
they capable of building rooms and other such objects involved in area
coding, but they also have a good idea of what is going on when the code
they write is executing. If you do not feel you are at this point, then go
back and read LPC Basics before continuing. If you do so, you will find
that what you read here will be much more meaningful to you.
1.2 Goals of This Textbook
The introductory textbook was meant to take people new to LPC from
knowing nothing to being able to code a nice realm on any LPMud. There
is naturally much more to LPC and to LPMud building, however, than
building rooms, armours, monsters, and weapons. As you get into more
complicated concepts like guilds, or desire to do more involved things with
your realm, you will find the concepts detailed in LPC Basics to be lacking
in support for these projects. Intermediate LPC is designed to take you
beyond the simple realm building process into a full knowledge of LPC for
functioning as a realm builder on an LPMud. The task of mudlib building
itself is left to a later text. After reading this textbook and working through
it by experimenting with actual code, the reader should be able to code game
objects to fit any design or idea they have in mind, so long as I have been
successful.
1.3 An Overview
What more is there? Well many of you are quite aware that LPC supports
mappings and arrays and have been asking me why those were not detailed
in LPC Basics. I felt that those concepts were beyond the scope of what I
was trying to do with that textbook and were more fitting to this textbook.
But new tools are all fine and dandy, what matters, however, is what you
can do with those tools. The goal of LPC Basics was to get you to building
quality LPMud realms. Mappings and arrays are not necessary to do that.
The goal of this book is to allow you to code any idea you might want to
code in your area. That ability requires the knowledge of mappings and
arrays.
Any idea you want to code in an LPMud is possible. LPC is a language
which is amazingly well suited to this task. All that prevents you from
coding your ideas is your knowledge of LPC or an inadequate mudlib or
your mud<75>s theme or administrative policies. This textbook cannot make
the mudlib you are working with any better, and it cannot change the mud
theme or the mud<75>s administrative policies. Never once think that LPC is
incapable of doing what you want to do. If your idea is prevented by
administrative policies or themes, then it is simply not an idea for your
current mud. If the mudlib is inadequate, talk to the people in charge of
your mudlib about what can be done at the mudlib level to facilitate it. You
would be surprised by what is actually in the mudlib you did not know
about. More important, after reading this textbook, you should be able to
read all of the mudlib code in your mud<75>s mudlib and understand what is
going on at each line in the mudlib code. You may not as yet be able to
reproduce that code on your own, but at least you can understand what is
going on at the mudlib level.
This textbook starts out with a discussion about what the LPMud driver is
doing. One nice thing about this textbook, in general it is completely driver
and mudlib independent (excepting for the Dworkin Game Driver). The
chapter on the game driver does not get into actual implementation, but
instead deals with what all game drivers basically do in order to run the
mud.
Next I discuss those magic topics everyone wants to know more about,
arrays and mappings. Mappings may be simultaneously the easiest and
most difficult data type to understand. Since they are sort of complex arrays
in a loose sense, you really need to understand arrays before discussing
them. All the same, once you understand them, they are much easier than
arrays to use in real situations. At any rate, spend most of your time
working with that chapter, because it is probably the most difficult, yet most
useful chapter in the book.
After that follows a brief chapter on the LPC pre-compiler, a tool you can
use for sorting out how your code will look before it gets sent to the
compiler. Despite my horrid intro to it here, this chapter is perhaps the
easiest chapter in the textbook. I put it after the mappings and arrays
chapter for exactly that reason.
Strings are re-introduced next, going into more detail with how you can do
such things as advanced command handling by breaking up strings. Once
you understand arrays fairly well, this chapter should be really simple.
The next chapter is the second most important in the book. It may be the
most important if you ever intend to go beyond the intermediate stage and
dive into mudlib coding. That chapter involves the complex ideas behind
LPC inheritance. Since the goal of this textbook is not to teach mudlib
programming, the chapter is not a detailed discussion on object oriented
programming. Understanding this chapter, however, will give you some
good insights into what is involved with object oriented programming, as
well as allow you to build more complex objects by overriding functions
and defining your own base classes.
Finally, the textbook ends with a simple discussion of code debugging.
This is not an essential chapter, but instead it is meant as more of an
auxiliary supplement to what the knowledge you have accumulated so far.
1.4 Not Appearing in This Textbook
Perhaps what might appear to some as the most glaring omission of this
textbook is largely a political omission, shadows. Never have I ever
encountered an example of where a shadow was either the best or most
effecient manner of doing anything. It does not follow from that, however,
that there are no uses for shadows. My reasoning for omitting shadows
from this textbook is that the learner is best served by learning the concepts
in this textbook first and having spent time with them before dealing with
the subject of shadows. In that way, I feel the person learning LPC will be
better capable of judging the merits of using a shadow down the road. I
will discuss shadows in a future textbook.
If you are someone who uses shadows some or a lot, please do not take the
above paragraph as a personal attack. There may be some perfectly valid
uses for shadows somewhere which I have yet to encounter. Nevertheless,
they are not the ideal way to accomplish any given task, and therefore they
are not considered for the purposes of this textbook an intermediate coding
tool.
I have also omitted discussions of security and object oriented
programming. Both are quite obviously mudlib issues. Many people,
however, might take exception with my leaving out a discussion of object
oriented programming. I chose to leave that for a later text, since most area
builders code for the creativity, not for the computer science theory. In both
the intermediate and beginner textbooks, I have chosen only to discuss
theory where it is directly applicable to practical LPC programming. For
people who are starting out green in LPC and want to code the next great
mudlib, perhaps theory would be more useful. But for the purposes of this
book, a discussion of object oriented programming is simply a snoozer. I
do plan to get heavy into theory with the next textbook.
1.5 Summary
LPC is not difficult to learn. It is a language which, although pathetic
compared to any other language for performing most computer language
tasks, is incredibly powerful and unequalled for the tasks of building an
area in MUD type games. For the beginner, it allows you to easily jump in
and code useful objects without even knowing what you are doing. For the
intermediate person, it allows you to turn any idea you have into textual
virtual reality. And for the advanced person, it<69>s object oriented features
can allow you to build one of the most popular games on the internet. What
you can do is simply limited by how much you know. And learning more
does not require a computer science degree.
Copyright (c) George Reese 1993
",]),"/doc/manual/chapter40":1164672846,"/doc/manual/chapter29":1164672846,"/doc/manual/chapter28":1164672846,"/doc/manual/chapter27":1164672846,"/doc/manual/chapter26":1164672846,"/doc/manual/chapter25":1164672846,"/doc/manual/chapter24":1164672846,"/doc/manual/chapter23":1164672846,"/doc/manual/chapter22":1164672846,"/doc/manual/chapter21":1164672846,"/doc/manual/chapter20":1164672846,"items":([({"chapter 1","chapter one","1",}):"\"Introduction to the Coding Environment\"
",({"chapter 11","chapter eleven","11",}):"\"Complex Data Types\"
",({"chapter 21","chapter twenty-one","21",}):"\"Meals\"
",({"chapter 27","chapter twenty-seven","27",}):"\"Towns\"
",({"chapter 7","chapter seven","7",}):"\"Flow Control\"
",({"chapter 37","chapter thirty-seven","37",}):"\"QCS: Modifying things and stuff\"
",({"chapter 10","chapter ten","10",}):"\"The LPMud Driver\"
",({"chapter 36","chapter thirty-six","36",}):"\"QCS: Modifying weapons\"
",({"chapter 26","chapter twenty-six","26",}):"\"Sentients\"
",({"chapter 30","chapter thirty","30",}):"\"The Natural Language Parser\"
",({"chapter 34","chapter thirty-four","34",}):"\"QCS: Modification of NPC's\"
",({"chapter 37","chapter thirty-seven","37",}):"\"QCS: Modifying things and stuff\"
",({"chapter 10","chapter ten","10",}):"\"The LPMud Driver\"
",({"chapter 29","chapter twenty-nine","29",}):"\"Weapons\"
",({"chapter 40","chapter forty","40",}):"\"Useful Creator Commands\"
",({"chapter 39","chapter thirty-nine","39",}):"\"QCS: Final notes\"
",({"chapter 18","chapter eighteen","18",}):"\"Valid climates\"
",({"chapter 8","chapter eight","8",}):"\"LPC Basics\"
",({"chapter 2","chapter two","2",}):"\"The LPC Program\"
",({"chapter 17","chapter seventeen","17",}):"\"Barkeeps\"
",({"chapter 4","chapter four","4",}):"\"Functions\"
",({"chapter 17","chapter seventeen","17",}):"\"Barkeeps\"
",({"chapter 4","chapter four","4",}):"\"Functions\"
",({"chapter 26","chapter twenty-six","26",}):"\"Sentients\"
",({"chapter 24","chapter twenty-four","24",}):"\"Quests\"
",({"chapter 23","chapter twenty-three","23",}):"\"Properties\"
",({"chapter 8","chapter eight","8",}):"\"LPC Basics\"
",({"chapter 18","chapter eighteen","18",}):"\"Valid climates\"
",({"chapter 2","chapter two","2",}):"\"The LPC Program\"
",({"chapter 28","chapter twenty-eight","28",}):"\"Vendors\"
",({"chapter 16","chapter sixteen","16",}):"\"Armor\"
",({"chapter 33","chapter thirty-three","33",}):"\"QCS: Creation\"
",({"chapter 18","chapter eighteen","18",}):"\"Valid climates\"
",({"chapter 5","chapter five","5",}):"\"The Basics of Inheritance\"
",({"chapter 23","chapter twenty-three","23",}):"\"Properties\"
",({"chapter 31","chapter thirty-one","31",}):"\"Overview of the Quick Creation System\"
",({"chapter 27","chapter twenty-seven","27",}):"\"Towns\"
",({"chapter 3","chapter three","3",}):"\"LPC Data Types\"
",({"chapter 28","chapter twenty-eight","28",}):"\"Vendors\"
",({"chapter 26","chapter twenty-six","26",}):"\"Sentients\"
",({"chapter 17","chapter seventeen","17",}):"\"Barkeeps\"
",({"chapter 14","chapter fourteen","14",}):"\"Intermediate Inheritance\"
",({"chapter 22","chapter twenty-two","22",}):"\"NPCs\"
",({"chapter 38","chapter thirty-eight","38",}):"\"QCS: Adding and deleting\"
",({"chapter 19","chapter nineteen","19",}):"\"Doors\"
",({"chapter 32","chapter thirty-two","32",}):"\"QCS: Commands\"
",({"chapter 14","chapter fourteen","14",}):"\"Intermediate Inheritance\"
",({"chapter 7","chapter seven","7",}):"\"Flow Control\"
",({"chapter 20","chapter twenty","20",}):"\"Items\"
",({"chapter 10","chapter ten","10",}):"\"The LPMud Driver\"
",({"chapter 1","chapter one","1",}):"\"Introduction to the Coding Environment\"
",({"chapter 7","chapter seven","7",}):"\"Flow Control\"
",({"chapter 20","chapter twenty","20",}):"\"Items\"
",({"chapter 32","chapter thirty-two","32",}):"\"QCS: Commands\"
",({"chapter 6","chapter six","6",}):"\"Variable Handling\"
",({"chapter 22","chapter twenty-two","22",}):"\"NPCs\"
",({"chapter 15","chapter fifteen","15",}):"\"Debugging\"
",({"chapter 31","chapter thirty-one","31",}):"\"Overview of the Quick Creation System\"
",({"chapter 13","chapter thirteen","13",}):"\"Advanced String Handling\"
",({"chapter 15","chapter fifteen","15",}):"\"Debugging\"
",({"chapter 30","chapter thirty","30",}):"\"The Natural Language Parser\"
",({"chapter 29","chapter twenty-nine","29",}):"\"Weapons\"
",({"chapter 22","chapter twenty-two","22",}):"\"NPCs\"
",({"chapter 16","chapter sixteen","16",}):"\"Armor\"
",({"chapter 39","chapter thirty-nine","39",}):"\"QCS: Final notes\"
",({"chapter 13","chapter thirteen","13",}):"\"Advanced String Handling\"
",({"chapter 33","chapter thirty-three","33",}):"\"QCS: Creation\"
",({"chapter 24","chapter twenty-four","24",}):"\"Quests\"
",({"chapter 40","chapter forty","40",}):"\"Useful Creator Commands\"
",({"chapter 13","chapter thirteen","13",}):"\"Advanced String Handling\"
",({"chapter 19","chapter nineteen","19",}):"\"Doors\"
",({"chapter 37","chapter thirty-seven","37",}):"\"QCS: Modifying things and stuff\"
",({"chapter 12","chapter twelve","12",}):"\"The LPC Pre-Compiler\"
",({"chapter 25","chapter twenty-five","25",}):"\"Rooms\"
",({"chapter 34","chapter thirty-four","34",}):"\"QCS: Modification of NPC's\"
",({"chapter 40","chapter forty","40",}):"\"Useful Creator Commands\"
",({"chapter 6","chapter six","6",}):"\"Variable Handling\"
",({"chapter 8","chapter eight","8",}):"\"LPC Basics\"
",({"chapter 11","chapter eleven","11",}):"\"Complex Data Types\"
",({"chapter 33","chapter thirty-three","33",}):"\"QCS: Creation\"
",({"chapter 38","chapter thirty-eight","38",}):"\"QCS: Adding and deleting\"
",({"chapter 35","chapter thirty-five","35",}):"\"QCS: Modifying rooms\"
",({"chapter 35","chapter thirty-five","35",}):"\"QCS: Modifying rooms\"
",({"chapter 25","chapter twenty-five","25",}):"\"Rooms\"
",({"chapter 6","chapter six","6",}):"\"Variable Handling\"
",({"chapter 30","chapter thirty","30",}):"\"The Natural Language Parser\"
",({"chapter 5","chapter five","5",}):"\"The Basics of Inheritance\"
",({"chapter 19","chapter nineteen","19",}):"\"Doors\"
",({"chapter 15","chapter fifteen","15",}):"\"Debugging\"
",({"chapter 12","chapter twelve","12",}):"\"The LPC Pre-Compiler\"
",({"chapter 2","chapter two","2",}):"\"The LPC Program\"
",({"chapter 25","chapter twenty-five","25",}):"\"Rooms\"
",({"chapter 32","chapter thirty-two","32",}):"\"QCS: Commands\"
",({"chapter 9","chapter nine","9",}):"\"Introduction to Intermediate LPC\"
",({"chapter 36","chapter thirty-six","36",}):"\"QCS: Modifying weapons\"
",({"chapter 14","chapter fourteen","14",}):"\"Intermediate Inheritance\"
",({"chapter 23","chapter twenty-three","23",}):"\"Properties\"
",({"chapter 3","chapter three","3",}):"\"LPC Data Types\"
",({"chapter 36","chapter thirty-six","36",}):"\"QCS: Modifying weapons\"
",({"chapter 11","chapter eleven","11",}):"\"Complex Data Types\"
",({"chapter 31","chapter thirty-one","31",}):"\"Overview of the Quick Creation System\"
",({"chapter 39","chapter thirty-nine","39",}):"\"QCS: Final notes\"
",({"chapter 4","chapter four","4",}):"\"Functions\"
",({"chapter 21","chapter twenty-one","21",}):"\"Meals\"
",({"chapter 1","chapter one","1",}):"\"Introduction to the Coding Environment\"
",({"chapter 34","chapter thirty-four","34",}):"\"QCS: Modification of NPC's\"
",({"chapter 9","chapter nine","9",}):"\"Introduction to Intermediate LPC\"
",({"chapter 29","chapter twenty-nine","29",}):"\"Weapons\"
",({"chapter 3","chapter three","3",}):"\"LPC Data Types\"
",({"chapter 28","chapter twenty-eight","28",}):"\"Vendors\"
",({"chapter 9","chapter nine","9",}):"\"Introduction to Intermediate LPC\"
",({"chapter 12","chapter twelve","12",}):"\"The LPC Pre-Compiler\"
",({"chapter 27","chapter twenty-seven","27",}):"\"Towns\"
",({"chapter 20","chapter twenty","20",}):"\"Items\"
",({"chapter 35","chapter thirty-five","35",}):"\"QCS: Modifying rooms\"
",({"chapter 21","chapter twenty-one","21",}):"\"Meals\"
",({"chapter 5","chapter five","5",}):"\"The Basics of Inheritance\"
",({"chapter 16","chapter sixteen","16",}):"\"Armor\"
",({"chapter 24","chapter twenty-four","24",}):"\"Quests\"
",({"chapter 38","chapter thirty-eight","38",}):"\"QCS: Adding and deleting\"
",]),"/doc/manual/chapter09":1164672846,"/doc/manual/chapter08":1204520744,"/doc/manual/chapter07":1164672846,"/doc/manual/chapter06":1164672846,"/doc/manual/chapter05":1164672846,"/doc/manual/chapter04":1164672846,"/doc/manual/chapter03":1164672846,"/doc/manual/chapter02":1164672846,"/doc/manual/chapter01":1164672846,]),"/doc/guide":(["object":"/domains/default/obj/guide","title":"Administrator's Guidebook","items":([({"chapter 8","chapter eight","8",}):"\"Understanding the Lib\"
",({"chapter 4","chapter four","4",}):"\"Gaining Experience\"
",({"chapter 3","chapter three","3",}):"\"Getting Started\"
",({"chapter 5","chapter five","5",}):"\"Guilds, Classes, and Clans\"
",({"chapter 5","chapter five","5",}):"\"Guilds, Classes, and Clans\"
",({"chapter 2","chapter two","2",}):"\"The Vision Thing\"
",({"chapter 2","chapter two","2",}):"\"The Vision Thing\"
",({"chapter 3","chapter three","3",}):"\"Getting Started\"
",({"chapter 6","chapter six","6",}):"\"Privacy\"
",({"chapter 7","chapter seven","7",}):"\"Advanced Topics\"
",({"chapter 1","chapter one","1",}):"\"Introduction\"
",({"chapter 9","chapter nine","9",}):"\"The Future\"
",({"chapter 6","chapter six","6",}):"\"Privacy\"
",({"chapter 8","chapter eight","8",}):"\"Understanding the Lib\"
",({"chapter 4","chapter four","4",}):"\"Gaining Experience\"
",({"chapter 7","chapter seven","7",}):"\"Advanced Topics\"
",({"chapter 9","chapter nine","9",}):"\"The Future\"
",({"chapter 5","chapter five","5",}):"\"Guilds, Classes, and Clans\"
",({"chapter 7","chapter seven","7",}):"\"Advanced Topics\"
",({"chapter 1","chapter one","1",}):"\"Introduction\"
",({"chapter 6","chapter six","6",}):"\"Privacy\"
",({"chapter 1","chapter one","1",}):"\"Introduction\"
",({"chapter 3","chapter three","3",}):"\"Getting Started\"
",({"chapter 4","chapter four","4",}):"\"Gaining Experience\"
",({"chapter 2","chapter two","2",}):"\"The Vision Thing\"
",({"chapter 9","chapter nine","9",}):"\"The Future\"
",({"chapter 8","chapter eight","8",}):"\"Understanding the Lib\"
",]),"/doc/guide/chapter09":1164672846,"/doc/guide/chapter08":1177376334,"/doc/guide/chapter07":1164672846,"/doc/guide/chapter06":1164672846,"/doc/guide/chapter05":1164672846,"/doc/guide/chapter04":1164672846,"index":" Administrator's Guidebook
Chapter 1: \"Introduction\"
Chapter 2: \"The Vision Thing\"
Chapter 3: \"Getting Started\"
Chapter 4: \"Gaining Experience\"
Chapter 5: \"Guilds, Classes, and Clans\"
Chapter 6: \"Privacy\"
Chapter 7: \"Advanced Topics\"
Chapter 8: \"Understanding the Lib\"
Chapter 9: \"The Future\"
","/doc/guide/chapter03":1164672846,"reads":([({"chapter 2","chapter two","2",}):"chapter 2 \"The Vision Thing\"
Are you sure you want to run a mud? I mean, are
you *really* sure?
Most newbie admins have no idea what a difficult
task lays before them. I started my own mud in 1995.
It's still around today, in fact. Back then, I'd
been coding on a mud that had its hosting pulled. I
finagled access to my university's systems and
told the old mud's admin \"Hey, let's host it here!\"
He didn't want to, so it was just me and my
new Nightmare IV mud. I figured \"what the heck, maybe
I can run my own,\" and the rest is history.
I hadn't a clue how to manage people, and things
just wouldn't come together. I had literally dozens of
creators come and go, and I could never figure
out why they'd build a few things and leave. The
problem was me, obviously. There was nothing about
the mud people disliked: Nightmare was a very popular
lib at the time.
The problem was that people wanted leadership
from me, and I didn't even know it, much less know
how to provide it.
Creators (\"builders\") are your most precious resource.
Without them you don't have a mud, you have a lib. Sure,
you can try building everything yourself...and with
Dead Souls, that's not so farfetched an idea. But
after a few months of toil, you'll see that you have
weaknesses, you are not the perfect builder, and you
will wish for the help and support of others.
If you don't carefully cultivate your relationships
with these people, you will fail. Your mud will be a
failed mud, and your self-expression squelched.
This is why I ask you if you're *really* sure you
want to run a mud. Running a mud isn't about lording
it over puny mortal players. It isn't about being
the sharpest lib coder logged in. It isn't about
bossing your staff, or making long lists of rules
and job titles and meeting schedules.
Your job as an administrator is to manage
people, and guide them toward a single vision, over
which you yourself may not have full control. People
will listen to the admin at first because, well, she's
the admin. But if you can't demonstrate the qualities
of leadership they expect, they will stop respecting
you, and they will leave. Or worse, they will hang
around and be difficult.
What's this about a \"vision\"? People will work for
a variety of reasons, mostly money, fun, recognition,
etc. Rewards. When your new coders show up, they
will need motivation to work. Since you probably
won't be offering much in the way of money or
recognition, you'll need to find a way to motivate
your coders by making it fun to work with you.
Obviously I don't mean you need to be jolly and
wear funny hats. In fact, you can be quite boring a
person and still be good to work with. When I mean it
has to be fun for your creators, I mean that they
have to be inspired to do stuff...they have to *want*
to build because they are expressing themselves in
a way they enjoy.
This means you'd be unwise to start parceling out
\"missions\" and \"assignments\". Find out what your new
creator *wants* to do, then do your best to
accommodate them. It's that simple. If they're working
on what they *want* to do, you don't need to actively
motivate them...you just need to make sure they
have what they need, and that they understand what
is expected of them.
These expectations are the other part of the
individual management of creators. Just as is it
fatal to give creators \"homework\", it is just as
counterproductive to say \"do whatever you want, man,
you're free to do anything.\" Part of the fun of
work is knowing what the standards are, and how your
work will be judged. If your creator feels like you
don't actually care what she builds, she won't
care much about doing a job that's up to any standards
but her own. After a while of this, she's going to
figure out she might as well just run her *own* mud.
You therefore have to have a strong sense of what
your mud will look like, and what each creator's role
in that mud will be. If you don't know, or it seems like
you don't know, you'll lose them.
You don't run the mud because you have the admin
password. You run it because people think you run it.
If they stop thinking it, you stop running it.
So I ask again. Do you know what you want out of
this mud? Have you planned out what you want people
to be doing? When a talented coder shows up, will
you be prepared to negotiate their proper role,
and persuade them that the mud will succeed?
Do you *really* want to be a mud admin? Or are
you just looking to be someone's boss?
First, find your vision. Everything else will be
hard work, but if you know what your mud will be,
and what you need from other people, then you
just might have a chance to succeed.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction\"
If you're reading this, you've probably been
successful in installing Dead Souls, and are able
to get around and manipulate stuff within it.
There are lots of questions that new admins
have at this point. This book is not intended to
answer them. Specific questions about administration
are handled in the Admin FAQ, which you can read
at http://dead-souls.net/ds-admin-faq.html . A
local copy is archived in the /www directory, but
this may be out of date by now, and you'd be best
served by looking at the online version.
This guidebook isn't about \"how to\" or \"where
is,\" although such issues may be handled incidentally.
The point of this guidebook is to describe to you
various principles that are important to your
success as a Dead Souls mud admin.
The tone of these chapters is intended to be
conversational. Because of this, it may sound like
I'm being condescending (that means \"talking down
to you\"), but that isn't intentional. I'm
assuming that you fully understand I'm not the boss
of you, that you can decide for yourself what is
best for your mud, and that the contents of this
guidebook are thoroughly biased.
However rambling these pages might be, know that
I claim no authority over What Should Be. I can only
tell you, from my experience, how things look, and
how I *think* things work best.
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Advanced Topics\"
Section I: Shadows
------------------
Shadows are a problematic topic. My first impulse is to warn
you sternly not to mess with them, they shouldn't be used, etc. In
his legendary LPC text, Descartes goes so far as to say he hasn't
seen a shadow do something that couldn't be done better another way.
So, be aware that the topic of shadows tends to generate
strong opinions.
Shadows *are* hazardous pretty much by definition. A shadow
is an object that effectively \"wraps\" another object, like an
invisible shadow around it. When a call is made to the shadowed
object, the shadow intercepts that call.
What the shadow does with the call is up to you. It can
do nothing at all, and simply pass the call along to the
shadowed object. It could block that call. It could manipulate
that call and send the modified version to the shadowed
object.
You can see an example of a shadow in /secure/npc/drone.c .
This is the code that enables you to take control of an npc
with the remote control. The remote control loads /shadows/drone.c
which inherits /secure/npc/drone.c , and attaches that shadow to
the npc that is to be controlled. This is a way of \"adding
functions on the fly\" to an already-loaded npc.
The drone shadow is in the lib as an example
of how shadows work, and an example of how to get an object to
posess functions it did not have when loaded. It is not
intended to represent shadow advocacy.
The danger, and the reason some people go ballistic
when they hear the phrase \"I think I'll use a shadow for that\"
is that a lib that allows unrestricted shadow use
effectively has no security at all. You can have creators
enshadow an arch for example, or enshadow other privileged
objects.
Dead Souls shadows are pretty tightly restricted. The
master object does not permit a shadow to be created unless its
code is in /shadows . This means creators can't hide their
rootkit shadows in their homedirs and expect them to work.
Further, because /shadows is outside the /secure dir,
it serves as an obstacle to defeating the stack security
model.
In any case, I strongly recommend you avoid using them
except in the extremely unusual case of a task that has
no other solution. If your mud starts collecting a bunch
of shadows out of laziness, sadness will likely be the result.
Section II: The \"class\" Data Type
---------------------------------
In 1995 Beek added a data type to MudOS: class.
I have to admit that I'm at a bit of a loss to explain classes,
because I am not a C guy, I'm an LPC guy. For people who
grok C, the class data type is a natural and elegant solution
for organizing related sets of variables.
I have nothing but respect for Beek and the leet programmers
who built MudOS, so please don't go running around saying I'm
dissing them.
But in my experience, the use of classes generally serves
the purpose of obscuring code and making it more difficult
to debug.
I've seen newbie LPC coders take to classes like fish to water.
I can't explain it other than to speculate that for some people
the class data type (I've even had people argue at me that it's
really a \"data structure\", like I have the slightest clue what
the difference is) Just Makes Sense. If you are one of those people,
then bully for you. I'm serious. I don't understand you, but
your thing probably works for you, so, right on.
However, I do not recommend that the average newbie coder
spend too much time on classes. You'll have plenty of opportunity
when you start dissecting the lib, but my friendly advice to
the noob is to use mappings instead. I've yet to see a class
do something that a mapping couldn't do with greater clarity.
Section III: add_action
-----------------------
Yet another topic with potential for violence. The passions
can run high on this one, and in fact, the Lima team felt
strongly enough about it that they don't enable it by default.
No add_actions by default. That's hard core.
add_action is an efun that LP muds originally depended
on for most kinds of user input. If you wanted to be able to
throw a ball, that ball needed an add_action for it. You'd
have that ball provide a user with the throw command whenever
the user came in proximity to the ball. You can see the syntax
for add_action by examining the /lib/shop.c file.
It's simple. It works. I mean, really, It Just Works, and
it's the fastest way to add functionality to any object. People
used add_action from pretty much the genesis of LP, as far as
I can tell, and it became just a way of life. That was just
how it was done. Yes, that wording was intentional ;)
However, there were serious problems with this parsing
scheme. Basically it let any creator, regardless of skill
level, define commands for the mud. Joe's throw add_action for
his ball could be poorly coded and worded, and was liable to
behave differently from Jon's version.
And suppose you held two or three items that had a throw
add_action each? Which one took precedence? What if that
one was bugged?
Using add_actions for general lib command functionality
is really problematic for this reason. It fosters a lack
of uniformity across the lib that can leave users basically
playing a \"guess the syntax\" game for any item like this,
and allows for conflicts by incorrectly coded items. The
natural language parser was the MudOS solution to this problem,
implementing the verb system with which you are now so
familiar.
The controversy boils down to preference. For some people,
add_action is just How It Is Done, and you still have
people starting muds with libs like Skylib and TMI-2 (!!)
that lack an advanced lib-wide parser. For these people,
verbs are an overcomplicated mess that they just don't
have a need to understand.
Then you have the people who find add_action anathema, and
simply an unacceptable vestige of a more primitive form of
mudding. These people view add_actioners as atavistic knuckle-
draggers, either too dumb or too pig-headed to understand the
beauty and majesty of natural language parsing.
My view, predictably, is somewhere in the middle. Verbs
*can* be exhausting to accommodate when you're just Trying To
Do One Thing. But add_actions truly are of limited use in
a modern lib, and allowing their proliferation out of
laziness is probably a bad idea.
That isn't to say there isn't a place for add_actions. Dead
Souls supports them, and you'll see them every now and then.
In fact, technically, every command you issue, verb
or not, is evaluated by an add_action in the mud shell object
(LIB_NMSH). It is nevertheless better to learn to use verbs, because
they eliminate many problems you don't need to reinvent the
wheel for.
I had one person tell me, as they chose a MudOS lib that
didn't use verbs, that they planned to write their own natural
language parser in the lib. I bade him good luck. I wonder
how he's coming along these days.
Section IV: Thoughts on MudOS
-----------------------------
Like, wow. MudOS. You know?
",({"chapter 2","chapter two","2",}):"chapter 2 \"The Vision Thing\"
Are you sure you want to run a mud? I mean, are
you *really* sure?
Most newbie admins have no idea what a difficult
task lays before them. I started my own mud in 1995.
It's still around today, in fact. Back then, I'd
been coding on a mud that had its hosting pulled. I
finagled access to my university's systems and
told the old mud's admin \"Hey, let's host it here!\"
He didn't want to, so it was just me and my
new Nightmare IV mud. I figured \"what the heck, maybe
I can run my own,\" and the rest is history.
I hadn't a clue how to manage people, and things
just wouldn't come together. I had literally dozens of
creators come and go, and I could never figure
out why they'd build a few things and leave. The
problem was me, obviously. There was nothing about
the mud people disliked: Nightmare was a very popular
lib at the time.
The problem was that people wanted leadership
from me, and I didn't even know it, much less know
how to provide it.
Creators (\"builders\") are your most precious resource.
Without them you don't have a mud, you have a lib. Sure,
you can try building everything yourself...and with
Dead Souls, that's not so farfetched an idea. But
after a few months of toil, you'll see that you have
weaknesses, you are not the perfect builder, and you
will wish for the help and support of others.
If you don't carefully cultivate your relationships
with these people, you will fail. Your mud will be a
failed mud, and your self-expression squelched.
This is why I ask you if you're *really* sure you
want to run a mud. Running a mud isn't about lording
it over puny mortal players. It isn't about being
the sharpest lib coder logged in. It isn't about
bossing your staff, or making long lists of rules
and job titles and meeting schedules.
Your job as an administrator is to manage
people, and guide them toward a single vision, over
which you yourself may not have full control. People
will listen to the admin at first because, well, she's
the admin. But if you can't demonstrate the qualities
of leadership they expect, they will stop respecting
you, and they will leave. Or worse, they will hang
around and be difficult.
What's this about a \"vision\"? People will work for
a variety of reasons, mostly money, fun, recognition,
etc. Rewards. When your new coders show up, they
will need motivation to work. Since you probably
won't be offering much in the way of money or
recognition, you'll need to find a way to motivate
your coders by making it fun to work with you.
Obviously I don't mean you need to be jolly and
wear funny hats. In fact, you can be quite boring a
person and still be good to work with. When I mean it
has to be fun for your creators, I mean that they
have to be inspired to do stuff...they have to *want*
to build because they are expressing themselves in
a way they enjoy.
This means you'd be unwise to start parceling out
\"missions\" and \"assignments\". Find out what your new
creator *wants* to do, then do your best to
accommodate them. It's that simple. If they're working
on what they *want* to do, you don't need to actively
motivate them...you just need to make sure they
have what they need, and that they understand what
is expected of them.
These expectations are the other part of the
individual management of creators. Just as is it
fatal to give creators \"homework\", it is just as
counterproductive to say \"do whatever you want, man,
you're free to do anything.\" Part of the fun of
work is knowing what the standards are, and how your
work will be judged. If your creator feels like you
don't actually care what she builds, she won't
care much about doing a job that's up to any standards
but her own. After a while of this, she's going to
figure out she might as well just run her *own* mud.
You therefore have to have a strong sense of what
your mud will look like, and what each creator's role
in that mud will be. If you don't know, or it seems like
you don't know, you'll lose them.
You don't run the mud because you have the admin
password. You run it because people think you run it.
If they stop thinking it, you stop running it.
So I ask again. Do you know what you want out of
this mud? Have you planned out what you want people
to be doing? When a talented coder shows up, will
you be prepared to negotiate their proper role,
and persuade them that the mud will succeed?
Do you *really* want to be a mud admin? Or are
you just looking to be someone's boss?
First, find your vision. Everything else will be
hard work, but if you know what your mud will be,
and what you need from other people, then you
just might have a chance to succeed.
",({"chapter 3","chapter three","3",}):"chapter 3 \"Getting Started\"
If you've read this far, there's a good chance
you already like the lib and are eager to get
going on this whole mud thing. Whether this is
true or not, this is where you find out for sure.
Let's assume you're set with the vision thing.
You're going to make a mud with a Dora the Explorer
theme. You can practically hear the music even
now! Let's get this mud written and open! Vamonos!
The first thing that happens here is you need
to get real familiar with this mud. If you haven't
done so already, when you finish this chapter,
you must do the following:
1) Read the Player's Handbook, cover to cover.
2) Create a test character. Do *not* use your
admin character's powers to give her any
equipment or money. Using the information from the
Player's Handbook, play this character until she
solves the Orcslayer Quest. Do not use any creator
characters to help her in any way.
It is very important that you do this. It
is the best way you will know for sure whether you
have made the right lib choice.
This doesn't mean that I'm asking you if you're
happy with the questing system, or how the items
in your inventory get listed, or whatever. Such
systems and cosmetics can be changed, sometimes with
trivial work.
What you're testing is the feel of the mud,
the parser, and that certain \"I don't know what\"
that tells you whether this is really a mud lib
you can live with.
Don't like quest advancement? That can be removed.
Want turns-based combat? That can be arranged (though
not trivially). But if you get a low level discomfort,
and can't shake the feeling that you can't get
anything done, then this is when you'll find out.
The second advantage to completing the Orcslayer
Quest is that it helps you see how things are
organized. As you proceed along the quest, you should
be using your admin character to examine the
files that compose the rooms, items, and npc's you see.
You will know where there are working samples of
doors, locks, hostile npc's, friendly npc's, spells,
and so on. This information will be valuable to you
in the next step you take.
If you complete the Orcslayer Quest and decide
you still like the lib, your next step is to create a
small area. Read the Creator's Manual starting from
chapter 31. This provides you a detailed set of
instructions on how to get stuff built quickly and
painlessly.
Build a few rooms. If you can't think of something
to build, create a room just like the one you're
sitting in right now, including desk, door, and
scruffy mud geek sitting on a chair.
Or you might already know exactly what you want
to build. This is a good time to build a few
rooms of Dora's Grandma's House, or the tree house
of Tico the Squirrel.
It's vitally important that you start using the
building tools, because just like with the Orcslayer
Quest, this is the point where you will discover whether
the build system here is a deal killer.
Once you're done with your few starter rooms, you'll
be in a position to know whether you've really made
the right mudlib choice.
",({"chapter 4","chapter four","4",}):"chapter 4 \"Gaining Experience\"
Now you must prepare for the
technical demands of your creators.
Your creators will expect you to have
answers to their questions. For the most part,
they'll find these answers in the Creator's FAQ,
at http://dead-souls.net/ds-creator-faq.html .
But you're going to get questions not on
that FAQ. You'll get stuff like:
* How many quest points should the Issa Quest award?
* What's the maximum player level on this mud?
* Can I make a player class called \"Swiper\"?
* What's a good weapon type for The Bouncy Ball?
These are questions that depend strictly on you
and what your vision for the mud is. Dead Souls is
a starting point. The classes, levels, systems,
*everything*, is open to modification.
It is normal and natural not to have all of
these answers at first. I suggest you concentrate on
making an \"area\", perhaps a quest, consisting of
perhaps 15 to 20 rooms and a few npc's, weapons,
pieces of armor and general items.
Test your weapons and armor in the arena. To
get to the arena, first go to the Creators' Hall
by typing: wiz
Then go east, then north.
You can have the dummy wear armor like this:
give mithril shirt to dummy
force dummy to wear shirt
And you can test your weapons by beating on the
dummy:
force fighter to drop sword
clone hammer
give hammer to fighter
force fighter to wield hammer
force fighter to kill dummy
The dummy will very helpfully blurt out the amount
and type of damage it receives.
You can get ideas for your npc's by visiting the
menagerie. It is east and south of the Creators' Hall.
By creating your first quest, and putting test
characters through it, you will gain the experience
you need to be able to tell your players things like:
\"The Issa Quest is supposed to be easy, so make it 2qp.\"
\"Level 50 explorers are way too strong. I'm capping
player advancement at level 40.\"
\"No, there is already a thief class.\"
\"Make it a projectile weapon.\"
",({"chapter 8","chapter eight","8",}):"chapter 8 \"Understanding the Lib\"
One of the most common questions I get goes something
like this: \"I'd like to change combat so that it is
turns-based, with actions. How would I do this?\" Another
example might be \"I'm setting up a farming system, with
livestock and stuff. What should I look at?\"
To me, the questions are the same. Translated into
my language, this is the meaning: \"I have great ideas that
require advanced knowledge of the lib to implement. How do I do it?\"
I'm usually at a loss when I get one of these, because
I want to set people straight, but I don't want to hurt
their feelings, either. In the FAQ's, my response is
something along the lines of:
\"If there's anything in the Creator's Manual you don't
understand, you aren't ready to try this.\"
I hate to say that, because I think it's probably discouraging
to hear. After all, whatever the project, it is very likely
doable. You can make an LP mud do pretty much anything
you want...that's the beauty of the flexibility of LPC.
However, as they say, with great power comes great responsibility,
and in this case, it is your responsibility to understand the
lib, if you want to make major changes to it. Let's take the
example of farming.
Section I: Verbs
----------------
It is critical to understand how verbs work in order to
do anything in Dead Souls of an advanced nature. Verbs are
basically commands that do something to your environment,
something in your environment, your \"body\", or something
in your inventory.
For example, \"who\" is not a verb. It's a standard command,
which doesn't act on any cloned items. All it does is communicate
with the lib to query the list of users logged on, and displays
it to you in a particular manner.
Then there's something like \"zap\". That *is* a verb, and
it takes cloned items as arguments. When you \"zap orc\" this
has a special meaning to the parsing system. The parser is the
part of the game driver that tries to interpret your input
and attempts to do something useful with it. When the parser
catches a verb at the beginning of your input, it gets to
work on figuring out how the rest of the words in the input
relate to that verb.
This is done through \"rules\". You can take a look
at /verbs/creators/zap.c for the specific rules in this case.
If the word or words (for example \"first orc\", \"orcs\", \"an orc\")
match one or more objects in the room, the parser then
sends the arguments to the verb object. The verb object is
the loaded code from /verbs/creators/zap.c in this case.
Depending on how the verb is coded, your command line will
succeed or fail.
For your new farming system, you're going to need some new
verbs, so the first thing you need to do is understand verbs.
You're going to have to build new verbs like \"plow\", and \"plant\",
and \"harvest\". Therefore, you'll need to go over the verb
tutorial, which is at http://dead-souls.net/verbs.html
Section II: Lib Event Objects
-----------------------------
In the verb tutorial, you read that when a verb acts on
an object, the parser requires that the object have a function
that handles that verb. If a chair object lacks a function
like direct_sit() or something similar, the parser will assume
your sit verb doesn't apply to chairs, and the command line
will fail with something like \"You can't sit on the chair\".
It would be incredibly tedious to have to code a sit verb
handler in every piece of furniture you create. Similarly,
your farmer's field plow *could* have a plow verb handler
coded in it, but it is much better to create a lib object that
your plow will inherit. That way, other objects can inherit
that functionality without having to reinvent the wheel, and
plowing in general will be a uniform experience across the mud.
For example, one of the first systems I made when
I started my lib obsession was the inheritable flashlight
system. The original Dead Souls lib had regular old torches
you'd light with a match, but it seemed to me that not every
Dead Souls mud would be Sword & Sandals style, and a modern
illumination system should be available. So I set about
making a \"turn\" verb, so that once I had flashlights,
you could \"turn on the flashlight\".
I then created the lib object /lib/events/turn.c (when
referring to lib objects, I often use the macro name. In
this case, if I'd said LIB_TURN, it would be the same thing
as saying /lib/events/turn.c). The lib object doesn't really
*do* much of anything. That object isn't really where you
need to be checking for validity of commands. What that
object does, almost *all* it does, is to have functions that
correspond to the verb \"turn\". That's it. It's kind of like
a socket for a plug. The verb is the plug and you're trying
to use it on something. If that something has a socket
that fits your plug, then it'll work.
Lib event objects come in different flavors, and some
really do perform a bunch of thinking. But for the most part,
for simple verbs, all you need is a lib event object that
says \"yes, I understand that verb\".
LIB_TURN is inherited by LIB_FLASHLIGHT. That means
that when you clone an object that inherits LIB_FLASHLIGHT,
it contains all the functions of /lib/flashlight.c plus
all the functions that LIB_FLASHLIGHT inherits from LIB_TURN.
Because your flashlight inherits LIB_FLASHLIGHT,
which inherits LIB_TURN, when you issue the command line
\"turn on flashlight\", the parser checks with the
flashlight to see if it knows what you're talking about,
and gets a \"yes, I know that verb\" response. At that point
the parser says \"fine, here's the rest of what this
player thinks he can do with you and the turn verb\" and now
it's up to LIB_FLASHLIGHT to figure out whether it has
enough batteries, of the right kind, with sufficient
charge, and so on.
For your new farming system, you'll need to implement
a similar scheme. Your \"plow\" and \"hoe\" verbs will need
lib event objects that can be inherited by the cloned
objects you want to plow and hoe with.
In this case, LIB_FLASHLIGHT and the turn verb
aren't the best models for your new plowing system. This
is because your plow is something you plow *with*,
as opposed to something that *is plowed*.
To see how a plowing system might be implemented,
take a look at the \"dig\" verb, LIB_DIGGING, and
LIB_DIG_WITH. This is what a shovel would use, so
that you can \"dig in sand with the shovel\". After
studying the dig system, and lots of trial and error,
you will hopefully eventually come up with a
plow system that will let you \"plow field with plow\",
for example.
Section III: Daemons
--------------------
So, now you've created a plow verb, and a plow lib
event object, it works, and now you're happily plowing
along. Let's say that the rooms field1.c and field2.c
are plowable rooms. Presumably, you don't want people
to be able to plow here all the time. The fields need
time to do their thing, and constant plowing would
slow down the growth of your tender young corn stalks.
Normally, you might deal with this by having
a local variable in the room, so that \"harvest time is
50 hours, unless someone plows again, which
makes it take longer\", this sort of thing. Let's call
that variable PlowedTimes.
But, oh noes! The mud rebooted! Now all the rooms
have reset, and the planting and plowing variables
have reset!
You might avoid this problem by just not rebooting,
but even if you manage never ever to reboot your mud,
the mud periodically does resets of unused objects,
retiring them from memory and resetting their values
to zero.
You might avoid *that* problem by setting your
fields to be \"NoClean\", to avoid resets, but this is
very inelegant. Rather than ensuring the integrity
of your game data, you're just crossing your fingers
and hoping it doesn't go away.
The solution is to use a daemon. A daemon is an
object loaded into memory that acts like an arbiter
of information. For example, STARGATE_D keeps track
of where stargates are, and which gates are in
what state, and which gates are connected to each
other. It is important to have one location where
this data can be accessed, because a new gate must
be able to know what other valid gates there are, and
it must be able to know what gates are idle and
therefore accessible. STARGATE_D is a central
repository of this data, and serves as a mediator
for connection requests, keeping things working right.
In this case, the daemon's job would be to
keep track of which fields have been plowed, how
many times, and how long it'll take to get to harvest
time. Dead Souls daemons typically use object
persistence files ( http://dead-souls.net/ds-admin-faq.html#80 )
to avoid losing information during object reloads or
mud reboots. A FARMING_D is exactly what you need to
keep track of and manage this kind of data.
Section IV: Skills
------------------
To what extent should people be able to plow? How
well should they do it? If you care enough about farming
to have come this far, you've probably got ideas about
what good plowing is and what criteria a player should
have for extracting the most from their land.
This is where skills can play an important role.
What you have to understand about skills is that they
are simply variables in a player's body. Skills don't
have to be gained by joining a class, guild, or being
member of a race. Adding a skill to a player is as
simple as having an object do something like this:
this_player()->SetSkill(\"scuba diving\",1);
And if just strapping on a scuba tank does it, then
now that player has that skill.
Now, *normally* players are granted skills through
something more sensible than just picking up an object.
It makes more sense to have skills granted when a
player is taught something by an npc, or joins a guild,
or whatever, which is why traditionally that's how
it has worked.
So let's say you have a Farmer's Guild, then. When
you show up and sign the registry, some npc pops out,
\"teaches\" you the farming skills you need (by simply
adding the skills \"farming\" and \"plowing\" and \"sowing\"
to the player) and now you have the skills. If you
want, you can even create a Farmer class, like Fighters,
but that's up to you and not in the scope of this chapter.
This plowing skill is totally useless right
now. It does nothing at all, because you haven't yet
coded anything that makes use of it. This is the key
concept of the skills system that you must understand.
Just giving a player a skill does not mean that
it has any use. For a skill to be useful, there
must be lib verbs and/or objects that evaluate the
skill and perform calculations based on it.
It is therefore time to add these skill checks to the
objects that need them. For example, suppose our
farmer's plowing skill is at level 5. This doesn't
mean he's a level 5 player necessarily, just that
at plowing, his skill level is 5.
You might have a function in your /verbs/items/plow.c
verb that checks that skill, and determines how long
the field will take to grow based on it. Perhaps
for a level 5 plower, the field will be ready for
harvest in 45 hours. Perhaps for a level 10 plower,
it would be 35. You might have either the plow verb
or the plow lib event object do something like:
int PlowFunction(string field){
int skill_level = this_player()->GetSkillLevel(\"plowing\");
if(skill_level) skill_level *= 2;
else skill_level = 1;
FARMING_D->eventModHarvestTime(field, skill_level);
return 1;
}
It's a silly example, but you get the idea. The
\"plowing\" skill is valuable because the lib uses it
in some way to modify events the player performs. If
the lib doesn't know about it, the skill has no value.
In the case of, for example, \"blade attack\", the
lib checks for this if you're wielding a sword and
you're in combat. Based on how good you are at blade
attack, combat.c will modify how much damage you
inflict when you hit your opponent.
Section V: Special Abilities
----------------------------
Perhaps \"plant\" and \"sow\" are verbs that should
only be available to players with the skills \"planting\"
and \"sowing\".
Or, if farming isn't your thing and you want to
enhance combat, you might want Fighters who are members
of the Viking Guild to have a special ability called
\"massacre\" that can do extra special damage.
This is best done by simply creating the sow,
plant, and/or massacre verbs, then coding the verbs to
work only for those people you designate. If the player
isn't a Fighter and a Viking, perhaps the massacre
verb would return something like \"You have no idea
how to do that.\" and do no more.
You are, of course, free to implement a Special
Abilities System along the lines of the existing Dead
Souls spell system. I encourage you to do so, if
you're so inclined, and to share that code with me,
if it works. But it isn't necessary. The existing
verb system is plenty sophisticated enough to
handle such special events.
Section VI: Summary
-------------------
At this point in my lib coder development, I have
a hard time distinguishing what is easy and what is
hard for new people. I have been surprised by people
who take a long time to grasp simple concepts. I have
been surprised by people who grasp complex concepts
so quickly that I can't answer their questions.
Where you stand in that continuum I can't say.
What I can say is that if this chapter seems like it
went mostly over your head, you shouldn't worry too
much about it. It took me years of coding experience
and months of obsessed lib analysis to reach my
current level of understanding. You should not expect
yourself to grok everything in this guidebook the
first time around.
As suggested in the previous chapters, it's best
to start small, slow, and steady. As you build simple
things, more complex things will make more sense, and
you'll eventually reach the level of technical
expertise you need.
This chapter was not written to make you feel
overwhelmed by what you don't know. It was written so
that you understand what you're asking when you
say \"How do I revamp bodies and limbs so they have
knees and elbows you can poke people with?\"
Once you understand the lib, it really really isn't
that hard to do. But if you are a beginner, don't
set yourself up for failure by taking a leap at a
project you don't have the experience to tackle.
",({"chapter 9","chapter nine","9",}):"chapter 9 \"The Future\"
In the late 1990's, Descartes @ Nightmare released
a public domain version of the Nightmare lib called
Dead Souls, and pulled the official Nightmare lib from
distribution. They were very similar, but Dead Souls
lacked documentation.
His final realease of Dead Souls was 1.1.
As you must know by now, from reading the FAQ's (you
*did* read the FAQ's, right?) at some point I lost my mind
and decided to dust off that old lib, get it working, and
get people using it so that I could have other people to
discuss LPC code with.
I can honestly say I didn't know what I was getting
myself into. I mean, when I started, it sure *seemed* like
things \"mostly worked\". The hard part was just getting it
installed, right?
It's now been slightly over a year since I started
realizing that fixing A broke B, and fixing B broke A
and C, etc, ad nauseam. My guiding principle was \"get it
working\" with a corollary of \"make it work well\". My
main aim was bugfixes, but where a feature gap made it
obviously difficult for a beginner, I attempted to address
it with a new system. Hence, admintool, QCS, SNOOP_D, etc.
In other cases, a new system was trivially easy
to add and its absence was an unreasonable burden, such
as SetAction for rooms.
Sometimes a new system comes from my desire to have
a tool that makes my own life easier while fixing the
lib, such as the medical tricorder, remote control, and
the laboratory facility east of the Creators' Hall.
For the most part, though, my main focus has been
fixing stuff, not adding stuff. The intention has been
to release Dead Souls 2.1 as a version that is as free
of pain as possible, as clear of bugs as reasonable, and
as fun and useful as 1.1 should have been. Today it is
May 18 2006, and I think that I am very close
to that landmark. Today I am at the point where the list
of bugs consists of minor annoyances, of the kind that
players would hardly ever take note.
The future for Dead Souls I see as lots of fun for me.
I yearn to be free of doing nothing but fix fix fix. As
great as Dead Souls is, it needs major new systems to make
it fully competitive with the libs currently out there.
Adding these new systems is the kind of fun challenge
I'd really meant to be doing all along, but suckered
myself out of.
Post 2.1 I intend to concentrate on things like
mounts, vehicles, naval/vehicle combat, 3d travel, extending
virtual rooms, player-independent world persistence,
and lots of other fun stuff. I'm not in the habit of
making promises about the lib. As the Dead Souls community
knows, I prefer to just *make* a new system than talk
about it. I wanted to share these plans with you, though,
so that you know that Dead Souls development continues
apace, and great things are on the horizon.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Privacy\"
One of the most powerful and most easily abused tools
in your administrative arsenal is the snoop command. When
you \"snoop <person>\", you get to see everything they
say and do.
Players usually find this intrusive and objectionable,
and it is ethically shaky to do this without their
knowledge and consent. The only circumstances under which
snooping is unambiguously ethical are:
* Snooping one of your own test characters.
* Snooping a player (with their consent) for the purposes of
troubleshooting a bug.
* Snooping a user (without their consent) to investigate
a legitimate suspicion of malfeasance.
Secretly snooping people for your personal amusement is
just flat wrong.
By default, only admins can snoop. Admins are players who
are members of one or both of the groups SECURE and ASSIST.
An assistant admin *cannot* snoop a full admin. However,
assistant admins have read access to the snoop log directory,
so if global monitoring is enabled, they can read the
contents of a full admin's monitor log.
The new SNOOP_D system allows for the simultaneous
snooping of multiple people, and allows multiple people to
snoop the same person. It also permits you to enable
monitoring of users without having to snoop, by using the
monitor command to log i/o to /secure/log/adm.
The GLOBAL_MONITOR parameter in config.h will take one of three
arguments. 0 = monitor nobody. 1 = monitor everyone. 2 = monitor
everyone except admins. After changing it, reboot the mud
to make sure the change takes effect.
This functionality isn't here for your entertainment. In
fact, I had to think long and hard before sharing my snoop
code with you and putting it in the general lib distribution.
In the end, though, I believe that the benefits outweigh
the risk of abuse. As an admin, you have the right to know
what's going on in your mud, and as a lib coder, it isn't
my business to interfere with that.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction\"
If you're reading this, you've probably been
successful in installing Dead Souls, and are able
to get around and manipulate stuff within it.
There are lots of questions that new admins
have at this point. This book is not intended to
answer them. Specific questions about administration
are handled in the Admin FAQ, which you can read
at http://dead-souls.net/ds-admin-faq.html . A
local copy is archived in the /www directory, but
this may be out of date by now, and you'd be best
served by looking at the online version.
This guidebook isn't about \"how to\" or \"where
is,\" although such issues may be handled incidentally.
The point of this guidebook is to describe to you
various principles that are important to your
success as a Dead Souls mud admin.
The tone of these chapters is intended to be
conversational. Because of this, it may sound like
I'm being condescending (that means \"talking down
to you\"), but that isn't intentional. I'm
assuming that you fully understand I'm not the boss
of you, that you can decide for yourself what is
best for your mud, and that the contents of this
guidebook are thoroughly biased.
However rambling these pages might be, know that
I claim no authority over What Should Be. I can only
tell you, from my experience, how things look, and
how I *think* things work best.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Privacy\"
One of the most powerful and most easily abused tools
in your administrative arsenal is the snoop command. When
you \"snoop <person>\", you get to see everything they
say and do.
Players usually find this intrusive and objectionable,
and it is ethically shaky to do this without their
knowledge and consent. The only circumstances under which
snooping is unambiguously ethical are:
* Snooping one of your own test characters.
* Snooping a player (with their consent) for the purposes of
troubleshooting a bug.
* Snooping a user (without their consent) to investigate
a legitimate suspicion of malfeasance.
Secretly snooping people for your personal amusement is
just flat wrong.
By default, only admins can snoop. Admins are players who
are members of one or both of the groups SECURE and ASSIST.
An assistant admin *cannot* snoop a full admin. However,
assistant admins have read access to the snoop log directory,
so if global monitoring is enabled, they can read the
contents of a full admin's monitor log.
The new SNOOP_D system allows for the simultaneous
snooping of multiple people, and allows multiple people to
snoop the same person. It also permits you to enable
monitoring of users without having to snoop, by using the
monitor command to log i/o to /secure/log/adm.
The GLOBAL_MONITOR parameter in config.h will take one of three
arguments. 0 = monitor nobody. 1 = monitor everyone. 2 = monitor
everyone except admins. After changing it, reboot the mud
to make sure the change takes effect.
This functionality isn't here for your entertainment. In
fact, I had to think long and hard before sharing my snoop
code with you and putting it in the general lib distribution.
In the end, though, I believe that the benefits outweigh
the risk of abuse. As an admin, you have the right to know
what's going on in your mud, and as a lib coder, it isn't
my business to interfere with that.
",({"chapter 3","chapter three","3",}):"chapter 3 \"Getting Started\"
If you've read this far, there's a good chance
you already like the lib and are eager to get
going on this whole mud thing. Whether this is
true or not, this is where you find out for sure.
Let's assume you're set with the vision thing.
You're going to make a mud with a Dora the Explorer
theme. You can practically hear the music even
now! Let's get this mud written and open! Vamonos!
The first thing that happens here is you need
to get real familiar with this mud. If you haven't
done so already, when you finish this chapter,
you must do the following:
1) Read the Player's Handbook, cover to cover.
2) Create a test character. Do *not* use your
admin character's powers to give her any
equipment or money. Using the information from the
Player's Handbook, play this character until she
solves the Orcslayer Quest. Do not use any creator
characters to help her in any way.
It is very important that you do this. It
is the best way you will know for sure whether you
have made the right lib choice.
This doesn't mean that I'm asking you if you're
happy with the questing system, or how the items
in your inventory get listed, or whatever. Such
systems and cosmetics can be changed, sometimes with
trivial work.
What you're testing is the feel of the mud,
the parser, and that certain \"I don't know what\"
that tells you whether this is really a mud lib
you can live with.
Don't like quest advancement? That can be removed.
Want turns-based combat? That can be arranged (though
not trivially). But if you get a low level discomfort,
and can't shake the feeling that you can't get
anything done, then this is when you'll find out.
The second advantage to completing the Orcslayer
Quest is that it helps you see how things are
organized. As you proceed along the quest, you should
be using your admin character to examine the
files that compose the rooms, items, and npc's you see.
You will know where there are working samples of
doors, locks, hostile npc's, friendly npc's, spells,
and so on. This information will be valuable to you
in the next step you take.
If you complete the Orcslayer Quest and decide
you still like the lib, your next step is to create a
small area. Read the Creator's Manual starting from
chapter 31. This provides you a detailed set of
instructions on how to get stuff built quickly and
painlessly.
Build a few rooms. If you can't think of something
to build, create a room just like the one you're
sitting in right now, including desk, door, and
scruffy mud geek sitting on a chair.
Or you might already know exactly what you want
to build. This is a good time to build a few
rooms of Dora's Grandma's House, or the tree house
of Tico the Squirrel.
It's vitally important that you start using the
building tools, because just like with the Orcslayer
Quest, this is the point where you will discover whether
the build system here is a deal killer.
Once you're done with your few starter rooms, you'll
be in a position to know whether you've really made
the right mudlib choice.
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Advanced Topics\"
Section I: Shadows
------------------
Shadows are a problematic topic. My first impulse is to warn
you sternly not to mess with them, they shouldn't be used, etc. In
his legendary LPC text, Descartes goes so far as to say he hasn't
seen a shadow do something that couldn't be done better another way.
So, be aware that the topic of shadows tends to generate
strong opinions.
Shadows *are* hazardous pretty much by definition. A shadow
is an object that effectively \"wraps\" another object, like an
invisible shadow around it. When a call is made to the shadowed
object, the shadow intercepts that call.
What the shadow does with the call is up to you. It can
do nothing at all, and simply pass the call along to the
shadowed object. It could block that call. It could manipulate
that call and send the modified version to the shadowed
object.
You can see an example of a shadow in /secure/npc/drone.c .
This is the code that enables you to take control of an npc
with the remote control. The remote control loads /shadows/drone.c
which inherits /secure/npc/drone.c , and attaches that shadow to
the npc that is to be controlled. This is a way of \"adding
functions on the fly\" to an already-loaded npc.
The drone shadow is in the lib as an example
of how shadows work, and an example of how to get an object to
posess functions it did not have when loaded. It is not
intended to represent shadow advocacy.
The danger, and the reason some people go ballistic
when they hear the phrase \"I think I'll use a shadow for that\"
is that a lib that allows unrestricted shadow use
effectively has no security at all. You can have creators
enshadow an arch for example, or enshadow other privileged
objects.
Dead Souls shadows are pretty tightly restricted. The
master object does not permit a shadow to be created unless its
code is in /shadows . This means creators can't hide their
rootkit shadows in their homedirs and expect them to work.
Further, because /shadows is outside the /secure dir,
it serves as an obstacle to defeating the stack security
model.
In any case, I strongly recommend you avoid using them
except in the extremely unusual case of a task that has
no other solution. If your mud starts collecting a bunch
of shadows out of laziness, sadness will likely be the result.
Section II: The \"class\" Data Type
---------------------------------
In 1995 Beek added a data type to MudOS: class.
I have to admit that I'm at a bit of a loss to explain classes,
because I am not a C guy, I'm an LPC guy. For people who
grok C, the class data type is a natural and elegant solution
for organizing related sets of variables.
I have nothing but respect for Beek and the leet programmers
who built MudOS, so please don't go running around saying I'm
dissing them.
But in my experience, the use of classes generally serves
the purpose of obscuring code and making it more difficult
to debug.
I've seen newbie LPC coders take to classes like fish to water.
I can't explain it other than to speculate that for some people
the class data type (I've even had people argue at me that it's
really a \"data structure\", like I have the slightest clue what
the difference is) Just Makes Sense. If you are one of those people,
then bully for you. I'm serious. I don't understand you, but
your thing probably works for you, so, right on.
However, I do not recommend that the average newbie coder
spend too much time on classes. You'll have plenty of opportunity
when you start dissecting the lib, but my friendly advice to
the noob is to use mappings instead. I've yet to see a class
do something that a mapping couldn't do with greater clarity.
Section III: add_action
-----------------------
Yet another topic with potential for violence. The passions
can run high on this one, and in fact, the Lima team felt
strongly enough about it that they don't enable it by default.
No add_actions by default. That's hard core.
add_action is an efun that LP muds originally depended
on for most kinds of user input. If you wanted to be able to
throw a ball, that ball needed an add_action for it. You'd
have that ball provide a user with the throw command whenever
the user came in proximity to the ball. You can see the syntax
for add_action by examining the /lib/shop.c file.
It's simple. It works. I mean, really, It Just Works, and
it's the fastest way to add functionality to any object. People
used add_action from pretty much the genesis of LP, as far as
I can tell, and it became just a way of life. That was just
how it was done. Yes, that wording was intentional ;)
However, there were serious problems with this parsing
scheme. Basically it let any creator, regardless of skill
level, define commands for the mud. Joe's throw add_action for
his ball could be poorly coded and worded, and was liable to
behave differently from Jon's version.
And suppose you held two or three items that had a throw
add_action each? Which one took precedence? What if that
one was bugged?
Using add_actions for general lib command functionality
is really problematic for this reason. It fosters a lack
of uniformity across the lib that can leave users basically
playing a \"guess the syntax\" game for any item like this,
and allows for conflicts by incorrectly coded items. The
natural language parser was the MudOS solution to this problem,
implementing the verb system with which you are now so
familiar.
The controversy boils down to preference. For some people,
add_action is just How It Is Done, and you still have
people starting muds with libs like Skylib and TMI-2 (!!)
that lack an advanced lib-wide parser. For these people,
verbs are an overcomplicated mess that they just don't
have a need to understand.
Then you have the people who find add_action anathema, and
simply an unacceptable vestige of a more primitive form of
mudding. These people view add_actioners as atavistic knuckle-
draggers, either too dumb or too pig-headed to understand the
beauty and majesty of natural language parsing.
My view, predictably, is somewhere in the middle. Verbs
*can* be exhausting to accommodate when you're just Trying To
Do One Thing. But add_actions truly are of limited use in
a modern lib, and allowing their proliferation out of
laziness is probably a bad idea.
That isn't to say there isn't a place for add_actions. Dead
Souls supports them, and you'll see them every now and then.
In fact, technically, every command you issue, verb
or not, is evaluated by an add_action in the mud shell object
(LIB_NMSH). It is nevertheless better to learn to use verbs, because
they eliminate many problems you don't need to reinvent the
wheel for.
I had one person tell me, as they chose a MudOS lib that
didn't use verbs, that they planned to write their own natural
language parser in the lib. I bade him good luck. I wonder
how he's coming along these days.
Section IV: Thoughts on MudOS
-----------------------------
Like, wow. MudOS. You know?
",({"chapter 9","chapter nine","9",}):"chapter 9 \"The Future\"
In the late 1990's, Descartes @ Nightmare released
a public domain version of the Nightmare lib called
Dead Souls, and pulled the official Nightmare lib from
distribution. They were very similar, but Dead Souls
lacked documentation.
His final realease of Dead Souls was 1.1.
As you must know by now, from reading the FAQ's (you
*did* read the FAQ's, right?) at some point I lost my mind
and decided to dust off that old lib, get it working, and
get people using it so that I could have other people to
discuss LPC code with.
I can honestly say I didn't know what I was getting
myself into. I mean, when I started, it sure *seemed* like
things \"mostly worked\". The hard part was just getting it
installed, right?
It's now been slightly over a year since I started
realizing that fixing A broke B, and fixing B broke A
and C, etc, ad nauseam. My guiding principle was \"get it
working\" with a corollary of \"make it work well\". My
main aim was bugfixes, but where a feature gap made it
obviously difficult for a beginner, I attempted to address
it with a new system. Hence, admintool, QCS, SNOOP_D, etc.
In other cases, a new system was trivially easy
to add and its absence was an unreasonable burden, such
as SetAction for rooms.
Sometimes a new system comes from my desire to have
a tool that makes my own life easier while fixing the
lib, such as the medical tricorder, remote control, and
the laboratory facility east of the Creators' Hall.
For the most part, though, my main focus has been
fixing stuff, not adding stuff. The intention has been
to release Dead Souls 2.1 as a version that is as free
of pain as possible, as clear of bugs as reasonable, and
as fun and useful as 1.1 should have been. Today it is
May 18 2006, and I think that I am very close
to that landmark. Today I am at the point where the list
of bugs consists of minor annoyances, of the kind that
players would hardly ever take note.
The future for Dead Souls I see as lots of fun for me.
I yearn to be free of doing nothing but fix fix fix. As
great as Dead Souls is, it needs major new systems to make
it fully competitive with the libs currently out there.
Adding these new systems is the kind of fun challenge
I'd really meant to be doing all along, but suckered
myself out of.
Post 2.1 I intend to concentrate on things like
mounts, vehicles, naval/vehicle combat, 3d travel, extending
virtual rooms, player-independent world persistence,
and lots of other fun stuff. I'm not in the habit of
making promises about the lib. As the Dead Souls community
knows, I prefer to just *make* a new system than talk
about it. I wanted to share these plans with you, though,
so that you know that Dead Souls development continues
apace, and great things are on the horizon.
",({"chapter 8","chapter eight","8",}):"chapter 8 \"Understanding the Lib\"
One of the most common questions I get goes something
like this: \"I'd like to change combat so that it is
turns-based, with actions. How would I do this?\" Another
example might be \"I'm setting up a farming system, with
livestock and stuff. What should I look at?\"
To me, the questions are the same. Translated into
my language, this is the meaning: \"I have great ideas that
require advanced knowledge of the lib to implement. How do I do it?\"
I'm usually at a loss when I get one of these, because
I want to set people straight, but I don't want to hurt
their feelings, either. In the FAQ's, my response is
something along the lines of:
\"If there's anything in the Creator's Manual you don't
understand, you aren't ready to try this.\"
I hate to say that, because I think it's probably discouraging
to hear. After all, whatever the project, it is very likely
doable. You can make an LP mud do pretty much anything
you want...that's the beauty of the flexibility of LPC.
However, as they say, with great power comes great responsibility,
and in this case, it is your responsibility to understand the
lib, if you want to make major changes to it. Let's take the
example of farming.
Section I: Verbs
----------------
It is critical to understand how verbs work in order to
do anything in Dead Souls of an advanced nature. Verbs are
basically commands that do something to your environment,
something in your environment, your \"body\", or something
in your inventory.
For example, \"who\" is not a verb. It's a standard command,
which doesn't act on any cloned items. All it does is communicate
with the lib to query the list of users logged on, and displays
it to you in a particular manner.
Then there's something like \"zap\". That *is* a verb, and
it takes cloned items as arguments. When you \"zap orc\" this
has a special meaning to the parsing system. The parser is the
part of the game driver that tries to interpret your input
and attempts to do something useful with it. When the parser
catches a verb at the beginning of your input, it gets to
work on figuring out how the rest of the words in the input
relate to that verb.
This is done through \"rules\". You can take a look
at /verbs/creators/zap.c for the specific rules in this case.
If the word or words (for example \"first orc\", \"orcs\", \"an orc\")
match one or more objects in the room, the parser then
sends the arguments to the verb object. The verb object is
the loaded code from /verbs/creators/zap.c in this case.
Depending on how the verb is coded, your command line will
succeed or fail.
For your new farming system, you're going to need some new
verbs, so the first thing you need to do is understand verbs.
You're going to have to build new verbs like \"plow\", and \"plant\",
and \"harvest\". Therefore, you'll need to go over the verb
tutorial, which is at http://dead-souls.net/verbs.html
Section II: Lib Event Objects
-----------------------------
In the verb tutorial, you read that when a verb acts on
an object, the parser requires that the object have a function
that handles that verb. If a chair object lacks a function
like direct_sit() or something similar, the parser will assume
your sit verb doesn't apply to chairs, and the command line
will fail with something like \"You can't sit on the chair\".
It would be incredibly tedious to have to code a sit verb
handler in every piece of furniture you create. Similarly,
your farmer's field plow *could* have a plow verb handler
coded in it, but it is much better to create a lib object that
your plow will inherit. That way, other objects can inherit
that functionality without having to reinvent the wheel, and
plowing in general will be a uniform experience across the mud.
For example, one of the first systems I made when
I started my lib obsession was the inheritable flashlight
system. The original Dead Souls lib had regular old torches
you'd light with a match, but it seemed to me that not every
Dead Souls mud would be Sword & Sandals style, and a modern
illumination system should be available. So I set about
making a \"turn\" verb, so that once I had flashlights,
you could \"turn on the flashlight\".
I then created the lib object /lib/events/turn.c (when
referring to lib objects, I often use the macro name. In
this case, if I'd said LIB_TURN, it would be the same thing
as saying /lib/events/turn.c). The lib object doesn't really
*do* much of anything. That object isn't really where you
need to be checking for validity of commands. What that
object does, almost *all* it does, is to have functions that
correspond to the verb \"turn\". That's it. It's kind of like
a socket for a plug. The verb is the plug and you're trying
to use it on something. If that something has a socket
that fits your plug, then it'll work.
Lib event objects come in different flavors, and some
really do perform a bunch of thinking. But for the most part,
for simple verbs, all you need is a lib event object that
says \"yes, I understand that verb\".
LIB_TURN is inherited by LIB_FLASHLIGHT. That means
that when you clone an object that inherits LIB_FLASHLIGHT,
it contains all the functions of /lib/flashlight.c plus
all the functions that LIB_FLASHLIGHT inherits from LIB_TURN.
Because your flashlight inherits LIB_FLASHLIGHT,
which inherits LIB_TURN, when you issue the command line
\"turn on flashlight\", the parser checks with the
flashlight to see if it knows what you're talking about,
and gets a \"yes, I know that verb\" response. At that point
the parser says \"fine, here's the rest of what this
player thinks he can do with you and the turn verb\" and now
it's up to LIB_FLASHLIGHT to figure out whether it has
enough batteries, of the right kind, with sufficient
charge, and so on.
For your new farming system, you'll need to implement
a similar scheme. Your \"plow\" and \"hoe\" verbs will need
lib event objects that can be inherited by the cloned
objects you want to plow and hoe with.
In this case, LIB_FLASHLIGHT and the turn verb
aren't the best models for your new plowing system. This
is because your plow is something you plow *with*,
as opposed to something that *is plowed*.
To see how a plowing system might be implemented,
take a look at the \"dig\" verb, LIB_DIGGING, and
LIB_DIG_WITH. This is what a shovel would use, so
that you can \"dig in sand with the shovel\". After
studying the dig system, and lots of trial and error,
you will hopefully eventually come up with a
plow system that will let you \"plow field with plow\",
for example.
Section III: Daemons
--------------------
So, now you've created a plow verb, and a plow lib
event object, it works, and now you're happily plowing
along. Let's say that the rooms field1.c and field2.c
are plowable rooms. Presumably, you don't want people
to be able to plow here all the time. The fields need
time to do their thing, and constant plowing would
slow down the growth of your tender young corn stalks.
Normally, you might deal with this by having
a local variable in the room, so that \"harvest time is
50 hours, unless someone plows again, which
makes it take longer\", this sort of thing. Let's call
that variable PlowedTimes.
But, oh noes! The mud rebooted! Now all the rooms
have reset, and the planting and plowing variables
have reset!
You might avoid this problem by just not rebooting,
but even if you manage never ever to reboot your mud,
the mud periodically does resets of unused objects,
retiring them from memory and resetting their values
to zero.
You might avoid *that* problem by setting your
fields to be \"NoClean\", to avoid resets, but this is
very inelegant. Rather than ensuring the integrity
of your game data, you're just crossing your fingers
and hoping it doesn't go away.
The solution is to use a daemon. A daemon is an
object loaded into memory that acts like an arbiter
of information. For example, STARGATE_D keeps track
of where stargates are, and which gates are in
what state, and which gates are connected to each
other. It is important to have one location where
this data can be accessed, because a new gate must
be able to know what other valid gates there are, and
it must be able to know what gates are idle and
therefore accessible. STARGATE_D is a central
repository of this data, and serves as a mediator
for connection requests, keeping things working right.
In this case, the daemon's job would be to
keep track of which fields have been plowed, how
many times, and how long it'll take to get to harvest
time. Dead Souls daemons typically use object
persistence files ( http://dead-souls.net/ds-admin-faq.html#80 )
to avoid losing information during object reloads or
mud reboots. A FARMING_D is exactly what you need to
keep track of and manage this kind of data.
Section IV: Skills
------------------
To what extent should people be able to plow? How
well should they do it? If you care enough about farming
to have come this far, you've probably got ideas about
what good plowing is and what criteria a player should
have for extracting the most from their land.
This is where skills can play an important role.
What you have to understand about skills is that they
are simply variables in a player's body. Skills don't
have to be gained by joining a class, guild, or being
member of a race. Adding a skill to a player is as
simple as having an object do something like this:
this_player()->SetSkill(\"scuba diving\",1);
And if just strapping on a scuba tank does it, then
now that player has that skill.
Now, *normally* players are granted skills through
something more sensible than just picking up an object.
It makes more sense to have skills granted when a
player is taught something by an npc, or joins a guild,
or whatever, which is why traditionally that's how
it has worked.
So let's say you have a Farmer's Guild, then. When
you show up and sign the registry, some npc pops out,
\"teaches\" you the farming skills you need (by simply
adding the skills \"farming\" and \"plowing\" and \"sowing\"
to the player) and now you have the skills. If you
want, you can even create a Farmer class, like Fighters,
but that's up to you and not in the scope of this chapter.
This plowing skill is totally useless right
now. It does nothing at all, because you haven't yet
coded anything that makes use of it. This is the key
concept of the skills system that you must understand.
Just giving a player a skill does not mean that
it has any use. For a skill to be useful, there
must be lib verbs and/or objects that evaluate the
skill and perform calculations based on it.
It is therefore time to add these skill checks to the
objects that need them. For example, suppose our
farmer's plowing skill is at level 5. This doesn't
mean he's a level 5 player necessarily, just that
at plowing, his skill level is 5.
You might have a function in your /verbs/items/plow.c
verb that checks that skill, and determines how long
the field will take to grow based on it. Perhaps
for a level 5 plower, the field will be ready for
harvest in 45 hours. Perhaps for a level 10 plower,
it would be 35. You might have either the plow verb
or the plow lib event object do something like:
int PlowFunction(string field){
int skill_level = this_player()->GetSkillLevel(\"plowing\");
if(skill_level) skill_level *= 2;
else skill_level = 1;
FARMING_D->eventModHarvestTime(field, skill_level);
return 1;
}
It's a silly example, but you get the idea. The
\"plowing\" skill is valuable because the lib uses it
in some way to modify events the player performs. If
the lib doesn't know about it, the skill has no value.
In the case of, for example, \"blade attack\", the
lib checks for this if you're wielding a sword and
you're in combat. Based on how good you are at blade
attack, combat.c will modify how much damage you
inflict when you hit your opponent.
Section V: Special Abilities
----------------------------
Perhaps \"plant\" and \"sow\" are verbs that should
only be available to players with the skills \"planting\"
and \"sowing\".
Or, if farming isn't your thing and you want to
enhance combat, you might want Fighters who are members
of the Viking Guild to have a special ability called
\"massacre\" that can do extra special damage.
This is best done by simply creating the sow,
plant, and/or massacre verbs, then coding the verbs to
work only for those people you designate. If the player
isn't a Fighter and a Viking, perhaps the massacre
verb would return something like \"You have no idea
how to do that.\" and do no more.
You are, of course, free to implement a Special
Abilities System along the lines of the existing Dead
Souls spell system. I encourage you to do so, if
you're so inclined, and to share that code with me,
if it works. But it isn't necessary. The existing
verb system is plenty sophisticated enough to
handle such special events.
Section VI: Summary
-------------------
At this point in my lib coder development, I have
a hard time distinguishing what is easy and what is
hard for new people. I have been surprised by people
who take a long time to grasp simple concepts. I have
been surprised by people who grasp complex concepts
so quickly that I can't answer their questions.
Where you stand in that continuum I can't say.
What I can say is that if this chapter seems like it
went mostly over your head, you shouldn't worry too
much about it. It took me years of coding experience
and months of obsessed lib analysis to reach my
current level of understanding. You should not expect
yourself to grok everything in this guidebook the
first time around.
As suggested in the previous chapters, it's best
to start small, slow, and steady. As you build simple
things, more complex things will make more sense, and
you'll eventually reach the level of technical
expertise you need.
This chapter was not written to make you feel
overwhelmed by what you don't know. It was written so
that you understand what you're asking when you
say \"How do I revamp bodies and limbs so they have
knees and elbows you can poke people with?\"
Once you understand the lib, it really really isn't
that hard to do. But if you are a beginner, don't
set yourself up for failure by taking a leap at a
project you don't have the experience to tackle.
",({"chapter 4","chapter four","4",}):"chapter 4 \"Gaining Experience\"
Now you must prepare for the
technical demands of your creators.
Your creators will expect you to have
answers to their questions. For the most part,
they'll find these answers in the Creator's FAQ,
at http://dead-souls.net/ds-creator-faq.html .
But you're going to get questions not on
that FAQ. You'll get stuff like:
* How many quest points should the Issa Quest award?
* What's the maximum player level on this mud?
* Can I make a player class called \"Swiper\"?
* What's a good weapon type for The Bouncy Ball?
These are questions that depend strictly on you
and what your vision for the mud is. Dead Souls is
a starting point. The classes, levels, systems,
*everything*, is open to modification.
It is normal and natural not to have all of
these answers at first. I suggest you concentrate on
making an \"area\", perhaps a quest, consisting of
perhaps 15 to 20 rooms and a few npc's, weapons,
pieces of armor and general items.
Test your weapons and armor in the arena. To
get to the arena, first go to the Creators' Hall
by typing: wiz
Then go east, then north.
You can have the dummy wear armor like this:
give mithril shirt to dummy
force dummy to wear shirt
And you can test your weapons by beating on the
dummy:
force fighter to drop sword
clone hammer
give hammer to fighter
force fighter to wield hammer
force fighter to kill dummy
The dummy will very helpfully blurt out the amount
and type of damage it receives.
You can get ideas for your npc's by visiting the
menagerie. It is east and south of the Creators' Hall.
By creating your first quest, and putting test
characters through it, you will gain the experience
you need to be able to tell your players things like:
\"The Issa Quest is supposed to be easy, so make it 2qp.\"
\"Level 50 explorers are way too strong. I'm capping
player advancement at level 40.\"
\"No, there is already a thief class.\"
\"Make it a projectile weapon.\"
",({"chapter 5","chapter five","5",}):"chapter 5 \"Guilds, Classes, and Clans\"
Section I: Classes
------------------
I suspect you are already quite familiar with
the concept of classes. Some people even have very
strong feelings about classes, specifically, they
hate them.
Dead Souls 1 came with a class system, and
I decided to keep it because it sort of worked, and
many people are familiar with classes and want to
have them on their mud.
However, you don't have to use them.
That's right. Using classes is not required. You
can very easily make your Dead Souls mud completely
devoid of classes, if that's what you want.
The reason for this is that in Dead Souls, the only
thing a class does is confer on a player a class title,
like \"Fighter\", and load her up with skill settings appropriate
to that class. That's it. Classes do nothing more.
If you want player X to have all the skills of a
fighter and all the skills of a mage, just make it so.
They don't need to have class to have skills. Add
\"kite flying\" while you're at it. It doesn't matter.
Dead Souls operates on a skill system. The class system
is incidental to it and can be dispensed with entirely.
On the other hand, for those folks who appreciate
the role play and tactical elements involved in using
classes, Dead Souls provides you the ability to use
the existing stock classes, and permits you to create
your own. The files in /secure/cfg/classes describe them.
See http://dead-souls.net/ds-creator-faq.html#2.46
for the exact syntax of these files.
You'll notice that there is a field for \"how important\"
a skill is. What this modifier does is determine how
quickly your player's skill levels rise when her player
level rises. A fighter's magical ability does not increase
much when she is promoted a player level, but a mage's
magical ability certainly does.
For those who find classes distasteful, this kind
of \"skill hobbling\" is just the reason they hate classes:
they feel they are too restrictive. If you are such
an admin, just dont use classes, and whenever you award
a skill to a player, set the \"how important\" number
(techninally known as the \"skill class\" but that's a very
confusing term) to the same for everyone.
Incidentally, you don't *need* player levels either.
I will leave it as an exercise for the reader to imagine
a scheme where players do not have advancement levels.
DS1 allowed players to join more than one class.
Unfortunately, the system was buggy enough and stripped
of classes so that it was both meaningless and a bit
of a problem. Multi-classing is by default not permitted
on DS2, but it's easy enough to implement on your own.
Review the header data in the class files to see the
syntax.
However, since multiclassing isn't something I
am interested in working on right now, I will not be
assisting anyone in getting that to work.
Section II: Guilds
------------------
The concept of \"guilds\" carries some baggage for me. I
learned about mudding by playing on Darker Realms, an LP
mud where a \"guild\" was effectively a class. There was
the mage's guild, the barbarian guild, etc. There were
also guilds that blurred the distinction between class
and race, like shapeshifters, cyborg, and dragon.
When I ran into the Dead Souls 1 conception of guilds,
then, I was pretty confused. In DS1, a guild was
kind of a player-run club. There was a guild object
that made some sort of determination about who
was in or out, who was the boss, etc. My presumption is
that in later iterations of Nightmare V, these guilds
were fleshed out and worked properly. I presume this
because in DS1 they didn't, and they seemed to make some
assumptions about the lib that were not correct.
I decided to change this for DS2, and the way it now
works is that guilds are neither classes nor player-run
clubs. The plan is to have guilds be not-necessarily-class-
based affiliations a player can have with balanced
advantages and disadvantages. As of DS2.0 there is no
working sample of this, but that's the plan for post
2.1 development.
Section III: Clans
------------------
Clans serve the purpose that DS1 guilds did. They are
player-run affiliations, managed by clan objects that
confer identity and status. As of DS2.0 there is no working
sample of this, but that's the plan for post 2.1 development.
",({"chapter 8","chapter eight","8",}):"chapter 8 \"Understanding the Lib\"
One of the most common questions I get goes something
like this: \"I'd like to change combat so that it is
turns-based, with actions. How would I do this?\" Another
example might be \"I'm setting up a farming system, with
livestock and stuff. What should I look at?\"
To me, the questions are the same. Translated into
my language, this is the meaning: \"I have great ideas that
require advanced knowledge of the lib to implement. How do I do it?\"
I'm usually at a loss when I get one of these, because
I want to set people straight, but I don't want to hurt
their feelings, either. In the FAQ's, my response is
something along the lines of:
\"If there's anything in the Creator's Manual you don't
understand, you aren't ready to try this.\"
I hate to say that, because I think it's probably discouraging
to hear. After all, whatever the project, it is very likely
doable. You can make an LP mud do pretty much anything
you want...that's the beauty of the flexibility of LPC.
However, as they say, with great power comes great responsibility,
and in this case, it is your responsibility to understand the
lib, if you want to make major changes to it. Let's take the
example of farming.
Section I: Verbs
----------------
It is critical to understand how verbs work in order to
do anything in Dead Souls of an advanced nature. Verbs are
basically commands that do something to your environment,
something in your environment, your \"body\", or something
in your inventory.
For example, \"who\" is not a verb. It's a standard command,
which doesn't act on any cloned items. All it does is communicate
with the lib to query the list of users logged on, and displays
it to you in a particular manner.
Then there's something like \"zap\". That *is* a verb, and
it takes cloned items as arguments. When you \"zap orc\" this
has a special meaning to the parsing system. The parser is the
part of the game driver that tries to interpret your input
and attempts to do something useful with it. When the parser
catches a verb at the beginning of your input, it gets to
work on figuring out how the rest of the words in the input
relate to that verb.
This is done through \"rules\". You can take a look
at /verbs/creators/zap.c for the specific rules in this case.
If the word or words (for example \"first orc\", \"orcs\", \"an orc\")
match one or more objects in the room, the parser then
sends the arguments to the verb object. The verb object is
the loaded code from /verbs/creators/zap.c in this case.
Depending on how the verb is coded, your command line will
succeed or fail.
For your new farming system, you're going to need some new
verbs, so the first thing you need to do is understand verbs.
You're going to have to build new verbs like \"plow\", and \"plant\",
and \"harvest\". Therefore, you'll need to go over the verb
tutorial, which is at http://dead-souls.net/verbs.html
Section II: Lib Event Objects
-----------------------------
In the verb tutorial, you read that when a verb acts on
an object, the parser requires that the object have a function
that handles that verb. If a chair object lacks a function
like direct_sit() or something similar, the parser will assume
your sit verb doesn't apply to chairs, and the command line
will fail with something like \"You can't sit on the chair\".
It would be incredibly tedious to have to code a sit verb
handler in every piece of furniture you create. Similarly,
your farmer's field plow *could* have a plow verb handler
coded in it, but it is much better to create a lib object that
your plow will inherit. That way, other objects can inherit
that functionality without having to reinvent the wheel, and
plowing in general will be a uniform experience across the mud.
For example, one of the first systems I made when
I started my lib obsession was the inheritable flashlight
system. The original Dead Souls lib had regular old torches
you'd light with a match, but it seemed to me that not every
Dead Souls mud would be Sword & Sandals style, and a modern
illumination system should be available. So I set about
making a \"turn\" verb, so that once I had flashlights,
you could \"turn on the flashlight\".
I then created the lib object /lib/events/turn.c (when
referring to lib objects, I often use the macro name. In
this case, if I'd said LIB_TURN, it would be the same thing
as saying /lib/events/turn.c). The lib object doesn't really
*do* much of anything. That object isn't really where you
need to be checking for validity of commands. What that
object does, almost *all* it does, is to have functions that
correspond to the verb \"turn\". That's it. It's kind of like
a socket for a plug. The verb is the plug and you're trying
to use it on something. If that something has a socket
that fits your plug, then it'll work.
Lib event objects come in different flavors, and some
really do perform a bunch of thinking. But for the most part,
for simple verbs, all you need is a lib event object that
says \"yes, I understand that verb\".
LIB_TURN is inherited by LIB_FLASHLIGHT. That means
that when you clone an object that inherits LIB_FLASHLIGHT,
it contains all the functions of /lib/flashlight.c plus
all the functions that LIB_FLASHLIGHT inherits from LIB_TURN.
Because your flashlight inherits LIB_FLASHLIGHT,
which inherits LIB_TURN, when you issue the command line
\"turn on flashlight\", the parser checks with the
flashlight to see if it knows what you're talking about,
and gets a \"yes, I know that verb\" response. At that point
the parser says \"fine, here's the rest of what this
player thinks he can do with you and the turn verb\" and now
it's up to LIB_FLASHLIGHT to figure out whether it has
enough batteries, of the right kind, with sufficient
charge, and so on.
For your new farming system, you'll need to implement
a similar scheme. Your \"plow\" and \"hoe\" verbs will need
lib event objects that can be inherited by the cloned
objects you want to plow and hoe with.
In this case, LIB_FLASHLIGHT and the turn verb
aren't the best models for your new plowing system. This
is because your plow is something you plow *with*,
as opposed to something that *is plowed*.
To see how a plowing system might be implemented,
take a look at the \"dig\" verb, LIB_DIGGING, and
LIB_DIG_WITH. This is what a shovel would use, so
that you can \"dig in sand with the shovel\". After
studying the dig system, and lots of trial and error,
you will hopefully eventually come up with a
plow system that will let you \"plow field with plow\",
for example.
Section III: Daemons
--------------------
So, now you've created a plow verb, and a plow lib
event object, it works, and now you're happily plowing
along. Let's say that the rooms field1.c and field2.c
are plowable rooms. Presumably, you don't want people
to be able to plow here all the time. The fields need
time to do their thing, and constant plowing would
slow down the growth of your tender young corn stalks.
Normally, you might deal with this by having
a local variable in the room, so that \"harvest time is
50 hours, unless someone plows again, which
makes it take longer\", this sort of thing. Let's call
that variable PlowedTimes.
But, oh noes! The mud rebooted! Now all the rooms
have reset, and the planting and plowing variables
have reset!
You might avoid this problem by just not rebooting,
but even if you manage never ever to reboot your mud,
the mud periodically does resets of unused objects,
retiring them from memory and resetting their values
to zero.
You might avoid *that* problem by setting your
fields to be \"NoClean\", to avoid resets, but this is
very inelegant. Rather than ensuring the integrity
of your game data, you're just crossing your fingers
and hoping it doesn't go away.
The solution is to use a daemon. A daemon is an
object loaded into memory that acts like an arbiter
of information. For example, STARGATE_D keeps track
of where stargates are, and which gates are in
what state, and which gates are connected to each
other. It is important to have one location where
this data can be accessed, because a new gate must
be able to know what other valid gates there are, and
it must be able to know what gates are idle and
therefore accessible. STARGATE_D is a central
repository of this data, and serves as a mediator
for connection requests, keeping things working right.
In this case, the daemon's job would be to
keep track of which fields have been plowed, how
many times, and how long it'll take to get to harvest
time. Dead Souls daemons typically use object
persistence files ( http://dead-souls.net/ds-admin-faq.html#80 )
to avoid losing information during object reloads or
mud reboots. A FARMING_D is exactly what you need to
keep track of and manage this kind of data.
Section IV: Skills
------------------
To what extent should people be able to plow? How
well should they do it? If you care enough about farming
to have come this far, you've probably got ideas about
what good plowing is and what criteria a player should
have for extracting the most from their land.
This is where skills can play an important role.
What you have to understand about skills is that they
are simply variables in a player's body. Skills don't
have to be gained by joining a class, guild, or being
member of a race. Adding a skill to a player is as
simple as having an object do something like this:
this_player()->SetSkill(\"scuba diving\",1);
And if just strapping on a scuba tank does it, then
now that player has that skill.
Now, *normally* players are granted skills through
something more sensible than just picking up an object.
It makes more sense to have skills granted when a
player is taught something by an npc, or joins a guild,
or whatever, which is why traditionally that's how
it has worked.
So let's say you have a Farmer's Guild, then. When
you show up and sign the registry, some npc pops out,
\"teaches\" you the farming skills you need (by simply
adding the skills \"farming\" and \"plowing\" and \"sowing\"
to the player) and now you have the skills. If you
want, you can even create a Farmer class, like Fighters,
but that's up to you and not in the scope of this chapter.
This plowing skill is totally useless right
now. It does nothing at all, because you haven't yet
coded anything that makes use of it. This is the key
concept of the skills system that you must understand.
Just giving a player a skill does not mean that
it has any use. For a skill to be useful, there
must be lib verbs and/or objects that evaluate the
skill and perform calculations based on it.
It is therefore time to add these skill checks to the
objects that need them. For example, suppose our
farmer's plowing skill is at level 5. This doesn't
mean he's a level 5 player necessarily, just that
at plowing, his skill level is 5.
You might have a function in your /verbs/items/plow.c
verb that checks that skill, and determines how long
the field will take to grow based on it. Perhaps
for a level 5 plower, the field will be ready for
harvest in 45 hours. Perhaps for a level 10 plower,
it would be 35. You might have either the plow verb
or the plow lib event object do something like:
int PlowFunction(string field){
int skill_level = this_player()->GetSkillLevel(\"plowing\");
if(skill_level) skill_level *= 2;
else skill_level = 1;
FARMING_D->eventModHarvestTime(field, skill_level);
return 1;
}
It's a silly example, but you get the idea. The
\"plowing\" skill is valuable because the lib uses it
in some way to modify events the player performs. If
the lib doesn't know about it, the skill has no value.
In the case of, for example, \"blade attack\", the
lib checks for this if you're wielding a sword and
you're in combat. Based on how good you are at blade
attack, combat.c will modify how much damage you
inflict when you hit your opponent.
Section V: Special Abilities
----------------------------
Perhaps \"plant\" and \"sow\" are verbs that should
only be available to players with the skills \"planting\"
and \"sowing\".
Or, if farming isn't your thing and you want to
enhance combat, you might want Fighters who are members
of the Viking Guild to have a special ability called
\"massacre\" that can do extra special damage.
This is best done by simply creating the sow,
plant, and/or massacre verbs, then coding the verbs to
work only for those people you designate. If the player
isn't a Fighter and a Viking, perhaps the massacre
verb would return something like \"You have no idea
how to do that.\" and do no more.
You are, of course, free to implement a Special
Abilities System along the lines of the existing Dead
Souls spell system. I encourage you to do so, if
you're so inclined, and to share that code with me,
if it works. But it isn't necessary. The existing
verb system is plenty sophisticated enough to
handle such special events.
Section VI: Summary
-------------------
At this point in my lib coder development, I have
a hard time distinguishing what is easy and what is
hard for new people. I have been surprised by people
who take a long time to grasp simple concepts. I have
been surprised by people who grasp complex concepts
so quickly that I can't answer their questions.
Where you stand in that continuum I can't say.
What I can say is that if this chapter seems like it
went mostly over your head, you shouldn't worry too
much about it. It took me years of coding experience
and months of obsessed lib analysis to reach my
current level of understanding. You should not expect
yourself to grok everything in this guidebook the
first time around.
As suggested in the previous chapters, it's best
to start small, slow, and steady. As you build simple
things, more complex things will make more sense, and
you'll eventually reach the level of technical
expertise you need.
This chapter was not written to make you feel
overwhelmed by what you don't know. It was written so
that you understand what you're asking when you
say \"How do I revamp bodies and limbs so they have
knees and elbows you can poke people with?\"
Once you understand the lib, it really really isn't
that hard to do. But if you are a beginner, don't
set yourself up for failure by taking a leap at a
project you don't have the experience to tackle.
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Advanced Topics\"
Section I: Shadows
------------------
Shadows are a problematic topic. My first impulse is to warn
you sternly not to mess with them, they shouldn't be used, etc. In
his legendary LPC text, Descartes goes so far as to say he hasn't
seen a shadow do something that couldn't be done better another way.
So, be aware that the topic of shadows tends to generate
strong opinions.
Shadows *are* hazardous pretty much by definition. A shadow
is an object that effectively \"wraps\" another object, like an
invisible shadow around it. When a call is made to the shadowed
object, the shadow intercepts that call.
What the shadow does with the call is up to you. It can
do nothing at all, and simply pass the call along to the
shadowed object. It could block that call. It could manipulate
that call and send the modified version to the shadowed
object.
You can see an example of a shadow in /secure/npc/drone.c .
This is the code that enables you to take control of an npc
with the remote control. The remote control loads /shadows/drone.c
which inherits /secure/npc/drone.c , and attaches that shadow to
the npc that is to be controlled. This is a way of \"adding
functions on the fly\" to an already-loaded npc.
The drone shadow is in the lib as an example
of how shadows work, and an example of how to get an object to
posess functions it did not have when loaded. It is not
intended to represent shadow advocacy.
The danger, and the reason some people go ballistic
when they hear the phrase \"I think I'll use a shadow for that\"
is that a lib that allows unrestricted shadow use
effectively has no security at all. You can have creators
enshadow an arch for example, or enshadow other privileged
objects.
Dead Souls shadows are pretty tightly restricted. The
master object does not permit a shadow to be created unless its
code is in /shadows . This means creators can't hide their
rootkit shadows in their homedirs and expect them to work.
Further, because /shadows is outside the /secure dir,
it serves as an obstacle to defeating the stack security
model.
In any case, I strongly recommend you avoid using them
except in the extremely unusual case of a task that has
no other solution. If your mud starts collecting a bunch
of shadows out of laziness, sadness will likely be the result.
Section II: The \"class\" Data Type
---------------------------------
In 1995 Beek added a data type to MudOS: class.
I have to admit that I'm at a bit of a loss to explain classes,
because I am not a C guy, I'm an LPC guy. For people who
grok C, the class data type is a natural and elegant solution
for organizing related sets of variables.
I have nothing but respect for Beek and the leet programmers
who built MudOS, so please don't go running around saying I'm
dissing them.
But in my experience, the use of classes generally serves
the purpose of obscuring code and making it more difficult
to debug.
I've seen newbie LPC coders take to classes like fish to water.
I can't explain it other than to speculate that for some people
the class data type (I've even had people argue at me that it's
really a \"data structure\", like I have the slightest clue what
the difference is) Just Makes Sense. If you are one of those people,
then bully for you. I'm serious. I don't understand you, but
your thing probably works for you, so, right on.
However, I do not recommend that the average newbie coder
spend too much time on classes. You'll have plenty of opportunity
when you start dissecting the lib, but my friendly advice to
the noob is to use mappings instead. I've yet to see a class
do something that a mapping couldn't do with greater clarity.
Section III: add_action
-----------------------
Yet another topic with potential for violence. The passions
can run high on this one, and in fact, the Lima team felt
strongly enough about it that they don't enable it by default.
No add_actions by default. That's hard core.
add_action is an efun that LP muds originally depended
on for most kinds of user input. If you wanted to be able to
throw a ball, that ball needed an add_action for it. You'd
have that ball provide a user with the throw command whenever
the user came in proximity to the ball. You can see the syntax
for add_action by examining the /lib/shop.c file.
It's simple. It works. I mean, really, It Just Works, and
it's the fastest way to add functionality to any object. People
used add_action from pretty much the genesis of LP, as far as
I can tell, and it became just a way of life. That was just
how it was done. Yes, that wording was intentional ;)
However, there were serious problems with this parsing
scheme. Basically it let any creator, regardless of skill
level, define commands for the mud. Joe's throw add_action for
his ball could be poorly coded and worded, and was liable to
behave differently from Jon's version.
And suppose you held two or three items that had a throw
add_action each? Which one took precedence? What if that
one was bugged?
Using add_actions for general lib command functionality
is really problematic for this reason. It fosters a lack
of uniformity across the lib that can leave users basically
playing a \"guess the syntax\" game for any item like this,
and allows for conflicts by incorrectly coded items. The
natural language parser was the MudOS solution to this problem,
implementing the verb system with which you are now so
familiar.
The controversy boils down to preference. For some people,
add_action is just How It Is Done, and you still have
people starting muds with libs like Skylib and TMI-2 (!!)
that lack an advanced lib-wide parser. For these people,
verbs are an overcomplicated mess that they just don't
have a need to understand.
Then you have the people who find add_action anathema, and
simply an unacceptable vestige of a more primitive form of
mudding. These people view add_actioners as atavistic knuckle-
draggers, either too dumb or too pig-headed to understand the
beauty and majesty of natural language parsing.
My view, predictably, is somewhere in the middle. Verbs
*can* be exhausting to accommodate when you're just Trying To
Do One Thing. But add_actions truly are of limited use in
a modern lib, and allowing their proliferation out of
laziness is probably a bad idea.
That isn't to say there isn't a place for add_actions. Dead
Souls supports them, and you'll see them every now and then.
In fact, technically, every command you issue, verb
or not, is evaluated by an add_action in the mud shell object
(LIB_NMSH). It is nevertheless better to learn to use verbs, because
they eliminate many problems you don't need to reinvent the
wheel for.
I had one person tell me, as they chose a MudOS lib that
didn't use verbs, that they planned to write their own natural
language parser in the lib. I bade him good luck. I wonder
how he's coming along these days.
Section IV: Thoughts on MudOS
-----------------------------
Like, wow. MudOS. You know?
",({"chapter 2","chapter two","2",}):"chapter 2 \"The Vision Thing\"
Are you sure you want to run a mud? I mean, are
you *really* sure?
Most newbie admins have no idea what a difficult
task lays before them. I started my own mud in 1995.
It's still around today, in fact. Back then, I'd
been coding on a mud that had its hosting pulled. I
finagled access to my university's systems and
told the old mud's admin \"Hey, let's host it here!\"
He didn't want to, so it was just me and my
new Nightmare IV mud. I figured \"what the heck, maybe
I can run my own,\" and the rest is history.
I hadn't a clue how to manage people, and things
just wouldn't come together. I had literally dozens of
creators come and go, and I could never figure
out why they'd build a few things and leave. The
problem was me, obviously. There was nothing about
the mud people disliked: Nightmare was a very popular
lib at the time.
The problem was that people wanted leadership
from me, and I didn't even know it, much less know
how to provide it.
Creators (\"builders\") are your most precious resource.
Without them you don't have a mud, you have a lib. Sure,
you can try building everything yourself...and with
Dead Souls, that's not so farfetched an idea. But
after a few months of toil, you'll see that you have
weaknesses, you are not the perfect builder, and you
will wish for the help and support of others.
If you don't carefully cultivate your relationships
with these people, you will fail. Your mud will be a
failed mud, and your self-expression squelched.
This is why I ask you if you're *really* sure you
want to run a mud. Running a mud isn't about lording
it over puny mortal players. It isn't about being
the sharpest lib coder logged in. It isn't about
bossing your staff, or making long lists of rules
and job titles and meeting schedules.
Your job as an administrator is to manage
people, and guide them toward a single vision, over
which you yourself may not have full control. People
will listen to the admin at first because, well, she's
the admin. But if you can't demonstrate the qualities
of leadership they expect, they will stop respecting
you, and they will leave. Or worse, they will hang
around and be difficult.
What's this about a \"vision\"? People will work for
a variety of reasons, mostly money, fun, recognition,
etc. Rewards. When your new coders show up, they
will need motivation to work. Since you probably
won't be offering much in the way of money or
recognition, you'll need to find a way to motivate
your coders by making it fun to work with you.
Obviously I don't mean you need to be jolly and
wear funny hats. In fact, you can be quite boring a
person and still be good to work with. When I mean it
has to be fun for your creators, I mean that they
have to be inspired to do stuff...they have to *want*
to build because they are expressing themselves in
a way they enjoy.
This means you'd be unwise to start parceling out
\"missions\" and \"assignments\". Find out what your new
creator *wants* to do, then do your best to
accommodate them. It's that simple. If they're working
on what they *want* to do, you don't need to actively
motivate them...you just need to make sure they
have what they need, and that they understand what
is expected of them.
These expectations are the other part of the
individual management of creators. Just as is it
fatal to give creators \"homework\", it is just as
counterproductive to say \"do whatever you want, man,
you're free to do anything.\" Part of the fun of
work is knowing what the standards are, and how your
work will be judged. If your creator feels like you
don't actually care what she builds, she won't
care much about doing a job that's up to any standards
but her own. After a while of this, she's going to
figure out she might as well just run her *own* mud.
You therefore have to have a strong sense of what
your mud will look like, and what each creator's role
in that mud will be. If you don't know, or it seems like
you don't know, you'll lose them.
You don't run the mud because you have the admin
password. You run it because people think you run it.
If they stop thinking it, you stop running it.
So I ask again. Do you know what you want out of
this mud? Have you planned out what you want people
to be doing? When a talented coder shows up, will
you be prepared to negotiate their proper role,
and persuade them that the mud will succeed?
Do you *really* want to be a mud admin? Or are
you just looking to be someone's boss?
First, find your vision. Everything else will be
hard work, but if you know what your mud will be,
and what you need from other people, then you
just might have a chance to succeed.
",({"chapter 9","chapter nine","9",}):"chapter 9 \"The Future\"
In the late 1990's, Descartes @ Nightmare released
a public domain version of the Nightmare lib called
Dead Souls, and pulled the official Nightmare lib from
distribution. They were very similar, but Dead Souls
lacked documentation.
His final realease of Dead Souls was 1.1.
As you must know by now, from reading the FAQ's (you
*did* read the FAQ's, right?) at some point I lost my mind
and decided to dust off that old lib, get it working, and
get people using it so that I could have other people to
discuss LPC code with.
I can honestly say I didn't know what I was getting
myself into. I mean, when I started, it sure *seemed* like
things \"mostly worked\". The hard part was just getting it
installed, right?
It's now been slightly over a year since I started
realizing that fixing A broke B, and fixing B broke A
and C, etc, ad nauseam. My guiding principle was \"get it
working\" with a corollary of \"make it work well\". My
main aim was bugfixes, but where a feature gap made it
obviously difficult for a beginner, I attempted to address
it with a new system. Hence, admintool, QCS, SNOOP_D, etc.
In other cases, a new system was trivially easy
to add and its absence was an unreasonable burden, such
as SetAction for rooms.
Sometimes a new system comes from my desire to have
a tool that makes my own life easier while fixing the
lib, such as the medical tricorder, remote control, and
the laboratory facility east of the Creators' Hall.
For the most part, though, my main focus has been
fixing stuff, not adding stuff. The intention has been
to release Dead Souls 2.1 as a version that is as free
of pain as possible, as clear of bugs as reasonable, and
as fun and useful as 1.1 should have been. Today it is
May 18 2006, and I think that I am very close
to that landmark. Today I am at the point where the list
of bugs consists of minor annoyances, of the kind that
players would hardly ever take note.
The future for Dead Souls I see as lots of fun for me.
I yearn to be free of doing nothing but fix fix fix. As
great as Dead Souls is, it needs major new systems to make
it fully competitive with the libs currently out there.
Adding these new systems is the kind of fun challenge
I'd really meant to be doing all along, but suckered
myself out of.
Post 2.1 I intend to concentrate on things like
mounts, vehicles, naval/vehicle combat, 3d travel, extending
virtual rooms, player-independent world persistence,
and lots of other fun stuff. I'm not in the habit of
making promises about the lib. As the Dead Souls community
knows, I prefer to just *make* a new system than talk
about it. I wanted to share these plans with you, though,
so that you know that Dead Souls development continues
apace, and great things are on the horizon.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction\"
If you're reading this, you've probably been
successful in installing Dead Souls, and are able
to get around and manipulate stuff within it.
There are lots of questions that new admins
have at this point. This book is not intended to
answer them. Specific questions about administration
are handled in the Admin FAQ, which you can read
at http://dead-souls.net/ds-admin-faq.html . A
local copy is archived in the /www directory, but
this may be out of date by now, and you'd be best
served by looking at the online version.
This guidebook isn't about \"how to\" or \"where
is,\" although such issues may be handled incidentally.
The point of this guidebook is to describe to you
various principles that are important to your
success as a Dead Souls mud admin.
The tone of these chapters is intended to be
conversational. Because of this, it may sound like
I'm being condescending (that means \"talking down
to you\"), but that isn't intentional. I'm
assuming that you fully understand I'm not the boss
of you, that you can decide for yourself what is
best for your mud, and that the contents of this
guidebook are thoroughly biased.
However rambling these pages might be, know that
I claim no authority over What Should Be. I can only
tell you, from my experience, how things look, and
how I *think* things work best.
",({"chapter 5","chapter five","5",}):"chapter 5 \"Guilds, Classes, and Clans\"
Section I: Classes
------------------
I suspect you are already quite familiar with
the concept of classes. Some people even have very
strong feelings about classes, specifically, they
hate them.
Dead Souls 1 came with a class system, and
I decided to keep it because it sort of worked, and
many people are familiar with classes and want to
have them on their mud.
However, you don't have to use them.
That's right. Using classes is not required. You
can very easily make your Dead Souls mud completely
devoid of classes, if that's what you want.
The reason for this is that in Dead Souls, the only
thing a class does is confer on a player a class title,
like \"Fighter\", and load her up with skill settings appropriate
to that class. That's it. Classes do nothing more.
If you want player X to have all the skills of a
fighter and all the skills of a mage, just make it so.
They don't need to have class to have skills. Add
\"kite flying\" while you're at it. It doesn't matter.
Dead Souls operates on a skill system. The class system
is incidental to it and can be dispensed with entirely.
On the other hand, for those folks who appreciate
the role play and tactical elements involved in using
classes, Dead Souls provides you the ability to use
the existing stock classes, and permits you to create
your own. The files in /secure/cfg/classes describe them.
See http://dead-souls.net/ds-creator-faq.html#2.46
for the exact syntax of these files.
You'll notice that there is a field for \"how important\"
a skill is. What this modifier does is determine how
quickly your player's skill levels rise when her player
level rises. A fighter's magical ability does not increase
much when she is promoted a player level, but a mage's
magical ability certainly does.
For those who find classes distasteful, this kind
of \"skill hobbling\" is just the reason they hate classes:
they feel they are too restrictive. If you are such
an admin, just dont use classes, and whenever you award
a skill to a player, set the \"how important\" number
(techninally known as the \"skill class\" but that's a very
confusing term) to the same for everyone.
Incidentally, you don't *need* player levels either.
I will leave it as an exercise for the reader to imagine
a scheme where players do not have advancement levels.
DS1 allowed players to join more than one class.
Unfortunately, the system was buggy enough and stripped
of classes so that it was both meaningless and a bit
of a problem. Multi-classing is by default not permitted
on DS2, but it's easy enough to implement on your own.
Review the header data in the class files to see the
syntax.
However, since multiclassing isn't something I
am interested in working on right now, I will not be
assisting anyone in getting that to work.
Section II: Guilds
------------------
The concept of \"guilds\" carries some baggage for me. I
learned about mudding by playing on Darker Realms, an LP
mud where a \"guild\" was effectively a class. There was
the mage's guild, the barbarian guild, etc. There were
also guilds that blurred the distinction between class
and race, like shapeshifters, cyborg, and dragon.
When I ran into the Dead Souls 1 conception of guilds,
then, I was pretty confused. In DS1, a guild was
kind of a player-run club. There was a guild object
that made some sort of determination about who
was in or out, who was the boss, etc. My presumption is
that in later iterations of Nightmare V, these guilds
were fleshed out and worked properly. I presume this
because in DS1 they didn't, and they seemed to make some
assumptions about the lib that were not correct.
I decided to change this for DS2, and the way it now
works is that guilds are neither classes nor player-run
clubs. The plan is to have guilds be not-necessarily-class-
based affiliations a player can have with balanced
advantages and disadvantages. As of DS2.0 there is no
working sample of this, but that's the plan for post
2.1 development.
Section III: Clans
------------------
Clans serve the purpose that DS1 guilds did. They are
player-run affiliations, managed by clan objects that
confer identity and status. As of DS2.0 there is no working
sample of this, but that's the plan for post 2.1 development.
",({"chapter 3","chapter three","3",}):"chapter 3 \"Getting Started\"
If you've read this far, there's a good chance
you already like the lib and are eager to get
going on this whole mud thing. Whether this is
true or not, this is where you find out for sure.
Let's assume you're set with the vision thing.
You're going to make a mud with a Dora the Explorer
theme. You can practically hear the music even
now! Let's get this mud written and open! Vamonos!
The first thing that happens here is you need
to get real familiar with this mud. If you haven't
done so already, when you finish this chapter,
you must do the following:
1) Read the Player's Handbook, cover to cover.
2) Create a test character. Do *not* use your
admin character's powers to give her any
equipment or money. Using the information from the
Player's Handbook, play this character until she
solves the Orcslayer Quest. Do not use any creator
characters to help her in any way.
It is very important that you do this. It
is the best way you will know for sure whether you
have made the right lib choice.
This doesn't mean that I'm asking you if you're
happy with the questing system, or how the items
in your inventory get listed, or whatever. Such
systems and cosmetics can be changed, sometimes with
trivial work.
What you're testing is the feel of the mud,
the parser, and that certain \"I don't know what\"
that tells you whether this is really a mud lib
you can live with.
Don't like quest advancement? That can be removed.
Want turns-based combat? That can be arranged (though
not trivially). But if you get a low level discomfort,
and can't shake the feeling that you can't get
anything done, then this is when you'll find out.
The second advantage to completing the Orcslayer
Quest is that it helps you see how things are
organized. As you proceed along the quest, you should
be using your admin character to examine the
files that compose the rooms, items, and npc's you see.
You will know where there are working samples of
doors, locks, hostile npc's, friendly npc's, spells,
and so on. This information will be valuable to you
in the next step you take.
If you complete the Orcslayer Quest and decide
you still like the lib, your next step is to create a
small area. Read the Creator's Manual starting from
chapter 31. This provides you a detailed set of
instructions on how to get stuff built quickly and
painlessly.
Build a few rooms. If you can't think of something
to build, create a room just like the one you're
sitting in right now, including desk, door, and
scruffy mud geek sitting on a chair.
Or you might already know exactly what you want
to build. This is a good time to build a few
rooms of Dora's Grandma's House, or the tree house
of Tico the Squirrel.
It's vitally important that you start using the
building tools, because just like with the Orcslayer
Quest, this is the point where you will discover whether
the build system here is a deal killer.
Once you're done with your few starter rooms, you'll
be in a position to know whether you've really made
the right mudlib choice.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Privacy\"
One of the most powerful and most easily abused tools
in your administrative arsenal is the snoop command. When
you \"snoop <person>\", you get to see everything they
say and do.
Players usually find this intrusive and objectionable,
and it is ethically shaky to do this without their
knowledge and consent. The only circumstances under which
snooping is unambiguously ethical are:
* Snooping one of your own test characters.
* Snooping a player (with their consent) for the purposes of
troubleshooting a bug.
* Snooping a user (without their consent) to investigate
a legitimate suspicion of malfeasance.
Secretly snooping people for your personal amusement is
just flat wrong.
By default, only admins can snoop. Admins are players who
are members of one or both of the groups SECURE and ASSIST.
An assistant admin *cannot* snoop a full admin. However,
assistant admins have read access to the snoop log directory,
so if global monitoring is enabled, they can read the
contents of a full admin's monitor log.
The new SNOOP_D system allows for the simultaneous
snooping of multiple people, and allows multiple people to
snoop the same person. It also permits you to enable
monitoring of users without having to snoop, by using the
monitor command to log i/o to /secure/log/adm.
The GLOBAL_MONITOR parameter in config.h will take one of three
arguments. 0 = monitor nobody. 1 = monitor everyone. 2 = monitor
everyone except admins. After changing it, reboot the mud
to make sure the change takes effect.
This functionality isn't here for your entertainment. In
fact, I had to think long and hard before sharing my snoop
code with you and putting it in the general lib distribution.
In the end, though, I believe that the benefits outweigh
the risk of abuse. As an admin, you have the right to know
what's going on in your mud, and as a lib coder, it isn't
my business to interfere with that.
",({"chapter 5","chapter five","5",}):"chapter 5 \"Guilds, Classes, and Clans\"
Section I: Classes
------------------
I suspect you are already quite familiar with
the concept of classes. Some people even have very
strong feelings about classes, specifically, they
hate them.
Dead Souls 1 came with a class system, and
I decided to keep it because it sort of worked, and
many people are familiar with classes and want to
have them on their mud.
However, you don't have to use them.
That's right. Using classes is not required. You
can very easily make your Dead Souls mud completely
devoid of classes, if that's what you want.
The reason for this is that in Dead Souls, the only
thing a class does is confer on a player a class title,
like \"Fighter\", and load her up with skill settings appropriate
to that class. That's it. Classes do nothing more.
If you want player X to have all the skills of a
fighter and all the skills of a mage, just make it so.
They don't need to have class to have skills. Add
\"kite flying\" while you're at it. It doesn't matter.
Dead Souls operates on a skill system. The class system
is incidental to it and can be dispensed with entirely.
On the other hand, for those folks who appreciate
the role play and tactical elements involved in using
classes, Dead Souls provides you the ability to use
the existing stock classes, and permits you to create
your own. The files in /secure/cfg/classes describe them.
See http://dead-souls.net/ds-creator-faq.html#2.46
for the exact syntax of these files.
You'll notice that there is a field for \"how important\"
a skill is. What this modifier does is determine how
quickly your player's skill levels rise when her player
level rises. A fighter's magical ability does not increase
much when she is promoted a player level, but a mage's
magical ability certainly does.
For those who find classes distasteful, this kind
of \"skill hobbling\" is just the reason they hate classes:
they feel they are too restrictive. If you are such
an admin, just dont use classes, and whenever you award
a skill to a player, set the \"how important\" number
(techninally known as the \"skill class\" but that's a very
confusing term) to the same for everyone.
Incidentally, you don't *need* player levels either.
I will leave it as an exercise for the reader to imagine
a scheme where players do not have advancement levels.
DS1 allowed players to join more than one class.
Unfortunately, the system was buggy enough and stripped
of classes so that it was both meaningless and a bit
of a problem. Multi-classing is by default not permitted
on DS2, but it's easy enough to implement on your own.
Review the header data in the class files to see the
syntax.
However, since multiclassing isn't something I
am interested in working on right now, I will not be
assisting anyone in getting that to work.
Section II: Guilds
------------------
The concept of \"guilds\" carries some baggage for me. I
learned about mudding by playing on Darker Realms, an LP
mud where a \"guild\" was effectively a class. There was
the mage's guild, the barbarian guild, etc. There were
also guilds that blurred the distinction between class
and race, like shapeshifters, cyborg, and dragon.
When I ran into the Dead Souls 1 conception of guilds,
then, I was pretty confused. In DS1, a guild was
kind of a player-run club. There was a guild object
that made some sort of determination about who
was in or out, who was the boss, etc. My presumption is
that in later iterations of Nightmare V, these guilds
were fleshed out and worked properly. I presume this
because in DS1 they didn't, and they seemed to make some
assumptions about the lib that were not correct.
I decided to change this for DS2, and the way it now
works is that guilds are neither classes nor player-run
clubs. The plan is to have guilds be not-necessarily-class-
based affiliations a player can have with balanced
advantages and disadvantages. As of DS2.0 there is no
working sample of this, but that's the plan for post
2.1 development.
Section III: Clans
------------------
Clans serve the purpose that DS1 guilds did. They are
player-run affiliations, managed by clan objects that
confer identity and status. As of DS2.0 there is no working
sample of this, but that's the plan for post 2.1 development.
",({"chapter 4","chapter four","4",}):"chapter 4 \"Gaining Experience\"
Now you must prepare for the
technical demands of your creators.
Your creators will expect you to have
answers to their questions. For the most part,
they'll find these answers in the Creator's FAQ,
at http://dead-souls.net/ds-creator-faq.html .
But you're going to get questions not on
that FAQ. You'll get stuff like:
* How many quest points should the Issa Quest award?
* What's the maximum player level on this mud?
* Can I make a player class called \"Swiper\"?
* What's a good weapon type for The Bouncy Ball?
These are questions that depend strictly on you
and what your vision for the mud is. Dead Souls is
a starting point. The classes, levels, systems,
*everything*, is open to modification.
It is normal and natural not to have all of
these answers at first. I suggest you concentrate on
making an \"area\", perhaps a quest, consisting of
perhaps 15 to 20 rooms and a few npc's, weapons,
pieces of armor and general items.
Test your weapons and armor in the arena. To
get to the arena, first go to the Creators' Hall
by typing: wiz
Then go east, then north.
You can have the dummy wear armor like this:
give mithril shirt to dummy
force dummy to wear shirt
And you can test your weapons by beating on the
dummy:
force fighter to drop sword
clone hammer
give hammer to fighter
force fighter to wield hammer
force fighter to kill dummy
The dummy will very helpfully blurt out the amount
and type of damage it receives.
You can get ideas for your npc's by visiting the
menagerie. It is east and south of the Creators' Hall.
By creating your first quest, and putting test
characters through it, you will gain the experience
you need to be able to tell your players things like:
\"The Issa Quest is supposed to be easy, so make it 2qp.\"
\"Level 50 explorers are way too strong. I'm capping
player advancement at level 40.\"
\"No, there is already a thief class.\"
\"Make it a projectile weapon.\"
",]),"/doc/guide/chapter02":1164672846,"/doc/guide/chapter01":1164672846,]),"/doc/phints":(["object":"/domains/default/obj/phints","title":"Untitled","items":([({"chapter 4","chapter four","4",}):"\"The Town Well\"
",({"chapter 2","chapter two","2",}):"\"Post-Mansion Hints\"
",({"chapter 1","chapter one","1",}):"\"Getting started: The Newbie Mansion\"
",({"chapter 5","chapter five","5",}):"\"The Orcslayer\"
",({"chapter 4","chapter four","4",}):"\"The Town Well\"
",({"chapter 2","chapter two","2",}):"\"Post-Mansion Hints\"
",({"chapter 3","chapter three","3",}):"\"Teleportation Devices\"
",({"chapter 1","chapter one","1",}):"\"Getting started: The Newbie Mansion\"
",({"chapter 2","chapter two","2",}):"\"Post-Mansion Hints\"
",({"chapter 1","chapter one","1",}):"\"Getting started: The Newbie Mansion\"
",({"chapter 4","chapter four","4",}):"\"The Town Well\"
",({"chapter 3","chapter three","3",}):"\"Teleportation Devices\"
",({"chapter 3","chapter three","3",}):"\"Teleportation Devices\"
",({"chapter 5","chapter five","5",}):"\"The Orcslayer\"
",({"chapter 5","chapter five","5",}):"\"The Orcslayer\"
",]),"/doc/phints/chapter05":1236662936,"/doc/phints/chapter04":1238277470,"index":" Untitled
Chapter 1: \"Getting started: The Newbie Mansion\"
Chapter 2: \"Post-Mansion Hints\"
Chapter 3: \"Teleportation Devices\"
Chapter 4: \"The Town Well\"
Chapter 5: \"The Orcslayer\"
","/doc/phints/chapter03":1236662936,"reads":([({"chapter 5","chapter five","5",}):"chapter 5 \"The Orcslayer\"
You're getting spoiled, aren't you? You're achieving dizzying
heights of player leveling in no time, and learning tons about
how to fight, move, and operate in the Dead Souls environment.
Well, sorry to say, we are close to the end of your free ride.
The orcslayer quest is the last one you get a full walkthrough
on. After this, you'll need to apply the lessons you've learned
from this hint book to whatever other quests and adventures your
mud features.
Before we really get started...do you remember what you did with
that bear costume? I ask because orcs are known to be quite
deferential to bears. Keep that in mind, if your player race
is not \"orc\".
Now, Leo the Archwizard misplaced his precious magical sword, Orcslayer.
Leo is in the basement of the village church (1w, 1n of the clocktower)
waiting for you to help him find it. Little does he know that some
juvenile orcs, trying to prove their mettle, stole it and have given
it to their wanna-be shaman in their improvised lair (4w, 1sw, 1w,
1n, 1w of the clocktower).
There are a few ways you can handle the orcs between you and the shaman.
If you decide to do it the hard way, let me remind you that Herkimer
can sometimes teach you some spells even if you are not a mage. In particular
the buffer spell can pro
+++
ATH0
NO CARRIER
",({"chapter 5","chapter five","5",}):"chapter 5 \"The Orcslayer\"
You're getting spoiled, aren't you? You're achieving dizzying
heights of player leveling in no time, and learning tons about
how to fight, move, and operate in the Dead Souls environment.
Well, sorry to say, we are close to the end of your free ride.
The orcslayer quest is the last one you get a full walkthrough
on. After this, you'll need to apply the lessons you've learned
from this hint book to whatever other quests and adventures your
mud features.
Before we really get started...do you remember what you did with
that bear costume? I ask because orcs are known to be quite
deferential to bears. Keep that in mind, if your player race
is not \"orc\".
Now, Leo the Archwizard misplaced his precious magical sword, Orcslayer.
Leo is in the basement of the village church (1w, 1n of the clocktower)
waiting for you to help him find it. Little does he know that some
juvenile orcs, trying to prove their mettle, stole it and have given
it to their wanna-be shaman in their improvised lair (4w, 1sw, 1w,
1n, 1w of the clocktower).
There are a few ways you can handle the orcs between you and the shaman.
If you decide to do it the hard way, let me remind you that Herkimer
can sometimes teach you some spells even if you are not a mage. In particular
the buffer spell can pro
+++
ATH0
NO CARRIER
",({"chapter 4","chapter four","4",}):"chapter 4 \"The Town Well\"
Ok, so you're now all set with a bunch of money in the bank,
light-but-decent equipment, and a thirst for more advancement.
Let's try a quest that requires a little fancy footwork and
little (if any) fighting.
I'm going to assume that the portal gun portals are where the
default distribution puts them. The blue portal in the hazlab,
the orange portal...well, you'll see.
By now you should have Kleiner's badge. If not, go back to where I
describe how to get it. If you sold it, go buy it back. Also, get
yourself a flashlight. I recommend maglite, but whatever you can
find is better than nothing. Now go back to Jennybot. Near her is
a stairwell that leads to the campus basement. Turn on your
flashlight then go 1n, 1w, 1s, 1d, 1w of Jennybot. You're now in
the western end of the campus basement. Note the smudged wall?
%^GREEN%^push wall%^RESET%^
Whee! Go west until you can't go any further, then go 1s. Lying
there is a key you'll need. Put it in your ruck, then go north
til you can't go north any further. Note that the next couple of
things you need to do must be done quickly. You're about to enter
a dangerous sewer service area, and you may get blasted with harmful
steam if you hang around too long.
Go down into the sewer area. See that pile of crap? Search it. Bonus!
Now go east twice, up, open grate, up. You're now back up on the
cobblestone road, but with a shiny new ring to wear or sell, and
a key you'll need. Head to the hazlab (4s, 1w, 1s). Now, just in case
some joker put the orange portal somewhere dangerous, you should have
an omni handy (you can buy one from Oana). Enter the blue portal. Unless
someone's been playing around, you should now be in an underground
tunnel, with a large metal door to the west. There's not much to
explore here...if you follow the tunnel north and east, you'll find
a locked gate with a cave troll on the other side. What you really
want to do is get past the door to the west. You got the key to this door
from the campus underground, so go ahead and unlock it. Now comes the
hard part...open the door.
Chances are that you can't. It's huge and it's heavy...it requires having
a strength stat of at least 50 to open. You have 2 possible ways of dealing
with this...one is expensive, the other a bit tricky. The expensive way
is to buy strength potions from Oana and drink them until your strength
is above 49, then rush back to this door before the potions wear off,
and open the door. Like I said, expensive.
The other way is to control something that is strong enough to open the
door. Go back to kleiner, then type:
%^GREEN%^dial stargate lab%^RESET%^
This takes you to a secret area used by creators to test their npc's,
armor, and equipment. Go north, then east. If you're lucky, nobody has
taken the landstrider mech for a joyride and it's standing there, waiting
for you. Type: %^GREEN%^enter mech%^RESET%^
You are now inside your very own mech-type robot you control. Cool!
Have it go back to the stargate:
%^GREEN%^drive west%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^direct mech to dial campus lab%^RESET%^
%^GREEN%^direct mech to enter gate%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^drive south%^RESET%^
Uh oh! The mech can't enter the hazlab because it's not carrying Kleiner's
badge! What you need to do is drop the badge. It will then be in the mech's
inventory, so that the security door can see it.
%^GREEN%^drop badge%^RESET%^
%^GREEN%^drive south%^RESET%^
Now we have the mech enter the portal and open the door for you.
%^GREEN%^direct mech to enter portal%^RESET%^
%^GREEN%^direct mech to open door%^RESET%^
Excellent! Now, the next part involves making this area kind of
hostile for general use, so let's get the mech out of harm's way
%^GREEN%^get badge%^RESET%^
%^GREEN%^wear badge%^RESET%^
%^GREEN%^direct mech to enter portal%^RESET%^
%^GREEN%^out%^RESET%^
Now go back in the portal (just you, without the mech), and
go west twice. This is the source of the problem. The town well is dry
because someone shut off the water source!
To restore water to the well, turn the metal wheel here, and
water will start rushing out of the pipe at high pressure. There's a
grate-type door overhead through which the water is supposed to flow up to
the bottom of the well. If the orange portal hadn't been there, you'd have had
to do the alternate solution, which involves accessing the
water source from that other direction.
Now, as you can imagine, this area is going to fill up with water
very rapidly, so leave east right away. The entire series of tunnels
will flood, so get back to the orange portal and enter it quickly.
%^RED%^PROTIP:%^RESET%^ The water will also go through the locked cavetroll
gate. And cavetrolls don't breathe water.
Now, go to the town well (1n, 1e, 5n of the hazlab), and: %^GREEN%^enter well%^RESET%^
Hopefully you did get the maglite. By now the weaker flashlights may have
pooped out. See the lever on the wall? That operates a set of two doors that
serve as a waterlock for the well. Pull it, and a door will open, letting
water flood into the well, and solving the quest!
It may be that someone has already been fooling with the water lock, though.
If the door was already open when you got there, push the lever, wait a
few minutes for water to flood the lock, then pull the lever. It could
take a while depending on the water pressure for the lock to fill up, so
if it's dry, close it again and wait a while before opening again.
%^CYAN%^Alternate Resolution%^RESET%^
It may be that the orange portal was not in the water tunnel. It could
be that the hazardous materials labs was not accessible to you. If that
is the case, you'll need to solve this quest by accessing the water
source from the direction of the well. This is tricky because you need
to be very strong to open the grate, *and* you need an extra set of
hands to operate the water lock lever. Let's deal with one problem at a time.
First, the strength issue. On the other side of the water lock is a
grate that you must have at least 50 strength to open. If your strength
is already above 49, great. If not, you need to decide whether you want
to boost it with strength potions from Oana (expensive) or use the
landstrider mech (tricky). If the plan is to use strength potions, go buy
them now and stick them in your ruck. If the plan is to use the mech,
go get it. Go to Kleiner, and:
%^GREEN%^dial stargate lab%^RESET%^
%^GREEN%^enter gate%^RESET%^
%^GREEN%^go north%^RESET%^
%^GREEN%^go east%^RESET%^
%^GREEN%^enter mech%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^direct mech to dial campus lab%^RESET%^
%^GREEN%^direct mech to enter gate%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^drive east%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^direct mech to enter well%^RESET%^
%^GREEN%^direct mech to pull lever%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^out%^RESET%^
%^GREEN%^go east%^RESET%^
The mech is now in the water lock. But neither you nor the mech
can proceed further west...you need someone to pull the lever
for you. You're going to need a servant...a slave...a ZOMBIE! Go
up and west, and buy a dark scroll from Oana. The scroll can be
used to raise a corpse, but you must be able to read English. If
you've chosen a race that doesn't speak English, you'll need to
learn it now. Go to Bugg (1n, 2e, 1n of the well) and ask him to
teach you English. Once you've learned it, you'll be ready to
create a zombie. From Bugg, go to the campus square (1s, 2w, 5s)
and murder Tim. It's important that you slay something that speaks
a language you know, so since you're either human or just now
learned English, a human victim is suitable. Plus Tim is a wuss.
Once he's slain: %^GREEN%^read scroll at tim%^RESET%^
There he is! Your very own zombie! We'll need to move quickly
now. Zombies don't last forever...the clock is ticking. Do the
following to get him to follow you:
%^GREEN%^speak in english%^RESET%^
%^GREEN%^say follow cratylus%^RESET%^
Except, of course, instead of \"cratylus\", put in your own name.
Note something important here. Zombies are obedient, but not
smart. If someone else starts saying things that the zombie understands
as a command, it will start obeying them. So remember that while
you created this zombie, someone else can steal him if you're not
careful.
Now, return to the well (4n) then enter the well and go west.
You'll need to tell the zombie to leave the water lock and operate it:
%^GREEN%^say go east%^RESET%^
%^GREEN%^yell push lever%^RESET%^
%^GREEN%^enter mech%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^direct mech to open grate%^RESET%^
Woohoo! If you don't have the mech, of course, you just
walk your own self west, drink your strength potions, and
open the grate yourself. If you *do* have the mech, let's get it out
of harm's way...
%^GREEN%^drive east%^RESET%^
%^GREEN%^out%^RESET%^
%^GREEN%^go west%^RESET%^
All righty, now let's go resolve this well problem. Go down
and you'll see the metal wheel and the water pipe. To get the water
flowing again, turn the wheel. Then very quickly get yourself back to
the water lock...the area will fill with water soon! When water
gets to the lock: %^GREEN%^yell pull lever%^RESET%^
Your zombie will then open the east door, allowing you and the water to
get to the well, and solving the Town Well Quest. Congratulations!
",({"chapter 3","chapter three","3",}):"chapter 3 \"Teleportation Devices\"
Let's talk a little about how to use some of the teleportation
technology in the default Dead Souls distribution. Some of
this stuff can save your hide, and some of it can kill you,
so it's worth being aware of it.
%^CYAN%^Visitor's Pass%^RESET%^
This is an extremely rare item, and only intended for official test
characters. If you don't show up in the \"who\" list with a [%^YELLOW%^TEST%^RESET%^]
tag, and you have a visitor's pass, something has gone wrong. You
use the pass to get out of trouble with the command: %^GREEN%^click heels%^RESET%^
%^CYAN%^omni%^RESET%^
The omni is a device created by a consortium of time travelers dedicated
to fixing errors in history. While they'd never make the full chrono-omni
available for public consuption, a \"gimped\" version can be purchased
from Oana at the magic shop. This omni does not do time travel, and when
you press the button on it, it will only take you to one location: Lars's
Pub. However, it is an incredibly valuable tool of recall when you find
yourself in a tight spot. For example, suppose you are an orc or a dwarf,
and you cannot swim, but you walked off the village shore and are now
drowning and sinking to the sea floor. If you have an omni in your
rucksack, you can:
%^GREEN%^get omi from ruck%^RESET%^
%^GREEN%^push button on omni%^RESET%^
And materialize, gasping but alive, in the old tavern. It's a must-have
for any adventurer. I suggest aliasing those commands, so you can
hit the buttons quickly in panic-mode. See: %^GREEN%^help alias%^RESET%^
%^RED%^PROTIP:%^RESET%^ The omni is programmed not to be in the possession of a
living being for very long. If you keep it in your inventory too long, it
will glow red and disappear. You can limit this by keeping it in your ruck.
%^CYAN%^Kleiner's omni%^RESET%^
Kleiner, brilliant scientist he is, has been tinkering with his omni
and disabled the auto-vanish feature for himself on it. He also has been
able to reprogram his omni to take him to different places. However,
you really don't want this omni. Only Kleiner can control where it takes
him. For anyone else, it takes them to a completely random location
on the mud, and believe me when I tell you this is not really as
fun as it sounds.
%^CYAN%^Stargates%^RESET%^
Ancient technology of a long-lost civilization, stargates permit travel
to and from each other. You dial a specific stargate you want to go to,
then enter the stargate. Note that if the destination stargate is already
in use the dialing process may fail. Stargates stay open for only a brief
time, though, so unless your mud is super busy, you can probably just
retry the dialing and find success.
Note that stargates can take you to places whose environment is unsuitable
to you. If you dial a stargate that is on the sea floor or one that is in
outer space, you'd best be wearing a breathing device or be carrying
an omni with you. Of course, if on your mud someone has coded a stargate
whose destination is next to a sun going nova, or in a nest of gundarks,
the breathing device may provide insufficient protection
%^CYAN%^Portal gun%^RESET%^
If it has both blue and orange vials installed, this device allows you to
create portals (one blue, one orange) that connect to each other. Entering
the blue portal will take you to the location of the orange portal, and
vice versa. Only one blue portal may exist in the game at any time...the
creation of a new one destroys the old one. This is also true of the
orange portal. Portal locations persist through reboots. By default, the
blue vial is found along with the portal gun, in the hazlab. The orange
vial is rather trickier to obtain...it is kept in the private storage area
of an incredibly powerful and aggressive cave troll.
You can create a blue portal wherever you want if you have the blue vial
installed, but without the orange vial you cannot change the location of
the orange portal.
%^RED%^Protip:%^RESET%^ Do not ever enter a portal if the other portal is
in the same room.
",({"chapter 4","chapter four","4",}):"chapter 4 \"The Town Well\"
Ok, so you're now all set with a bunch of money in the bank,
light-but-decent equipment, and a thirst for more advancement.
Let's try a quest that requires a little fancy footwork and
little (if any) fighting.
I'm going to assume that the portal gun portals are where the
default distribution puts them. The blue portal in the hazlab,
the orange portal...well, you'll see.
By now you should have Kleiner's badge. If not, go back to where I
describe how to get it. If you sold it, go buy it back. Also, get
yourself a flashlight. I recommend maglite, but whatever you can
find is better than nothing. Now go back to Jennybot. Near her is
a stairwell that leads to the campus basement. Turn on your
flashlight then go 1n, 1w, 1s, 1d, 1w of Jennybot. You're now in
the western end of the campus basement. Note the smudged wall?
%^GREEN%^push wall%^RESET%^
Whee! Go west until you can't go any further, then go 1s. Lying
there is a key you'll need. Put it in your ruck, then go north
til you can't go north any further. Note that the next couple of
things you need to do must be done quickly. You're about to enter
a dangerous sewer service area, and you may get blasted with harmful
steam if you hang around too long.
Go down into the sewer area. See that pile of crap? Search it. Bonus!
Now go east twice, up, open grate, up. You're now back up on the
cobblestone road, but with a shiny new ring to wear or sell, and
a key you'll need. Head to the hazlab (4s, 1w, 1s). Now, just in case
some joker put the orange portal somewhere dangerous, you should have
an omni handy (you can buy one from Oana). Enter the blue portal. Unless
someone's been playing around, you should now be in an underground
tunnel, with a large metal door to the west. There's not much to
explore here...if you follow the tunnel north and east, you'll find
a locked gate with a cave troll on the other side. What you really
want to do is get past the door to the west. You got the key to this door
from the campus underground, so go ahead and unlock it. Now comes the
hard part...open the door.
Chances are that you can't. It's huge and it's heavy...it requires having
a strength stat of at least 50 to open. You have 2 possible ways of dealing
with this...one is expensive, the other a bit tricky. The expensive way
is to buy strength potions from Oana and drink them until your strength
is above 49, then rush back to this door before the potions wear off,
and open the door. Like I said, expensive.
The other way is to control something that is strong enough to open the
door. Go back to kleiner, then type:
%^GREEN%^dial stargate lab%^RESET%^
This takes you to a secret area used by creators to test their npc's,
armor, and equipment. Go north, then east. If you're lucky, nobody has
taken the landstrider mech for a joyride and it's standing there, waiting
for you. Type: %^GREEN%^enter mech%^RESET%^
You are now inside your very own mech-type robot you control. Cool!
Have it go back to the stargate:
%^GREEN%^drive west%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^direct mech to dial campus lab%^RESET%^
%^GREEN%^direct mech to enter gate%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^drive south%^RESET%^
Uh oh! The mech can't enter the hazlab because it's not carrying Kleiner's
badge! What you need to do is drop the badge. It will then be in the mech's
inventory, so that the security door can see it.
%^GREEN%^drop badge%^RESET%^
%^GREEN%^drive south%^RESET%^
Now we have the mech enter the portal and open the door for you.
%^GREEN%^direct mech to enter portal%^RESET%^
%^GREEN%^direct mech to open door%^RESET%^
Excellent! Now, the next part involves making this area kind of
hostile for general use, so let's get the mech out of harm's way
%^GREEN%^get badge%^RESET%^
%^GREEN%^wear badge%^RESET%^
%^GREEN%^direct mech to enter portal%^RESET%^
%^GREEN%^out%^RESET%^
Now go back in the portal (just you, without the mech), and
go west twice. This is the source of the problem. The town well is dry
because someone shut off the water source!
To restore water to the well, turn the metal wheel here, and
water will start rushing out of the pipe at high pressure. There's a
grate-type door overhead through which the water is supposed to flow up to
the bottom of the well. If the orange portal hadn't been there, you'd have had
to do the alternate solution, which involves accessing the
water source from that other direction.
Now, as you can imagine, this area is going to fill up with water
very rapidly, so leave east right away. The entire series of tunnels
will flood, so get back to the orange portal and enter it quickly.
%^RED%^PROTIP:%^RESET%^ The water will also go through the locked cavetroll
gate. And cavetrolls don't breathe water.
Now, go to the town well (1n, 1e, 5n of the hazlab), and: %^GREEN%^enter well%^RESET%^
Hopefully you did get the maglite. By now the weaker flashlights may have
pooped out. See the lever on the wall? That operates a set of two doors that
serve as a waterlock for the well. Pull it, and a door will open, letting
water flood into the well, and solving the quest!
It may be that someone has already been fooling with the water lock, though.
If the door was already open when you got there, push the lever, wait a
few minutes for water to flood the lock, then pull the lever. It could
take a while depending on the water pressure for the lock to fill up, so
if it's dry, close it again and wait a while before opening again.
%^CYAN%^Alternate Resolution%^RESET%^
It may be that the orange portal was not in the water tunnel. It could
be that the hazardous materials labs was not accessible to you. If that
is the case, you'll need to solve this quest by accessing the water
source from the direction of the well. This is tricky because you need
to be very strong to open the grate, *and* you need an extra set of
hands to operate the water lock lever. Let's deal with one problem at a time.
First, the strength issue. On the other side of the water lock is a
grate that you must have at least 50 strength to open. If your strength
is already above 49, great. If not, you need to decide whether you want
to boost it with strength potions from Oana (expensive) or use the
landstrider mech (tricky). If the plan is to use strength potions, go buy
them now and stick them in your ruck. If the plan is to use the mech,
go get it. Go to Kleiner, and:
%^GREEN%^dial stargate lab%^RESET%^
%^GREEN%^enter gate%^RESET%^
%^GREEN%^go north%^RESET%^
%^GREEN%^go east%^RESET%^
%^GREEN%^enter mech%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^direct mech to dial campus lab%^RESET%^
%^GREEN%^direct mech to enter gate%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^drive east%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^direct mech to enter well%^RESET%^
%^GREEN%^direct mech to pull lever%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^out%^RESET%^
%^GREEN%^go east%^RESET%^
The mech is now in the water lock. But neither you nor the mech
can proceed further west...you need someone to pull the lever
for you. You're going to need a servant...a slave...a ZOMBIE! Go
up and west, and buy a dark scroll from Oana. The scroll can be
used to raise a corpse, but you must be able to read English. If
you've chosen a race that doesn't speak English, you'll need to
learn it now. Go to Bugg (1n, 2e, 1n of the well) and ask him to
teach you English. Once you've learned it, you'll be ready to
create a zombie. From Bugg, go to the campus square (1s, 2w, 5s)
and murder Tim. It's important that you slay something that speaks
a language you know, so since you're either human or just now
learned English, a human victim is suitable. Plus Tim is a wuss.
Once he's slain: %^GREEN%^read scroll at tim%^RESET%^
There he is! Your very own zombie! We'll need to move quickly
now. Zombies don't last forever...the clock is ticking. Do the
following to get him to follow you:
%^GREEN%^speak in english%^RESET%^
%^GREEN%^say follow cratylus%^RESET%^
Except, of course, instead of \"cratylus\", put in your own name.
Note something important here. Zombies are obedient, but not
smart. If someone else starts saying things that the zombie understands
as a command, it will start obeying them. So remember that while
you created this zombie, someone else can steal him if you're not
careful.
Now, return to the well (4n) then enter the well and go west.
You'll need to tell the zombie to leave the water lock and operate it:
%^GREEN%^say go east%^RESET%^
%^GREEN%^yell push lever%^RESET%^
%^GREEN%^enter mech%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^direct mech to open grate%^RESET%^
Woohoo! If you don't have the mech, of course, you just
walk your own self west, drink your strength potions, and
open the grate yourself. If you *do* have the mech, let's get it out
of harm's way...
%^GREEN%^drive east%^RESET%^
%^GREEN%^out%^RESET%^
%^GREEN%^go west%^RESET%^
All righty, now let's go resolve this well problem. Go down
and you'll see the metal wheel and the water pipe. To get the water
flowing again, turn the wheel. Then very quickly get yourself back to
the water lock...the area will fill with water soon! When water
gets to the lock: %^GREEN%^yell pull lever%^RESET%^
Your zombie will then open the east door, allowing you and the water to
get to the well, and solving the Town Well Quest. Congratulations!
",({"chapter 1","chapter one","1",}):"chapter 1 \"Getting started: The Newbie Mansion\"
You understand the basics. You've read the player's
handbook (you read that, right?), you now understand stats
and skills, you've customized your stats, and you're
ready to play. This chapter will now give you a walkthrough
of the first quest a newbie should perform. It is a *complete*
walkthrough! It is a SPOILER. If you don't want the quest
spoiled, stop reading now!
I'm going to assume your mud does not allow picking your
class at char creation, or that the class you picked is
\"explorer\". This is to simplify the walkthrough. This also
assumes you've just created this char and it is level 1.
We'll start in the reception room, with Jennybot (5 south,
3 east, 1 south, of the village clocktower). If you haven't
done so already, this might be a good time to:
%^GREEN%^activate bot%^RESET%^
What she says is kind of boring if you're not new to muds.
But hang around until she gives you the baseball cap. It's
worth some money.
%^RED%^PROTIP%^RESET%^: If she has already given you a cap before,
you won't get a new one.
Once you get the cap, go 1n, 3w, 5n, 1e, 1n. This is the village
shop. Remove and drop your worthless t-shirt and jeans,
and sell the cap to otik:
%^GREEN%^sell cap to otik%^RESET%^
Sell any other crap you've picked up along the way. The newbie
mansion will have all the equipment you'll need to solve it.
But keep this hint book handy!
Now it's time to open a bank account. Believe me, you do not
want to be carrying around all your money if you die. So let's
go open an account and put our hard-earned cash somewhere safe:
1s, 1w, 3n, 1e.
%^GREEN%^request account from zoe%^RESET%^
%^GREEN%^deposit all%^RESET%^
Good! I feel safer already! Now let's head to the mansion.
From the bank it is: 1w, 3s, 2w, 2s. If you're still at newbie
levels, the mansion guard won't notice you slip by.
You're now in front of the mansion, but how to get in? The door
is locked! Pay close attention to the description...did you
notice that the mansion is described as having an open
second story window?
Go to the gardener's shack (1w, 1n) and get the ladder. Return
to the front of the mansion (1s, 1e) and:
%^GREEN%^drop ladder%^RESET%^
%^GREEN%^climb ladder%^RESET%^
You're in! You're now standing in the second floor hallway of
the newbie mansion. Time to get equipped. Go east into the
guest room, open the overnight bag, and get and wear the
following: leather jacket, leather pants, helmet, shirt, and gloves.
This is relatively decent newbie armor that still lets you
gather loot because it's not too heavy.
%^RED%^PROTIP:%^RESET%^ You can do stuff to more than one
item at a time, and when you do, worn items are ignored. Try:
%^GREEN%^open overnight bag%^RESET%^
%^GREEN%^get all from overnight bag%^RESET%^
%^GREEN%^wear all%^RESET%^
%^GREEN%^drop all%^RESET%^
Did you notice that you only dropped the things you were not wearing?
Now, go to the master bedroom (1w, 3s), open the wardrobe and
get the boots from the wardrobe and wear them. These are long
boots and are better than the boots in the guest room. These
boots also protect your legs.
Leave the rest of the stuff in the wardrobe alone for now...you
can loot the mansion after you complete the quest. Next, let's
get you a weapon. Go downstairs, just outside the kitchen (1n, 1d,
1n). The kitchen will be east. Do this, very quickly:
%^GREEN%^go east%^RESET%^
%^GREEN%^get carving knife from rack%^RESET%^
%^GREEN%^go west%^RESET%^
Ignore the kitchen rats...kill them later if you want, for now
let's save up your strength for the mansion boss...and he's
next! Before we go on, though, let me ask you...did you get
both knives from the rack? No! just the carving knife! You see,
only fighters are skilled at using two weapons at once. As
an explorer, if you try to wield two weapons at once, you
will do very very badly in combat. The carving knife is an
excellent weapon. Just wield that, and drop the butcher knife.
Now go north and east...and meet your first quest boss. Yes,
the soggy thief! He's snuck into the mansion while the master
was away, and made himself at home! He won't like your
presence and will attack you, but he's really quite wimpy.
Just stand your ground and you'll defeat him with no effort.
Finished? Aren't you sad now? No? Good. There's plenty of
slaying to do in this game, if you're getting sentimental over
killing a burglar while wearing his armor (that you stole) then
this just might not be the game for you. Now, take his stuff
and look around. See anything that piques your curiosity?
%^GREEN%^get all from corpse%^RESET%^
%^GREEN%^look%^RESET%^
Maybe the rug is valuable...
%^GREEN%^exa rug%^RESET%^
%^GREEN%^get rug%^RESET%^
Weird! What if we...
%^GREEN%^move rug%^RESET%^
Well what do you know! Open that trap door, go down into
the secret chamber, and you'll have solved your first Dead Souls
quest. Congratulations! You'll be awarded quest and experience
points, and if your mud is set to auto-advance, you'll
probably gain a level or two. If not, you can visit Dirk and
ask him to advance (Dirk is 1e, 1s of the village clocktower).
There's a bunch of loot in the chest in the secret chamber, as well
as a bag with money (don't just sell the bag, \"get all from bag\"
first!). You can now focus on robbing the mansion blind and
selling everything you steal. With the money, you can start buying
expensive equipment you need for the more challenging quests.
%^RED%^Loot tip 1:%^RESET%^ Go back to the master bedroom and move the bed.
Surprise! Enter the passageway and unlock the safe with the complex key
you took from the thief. Inside the safe you'll find stuff you
will want to keep, not sell. Trust me on that. Don't sell that stuff.
%^RED%^Loot tip 2:%^RESET%^ Search the room across the hall from the guest
room you looted.
%^RED%^Loot tip 3:%^RESET%^ Whether you sell or keep it, remember where the
bear costume is. It could come in handy later.
%^RED%^Loot tip 4:%^RESET%^ Sell stuff to Kim if you can, rather than Otik. Otik pays
with silver, which is heavy. Kim pays with dollars, which are light. Sadly,
Kim will not buy or sell weapons, but for everything else, her light
currency is very convenient. You can always exchange currencies at
the bank. Kim's bookstore is 1n, 2e, 1n of Jennybot.
%^RED%^Loot tip 5:%^RESET%^ If you advanced automatically, you might find that the mansion
guard no longer lets you past him any more...and you can't get rid of him.
Strategies for dealing with the gate guard are discussed later.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Post-Mansion Hints\"
So you're flush with joy at having solved your first quest,
and now you're loaded with money and loot! Woo! But...oh no...
there's so much loot left in the mansion, and the gate gard won't
let you pass because you're not a newbie anymore. Bummer! Well,
we're going to fix that, but first, let's get organized. You're
carrying a bunch of stuff, some of which you want to keep, some
of which you want to sell, and it's going to get tiresome soon,
having to sort through your inventory all the time at shops. So
the first thing we're going to do is buy a rucksack.
Why?
Remember when you tried to \"drop all\" and the only things
you didn't drop were the things you were wearing? A rucksack
gives you somewhere to keep a bunch of stuff, and you *wear*
the rucksack. Meaning that \"drop all\" and \"sell all\" will
ignore the valuable stuff you keep in your rucksack, if
you're wearing it. Neat, huh? It makes life much easier when
selling tons of loot.
So, let's go to Otik (from the gate guard, 1n, 3e, 1n), and
buy the rucksack. Try:
%^GREEN%^buy ruck from otik%^RESET%^
If you dont have enough money, sell some of the junk you got
from the mansion. It also could be that you *do* have enough money,
but not in silver. If this is the case, go to zoe and do a
currency exchange:
%^GREEN%^exchange 5 gold for silver%^RESET%^
Note that you can only exchange money in your possession. Zoe
does not exchange money while it is deposited.
Once you have your rucksack, wear it, open it, and put in it
the stuff you want to keep. I suggest putting the grenade, the
pistol, and the slips in there. Now sell the junk you don't
want, deposit the excess cash with Zoe, and let's get ready
to deal with the gate guard.
The problem: A guard you cannot kill, preventing passage.
The solution: Find a way around, or through him.
Now, you might be tempted to use the grenade. You *could* pull
the pin on the grenade, drop it, leave, wait a few seconds, and
come back...and the guard *might* be dead. But this is a magical
guard. Even if he dies, his spirit prevents you from getting
past. And he will regenerate into a new body within seconds. So...
what to do? There are two solutions, and both of them require...
SCIENCE!
Go to Zoe and withdraw 110 dollars or so (darn fees), then
go to the bookstore (1w, 8s, 5e, 1n), and buy the lab coat from Kim.
Now go to the campus stargate lab (1s, 5w, 1s, 1w, 1n), where Kleiner
is busy working on exotechnology, as usual. In typical egghead
fashion, Kleiner is too preoccupied with his experiments to
help you, but he wants *your* help. He's lost his lab coat, you see.
Like so many geniuses, Kleiner is somewhat physically clumsy,
and if he wears a lab coat you give him, he's liable to drop
his identification badge. This badge gives its carrier access
to the \"hazardous technologies\" lab two rooms south of Kleiner.
%^RED%^PROTIP%^RESET%^: Do not attempt to kill Kleiner. He is carrying a
teleportation device called an omni which he will use the moment he
receives harm from someone. Attacking Kleiner just means he and
his ID badge will teleport to some random location, and you'll be
up the creek.
%^RED%^PROTIP%^RESET%^: In the hazardous materials lab, DO NOT attempt to harm
the tripod gun turret. Failure to heed this warning will result in
catastrophic injury to anyone in the room.
On the hazlab workbench you'll find various items of interest. For
now we'll concentrate on the Yautja wrist computer, the Yautja data
module, and the rocket pack. The rocket pack is the simplest tool for
circumventing the gate guard. Wear it, and head back to the guard
(1n, 1e, 6n, 2w, 1s). Now use the pack to fly over the guard:
%^GREEN%^activate pack%^RESET%^
%^GREEN%^boost up%^RESET%^
%^GREEN%^boost south%^RESET%^
%^GREEN%^boost down%^RESET%^
%^GREEN%^deactivate pack%^RESET%^
How do ya like that! Remember to deactivate the pack, too. If you
leave it running, it'll eventually run out of fuel. Don't get too
crazy flying around with it, either. You're extremely likely to boost
yourself out over the ocean if you're not careful, and wind up
lost and drowning. Also, if you run out of fuel while up in the
air, you *will* fall and the damage you'll receive will be grievous.
So let's be very careful in our use of the rocket pack.
Another way to get past the guard is by having him not see you at all.
The Yautja technology can help you in this:
%^GREEN%^wear wristcomp%^RESET%^
%^GREEN%^open wristcomp%^RESET%^
%^GREEN%^activate wristcomp%^RESET%^
%^GREEN%^install module in wristcomp%^RESET%^
%^GREEN%^close wristcomp%^RESET%^
%^GREEN%^cloak%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^decloak%^RESET%^
It is crucially important that you are cloaked only as long as
necessary. If you walk around while cloaked, it will sap your stamina
extremely quickly. Note that it is a peculiarity of Yautja technology
that it's a bit awkward to use. If you remove the wristcomp, it
deactivates, and you'll need to wear it, activate it again, uninstall
the module, and install it again. Those Yautja hunters always do
things the hard way, I'll tellya.
You now have the tools you need to successfully steal every
ounce of worth from the newbie mansion regardless of your
newbie status. Congratulations...I guess.
",({"chapter 3","chapter three","3",}):"chapter 3 \"Teleportation Devices\"
Let's talk a little about how to use some of the teleportation
technology in the default Dead Souls distribution. Some of
this stuff can save your hide, and some of it can kill you,
so it's worth being aware of it.
%^CYAN%^Visitor's Pass%^RESET%^
This is an extremely rare item, and only intended for official test
characters. If you don't show up in the \"who\" list with a [%^YELLOW%^TEST%^RESET%^]
tag, and you have a visitor's pass, something has gone wrong. You
use the pass to get out of trouble with the command: %^GREEN%^click heels%^RESET%^
%^CYAN%^omni%^RESET%^
The omni is a device created by a consortium of time travelers dedicated
to fixing errors in history. While they'd never make the full chrono-omni
available for public consuption, a \"gimped\" version can be purchased
from Oana at the magic shop. This omni does not do time travel, and when
you press the button on it, it will only take you to one location: Lars's
Pub. However, it is an incredibly valuable tool of recall when you find
yourself in a tight spot. For example, suppose you are an orc or a dwarf,
and you cannot swim, but you walked off the village shore and are now
drowning and sinking to the sea floor. If you have an omni in your
rucksack, you can:
%^GREEN%^get omi from ruck%^RESET%^
%^GREEN%^push button on omni%^RESET%^
And materialize, gasping but alive, in the old tavern. It's a must-have
for any adventurer. I suggest aliasing those commands, so you can
hit the buttons quickly in panic-mode. See: %^GREEN%^help alias%^RESET%^
%^RED%^PROTIP:%^RESET%^ The omni is programmed not to be in the possession of a
living being for very long. If you keep it in your inventory too long, it
will glow red and disappear. You can limit this by keeping it in your ruck.
%^CYAN%^Kleiner's omni%^RESET%^
Kleiner, brilliant scientist he is, has been tinkering with his omni
and disabled the auto-vanish feature for himself on it. He also has been
able to reprogram his omni to take him to different places. However,
you really don't want this omni. Only Kleiner can control where it takes
him. For anyone else, it takes them to a completely random location
on the mud, and believe me when I tell you this is not really as
fun as it sounds.
%^CYAN%^Stargates%^RESET%^
Ancient technology of a long-lost civilization, stargates permit travel
to and from each other. You dial a specific stargate you want to go to,
then enter the stargate. Note that if the destination stargate is already
in use the dialing process may fail. Stargates stay open for only a brief
time, though, so unless your mud is super busy, you can probably just
retry the dialing and find success.
Note that stargates can take you to places whose environment is unsuitable
to you. If you dial a stargate that is on the sea floor or one that is in
outer space, you'd best be wearing a breathing device or be carrying
an omni with you. Of course, if on your mud someone has coded a stargate
whose destination is next to a sun going nova, or in a nest of gundarks,
the breathing device may provide insufficient protection
%^CYAN%^Portal gun%^RESET%^
If it has both blue and orange vials installed, this device allows you to
create portals (one blue, one orange) that connect to each other. Entering
the blue portal will take you to the location of the orange portal, and
vice versa. Only one blue portal may exist in the game at any time...the
creation of a new one destroys the old one. This is also true of the
orange portal. Portal locations persist through reboots. By default, the
blue vial is found along with the portal gun, in the hazlab. The orange
vial is rather trickier to obtain...it is kept in the private storage area
of an incredibly powerful and aggressive cave troll.
You can create a blue portal wherever you want if you have the blue vial
installed, but without the orange vial you cannot change the location of
the orange portal.
%^RED%^Protip:%^RESET%^ Do not ever enter a portal if the other portal is
in the same room.
",({"chapter 5","chapter five","5",}):"chapter 5 \"The Orcslayer\"
You're getting spoiled, aren't you? You're achieving dizzying
heights of player leveling in no time, and learning tons about
how to fight, move, and operate in the Dead Souls environment.
Well, sorry to say, we are close to the end of your free ride.
The orcslayer quest is the last one you get a full walkthrough
on. After this, you'll need to apply the lessons you've learned
from this hint book to whatever other quests and adventures your
mud features.
Before we really get started...do you remember what you did with
that bear costume? I ask because orcs are known to be quite
deferential to bears. Keep that in mind, if your player race
is not \"orc\".
Now, Leo the Archwizard misplaced his precious magical sword, Orcslayer.
Leo is in the basement of the village church (1w, 1n of the clocktower)
waiting for you to help him find it. Little does he know that some
juvenile orcs, trying to prove their mettle, stole it and have given
it to their wanna-be shaman in their improvised lair (4w, 1sw, 1w,
1n, 1w of the clocktower).
There are a few ways you can handle the orcs between you and the shaman.
If you decide to do it the hard way, let me remind you that Herkimer
can sometimes teach you some spells even if you are not a mage. In particular
the buffer spell can pro
+++
ATH0
NO CARRIER
",({"chapter 1","chapter one","1",}):"chapter 1 \"Getting started: The Newbie Mansion\"
You understand the basics. You've read the player's
handbook (you read that, right?), you now understand stats
and skills, you've customized your stats, and you're
ready to play. This chapter will now give you a walkthrough
of the first quest a newbie should perform. It is a *complete*
walkthrough! It is a SPOILER. If you don't want the quest
spoiled, stop reading now!
I'm going to assume your mud does not allow picking your
class at char creation, or that the class you picked is
\"explorer\". This is to simplify the walkthrough. This also
assumes you've just created this char and it is level 1.
We'll start in the reception room, with Jennybot (5 south,
3 east, 1 south, of the village clocktower). If you haven't
done so already, this might be a good time to:
%^GREEN%^activate bot%^RESET%^
What she says is kind of boring if you're not new to muds.
But hang around until she gives you the baseball cap. It's
worth some money.
%^RED%^PROTIP%^RESET%^: If she has already given you a cap before,
you won't get a new one.
Once you get the cap, go 1n, 3w, 5n, 1e, 1n. This is the village
shop. Remove and drop your worthless t-shirt and jeans,
and sell the cap to otik:
%^GREEN%^sell cap to otik%^RESET%^
Sell any other crap you've picked up along the way. The newbie
mansion will have all the equipment you'll need to solve it.
But keep this hint book handy!
Now it's time to open a bank account. Believe me, you do not
want to be carrying around all your money if you die. So let's
go open an account and put our hard-earned cash somewhere safe:
1s, 1w, 3n, 1e.
%^GREEN%^request account from zoe%^RESET%^
%^GREEN%^deposit all%^RESET%^
Good! I feel safer already! Now let's head to the mansion.
From the bank it is: 1w, 3s, 2w, 2s. If you're still at newbie
levels, the mansion guard won't notice you slip by.
You're now in front of the mansion, but how to get in? The door
is locked! Pay close attention to the description...did you
notice that the mansion is described as having an open
second story window?
Go to the gardener's shack (1w, 1n) and get the ladder. Return
to the front of the mansion (1s, 1e) and:
%^GREEN%^drop ladder%^RESET%^
%^GREEN%^climb ladder%^RESET%^
You're in! You're now standing in the second floor hallway of
the newbie mansion. Time to get equipped. Go east into the
guest room, open the overnight bag, and get and wear the
following: leather jacket, leather pants, helmet, shirt, and gloves.
This is relatively decent newbie armor that still lets you
gather loot because it's not too heavy.
%^RED%^PROTIP:%^RESET%^ You can do stuff to more than one
item at a time, and when you do, worn items are ignored. Try:
%^GREEN%^open overnight bag%^RESET%^
%^GREEN%^get all from overnight bag%^RESET%^
%^GREEN%^wear all%^RESET%^
%^GREEN%^drop all%^RESET%^
Did you notice that you only dropped the things you were not wearing?
Now, go to the master bedroom (1w, 3s), open the wardrobe and
get the boots from the wardrobe and wear them. These are long
boots and are better than the boots in the guest room. These
boots also protect your legs.
Leave the rest of the stuff in the wardrobe alone for now...you
can loot the mansion after you complete the quest. Next, let's
get you a weapon. Go downstairs, just outside the kitchen (1n, 1d,
1n). The kitchen will be east. Do this, very quickly:
%^GREEN%^go east%^RESET%^
%^GREEN%^get carving knife from rack%^RESET%^
%^GREEN%^go west%^RESET%^
Ignore the kitchen rats...kill them later if you want, for now
let's save up your strength for the mansion boss...and he's
next! Before we go on, though, let me ask you...did you get
both knives from the rack? No! just the carving knife! You see,
only fighters are skilled at using two weapons at once. As
an explorer, if you try to wield two weapons at once, you
will do very very badly in combat. The carving knife is an
excellent weapon. Just wield that, and drop the butcher knife.
Now go north and east...and meet your first quest boss. Yes,
the soggy thief! He's snuck into the mansion while the master
was away, and made himself at home! He won't like your
presence and will attack you, but he's really quite wimpy.
Just stand your ground and you'll defeat him with no effort.
Finished? Aren't you sad now? No? Good. There's plenty of
slaying to do in this game, if you're getting sentimental over
killing a burglar while wearing his armor (that you stole) then
this just might not be the game for you. Now, take his stuff
and look around. See anything that piques your curiosity?
%^GREEN%^get all from corpse%^RESET%^
%^GREEN%^look%^RESET%^
Maybe the rug is valuable...
%^GREEN%^exa rug%^RESET%^
%^GREEN%^get rug%^RESET%^
Weird! What if we...
%^GREEN%^move rug%^RESET%^
Well what do you know! Open that trap door, go down into
the secret chamber, and you'll have solved your first Dead Souls
quest. Congratulations! You'll be awarded quest and experience
points, and if your mud is set to auto-advance, you'll
probably gain a level or two. If not, you can visit Dirk and
ask him to advance (Dirk is 1e, 1s of the village clocktower).
There's a bunch of loot in the chest in the secret chamber, as well
as a bag with money (don't just sell the bag, \"get all from bag\"
first!). You can now focus on robbing the mansion blind and
selling everything you steal. With the money, you can start buying
expensive equipment you need for the more challenging quests.
%^RED%^Loot tip 1:%^RESET%^ Go back to the master bedroom and move the bed.
Surprise! Enter the passageway and unlock the safe with the complex key
you took from the thief. Inside the safe you'll find stuff you
will want to keep, not sell. Trust me on that. Don't sell that stuff.
%^RED%^Loot tip 2:%^RESET%^ Search the room across the hall from the guest
room you looted.
%^RED%^Loot tip 3:%^RESET%^ Whether you sell or keep it, remember where the
bear costume is. It could come in handy later.
%^RED%^Loot tip 4:%^RESET%^ Sell stuff to Kim if you can, rather than Otik. Otik pays
with silver, which is heavy. Kim pays with dollars, which are light. Sadly,
Kim will not buy or sell weapons, but for everything else, her light
currency is very convenient. You can always exchange currencies at
the bank. Kim's bookstore is 1n, 2e, 1n of Jennybot.
%^RED%^Loot tip 5:%^RESET%^ If you advanced automatically, you might find that the mansion
guard no longer lets you past him any more...and you can't get rid of him.
Strategies for dealing with the gate guard are discussed later.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Post-Mansion Hints\"
So you're flush with joy at having solved your first quest,
and now you're loaded with money and loot! Woo! But...oh no...
there's so much loot left in the mansion, and the gate gard won't
let you pass because you're not a newbie anymore. Bummer! Well,
we're going to fix that, but first, let's get organized. You're
carrying a bunch of stuff, some of which you want to keep, some
of which you want to sell, and it's going to get tiresome soon,
having to sort through your inventory all the time at shops. So
the first thing we're going to do is buy a rucksack.
Why?
Remember when you tried to \"drop all\" and the only things
you didn't drop were the things you were wearing? A rucksack
gives you somewhere to keep a bunch of stuff, and you *wear*
the rucksack. Meaning that \"drop all\" and \"sell all\" will
ignore the valuable stuff you keep in your rucksack, if
you're wearing it. Neat, huh? It makes life much easier when
selling tons of loot.
So, let's go to Otik (from the gate guard, 1n, 3e, 1n), and
buy the rucksack. Try:
%^GREEN%^buy ruck from otik%^RESET%^
If you dont have enough money, sell some of the junk you got
from the mansion. It also could be that you *do* have enough money,
but not in silver. If this is the case, go to zoe and do a
currency exchange:
%^GREEN%^exchange 5 gold for silver%^RESET%^
Note that you can only exchange money in your possession. Zoe
does not exchange money while it is deposited.
Once you have your rucksack, wear it, open it, and put in it
the stuff you want to keep. I suggest putting the grenade, the
pistol, and the slips in there. Now sell the junk you don't
want, deposit the excess cash with Zoe, and let's get ready
to deal with the gate guard.
The problem: A guard you cannot kill, preventing passage.
The solution: Find a way around, or through him.
Now, you might be tempted to use the grenade. You *could* pull
the pin on the grenade, drop it, leave, wait a few seconds, and
come back...and the guard *might* be dead. But this is a magical
guard. Even if he dies, his spirit prevents you from getting
past. And he will regenerate into a new body within seconds. So...
what to do? There are two solutions, and both of them require...
SCIENCE!
Go to Zoe and withdraw 110 dollars or so (darn fees), then
go to the bookstore (1w, 8s, 5e, 1n), and buy the lab coat from Kim.
Now go to the campus stargate lab (1s, 5w, 1s, 1w, 1n), where Kleiner
is busy working on exotechnology, as usual. In typical egghead
fashion, Kleiner is too preoccupied with his experiments to
help you, but he wants *your* help. He's lost his lab coat, you see.
Like so many geniuses, Kleiner is somewhat physically clumsy,
and if he wears a lab coat you give him, he's liable to drop
his identification badge. This badge gives its carrier access
to the \"hazardous technologies\" lab two rooms south of Kleiner.
%^RED%^PROTIP%^RESET%^: Do not attempt to kill Kleiner. He is carrying a
teleportation device called an omni which he will use the moment he
receives harm from someone. Attacking Kleiner just means he and
his ID badge will teleport to some random location, and you'll be
up the creek.
%^RED%^PROTIP%^RESET%^: In the hazardous materials lab, DO NOT attempt to harm
the tripod gun turret. Failure to heed this warning will result in
catastrophic injury to anyone in the room.
On the hazlab workbench you'll find various items of interest. For
now we'll concentrate on the Yautja wrist computer, the Yautja data
module, and the rocket pack. The rocket pack is the simplest tool for
circumventing the gate guard. Wear it, and head back to the guard
(1n, 1e, 6n, 2w, 1s). Now use the pack to fly over the guard:
%^GREEN%^activate pack%^RESET%^
%^GREEN%^boost up%^RESET%^
%^GREEN%^boost south%^RESET%^
%^GREEN%^boost down%^RESET%^
%^GREEN%^deactivate pack%^RESET%^
How do ya like that! Remember to deactivate the pack, too. If you
leave it running, it'll eventually run out of fuel. Don't get too
crazy flying around with it, either. You're extremely likely to boost
yourself out over the ocean if you're not careful, and wind up
lost and drowning. Also, if you run out of fuel while up in the
air, you *will* fall and the damage you'll receive will be grievous.
So let's be very careful in our use of the rocket pack.
Another way to get past the guard is by having him not see you at all.
The Yautja technology can help you in this:
%^GREEN%^wear wristcomp%^RESET%^
%^GREEN%^open wristcomp%^RESET%^
%^GREEN%^activate wristcomp%^RESET%^
%^GREEN%^install module in wristcomp%^RESET%^
%^GREEN%^close wristcomp%^RESET%^
%^GREEN%^cloak%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^decloak%^RESET%^
It is crucially important that you are cloaked only as long as
necessary. If you walk around while cloaked, it will sap your stamina
extremely quickly. Note that it is a peculiarity of Yautja technology
that it's a bit awkward to use. If you remove the wristcomp, it
deactivates, and you'll need to wear it, activate it again, uninstall
the module, and install it again. Those Yautja hunters always do
things the hard way, I'll tellya.
You now have the tools you need to successfully steal every
ounce of worth from the newbie mansion regardless of your
newbie status. Congratulations...I guess.
",({"chapter 3","chapter three","3",}):"chapter 3 \"Teleportation Devices\"
Let's talk a little about how to use some of the teleportation
technology in the default Dead Souls distribution. Some of
this stuff can save your hide, and some of it can kill you,
so it's worth being aware of it.
%^CYAN%^Visitor's Pass%^RESET%^
This is an extremely rare item, and only intended for official test
characters. If you don't show up in the \"who\" list with a [%^YELLOW%^TEST%^RESET%^]
tag, and you have a visitor's pass, something has gone wrong. You
use the pass to get out of trouble with the command: %^GREEN%^click heels%^RESET%^
%^CYAN%^omni%^RESET%^
The omni is a device created by a consortium of time travelers dedicated
to fixing errors in history. While they'd never make the full chrono-omni
available for public consuption, a \"gimped\" version can be purchased
from Oana at the magic shop. This omni does not do time travel, and when
you press the button on it, it will only take you to one location: Lars's
Pub. However, it is an incredibly valuable tool of recall when you find
yourself in a tight spot. For example, suppose you are an orc or a dwarf,
and you cannot swim, but you walked off the village shore and are now
drowning and sinking to the sea floor. If you have an omni in your
rucksack, you can:
%^GREEN%^get omi from ruck%^RESET%^
%^GREEN%^push button on omni%^RESET%^
And materialize, gasping but alive, in the old tavern. It's a must-have
for any adventurer. I suggest aliasing those commands, so you can
hit the buttons quickly in panic-mode. See: %^GREEN%^help alias%^RESET%^
%^RED%^PROTIP:%^RESET%^ The omni is programmed not to be in the possession of a
living being for very long. If you keep it in your inventory too long, it
will glow red and disappear. You can limit this by keeping it in your ruck.
%^CYAN%^Kleiner's omni%^RESET%^
Kleiner, brilliant scientist he is, has been tinkering with his omni
and disabled the auto-vanish feature for himself on it. He also has been
able to reprogram his omni to take him to different places. However,
you really don't want this omni. Only Kleiner can control where it takes
him. For anyone else, it takes them to a completely random location
on the mud, and believe me when I tell you this is not really as
fun as it sounds.
%^CYAN%^Stargates%^RESET%^
Ancient technology of a long-lost civilization, stargates permit travel
to and from each other. You dial a specific stargate you want to go to,
then enter the stargate. Note that if the destination stargate is already
in use the dialing process may fail. Stargates stay open for only a brief
time, though, so unless your mud is super busy, you can probably just
retry the dialing and find success.
Note that stargates can take you to places whose environment is unsuitable
to you. If you dial a stargate that is on the sea floor or one that is in
outer space, you'd best be wearing a breathing device or be carrying
an omni with you. Of course, if on your mud someone has coded a stargate
whose destination is next to a sun going nova, or in a nest of gundarks,
the breathing device may provide insufficient protection
%^CYAN%^Portal gun%^RESET%^
If it has both blue and orange vials installed, this device allows you to
create portals (one blue, one orange) that connect to each other. Entering
the blue portal will take you to the location of the orange portal, and
vice versa. Only one blue portal may exist in the game at any time...the
creation of a new one destroys the old one. This is also true of the
orange portal. Portal locations persist through reboots. By default, the
blue vial is found along with the portal gun, in the hazlab. The orange
vial is rather trickier to obtain...it is kept in the private storage area
of an incredibly powerful and aggressive cave troll.
You can create a blue portal wherever you want if you have the blue vial
installed, but without the orange vial you cannot change the location of
the orange portal.
%^RED%^Protip:%^RESET%^ Do not ever enter a portal if the other portal is
in the same room.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Getting started: The Newbie Mansion\"
You understand the basics. You've read the player's
handbook (you read that, right?), you now understand stats
and skills, you've customized your stats, and you're
ready to play. This chapter will now give you a walkthrough
of the first quest a newbie should perform. It is a *complete*
walkthrough! It is a SPOILER. If you don't want the quest
spoiled, stop reading now!
I'm going to assume your mud does not allow picking your
class at char creation, or that the class you picked is
\"explorer\". This is to simplify the walkthrough. This also
assumes you've just created this char and it is level 1.
We'll start in the reception room, with Jennybot (5 south,
3 east, 1 south, of the village clocktower). If you haven't
done so already, this might be a good time to:
%^GREEN%^activate bot%^RESET%^
What she says is kind of boring if you're not new to muds.
But hang around until she gives you the baseball cap. It's
worth some money.
%^RED%^PROTIP%^RESET%^: If she has already given you a cap before,
you won't get a new one.
Once you get the cap, go 1n, 3w, 5n, 1e, 1n. This is the village
shop. Remove and drop your worthless t-shirt and jeans,
and sell the cap to otik:
%^GREEN%^sell cap to otik%^RESET%^
Sell any other crap you've picked up along the way. The newbie
mansion will have all the equipment you'll need to solve it.
But keep this hint book handy!
Now it's time to open a bank account. Believe me, you do not
want to be carrying around all your money if you die. So let's
go open an account and put our hard-earned cash somewhere safe:
1s, 1w, 3n, 1e.
%^GREEN%^request account from zoe%^RESET%^
%^GREEN%^deposit all%^RESET%^
Good! I feel safer already! Now let's head to the mansion.
From the bank it is: 1w, 3s, 2w, 2s. If you're still at newbie
levels, the mansion guard won't notice you slip by.
You're now in front of the mansion, but how to get in? The door
is locked! Pay close attention to the description...did you
notice that the mansion is described as having an open
second story window?
Go to the gardener's shack (1w, 1n) and get the ladder. Return
to the front of the mansion (1s, 1e) and:
%^GREEN%^drop ladder%^RESET%^
%^GREEN%^climb ladder%^RESET%^
You're in! You're now standing in the second floor hallway of
the newbie mansion. Time to get equipped. Go east into the
guest room, open the overnight bag, and get and wear the
following: leather jacket, leather pants, helmet, shirt, and gloves.
This is relatively decent newbie armor that still lets you
gather loot because it's not too heavy.
%^RED%^PROTIP:%^RESET%^ You can do stuff to more than one
item at a time, and when you do, worn items are ignored. Try:
%^GREEN%^open overnight bag%^RESET%^
%^GREEN%^get all from overnight bag%^RESET%^
%^GREEN%^wear all%^RESET%^
%^GREEN%^drop all%^RESET%^
Did you notice that you only dropped the things you were not wearing?
Now, go to the master bedroom (1w, 3s), open the wardrobe and
get the boots from the wardrobe and wear them. These are long
boots and are better than the boots in the guest room. These
boots also protect your legs.
Leave the rest of the stuff in the wardrobe alone for now...you
can loot the mansion after you complete the quest. Next, let's
get you a weapon. Go downstairs, just outside the kitchen (1n, 1d,
1n). The kitchen will be east. Do this, very quickly:
%^GREEN%^go east%^RESET%^
%^GREEN%^get carving knife from rack%^RESET%^
%^GREEN%^go west%^RESET%^
Ignore the kitchen rats...kill them later if you want, for now
let's save up your strength for the mansion boss...and he's
next! Before we go on, though, let me ask you...did you get
both knives from the rack? No! just the carving knife! You see,
only fighters are skilled at using two weapons at once. As
an explorer, if you try to wield two weapons at once, you
will do very very badly in combat. The carving knife is an
excellent weapon. Just wield that, and drop the butcher knife.
Now go north and east...and meet your first quest boss. Yes,
the soggy thief! He's snuck into the mansion while the master
was away, and made himself at home! He won't like your
presence and will attack you, but he's really quite wimpy.
Just stand your ground and you'll defeat him with no effort.
Finished? Aren't you sad now? No? Good. There's plenty of
slaying to do in this game, if you're getting sentimental over
killing a burglar while wearing his armor (that you stole) then
this just might not be the game for you. Now, take his stuff
and look around. See anything that piques your curiosity?
%^GREEN%^get all from corpse%^RESET%^
%^GREEN%^look%^RESET%^
Maybe the rug is valuable...
%^GREEN%^exa rug%^RESET%^
%^GREEN%^get rug%^RESET%^
Weird! What if we...
%^GREEN%^move rug%^RESET%^
Well what do you know! Open that trap door, go down into
the secret chamber, and you'll have solved your first Dead Souls
quest. Congratulations! You'll be awarded quest and experience
points, and if your mud is set to auto-advance, you'll
probably gain a level or two. If not, you can visit Dirk and
ask him to advance (Dirk is 1e, 1s of the village clocktower).
There's a bunch of loot in the chest in the secret chamber, as well
as a bag with money (don't just sell the bag, \"get all from bag\"
first!). You can now focus on robbing the mansion blind and
selling everything you steal. With the money, you can start buying
expensive equipment you need for the more challenging quests.
%^RED%^Loot tip 1:%^RESET%^ Go back to the master bedroom and move the bed.
Surprise! Enter the passageway and unlock the safe with the complex key
you took from the thief. Inside the safe you'll find stuff you
will want to keep, not sell. Trust me on that. Don't sell that stuff.
%^RED%^Loot tip 2:%^RESET%^ Search the room across the hall from the guest
room you looted.
%^RED%^Loot tip 3:%^RESET%^ Whether you sell or keep it, remember where the
bear costume is. It could come in handy later.
%^RED%^Loot tip 4:%^RESET%^ Sell stuff to Kim if you can, rather than Otik. Otik pays
with silver, which is heavy. Kim pays with dollars, which are light. Sadly,
Kim will not buy or sell weapons, but for everything else, her light
currency is very convenient. You can always exchange currencies at
the bank. Kim's bookstore is 1n, 2e, 1n of Jennybot.
%^RED%^Loot tip 5:%^RESET%^ If you advanced automatically, you might find that the mansion
guard no longer lets you past him any more...and you can't get rid of him.
Strategies for dealing with the gate guard are discussed later.
",({"chapter 4","chapter four","4",}):"chapter 4 \"The Town Well\"
Ok, so you're now all set with a bunch of money in the bank,
light-but-decent equipment, and a thirst for more advancement.
Let's try a quest that requires a little fancy footwork and
little (if any) fighting.
I'm going to assume that the portal gun portals are where the
default distribution puts them. The blue portal in the hazlab,
the orange portal...well, you'll see.
By now you should have Kleiner's badge. If not, go back to where I
describe how to get it. If you sold it, go buy it back. Also, get
yourself a flashlight. I recommend maglite, but whatever you can
find is better than nothing. Now go back to Jennybot. Near her is
a stairwell that leads to the campus basement. Turn on your
flashlight then go 1n, 1w, 1s, 1d, 1w of Jennybot. You're now in
the western end of the campus basement. Note the smudged wall?
%^GREEN%^push wall%^RESET%^
Whee! Go west until you can't go any further, then go 1s. Lying
there is a key you'll need. Put it in your ruck, then go north
til you can't go north any further. Note that the next couple of
things you need to do must be done quickly. You're about to enter
a dangerous sewer service area, and you may get blasted with harmful
steam if you hang around too long.
Go down into the sewer area. See that pile of crap? Search it. Bonus!
Now go east twice, up, open grate, up. You're now back up on the
cobblestone road, but with a shiny new ring to wear or sell, and
a key you'll need. Head to the hazlab (4s, 1w, 1s). Now, just in case
some joker put the orange portal somewhere dangerous, you should have
an omni handy (you can buy one from Oana). Enter the blue portal. Unless
someone's been playing around, you should now be in an underground
tunnel, with a large metal door to the west. There's not much to
explore here...if you follow the tunnel north and east, you'll find
a locked gate with a cave troll on the other side. What you really
want to do is get past the door to the west. You got the key to this door
from the campus underground, so go ahead and unlock it. Now comes the
hard part...open the door.
Chances are that you can't. It's huge and it's heavy...it requires having
a strength stat of at least 50 to open. You have 2 possible ways of dealing
with this...one is expensive, the other a bit tricky. The expensive way
is to buy strength potions from Oana and drink them until your strength
is above 49, then rush back to this door before the potions wear off,
and open the door. Like I said, expensive.
The other way is to control something that is strong enough to open the
door. Go back to kleiner, then type:
%^GREEN%^dial stargate lab%^RESET%^
This takes you to a secret area used by creators to test their npc's,
armor, and equipment. Go north, then east. If you're lucky, nobody has
taken the landstrider mech for a joyride and it's standing there, waiting
for you. Type: %^GREEN%^enter mech%^RESET%^
You are now inside your very own mech-type robot you control. Cool!
Have it go back to the stargate:
%^GREEN%^drive west%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^direct mech to dial campus lab%^RESET%^
%^GREEN%^direct mech to enter gate%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^drive south%^RESET%^
Uh oh! The mech can't enter the hazlab because it's not carrying Kleiner's
badge! What you need to do is drop the badge. It will then be in the mech's
inventory, so that the security door can see it.
%^GREEN%^drop badge%^RESET%^
%^GREEN%^drive south%^RESET%^
Now we have the mech enter the portal and open the door for you.
%^GREEN%^direct mech to enter portal%^RESET%^
%^GREEN%^direct mech to open door%^RESET%^
Excellent! Now, the next part involves making this area kind of
hostile for general use, so let's get the mech out of harm's way
%^GREEN%^get badge%^RESET%^
%^GREEN%^wear badge%^RESET%^
%^GREEN%^direct mech to enter portal%^RESET%^
%^GREEN%^out%^RESET%^
Now go back in the portal (just you, without the mech), and
go west twice. This is the source of the problem. The town well is dry
because someone shut off the water source!
To restore water to the well, turn the metal wheel here, and
water will start rushing out of the pipe at high pressure. There's a
grate-type door overhead through which the water is supposed to flow up to
the bottom of the well. If the orange portal hadn't been there, you'd have had
to do the alternate solution, which involves accessing the
water source from that other direction.
Now, as you can imagine, this area is going to fill up with water
very rapidly, so leave east right away. The entire series of tunnels
will flood, so get back to the orange portal and enter it quickly.
%^RED%^PROTIP:%^RESET%^ The water will also go through the locked cavetroll
gate. And cavetrolls don't breathe water.
Now, go to the town well (1n, 1e, 5n of the hazlab), and: %^GREEN%^enter well%^RESET%^
Hopefully you did get the maglite. By now the weaker flashlights may have
pooped out. See the lever on the wall? That operates a set of two doors that
serve as a waterlock for the well. Pull it, and a door will open, letting
water flood into the well, and solving the quest!
It may be that someone has already been fooling with the water lock, though.
If the door was already open when you got there, push the lever, wait a
few minutes for water to flood the lock, then pull the lever. It could
take a while depending on the water pressure for the lock to fill up, so
if it's dry, close it again and wait a while before opening again.
%^CYAN%^Alternate Resolution%^RESET%^
It may be that the orange portal was not in the water tunnel. It could
be that the hazardous materials labs was not accessible to you. If that
is the case, you'll need to solve this quest by accessing the water
source from the direction of the well. This is tricky because you need
to be very strong to open the grate, *and* you need an extra set of
hands to operate the water lock lever. Let's deal with one problem at a time.
First, the strength issue. On the other side of the water lock is a
grate that you must have at least 50 strength to open. If your strength
is already above 49, great. If not, you need to decide whether you want
to boost it with strength potions from Oana (expensive) or use the
landstrider mech (tricky). If the plan is to use strength potions, go buy
them now and stick them in your ruck. If the plan is to use the mech,
go get it. Go to Kleiner, and:
%^GREEN%^dial stargate lab%^RESET%^
%^GREEN%^enter gate%^RESET%^
%^GREEN%^go north%^RESET%^
%^GREEN%^go east%^RESET%^
%^GREEN%^enter mech%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^direct mech to dial campus lab%^RESET%^
%^GREEN%^direct mech to enter gate%^RESET%^
%^GREEN%^drive south%^RESET%^
%^GREEN%^drive east%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^drive north%^RESET%^
%^GREEN%^direct mech to enter well%^RESET%^
%^GREEN%^direct mech to pull lever%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^out%^RESET%^
%^GREEN%^go east%^RESET%^
The mech is now in the water lock. But neither you nor the mech
can proceed further west...you need someone to pull the lever
for you. You're going to need a servant...a slave...a ZOMBIE! Go
up and west, and buy a dark scroll from Oana. The scroll can be
used to raise a corpse, but you must be able to read English. If
you've chosen a race that doesn't speak English, you'll need to
learn it now. Go to Bugg (1n, 2e, 1n of the well) and ask him to
teach you English. Once you've learned it, you'll be ready to
create a zombie. From Bugg, go to the campus square (1s, 2w, 5s)
and murder Tim. It's important that you slay something that speaks
a language you know, so since you're either human or just now
learned English, a human victim is suitable. Plus Tim is a wuss.
Once he's slain: %^GREEN%^read scroll at tim%^RESET%^
There he is! Your very own zombie! We'll need to move quickly
now. Zombies don't last forever...the clock is ticking. Do the
following to get him to follow you:
%^GREEN%^speak in english%^RESET%^
%^GREEN%^say follow cratylus%^RESET%^
Except, of course, instead of \"cratylus\", put in your own name.
Note something important here. Zombies are obedient, but not
smart. If someone else starts saying things that the zombie understands
as a command, it will start obeying them. So remember that while
you created this zombie, someone else can steal him if you're not
careful.
Now, return to the well (4n) then enter the well and go west.
You'll need to tell the zombie to leave the water lock and operate it:
%^GREEN%^say go east%^RESET%^
%^GREEN%^yell push lever%^RESET%^
%^GREEN%^enter mech%^RESET%^
%^GREEN%^drive west%^RESET%^
%^GREEN%^direct mech to open grate%^RESET%^
Woohoo! If you don't have the mech, of course, you just
walk your own self west, drink your strength potions, and
open the grate yourself. If you *do* have the mech, let's get it out
of harm's way...
%^GREEN%^drive east%^RESET%^
%^GREEN%^out%^RESET%^
%^GREEN%^go west%^RESET%^
All righty, now let's go resolve this well problem. Go down
and you'll see the metal wheel and the water pipe. To get the water
flowing again, turn the wheel. Then very quickly get yourself back to
the water lock...the area will fill with water soon! When water
gets to the lock: %^GREEN%^yell pull lever%^RESET%^
Your zombie will then open the east door, allowing you and the water to
get to the well, and solving the Town Well Quest. Congratulations!
",({"chapter 2","chapter two","2",}):"chapter 2 \"Post-Mansion Hints\"
So you're flush with joy at having solved your first quest,
and now you're loaded with money and loot! Woo! But...oh no...
there's so much loot left in the mansion, and the gate gard won't
let you pass because you're not a newbie anymore. Bummer! Well,
we're going to fix that, but first, let's get organized. You're
carrying a bunch of stuff, some of which you want to keep, some
of which you want to sell, and it's going to get tiresome soon,
having to sort through your inventory all the time at shops. So
the first thing we're going to do is buy a rucksack.
Why?
Remember when you tried to \"drop all\" and the only things
you didn't drop were the things you were wearing? A rucksack
gives you somewhere to keep a bunch of stuff, and you *wear*
the rucksack. Meaning that \"drop all\" and \"sell all\" will
ignore the valuable stuff you keep in your rucksack, if
you're wearing it. Neat, huh? It makes life much easier when
selling tons of loot.
So, let's go to Otik (from the gate guard, 1n, 3e, 1n), and
buy the rucksack. Try:
%^GREEN%^buy ruck from otik%^RESET%^
If you dont have enough money, sell some of the junk you got
from the mansion. It also could be that you *do* have enough money,
but not in silver. If this is the case, go to zoe and do a
currency exchange:
%^GREEN%^exchange 5 gold for silver%^RESET%^
Note that you can only exchange money in your possession. Zoe
does not exchange money while it is deposited.
Once you have your rucksack, wear it, open it, and put in it
the stuff you want to keep. I suggest putting the grenade, the
pistol, and the slips in there. Now sell the junk you don't
want, deposit the excess cash with Zoe, and let's get ready
to deal with the gate guard.
The problem: A guard you cannot kill, preventing passage.
The solution: Find a way around, or through him.
Now, you might be tempted to use the grenade. You *could* pull
the pin on the grenade, drop it, leave, wait a few seconds, and
come back...and the guard *might* be dead. But this is a magical
guard. Even if he dies, his spirit prevents you from getting
past. And he will regenerate into a new body within seconds. So...
what to do? There are two solutions, and both of them require...
SCIENCE!
Go to Zoe and withdraw 110 dollars or so (darn fees), then
go to the bookstore (1w, 8s, 5e, 1n), and buy the lab coat from Kim.
Now go to the campus stargate lab (1s, 5w, 1s, 1w, 1n), where Kleiner
is busy working on exotechnology, as usual. In typical egghead
fashion, Kleiner is too preoccupied with his experiments to
help you, but he wants *your* help. He's lost his lab coat, you see.
Like so many geniuses, Kleiner is somewhat physically clumsy,
and if he wears a lab coat you give him, he's liable to drop
his identification badge. This badge gives its carrier access
to the \"hazardous technologies\" lab two rooms south of Kleiner.
%^RED%^PROTIP%^RESET%^: Do not attempt to kill Kleiner. He is carrying a
teleportation device called an omni which he will use the moment he
receives harm from someone. Attacking Kleiner just means he and
his ID badge will teleport to some random location, and you'll be
up the creek.
%^RED%^PROTIP%^RESET%^: In the hazardous materials lab, DO NOT attempt to harm
the tripod gun turret. Failure to heed this warning will result in
catastrophic injury to anyone in the room.
On the hazlab workbench you'll find various items of interest. For
now we'll concentrate on the Yautja wrist computer, the Yautja data
module, and the rocket pack. The rocket pack is the simplest tool for
circumventing the gate guard. Wear it, and head back to the guard
(1n, 1e, 6n, 2w, 1s). Now use the pack to fly over the guard:
%^GREEN%^activate pack%^RESET%^
%^GREEN%^boost up%^RESET%^
%^GREEN%^boost south%^RESET%^
%^GREEN%^boost down%^RESET%^
%^GREEN%^deactivate pack%^RESET%^
How do ya like that! Remember to deactivate the pack, too. If you
leave it running, it'll eventually run out of fuel. Don't get too
crazy flying around with it, either. You're extremely likely to boost
yourself out over the ocean if you're not careful, and wind up
lost and drowning. Also, if you run out of fuel while up in the
air, you *will* fall and the damage you'll receive will be grievous.
So let's be very careful in our use of the rocket pack.
Another way to get past the guard is by having him not see you at all.
The Yautja technology can help you in this:
%^GREEN%^wear wristcomp%^RESET%^
%^GREEN%^open wristcomp%^RESET%^
%^GREEN%^activate wristcomp%^RESET%^
%^GREEN%^install module in wristcomp%^RESET%^
%^GREEN%^close wristcomp%^RESET%^
%^GREEN%^cloak%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^decloak%^RESET%^
It is crucially important that you are cloaked only as long as
necessary. If you walk around while cloaked, it will sap your stamina
extremely quickly. Note that it is a peculiarity of Yautja technology
that it's a bit awkward to use. If you remove the wristcomp, it
deactivates, and you'll need to wear it, activate it again, uninstall
the module, and install it again. Those Yautja hunters always do
things the hard way, I'll tellya.
You now have the tools you need to successfully steal every
ounce of worth from the newbie mansion regardless of your
newbie status. Congratulations...I guess.
",]),"/doc/phints/chapter02":1236662936,"/doc/phints/.chapter04.swp":1585966503,"/doc/phints/chapter01":1236662936,]),"/obj/book_source":(["/obj/book_source/chapter1":1156518232,"object":"null","title":"The Coder's Manual","items":([({"chapter 2","chapter two","2",}):"\"Second Chapter Title\"
",({"chapter 2","chapter two","2",}):"\"Second Chapter Title\"
",({"chapter 1","chapter one","1",}):"\"First Chapter Title\"
",({"chapter 1","chapter one","1",}):"\"First Chapter Title\"
",({"chapter 2","chapter two","2",}):"\"Second Chapter Title\"
",({"chapter 1","chapter one","1",}):"\"First Chapter Title\"
",]),"index":" The Coder's Manual
Chapter 1: \"First Chapter Title\"
Chapter 2: \"Second Chapter Title\"
","reads":([({"chapter 1","chapter one","1",}):"chapter 1 \"First Chapter Title\"
This is the text of the first chapter.
",({"chapter 1","chapter one","1",}):"chapter 1 \"First Chapter Title\"
This is the text of the first chapter.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Second Chapter Title\"
This is the text of the second chapter.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Second Chapter Title\"
This is the text of the second chapter.
",({"chapter 1","chapter one","1",}):"chapter 1 \"First Chapter Title\"
This is the text of the first chapter.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Second Chapter Title\"
This is the text of the second chapter.
",]),"/obj/book_source/chapter2":1156518232,]),"/doc/hbook":(["object":"/domains/default/obj/handbook","/doc/hbook/chapter09":1236662936,"/doc/hbook/chapter08":1236712552,"/doc/hbook/chapter07":1164672846,"/doc/hbook/chapter06":1164672846,"/doc/hbook/chapter05":1164672846,"/doc/hbook/chapter04":1195053468,"/doc/hbook/chapter03":1177376334,"/doc/hbook/chapter02":1164672846,"/doc/hbook/chapter01":1164672846,"title":"Untitled","items":([({"chapter 8","chapter eight","8",}):"\"Stats details\"
",({"chapter 5","chapter five","5",}):"\"Communication\"
",({"chapter 2","chapter two","2",}):"\"Command Syntax: Doing Stuff\"
",({"chapter 7","chapter seven","7",}):"\"Hints and tips\"
",({"chapter 6","chapter six","6",}):"\"Note to New Creators\"
",({"chapter 2","chapter two","2",}):"\"Command Syntax: Doing Stuff\"
",({"chapter 8","chapter eight","8",}):"\"Stats details\"
",({"chapter 8","chapter eight","8",}):"\"Stats details\"
",({"chapter 1","chapter one","1",}):"\"Introduction\"
",({"chapter 9","chapter nine","9",}):"\"Skills, teachers, and trainers\"
",({"chapter 3","chapter three","3",}):"\"Your Health and Abilities\"
",({"chapter 1","chapter one","1",}):"\"Introduction\"
",({"chapter 7","chapter seven","7",}):"\"Hints and tips\"
",({"chapter 3","chapter three","3",}):"\"Your Health and Abilities\"
",({"chapter 6","chapter six","6",}):"\"Note to New Creators\"
",({"chapter 2","chapter two","2",}):"\"Command Syntax: Doing Stuff\"
",({"chapter 3","chapter three","3",}):"\"Your Health and Abilities\"
",({"chapter 9","chapter nine","9",}):"\"Skills, teachers, and trainers\"
",({"chapter 6","chapter six","6",}):"\"Note to New Creators\"
",({"chapter 5","chapter five","5",}):"\"Communication\"
",({"chapter 4","chapter four","4",}):"\"Quests\"
",({"chapter 4","chapter four","4",}):"\"Quests\"
",({"chapter 7","chapter seven","7",}):"\"Hints and tips\"
",({"chapter 1","chapter one","1",}):"\"Introduction\"
",({"chapter 4","chapter four","4",}):"\"Quests\"
",({"chapter 5","chapter five","5",}):"\"Communication\"
",({"chapter 9","chapter nine","9",}):"\"Skills, teachers, and trainers\"
",]),"index":" Untitled
Chapter 1: \"Introduction\"
Chapter 2: \"Command Syntax: Doing Stuff\"
Chapter 3: \"Your Health and Abilities\"
Chapter 4: \"Quests\"
Chapter 5: \"Communication\"
Chapter 6: \"Note to New Creators\"
Chapter 7: \"Hints and tips\"
Chapter 8: \"Stats details\"
Chapter 9: \"Skills, teachers, and trainers\"
","reads":([({"chapter 4","chapter four","4",}):"chapter 4 \"Quests\"
Some muds don't have quests, and the fun people have is
through role-playing and social activities with other players.
Other muds prefer to concentrate on killing lots and lots
of monsters, a lot, over and over.
Quests give you a chance to problems-solve by performing some
series of actions that satisfies a pre-determined requirement.
For example, Dead Souls's sample town contains a quest called
Orcslayer. Leo the archwizard lives in the basement of the old
abandoned church, and he has lost a powerful magic sword called
\"Orcslayer\". If you return it to him, he will reward you with
experience points, quest points, and a new title you can use. To
complete the quest, you need to defeat the warrior orcs, penetrate
deep into their lair, defeat the orc shaman, and take Orcslayer from
his corpse, then go to the church basement and give the sword to Leo.
In this case, if you're a level 1 newbie, the orcs will massacre you
before you get anywhere near the shaman. So either team up with
friends to tackle the orcs together, or raise your level to the
point where you're tough enough to take them on.
To raise your level, wander around in the newbie mansion, which
is south of the village church.
There's lots of loot there you can sell at Otik's shop, and with the
cash you can then get some proper weaponry and armor.
Silver is heavy, so don't try to carry all your money around
all the time. Request an account from Zoe the banker and keep your
money there until you really need it.
There is a quest in the newbie mansion, and solving it by
finding the secret room will give you experience and quest points too.
(hint, there might be more than one secret room)
Once you have enough experience and/or points, go to
Dirk in the adventurers hall and \"%^GREEN%^ask dirk to advance%^RESET%^\".
Make sure you learn some spells from Herkimer, because if you
go up against a bunch of orcs in their lair, you'll want spells to
shield you from attacks, and spells to recover your strength after
combat. As a non-mage, your spell abilities will be limited at
lower levels, but as you gain levels you'll get better. Also, spells
will rarely work after you first learn them. Keep casting them,
even if you screw them up, so that your magic skills increase.
Also, save your money. Drinking and sleeping help you heal,
but not fast enough. By the time those natural processes finish
and you're ready for combat again, the orcs may have gotten reinforcements.
So if you can afford it, buy healing slips and use them at Clepius's
healers guild. His treatment is expensive, but you will heal much
more quickly.
In the tragic event of the loss of a limb, Clepius can also magically
regenerate a new limb...but obviously at some great cost.
There. I've just spoiled the Orcslayer quest for you. Normally,
all you'd know about a quest is a cryptic clue, like the one in the
scroll in the adventurers guild. Instead I've just spoiled the quest
for you by telling you all about it. They're more fun when you have to
figure them out on your own, like puzzles.
Normally, spoiling quests like this is a bannable offense on
a mud, so if you solve a quest, keep it to yourself unless you know
the admins on your mud don't mind.
",({"chapter 4","chapter four","4",}):"chapter 4 \"Quests\"
Some muds don't have quests, and the fun people have is
through role-playing and social activities with other players.
Other muds prefer to concentrate on killing lots and lots
of monsters, a lot, over and over.
Quests give you a chance to problems-solve by performing some
series of actions that satisfies a pre-determined requirement.
For example, Dead Souls's sample town contains a quest called
Orcslayer. Leo the archwizard lives in the basement of the old
abandoned church, and he has lost a powerful magic sword called
\"Orcslayer\". If you return it to him, he will reward you with
experience points, quest points, and a new title you can use. To
complete the quest, you need to defeat the warrior orcs, penetrate
deep into their lair, defeat the orc shaman, and take Orcslayer from
his corpse, then go to the church basement and give the sword to Leo.
In this case, if you're a level 1 newbie, the orcs will massacre you
before you get anywhere near the shaman. So either team up with
friends to tackle the orcs together, or raise your level to the
point where you're tough enough to take them on.
To raise your level, wander around in the newbie mansion, which
is south of the village church.
There's lots of loot there you can sell at Otik's shop, and with the
cash you can then get some proper weaponry and armor.
Silver is heavy, so don't try to carry all your money around
all the time. Request an account from Zoe the banker and keep your
money there until you really need it.
There is a quest in the newbie mansion, and solving it by
finding the secret room will give you experience and quest points too.
(hint, there might be more than one secret room)
Once you have enough experience and/or points, go to
Dirk in the adventurers hall and \"%^GREEN%^ask dirk to advance%^RESET%^\".
Make sure you learn some spells from Herkimer, because if you
go up against a bunch of orcs in their lair, you'll want spells to
shield you from attacks, and spells to recover your strength after
combat. As a non-mage, your spell abilities will be limited at
lower levels, but as you gain levels you'll get better. Also, spells
will rarely work after you first learn them. Keep casting them,
even if you screw them up, so that your magic skills increase.
Also, save your money. Drinking and sleeping help you heal,
but not fast enough. By the time those natural processes finish
and you're ready for combat again, the orcs may have gotten reinforcements.
So if you can afford it, buy healing slips and use them at Clepius's
healers guild. His treatment is expensive, but you will heal much
more quickly.
In the tragic event of the loss of a limb, Clepius can also magically
regenerate a new limb...but obviously at some great cost.
There. I've just spoiled the Orcslayer quest for you. Normally,
all you'd know about a quest is a cryptic clue, like the one in the
scroll in the adventurers guild. Instead I've just spoiled the quest
for you by telling you all about it. They're more fun when you have to
figure them out on your own, like puzzles.
Normally, spoiling quests like this is a bannable offense on
a mud, so if you solve a quest, keep it to yourself unless you know
the admins on your mud don't mind.
",({"chapter 8","chapter eight","8",}):"chapter 8 \"Stats details\"
Stats are very important! Depending on the style of
play you prefer, different stats can be of more value
to you. Fighters may prefer strength, mages
may favor intelligence.
%^RED%^IMPORTANT TIP%^RESET%^: Players start off with 15 stat points
they can add to their stats...so that if you find your
strength is abysmally low, you can raise it to where
your character is playable at lower levels. Don't
forget to type: %^GREEN%^help customize%^RESET%^
%^CYAN%^About \"stat classes\"%^RESET%^: Depending on the race you choose at
character creation, your stat class will be something
in the range of 1 to 5. This is a measure of the stat's
typical priority for that race. For example, orcs are very
strong, and so not only do they usually start with a
high strength stat, their strength class is 1, meaning
that every time an orc has a level promotion, their strength
stat is also raised by 1. Humans, however, have a strength
stat class of 3, meaning a human player's strength stat
is increased by 1 only every third level promotion.
%^CYAN%^Deviation%^RESET%^:Starting in Dead Souls 2.9a18, a player can choose to
\"deviate\" from her race's default stat classes. For example,
you *can* have an orc mage with a high-priority intelligence
stat. To do so, you use the deviate command to set your
stat deviation...meaning you're going to devote a certain
percent of the XP you earn to making yourself better at
some stats you choose. Naturally, the more you want to
change a stat class, the more expensive this will be in
terms of your XP deviance cost. For more information,
see: %^GREEN%^help deviate%^RESET%^
%^CYAN%^Some notes on individual stats%^RESET%^:
High %^MAGENTA%^strength%^RESET%^ means you hit hard when you're in combat,
but it also means you might be strong enough to perform some
tasks others cannot, like open very heavy metal doors.
High %^MAGENTA%^intelligence%^RESET%^ means you're able to learn things
better, quicker, like skill training and languages. It
means your training points do you more good.
High %^MAGENTA%^charisma%^RESET%^ means it's easier to befriend animals,
which is important if you want to, for example, mount
a horse:
%^GREEN%^befriend horse%^RESET%^
%^GREEN%^mount horse%^RESET%^
%^MAGENTA%^Agility%^RESET%^ and %^MAGENTA%^coordination%^RESET%^ are important for avoiding being
hit, and for certain special abilities like stealth,
lock picking, shooting, etc.
%^MAGENTA%^Wisdom%^RESET%^ is important for some types of spells.
%^MAGENTA%^Durability%^RESET%^ helps determine how much stamina you have...
this is vital to lasting through long combat or moving
quickly through terrain on foot.
Finally, %^MAGENTA%^luck%^RESET%^ is an all-around general good thing to have
lots of...some random situations may call for it, and just
like in real life, it's hard to know when it'll come in handy,
but boy can it mean the difference between win and fail!
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction\"
If you are unfamiliar with LPC based muds in general or Dead Souls
mudlib in particular, you will find this handbook valuable in
orienting you around what you can do and how you can do it.
Keep in mind that this handbook describes the features of a mud
running an unmodifed version of the lib. The mud you are on may
differ somewhat, though probably not extremely so.
To advance a page in a chapter, just hit \"return\".
Let's start with just navigating this book. Once you are done
reading this chapter, you can read the next chapter by typing:
%^GREEN%^read chapter 2 in handbook%^RESET%^
Make sure you wait until you are done reading this chapter,
though. The reason you should wait is that you are now in \"reading
mode\", which means that anything you type and send to the mud
is actually a command to the editing system that is displaying this
text.
To leave reading mode (or more accurately, pager, or ed mode)
you can hit \"return\" a bunch of times to complete the chapter, thus
automatically exiting the pager. Another way is to enter the
letter \"q\" (without the quotes) and then \"return\". That will also
make you stop reading.
When you are not in reading mode, you can find out the chapter
titles by typing:
%^GREEN%^read index in handbook%^RESET%^
You really should read the whole thing, but in case you don't, the
chapter titles will help as a reference to find the information you
need.
Something to watch out for is that if you or your environment
contain another handbook, the mud may not know which one you are
trying to read. If you get a message like \"Which of the two handbooks
would you like to read?\", you can try one or more of the following:
%^GREEN%^read index in first handbook%^RESET%^
%^GREEN%^read index in my handbook%^RESET%^
%^GREEN%^read index in my first player handbook%^RESET%^
",({"chapter 6","chapter six","6",}):"chapter 6 \"Note to New Creators\"
You should probably hang on to this book for reference. If
you lose it, pick up a copy at the adventurers hall.
However, you need to start reading the Creators Manual. If
you don't have one on you, get the one in the chest in your workroom.
If you're new to coding, start with chapter 31. It'll
get you started with the Quick Creation System, or QCS.
Cratylus @ Frontiers
04 Jan 2006
",({"chapter 3","chapter three","3",}):"chapter 3 \"Your Health and Abilities\"
In the previous chapter you learned the basics of getting
around and taking care of yourself. It's important also to care
*for* yourself, and this chapter describes the various aspects of
your body's state and what abilities you may have.
The command that tells you almost everything you need to
know is \"stat\". This diplays a whole lot of stuff, perhaps some of
it completely unfamiliar. Let's start at the top, using my output
as an example.
First line:
----------
%^CYAN%^Cratylus aka Cratylus the unaccomplished, level 10 male human Explorer%^RESET%^
Here you see my short name, my name with title, my level, my
gender, my race, and my class. Let's go over each.
* short name: What a person would use to address you. \"look at cratylus\",
for example.
* name with title: This displays my title. Creators can have whatever title
they want. Players can only have the titles they earn. As a player, a
title is usually earned when you are promoted a level or complete a
quest, though it is not always so on every mud.
* level: This is a measure of your overall experience, expertise, and
all-around game status. Being promoted a level means your skills,
health, and vital statistics increase. This often means you can handle
tougher monsters, for example, or tackle more challenging quests, learn
new spells, and so on.
* gender: This has no effect on your status. It is a cosmetic feature
of your body that is only useful to you in the social context of your
fellow mud players.
* race: In Dead Souls, race has nothing to do with your local genetic
makeup on planet Earth. In the mud, \"race\" refers to what one typically
would call \"species\" in real-life. An example of a race other than human
might be \"orc\" or \"feline\". Not all races are available for players. Once
you have chosen a race to play, it is in theory possible to change it, but
there is a nonzero chance you'll hose up your player file and lose your
character forever. Besides, it's up to your local admins whether race
changing is permitted on your mud. Different races have different
abilities. Elves see better in darkness, for example. Orcs are stronger
than some other humanoids, but dumber, too (which does affect gameplay).
* class: This can be considered an occupational specialty. In the real
world you have plumbers, doctors, soldiers, etc. In the mud world,
we can have explorers, fighters, mages, and the like. Each class brings
its own unique advantages and disadvantages to your gameplay. A fighter
can really kick more butt in melee combat than a mage, but a mage
gets to cast powerful spells. Explorers are a middle of the road class
that gives you a bit of everything without specializing in anything.
Next line:
----------
%^CYAN%^Alive / Awake%^RESET%^
It is indeed possible for your virtual body to cease life functions.
When this happens your spirit speeds off to the land of the dead,
where you drift until you decide to \"regenerate\" and regain your
physical form. Except for some special magical items, anything you
were carrying when you died is with that dead body, so it's a good
idea to rush your new body back to the scene of the fatality and get
your stuff back before someone else grabs it. Death is not only
inconvenient, it also incurs major penalties on your statistics, so it
should be avoided.
It is also possible to sleep. If you are drunk and asleep, your
injuries will heal more quickly. It's magic, don't worry about the
logic behind it.
If you are attacked while sleeping, you will wake up. You can
force yourself awake, too, but it's a bit tedious.
Next line:
---------
%^CYAN%^Health: 350/350 Magic: 560/560 Stamina: 400/400 Carry: 1184/1300%^RESET%^
In each case, the number on the left of the slash indicates the
current level, and the number on the right indicates what the maximum is.
health: When I am 100% healthy, I have a total of 350 hp. If my hp ever
reach 0 or less (!), I die. Poison and illness can cause hp's to
gradually decrease, and although with time my hp's will normally
return to 350 as I heal, poison and illness can slow down that healing
or even cause me to die. Injury in combat is the most common source
of hp loss, though spells, falls, and other adverse events can cause
you injury or death.
magic: I cast magic missile! Spell casting takes a toll on your magical
abilities, and mp measure how much magic you've got left in you at
any given point. Like hp, mp gradually grow back to your max if you
avoid spellcasting for a while.
stamina: Fighting is tough work, and swinging around swords while
getting bashed with hammers really takes a lot out of a guy. Therefore
keep an eye on this stat while you're fighting, because if it gets too
low you will collapse and be unable to do anything for a while.
carry: Objects have mass, and your body is of limited size and strength.
My carry capacity is 0 when I carry nothing, and 1300 when I can
carry no more. Creators are allowed to exceed their bodies' carry
capacity, but players cannot.
Next line:
---------
%^CYAN%^Food: 0 Drink: 0 Alcohol: 0 Caffeine: 0 Poison: 0 %^RESET%^
These are pretty self-explanatory. Alcohol is good for healing,
bad for fighting. Food and drink also help speed healing. Poison
has the opposite effect. Caffeine can speed up your combat slightly,
but tends to prevent full rest.
You will not die from lack of food or lack of drink, but you
will do better with a body not starved for nutrients.
Your maximum load for any of these is not fixed, and varies
depending on many factors, such as level, endurance, etc.
Next line:
---------
%^CYAN%^Training Points: 0 Quest Points: 0 Experience Points: 50 %^RESET%^
Training points can be cashed in with special NPC's called
trainers, who can help you improve some skills. A trainer that
specializes in fighting might be able to raise your \"blade attack\"
skill, for example. you earn training points when you are
promoted a level.
Quest points are awarded when you complete a quest. In
the default version of Dead Souls, you cannot advance past a
certain player level unless you earn some qp's. Read the sign
in the adventurers guild for more details on this.
Experience points can be awarded for various reasons: completing
a quest, solving a puzzle, winning a contest. Most often you
will receive xp after killing an NPC. The amount of xp awarded
will depend on the level of the NPC. Like qp, xp are needed to
qualify for level advancement.
Limb section:
------------
Remember how wearing armor requires the right body parts?
Well here they are, and this is their health. You can issue the
\"body\" command for a quicker self-check.
Let's look at what the numbers mean with an example:
%^CYAN%^left leg (2) 160/160%^RESET%^
Obviously the first item identifies the limb in question.
The (2) is a kind of \"importance score\", indicating how critical
a body part is. If this number is (1), like the head, it means that
losing that limb causes immediate death.
The number on the right side of the slash indicates the hit point
damage you may receive on that limb before it is severed. The number
on the left is how many of those hits you have left.
It doesn't mean my leg has 160 of my hitpoints. If that were true,
my hit points would add up to a heck of a lot more than 350.
This means that if I've lost, say, 200hp fighting a troll, and
159hp of those hits were on my left leg, getting hit there again means I
lose my left leg.
I would then collapse and have to crawl away to seek medical attention.
Wearing armor on your limbs is a great way to minimize the danger of
this happening.
Skills section:
--------------
Let's review skills by examining one of mine:
%^CYAN%^blade attack (1) 00% - 20/24%^RESET%^
This measures how likely I am to hit an opponent when I
use a blade, and how good a hit it was. The number (1) means
that this is a skill critical to my class. If an explorer can't
swing a sword, he oughta think about another line
of work.
The 00% means I have thus far earned no blade attack
experience toward achieving the next level of this skill.
The 20 is my current proficiency level.
The 24 is the maximum level I can reach at my current
player level and with my current stats.
What's all this mean? Well, if I practice a lot of blade
attacking, that 00% will gradually climb up to 99, and one more
point causes me to go from a level 20 slicer of things to a
level 21 slicer of things. This increases my likelihood of
hitting my target in the future.
Meaning, in short, practice a skill, and you'll get
better at it.
Of course, if my blade attack level reaches 24, I can advance
my blade attack skills no further until my player level rises.
Stats section:
-------------
Remember these from Dungeons & Dragons? No? Well these vital
statistics measure your general giftedness in that feature of your
body. Let's look at one of mine:
%^CYAN%^coordination (2) 42/42%^RESET%^
Coordination is one of those important stats for fighting and
such. The more coordinated you are, the more likely you are to hit your
target. The (2) indicates that this stat is important to my class,
but not critical. This influences its effect on my skills.
42/42 means that my coordination is not currently impaired. If
someone cast a \"stumble\" spell on me, for example, this might look more
like 30/42, and if I were drunk, it would look very shabby indeed.
New characters should avail themselves of the \"customize\" command.
When you create a character, you are assigned stats based on
random numbers modified by the race you choose. For example, humans
are physically weaker than other races, so you might have a
strength of 15 as a human, whereas a dwarf might expect something
like 42. On the other hand, humans tend to be quite smart, and so
your human character might have a high intelligence stat, and
the dwarf a substantially lower one. To balance out stats that
are grossly unfair, new characters are given 15 points to spend
to add to their stats. As a human with 15 strength, you might
choose to throw all your customization points into strength, adding
up to a whopping 30. Or you might choose to distribute points
among your stats in a manner most suited to your playing style.
For syntax and details, type:
help customize
Last section:
------------
\"Cratylus has amassed a net worth of 11 gold.\" means that when you
add up the money in my bank accounts and the money I'm carrying,
converted to gold, I have 11 gold to my name. It looks bad, but gold
is actually quite valuable in the default Dead Souls economy.
\"Money on hand: 79 dollars, 34 silver\" means that this is the amount of
money I'm carrying. Don't forget that the amount of money you are carrying
affects your overall carry capacity. Gold is an especially heavy
currency.
Final notes:
-----------
\"stat\" is a great command to get thorough information about
yourself. It is, however, quite a screenful. Briefer reports can be
viewed with the following commands:
%^GREEN%^body%^RESET%^
%^GREEN%^skills%^RESET%^
%^GREEN%^stats%^RESET%^
%^GREEN%^score%^RESET%^
%^GREEN%^status%^RESET%^
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction\"
If you are unfamiliar with LPC based muds in general or Dead Souls
mudlib in particular, you will find this handbook valuable in
orienting you around what you can do and how you can do it.
Keep in mind that this handbook describes the features of a mud
running an unmodifed version of the lib. The mud you are on may
differ somewhat, though probably not extremely so.
To advance a page in a chapter, just hit \"return\".
Let's start with just navigating this book. Once you are done
reading this chapter, you can read the next chapter by typing:
%^GREEN%^read chapter 2 in handbook%^RESET%^
Make sure you wait until you are done reading this chapter,
though. The reason you should wait is that you are now in \"reading
mode\", which means that anything you type and send to the mud
is actually a command to the editing system that is displaying this
text.
To leave reading mode (or more accurately, pager, or ed mode)
you can hit \"return\" a bunch of times to complete the chapter, thus
automatically exiting the pager. Another way is to enter the
letter \"q\" (without the quotes) and then \"return\". That will also
make you stop reading.
When you are not in reading mode, you can find out the chapter
titles by typing:
%^GREEN%^read index in handbook%^RESET%^
You really should read the whole thing, but in case you don't, the
chapter titles will help as a reference to find the information you
need.
Something to watch out for is that if you or your environment
contain another handbook, the mud may not know which one you are
trying to read. If you get a message like \"Which of the two handbooks
would you like to read?\", you can try one or more of the following:
%^GREEN%^read index in first handbook%^RESET%^
%^GREEN%^read index in my handbook%^RESET%^
%^GREEN%^read index in my first player handbook%^RESET%^
",({"chapter 1","chapter one","1",}):"chapter 1 \"Introduction\"
If you are unfamiliar with LPC based muds in general or Dead Souls
mudlib in particular, you will find this handbook valuable in
orienting you around what you can do and how you can do it.
Keep in mind that this handbook describes the features of a mud
running an unmodifed version of the lib. The mud you are on may
differ somewhat, though probably not extremely so.
To advance a page in a chapter, just hit \"return\".
Let's start with just navigating this book. Once you are done
reading this chapter, you can read the next chapter by typing:
%^GREEN%^read chapter 2 in handbook%^RESET%^
Make sure you wait until you are done reading this chapter,
though. The reason you should wait is that you are now in \"reading
mode\", which means that anything you type and send to the mud
is actually a command to the editing system that is displaying this
text.
To leave reading mode (or more accurately, pager, or ed mode)
you can hit \"return\" a bunch of times to complete the chapter, thus
automatically exiting the pager. Another way is to enter the
letter \"q\" (without the quotes) and then \"return\". That will also
make you stop reading.
When you are not in reading mode, you can find out the chapter
titles by typing:
%^GREEN%^read index in handbook%^RESET%^
You really should read the whole thing, but in case you don't, the
chapter titles will help as a reference to find the information you
need.
Something to watch out for is that if you or your environment
contain another handbook, the mud may not know which one you are
trying to read. If you get a message like \"Which of the two handbooks
would you like to read?\", you can try one or more of the following:
%^GREEN%^read index in first handbook%^RESET%^
%^GREEN%^read index in my handbook%^RESET%^
%^GREEN%^read index in my first player handbook%^RESET%^
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Hints and tips\"
* The \"wimpy\" command helps you avoid death due to inattention or
network lag. If you \"wimpy 20\", you will automatically try to escape
combat if your health goes below 20% of your maximum.
* \"target\" and \"ignore\" are extremely useful when fighting more than
one enemy. You should always target the toughest npc first, and
always ignore any npc who can't get up because their foot or leg
is severed.
But if they collapse due to exhaustion, it's a good idea to keep
beating on them, otherwise they may get back up and get healthy sooner
than you expect.
* By default, different races speak different languages. If someone
says something to you and you see no words in the same language as
the rest of the mud, it means they are speaking a language you do
not understand.
For example, if you are an elf, and you ask Radagast to teach
magic attack, you might get something like this:
Radagast exclaims in English, \"embleer con boltehe oota goota nehi auch\"
Even though in the real world you may speak English fluently, in
the mud world, you do not speak English fluently. As an elf, your
native tongue is Edhellen, and you may find human speech incomprehensible.
If you find a trainer to teach you English, your skills in that
language will need time to improve. As you get better at a language,
you will see fewer gibberish words.
If you are a \"newbie\", this does not apply to you. A newbie in the
default Dead Souls distribution is a player at level 4 or below. This
definition may be changed by your admin.
Newbies need all the help they can get just to survive, so they
are magically granted understanding of all languages, until they outgrow
their naivete.
If you are a student of languages in the Real World, you may
recognize many of the \"gibberish\" words used by Dead Souls to represent
a foreign tongue. Your understanding of these words is not useful in
the context of the game, however, because they are not intended to
convey meaning other than \"non-comprehensible words\".
* Your ability to see is affected by various things:
- A room's ambient light level
- Time of day
- Local light sources (flashlights, torches, etc)
- Your race's light sensitivity
- Magical effects
- Exposure to an excessive-light event
It's important to remember that a room may be too dark for
you to see everything in it. You might be able to see the description
of a room with no problem, but it may be necessary for you to
light a torch in order to see the treasure chest there.
In the same way that darkness can impair vision, brightness
can do the same. For elves, an outdoor area in bright sunlight that
contains additional light sources can be just as hostile to
vision as a dark cave with no torch would be for a human.
Regardless of race, a sufficiently adverse event, such as
a bright flash or special spell, can render you temporarily blind.
As with languages, newbies have some exemption to light-level
limitations.
* Mages can wield knives but are pretty much helpless with any other
vind of edged weapon.
",({"chapter 5","chapter five","5",}):"chapter 5 \"Communication\"
There are many ways to communicate with other players. If
you're in the same room as your intended listener, you can just
use the \"say\" command, like this:
%^GREEN%^say hi, crat%^RESET%^
If the message is secret, you can \"whisper\":
%^GREEN%^whisper to cratylus are you idle?%^RESET%^
If you want to say something that everyone in the mud can
hear, use the \"shout\" command (at the cost of a lot of stamina):
%^GREEN%^shout hey crat, wheredya go?%^RESET%^
Or, if it's an important secret and the target is not in the
same room as you, you can use the magical \"tell\" command:
%^GREEN%^tell cratylus are you mad at me or something?%^RESET%^
There are also special communication lines on the mud that are
class or role-specific. For example, if you type:
%^GREEN%^newbie does anyone know what's up with cratylus?%^RESET%^
All people who are tuned into the newbie line will get
your message. To see what lines are available to you, type:
%^GREEN%^lines%^RESET%^
To see who is listening to the newbie channel:
%^GREEN%^list newbie%^RESET%^
To see who is listening to some other channel on some other mud:
%^GREEN%^list otherchannel@othermud%^RESET%^
To enable or disable a line, just type the name of it with no message.
To see a brief history of the past few messages on a line (in
this case, the newbie line), type:
%^GREEN%^hist newbie%^RESET%^
Spamming lines is rude and probably dangerous to your character, so
be sure you comply with your mud's rules on lines.
Your mud may be on the intermud network. To find out, type the
command:
%^GREEN%^mudlist%^RESET%^
If a list of muds comes up, you know your mud is probably
on the intermud3 communication network. Dead Souls by default restricts
players from access to intermud channels, but you can \"tell\" to
players on other muds, if you want. If you think your friend Xyzzy
is online on a mud on intermud3, you can issue this command:
%^GREEN%^locate xyzzy%^RESET%^
If he's logged into a mud on i3, you will get something like:
Xyzzy was just located on Frontiers. (idle 00:03:17) [status: inactive]
You can then tell to him:
%^GREEN%^tell xyzzy@frontiers dude, what's the deal with crat lately?%^RESET%^
Sometimes a player or NPC does not understand your character's
native tongue. For example, if you are en elf, your native tongue is
not English, it is Edhellen. If someone talks to you in English, you
might see something like this:
Xyzzy says in English, \"leka mifahmam, potong-hwa.\"
Since your character doesn't speak English, what you see is gibberish.
If you find a language teacher, your proficiency in the language they
teach you will allow you to understand more of the words you hear.
Suppose that your elf character is now 100% fluent in English.
If you greet a human player named Xyzzy by typing:
%^GREEN%^say hello there, xyzzy%^RESET%^
Xyzzy will probably see something like:
Noobie says in Edhellen, \"pericolo temak, forshtor.\"
Instead, if you want to speak to a human, you'll have to type:
%^GREEN%^speak in english hello there, xyzzy%^RESET%^
To find out what languages you speak, type:
%^GREEN%^language%^RESET%^
",({"chapter 2","chapter two","2",}):"chapter 2 \"Command Syntax: Doing Stuff\"
Section 1: Manipulating Objects
----------
You've already noticed that Dead Souls, like most modern LP muds,
uses natural-like command syntax, like:
%^GREEN%^read first handbook%^RESET%^
rather than:
%^GREEN%^read handbook 1%^RESET%^
This is because Dead Souls uses a natural language parser. It isn't
perfect, of course. If you try to \"put all apples from box in my bag after
opening them\" you won't see much success, but this will work:
%^GREEN%^open box%^RESET%^
%^GREEN%^open bag%^RESET%^
%^GREEN%^get apples from box%^RESET%^
%^GREEN%^put apples in bag%^RESET%^
The parser will understand \"the first box\" or \"my second bag\",
assuming those objects exist in your inventory or in your environment.
If you want to know what is in the box, the command is:
%^GREEN%^look in box%^RESET%^
The command \"look at box\" or \"examine box\" will usually *not* show
you the contents of that box. This is because normally, boxes are
opaque, and in the real world, just looking at a box is rarely enough
to see what it contains as well.
An exception to this rule are transparent containers (a glass
trophy case, perhaps) whose interior is always visible from the
outside.
Sometimes looking at an object reveals its contents because of
the nature of the object. A table, for example, can have things on it,
and typing:
%^GREEN%^look at table %^RESET%^
...will usually let you know what is on it. It is also possible to
see what other players are carrying by just looking at them,
unless what they have is inside a container.
You'll want to remember that while you can \"put apple in bag\",
if you want to put that apple on a surface like a table, you'll
need to:
%^GREEN%^put apple on table%^RESET%^
You can give things to people, and they will automatically
accept them. However, you may not \"take\" or \"get\" things from living
beings. It's theirs, and it's up to them if they want to share.
You can try to \"steal sword from fighter\" if you dare, but unless
you have trained a lot, this is unlikely to succeed. We'll talk
more about training and skills in a later chapter.
Naturally you may also drop things you no longer need, though
it's nicer to your fellow mudders (and the mud's memory) to put
them in recycling bins so the bits can be reused.
Some other common object manipulation commands are:
close, donate, attack, eat, drink, listen, smell, search,
shoot, touch, turn.
There are many others you may find useful, but these will be
the ones you use most often to handle simple objects.
* A note about articles:
Dead Souls understands both definite and indefinite articles.
This means that you can refer to a specific apple, like so:
%^GREEN%^get the apple%^RESET%^
But you can also be unspecific. If there are a dozen
apples in a crate and you don't care which one you pick up:
%^GREEN%^get an apple from the crate%^RESET%^
Section 2: Navigation
---------
Moving around here is probably much like any other mud. You
can expect to move mostly in cardinal directions (like north and
northwest), but you may sometimes need to go up, down, or out.
Strictly speaking, the way to do this is:
%^GREEN%^go south%^RESET%^
%^GREEN%^go out%^RESET%^
...and so on, but this can get tedious after a while. Instead
of having to type in \"go\" plus the entire direction, the mud allows
you to enter shortcuts like \"sw\" for \"go southwest\" or \"u\" for
\"go up\".
When you enter a room, very often you will see letters in
brackets above the room description, like this: [n, u, out]
These are the \"obvious exits\" of that room, and help you
quickly find your way around without having to go through each
description. But remember! Just because a room has obvious exits
doesn't mean those are the only exits. Sometimes a room must
be searched to discover an exit, or there may be an exit available
that just doesn't happen to be very obvious.
If a room is dark, obvious exits may not be visible at all.
Aside from those ordinary means of travel, there are situations
that require more specific locomotion than just \"go\". These are
examples of the use of some other commands to get around:
%^GREEN%^jump onto road%^RESET%^
%^GREEN%^enter window%^RESET%^
%^GREEN%^climb ladder%^RESET%^
%^GREEN%^crawl east%^RESET%^ (if you are lying down and can't get up)
%^GREEN%^fly up%^RESET%^
%^GREEN%^follow thief%^RESET%^
%^GREEN%^evade hunter%^RESET%^
Section 3: Armor
-------
Now that you can manipulate objects and move around, you'll want
to be able to defend yourself, should the need arise. The special
object categories of \"weapons\" and \"armor\" should help.
Armor is an item that can be worn. That means that a pair of
blue jeans is considered armor, and a suit of chainmail is considered
armor as well. Basically, if you can wear it, it's \"armor\", because
whether it's a lot or a little, it protects you.
Assuming you are humanoid, you have the following limbs:
head, neck, torso, right arm, right hand, left arm,
left hand, right leg, right foot, left leg, left foot.
Properly coded armor must be worn on the corect limbs. Usually
a command like:
%^GREEN%^wear chainmail%^RESET%^
or
%^GREEN%^wear all%^RESET%^
...will cause you to automatically wear armor where it makes
most sense. However, it is possible to find armor that,
for example, can be worn either on your neck or your torso, like
an amulet. If this is so, you'll need to specify where you want it.
There are various types of armor, like cloak, pants, glove,
etc. Many of them overlap. You can wear a shirt on your torso as
well as a cloak and combat armor, but you may not wear two of
the same type. If you have a robe and a cape that are both cloaks,
you'll have to decide which one is going on.
You will find that shoes and gloves are often for one of your
hands but not the other. Sometimes you will find shoes, or gloves
that don't care which appendage they occupy, but usually these
are simply incorrectly coded.
If you are of some exotic or non-humanoid race, you may have
additional limbs to consider, and humanoid armor may not work for you.
Section 4: Weapons
---------
You may be surprised to learn that almost any manipulable object
can be wielded as a weapon, or thrown as a missile. You can wield
a can of Spam and try to kill an orc with it...and you may even succeed,
if you are strong and tough enough. Don't count on it, though,
and instead go for items that are made specifically with personal
security in mind.
There are four main types of weapons:
knife: knives, daggers
blade: like swords, and spears
blunt: like clubs, staves, and shillelaghs
projectile: things designed to be thrown, like darts or grenades
Unless it is a special device or magical item, weapons
must be wielded in order to be of use in combat. Some weapons,
like staves or pikes, may require the use of both hands. If this
is the case, wearing a shield may not be possible at the same time.
Like armor, weapons differ in quality and effectiveness. A
\"well-crafted sword\" is probably a better choice than a \"small
rusty knife\", but then again, you never know. Maybe rusty knives
are exactly what some monster is most vulnerable to.
Note also that, like armor, weapons wear down with use.
Examples of commands that involve weapons or fighting:
%^GREEN%^wield sword%^RESET%^
%^GREEN%^wield hammer in left hand%^RESET%^
%^GREEN%^wield staff in left hand and right hand%^RESET%^
%^GREEN%^unwield dagger%^RESET%^
%^GREEN%^shoot gun at otik%^RESET%^
%^GREEN%^throw dart at beggar%^RESET%^
%^GREEN%^kill all%^RESET%^ (this makes an enemy of everyone in the room)
%^GREEN%^ignore first orc%^RESET%^ (lets you concentrate on the other orcs)
%^GREEN%^ignore all%^RESET%^ (don't fight anyone in the room, even if they are attacking you)
%^GREEN%^target boss orc%^RESET%^ (this makes you ignore attacks from anyone else)
%^GREEN%^wimpy 30%^RESET%^ (this makes you run away if your health points drop below 30%)
Section 5: Miscellaneous Things to to with Things
---------
%^GREEN%^turn on flashlight%^RESET%^
%^GREEN%^turn off flashlight%^RESET%^
%^GREEN%^strike match%^RESET%^
%^GREEN%^light torch with match%^RESET%^
%^GREEN%^extinguish match%^RESET%^
%^GREEN%^dig hole with shovel%^RESET%^
%^GREEN%^move bed%^RESET%^
%^GREEN%^search %^RESET%^ (by default searches the room)
%^GREEN%^search rocks%^RESET%^
%^GREEN%^unlock east door with silver key%^RESET%^
%^GREEN%^bait pole with worm%^RESET%^
%^GREEN%^fish with pole%^RESET%^
%^GREEN%^stop fishing%^RESET%^
%^GREEN%^drop all%^RESET%^
%^GREEN%^donate 2 silver%^RESET%^
%^GREEN%^get all%^RESET%^
%^GREEN%^get all from corpse%^RESET%^
%^GREEN%^sell first right glove to otik%^RESET%^
%^GREEN%^sell all to otik%^RESET%^
%^GREEN%^buy sword from otik%^RESET%^
%^GREEN%^buy 8 from otik%^RESET%^ (get Otik to sell you item number 8)
",({"chapter 8","chapter eight","8",}):"chapter 8 \"Stats details\"
Stats are very important! Depending on the style of
play you prefer, different stats can be of more value
to you. Fighters may prefer strength, mages
may favor intelligence.
%^RED%^IMPORTANT TIP%^RESET%^: Players start off with 15 stat points
they can add to their stats...so that if you find your
strength is abysmally low, you can raise it to where
your character is playable at lower levels. Don't
forget to type: %^GREEN%^help customize%^RESET%^
%^CYAN%^About \"stat classes\"%^RESET%^: Depending on the race you choose at
character creation, your stat class will be something
in the range of 1 to 5. This is a measure of the stat's
typical priority for that race. For example, orcs are very
strong, and so not only do they usually start with a
high strength stat, their strength class is 1, meaning
that every time an orc has a level promotion, their strength
stat is also raised by 1. Humans, however, have a strength
stat class of 3, meaning a human player's strength stat
is increased by 1 only every third level promotion.
%^CYAN%^Deviation%^RESET%^:Starting in Dead Souls 2.9a18, a player can choose to
\"deviate\" from her race's default stat classes. For example,
you *can* have an orc mage with a high-priority intelligence
stat. To do so, you use the deviate command to set your
stat deviation...meaning you're going to devote a certain
percent of the XP you earn to making yourself better at
some stats you choose. Naturally, the more you want to
change a stat class, the more expensive this will be in
terms of your XP deviance cost. For more information,
see: %^GREEN%^help deviate%^RESET%^
%^CYAN%^Some notes on individual stats%^RESET%^:
High %^MAGENTA%^strength%^RESET%^ means you hit hard when you're in combat,
but it also means you might be strong enough to perform some
tasks others cannot, like open very heavy metal doors.
High %^MAGENTA%^intelligence%^RESET%^ means you're able to learn things
better, quicker, like skill training and languages. It
means your training points do you more good.
High %^MAGENTA%^charisma%^RESET%^ means it's easier to befriend animals,
which is important if you want to, for example, mount
a horse:
%^GREEN%^befriend horse%^RESET%^
%^GREEN%^mount horse%^RESET%^
%^MAGENTA%^Agility%^RESET%^ and %^MAGENTA%^coordination%^RESET%^ are important for avoiding being
hit, and for certain special abilities like stealth,
lock picking, shooting, etc.
%^MAGENTA%^Wisdom%^RESET%^ is important for some types of spells.
%^MAGENTA%^Durability%^RESET%^ helps determine how much stamina you have...
this is vital to lasting through long combat or moving
quickly through terrain on foot.
Finally, %^MAGENTA%^luck%^RESET%^ is an all-around general good thing to have
lots of...some random situations may call for it, and just
like in real life, it's hard to know when it'll come in handy,
but boy can it mean the difference between win and fail!
",({"chapter 2","chapter two","2",}):"chapter 2 \"Command Syntax: Doing Stuff\"
Section 1: Manipulating Objects
----------
You've already noticed that Dead Souls, like most modern LP muds,
uses natural-like command syntax, like:
%^GREEN%^read first handbook%^RESET%^
rather than:
%^GREEN%^read handbook 1%^RESET%^
This is because Dead Souls uses a natural language parser. It isn't
perfect, of course. If you try to \"put all apples from box in my bag after
opening them\" you won't see much success, but this will work:
%^GREEN%^open box%^RESET%^
%^GREEN%^open bag%^RESET%^
%^GREEN%^get apples from box%^RESET%^
%^GREEN%^put apples in bag%^RESET%^
The parser will understand \"the first box\" or \"my second bag\",
assuming those objects exist in your inventory or in your environment.
If you want to know what is in the box, the command is:
%^GREEN%^look in box%^RESET%^
The command \"look at box\" or \"examine box\" will usually *not* show
you the contents of that box. This is because normally, boxes are
opaque, and in the real world, just looking at a box is rarely enough
to see what it contains as well.
An exception to this rule are transparent containers (a glass
trophy case, perhaps) whose interior is always visible from the
outside.
Sometimes looking at an object reveals its contents because of
the nature of the object. A table, for example, can have things on it,
and typing:
%^GREEN%^look at table %^RESET%^
...will usually let you know what is on it. It is also possible to
see what other players are carrying by just looking at them,
unless what they have is inside a container.
You'll want to remember that while you can \"put apple in bag\",
if you want to put that apple on a surface like a table, you'll
need to:
%^GREEN%^put apple on table%^RESET%^
You can give things to people, and they will automatically
accept them. However, you may not \"take\" or \"get\" things from living
beings. It's theirs, and it's up to them if they want to share.
You can try to \"steal sword from fighter\" if you dare, but unless
you have trained a lot, this is unlikely to succeed. We'll talk
more about training and skills in a later chapter.
Naturally you may also drop things you no longer need, though
it's nicer to your fellow mudders (and the mud's memory) to put
them in recycling bins so the bits can be reused.
Some other common object manipulation commands are:
close, donate, attack, eat, drink, listen, smell, search,
shoot, touch, turn.
There are many others you may find useful, but these will be
the ones you use most often to handle simple objects.
* A note about articles:
Dead Souls understands both definite and indefinite articles.
This means that you can refer to a specific apple, like so:
%^GREEN%^get the apple%^RESET%^
But you can also be unspecific. If there are a dozen
apples in a crate and you don't care which one you pick up:
%^GREEN%^get an apple from the crate%^RESET%^
Section 2: Navigation
---------
Moving around here is probably much like any other mud. You
can expect to move mostly in cardinal directions (like north and
northwest), but you may sometimes need to go up, down, or out.
Strictly speaking, the way to do this is:
%^GREEN%^go south%^RESET%^
%^GREEN%^go out%^RESET%^
...and so on, but this can get tedious after a while. Instead
of having to type in \"go\" plus the entire direction, the mud allows
you to enter shortcuts like \"sw\" for \"go southwest\" or \"u\" for
\"go up\".
When you enter a room, very often you will see letters in
brackets above the room description, like this: [n, u, out]
These are the \"obvious exits\" of that room, and help you
quickly find your way around without having to go through each
description. But remember! Just because a room has obvious exits
doesn't mean those are the only exits. Sometimes a room must
be searched to discover an exit, or there may be an exit available
that just doesn't happen to be very obvious.
If a room is dark, obvious exits may not be visible at all.
Aside from those ordinary means of travel, there are situations
that require more specific locomotion than just \"go\". These are
examples of the use of some other commands to get around:
%^GREEN%^jump onto road%^RESET%^
%^GREEN%^enter window%^RESET%^
%^GREEN%^climb ladder%^RESET%^
%^GREEN%^crawl east%^RESET%^ (if you are lying down and can't get up)
%^GREEN%^fly up%^RESET%^
%^GREEN%^follow thief%^RESET%^
%^GREEN%^evade hunter%^RESET%^
Section 3: Armor
-------
Now that you can manipulate objects and move around, you'll want
to be able to defend yourself, should the need arise. The special
object categories of \"weapons\" and \"armor\" should help.
Armor is an item that can be worn. That means that a pair of
blue jeans is considered armor, and a suit of chainmail is considered
armor as well. Basically, if you can wear it, it's \"armor\", because
whether it's a lot or a little, it protects you.
Assuming you are humanoid, you have the following limbs:
head, neck, torso, right arm, right hand, left arm,
left hand, right leg, right foot, left leg, left foot.
Properly coded armor must be worn on the corect limbs. Usually
a command like:
%^GREEN%^wear chainmail%^RESET%^
or
%^GREEN%^wear all%^RESET%^
...will cause you to automatically wear armor where it makes
most sense. However, it is possible to find armor that,
for example, can be worn either on your neck or your torso, like
an amulet. If this is so, you'll need to specify where you want it.
There are various types of armor, like cloak, pants, glove,
etc. Many of them overlap. You can wear a shirt on your torso as
well as a cloak and combat armor, but you may not wear two of
the same type. If you have a robe and a cape that are both cloaks,
you'll have to decide which one is going on.
You will find that shoes and gloves are often for one of your
hands but not the other. Sometimes you will find shoes, or gloves
that don't care which appendage they occupy, but usually these
are simply incorrectly coded.
If you are of some exotic or non-humanoid race, you may have
additional limbs to consider, and humanoid armor may not work for you.
Section 4: Weapons
---------
You may be surprised to learn that almost any manipulable object
can be wielded as a weapon, or thrown as a missile. You can wield
a can of Spam and try to kill an orc with it...and you may even succeed,
if you are strong and tough enough. Don't count on it, though,
and instead go for items that are made specifically with personal
security in mind.
There are four main types of weapons:
knife: knives, daggers
blade: like swords, and spears
blunt: like clubs, staves, and shillelaghs
projectile: things designed to be thrown, like darts or grenades
Unless it is a special device or magical item, weapons
must be wielded in order to be of use in combat. Some weapons,
like staves or pikes, may require the use of both hands. If this
is the case, wearing a shield may not be possible at the same time.
Like armor, weapons differ in quality and effectiveness. A
\"well-crafted sword\" is probably a better choice than a \"small
rusty knife\", but then again, you never know. Maybe rusty knives
are exactly what some monster is most vulnerable to.
Note also that, like armor, weapons wear down with use.
Examples of commands that involve weapons or fighting:
%^GREEN%^wield sword%^RESET%^
%^GREEN%^wield hammer in left hand%^RESET%^
%^GREEN%^wield staff in left hand and right hand%^RESET%^
%^GREEN%^unwield dagger%^RESET%^
%^GREEN%^shoot gun at otik%^RESET%^
%^GREEN%^throw dart at beggar%^RESET%^
%^GREEN%^kill all%^RESET%^ (this makes an enemy of everyone in the room)
%^GREEN%^ignore first orc%^RESET%^ (lets you concentrate on the other orcs)
%^GREEN%^ignore all%^RESET%^ (don't fight anyone in the room, even if they are attacking you)
%^GREEN%^target boss orc%^RESET%^ (this makes you ignore attacks from anyone else)
%^GREEN%^wimpy 30%^RESET%^ (this makes you run away if your health points drop below 30%)
Section 5: Miscellaneous Things to to with Things
---------
%^GREEN%^turn on flashlight%^RESET%^
%^GREEN%^turn off flashlight%^RESET%^
%^GREEN%^strike match%^RESET%^
%^GREEN%^light torch with match%^RESET%^
%^GREEN%^extinguish match%^RESET%^
%^GREEN%^dig hole with shovel%^RESET%^
%^GREEN%^move bed%^RESET%^
%^GREEN%^search %^RESET%^ (by default searches the room)
%^GREEN%^search rocks%^RESET%^
%^GREEN%^unlock east door with silver key%^RESET%^
%^GREEN%^bait pole with worm%^RESET%^
%^GREEN%^fish with pole%^RESET%^
%^GREEN%^stop fishing%^RESET%^
%^GREEN%^drop all%^RESET%^
%^GREEN%^donate 2 silver%^RESET%^
%^GREEN%^get all%^RESET%^
%^GREEN%^get all from corpse%^RESET%^
%^GREEN%^sell first right glove to otik%^RESET%^
%^GREEN%^sell all to otik%^RESET%^
%^GREEN%^buy sword from otik%^RESET%^
%^GREEN%^buy 8 from otik%^RESET%^ (get Otik to sell you item number 8)
",({"chapter 9","chapter nine","9",}):"chapter 9 \"Skills, teachers, and trainers\"
%^CYAN%^General stuff%^RESET%^
Stats are the physical attributes that let you do things.
Skills are those things you do. \"blunt attack\", for example,
is the skill that determines how good you are at using
blunt weapons in combat...while \"blunt defense\" is how
good you are at *avoiding* damage from such weapons.
\"melee attack\" and \"melee defense\" determine your proficiency
at unarmed combat, etc. It is very rare for players who
are not fighters to be able to physically fight without weapons,
by the way.
If the \"skills\" command does not list a particular skill
that means you don't have it at all, and whatever it is, you
are very, very bad at it. Mages, for example, usually lack
\"blade attack\" and \"blade defense\" entirely...meaning they
are utterly awful at fighting with swords and avoiding
swords in combat.
Some skills seem useless but are actually quite important.
For example, mages can cast a spell called \"whip\" which
summons up an energy weapon whose power can be devastating
if the mage is high-level. However, if the mage's conjuring
skill is low, the whip does not last long...meaning she will
have to keep casting this expensive spell frequently.
Skill levels, like stat levels, increase when a player's
player level is promoted, depending on the skill class. For
a fighter, \"blunt attack\" is a major priority, and so their
skill class for this is 1. For another class, it may differ.
If the skill class is a high priority, then the skill
improves more often when your player level increases.
There is no \"deviation\" command to manipulate skill class.
Emphasis on skill classes is what player classes (like
\"fighter\", \"cleric\", etc) are for.
%^CYAN%^Trainers%^RESET%^
Trainers are folks that can help you improve your skills.
When your player level goes up, you are awarded \"training
points\". You use these training points by asking trainers to
train you on some specific skill. For example, a mage can
go to Radagast (east, south, up, from the town clocktower)
and: %^GREEN%^ask radagast to train magic attack%^RESET%^
If you're a fighter, Radagast will not be able to help you,
since you do not already posess that skill. Trainers can
only train you in skills you already have some knowledge of.
Instead, a fighter might go to Roshd Burlyneck (down,
south, west, north, of the Ylsrim tower top) and:
%^GREEN%^ask roshd to train knife defense%^RESET%^
At this point, a human player might run into a problem. Roshd
is an orc, you see, and speaks Tangetto. He can't train
you if you don't understand that language. This is where
a different kind of instruction comes in...
%^CYAN%^Teachers%^RESET%^
Teachers are a special kind of trainer than can also teach
you things you did not already know. Typically this would be
a language teacher (like Bugg: east, east, east, north, of
the village clocktower), or a spell teacher (Herkimer: south,
west, south, of the village clocktower).
%^RED%^PROTIP%^RESET%^: You don't have to be a mage in order to have
Herkimer teach you a spell. As long as you have some magical
ability, and have the money, he will teach you some simple
spells whether you are a mage or not. Mages get to learn them
free, however, and Herkimer saves some spells for mages only.
Trainers and teachers are where having a high intelligence stat
can really pay off. It takes fewer lessons to improve your language
or your skills if you have high intelligence.
",({"chapter 3","chapter three","3",}):"chapter 3 \"Your Health and Abilities\"
In the previous chapter you learned the basics of getting
around and taking care of yourself. It's important also to care
*for* yourself, and this chapter describes the various aspects of
your body's state and what abilities you may have.
The command that tells you almost everything you need to
know is \"stat\". This diplays a whole lot of stuff, perhaps some of
it completely unfamiliar. Let's start at the top, using my output
as an example.
First line:
----------
%^CYAN%^Cratylus aka Cratylus the unaccomplished, level 10 male human Explorer%^RESET%^
Here you see my short name, my name with title, my level, my
gender, my race, and my class. Let's go over each.
* short name: What a person would use to address you. \"look at cratylus\",
for example.
* name with title: This displays my title. Creators can have whatever title
they want. Players can only have the titles they earn. As a player, a
title is usually earned when you are promoted a level or complete a
quest, though it is not always so on every mud.
* level: This is a measure of your overall experience, expertise, and
all-around game status. Being promoted a level means your skills,
health, and vital statistics increase. This often means you can handle
tougher monsters, for example, or tackle more challenging quests, learn
new spells, and so on.
* gender: This has no effect on your status. It is a cosmetic feature
of your body that is only useful to you in the social context of your
fellow mud players.
* race: In Dead Souls, race has nothing to do with your local genetic
makeup on planet Earth. In the mud, \"race\" refers to what one typically
would call \"species\" in real-life. An example of a race other than human
might be \"orc\" or \"feline\". Not all races are available for players. Once
you have chosen a race to play, it is in theory possible to change it, but
there is a nonzero chance you'll hose up your player file and lose your
character forever. Besides, it's up to your local admins whether race
changing is permitted on your mud. Different races have different
abilities. Elves see better in darkness, for example. Orcs are stronger
than some other humanoids, but dumber, too (which does affect gameplay).
* class: This can be considered an occupational specialty. In the real
world you have plumbers, doctors, soldiers, etc. In the mud world,
we can have explorers, fighters, mages, and the like. Each class brings
its own unique advantages and disadvantages to your gameplay. A fighter
can really kick more butt in melee combat than a mage, but a mage
gets to cast powerful spells. Explorers are a middle of the road class
that gives you a bit of everything without specializing in anything.
Next line:
----------
%^CYAN%^Alive / Awake%^RESET%^
It is indeed possible for your virtual body to cease life functions.
When this happens your spirit speeds off to the land of the dead,
where you drift until you decide to \"regenerate\" and regain your
physical form. Except for some special magical items, anything you
were carrying when you died is with that dead body, so it's a good
idea to rush your new body back to the scene of the fatality and get
your stuff back before someone else grabs it. Death is not only
inconvenient, it also incurs major penalties on your statistics, so it
should be avoided.
It is also possible to sleep. If you are drunk and asleep, your
injuries will heal more quickly. It's magic, don't worry about the
logic behind it.
If you are attacked while sleeping, you will wake up. You can
force yourself awake, too, but it's a bit tedious.
Next line:
---------
%^CYAN%^Health: 350/350 Magic: 560/560 Stamina: 400/400 Carry: 1184/1300%^RESET%^
In each case, the number on the left of the slash indicates the
current level, and the number on the right indicates what the maximum is.
health: When I am 100% healthy, I have a total of 350 hp. If my hp ever
reach 0 or less (!), I die. Poison and illness can cause hp's to
gradually decrease, and although with time my hp's will normally
return to 350 as I heal, poison and illness can slow down that healing
or even cause me to die. Injury in combat is the most common source
of hp loss, though spells, falls, and other adverse events can cause
you injury or death.
magic: I cast magic missile! Spell casting takes a toll on your magical
abilities, and mp measure how much magic you've got left in you at
any given point. Like hp, mp gradually grow back to your max if you
avoid spellcasting for a while.
stamina: Fighting is tough work, and swinging around swords while
getting bashed with hammers really takes a lot out of a guy. Therefore
keep an eye on this stat while you're fighting, because if it gets too
low you will collapse and be unable to do anything for a while.
carry: Objects have mass, and your body is of limited size and strength.
My carry capacity is 0 when I carry nothing, and 1300 when I can
carry no more. Creators are allowed to exceed their bodies' carry
capacity, but players cannot.
Next line:
---------
%^CYAN%^Food: 0 Drink: 0 Alcohol: 0 Caffeine: 0 Poison: 0 %^RESET%^
These are pretty self-explanatory. Alcohol is good for healing,
bad for fighting. Food and drink also help speed healing. Poison
has the opposite effect. Caffeine can speed up your combat slightly,
but tends to prevent full rest.
You will not die from lack of food or lack of drink, but you
will do better with a body not starved for nutrients.
Your maximum load for any of these is not fixed, and varies
depending on many factors, such as level, endurance, etc.
Next line:
---------
%^CYAN%^Training Points: 0 Quest Points: 0 Experience Points: 50 %^RESET%^
Training points can be cashed in with special NPC's called
trainers, who can help you improve some skills. A trainer that
specializes in fighting might be able to raise your \"blade attack\"
skill, for example. you earn training points when you are
promoted a level.
Quest points are awarded when you complete a quest. In
the default version of Dead Souls, you cannot advance past a
certain player level unless you earn some qp's. Read the sign
in the adventurers guild for more details on this.
Experience points can be awarded for various reasons: completing
a quest, solving a puzzle, winning a contest. Most often you
will receive xp after killing an NPC. The amount of xp awarded
will depend on the level of the NPC. Like qp, xp are needed to
qualify for level advancement.
Limb section:
------------
Remember how wearing armor requires the right body parts?
Well here they are, and this is their health. You can issue the
\"body\" command for a quicker self-check.
Let's look at what the numbers mean with an example:
%^CYAN%^left leg (2) 160/160%^RESET%^
Obviously the first item identifies the limb in question.
The (2) is a kind of \"importance score\", indicating how critical
a body part is. If this number is (1), like the head, it means that
losing that limb causes immediate death.
The number on the right side of the slash indicates the hit point
damage you may receive on that limb before it is severed. The number
on the left is how many of those hits you have left.
It doesn't mean my leg has 160 of my hitpoints. If that were true,
my hit points would add up to a heck of a lot more than 350.
This means that if I've lost, say, 200hp fighting a troll, and
159hp of those hits were on my left leg, getting hit there again means I
lose my left leg.
I would then collapse and have to crawl away to seek medical attention.
Wearing armor on your limbs is a great way to minimize the danger of
this happening.
Skills section:
--------------
Let's review skills by examining one of mine:
%^CYAN%^blade attack (1) 00% - 20/24%^RESET%^
This measures how likely I am to hit an opponent when I
use a blade, and how good a hit it was. The number (1) means
that this is a skill critical to my class. If an explorer can't
swing a sword, he oughta think about another line
of work.
The 00% means I have thus far earned no blade attack
experience toward achieving the next level of this skill.
The 20 is my current proficiency level.
The 24 is the maximum level I can reach at my current
player level and with my current stats.
What's all this mean? Well, if I practice a lot of blade
attacking, that 00% will gradually climb up to 99, and one more
point causes me to go from a level 20 slicer of things to a
level 21 slicer of things. This increases my likelihood of
hitting my target in the future.
Meaning, in short, practice a skill, and you'll get
better at it.
Of course, if my blade attack level reaches 24, I can advance
my blade attack skills no further until my player level rises.
Stats section:
-------------
Remember these from Dungeons & Dragons? No? Well these vital
statistics measure your general giftedness in that feature of your
body. Let's look at one of mine:
%^CYAN%^coordination (2) 42/42%^RESET%^
Coordination is one of those important stats for fighting and
such. The more coordinated you are, the more likely you are to hit your
target. The (2) indicates that this stat is important to my class,
but not critical. This influences its effect on my skills.
42/42 means that my coordination is not currently impaired. If
someone cast a \"stumble\" spell on me, for example, this might look more
like 30/42, and if I were drunk, it would look very shabby indeed.
New characters should avail themselves of the \"customize\" command.
When you create a character, you are assigned stats based on
random numbers modified by the race you choose. For example, humans
are physically weaker than other races, so you might have a
strength of 15 as a human, whereas a dwarf might expect something
like 42. On the other hand, humans tend to be quite smart, and so
your human character might have a high intelligence stat, and
the dwarf a substantially lower one. To balance out stats that
are grossly unfair, new characters are given 15 points to spend
to add to their stats. As a human with 15 strength, you might
choose to throw all your customization points into strength, adding
up to a whopping 30. Or you might choose to distribute points
among your stats in a manner most suited to your playing style.
For syntax and details, type:
help customize
Last section:
------------
\"Cratylus has amassed a net worth of 11 gold.\" means that when you
add up the money in my bank accounts and the money I'm carrying,
converted to gold, I have 11 gold to my name. It looks bad, but gold
is actually quite valuable in the default Dead Souls economy.
\"Money on hand: 79 dollars, 34 silver\" means that this is the amount of
money I'm carrying. Don't forget that the amount of money you are carrying
affects your overall carry capacity. Gold is an especially heavy
currency.
Final notes:
-----------
\"stat\" is a great command to get thorough information about
yourself. It is, however, quite a screenful. Briefer reports can be
viewed with the following commands:
%^GREEN%^body%^RESET%^
%^GREEN%^skills%^RESET%^
%^GREEN%^stats%^RESET%^
%^GREEN%^score%^RESET%^
%^GREEN%^status%^RESET%^
",({"chapter 4","chapter four","4",}):"chapter 4 \"Quests\"
Some muds don't have quests, and the fun people have is
through role-playing and social activities with other players.
Other muds prefer to concentrate on killing lots and lots
of monsters, a lot, over and over.
Quests give you a chance to problems-solve by performing some
series of actions that satisfies a pre-determined requirement.
For example, Dead Souls's sample town contains a quest called
Orcslayer. Leo the archwizard lives in the basement of the old
abandoned church, and he has lost a powerful magic sword called
\"Orcslayer\". If you return it to him, he will reward you with
experience points, quest points, and a new title you can use. To
complete the quest, you need to defeat the warrior orcs, penetrate
deep into their lair, defeat the orc shaman, and take Orcslayer from
his corpse, then go to the church basement and give the sword to Leo.
In this case, if you're a level 1 newbie, the orcs will massacre you
before you get anywhere near the shaman. So either team up with
friends to tackle the orcs together, or raise your level to the
point where you're tough enough to take them on.
To raise your level, wander around in the newbie mansion, which
is south of the village church.
There's lots of loot there you can sell at Otik's shop, and with the
cash you can then get some proper weaponry and armor.
Silver is heavy, so don't try to carry all your money around
all the time. Request an account from Zoe the banker and keep your
money there until you really need it.
There is a quest in the newbie mansion, and solving it by
finding the secret room will give you experience and quest points too.
(hint, there might be more than one secret room)
Once you have enough experience and/or points, go to
Dirk in the adventurers hall and \"%^GREEN%^ask dirk to advance%^RESET%^\".
Make sure you learn some spells from Herkimer, because if you
go up against a bunch of orcs in their lair, you'll want spells to
shield you from attacks, and spells to recover your strength after
combat. As a non-mage, your spell abilities will be limited at
lower levels, but as you gain levels you'll get better. Also, spells
will rarely work after you first learn them. Keep casting them,
even if you screw them up, so that your magic skills increase.
Also, save your money. Drinking and sleeping help you heal,
but not fast enough. By the time those natural processes finish
and you're ready for combat again, the orcs may have gotten reinforcements.
So if you can afford it, buy healing slips and use them at Clepius's
healers guild. His treatment is expensive, but you will heal much
more quickly.
In the tragic event of the loss of a limb, Clepius can also magically
regenerate a new limb...but obviously at some great cost.
There. I've just spoiled the Orcslayer quest for you. Normally,
all you'd know about a quest is a cryptic clue, like the one in the
scroll in the adventurers guild. Instead I've just spoiled the quest
for you by telling you all about it. They're more fun when you have to
figure them out on your own, like puzzles.
Normally, spoiling quests like this is a bannable offense on
a mud, so if you solve a quest, keep it to yourself unless you know
the admins on your mud don't mind.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Note to New Creators\"
You should probably hang on to this book for reference. If
you lose it, pick up a copy at the adventurers hall.
However, you need to start reading the Creators Manual. If
you don't have one on you, get the one in the chest in your workroom.
If you're new to coding, start with chapter 31. It'll
get you started with the Quick Creation System, or QCS.
Cratylus @ Frontiers
04 Jan 2006
",({"chapter 9","chapter nine","9",}):"chapter 9 \"Skills, teachers, and trainers\"
%^CYAN%^General stuff%^RESET%^
Stats are the physical attributes that let you do things.
Skills are those things you do. \"blunt attack\", for example,
is the skill that determines how good you are at using
blunt weapons in combat...while \"blunt defense\" is how
good you are at *avoiding* damage from such weapons.
\"melee attack\" and \"melee defense\" determine your proficiency
at unarmed combat, etc. It is very rare for players who
are not fighters to be able to physically fight without weapons,
by the way.
If the \"skills\" command does not list a particular skill
that means you don't have it at all, and whatever it is, you
are very, very bad at it. Mages, for example, usually lack
\"blade attack\" and \"blade defense\" entirely...meaning they
are utterly awful at fighting with swords and avoiding
swords in combat.
Some skills seem useless but are actually quite important.
For example, mages can cast a spell called \"whip\" which
summons up an energy weapon whose power can be devastating
if the mage is high-level. However, if the mage's conjuring
skill is low, the whip does not last long...meaning she will
have to keep casting this expensive spell frequently.
Skill levels, like stat levels, increase when a player's
player level is promoted, depending on the skill class. For
a fighter, \"blunt attack\" is a major priority, and so their
skill class for this is 1. For another class, it may differ.
If the skill class is a high priority, then the skill
improves more often when your player level increases.
There is no \"deviation\" command to manipulate skill class.
Emphasis on skill classes is what player classes (like
\"fighter\", \"cleric\", etc) are for.
%^CYAN%^Trainers%^RESET%^
Trainers are folks that can help you improve your skills.
When your player level goes up, you are awarded \"training
points\". You use these training points by asking trainers to
train you on some specific skill. For example, a mage can
go to Radagast (east, south, up, from the town clocktower)
and: %^GREEN%^ask radagast to train magic attack%^RESET%^
If you're a fighter, Radagast will not be able to help you,
since you do not already posess that skill. Trainers can
only train you in skills you already have some knowledge of.
Instead, a fighter might go to Roshd Burlyneck (down,
south, west, north, of the Ylsrim tower top) and:
%^GREEN%^ask roshd to train knife defense%^RESET%^
At this point, a human player might run into a problem. Roshd
is an orc, you see, and speaks Tangetto. He can't train
you if you don't understand that language. This is where
a different kind of instruction comes in...
%^CYAN%^Teachers%^RESET%^
Teachers are a special kind of trainer than can also teach
you things you did not already know. Typically this would be
a language teacher (like Bugg: east, east, east, north, of
the village clocktower), or a spell teacher (Herkimer: south,
west, south, of the village clocktower).
%^RED%^PROTIP%^RESET%^: You don't have to be a mage in order to have
Herkimer teach you a spell. As long as you have some magical
ability, and have the money, he will teach you some simple
spells whether you are a mage or not. Mages get to learn them
free, however, and Herkimer saves some spells for mages only.
Trainers and teachers are where having a high intelligence stat
can really pay off. It takes fewer lessons to improve your language
or your skills if you have high intelligence.
",({"chapter 8","chapter eight","8",}):"chapter 8 \"Stats details\"
Stats are very important! Depending on the style of
play you prefer, different stats can be of more value
to you. Fighters may prefer strength, mages
may favor intelligence.
%^RED%^IMPORTANT TIP%^RESET%^: Players start off with 15 stat points
they can add to their stats...so that if you find your
strength is abysmally low, you can raise it to where
your character is playable at lower levels. Don't
forget to type: %^GREEN%^help customize%^RESET%^
%^CYAN%^About \"stat classes\"%^RESET%^: Depending on the race you choose at
character creation, your stat class will be something
in the range of 1 to 5. This is a measure of the stat's
typical priority for that race. For example, orcs are very
strong, and so not only do they usually start with a
high strength stat, their strength class is 1, meaning
that every time an orc has a level promotion, their strength
stat is also raised by 1. Humans, however, have a strength
stat class of 3, meaning a human player's strength stat
is increased by 1 only every third level promotion.
%^CYAN%^Deviation%^RESET%^:Starting in Dead Souls 2.9a18, a player can choose to
\"deviate\" from her race's default stat classes. For example,
you *can* have an orc mage with a high-priority intelligence
stat. To do so, you use the deviate command to set your
stat deviation...meaning you're going to devote a certain
percent of the XP you earn to making yourself better at
some stats you choose. Naturally, the more you want to
change a stat class, the more expensive this will be in
terms of your XP deviance cost. For more information,
see: %^GREEN%^help deviate%^RESET%^
%^CYAN%^Some notes on individual stats%^RESET%^:
High %^MAGENTA%^strength%^RESET%^ means you hit hard when you're in combat,
but it also means you might be strong enough to perform some
tasks others cannot, like open very heavy metal doors.
High %^MAGENTA%^intelligence%^RESET%^ means you're able to learn things
better, quicker, like skill training and languages. It
means your training points do you more good.
High %^MAGENTA%^charisma%^RESET%^ means it's easier to befriend animals,
which is important if you want to, for example, mount
a horse:
%^GREEN%^befriend horse%^RESET%^
%^GREEN%^mount horse%^RESET%^
%^MAGENTA%^Agility%^RESET%^ and %^MAGENTA%^coordination%^RESET%^ are important for avoiding being
hit, and for certain special abilities like stealth,
lock picking, shooting, etc.
%^MAGENTA%^Wisdom%^RESET%^ is important for some types of spells.
%^MAGENTA%^Durability%^RESET%^ helps determine how much stamina you have...
this is vital to lasting through long combat or moving
quickly through terrain on foot.
Finally, %^MAGENTA%^luck%^RESET%^ is an all-around general good thing to have
lots of...some random situations may call for it, and just
like in real life, it's hard to know when it'll come in handy,
but boy can it mean the difference between win and fail!
",({"chapter 5","chapter five","5",}):"chapter 5 \"Communication\"
There are many ways to communicate with other players. If
you're in the same room as your intended listener, you can just
use the \"say\" command, like this:
%^GREEN%^say hi, crat%^RESET%^
If the message is secret, you can \"whisper\":
%^GREEN%^whisper to cratylus are you idle?%^RESET%^
If you want to say something that everyone in the mud can
hear, use the \"shout\" command (at the cost of a lot of stamina):
%^GREEN%^shout hey crat, wheredya go?%^RESET%^
Or, if it's an important secret and the target is not in the
same room as you, you can use the magical \"tell\" command:
%^GREEN%^tell cratylus are you mad at me or something?%^RESET%^
There are also special communication lines on the mud that are
class or role-specific. For example, if you type:
%^GREEN%^newbie does anyone know what's up with cratylus?%^RESET%^
All people who are tuned into the newbie line will get
your message. To see what lines are available to you, type:
%^GREEN%^lines%^RESET%^
To see who is listening to the newbie channel:
%^GREEN%^list newbie%^RESET%^
To see who is listening to some other channel on some other mud:
%^GREEN%^list otherchannel@othermud%^RESET%^
To enable or disable a line, just type the name of it with no message.
To see a brief history of the past few messages on a line (in
this case, the newbie line), type:
%^GREEN%^hist newbie%^RESET%^
Spamming lines is rude and probably dangerous to your character, so
be sure you comply with your mud's rules on lines.
Your mud may be on the intermud network. To find out, type the
command:
%^GREEN%^mudlist%^RESET%^
If a list of muds comes up, you know your mud is probably
on the intermud3 communication network. Dead Souls by default restricts
players from access to intermud channels, but you can \"tell\" to
players on other muds, if you want. If you think your friend Xyzzy
is online on a mud on intermud3, you can issue this command:
%^GREEN%^locate xyzzy%^RESET%^
If he's logged into a mud on i3, you will get something like:
Xyzzy was just located on Frontiers. (idle 00:03:17) [status: inactive]
You can then tell to him:
%^GREEN%^tell xyzzy@frontiers dude, what's the deal with crat lately?%^RESET%^
Sometimes a player or NPC does not understand your character's
native tongue. For example, if you are en elf, your native tongue is
not English, it is Edhellen. If someone talks to you in English, you
might see something like this:
Xyzzy says in English, \"leka mifahmam, potong-hwa.\"
Since your character doesn't speak English, what you see is gibberish.
If you find a language teacher, your proficiency in the language they
teach you will allow you to understand more of the words you hear.
Suppose that your elf character is now 100% fluent in English.
If you greet a human player named Xyzzy by typing:
%^GREEN%^say hello there, xyzzy%^RESET%^
Xyzzy will probably see something like:
Noobie says in Edhellen, \"pericolo temak, forshtor.\"
Instead, if you want to speak to a human, you'll have to type:
%^GREEN%^speak in english hello there, xyzzy%^RESET%^
To find out what languages you speak, type:
%^GREEN%^language%^RESET%^
",({"chapter 9","chapter nine","9",}):"chapter 9 \"Skills, teachers, and trainers\"
%^CYAN%^General stuff%^RESET%^
Stats are the physical attributes that let you do things.
Skills are those things you do. \"blunt attack\", for example,
is the skill that determines how good you are at using
blunt weapons in combat...while \"blunt defense\" is how
good you are at *avoiding* damage from such weapons.
\"melee attack\" and \"melee defense\" determine your proficiency
at unarmed combat, etc. It is very rare for players who
are not fighters to be able to physically fight without weapons,
by the way.
If the \"skills\" command does not list a particular skill
that means you don't have it at all, and whatever it is, you
are very, very bad at it. Mages, for example, usually lack
\"blade attack\" and \"blade defense\" entirely...meaning they
are utterly awful at fighting with swords and avoiding
swords in combat.
Some skills seem useless but are actually quite important.
For example, mages can cast a spell called \"whip\" which
summons up an energy weapon whose power can be devastating
if the mage is high-level. However, if the mage's conjuring
skill is low, the whip does not last long...meaning she will
have to keep casting this expensive spell frequently.
Skill levels, like stat levels, increase when a player's
player level is promoted, depending on the skill class. For
a fighter, \"blunt attack\" is a major priority, and so their
skill class for this is 1. For another class, it may differ.
If the skill class is a high priority, then the skill
improves more often when your player level increases.
There is no \"deviation\" command to manipulate skill class.
Emphasis on skill classes is what player classes (like
\"fighter\", \"cleric\", etc) are for.
%^CYAN%^Trainers%^RESET%^
Trainers are folks that can help you improve your skills.
When your player level goes up, you are awarded \"training
points\". You use these training points by asking trainers to
train you on some specific skill. For example, a mage can
go to Radagast (east, south, up, from the town clocktower)
and: %^GREEN%^ask radagast to train magic attack%^RESET%^
If you're a fighter, Radagast will not be able to help you,
since you do not already posess that skill. Trainers can
only train you in skills you already have some knowledge of.
Instead, a fighter might go to Roshd Burlyneck (down,
south, west, north, of the Ylsrim tower top) and:
%^GREEN%^ask roshd to train knife defense%^RESET%^
At this point, a human player might run into a problem. Roshd
is an orc, you see, and speaks Tangetto. He can't train
you if you don't understand that language. This is where
a different kind of instruction comes in...
%^CYAN%^Teachers%^RESET%^
Teachers are a special kind of trainer than can also teach
you things you did not already know. Typically this would be
a language teacher (like Bugg: east, east, east, north, of
the village clocktower), or a spell teacher (Herkimer: south,
west, south, of the village clocktower).
%^RED%^PROTIP%^RESET%^: You don't have to be a mage in order to have
Herkimer teach you a spell. As long as you have some magical
ability, and have the money, he will teach you some simple
spells whether you are a mage or not. Mages get to learn them
free, however, and Herkimer saves some spells for mages only.
Trainers and teachers are where having a high intelligence stat
can really pay off. It takes fewer lessons to improve your language
or your skills if you have high intelligence.
",({"chapter 6","chapter six","6",}):"chapter 6 \"Note to New Creators\"
You should probably hang on to this book for reference. If
you lose it, pick up a copy at the adventurers hall.
However, you need to start reading the Creators Manual. If
you don't have one on you, get the one in the chest in your workroom.
If you're new to coding, start with chapter 31. It'll
get you started with the Quick Creation System, or QCS.
Cratylus @ Frontiers
04 Jan 2006
",({"chapter 5","chapter five","5",}):"chapter 5 \"Communication\"
There are many ways to communicate with other players. If
you're in the same room as your intended listener, you can just
use the \"say\" command, like this:
%^GREEN%^say hi, crat%^RESET%^
If the message is secret, you can \"whisper\":
%^GREEN%^whisper to cratylus are you idle?%^RESET%^
If you want to say something that everyone in the mud can
hear, use the \"shout\" command (at the cost of a lot of stamina):
%^GREEN%^shout hey crat, wheredya go?%^RESET%^
Or, if it's an important secret and the target is not in the
same room as you, you can use the magical \"tell\" command:
%^GREEN%^tell cratylus are you mad at me or something?%^RESET%^
There are also special communication lines on the mud that are
class or role-specific. For example, if you type:
%^GREEN%^newbie does anyone know what's up with cratylus?%^RESET%^
All people who are tuned into the newbie line will get
your message. To see what lines are available to you, type:
%^GREEN%^lines%^RESET%^
To see who is listening to the newbie channel:
%^GREEN%^list newbie%^RESET%^
To see who is listening to some other channel on some other mud:
%^GREEN%^list otherchannel@othermud%^RESET%^
To enable or disable a line, just type the name of it with no message.
To see a brief history of the past few messages on a line (in
this case, the newbie line), type:
%^GREEN%^hist newbie%^RESET%^
Spamming lines is rude and probably dangerous to your character, so
be sure you comply with your mud's rules on lines.
Your mud may be on the intermud network. To find out, type the
command:
%^GREEN%^mudlist%^RESET%^
If a list of muds comes up, you know your mud is probably
on the intermud3 communication network. Dead Souls by default restricts
players from access to intermud channels, but you can \"tell\" to
players on other muds, if you want. If you think your friend Xyzzy
is online on a mud on intermud3, you can issue this command:
%^GREEN%^locate xyzzy%^RESET%^
If he's logged into a mud on i3, you will get something like:
Xyzzy was just located on Frontiers. (idle 00:03:17) [status: inactive]
You can then tell to him:
%^GREEN%^tell xyzzy@frontiers dude, what's the deal with crat lately?%^RESET%^
Sometimes a player or NPC does not understand your character's
native tongue. For example, if you are en elf, your native tongue is
not English, it is Edhellen. If someone talks to you in English, you
might see something like this:
Xyzzy says in English, \"leka mifahmam, potong-hwa.\"
Since your character doesn't speak English, what you see is gibberish.
If you find a language teacher, your proficiency in the language they
teach you will allow you to understand more of the words you hear.
Suppose that your elf character is now 100% fluent in English.
If you greet a human player named Xyzzy by typing:
%^GREEN%^say hello there, xyzzy%^RESET%^
Xyzzy will probably see something like:
Noobie says in Edhellen, \"pericolo temak, forshtor.\"
Instead, if you want to speak to a human, you'll have to type:
%^GREEN%^speak in english hello there, xyzzy%^RESET%^
To find out what languages you speak, type:
%^GREEN%^language%^RESET%^
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Hints and tips\"
* The \"wimpy\" command helps you avoid death due to inattention or
network lag. If you \"wimpy 20\", you will automatically try to escape
combat if your health goes below 20% of your maximum.
* \"target\" and \"ignore\" are extremely useful when fighting more than
one enemy. You should always target the toughest npc first, and
always ignore any npc who can't get up because their foot or leg
is severed.
But if they collapse due to exhaustion, it's a good idea to keep
beating on them, otherwise they may get back up and get healthy sooner
than you expect.
* By default, different races speak different languages. If someone
says something to you and you see no words in the same language as
the rest of the mud, it means they are speaking a language you do
not understand.
For example, if you are an elf, and you ask Radagast to teach
magic attack, you might get something like this:
Radagast exclaims in English, \"embleer con boltehe oota goota nehi auch\"
Even though in the real world you may speak English fluently, in
the mud world, you do not speak English fluently. As an elf, your
native tongue is Edhellen, and you may find human speech incomprehensible.
If you find a trainer to teach you English, your skills in that
language will need time to improve. As you get better at a language,
you will see fewer gibberish words.
If you are a \"newbie\", this does not apply to you. A newbie in the
default Dead Souls distribution is a player at level 4 or below. This
definition may be changed by your admin.
Newbies need all the help they can get just to survive, so they
are magically granted understanding of all languages, until they outgrow
their naivete.
If you are a student of languages in the Real World, you may
recognize many of the \"gibberish\" words used by Dead Souls to represent
a foreign tongue. Your understanding of these words is not useful in
the context of the game, however, because they are not intended to
convey meaning other than \"non-comprehensible words\".
* Your ability to see is affected by various things:
- A room's ambient light level
- Time of day
- Local light sources (flashlights, torches, etc)
- Your race's light sensitivity
- Magical effects
- Exposure to an excessive-light event
It's important to remember that a room may be too dark for
you to see everything in it. You might be able to see the description
of a room with no problem, but it may be necessary for you to
light a torch in order to see the treasure chest there.
In the same way that darkness can impair vision, brightness
can do the same. For elves, an outdoor area in bright sunlight that
contains additional light sources can be just as hostile to
vision as a dark cave with no torch would be for a human.
Regardless of race, a sufficiently adverse event, such as
a bright flash or special spell, can render you temporarily blind.
As with languages, newbies have some exemption to light-level
limitations.
* Mages can wield knives but are pretty much helpless with any other
vind of edged weapon.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Command Syntax: Doing Stuff\"
Section 1: Manipulating Objects
----------
You've already noticed that Dead Souls, like most modern LP muds,
uses natural-like command syntax, like:
%^GREEN%^read first handbook%^RESET%^
rather than:
%^GREEN%^read handbook 1%^RESET%^
This is because Dead Souls uses a natural language parser. It isn't
perfect, of course. If you try to \"put all apples from box in my bag after
opening them\" you won't see much success, but this will work:
%^GREEN%^open box%^RESET%^
%^GREEN%^open bag%^RESET%^
%^GREEN%^get apples from box%^RESET%^
%^GREEN%^put apples in bag%^RESET%^
The parser will understand \"the first box\" or \"my second bag\",
assuming those objects exist in your inventory or in your environment.
If you want to know what is in the box, the command is:
%^GREEN%^look in box%^RESET%^
The command \"look at box\" or \"examine box\" will usually *not* show
you the contents of that box. This is because normally, boxes are
opaque, and in the real world, just looking at a box is rarely enough
to see what it contains as well.
An exception to this rule are transparent containers (a glass
trophy case, perhaps) whose interior is always visible from the
outside.
Sometimes looking at an object reveals its contents because of
the nature of the object. A table, for example, can have things on it,
and typing:
%^GREEN%^look at table %^RESET%^
...will usually let you know what is on it. It is also possible to
see what other players are carrying by just looking at them,
unless what they have is inside a container.
You'll want to remember that while you can \"put apple in bag\",
if you want to put that apple on a surface like a table, you'll
need to:
%^GREEN%^put apple on table%^RESET%^
You can give things to people, and they will automatically
accept them. However, you may not \"take\" or \"get\" things from living
beings. It's theirs, and it's up to them if they want to share.
You can try to \"steal sword from fighter\" if you dare, but unless
you have trained a lot, this is unlikely to succeed. We'll talk
more about training and skills in a later chapter.
Naturally you may also drop things you no longer need, though
it's nicer to your fellow mudders (and the mud's memory) to put
them in recycling bins so the bits can be reused.
Some other common object manipulation commands are:
close, donate, attack, eat, drink, listen, smell, search,
shoot, touch, turn.
There are many others you may find useful, but these will be
the ones you use most often to handle simple objects.
* A note about articles:
Dead Souls understands both definite and indefinite articles.
This means that you can refer to a specific apple, like so:
%^GREEN%^get the apple%^RESET%^
But you can also be unspecific. If there are a dozen
apples in a crate and you don't care which one you pick up:
%^GREEN%^get an apple from the crate%^RESET%^
Section 2: Navigation
---------
Moving around here is probably much like any other mud. You
can expect to move mostly in cardinal directions (like north and
northwest), but you may sometimes need to go up, down, or out.
Strictly speaking, the way to do this is:
%^GREEN%^go south%^RESET%^
%^GREEN%^go out%^RESET%^
...and so on, but this can get tedious after a while. Instead
of having to type in \"go\" plus the entire direction, the mud allows
you to enter shortcuts like \"sw\" for \"go southwest\" or \"u\" for
\"go up\".
When you enter a room, very often you will see letters in
brackets above the room description, like this: [n, u, out]
These are the \"obvious exits\" of that room, and help you
quickly find your way around without having to go through each
description. But remember! Just because a room has obvious exits
doesn't mean those are the only exits. Sometimes a room must
be searched to discover an exit, or there may be an exit available
that just doesn't happen to be very obvious.
If a room is dark, obvious exits may not be visible at all.
Aside from those ordinary means of travel, there are situations
that require more specific locomotion than just \"go\". These are
examples of the use of some other commands to get around:
%^GREEN%^jump onto road%^RESET%^
%^GREEN%^enter window%^RESET%^
%^GREEN%^climb ladder%^RESET%^
%^GREEN%^crawl east%^RESET%^ (if you are lying down and can't get up)
%^GREEN%^fly up%^RESET%^
%^GREEN%^follow thief%^RESET%^
%^GREEN%^evade hunter%^RESET%^
Section 3: Armor
-------
Now that you can manipulate objects and move around, you'll want
to be able to defend yourself, should the need arise. The special
object categories of \"weapons\" and \"armor\" should help.
Armor is an item that can be worn. That means that a pair of
blue jeans is considered armor, and a suit of chainmail is considered
armor as well. Basically, if you can wear it, it's \"armor\", because
whether it's a lot or a little, it protects you.
Assuming you are humanoid, you have the following limbs:
head, neck, torso, right arm, right hand, left arm,
left hand, right leg, right foot, left leg, left foot.
Properly coded armor must be worn on the corect limbs. Usually
a command like:
%^GREEN%^wear chainmail%^RESET%^
or
%^GREEN%^wear all%^RESET%^
...will cause you to automatically wear armor where it makes
most sense. However, it is possible to find armor that,
for example, can be worn either on your neck or your torso, like
an amulet. If this is so, you'll need to specify where you want it.
There are various types of armor, like cloak, pants, glove,
etc. Many of them overlap. You can wear a shirt on your torso as
well as a cloak and combat armor, but you may not wear two of
the same type. If you have a robe and a cape that are both cloaks,
you'll have to decide which one is going on.
You will find that shoes and gloves are often for one of your
hands but not the other. Sometimes you will find shoes, or gloves
that don't care which appendage they occupy, but usually these
are simply incorrectly coded.
If you are of some exotic or non-humanoid race, you may have
additional limbs to consider, and humanoid armor may not work for you.
Section 4: Weapons
---------
You may be surprised to learn that almost any manipulable object
can be wielded as a weapon, or thrown as a missile. You can wield
a can of Spam and try to kill an orc with it...and you may even succeed,
if you are strong and tough enough. Don't count on it, though,
and instead go for items that are made specifically with personal
security in mind.
There are four main types of weapons:
knife: knives, daggers
blade: like swords, and spears
blunt: like clubs, staves, and shillelaghs
projectile: things designed to be thrown, like darts or grenades
Unless it is a special device or magical item, weapons
must be wielded in order to be of use in combat. Some weapons,
like staves or pikes, may require the use of both hands. If this
is the case, wearing a shield may not be possible at the same time.
Like armor, weapons differ in quality and effectiveness. A
\"well-crafted sword\" is probably a better choice than a \"small
rusty knife\", but then again, you never know. Maybe rusty knives
are exactly what some monster is most vulnerable to.
Note also that, like armor, weapons wear down with use.
Examples of commands that involve weapons or fighting:
%^GREEN%^wield sword%^RESET%^
%^GREEN%^wield hammer in left hand%^RESET%^
%^GREEN%^wield staff in left hand and right hand%^RESET%^
%^GREEN%^unwield dagger%^RESET%^
%^GREEN%^shoot gun at otik%^RESET%^
%^GREEN%^throw dart at beggar%^RESET%^
%^GREEN%^kill all%^RESET%^ (this makes an enemy of everyone in the room)
%^GREEN%^ignore first orc%^RESET%^ (lets you concentrate on the other orcs)
%^GREEN%^ignore all%^RESET%^ (don't fight anyone in the room, even if they are attacking you)
%^GREEN%^target boss orc%^RESET%^ (this makes you ignore attacks from anyone else)
%^GREEN%^wimpy 30%^RESET%^ (this makes you run away if your health points drop below 30%)
Section 5: Miscellaneous Things to to with Things
---------
%^GREEN%^turn on flashlight%^RESET%^
%^GREEN%^turn off flashlight%^RESET%^
%^GREEN%^strike match%^RESET%^
%^GREEN%^light torch with match%^RESET%^
%^GREEN%^extinguish match%^RESET%^
%^GREEN%^dig hole with shovel%^RESET%^
%^GREEN%^move bed%^RESET%^
%^GREEN%^search %^RESET%^ (by default searches the room)
%^GREEN%^search rocks%^RESET%^
%^GREEN%^unlock east door with silver key%^RESET%^
%^GREEN%^bait pole with worm%^RESET%^
%^GREEN%^fish with pole%^RESET%^
%^GREEN%^stop fishing%^RESET%^
%^GREEN%^drop all%^RESET%^
%^GREEN%^donate 2 silver%^RESET%^
%^GREEN%^get all%^RESET%^
%^GREEN%^get all from corpse%^RESET%^
%^GREEN%^sell first right glove to otik%^RESET%^
%^GREEN%^sell all to otik%^RESET%^
%^GREEN%^buy sword from otik%^RESET%^
%^GREEN%^buy 8 from otik%^RESET%^ (get Otik to sell you item number 8)
",({"chapter 3","chapter three","3",}):"chapter 3 \"Your Health and Abilities\"
In the previous chapter you learned the basics of getting
around and taking care of yourself. It's important also to care
*for* yourself, and this chapter describes the various aspects of
your body's state and what abilities you may have.
The command that tells you almost everything you need to
know is \"stat\". This diplays a whole lot of stuff, perhaps some of
it completely unfamiliar. Let's start at the top, using my output
as an example.
First line:
----------
%^CYAN%^Cratylus aka Cratylus the unaccomplished, level 10 male human Explorer%^RESET%^
Here you see my short name, my name with title, my level, my
gender, my race, and my class. Let's go over each.
* short name: What a person would use to address you. \"look at cratylus\",
for example.
* name with title: This displays my title. Creators can have whatever title
they want. Players can only have the titles they earn. As a player, a
title is usually earned when you are promoted a level or complete a
quest, though it is not always so on every mud.
* level: This is a measure of your overall experience, expertise, and
all-around game status. Being promoted a level means your skills,
health, and vital statistics increase. This often means you can handle
tougher monsters, for example, or tackle more challenging quests, learn
new spells, and so on.
* gender: This has no effect on your status. It is a cosmetic feature
of your body that is only useful to you in the social context of your
fellow mud players.
* race: In Dead Souls, race has nothing to do with your local genetic
makeup on planet Earth. In the mud, \"race\" refers to what one typically
would call \"species\" in real-life. An example of a race other than human
might be \"orc\" or \"feline\". Not all races are available for players. Once
you have chosen a race to play, it is in theory possible to change it, but
there is a nonzero chance you'll hose up your player file and lose your
character forever. Besides, it's up to your local admins whether race
changing is permitted on your mud. Different races have different
abilities. Elves see better in darkness, for example. Orcs are stronger
than some other humanoids, but dumber, too (which does affect gameplay).
* class: This can be considered an occupational specialty. In the real
world you have plumbers, doctors, soldiers, etc. In the mud world,
we can have explorers, fighters, mages, and the like. Each class brings
its own unique advantages and disadvantages to your gameplay. A fighter
can really kick more butt in melee combat than a mage, but a mage
gets to cast powerful spells. Explorers are a middle of the road class
that gives you a bit of everything without specializing in anything.
Next line:
----------
%^CYAN%^Alive / Awake%^RESET%^
It is indeed possible for your virtual body to cease life functions.
When this happens your spirit speeds off to the land of the dead,
where you drift until you decide to \"regenerate\" and regain your
physical form. Except for some special magical items, anything you
were carrying when you died is with that dead body, so it's a good
idea to rush your new body back to the scene of the fatality and get
your stuff back before someone else grabs it. Death is not only
inconvenient, it also incurs major penalties on your statistics, so it
should be avoided.
It is also possible to sleep. If you are drunk and asleep, your
injuries will heal more quickly. It's magic, don't worry about the
logic behind it.
If you are attacked while sleeping, you will wake up. You can
force yourself awake, too, but it's a bit tedious.
Next line:
---------
%^CYAN%^Health: 350/350 Magic: 560/560 Stamina: 400/400 Carry: 1184/1300%^RESET%^
In each case, the number on the left of the slash indicates the
current level, and the number on the right indicates what the maximum is.
health: When I am 100% healthy, I have a total of 350 hp. If my hp ever
reach 0 or less (!), I die. Poison and illness can cause hp's to
gradually decrease, and although with time my hp's will normally
return to 350 as I heal, poison and illness can slow down that healing
or even cause me to die. Injury in combat is the most common source
of hp loss, though spells, falls, and other adverse events can cause
you injury or death.
magic: I cast magic missile! Spell casting takes a toll on your magical
abilities, and mp measure how much magic you've got left in you at
any given point. Like hp, mp gradually grow back to your max if you
avoid spellcasting for a while.
stamina: Fighting is tough work, and swinging around swords while
getting bashed with hammers really takes a lot out of a guy. Therefore
keep an eye on this stat while you're fighting, because if it gets too
low you will collapse and be unable to do anything for a while.
carry: Objects have mass, and your body is of limited size and strength.
My carry capacity is 0 when I carry nothing, and 1300 when I can
carry no more. Creators are allowed to exceed their bodies' carry
capacity, but players cannot.
Next line:
---------
%^CYAN%^Food: 0 Drink: 0 Alcohol: 0 Caffeine: 0 Poison: 0 %^RESET%^
These are pretty self-explanatory. Alcohol is good for healing,
bad for fighting. Food and drink also help speed healing. Poison
has the opposite effect. Caffeine can speed up your combat slightly,
but tends to prevent full rest.
You will not die from lack of food or lack of drink, but you
will do better with a body not starved for nutrients.
Your maximum load for any of these is not fixed, and varies
depending on many factors, such as level, endurance, etc.
Next line:
---------
%^CYAN%^Training Points: 0 Quest Points: 0 Experience Points: 50 %^RESET%^
Training points can be cashed in with special NPC's called
trainers, who can help you improve some skills. A trainer that
specializes in fighting might be able to raise your \"blade attack\"
skill, for example. you earn training points when you are
promoted a level.
Quest points are awarded when you complete a quest. In
the default version of Dead Souls, you cannot advance past a
certain player level unless you earn some qp's. Read the sign
in the adventurers guild for more details on this.
Experience points can be awarded for various reasons: completing
a quest, solving a puzzle, winning a contest. Most often you
will receive xp after killing an NPC. The amount of xp awarded
will depend on the level of the NPC. Like qp, xp are needed to
qualify for level advancement.
Limb section:
------------
Remember how wearing armor requires the right body parts?
Well here they are, and this is their health. You can issue the
\"body\" command for a quicker self-check.
Let's look at what the numbers mean with an example:
%^CYAN%^left leg (2) 160/160%^RESET%^
Obviously the first item identifies the limb in question.
The (2) is a kind of \"importance score\", indicating how critical
a body part is. If this number is (1), like the head, it means that
losing that limb causes immediate death.
The number on the right side of the slash indicates the hit point
damage you may receive on that limb before it is severed. The number
on the left is how many of those hits you have left.
It doesn't mean my leg has 160 of my hitpoints. If that were true,
my hit points would add up to a heck of a lot more than 350.
This means that if I've lost, say, 200hp fighting a troll, and
159hp of those hits were on my left leg, getting hit there again means I
lose my left leg.
I would then collapse and have to crawl away to seek medical attention.
Wearing armor on your limbs is a great way to minimize the danger of
this happening.
Skills section:
--------------
Let's review skills by examining one of mine:
%^CYAN%^blade attack (1) 00% - 20/24%^RESET%^
This measures how likely I am to hit an opponent when I
use a blade, and how good a hit it was. The number (1) means
that this is a skill critical to my class. If an explorer can't
swing a sword, he oughta think about another line
of work.
The 00% means I have thus far earned no blade attack
experience toward achieving the next level of this skill.
The 20 is my current proficiency level.
The 24 is the maximum level I can reach at my current
player level and with my current stats.
What's all this mean? Well, if I practice a lot of blade
attacking, that 00% will gradually climb up to 99, and one more
point causes me to go from a level 20 slicer of things to a
level 21 slicer of things. This increases my likelihood of
hitting my target in the future.
Meaning, in short, practice a skill, and you'll get
better at it.
Of course, if my blade attack level reaches 24, I can advance
my blade attack skills no further until my player level rises.
Stats section:
-------------
Remember these from Dungeons & Dragons? No? Well these vital
statistics measure your general giftedness in that feature of your
body. Let's look at one of mine:
%^CYAN%^coordination (2) 42/42%^RESET%^
Coordination is one of those important stats for fighting and
such. The more coordinated you are, the more likely you are to hit your
target. The (2) indicates that this stat is important to my class,
but not critical. This influences its effect on my skills.
42/42 means that my coordination is not currently impaired. If
someone cast a \"stumble\" spell on me, for example, this might look more
like 30/42, and if I were drunk, it would look very shabby indeed.
New characters should avail themselves of the \"customize\" command.
When you create a character, you are assigned stats based on
random numbers modified by the race you choose. For example, humans
are physically weaker than other races, so you might have a
strength of 15 as a human, whereas a dwarf might expect something
like 42. On the other hand, humans tend to be quite smart, and so
your human character might have a high intelligence stat, and
the dwarf a substantially lower one. To balance out stats that
are grossly unfair, new characters are given 15 points to spend
to add to their stats. As a human with 15 strength, you might
choose to throw all your customization points into strength, adding
up to a whopping 30. Or you might choose to distribute points
among your stats in a manner most suited to your playing style.
For syntax and details, type:
help customize
Last section:
------------
\"Cratylus has amassed a net worth of 11 gold.\" means that when you
add up the money in my bank accounts and the money I'm carrying,
converted to gold, I have 11 gold to my name. It looks bad, but gold
is actually quite valuable in the default Dead Souls economy.
\"Money on hand: 79 dollars, 34 silver\" means that this is the amount of
money I'm carrying. Don't forget that the amount of money you are carrying
affects your overall carry capacity. Gold is an especially heavy
currency.
Final notes:
-----------
\"stat\" is a great command to get thorough information about
yourself. It is, however, quite a screenful. Briefer reports can be
viewed with the following commands:
%^GREEN%^body%^RESET%^
%^GREEN%^skills%^RESET%^
%^GREEN%^stats%^RESET%^
%^GREEN%^score%^RESET%^
%^GREEN%^status%^RESET%^
",({"chapter 7","chapter seven","7",}):"chapter 7 \"Hints and tips\"
* The \"wimpy\" command helps you avoid death due to inattention or
network lag. If you \"wimpy 20\", you will automatically try to escape
combat if your health goes below 20% of your maximum.
* \"target\" and \"ignore\" are extremely useful when fighting more than
one enemy. You should always target the toughest npc first, and
always ignore any npc who can't get up because their foot or leg
is severed.
But if they collapse due to exhaustion, it's a good idea to keep
beating on them, otherwise they may get back up and get healthy sooner
than you expect.
* By default, different races speak different languages. If someone
says something to you and you see no words in the same language as
the rest of the mud, it means they are speaking a language you do
not understand.
For example, if you are an elf, and you ask Radagast to teach
magic attack, you might get something like this:
Radagast exclaims in English, \"embleer con boltehe oota goota nehi auch\"
Even though in the real world you may speak English fluently, in
the mud world, you do not speak English fluently. As an elf, your
native tongue is Edhellen, and you may find human speech incomprehensible.
If you find a trainer to teach you English, your skills in that
language will need time to improve. As you get better at a language,
you will see fewer gibberish words.
If you are a \"newbie\", this does not apply to you. A newbie in the
default Dead Souls distribution is a player at level 4 or below. This
definition may be changed by your admin.
Newbies need all the help they can get just to survive, so they
are magically granted understanding of all languages, until they outgrow
their naivete.
If you are a student of languages in the Real World, you may
recognize many of the \"gibberish\" words used by Dead Souls to represent
a foreign tongue. Your understanding of these words is not useful in
the context of the game, however, because they are not intended to
convey meaning other than \"non-comprehensible words\".
* Your ability to see is affected by various things:
- A room's ambient light level
- Time of day
- Local light sources (flashlights, torches, etc)
- Your race's light sensitivity
- Magical effects
- Exposure to an excessive-light event
It's important to remember that a room may be too dark for
you to see everything in it. You might be able to see the description
of a room with no problem, but it may be necessary for you to
light a torch in order to see the treasure chest there.
In the same way that darkness can impair vision, brightness
can do the same. For elves, an outdoor area in bright sunlight that
contains additional light sources can be just as hostile to
vision as a dark cave with no torch would be for a human.
Regardless of race, a sufficiently adverse event, such as
a bright flash or special spell, can render you temporarily blind.
As with languages, newbies have some exemption to light-level
limitations.
* Mages can wield knives but are pretty much helpless with any other
vind of edged weapon.
",]),]),"/doc/bguide":(["object":"/domains/default/obj/bguide","/doc/bguide/chapter05":1203153606,"title":"Builder's Guidebook","items":([({"chapter 35","chapter thirty-five","35",}):"\"QCS: Modifying rooms\"
",({"chapter 37","chapter thirty-seven","37",}):"\"QCS: Modifying things and stuff\"
",({"chapter 34","chapter thirty-four","34",}):"\"QCS: Modification of NPC's\"
",({"chapter 1","chapter one","1",}):"\"QCS and Builder Commands\"
",({"chapter 35","chapter thirty-five","35",}):"\"QCS: Modifying rooms\"
",({"chapter 34","chapter thirty-four","34",}):"\"QCS: Modification of NPC's\"
",({"chapter 1","chapter one","1",}):"\"QCS and Builder Commands\"
",({"chapter 36","chapter thirty-six","36",}):"\"QCS: Modifying weapons\"
",({"chapter 36","chapter thirty-six","36",}):"\"QCS: Modifying weapons\"
",({"chapter 38","chapter thirty-eight","38",}):"\"QCS: Adding and deleting\"
",({"chapter 39","chapter thirty-nine","39",}):"\"QCS: Final notes\"
",({"chapter 33","chapter thirty-three","33",}):"\"QCS: Creation\"
",({"chapter 39","chapter thirty-nine","39",}):"\"QCS: Final notes\"
",({"chapter 38","chapter thirty-eight","38",}):"\"QCS: Adding and deleting\"
",({"chapter 33","chapter thirty-three","33",}):"\"QCS: Creation\"
",({"chapter 37","chapter thirty-seven","37",}):"\"QCS: Modifying things and stuff\"
",]),"/doc/bguide/chapter04":1203153606,"/doc/bguide/chapter03":1203153606,"/doc/bguide/chapter02":1203153606,"/doc/bguide/chapter01":1212343010,"index":" Builder's Guidebook
Chapter 1: \"QCS and Builder Commands\"
","reads":([({"chapter 34","chapter thirty-four","34",}):"chapter 34 \"QCS: Modification of NPC's\"
In the previous chapter we learned how to make a generic object.
Now that we have it, what to do with it?
It's important to keep in mind that the generic thing now in
front of you isn't just a clone from a template file. If the command
you used was \"create npc cowboy\", there is now a file (probably)
called /realms/you/area/npc/cowboy.c that contains the code for the
creature in front of you.
However, this poor beast has the most uninteresting of features.
It is in fact so boring that it responds only to its generic type.
Such that \"examine cowboy\" or \"kill tex\" won't work. You'll need to
\"look at npc\".
Accordingly, any modification commands need to be made referring
to the new thing with an it responds to, until such a time as you
change the id to suit your tastes. Let's carry on with the example
of our generic npc. To make a cowboy out of him, we can either
change his name, or his id, or both. Let's start with his name:
%^GREEN%^modify npc name cowboy%^RESET%^
This makes the SetKeyName() directive in cowboy.c use \"cowboy\"
as its argument, effectively allowing you to address this npc as \"cowboy\"
from now on. Now you can \"look at cowboy\" with some results.
Obviously our NPC isn't *just* a cowboy. He's also a human, a dude,
and his name is Tex. How do we make him respond to all of these nouns?
%^GREEN%^modify cowboy id%^RESET%^
You'll notice there are no arguments following the word \"id\". Setting
a thing's id is different from most other settings. If you'll think back
to the LPC datatypes chapter of this manual (you did read the LPC
chapters, didn't you?) you'll remember that some information about
objects is in the form of strings (\"cowboy\"), some is in the form of
integers (the cowboy's health points, for example) and some is in the
form of arrays, which are a group of data points. In this example we
want the cowboy's id to be an array, because of the many ways we might
want to address him. Therefore, we want the SetId directive in his
file to look like this:
SetId( ({\"human\", \"dude\", \"tex\" }) );
You might think that QCS should be able to accept multiple values
like this on a line, perhaps with \"modify cowboy id human dude tex\" as
the command.
But what if you want this npc to be a \"Boy Named Sue\"? How would
you accommodate id's that contain spaces? In designing QCS, I considered
having escape characters to allow for such things, but ultimately
reasoned that this was just way too much mess. Instead, SetId, and
other directives that take arrays as arguments, are handled by entering
a query session. Below is an example of what it might look like. The
example is necessarily messy because I am including both the queries
and the responses.
---------------------------------------------------
> %^GREEN%^modify npc name cowboy%^RESET%^
Indenting file...
\"/tmp/indent.1136206130.tmp.dat\" 15 lines 420 bytes
Exit from ed.
> %^GREEN%^modify cowboy id%^RESET%^
This setting takes multiple values. If you have no more values to enter, then
enter a dot on a blank line. To cancel, enter a single q on a blank line.
You may now enter the next value. So far, it is blank.
If you're done entering values, enter a dot on a blank line.
%^GREEN%^dude%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^human%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^tex%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^boy named sue%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\",
\"boy named sue\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^.%^RESET%^
Entries complete. Final array is: ({ \"dude\", \"human\", \"tex\", \"boy named sue\" })
Indenting file...
\"/tmp/indent.1136206156.tmp.dat\" 19 lines 459 bytes
Exit from ed.
/open/1136206138: Ok
/realms/cratylus/area/npc/cowboy: Ok
SetId modification complete.
> %^GREEN%^exa tex%^RESET%^
Other than being human, this npc is entirely unremarkable.
The male human is in top condition.
---------------------------------------------------
If you were now to examine Tex's code (with the command \"about tex\")
you'd see that his SetId directive now looks like this:
SetId( ({\"dude\", \"human\", \"tex\", \"boy named sue\"}) );
Other NPC features take arrays also. SetAdjectives is one. You
might enter this (mud output omitted for clarity):
%^GREEN%^modify tex adjectives%^RESET%^
%^GREEN%^dusty%^RESET%^
%^GREEN%^hardy%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^look at dusty cowboy%^RESET%^
There are two other directives that require queries. Things and
NPC's can be looked at, but they can also be smelled and listened
to, if you add SetSmell and SetListen. The syntax is:
%^GREEN%^modify tex smell%^RESET%^
And you will then be asked a question about keys and mappings.
Understanding mappings is important, but for now you just need to
understand that you are being asked *two* separate questions:
1) What on the cowboy is being smelled/listened to?
2) What is the smell/sound?
What this means is that your input will look something like
this:
%^GREEN%^modify tex smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex smells of sweat and manure.
What happens is this:
- You enter the modify command.
- You enter the word \"default\" to indicate this is Tex's general smell.
- You enter a dot to indicate that you are done specifying what
part of Tex is being smelled.
- You then specify the smell.
This may seem odd until you realize you can also add smells/listens to
parts of things. Not on NPC's, though. We'll look at this more closely in
later chapters. For now, just use the syntax as shown above. For adding
a listen to the cowboy, it works the same way:
%^GREEN%^modify tex listen%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex seems to be humming a jaunty melody.
Other features of an NPC do not take arrays, so one-line commands
will do. For example:
%^GREEN%^modify cowboy long This is a cowboy who calls himself Tex, but is in fact a Boy Named Sue.%^RESET%^
%^GREEN%^modify cowboy short a cowboy%^RESET%^
%^GREEN%^modify cowboy level 5%^RESET%^
%^GREEN%^modify cowboy class fighter%^RESET%^
%^GREEN%^modify tex currency gold 1%^RESET%^
%^GREEN%^modify tex currency silver 12%^RESET%^
%^GREEN%^modify tex skill bargaining 5%^RESET%^
%^GREEN%^modify tex skill projectile attack 7%^RESET%^
%^GREEN%^modify tex stat strength 33%^RESET%^
%^GREEN%^modify tex property nice guy 1%^RESET%^
%^GREEN%^modify tex healthpoints 150%^RESET%^
%^GREEN%^modify tex maxhealthpoints 170%^RESET%^
%^GREEN%^modify tex melee 0%^RESET%^
%^GREEN%^modify tex unique 1%^RESET%^
If you now issue the \"about tex\" command you will see that all
the changes you made have been put into the file.
You may have noticed the \"melee\" keyword. Dead Souls 2 NPC's come
in various shapes and sizes, and some of them shouldn't wield weapons. A
wolf with a battle axe would be a strange sight indeed. However, the
default combat system makes unarmed creatures extremely vulnerable in
combat.
To make an NPC combat-capable without weapons, use the new SetMelee
directive. SetMelee(1) makes the NPC capable of proper unarmed combat.
SetMelee(0) makes the NPC a weak opponent if unarmed.
NPC's will generally try to bite during unarmed combat. If this
is beneath the capability or dignity of your NPC, you can prevent
this with:
%^GREEN%^modify tex canbite 0%^RESET%^
If your NPC should try to escape when the battle isn't going
his way, the wimpy settings should do:
%^GREEN%^modify tex wimpy 30%^RESET%^
%^GREEN%^modify tex wimpycommand climb ladder%^RESET%^
If you don't specify a wimpy command, Tex will leave the
room through a random exit. In this case, when Tex's health
is down to 30% and he is in combat, he will try to climb a ladder.
Some NPC's are designed to travel about. To enable this
feature, use the wanderspeed directive:
%^GREEN%^modify tex wanderspeed 5%^RESET%^
If you want him to travel more quickly, use a
lower number. By default, wandering NPC's only wander in rooms
that have already been loaded into memory. They avoid loading
rooms because loading a bunch of rooms that only the NPC
will ever see is a waste of your mud's resources.
However, if you *do* want your NPC to wander in an
unrestricted manner, regardless of whether a room is loaded,
use the permitload directive:
%^GREEN%^modify tex permitload 1%^RESET%^
By default, NPC's stand up when they can. This is
so that if they collapse during combat, they try to
get back up once they are able to do so.
If you prefer that your NPC maintain some other
posture, you can set that posture, then disable
autostanding like this:
%^GREEN%^modify tex posture lying%^RESET%^
%^GREEN%^modify tex autostand 0%^RESET%^
If he's especially lazy, you can have him take
a nap this way:
%^GREEN%^modify tex sleeping 10%^RESET%^
Which will have him wake up after about a minute.
However, note that if you've disabled autostanding,
he will remain lying down after he wakes up.
If the NPC should be hostile, that is, he should
attack any creatures that it sees enter a room,
SetEncounter should do it:
%^GREEN%^modify tex encounter 100%^RESET%^
This means that if the creature it sees has a
charisma score of less than 100 (which should pretty
much be always true), Tex will try to kill it. You
can do some fancy stuff with SetEncounter, such
as only attacking orcs, or actually doing something
friendly, but to do so you can't use QCS. Read
the NPC and Sentients chapter in the Creator's
Manual for details on how to code such stuff.
If the NPC is a golem or other such non-biological
creature, it may be useful to specify what they are
made of. The SetComposition setting for a clay
golem might look like this:
%^GREEN%^modify golem composition clay%^RESET%^
If it happens to be a golem that does not believe
in violence as a solution to problems, you can
make refuse to hurt others with the following:
%^GREEN%^modify golem pacifist 1%^RESET%^
Vendors:
-------
Vendors are a special kind of NPC that can sell stuff.
Along with the standard NPC settings, vendors have
the following:
SetStorageRoom specifies where the vendor's stock
is stored. If a valid room is specified, anything
in that room can be sold by the vendor.
SetLocalCurrency specifies the type of currency,
such as gold or silver, that the vendor accepts.
SetMaxItems is the maximum number of items in
the storeroom that the vendor is permitted to sell.
SetVendorType specifies the kind of stuff the vendor can
trade in. \"all\" allows him to buy and sell whatever.
But if he is a weapon vendor, he can't trade in armor,
etc. See /include/vendor_types.h for the available
vendor types. To have a \"multi-type\" vendor, you'll have to
code it by hand. The result of that looks something
like this:
SetVendorType( VT_TREASURE | VT_ARMOR | VT_HERBS );
Barkeeps:
--------
Like vendors, barkeeps sell stuff, but they are
limited to selling food and drink.
Unlike vendors, barkeeps have no limitation on
the amount of stuff they can sell. They also do
not have a storeroom. The stuff they can sell is
specified in their SetMenu directive, like this:
%^GREEN%^clone woody%^RESET%^
You clone a generic barkeep (/realms/temujin/area/npc/woody.c).
%^GREEN%^modify woody menu%^RESET%^
If you don't understand these questions, type the letter q on a blank line and hit enter.
Please enter the first key element for this mapping:
%^GREEN%^bourbon%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^whiskey%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^.%^RESET%^
Please enter the value for key ({ \"bourbon\", \"whiskey\" }):
%^GREEN%^/domains/town/meals/bourbon%^RESET%^
Barkeeps also have the SetLocalCurrency directive
to specify the currency they accept.
",({"chapter 38","chapter thirty-eight","38",}):"chapter 38 \"QCS: Adding and deleting\"
Rooms, containers and NPC's all are capable of holding
items, and often it is convenient to have them already holding certain
items upon creation.
The SetInventory directive in an object's file provides us with
a list of that object's \"permanent inventory\". To modify an object's
inventory, we use the add and delete commands.
Let's say that we want our cowboy to be wielding a
hammer when he appears...
%^GREEN%^clone cowboy%^RESET%^
%^GREEN%^cd ../weap%^RESET%^
%^GREEN%^clone hammer%^RESET%^
%^GREEN%^add hammer to cowboy%^RESET%^
%^GREEN%^wield hammer%^RESET%^
Believe it or not, it's that simple. The add command will ask
you a question after you issue it. What it wants to know is if you
want the NPC to do anything special when the hammer appears on him.
In this case, yes, we wanted him to wield it.
However, if you want to add something to an NPC and don't
have anything interesting for him to do with it, respond to the
question with the number of items that you want to appear. For
example, if I want the cowboy to be carrying a key:
%^GREEN%^cd ../obj%^RESET%^
%^GREEN%^clone key%^RESET%^
%^GREEN%^add key to cowboy%^RESET%^
%^GREEN%^1%^RESET%^
And that's it. Now if I want the cowboy to be a permanent
resident of this room:
%^GREEN%^add cowboy%^RESET%^
%^GREEN%^1%^RESET%^
The add command understands that if you don't specify a
target argument, you must mean the room. You can also be specific:
%^GREEN%^add cowboy to room%^RESET%^
%^GREEN%^1%^RESET%^
The delete command works the opposite way. It removes items from
an object's permanent inventory:
%^GREEN%^delete hammer from cowboy%^RESET%^
%^GREEN%^delete cowboy%^RESET%^
The delete command is also the way to get rid of rooms. You won't
be removing the file from disk, you'll just be deleting that exit
from the room:
%^GREEN%^delete exit garden%^RESET%^
NOTE: When you delete an exit, only the connection from your room to
the other room is removed. The connection from the other room to
your room remains. This is not a bug, it's a feature. There are plenty
of circumstances where one-way travel is desirable.
",({"chapter 37","chapter thirty-seven","37",}):"chapter 37 \"QCS: Modifying things and stuff\"
You should have a firm grasp now on how QCS works
in relation to manipulable objects. Let's look at the
settings for a few special kinds of items:
chairs
------
%^GREEN%^modify stool maxsitters 1%^RESET%^
%^GREEN%^modify stool setmaxcarry 200%^RESET%^
beds
----
%^GREEN%^modify sofa maxsitters 2%^RESET%^
%^GREEN%^modify sofa maxliers 1%^RESET%^
%^GREEN%^modify sofa maxcarry 400%^RESET%^
containers
----------
%^GREEN%^modify box canclose 1%^RESET%^
%^GREEN%^modify box closed 1%^RESET%^
%^GREEN%^modify box locked 1%^RESET%^
%^GREEN%^modify box key magic_skeleton_key%^RESET%^
%^GREEN%^modify box maxcarry 200%^RESET%^
%^GREEN%^modify box setmoney gold 15%^RESET%^
tables
------
%^GREEN%^modify altar maxcarry 300%^RESET%^
%^GREEN%^modify altar maxliers 1%^RESET%^
meals/drinks
------------
%^GREEN%^modify burger mealtype food%^RESET%^
%^GREEN%^modify schlitz mealtype alcohol%^RESET%^
%^GREEN%^modify apple mealstrength 10%^RESET%^
books
-----
%^GREEN%^modify journal title The Orc Within%^RESET%^
%^GREEN%^modify journal source /domains/Orcland/etc/books/journal%^RESET%^
Readable things:
----------------
If you want to be able to \"read thing\", for example, \"read sign\":
%^GREEN%^modify sign defaultread This is a message written on the sign.%^RESET%^
If you want to make a thing on a thing readable, as in
\"read inscription on ring\":
%^GREEN%^modify ring item%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^This is an inscription on the ring. Try 'read inscription on ring'%^RESET%^
%^GREEN%^modify ring read%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^So! We, the spear-Danes%^RESET%^
By default, readabale items are readable by anyone, regardless
of the languages they know. If, however, your item should
only be readable by someone who understands the elvish tongue:
%^GREEN%^modify ring language edhellen%^RESET%^
Miscellaneous:
--------------
To make a key have a 50% chance of breaking when it's used:
%^GREEN%^modify golden key disablechance 50%^RESET%^
To make a room or object immune to resets:
%^GREEN%^modify sentry noclean 1%^RESET%^
To make sure there is only one instance of an object or
NPC loaded at any given time:
%^GREEN%^modify tiamat unique 1%^RESET%^
To make a thing or room immune to the QCS (except for this
command):
%^GREEN%^modify workroom nomodify 1%^RESET%^
To specify what kind of vendor should be allowed to traffic
in this item:
%^GREEN%^modify necklace vendortype treasure%^RESET%^
",({"chapter 33","chapter thirty-three","33",}):"chapter 33 \"QCS: Creation\"
Creation breaks down into three categories. \"Things\", rooms, and
doors. Let's look at things first:
Thing creation:
---------------
In this category we're including NPC's, weapon, armor, unspecial items,
tables, furniture, books and containers. Basically things that actually show
up somewhere. A door is a very special sort of object and does not fall
under this category.
To make a new thing, the syntax looks like this:
create THING FILENAME
The THING is the category of item we are creating. The FILENAME is
the name of the file that will contain this item's configuration data.
You may enter an absolute path, or you may simply enter a filename. If
you enter a filename, QCS will attempt to figure out the best place
for this file. If your current working directory has a name QCS understands
as being compatible with the type of object you are making (for example,
your cwd is /realms/you/npc and you are making an NPC) it will use that.
Otherwise it will search parent and children directories for a
directory name it understands this way. Finally, if it can find no
compatible directories for your file near your cwd, it will put it in
the appropriate directory in your home area (in this case,
/realms/you/area/npc ).
Avoid a relative path. It probably won't work the way you think.
When the command completes, FILENAME will be a file containing the
data for your generic thing, and a copy of that generic thing will appear
in the room you are in.
If, for example, you entered:
%^GREEN%^create npc cowboy%^RESET%^
The room you are in will contain a generic NPC which *does not*
answer to the id of \"cowboy\". This NPC is just a generic NPC whose
filename is (probably) /realms/you/npc/cowboy.c and isn't yet a real
cowboy. You'll need to use the \"modify\" command to make a proper
cowboy out of him.
Room creation
-------------
Naturally, if you create a room, a new room will not appear inside
your current environment. Instead, the syntax of the \"create\" command
is different when you want to create a new room.
You may have noticed that you can't use the \"create\" command to make
a new room adjacent to your workroom. This is for your protection and
my sanity. Files which contain the directive \"SetNoModify(1)\" are
immune to QCS manipulation.
Rooms like your workroom, the default start room, the void room, etc,
are set nomodify. This is because if you screw it up, you will be
sorry, and I just don't want to hear it.
So, suppose then that you're in your sample room (one room east
of your workroom) and you want to make a new room. You might issue
the following command:
%^GREEN%^create room south testroom1%^RESET%^
What this does is copy the room you are in (in this case,
/realms/you/area/sample_room.c) to a new location (perhaps
/realms/you/area/testroom1.c). But the neat thing is, this new room
does not have the same exits as the room you are in. The new room
has just one exit, leading back to where you are.
The net effect of all this is that when you issue this command,
you make a new room in the direction you specify, and this
new room looks just like the room you're in, only the exits are such
that you can travel back and forth between the rooms.
Door creation
-------------
Doors are funny things. In Dead Souls, they aren't objects in the
conventional sense of a thing which occupies a room you're in. Rather,
they are really daemons which attach to adjoining rooms. If that doesn't
make sense to you, don't worry. You're not alone.
The syntax for door creation is much like that for room creation.
After you create that room south of your sample room, you can now create
a door between them:
%^GREEN%^create door south sample_door%^RESET%^
This brings into existence a generic door (closed by default) which
is between the two rooms.
",({"chapter 1","chapter one","1",}):"chapter 1 \"QCS and Builder Commands\"
\"QCS\" is the Dead Souls Quick Creation system, similar in function
to what other codebases refer to as OLC, or On-Line Creation.
What with muds being text only, QCS has no fancy windowing system.
Using a menu-driven creation system was ruled out quickly due to the
vast complexity of the menus that would be required. Instead, QCS
relies on a few powerful commands.
BUILDER COMMANDS
----------------
home
----
This command magically teleports you to your workroom.
arealist
--------
This lets you see a list of the things you have created. To see
a list of NPC's (or mobs) you've created, type: arealist npc
Other valid categories are: room, object, weapon, armor
areagoto
--------
You can travel directly to rooms you've created. If one of
your rooms' file is, for example, ogre_room1, you can: areagoto ogre_room1
areaclone
---------
This allows you to bring into existence a copy of a thing you've
created. For example, one of the weapons you've made has a file name of golden_sword,
you can make one appear by typing: areaclone golden_sword
dest
----
This destroys one of your created objects. If you're done playing
with a sword, for example, you can: dest sword
Note that this does not delete the file, it just destroys the cloned
copy. Also, it only works on items cloned from files in your area. You
aren't allowed to dest other people's stuff!
reload
------
This destroys the current object and reloads it with its current
code. For example, if your workroom is full of junk and you want to
reset it to its base status, type: reload here
And the room will reset to its default, with the junk gone.
QCS COMMANDS
------------
create
------
This is the command that gets the ball rolling. This command
is what lets you bring a new thing into existence. The things you can
create can be seen by typing \"help create\". Examples are rooms, weapons,
doors, and so on. We will be reviewing each of those in later chapters.
When you issue this command a generic version of the item you
wish to create appears (or, in the case of a room, appears in the
direction you specify). Once that generic copy materializes, you can
change it to suit your needs using the \"modify\" command.
modify
------
I tend to regard this command as the heart and soul of QCS. It's
this tool that lets you make your world your own. Your new generic
things are not useful or fun until you modify them. A \"generic weapon\"
isn't very interesting, but a \"mithrilite poleaxe\" might be just the
thing to deal with a pesky dragon.
add
---
Creatures, rooms, and containers are capable of storing other
things. Once you make an ogre, you may want to give him a hammer to
wield. After you make that hammer, you use the add command to let
the ogre have that wepon in his permanent inventory.
delete
------
On the other hand, you may be tired of that ogre after a while. If
he is a part of the permanent inventory of a room, you can use the
delete command to remove him permanently. Or if you'd rather he have
a Kill-O-Zap Frogstar blaster rather than a hammer, get rid of the
hammer in his inventory with this command.
copy
----
This is a room-specific command. Rather than write multiple,
nearly identical rooms for large areas, you can use the copy command to
make the room you are almost exactly like any other room you choose,
except for the exits, which remain the same. Handy for big forests,
cell-blocks, twisty mazes of little passages, etc.
initfix
-------
If a thing isn't working right, try to initfix it. \"init()\" is
an object function that many items need in order to work properly. If
you've run into something that is behaving unexpectedly, run initfix on
it. The trouble just might clear up.
",({"chapter 37","chapter thirty-seven","37",}):"chapter 37 \"QCS: Modifying things and stuff\"
You should have a firm grasp now on how QCS works
in relation to manipulable objects. Let's look at the
settings for a few special kinds of items:
chairs
------
%^GREEN%^modify stool maxsitters 1%^RESET%^
%^GREEN%^modify stool setmaxcarry 200%^RESET%^
beds
----
%^GREEN%^modify sofa maxsitters 2%^RESET%^
%^GREEN%^modify sofa maxliers 1%^RESET%^
%^GREEN%^modify sofa maxcarry 400%^RESET%^
containers
----------
%^GREEN%^modify box canclose 1%^RESET%^
%^GREEN%^modify box closed 1%^RESET%^
%^GREEN%^modify box locked 1%^RESET%^
%^GREEN%^modify box key magic_skeleton_key%^RESET%^
%^GREEN%^modify box maxcarry 200%^RESET%^
%^GREEN%^modify box setmoney gold 15%^RESET%^
tables
------
%^GREEN%^modify altar maxcarry 300%^RESET%^
%^GREEN%^modify altar maxliers 1%^RESET%^
meals/drinks
------------
%^GREEN%^modify burger mealtype food%^RESET%^
%^GREEN%^modify schlitz mealtype alcohol%^RESET%^
%^GREEN%^modify apple mealstrength 10%^RESET%^
books
-----
%^GREEN%^modify journal title The Orc Within%^RESET%^
%^GREEN%^modify journal source /domains/Orcland/etc/books/journal%^RESET%^
Readable things:
----------------
If you want to be able to \"read thing\", for example, \"read sign\":
%^GREEN%^modify sign defaultread This is a message written on the sign.%^RESET%^
If you want to make a thing on a thing readable, as in
\"read inscription on ring\":
%^GREEN%^modify ring item%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^This is an inscription on the ring. Try 'read inscription on ring'%^RESET%^
%^GREEN%^modify ring read%^RESET%^
%^GREEN%^inscription%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^So! We, the spear-Danes%^RESET%^
By default, readabale items are readable by anyone, regardless
of the languages they know. If, however, your item should
only be readable by someone who understands the elvish tongue:
%^GREEN%^modify ring language edhellen%^RESET%^
Miscellaneous:
--------------
To make a key have a 50% chance of breaking when it's used:
%^GREEN%^modify golden key disablechance 50%^RESET%^
To make a room or object immune to resets:
%^GREEN%^modify sentry noclean 1%^RESET%^
To make sure there is only one instance of an object or
NPC loaded at any given time:
%^GREEN%^modify tiamat unique 1%^RESET%^
To make a thing or room immune to the QCS (except for this
command):
%^GREEN%^modify workroom nomodify 1%^RESET%^
To specify what kind of vendor should be allowed to traffic
in this item:
%^GREEN%^modify necklace vendortype treasure%^RESET%^
",({"chapter 35","chapter thirty-five","35",}):"chapter 35 \"QCS: Modifying rooms\"
Suppose you are in your sample room and you issued the
command:
%^GREEN%^create room south testroom1%^RESET%^
You then travel south and see that you are in a room that is
almost exactly like the sample room except for the exits. Well,
probably you don't want to have a mud with nothing but identical
rooms, so let's modify it:
%^GREEN%^modify here short Test Room One%^RESET%^
%^GREEN%^modify here long This is the first test room. The walls are rather blank.%^RESET%^
%^GREEN%^modify here climate indoors%^RESET%^
%^GREEN%^modify here light 30%^RESET%^
Ok, so far so good. Standard interior. However, a good mud has
rooms with details. Let's add some detail to this room.
I've omitted the system output for clarity. This is just what you
would input.
%^GREEN%^modify here item%^RESET%^
%^GREEN%^wall%^RESET%^
%^GREEN%^walls%^RESET%^
%^GREEN%^blank wall%^RESET%^
%^GREEN%^blank walls%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^These are just blank walls.%^RESET%^
Let's review what we've done here:
1) You issued the modify command specifying your current room as the
target, and the SetItems directive as the argument.
2) You entered a query session, and were asked to enter each element
of the item's key.
3) You entered a single dot to indicate you were done entering
key elements.
4) You entered the value for the key, which is the description of the
item.
The result of all this is that now you can issue these commands:
%^GREEN%^exa wall%^RESET%^
%^GREEN%^look at blank walls%^RESET%^
%^GREEN%^examine walls%^RESET%^
And the output will be:
These are just blank walls.
Let's add a floor while we're at it:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^floor%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A floor like any other.%^RESET%^
In this case, you didn't feel like adding extra synonyms for \"floor\",
so you entered the final dot rather than entering another key element.
Then you added the description, and now if you \"exa floor\", you'll get
that description.
\"about here\" will display to you the file you have modified.
Well, that's enough fun with indoor rooms. There's not much more
to them. Let's go outdoors now:
%^GREEN%^create room south exterior_room%^RESET%^
%^GREEN%^create door south test_door%^RESET%^
%^GREEN%^open door%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^modify here short a small lawn%^RESET%^
%^GREEN%^modify here daylong A small, well groomed lawn on a lovely sunny day. There is a small building north of here.%^RESET%^
%^GREEN%^modify here nightlong This is a small lawn. Stars twinkle in the night sky above, and some light is coming from a small building to the north.%^RESET%^
%^GREEN%^modify here daylight 30%^RESET%^
%^GREEN%^modify here nightlight 20%^RESET%^
%^GREEN%^modify here light delete%^RESET%^
%^GREEN%^modify here long delete%^RESET%^
%^GREEN%^modify here items delete%^RESET%^
%^GREEN%^modify here items%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^small building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A small building, rather ramshackle as if hastily put together.%^RESET%^
%^GREEN%^modify here climate temperate%^RESET%^
Ok! A few new things here. A neat thing about outdoor rooms is that
typically they are subject to the time of day. A SetClimate directive
that indicates an exterior environment causes the room to receive
messages about the sun setting, rising, etc.
The SetDayLong and SetNightLong directives allow you to more
sensibly describe the area depending on the time of day. To avoid
confusion, I deleted the SetLong directive. It is not mandatory to
have different day and night descriptions, but players appreciate the
effort.
It is also possible to have differing ambient light levels depending
on the time of day, so we've added SetDayLight and SetNightLight, and
we deleted the SetAmbientLight directive.
Let's continue to add detail:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Healthy, well groomed and freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You can smell the refreshing scent of freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Yep, it's got that new lawn smell.%^RESET%^
%^GREEN%^modify here listen%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Sounds like someone's fumbling about in there, making a mess. New creators can be so noisy.%^RESET%^
%^GREEN%^modify here item%^RESET%^
%^GREEN%^garden%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You may enter the garden from here.%^RESET%^
%^GREEN%^create room garden garden_room%^RESET%^
You now have a room with lots of charm and detail. You can \"smell grass\"
and \"listen to small building\", if you like. Neat, huh? But there's something
very important to keep in mind:
Enters, listens and smells don't work properly if there is no item defined
for that smell. For example, if you want to be able to listen to the sea,
you must \"modify here item\" and add a \"sea\" item. Otherwise, \"listen
to the sea\" will respond with \"There is no sea here.\"
The only exception to this rule is the \"default\" smell.
Enters behave similarly. If you want to be able to \"enter\" something,
you'll need to create the corresponding item first, as in the example above.
You can use the SetProperties directive to make the room
conform to some presets, like:
%^GREEN%^modify here property no attack 1%^RESET%^
Read chapter 23 in the Creator's Manual for details on room properties.
Also, please note that indoor rooms can also have differing
descriptions and light levels for night and day. It's just that
indoor rooms don't get notification of daytime changes.
Finally, the SetTown directive allows the room to participate in
area-wide events, and is useful for security purposes as well:
%^GREEN%^modify here town MyTown%^RESET%^
Notes on room filenames:
-----------------------
By default, a filename without a leading path creates a room
in your area room directory, which in my case would be
\"/realms/cratylus/area/room\". However, you can specify a different
location for the new room.
To create a room in your current working directory:
%^GREEN%^create room east ./newroom%^RESET%^
To create a room in a specific directory:
%^GREEN%^create room east /realms/cratylus/testrooms/newroom%^RESET%^
",({"chapter 36","chapter thirty-six","36",}):"chapter 36 \"QCS: Modifying weapons\"
Remember that the QCS chapters are supposed to be read in
sequence. This is important because as we progress, I will not make
explanations about directives and concepts explained previously.
Weapons are different from rooms and NPC's in that they can
be handled, sold, thrown, etc. They are manipulable objects. As such,
we will see new directives:
%^GREEN%^create weapon hammer%^RESET%^
You may be familiar with this example from the example webpage.
Let's go ahead and plow through the commands:
%^GREEN%^modify weapon id hammer%^RESET%^
%^GREEN%^warhammer%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer name hammer%^RESET%^
%^GREEN%^modify hammer damagetype blunt%^RESET%^
%^GREEN%^modify hammer weapontype blunt%^RESET%^
%^GREEN%^modify hammer mass 700%^RESET%^
%^GREEN%^modify hammer hands 2%^RESET%^
%^GREEN%^modify hammer short a heavy war hammer%^RESET%^
%^GREEN%^modify hammer long This is an extremely large and heavy hammer designed to be wielded in both hands and used to hurt people very badly indeed.%^RESET%^
%^GREEN%^modify hammer adj%^RESET%^
%^GREEN%^large%^RESET%^
%^GREEN%^heavy%^RESET%^
%^GREEN%^war%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer basecost silver 750%^RESET%^
%^GREEN%^about hammer%^RESET%^
Like a room and unlike an NPC, you can also modify the SetItems on
manipulable objects like weapons, so you could do something like this:
%^GREEN%^modify hammer item%^RESET%^
%^GREEN%^shaft%^RESET%^
%^GREEN%^handle%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A thick, reinforced steel shaft with leather bands for a strong grip.%^RESET%^
%^GREEN%^exa shaft on hammer%^RESET%^
",({"chapter 33","chapter thirty-three","33",}):"chapter 33 \"QCS: Creation\"
Creation breaks down into three categories. \"Things\", rooms, and
doors. Let's look at things first:
Thing creation:
---------------
In this category we're including NPC's, weapon, armor, unspecial items,
tables, furniture, books and containers. Basically things that actually show
up somewhere. A door is a very special sort of object and does not fall
under this category.
To make a new thing, the syntax looks like this:
create THING FILENAME
The THING is the category of item we are creating. The FILENAME is
the name of the file that will contain this item's configuration data.
You may enter an absolute path, or you may simply enter a filename. If
you enter a filename, QCS will attempt to figure out the best place
for this file. If your current working directory has a name QCS understands
as being compatible with the type of object you are making (for example,
your cwd is /realms/you/npc and you are making an NPC) it will use that.
Otherwise it will search parent and children directories for a
directory name it understands this way. Finally, if it can find no
compatible directories for your file near your cwd, it will put it in
the appropriate directory in your home area (in this case,
/realms/you/area/npc ).
Avoid a relative path. It probably won't work the way you think.
When the command completes, FILENAME will be a file containing the
data for your generic thing, and a copy of that generic thing will appear
in the room you are in.
If, for example, you entered:
%^GREEN%^create npc cowboy%^RESET%^
The room you are in will contain a generic NPC which *does not*
answer to the id of \"cowboy\". This NPC is just a generic NPC whose
filename is (probably) /realms/you/npc/cowboy.c and isn't yet a real
cowboy. You'll need to use the \"modify\" command to make a proper
cowboy out of him.
Room creation
-------------
Naturally, if you create a room, a new room will not appear inside
your current environment. Instead, the syntax of the \"create\" command
is different when you want to create a new room.
You may have noticed that you can't use the \"create\" command to make
a new room adjacent to your workroom. This is for your protection and
my sanity. Files which contain the directive \"SetNoModify(1)\" are
immune to QCS manipulation.
Rooms like your workroom, the default start room, the void room, etc,
are set nomodify. This is because if you screw it up, you will be
sorry, and I just don't want to hear it.
So, suppose then that you're in your sample room (one room east
of your workroom) and you want to make a new room. You might issue
the following command:
%^GREEN%^create room south testroom1%^RESET%^
What this does is copy the room you are in (in this case,
/realms/you/area/sample_room.c) to a new location (perhaps
/realms/you/area/testroom1.c). But the neat thing is, this new room
does not have the same exits as the room you are in. The new room
has just one exit, leading back to where you are.
The net effect of all this is that when you issue this command,
you make a new room in the direction you specify, and this
new room looks just like the room you're in, only the exits are such
that you can travel back and forth between the rooms.
Door creation
-------------
Doors are funny things. In Dead Souls, they aren't objects in the
conventional sense of a thing which occupies a room you're in. Rather,
they are really daemons which attach to adjoining rooms. If that doesn't
make sense to you, don't worry. You're not alone.
The syntax for door creation is much like that for room creation.
After you create that room south of your sample room, you can now create
a door between them:
%^GREEN%^create door south sample_door%^RESET%^
This brings into existence a generic door (closed by default) which
is between the two rooms.
",({"chapter 1","chapter one","1",}):"chapter 1 \"QCS and Builder Commands\"
\"QCS\" is the Dead Souls Quick Creation system, similar in function
to what other codebases refer to as OLC, or On-Line Creation.
What with muds being text only, QCS has no fancy windowing system.
Using a menu-driven creation system was ruled out quickly due to the
vast complexity of the menus that would be required. Instead, QCS
relies on a few powerful commands.
BUILDER COMMANDS
----------------
home
----
This command magically teleports you to your workroom.
arealist
--------
This lets you see a list of the things you have created. To see
a list of NPC's (or mobs) you've created, type: arealist npc
Other valid categories are: room, object, weapon, armor
areagoto
--------
You can travel directly to rooms you've created. If one of
your rooms' file is, for example, ogre_room1, you can: areagoto ogre_room1
areaclone
---------
This allows you to bring into existence a copy of a thing you've
created. For example, one of the weapons you've made has a file name of golden_sword,
you can make one appear by typing: areaclone golden_sword
dest
----
This destroys one of your created objects. If you're done playing
with a sword, for example, you can: dest sword
Note that this does not delete the file, it just destroys the cloned
copy. Also, it only works on items cloned from files in your area. You
aren't allowed to dest other people's stuff!
reload
------
This destroys the current object and reloads it with its current
code. For example, if your workroom is full of junk and you want to
reset it to its base status, type: reload here
And the room will reset to its default, with the junk gone.
QCS COMMANDS
------------
create
------
This is the command that gets the ball rolling. This command
is what lets you bring a new thing into existence. The things you can
create can be seen by typing \"help create\". Examples are rooms, weapons,
doors, and so on. We will be reviewing each of those in later chapters.
When you issue this command a generic version of the item you
wish to create appears (or, in the case of a room, appears in the
direction you specify). Once that generic copy materializes, you can
change it to suit your needs using the \"modify\" command.
modify
------
I tend to regard this command as the heart and soul of QCS. It's
this tool that lets you make your world your own. Your new generic
things are not useful or fun until you modify them. A \"generic weapon\"
isn't very interesting, but a \"mithrilite poleaxe\" might be just the
thing to deal with a pesky dragon.
add
---
Creatures, rooms, and containers are capable of storing other
things. Once you make an ogre, you may want to give him a hammer to
wield. After you make that hammer, you use the add command to let
the ogre have that wepon in his permanent inventory.
delete
------
On the other hand, you may be tired of that ogre after a while. If
he is a part of the permanent inventory of a room, you can use the
delete command to remove him permanently. Or if you'd rather he have
a Kill-O-Zap Frogstar blaster rather than a hammer, get rid of the
hammer in his inventory with this command.
copy
----
This is a room-specific command. Rather than write multiple,
nearly identical rooms for large areas, you can use the copy command to
make the room you are almost exactly like any other room you choose,
except for the exits, which remain the same. Handy for big forests,
cell-blocks, twisty mazes of little passages, etc.
initfix
-------
If a thing isn't working right, try to initfix it. \"init()\" is
an object function that many items need in order to work properly. If
you've run into something that is behaving unexpectedly, run initfix on
it. The trouble just might clear up.
",({"chapter 35","chapter thirty-five","35",}):"chapter 35 \"QCS: Modifying rooms\"
Suppose you are in your sample room and you issued the
command:
%^GREEN%^create room south testroom1%^RESET%^
You then travel south and see that you are in a room that is
almost exactly like the sample room except for the exits. Well,
probably you don't want to have a mud with nothing but identical
rooms, so let's modify it:
%^GREEN%^modify here short Test Room One%^RESET%^
%^GREEN%^modify here long This is the first test room. The walls are rather blank.%^RESET%^
%^GREEN%^modify here climate indoors%^RESET%^
%^GREEN%^modify here light 30%^RESET%^
Ok, so far so good. Standard interior. However, a good mud has
rooms with details. Let's add some detail to this room.
I've omitted the system output for clarity. This is just what you
would input.
%^GREEN%^modify here item%^RESET%^
%^GREEN%^wall%^RESET%^
%^GREEN%^walls%^RESET%^
%^GREEN%^blank wall%^RESET%^
%^GREEN%^blank walls%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^These are just blank walls.%^RESET%^
Let's review what we've done here:
1) You issued the modify command specifying your current room as the
target, and the SetItems directive as the argument.
2) You entered a query session, and were asked to enter each element
of the item's key.
3) You entered a single dot to indicate you were done entering
key elements.
4) You entered the value for the key, which is the description of the
item.
The result of all this is that now you can issue these commands:
%^GREEN%^exa wall%^RESET%^
%^GREEN%^look at blank walls%^RESET%^
%^GREEN%^examine walls%^RESET%^
And the output will be:
These are just blank walls.
Let's add a floor while we're at it:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^floor%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A floor like any other.%^RESET%^
In this case, you didn't feel like adding extra synonyms for \"floor\",
so you entered the final dot rather than entering another key element.
Then you added the description, and now if you \"exa floor\", you'll get
that description.
\"about here\" will display to you the file you have modified.
Well, that's enough fun with indoor rooms. There's not much more
to them. Let's go outdoors now:
%^GREEN%^create room south exterior_room%^RESET%^
%^GREEN%^create door south test_door%^RESET%^
%^GREEN%^open door%^RESET%^
%^GREEN%^go south%^RESET%^
%^GREEN%^modify here short a small lawn%^RESET%^
%^GREEN%^modify here daylong A small, well groomed lawn on a lovely sunny day. There is a small building north of here.%^RESET%^
%^GREEN%^modify here nightlong This is a small lawn. Stars twinkle in the night sky above, and some light is coming from a small building to the north.%^RESET%^
%^GREEN%^modify here daylight 30%^RESET%^
%^GREEN%^modify here nightlight 20%^RESET%^
%^GREEN%^modify here light delete%^RESET%^
%^GREEN%^modify here long delete%^RESET%^
%^GREEN%^modify here items delete%^RESET%^
%^GREEN%^modify here items%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^small building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A small building, rather ramshackle as if hastily put together.%^RESET%^
%^GREEN%^modify here climate temperate%^RESET%^
Ok! A few new things here. A neat thing about outdoor rooms is that
typically they are subject to the time of day. A SetClimate directive
that indicates an exterior environment causes the room to receive
messages about the sun setting, rising, etc.
The SetDayLong and SetNightLong directives allow you to more
sensibly describe the area depending on the time of day. To avoid
confusion, I deleted the SetLong directive. It is not mandatory to
have different day and night descriptions, but players appreciate the
effort.
It is also possible to have differing ambient light levels depending
on the time of day, so we've added SetDayLight and SetNightLight, and
we deleted the SetAmbientLight directive.
Let's continue to add detail:
%^GREEN%^modify here item%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Healthy, well groomed and freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You can smell the refreshing scent of freshly cut grass.%^RESET%^
%^GREEN%^modify here smell%^RESET%^
%^GREEN%^lawn%^RESET%^
%^GREEN%^grass%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Yep, it's got that new lawn smell.%^RESET%^
%^GREEN%^modify here listen%^RESET%^
%^GREEN%^building%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^Sounds like someone's fumbling about in there, making a mess. New creators can be so noisy.%^RESET%^
%^GREEN%^modify here item%^RESET%^
%^GREEN%^garden%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^You may enter the garden from here.%^RESET%^
%^GREEN%^create room garden garden_room%^RESET%^
You now have a room with lots of charm and detail. You can \"smell grass\"
and \"listen to small building\", if you like. Neat, huh? But there's something
very important to keep in mind:
Enters, listens and smells don't work properly if there is no item defined
for that smell. For example, if you want to be able to listen to the sea,
you must \"modify here item\" and add a \"sea\" item. Otherwise, \"listen
to the sea\" will respond with \"There is no sea here.\"
The only exception to this rule is the \"default\" smell.
Enters behave similarly. If you want to be able to \"enter\" something,
you'll need to create the corresponding item first, as in the example above.
You can use the SetProperties directive to make the room
conform to some presets, like:
%^GREEN%^modify here property no attack 1%^RESET%^
Read chapter 23 in the Creator's Manual for details on room properties.
Also, please note that indoor rooms can also have differing
descriptions and light levels for night and day. It's just that
indoor rooms don't get notification of daytime changes.
Finally, the SetTown directive allows the room to participate in
area-wide events, and is useful for security purposes as well:
%^GREEN%^modify here town MyTown%^RESET%^
Notes on room filenames:
-----------------------
By default, a filename without a leading path creates a room
in your area room directory, which in my case would be
\"/realms/cratylus/area/room\". However, you can specify a different
location for the new room.
To create a room in your current working directory:
%^GREEN%^create room east ./newroom%^RESET%^
To create a room in a specific directory:
%^GREEN%^create room east /realms/cratylus/testrooms/newroom%^RESET%^
",({"chapter 39","chapter thirty-nine","39",}):"chapter 39 \"QCS: Final notes\"
* Remember that QCS commands work on objects, not files. To
load a file into memory, use the update command. To reload an
already existing object, like a cloned orc or a book, use
the reload command. To use modify, delete, and add, you have
to specify a cloned object that is on you or in your environment.
I think you're getting the idea of how this works. Here's
an example of armor creation:
%^GREEN%^create armor jeans%^RESET%^
%^GREEN%^modify jeans id%^RESET%^
%^GREEN%^pants%^RESET%^
%^GREEN%^trousers%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify jeans short a pair of denim jeans%^RESET%^
%^GREEN%^modify jeans long Worn jeans, frayed and stained.%^RESET%^
%^GREEN%^modify jeans adj%^RESET%^
%^GREEN%^pair of%^RESET%^
%^GREEN%^denim%^RESET%^
%^GREEN%^frayed%^RESET%^
%^GREEN%^worn%^RESET%^
%^GREEN%^stained%^RESET%^
%^GREEN%^.%^RESET%^
To know what directives QCS can change on an object, type:
%^GREEN%^help modify%^RESET%^
This provides a list of modifiable things and the directives
that can be modified on them.
Ultimately the Quick Creation System generates LPC code, so
you'll want to review the earlier chapters of this handbook to
get a base of understanding of the code that comprises your new
creations.
Some notes and tips:
* The SetNoCondition directive makes it so an item does not report
its physical status when examined. Weapons and armor wear down in
combat, and most objects let you know their condition when you
examine them. However, in some cases (a sandwich for example)
this is inappropriate, so the SetNoCondition directive may be
useful.
* Doors aren't like normal objects. They have to be modified *twice*.
Once for each side of the door. If this sounds unnecessarily
tedious, remember that a door leading south is also a door
leading north from the other room.
* Doors generally are not visible in the same way that regular
objects are. To make a door especially obvious and noticeable,
do something like:
%^GREEN%^modify door sethiddendoor 0%^RESET%^
* SetCurrency is for adding money to NPC's. SetMoney is for adding
money to non-living containers (bags, etc).
* Item subtypes are listed in /include. To know what kinds of vendors
are available, for example, look in /include/vendor_types.h
* Books need a \"source directory\", which must contain one file per
chapter. The SetSource for this manual, for example, is /doc/manual
The first line of each file must follow the same format as
the files you see in /doc/manual
* SetObviousExits is usually no longer needed: rooms report
obvious exits automatically. However, if you don't want an exit
to show up by default, use the SetObviousExits directive
to specify only those that you want seen. This will override the
room's default exit display.
",({"chapter 36","chapter thirty-six","36",}):"chapter 36 \"QCS: Modifying weapons\"
Remember that the QCS chapters are supposed to be read in
sequence. This is important because as we progress, I will not make
explanations about directives and concepts explained previously.
Weapons are different from rooms and NPC's in that they can
be handled, sold, thrown, etc. They are manipulable objects. As such,
we will see new directives:
%^GREEN%^create weapon hammer%^RESET%^
You may be familiar with this example from the example webpage.
Let's go ahead and plow through the commands:
%^GREEN%^modify weapon id hammer%^RESET%^
%^GREEN%^warhammer%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer name hammer%^RESET%^
%^GREEN%^modify hammer damagetype blunt%^RESET%^
%^GREEN%^modify hammer weapontype blunt%^RESET%^
%^GREEN%^modify hammer mass 700%^RESET%^
%^GREEN%^modify hammer hands 2%^RESET%^
%^GREEN%^modify hammer short a heavy war hammer%^RESET%^
%^GREEN%^modify hammer long This is an extremely large and heavy hammer designed to be wielded in both hands and used to hurt people very badly indeed.%^RESET%^
%^GREEN%^modify hammer adj%^RESET%^
%^GREEN%^large%^RESET%^
%^GREEN%^heavy%^RESET%^
%^GREEN%^war%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify hammer basecost silver 750%^RESET%^
%^GREEN%^about hammer%^RESET%^
Like a room and unlike an NPC, you can also modify the SetItems on
manipulable objects like weapons, so you could do something like this:
%^GREEN%^modify hammer item%^RESET%^
%^GREEN%^shaft%^RESET%^
%^GREEN%^handle%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^A thick, reinforced steel shaft with leather bands for a strong grip.%^RESET%^
%^GREEN%^exa shaft on hammer%^RESET%^
",({"chapter 38","chapter thirty-eight","38",}):"chapter 38 \"QCS: Adding and deleting\"
Rooms, containers and NPC's all are capable of holding
items, and often it is convenient to have them already holding certain
items upon creation.
The SetInventory directive in an object's file provides us with
a list of that object's \"permanent inventory\". To modify an object's
inventory, we use the add and delete commands.
Let's say that we want our cowboy to be wielding a
hammer when he appears...
%^GREEN%^clone cowboy%^RESET%^
%^GREEN%^cd ../weap%^RESET%^
%^GREEN%^clone hammer%^RESET%^
%^GREEN%^add hammer to cowboy%^RESET%^
%^GREEN%^wield hammer%^RESET%^
Believe it or not, it's that simple. The add command will ask
you a question after you issue it. What it wants to know is if you
want the NPC to do anything special when the hammer appears on him.
In this case, yes, we wanted him to wield it.
However, if you want to add something to an NPC and don't
have anything interesting for him to do with it, respond to the
question with the number of items that you want to appear. For
example, if I want the cowboy to be carrying a key:
%^GREEN%^cd ../obj%^RESET%^
%^GREEN%^clone key%^RESET%^
%^GREEN%^add key to cowboy%^RESET%^
%^GREEN%^1%^RESET%^
And that's it. Now if I want the cowboy to be a permanent
resident of this room:
%^GREEN%^add cowboy%^RESET%^
%^GREEN%^1%^RESET%^
The add command understands that if you don't specify a
target argument, you must mean the room. You can also be specific:
%^GREEN%^add cowboy to room%^RESET%^
%^GREEN%^1%^RESET%^
The delete command works the opposite way. It removes items from
an object's permanent inventory:
%^GREEN%^delete hammer from cowboy%^RESET%^
%^GREEN%^delete cowboy%^RESET%^
The delete command is also the way to get rid of rooms. You won't
be removing the file from disk, you'll just be deleting that exit
from the room:
%^GREEN%^delete exit garden%^RESET%^
NOTE: When you delete an exit, only the connection from your room to
the other room is removed. The connection from the other room to
your room remains. This is not a bug, it's a feature. There are plenty
of circumstances where one-way travel is desirable.
",({"chapter 39","chapter thirty-nine","39",}):"chapter 39 \"QCS: Final notes\"
* Remember that QCS commands work on objects, not files. To
load a file into memory, use the update command. To reload an
already existing object, like a cloned orc or a book, use
the reload command. To use modify, delete, and add, you have
to specify a cloned object that is on you or in your environment.
I think you're getting the idea of how this works. Here's
an example of armor creation:
%^GREEN%^create armor jeans%^RESET%^
%^GREEN%^modify jeans id%^RESET%^
%^GREEN%^pants%^RESET%^
%^GREEN%^trousers%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^modify jeans short a pair of denim jeans%^RESET%^
%^GREEN%^modify jeans long Worn jeans, frayed and stained.%^RESET%^
%^GREEN%^modify jeans adj%^RESET%^
%^GREEN%^pair of%^RESET%^
%^GREEN%^denim%^RESET%^
%^GREEN%^frayed%^RESET%^
%^GREEN%^worn%^RESET%^
%^GREEN%^stained%^RESET%^
%^GREEN%^.%^RESET%^
To know what directives QCS can change on an object, type:
%^GREEN%^help modify%^RESET%^
This provides a list of modifiable things and the directives
that can be modified on them.
Ultimately the Quick Creation System generates LPC code, so
you'll want to review the earlier chapters of this handbook to
get a base of understanding of the code that comprises your new
creations.
Some notes and tips:
* The SetNoCondition directive makes it so an item does not report
its physical status when examined. Weapons and armor wear down in
combat, and most objects let you know their condition when you
examine them. However, in some cases (a sandwich for example)
this is inappropriate, so the SetNoCondition directive may be
useful.
* Doors aren't like normal objects. They have to be modified *twice*.
Once for each side of the door. If this sounds unnecessarily
tedious, remember that a door leading south is also a door
leading north from the other room.
* Doors generally are not visible in the same way that regular
objects are. To make a door especially obvious and noticeable,
do something like:
%^GREEN%^modify door sethiddendoor 0%^RESET%^
* SetCurrency is for adding money to NPC's. SetMoney is for adding
money to non-living containers (bags, etc).
* Item subtypes are listed in /include. To know what kinds of vendors
are available, for example, look in /include/vendor_types.h
* Books need a \"source directory\", which must contain one file per
chapter. The SetSource for this manual, for example, is /doc/manual
The first line of each file must follow the same format as
the files you see in /doc/manual
* SetObviousExits is usually no longer needed: rooms report
obvious exits automatically. However, if you don't want an exit
to show up by default, use the SetObviousExits directive
to specify only those that you want seen. This will override the
room's default exit display.
",({"chapter 34","chapter thirty-four","34",}):"chapter 34 \"QCS: Modification of NPC's\"
In the previous chapter we learned how to make a generic object.
Now that we have it, what to do with it?
It's important to keep in mind that the generic thing now in
front of you isn't just a clone from a template file. If the command
you used was \"create npc cowboy\", there is now a file (probably)
called /realms/you/area/npc/cowboy.c that contains the code for the
creature in front of you.
However, this poor beast has the most uninteresting of features.
It is in fact so boring that it responds only to its generic type.
Such that \"examine cowboy\" or \"kill tex\" won't work. You'll need to
\"look at npc\".
Accordingly, any modification commands need to be made referring
to the new thing with an it responds to, until such a time as you
change the id to suit your tastes. Let's carry on with the example
of our generic npc. To make a cowboy out of him, we can either
change his name, or his id, or both. Let's start with his name:
%^GREEN%^modify npc name cowboy%^RESET%^
This makes the SetKeyName() directive in cowboy.c use \"cowboy\"
as its argument, effectively allowing you to address this npc as \"cowboy\"
from now on. Now you can \"look at cowboy\" with some results.
Obviously our NPC isn't *just* a cowboy. He's also a human, a dude,
and his name is Tex. How do we make him respond to all of these nouns?
%^GREEN%^modify cowboy id%^RESET%^
You'll notice there are no arguments following the word \"id\". Setting
a thing's id is different from most other settings. If you'll think back
to the LPC datatypes chapter of this manual (you did read the LPC
chapters, didn't you?) you'll remember that some information about
objects is in the form of strings (\"cowboy\"), some is in the form of
integers (the cowboy's health points, for example) and some is in the
form of arrays, which are a group of data points. In this example we
want the cowboy's id to be an array, because of the many ways we might
want to address him. Therefore, we want the SetId directive in his
file to look like this:
SetId( ({\"human\", \"dude\", \"tex\" }) );
You might think that QCS should be able to accept multiple values
like this on a line, perhaps with \"modify cowboy id human dude tex\" as
the command.
But what if you want this npc to be a \"Boy Named Sue\"? How would
you accommodate id's that contain spaces? In designing QCS, I considered
having escape characters to allow for such things, but ultimately
reasoned that this was just way too much mess. Instead, SetId, and
other directives that take arrays as arguments, are handled by entering
a query session. Below is an example of what it might look like. The
example is necessarily messy because I am including both the queries
and the responses.
---------------------------------------------------
> %^GREEN%^modify npc name cowboy%^RESET%^
Indenting file...
\"/tmp/indent.1136206130.tmp.dat\" 15 lines 420 bytes
Exit from ed.
> %^GREEN%^modify cowboy id%^RESET%^
This setting takes multiple values. If you have no more values to enter, then
enter a dot on a blank line. To cancel, enter a single q on a blank line.
You may now enter the next value. So far, it is blank.
If you're done entering values, enter a dot on a blank line.
%^GREEN%^dude%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^human%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^tex%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^boy named sue%^RESET%^
You may now enter the next value. So far, we have: ({ \"dude\", \"human\", \"tex\",
\"boy named sue\" })
If you're done entering values, enter a dot on a blank line.
%^GREEN%^.%^RESET%^
Entries complete. Final array is: ({ \"dude\", \"human\", \"tex\", \"boy named sue\" })
Indenting file...
\"/tmp/indent.1136206156.tmp.dat\" 19 lines 459 bytes
Exit from ed.
/open/1136206138: Ok
/realms/cratylus/area/npc/cowboy: Ok
SetId modification complete.
> %^GREEN%^exa tex%^RESET%^
Other than being human, this npc is entirely unremarkable.
The male human is in top condition.
---------------------------------------------------
If you were now to examine Tex's code (with the command \"about tex\")
you'd see that his SetId directive now looks like this:
SetId( ({\"dude\", \"human\", \"tex\", \"boy named sue\"}) );
Other NPC features take arrays also. SetAdjectives is one. You
might enter this (mud output omitted for clarity):
%^GREEN%^modify tex adjectives%^RESET%^
%^GREEN%^dusty%^RESET%^
%^GREEN%^hardy%^RESET%^
%^GREEN%^.%^RESET%^
%^GREEN%^look at dusty cowboy%^RESET%^
There are two other directives that require queries. Things and
NPC's can be looked at, but they can also be smelled and listened
to, if you add SetSmell and SetListen. The syntax is:
%^GREEN%^modify tex smell%^RESET%^
And you will then be asked a question about keys and mappings.
Understanding mappings is important, but for now you just need to
understand that you are being asked *two* separate questions:
1) What on the cowboy is being smelled/listened to?
2) What is the smell/sound?
What this means is that your input will look something like
this:
%^GREEN%^modify tex smell%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex smells of sweat and manure.
What happens is this:
- You enter the modify command.
- You enter the word \"default\" to indicate this is Tex's general smell.
- You enter a dot to indicate that you are done specifying what
part of Tex is being smelled.
- You then specify the smell.
This may seem odd until you realize you can also add smells/listens to
parts of things. Not on NPC's, though. We'll look at this more closely in
later chapters. For now, just use the syntax as shown above. For adding
a listen to the cowboy, it works the same way:
%^GREEN%^modify tex listen%^RESET%^
%^GREEN%^default%^RESET%^
%^GREEN%^.%^RESET%^
Tex seems to be humming a jaunty melody.
Other features of an NPC do not take arrays, so one-line commands
will do. For example:
%^GREEN%^modify cowboy long This is a cowboy who calls himself Tex, but is in fact a Boy Named Sue.%^RESET%^
%^GREEN%^modify cowboy short a cowboy%^RESET%^
%^GREEN%^modify cowboy level 5%^RESET%^
%^GREEN%^modify cowboy class fighter%^RESET%^
%^GREEN%^modify tex currency gold 1%^RESET%^
%^GREEN%^modify tex currency silver 12%^RESET%^
%^GREEN%^modify tex skill bargaining 5%^RESET%^
%^GREEN%^modify tex skill projectile attack 7%^RESET%^
%^GREEN%^modify tex stat strength 33%^RESET%^
%^GREEN%^modify tex property nice guy 1%^RESET%^
%^GREEN%^modify tex healthpoints 150%^RESET%^
%^GREEN%^modify tex maxhealthpoints 170%^RESET%^
%^GREEN%^modify tex melee 0%^RESET%^
%^GREEN%^modify tex unique 1%^RESET%^
If you now issue the \"about tex\" command you will see that all
the changes you made have been put into the file.
You may have noticed the \"melee\" keyword. Dead Souls 2 NPC's come
in various shapes and sizes, and some of them shouldn't wield weapons. A
wolf with a battle axe would be a strange sight indeed. However, the
default combat system makes unarmed creatures extremely vulnerable in
combat.
To make an NPC combat-capable without weapons, use the new SetMelee
directive. SetMelee(1) makes the NPC capable of proper unarmed combat.
SetMelee(0) makes the NPC a weak opponent if unarmed.
NPC's will generally try to bite during unarmed combat. If this
is beneath the capability or dignity of your NPC, you can prevent
this with:
%^GREEN%^modify tex canbite 0%^RESET%^
If your NPC should try to escape when the battle isn't going
his way, the wimpy settings should do:
%^GREEN%^modify tex wimpy 30%^RESET%^
%^GREEN%^modify tex wimpycommand climb ladder%^RESET%^
If you don't specify a wimpy command, Tex will leave the
room through a random exit. In this case, when Tex's health
is down to 30% and he is in combat, he will try to climb a ladder.
Some NPC's are designed to travel about. To enable this
feature, use the wanderspeed directive:
%^GREEN%^modify tex wanderspeed 5%^RESET%^
If you want him to travel more quickly, use a
lower number. By default, wandering NPC's only wander in rooms
that have already been loaded into memory. They avoid loading
rooms because loading a bunch of rooms that only the NPC
will ever see is a waste of your mud's resources.
However, if you *do* want your NPC to wander in an
unrestricted manner, regardless of whether a room is loaded,
use the permitload directive:
%^GREEN%^modify tex permitload 1%^RESET%^
By default, NPC's stand up when they can. This is
so that if they collapse during combat, they try to
get back up once they are able to do so.
If you prefer that your NPC maintain some other
posture, you can set that posture, then disable
autostanding like this:
%^GREEN%^modify tex posture lying%^RESET%^
%^GREEN%^modify tex autostand 0%^RESET%^
If he's especially lazy, you can have him take
a nap this way:
%^GREEN%^modify tex sleeping 10%^RESET%^
Which will have him wake up after about a minute.
However, note that if you've disabled autostanding,
he will remain lying down after he wakes up.
If the NPC should be hostile, that is, he should
attack any creatures that it sees enter a room,
SetEncounter should do it:
%^GREEN%^modify tex encounter 100%^RESET%^
This means that if the creature it sees has a
charisma score of less than 100 (which should pretty
much be always true), Tex will try to kill it. You
can do some fancy stuff with SetEncounter, such
as only attacking orcs, or actually doing something
friendly, but to do so you can't use QCS. Read
the NPC and Sentients chapter in the Creator's
Manual for details on how to code such stuff.
If the NPC is a golem or other such non-biological
creature, it may be useful to specify what they are
made of. The SetComposition setting for a clay
golem might look like this:
%^GREEN%^modify golem composition clay%^RESET%^
If it happens to be a golem that does not believe
in violence as a solution to problems, you can
make refuse to hurt others with the following:
%^GREEN%^modify golem pacifist 1%^RESET%^
Vendors:
-------
Vendors are a special kind of NPC that can sell stuff.
Along with the standard NPC settings, vendors have
the following:
SetStorageRoom specifies where the vendor's stock
is stored. If a valid room is specified, anything
in that room can be sold by the vendor.
SetLocalCurrency specifies the type of currency,
such as gold or silver, that the vendor accepts.
SetMaxItems is the maximum number of items in
the storeroom that the vendor is permitted to sell.
SetVendorType specifies the kind of stuff the vendor can
trade in. \"all\" allows him to buy and sell whatever.
But if he is a weapon vendor, he can't trade in armor,
etc. See /include/vendor_types.h for the available
vendor types. To have a \"multi-type\" vendor, you'll have to
code it by hand. The result of that looks something
like this:
SetVendorType( VT_TREASURE | VT_ARMOR | VT_HERBS );
Barkeeps:
--------
Like vendors, barkeeps sell stuff, but they are
limited to selling food and drink.
Unlike vendors, barkeeps have no limitation on
the amount of stuff they can sell. They also do
not have a storeroom. The stuff they can sell is
specified in their SetMenu directive, like this:
%^GREEN%^clone woody%^RESET%^
You clone a generic barkeep (/realms/temujin/area/npc/woody.c).
%^GREEN%^modify woody menu%^RESET%^
If you don't understand these questions, type the letter q on a blank line and hit enter.
Please enter the first key element for this mapping:
%^GREEN%^bourbon%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^whiskey%^RESET%^
Please enter the next key element, or enter a single dot to finish entering key elements.
%^GREEN%^.%^RESET%^
Please enter the value for key ({ \"bourbon\", \"whiskey\" }):
%^GREEN%^/domains/town/meals/bourbon%^RESET%^
Barkeeps also have the SetLocalCurrency directive
to specify the currency they accept.
",]),"/doc/bguide/chapter08":1203153606,"/doc/bguide/chapter07":1203153606,"/doc/bguide/chapter06":1203153606,]),"/domains/town/txt/shame":(["object":"/domains/town/obj/shamelog","title":"Untitled","items":([({"chapter 3","chapter three","3",}):"\"September 11 2001 (Warning: not that funny)\"
",({"chapter 5","chapter five","5",}):"\"The Great Heat Radiation Debate, December 1999\"
",({"chapter 4","chapter four","4",}):"\"Cratylus Has His Buttons Pushed\"
",({"chapter 1","chapter one","1",}):"\"Duuk Kills a Newbie\"
",({"chapter 2","chapter two","2",}):"\"The Demise of Wolfsong\"
",]),"/domains/town/txt/shame/chapter05":1152072075,"index":" Untitled
Chapter 1: \"Duuk Kills a Newbie\"
Chapter 2: \"The Demise of Wolfsong\"
Chapter 3: \"September 11 2001 (Warning: not that funny)\"
Chapter 4: \"Cratylus Has His Buttons Pushed\"
Chapter 5: \"The Great Heat Radiation Debate, December 1999\"
","/domains/town/txt/shame/chapter04":1152072075,"reads":([({"chapter 3","chapter three","3",}):"chapter 3 \"September 11 2001 (Warning: not that funny)\"
[igossip] Hellmonger@Trilogy: actually, I think efnet.net got rooted and they
just took the server down.
[igossip] Alric@Nanvaent: efnet is still up.
[igossip] Smack@Lima Bean: ZIONIST NAZIS!!!!!!!!!!!!!!!!!!
[igossip] Motorhed@Trilogy says, \"Four out of five doctors agree. I am the
fifth doctor. I hope you die. Fuck you.\"
[igossip] Shandor@Bakhara: we had a student riot over the weekend!
[igossip] Malic@Lima Bean:
http://mdn.mainichi.co.jp/waiwai/0109/010909diet.html.
[igossip] Blue@Lima Bean: of all the igossip questions for me to be able to
answer, one about boxer shorts was _not_ the one I expected.
[igossip] Ninja@VargonMUD: SHARK!!!
[igossip] Zeddicus@Haven: ARBORPHILES!!!
[igossip] Ninja@VargonMUD: Once ya hug a lumberjack, ya'll never hug a tree
again!
[igossip] Hellmonger@Trilogy: THE NUKES ARE FALLING.
[igossip] Hellmonger@Trilogy: FRM THE SKIES.
[igossip] Hellmonger@Trilogy: AS TERRORISTS TORCH THE U.S.
[igossip] Hellmonger@Trilogy: ITS A HORIBLE DAY.
[igossip] Hellmonger@Trilogy: AHHHHHHHHH I'M MELTING.
[igossip] Al@Anarres II: HM, shut up.
[igossip] Hellmonger@Trilogy: I just heard two planes crahsed into the trade
center.
[igossip] Al@Anarres II: and very f*cking news iste is down for me ATM :(
[igossip] Zeddicus@Haven: Good for them.
[igossip] Nirvan@Nanvaent: yeah - two big jets.
[igossip] Hellmonger@Trilogy: Yeah, how fucked.
[igossip] Nirvan@Nanvaent: one for each tower - I 'm trying to work out if its
April 1st somewhere...
[igossip] Hellmonger@Trilogy: I vote we turn the fucking middle east into a
large sheet of glass.
[igossip] Al@Anarres II: its a terrorist thing!
[igossip] Hellmonger@Trilogy: I'm glad I don't live in england, where there are
a lot of terrorists.
[igossip] Al@Anarres II: it makes life fun!
[igossip] Al@Anarres II: nightly bulletins saying some irish bloke has shot
another.
[igossip] AndrewH@Anarres II: keeps the irish population down. like the potato
famine.
[igossip] Al@Anarres II nods
[igossip] Al@Anarres II: its most definetly terrorist.
[igossip] Al@Anarres II: two planes, two towers.. thats not just unlucky.
[igossip] Al@Anarres II: well, back to work...
[igossip] Zeddicus@Haven: A father came in the bedroom to find his 13-year-old
daughter smoking a cigarette. \"My God! How long have you been smoking?\" screams
the father. \"Since I lost my virginity,\" replies the girl. \"You lost your
VIRGINITY!!! When the hell did this happen?\" shrieks the father. \"I don't
remember,\" says the girl. \"I was completely drunk.\"
[igossip] Smack@Lima Bean: holy fuck.
[igossip] Hellmonger@Trilogy: yeah no doubt.
[igossip] Hellmonger@Trilogy chants: \"Nuke The Middle East! Nuke The Middle
East! Nuke The Middle East!\"
[igossip] Al@Anarres II: why do you say its the middle east? you said that
about oklahoma and it was some US bloke.
[igossip] Styxx@OuterSpace: shaddup.
[igossip] Al@Anarres II: you HM, are a racist homophobe.
[igossip] Hellmonger@Trilogy: You are obviously a jew-sympathiser. You will
burn with your dirty hebrew friends.
[igossip] Al@Anarres II: hmmm. HM, shut the fuck up.
[igossip] Smack@Lima Bean: whoever it is must die.
[igossip] Hellmonger@Trilogy: Al, do you worship Osama Bin Laden?
[igossip] Hellmonger@Trilogy: Why can't people pick new and exciting targets
for their terrorist acts?
[igossip] Hellmonger@Trilogy: 'Lets blow up the world trade center!' 'Lets
crash into the world trade center!'
[igossip] Al@Anarres II grins at HM, \"I only worship you\"
[igossip] Hellmonger@Trilogy: Al, you must blow up the quik-mart. The infidels
must pay.
[igossip] Nirvan@Nanvaent: wouldn't it be a better statement to fly the plane
into the white house ?
[igossip] Sinistrad@VargonMUD: Harder to do.
[igossip] Nirvan@Nanvaent: or are the protests against the WTO stepped up from
hippies throwing rocks in the streets ?
[igossip] Nirvan@Nanvaent: *blinks*
[igossip] Al@Anarres II: maybe they were passenger planes and someone at air
traffic f*cked up?
[igossip] Sinistrad@VargonMUD: BREAKING NEWS: One of the planes was being flows
by Captain Hazelwood, of the Exxon Valdez!
[igossip] Nirvan@Nanvaent: harder than hijacking two 747s and flying them into
the center of NY ?
[igossip] Al@Anarres II: doesn;t Xyzzy have summin' to do with flying things?
[igossip] Sinistrad@VargonMUD: Yes, Nirvan. Why are you so... curious?
[igossip] Nirvan@Nanvaent: so the SDN is working over the white house then ?
[igossip] Sinistrad@VargonMUD: Try making a hijacked plane hit a ground target.
[igossip] Al@Anarres II: good point Sini.
[igossip] Nirvan@Nanvaent: ahh - so you've thought ab out this then ?
[igossip] Nirvan@Nanvaent: it is a good point.
[igossip] Sinistrad@VargonMUD: No, I just work for the FBI. But you, on the
other hand.. *shuffles papers* Nirvan, AKA \"Idi Ib Amin\".. why are YOU so
curious?
[igossip] Nirvan@Nanvaent: woo - breaking news. CNN have decided that it isn't
an accident !
[igossip] Skullslayer@RoD: so anyone got a good news site with live feed, that
isn't down(DoS'd)?
[igossip] Nirvan@Nanvaent: turn on a TV ?
[igossip] Skullslayer@RoD: haha not an accident - thats clever
[igossip] Skullslayer@RoD: I'm at work, and there is no tv here
[igossip] Pickett@Sumu:
http://a799.ms.akamai.net/3/799/388/9ce0ee0b875c5d/www.msnbc.com/news/1160603.j
pg.
[announce] Xyzzy enters Frontiers.
[igossip] Nirvan@Nanvaent: MSN Search : We can't find \"cnn.com\".
[igossip] Nirvan@Nanvaent: oh god - someone's bombed CNN!
[cre] Cratylus: oi.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: slashdot is even running the story - because CNN is
DOS'ed :)
[igossip] Al@Anarres II: is CCN DoS'd or just very busy?
[igossip] Sinistrad@VargonMUD: Try CNN instead of CCN.
[igossip] Sinistrad@VargonMUD: Maybe you'll get somewhere.
[igossip] Skullslayer@RoD: very busy is a DoS - so is MSNBC and ABCnews
[igossip] Al@Anarres II: DoS implied bad intent.
[igossip] Al@Anarres II: rather than just lots of happy punters.
[igossip] Vashkar@Split Infinity: haha.. dailynews.yahoo.com changed their
story to simply \"A plane crashed into one of the twin towers of the World Trade
Center Tuesday, witnesses said.\" and that's it.. it loads just fine now :)
[igossip] Skullslayer@RoD: they're hit by the slashdot effect, except on a
bigger scale
[igossip] Xyzzy: wtf.
[igossip] Sinistrad@VargonMUD: Jesus, that's impressive.
[igossip] Cratylus: thats so fucked up.
/wiz/cratylus # [igossip] Undh@Sumu: impressive.. yeah .)
/wiz/cratylus # [igossip] Sinistrad@VargonMUD: If I were a terrorist, I would have waited until
more people were at work.
[igossip] Nirvan@Nanvaent: 9am isn't a bad time...
[igossip] Bayard@Nanvaent: morning meetings happening. people mainly in at 8am.
[igossip] Blue@Inon-Net: people start work at 8 in most of those companies.
[igossip] Bayard@Nanvaent: all gathered together in rooms.
[cre] Xyzzy: thats fucked up, dude.
[igossip] Nirvan@Nanvaent: a good day not to be too highly promoted I would
guess.
[igossip] Al@Anarres II: hmm, 2nd plane \"was passenger 727\"... oops.
[igossip] Skullslayer@RoD: yeah, FBI was investigating a report of a hijacking
at the time
[cre] Cratylus: bizarre.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: bush wants to 'hunt down the people who did this'
[igossip] Al@Anarres II: nice - hijack a plane full of US citizen and then
crash it into the trade center :) sweet.
[igossip] Nirvan@Nanvaent: get me my gun, maw...
[igossip] Cratylus: well it's easier than carting a bomb in there, with their
current security.
/wiz/cratylus # [igossip] Sinistrad@VargonMUD: look in 'the charred remains between floor 98
and 99'
[igossip] Nirvan@Nanvaent: seems there is a 3rd plane that's been hijacked ?
[igossip] Cratylus: i guess they'll need to install antiaricraft turrets now.
/wiz/cratylus # [igossip] Xyzzy: YES!
[igossip] Hellmonger@Trilogy: hell yeah.
[igossip] Sinistrad@VargonMUD: Whoever invents the force field will make a
billion.
[igossip] Xyzzy: but, we already HAVE.
[igossip] Al@Anarres II: if someone wants to be a terrorist, they'll be a
terrorist.
[igossip] Xyzzy: the antimissile shield will protect us!
[igossip] Cratylus: and if they want to sing out, sing out.
/wiz/cratylus # [igossip] Styxx@OuterSpace: asia.cnn.com still works.
[igossip] Skullslayer@RoD: a 3rd plane?
[igossip] Nirvan@Nanvaent: the pentagon is on fire.
[igossip] Steve@Anarres II: pentagon on fire?
[igossip] Cratylus: cats and dogs, living together.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: and the palestinians are claiming responsibility.
[igossip] Xyzzy: dont cross the streams.
[igossip] Nirvan@Nanvaent: and my boss wanted me to go to israel last week.
[igossip] Sinistrad@VargonMUD: Ooh, ht//praiseallah.terrorists.com has the
inside report!
[igossip] Cratylus: jesus, a 767
/wiz/cratylus # [igossip] Sinistrad@VargonMUD: The pentagon IS on fire!
[igossip] Cratylus: thats a lot of aircraft.
/wiz/cratylus # [igossip] Xyzzy: wow.
[igossip] Sinistrad@VargonMUD: WOOOOOOO!!!!!
[igossip] Xyzzy: co-worker just said pentgon IS on fire.
[igossip] Sinistrad@VargonMUD: Shit, this is really cool.
[igossip] Al@Anarres II: cool :) You guys have better terrorists!
[igossip] Sinistrad@VargonMUD: Guys, if you're in any kind of important
building.. RUN!
[igossip] Hellmonger@Trilogy: My Unc works at the pentagon. :(
[igossip] Cratylus: did they drop a plane on the pentagon as well?
/wiz/cratylus # [igossip] Xyzzy: fuck this, im goin home.
[igossip] Sinistrad@VargonMUD: Not so happy now, eh?
[igossip] Al@Anarres II cheers for Xyzzy
[igossip] Hellmonger@Trilogy: heh.
[igossip] Xyzzy: no, as in, i dont wanna be in this building.
[igossip] Sinistrad@VargonMUD: Wtf, is today Allah's birthday or something?
[igossip] Cratylus: can someone post a burning-pentagon url?
/wiz/cratylus # [igossip] Nirvan@Nanvaent: white house has been evacuated.
[igossip] Nirvan@Nanvaent: pentagon is on fire, and been evacuated.
[igossip] Blue@Inon-Net: if you were going to start a nuclear war, which two
buildings would you want to have evacuated first?
[igossip] Nirvan@Nanvaent: today is the 20th anniversary of the UN
international day of peace.
[igossip] Blue@Inon-Net: I guess they still have NORAD or some such place.
[igossip] Nirvan@Nanvaent: but war games was on the TV at the weekend.
[igossip] Sinistrad@VargonMUD: No shit, Nirvan?
[igossip] Nirvan@Nanvaent: I know how to get in ther!
[igossip] Cratylus: i'm too tired for this to be Anarchy Day.
/wiz/cratylus # [igossip] Xyzzy: hmm, that is, if i CAN leave...
[igossip] Sinistrad@VargonMUD: I know how to get into the Pentagon, too:
Through the gaping hole.
[igossip] Al@Anarres II: the pentagon has like, 12 stories going down and a
power plant and such. Its paper pushers up top.
[cre] Cratylus: is the pentagon really burning?
/wiz/cratylus # [cre] Xyzzy: yes.
[cre] Xyzzy: it was bombed.
[cre] Cratylus: whew.
/wiz/cratylus # [cre] Xyzzy: apparently.
[cre] Xyzzy: this base is closed.
[cre] Xyzzy: no one gets on.
[igossip] Al@Anarres II: this is class :) brightened up my afternoon no end.
[igossip] Cratylus: all your trade center are belong to us.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: CNN showing a big fire near washington.
[cre] Cratylus: i'd go home.
/wiz/cratylus # [cre] Cratylus: traffic will really suck later.
/wiz/cratylus # [igossip] Skullslayer@RoD: slashdot is down now too :)
[igossip] Diruces@Mystic: they say they have exvacuated the west wing of the
whitehouse too.
[cre] Cratylus: and they'll drop a plane on the highway.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: I saw a comment on slashdot wondering if there was a
'cyberattack' going on at the same time - what a dumb fuckwith.
[igossip] Skullslayer@RoD: most people on slashdot are fuckwits
[igossip] Nirvan@Nanvaent: where is washington amll ?
[igossip] Nirvan@Nanvaent: where is washington mall even.
[igossip] Xyzzy: lessee.... commerce... military...
[igossip] Sinistrad@VargonMUD: Around the Pentagon.
[igossip] Diruces@Mystic: next the whitehouse.
[igossip] Xyzzy: mall is accross the river, in VA.
[igossip] Xyzzy: er.
[igossip] Xyzzy: the pentagon is in VA.
[igossip] Cratylus: thank god, whitehouse.com hasn't been bombed.
/wiz/cratylus # [cre] Xyzzy: really, whats next.
[igossip] Sinistrad@VargonMUD: Your old password still work there?
[igossip] Diruces@Mystic: i like the idea of cheney as pres.
[igossip] Nirvan@Nanvaent: apparently the pentagon fire was caused by a plane
or rocket attack.
[cre] Xyzzy: \"if this was a coordinated attack, what ELSE might be happening
rigt now?\"
[cre] Xyzzy: then tehre weas the pentagon.
[igossip] Nirvan@Nanvaent: bush is at some school somewhere.
[igossip] Nirvan@Nanvaent: probs learning how to spell.
[igossip] Sinistrad@VargonMUD: Was.
[igossip] Nirvan@Nanvaent: the third hijacked plane apparently hit the
pentagon.
[igossip] Al@Anarres II: bush is in a bunker!
[igossip] Xyzzy: what.
[igossip] Xyzzy: next.
[igossip] Blue@Inon-Net: playing golf at a time like this... just like his dad.
[igossip] Cratylus: with his teddy bear.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: well, he was in a bunker with lots of school kids
around him.
[igossip] Xyzzy: any bets?
[igossip] Nirvan@Nanvaent: unless they recorded it.
[igossip] Cratylus: curled up under a desk.
/wiz/cratylus # [igossip] Xyzzy: thumb in mouth.
[igossip] Xyzzy: rockingh back n forth.
[igossip] Nirvan@Nanvaent: xyzzy : 10 to 1, jenna bush gets drunk and falls
around in public in downtown austin this weekend.
[igossip] Nirvan@Nanvaent: naw, make that 2 to 1
[igossip] Nirvan@Nanvaent: bbc is reporting 6 people killed in the crashes
(that all ???)
[igossip] Cratylus: \"Jenna Bush's federally protected wetlands now open for
public drilling\"
/wiz/cratylus # [igossip] Nirvan@Nanvaent: FAA has grounded all flights in the US.
[cre] Cratylus: it's days like this when i kinda wish i had cable tv.
/wiz/cratylus # [igossip] Musashi@Sumu: where'd you read that?
[igossip] Nirvan@Nanvaent: AP.
[cre] Xyzzy: we have a tv, but theres a meeting in tehre now.
[cre] Xyzzy: i hafta rely on YOU GUYS for news.
[cre] Cratylus: ME?
/wiz/cratylus # [cre] Cratylus: i dunno NOTHIN.
/wiz/cratylus # [cre] Xyzzy: hmm, should i go home?
[igossip] Nirvan@Nanvaent: 'the associated press'
[igossip] Nirvan@Nanvaent: or the standard 'newswire'
[igossip] Sinistrad@VargonMUD: CONFIRMED: It was a plane that crashed into the
Pentagon, too.
[igossip] Sinistrad@VargonMUD: CNN is law.
[igossip] Sinistrad@VargonMUD: If CNN makes up stuff about aliens, I believe
it.
[igossip] Nirvan@Nanvaent: who was saying it was really difficult to hit ground
targets with planes ? :)
[igossip] Nirvan@Nanvaent: did they miss ?
[igossip] Sinistrad@VargonMUD: \"Aliens crashed into the Pentagon\" I'll believe
it.
[igossip] Skullslayer@RoD: heh, lots of the US govt agencies get their info
from CNN since its cheaper than having their own people in the field
[igossip] Blaze@VargonMUD: All american flights have been grounded.
[igossip] Nirvan@Nanvaent: it was - illegal Aliens crashed into the Pentagon.
[igossip] Sinistrad@VargonMUD: Heh =-)
[igossip] Xyzzy: ALIENS BOMBED THE PENTAGON?!?!
[igossip] Cratylus: those two targets seem kinda pointless to.
/wiz/cratylus # [igossip] Cratylus: me.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: which ones ?
[igossip] Xyzzy: \"she thought they said 'ILLEGAL aliens' and signed up\"
[igossip] Bayard@Nanvaent: symbolic Crat.
[igossip] Cratylus: they shoulda found bush's locaation and dropped a plane on
him.
/wiz/cratylus # [igossip] Bayard@Nanvaent: very symbolic. especially outside of US.
[igossip] Sinistrad@VargonMUD: Maybe it was Jenna Bush flying daddy's plane.
[igossip] Nirvan@Nanvaent: he was in a school.
[igossip] Al@Anarres II: hijacking any plane and crashing it is gonna get you
coverage.
------------------------------------------------------------------------
F r o n t i e r s
(EDT is: Tue Sep 11 09:53:36 2001)
There are 3 users connected.
------------------------------------------------------------------------
Xyzzy 4s Low Crawl access4.digex.net
Zaphod 11h empty plain 77.163.252.64.snet.net
Cratylus Cratylus' Cubicle cratylus
------------------------------------------------------------------------
/wiz/cratylus # [igossip] Blue@Inon-Net: American spy plane disappeared over Iraq today.
[igossip] Sinistrad@VargonMUD: Xy, the actress who played Valdez in that movie
actually showed up at casting time thinking the movie WAS about illegal aliens.
They kept the \"inside joke\" in the movie.
[igossip] Nirvan@Nanvaent: unmanned US spy plane.
[igossip] Cratylus: vasquez.
/wiz/cratylus # [igossip] Xyzzy: ya, i read that on IMDB :)
[igossip] Nirvan@Nanvaent: you know the real big question about all this ?
Who has the movie rights ?
[igossip] Xyzzy: me.
[igossip] Nirvan@Nanvaent: when will Tom Clancey's book come out ?
[igossip] Blue@Inon-Net: Tom Clancy already wrote this book :)
[igossip] Bayard@Nanvaent: \"Why hasn't Tom Clancy forseen this!\"
[igossip] Cratylus: clancey's book will have biological weapons in the planes.
/wiz/cratylus # [igossip] Blue@Inon-Net: I bet that's where they got the idea.
[igossip] Skullslayer@RoD: he did the book years ago
[igossip] Xyzzy: wait, whata bout the unmanned Am. spyplane?
[igossip] Al@Anarres II: I lost one of my peers in NYC... wonder why.
[igossip] Cratylus: im still waiting for them to nuke a football stadium, like
he said.
/wiz/cratylus # [igossip] Xyzzy: ah, ok, i see the story.
[igossip] Nirvan@Nanvaent: israel was moving in to palestine today -
[igossip] Xyzzy: GIT MAH GUN!
[igossip] Cratylus: they highjacked an unmanned us spy plane and crashed it
into the iraqui desert! those madmen!
/wiz/cratylus # [igossip] Musashi@Sumu: so has anyone claimed responsibility for this yet?
[igossip] Nirvan@Nanvaent: palestinians,
[igossip] Nirvan@Nanvaent: the IRA, Iraq want a piece...
[igossip] Bayard@Nanvaent: the NBA.
[igossip] Nirvan@Nanvaent: problem is, everyone will try to claim it.
[igossip] Xyzzy: not to jinx us, but it seems that you never know when the shit
is gonna hit the fan like this.
[igossip] Cratylus: the judean people's front.
/wiz/cratylus # [igossip] Xyzzy: SPLITTERS!
[igossip] Blue@Inon-Net: it's the anti-globalisation protesters.
[igossip] Bayard@Nanvaent: people's judean front.
[igossip] Blue@Inon-Net: think about it: the World Trade Centre.
[igossip] Xyzzy: and?
[igossip] Iain@Anarres II: popular peples front of judea.
[igossip] Blue@Inon-Net: They got it confused with the WTO.
[igossip] Xyzzy: WWF?
[igossip] Izzy@VargonMUD: I think it was just a big misunderstanding.
[igossip] Nirvan@Nanvaent: state dept on fire.
[igossip] Nirvan@Nanvaent: another plane just hit.
[igossip] Xyzzy: WHAT?
[igossip] Nirvan@Nanvaent: south tower of the WTO has collapsed.
[igossip] Presto@Discworld: fuckin hell.
[igossip] Musashi@Sumu: jesus, I'm not going to be able to get cricket results
for the next 4 weeks with this flooding the news...
[igossip] Xyzzy: at least we dont hafta hear about gary conit anymore.
[igossip] Xyzzy: er, conDit.
[igossip] Nirvan@Nanvaent: capitol building and treasury evacuated.
[igossip] Nirvan@Nanvaent: most of manhantten under smoke/ on fire.
[igossip] Al@Anarres II goes to thebunker.net
[igossip] Blaze@VargonMUD: And a couple in SF. The trans-america building too.
[igossip] Nirvan@Nanvaent: sears tower in chicago attacked too.
[igossip] Al@Anarres II: nice!
[igossip] Xyzzy: overheard at work: USA today building.
[igossip] Presto@Discworld: not attacked, evacuated.
[igossip] Al@Anarres II lives no where near anything tall :)
[igossip] Cratylus: can't believe it didnt occur to them to drop a plane on
clinton's harlem office.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: who cares about clinton ?
[igossip] Cratylus: best way to intimidat current presidents is killing former
ones.
/wiz/cratylus # [igossip] Nirvan@Nanvaent: saw the 'third explosion' replay - looked like the
building just collapsed inwards.
[igossip] Al@Anarres II: nice, the BBC have \"given up\" trying to get their
sites up :))
[igossip] Skullslayer@RoD: clinton is in australia at the moment anyway
[igossip] Pickett@Sumu: wtc collapsed?
[igossip] Skullslayer@RoD: south tower
[igossip] Cratylus: the whole thing fell?
/wiz/cratylus # [igossip] Tarl@Alsherok: He is ? Crap. Any idea where in Australia ?
[igossip] Presto@Discworld: top half.
[igossip] Presto@Discworld: as opposed to the bottom half.
[igossip] Skullslayer@RoD: golf resort
[igossip] Xyzzy: heh.
[igossip] Cratylus: that is a lot of cead people.
/wiz/cratylus # [igossip] Xyzzy: so, sears tower, too?
[igossip] Cratylus: hard to believe so much of it would fall off from a plane
crash.
/wiz/cratylus # [igossip] Presto@Discworld: no.
[igossip] Xyzzy: fuck, i need a list, here.
[igossip] Pickett@Sumu: ... President Bush, .. from a school in Sarasota,
Fla...
[igossip] Presto@Discworld: Well, Jesus, it wasn't a Cessna, it was a frigging
737/767 depending on who you believe.
[igossip] Cratylus: i say we cut off aid to israel until they handle their
terrorist problem.
/wiz/cratylus # [igossip] Pickett@Sumu: total three planes crashed in wtc. the last one caused
the tower to collapse.
[igossip] Xyzzy: overheard at work: whitehouse has smoke comng out of it.
[igossip] Blue@Inon-Net: it's not exactly \"their\" problem any more.
[igossip] Sinistrad@VargonMUD: BREAKING NEWS: Dave Matthew's song \"Crash into
me\" said to have inspired the terrorists!
[igossip] Cratylus: well its all coming from there, one way or another.
/wiz/cratylus # [igossip] Xyzzy: THREE?
[igossip] Presto@Discworld: It didn't look to me like there was a third crash
into the WTC, looked like it just fell.
[igossip] Cratylus: it aint the IRA dropping planes in NYC.
/wiz/cratylus # [igossip] Pickett@Sumu: yeah, a third (smaller) plane in the bottom part of the
tower caused it to fall.
[igossip] Xyzzy: maybe they stored a plane in the basement.
[igossip] Presto@Discworld: CNN now says the Sears Tower has NOT been
evacuated.
[igossip] Blue@Inon-Net: if I were working there, I'd have evacuated.
[igossip] Sinistrad@VargonMUD: Thank you, Presto. *Presses a button labeled
\"Sears\"*
[igossip] Cratylus: if i worked there, i'd have evacuated my bowels.
/wiz/cratylus # [igossip] Skullslayer@RoD: is the whitehouse hit confirmed?
[igossip] Cratylus: no whitehouse.com is safe.
/wiz/cratylus # [igossip] Cratylus: and how do you slay a skull, anyway?
/wiz/cratylus # [igossip] Skullslayer@RoD: very carefully
[igossip] Xyzzy: with a skullslaying device.
[igossip] Skullslayer@RoD: exec building next to the whitehouse hit?
[igossip] Sinistrad@VargonMUD: Jesus Christ!
[igossip] Al@Anarres II: well, the Dow and NASDAQ are both up.
[igossip] Xyzzy: alright, everyone keep their rumors to themselves, please only
give us confirmed hits.
[igossip] Blue@Inon-Net: is he responsible, or been hit?
[igossip] Presto@Discworld: trading is closed.
[igossip] Vashkar@Split Infinity: capitol hill .. another explosion.
[igossip] Al@Anarres II: would all US resident, please leave the country.
[igossip] Cratylus: remain calm, do not run.
/wiz/cratylus # [igossip] Al@Anarres II: head for Canada!
[igossip] Sinistrad@VargonMUD: I miss the twin towers.
[igossip] Xyzzy: pretty sad when im relying on you guys for news.
[igossip] Myrias@Discworld: I logged on for news too.
[igossip] Blue@Inon-Net: they'll have to reprint all those NYC t-shirts, shot
glasses, posters, everything.
[cre] Cratylus: thats pathetic.
/wiz/cratylus # [igossip] Myrias@Discworld: The major news websites are all crashing under the
weight of traffic.
[cre] Cratylus: i cant believe cnn got overwhelmed.
/wiz/cratylus # [igossip] Blue@Inon-Net: no, they've been hit by more planes!
[igossip] Skullslayer@RoD: so, any bets on the FBI or CIA going out next?
[cre] Cratylus: dispensing breaking news is their JOB.
/wiz/cratylus # [cre] Cratylus: i wnt them FIRED.
/wiz/cratylus # [igossip] Xyzzy: youd think cnn would be their FIRST target.
[cre] Xyzzy: yer on cre.
[igossip] Bayard@Nanvaent: anyone started any rumours about massive virtual
terrorism attack on the US websites?
[cre] Cratylus: i know.
/wiz/cratylus # [igossip] Musashi@Sumu: no way, they want people to know.
[cre] Xyzzy: ah.
[igossip] Bayard@Nanvaent: i'm sure there's mileage in that one.
[igossip] Xyzzy: anyone familiar with the laws concerning gun possession and
transport in the state of maryland?
[igossip] Al@Anarres II: right now X, no one will care.
[igossip] Xyzzy: GOOD POINT!
[igossip] Presto@Discworld: explosion on Capitol hill.
[igossip] Xyzzy: where.
[igossip] Presto@Discworld: or not.
[cre] Cratylus: cant believe i have to turn on the radio for news now.
/wiz/cratylus # [igossip] Vashkar@Split Infinity: no, that's what happened.
[igossip] Xyzzy: is todays date of any signifigance?
[igossip] Skullslayer@RoD: apparently CNN is reporting a Capitol Hill hit
[igossip] Presto@Discworld: I wish people would get their fucking story
straight. :-b.
[igossip] Sinistrad@VargonMUD: 20th anniversary of world peace, Xy.
[igossip] Sinistrad@VargonMUD: CNN is reporting everything's ok on Capitol
Hill, except a loud sound.
[igossip] Vashkar@Split Infinity: oh.
[igossip] Presto@Discworld: CNNfn is talking to someone on Pennsylvania Ave,
and they said there's no explosion.
[igossip] Izzy@VargonMUD: It's the Democrats complaining.
[igossip] Cratylus: npr's interviewing freaked out WTC occupants.
/wiz/cratylus # [igossip] Xyzzy: now THATS journalistic integrity.
[igossip] Cratylus: it's quite tedious.
/wiz/cratylus # [igossip] Xyzzy: can you imagine the traffic and general panic?
[igossip] Cratylus: huh. the upper part of one of them did in fact collapse.
/wiz/cratylus # [igossip] Skullslayer@RoD: a few players here have confirmed capitol hill as
being hit
[igossip] Blue@Inon-Net: Interesting that of all the scary talk over recent
years about how easy it would be for terrorists to deploy biological, chemical,
nuclear or other fancy weapons in NYC, that when it actually happens, it could
have been done the same way 20 years ago.
[igossip] Presto@Discworld: Empire State building now evacuated. Duh.
[igossip] Sinistrad@VargonMUD: Yes, thanks, 30 minute ago man.
[igossip] Xyzzy: plane bombings is SO last decade.
[igossip] Blue@Inon-Net: I wonder how they'll persuade King Kong to come down.
[igossip] Cratylus: hey, i'm not gonna believe YOU bozos.
/wiz/cratylus # [igossip] Xyzzy: screw YOU guys, ah'm goin HOME.
[igossip] Sinistrad@VargonMUD: Famous last words, target!
[igossip] Skullslayer@RoD: nah, it was 'fore!'
[igossip] Tarl@Alsherok: In case no one said it, it appears one of the planes
was american airlines flight number 11, I hope no one knew anyone supposed to
be on that..
[igossip] Al@Anarres II: so one tower has fallen over completly?
[igossip] Cratylus: no. the upper part of one collapsed.
/wiz/cratylus # [igossip] Xyzzy: it has fallen UP.
[igossip] Cratylus: not clear if it actually hit the ground.
/wiz/cratylus # [igossip] Al@Anarres II: from news feed I have heard about, its fallen over
completly?
[igossip] Sinistrad@VargonMUD: It's just floating there.
[igossip] Xyzzy: the Good Samaratin Terrorist Organization has built it back
up.
[igossip] Al@Anarres II ponders going to find a TV
[igossip] Xyzzy: this... this cant happen to US.... were AMERICA!
[igossip] Cratylus: heh, reporter is shocked to see police patrolling around
with machine guns.
/wiz/cratylus # [igossip] Bayard@Nanvaent: no tanks?
[igossip] Xyzzy: GIT MAH GUN.
[igossip] Al@Anarres II: they are gonna shoot planes down first!
[igossip] Xyzzy: and pilots.
[igossip] Cratylus: no, but apparently fighter jets patrolling too.
/wiz/cratylus # [igossip] Xyzzy: kick ASS.
[igossip] Al@Anarres II: \"At about 10 a.m., one of the 110-story World Trade
Center towers collapsed\"
[igossip] Xyzzy: just heard on radio: state building car combed.
[igossip] Al@Anarres II: washingtonpost.com.
[igossip] Presto@Discworld: car bomb outside the State Dept.
[igossip] Xyzzy: shhh, al, dont overload our local newspaper's site!
[igossip] Al@Anarres II: hey, its the only one up!
[igossip] Cratylus: not anymore.
/wiz/cratylus # [igossip] Xyzzy: thanks a LOT, guys.
[igossip] Xyzzy: i can see it.
[igossip] Xyzzy: heh, theres a webcam pointed at the pentagon smoke.
[igossip] Al@Anarres II uses akamaitech.net
[igossip] Al@Anarres II: it works.
[igossip] Cratylus: howsabout a url?
/wiz/cratylus # [igossip] Xyzzy:
http://www.washingtonpost.com/wp-srv/mmedia/webcams/eyeondc.htm.
[igossip] Sinistrad@VargonMUD: WHERE THE FUCK IS SPIDER-MAN?!?!
[igossip] Xyzzy: helicopter in the picture, now.
[igossip] Xyzzy: wtf is SPIERman gonna do.
[igossip] Cratylus: mighty mouse is busy eating cheese.
/wiz/cratylus # [igossip] Sinistrad@VargonMUD: Web the tower together.
[igossip] Xyzzy: ohhhh yeah.
[igossip] Skullslayer@RoD: rumour of 6 more planes in the air and hijacked
[igossip] Xyzzy: spider MAN, SPIDER man.
[igossip] Bayard@Nanvaent: yeah. that seems very rumourish atm.
[igossip] Xyzzy: rumor from where?
[igossip] Steve@Anarres II: 2nd has gone now.
[igossip] Tariq@Demonslair Dev: 2nd tower just went down.
[igossip] Al@Anarres II: how the hell can that many planes get hijacked?
[igossip] Xyzzy: 2nd one what.
[igossip] Xyzzy: tower?
[igossip] Presto@Discworld: fucking Christ, the second tower collapsed.
[igossip] Sinistrad@VargonMUD: MAYDAY MAYDAY, TOWER DOWN.
[igossip] Steve@Anarres II: no, the 2nd fucking pentagon.
[igossip] Cratylus: the Mayor's disaster control center was in the
blown up tower.
/wiz/cratylus # [igossip] Sinistrad@VargonMUD: Both towers are gone, you fucking moron.
[igossip] Tariq@Demonslair Dev: saw it dropping live, it was truely scary.
[igossip] Tariq@Demonslair Dev: live on the telly, in case anyone
misunderstands that.
[igossip] Al@Anarres II: did it crumple downwards, or topple over and take out
alot og other buildings?
[igossip] Musashi@Sumu: carbomb at state dept confirmed?
[igossip] Tariq@Demonslair Dev: the top spire was in view, and it just dropped
downwards.
[igossip] Cratylus: wtf npr says both towers collapsed.
/wiz/cratylus # [igossip] Pickett@Sumu: hrm, another plane hijacked and headed to washington.
[igossip] Cratylus: im guessing any further hijacked planes might get shot
down.
/wiz/cratylus # [igossip] Al@Anarres II: but the US cant shoot down its own commertial
airliner!
[igossip] Cratylus: sure it can.
/wiz/cratylus # [cre] Xyzzy: yes, ive heard both towers collapsed, too.
[igossip] Al@Anarres II: they might not crash...
[igossip] Bayard@Nanvaent: i reckon it will.
[igossip] Xyzzy: ok, boss giving orders to go home.
[igossip] Al@Anarres II: bye X! drive safely.
[igossip] Al@Anarres II: don;t go rubber necking!
[igossip] Xyzzy: fuck that, im driving like theres NO TOMOROW.
[igossip] Xyzzy: nah, im takin back roads.
[igossip] Al@Anarres II: there might not be a tomorrow.
[igossip] Xyzzy: MIGHT even stop by parents... see if my dad keeps the gun-safe
locked...
[igossip] Xyzzy: kidding, of course.
[igossip] Xyzzy: ok, see y'all later.
[cre] Xyzzy waves.
[announce] Xyzzy has left Frontiers.
[cre] Cratylus waves.
/wiz/cratylus # [igossip] Musashi@Sumu: perfect time to go raiding ;)
[igossip] Blue@Inon-Net: I hear a rumour that Capitol Hill has not been
attacked after all.
[igossip] Skullslayer@RoD: I have a rumour a second explosion at the pentagon
[igossip] Al@Anarres II: I have a rumour that they are gonna fly nto the statue
of liberty.
[igossip] Tarl@Alsherok: blue: it wasn't. it was a car bomb at the state dept.
[igossip] Tarzan@Darkland: UN (NY) evacutated.
[igossip] Skullslayer@RoD: the pentagon webcam shows a much bigger cloud of
smoke now
[igossip] Blue@Inon-Net: NEWSFLASH: smoke expands.
[igossip] Tarl@Alsherok: reputedly also a large plane crash SE of pittsburgh,
not sure if its related or not, and don't know if its real or not.
[igossip] Tea@Nanvaent: where's the pentagon webcam?
[igossip] Musashi@Sumu:
http://www.washingtonpost.com/wp-srv/mmedia/webcams/eyeondc.htm.
[igossip] Tea@Nanvaent: cheers.
[igossip] Skullslayer@RoD: ronald reagan buliding evacuated
[igossip] Skullslayer@RoD: confirmed hit in pittsburgh
[igossip] Cratylus: so...uh...the two buildings are completely gone, then?
/wiz/cratylus # [igossip] Tarzan@Darkland: whats in Pittsburgh?
[igossip] Styxx@OuterSpace nods.
[igossip] Skullslayer@RoD: north of the airport, somerset county
[igossip] Skullslayer@RoD: rumour - all planes approaching DC will now be shot
down
[igossip] Skullslayer@RoD: the plane approaching DC was shot down?
[cre] Zaphod: shit man.
[igossip] Leto@Earth: http://robots.cnn.com/ and http://asian.cnn.com still
work.
[igossip] Spoo@Nanvaent: http://www.sky.co.uk is relatively lively still as
well.
[igossip] Bayard@Nanvaent nods.
[igossip] Skullslayer@RoD: asia.cnn.com is still up, but it hasn't been updated
in 2 hours that I can see
[igossip] Leto@Earth: ah.
[igossip] Leto@Earth: so what's the stuatus?
[igossip] Sinistrad@VargonMUD: middleeast.cnn.com reports, \"Imperialist Scum
Taught A Lesson\"
[igossip] Skullslayer@RoD: rumour of another plane is a few mins out from DC,
after the last one was shot down
[igossip] Spoo@Nanvaent: fuck off Sinistrad.
[igossip] Leto@Earth: 7 planes hijacked?
[igossip] Sinistrad@VargonMUD: nya nya, nya nya nya.
[igossip] Leto@Earth: one failed in pitsburg?
[igossip] Musashi@Sumu: that DC-bound jet was shot down? That confirmed?
[igossip] Sinistrad@VargonMUD: That was Air Force 1!
[igossip] Skullslayer@RoD: yeah, one down in pittsburgh, just north of the
airport
[igossip] Spoo@Nanvaent: seems so, 1 may have been shot down near Pittsburgh.
the other still flying near Dulles it would seem, other 5 either crashed or
landed.
[igossip] Hekubah@Annwn: What's happened in Chicago?
[igossip] Skullslayer@RoD: I had a few players here confirm the shoot down
[igossip] Spoo@Nanvaent: Sears tower evacuated. Israelis have shutdown ALL
their embassies.
[igossip] Lightfoot@VargonMUD: I'm unabvle to get to cnn.com.
[igossip] Hekubah@Annwn: Woah.
[igossip] Hekubah@Annwn: reuters isn't working...
[igossip] Isis@Mystic: Me either. Or Fox News.
[igossip] Isis@Mystic: Someone in the office is sreaming BBC.
[igossip] Isis@Mystic: Er, streaming.
[igossip] Lightfoot@VargonMUD: what about it?
[igossip] Hekubah@Annwn has BBC on the TV...
[igossip] Al@Anarres II: Isis - the BBC is fubared.
[igossip] Spoo@Nanvaent: yeah, we have our own stream of N-TV.
[igossip] Skullslayer@RoD: cnn, abcnews, msnbc and a few other big news sites
are all down
[igossip] Spoo@Nanvaent: we are getting CNN live feed.
[igossip] Isis@Mystic: FoxNews too.
[igossip] Skullslayer@RoD: bbc seems to be still up, but a bit behind
[igossip] Tea@Nanvaent: http://news6.thdo.bbc.co.uk/
[igossip] Sinistrad@VargonMUD: DIE, IMPERIALIST SCUM!
[igossip] Sinistrad@VargonMUD: HAH!
[igossip] Skullslayer@RoD: rumour of crash into camp david
[igossip] Isis@Mystic: Sinis, I love you.
[igossip] Sinistrad@VargonMUD: I know.
[igossip] Musashi@Sumu: remember sinistrad, much as you hate the
'imperialists', someone hates your beliefs just as much.. you deserve to die
too?
[igossip] Sinistrad@VargonMUD: Honey, it's funny that people are always
laughing at the misfortunes of others on this channel - now those same people
are all, \"dude, be more sensitive\"
[igossip] Teny@Mystic: D.C Streets are one big traffic Jam.
[igossip] Spoo@Nanvaent: pretty much not a joking matter.
[igossip] Musashi@Sumu: misfortunte != death.
[igossip] Musashi@Sumu: -t.
[igossip] Al@Anarres II nods at Spoo
[igossip] Sinistrad@VargonMUD: Go be righteous somewhere else. When it's not
affecting you, you're the ruelest bunch of assholes ever.
[igossip] Sinistrad@VargonMUD: Now it's your turn. HAH!!! DIE GRINGOS!!
[igossip] Spoo@Nanvaent: not affecting you either, so be completely tasteless
some other time.
[igossip] Sinistrad@VargonMUD: No.
[igossip] Al@Anarres II: hey Sini, this doesnt affect me, but it does affect
alot of ppl.
[igossip] Musashi@Sumu: shrugs, I'm not American, but I'm still not gonna laugh
at people dying.
[igossip] Al@Anarres II: there are like 50,000 ppl working in the WTO.
[igossip] Spoo@Nanvaent: I ain't laughing because this is only the start of the
bloodletting.
[igossip] Sinistrad@VargonMUD: Not any more!
[igossip] Lightfoot@VargonMUD: more like 75K.
[igossip] Skullslayer@RoD: plus all the people in the surrounding buildings
[igossip] Lightfoot@VargonMUD: people were jumping out of the building for over
1/2 hour.
[igossip] Al@Anarres II: [flap] Iain: itn reporting 100 tousand dead in new
york.
[igossip] Sinistrad@VargonMUD: Where were you guys when 80K Hindis died of
starvation last year? Did you care? No, you laughed at the hungry brownies.
[igossip] Spoo@Nanvaent: in case you hadn't noticed the madmen have just fucked
off the USA, NATO and the Israeli's.
[igossip] Leto@Earth: 100.000 ?
[igossip] Iain@Anarres II: 100k people.
[igossip] Spoo@Nanvaent: well fuck you for being so consciencious, yeah I care,
I don't find it funny either.
[igossip] Iain@Anarres II: lots of them firefighters/police.
[igossip] Sinistrad@VargonMUD: \"Whine whine, now it's hurting us directly,
please don't laugh.. please forget that I used to laugh at you when you were
down, now it's about me and I'm more important.\"
[igossip] Lightfoot@VargonMUD: I live on Long Island, firefighters and police
were called into the city.
[igossip] Lightfoot@VargonMUD: all bridges and tunnels to NYC are closed.
[igossip] Al@Anarres II: swim!
[igossip] Al@Anarres II: how are they getting ppl out ?
[igossip] Lightfoot@VargonMUD: they are not.
[igossip] Sinistrad@VargonMUD: With straws.
[igossip] Spoo@Nanvaent: both the towers are basically piles of burning rubble,
unlikely to be any survivors in that.
[igossip] Ebony@Earth: I heard a witness say he worked on the 36th floor and
they got out ok.
[igossip] Ebony@Earth: they had 1/2 an hour before the collapse.
[igossip] Spoo@Nanvaent: 1st or 2nd tower?
[igossip] Hellmonger@Trilogy: http://www.shef.ac.uk/cs1xtc/sa/planewtc.jpg.
[igossip] Ebony@Earth: 69 ppl on the plane that flew into the wtc.
[igossip] Lightfoot@VargonMUD: both towers are gone.
[igossip] Hellmonger@Trilogy: no doubt.
[igossip] Ebony@Earth: first tower, they started leaving right after they saw
the 2nd plane.
[igossip] Lightfoot@VargonMUD: 2nd plane hit 18 mins after first.
[igossip] Hellmonger@Trilogy: Vote Yes to Proposition 37 \"Systematic Ethnic
Clensing of America\"
[igossip] Sinistrad@VargonMUD: \"Leave only the injuns\"
[igossip] Spoo@Nanvaent: what, you mean America will belong to the natives
again?
[igossip] Shandor@Bakhara: just the mexicans.
[igossip] Hellmonger@Trilogy: Leave only the whites, the blacks, and the
mexicans.
[igossip] Hellmonger@Trilogy: cuz everybody else is out to fucking get us.
[igossip] Lightfoot@VargonMUD: boston is very close, right across the LI sound.
[igossip] Ebony@Earth: 2 planes, carrying approx 80 passengers each..
[igossip] Ebony@Earth: unsure which they were.
[igossip] Spoo@Nanvaent: seems they just shot down the 2nd plane as well.
[igossip] Spoo@Nanvaent: the one near Dulles.
[igossip] Sinistrad@VargonMUD: Shit, HM's link made me bust a gut!
[igossip] Vraxor@Split Infinity: how do you know?
[igossip] Sinistrad@VargonMUD: You rule, HM. =)
[igossip] Skullslayer@RoD: rumour of a bomb in a school near the WTC
[igossip] Hellmonger@Trilogy: no doubt.
[igossip] Al@Anarres II: nice to see students at shefield are back, hard at
work ;)
[igossip] Skullslayer@RoD: plane inbound to chicago
[announce] Xyzzy enters Frontiers.
[igossip] Xyzzy: i dont know if yer keeping up on current events, but we just
got our ASSES kicked back there!
[igossip] Sinistrad@VargonMUD: http://www.shef.ac.uk/cs1xtc/sa/planewtc.jpg.
[igossip] Hekubah@Annwn: Another plane?
[igossip] Bayard@Nanvaent: the first joke picture :)
[igossip] Sinistrad@VargonMUD: That's not a joke.
[igossip] Xyzzy: thats fucked up.
[igossip] Bayard@Nanvaent: buh?
[igossip] Vashkar@Split Infinity: ummm.. that looks like nice photoshop work,
Sinistrad.
[igossip] Xyzzy: jeez, fuck the fox network... \"apparent terrorist attacks...\"
[igossip] Sinistrad@VargonMUD: Reality always looks so fake, doesn't it?
[igossip] Izzy@VargonMUD: \"This just in, JFK shot.\"
[igossip] Musashi@Sumu: NBC just reported bomb gone off in NYC Highschool.
[igossip] Xyzzy: WE GOT A MAN ON TH MOON?!
[igossip] Presto@Discworld: yeah, it was obviously a big fucking coincidence.
[igossip] Xyzzy: fox investigative news.
[igossip] Xyzzy: at work.
[igossip] Musashi@Sumu: FAA say several planes still unaccounted for.
[igossip] Presto@Discworld: This just in: Jimmy Hoffa still missing. Police are
baffled.
[igossip] Xyzzy: plane crash 80 miles SE of pittsburg.
[igossip] Xyzzy: holy shit, footage of the WTC collapsing.
[igossip] Presto@Discworld: American Air confirms two flights down, United at
least one (the Pittsbhrugh one)
[igossip] Al@Anarres II: well done Xyzzy, do catch up.
[igossip] Al@Anarres II: which was the dullus one?
[igossip] Xyzzy: gee, its almost as if i havent had a TV at work for th last
two hours.
[igossip] Vashkar@Split Infinity: the only thing slightly amusing is that I
swore to myself that I'd never set foot in the WTC again.. then seconds later,
one collapses..then the other.
[igossip] Bayard@Nanvaent: you've had i3 though. and it's all been mentioned on
here.
[igossip] Xyzzy: its all YOUR fault, vash.
[igossip] Xyzzy: no, im SEEING the collapse for th first time.
[igossip] Vashkar@Split Infinity: sorry, I don't claim responsibility for the
deaths of tens of thousands today.
[igossip] Blue@Earth: you should, get your place in the queue.
[cre] Cratylus: whad i miss?
/wiz/cratylus # [igossip] Xyzzy: no one ever told us terrorist could be organized.
[igossip] Xyzzy: is that fair?
[cre] Xyzzy: dunno, i just got home.
[cre] Zaphod: westconn just got bombed.
[cre] Cratylus: no.
/wiz/cratylus # [igossip] Al@Anarres II: is this the largest terrorist attack ever?
[cre] Cratylus: they installed antiarircraft batteries on the ice rink and
patriot garage.
/wiz/cratylus # [cre] Cratylus: i just saw em.
/wiz/cratylus # [cre] Zaphod: ah.
[igossip] Xyzzy: heh, on cnn, \"america under attack\", all red white n blue
fonts n stuff.
[cre] Zaphod: cool.
[igossip] Bayard@Nanvaent: easily.
[igossip] Sinistrad@VargonMUD: Shit. Now my Flight Simulator NYC edition is
outdated. =(
[igossip] Al@Anarres II giggles
[igossip] Xyzzy: expansion pack.
[igossip] Bayard@Nanvaent: equally, how do u define a terrorist attack. this
might not be a terrorist attack.
[igossip] Hekubah@Annwn: Might be an act of war?
[igossip] Bayard@Nanvaent: could end up being defined as an opening move in a
war.
[cre] Zaphod: few.
[cre] Zaphod: phewww.
[igossip] Xyzzy: oh yeah, fergot, it might just be a wacky coincidence.
[cre] Zaphod: my package made it out of newark.
[igossip] Vashkar@Split Infinity: well, it wasn't a bunch of drunks, Bayard.
[igossip] Iain@Anarres II: sick puppy sini ;-)
[igossip] Xyzzy: hmm, good point, bayard.
[igossip] Xyzzy: GIT MAH GUN!
[igossip] Lightfoot@VargonMUD: were at defcon 5!
[igossip] Xyzzy: yer MOM is at defcon5
[igossip] Sinistrad@VargonMUD: Hey, more people died right now than in Pearl
Harbor.
[igossip] Bayard@Nanvaent: but, the style of attack will prolly mean it's a
terrorist attack that started a war in history books.
[igossip] Xyzzy: what, you got a death-count goin, sini?
[igossip] Bayard@Nanvaent: s'diff than pearl harbour or czechoslovakia.
[igossip] Lightfoot@VargonMUD: shut up dick.
[igossip] Vashkar@Split Infinity: please someone ban Xyzzy.
[igossip] Spoo@Nanvaent: WW 1 was started because of a terrorist attack that
killed 1 person.
[igossip] Bayard@Nanvaent nods.
[igossip] Xyzzy: please someone suck my dick.
[igossip] Sinistrad@VargonMUD: Xy, 50K+ people worked in the towers.
[igossip] Sinistrad@VargonMUD: How many people died at Pearl Harbor?
[igossip] Cratylus watches in amusement as Vashkar greedily gobbles Xyzzy's
knob.
/wiz/cratylus # [igossip] Vashkar@Split Infinity: Vrax@SI says there was 10k in each tower,
approximately, at the time.
[igossip] Xyzzy: what, like i work for th navy or something, sini?
[cre] Zaphod: my packge drove right through ny just before this.
[cre] Cratylus: yer motherboard?
/wiz/cratylus # [cre] Xyzzy: a little... coincidental.
[igossip] Spoo@Nanvaent: less than 2k people were killed at PH.
[igossip] Vraxor@Split Infinity: that's what the guy who owned the tower said.
[cre] Zaphod: this one is my video card.
[cre] Zaphod: mb just left la.
[cre] Zaphod: maybe it's been hijacked.
[igossip] Xyzzy: that tower was 0WN3D.
[igossip] Ebony@Earth: You have to account for tourists in the towers too.
[igossip] Vraxor@Split Infinity: and the people in the planes.
[cre] Zaphod: now I REALLY don't feel like going to the office.
[32m[igossip] Xyzzy: shoot, none of this is gonna drive up memory prices, will it?
[cre] Zaphod: buy it quick.
[igossip] Spoo@Nanvaent: automatic if the pres is in AF1
[cre] Zaphod: WTC attack sale!
[igossip] Xyzzy: heard on radio: military air coverage over DC.
[igossip] Musashi@Sumu: ok, of the two planes to hit the towers, one was out of
boston, 81 passengers, 11 crew, one was out of washington, 58 passengers, 6
crew.
[cre] Zaphod: cheech sent an email saying pahts delivery will be screwy.
[igossip] Lightfoot@VargonMUD: there are tomcats flying around NYC.
[igossip] Xyzzy: thanks for th numbers, musashi.
[cre] Cratylus: AHAHAH.
/wiz/cratylus # [cre] Cratylus: i fucken BET.
/wiz/cratylus # [igossip] Xyzzy: i knew id never regret hiring you as my accountant.
[igossip] Cratylus: i cant wait til we fuckin nuke the shitbags from orbit.
/wiz/cratylus # [igossip] Zaphod: 9/11
[igossip] Xyzzy: its th only way to be sure.
[igossip] Dasquian@Discworld: You idiot.
[igossip] Musashi@Sumu: spam incoming, this from CNN.
[igossip] Musashi@Sumu: United Airlines Flight 93 airliner headed from Newark,
New Jersey, to San Francisco, crashed near Somerset, Pennsylvania -- police
said initial reports indicated no survivors. It was not known if this was
connected to the attacks. United also said it was \"deepl.
[igossip] Xyzzy: DEEPL?!?!
[igossip] Xyzzy: NOOOOOOO!!!!!!
[igossip] Musashi@Sumu: ________________23:43
[igossip] Musashi@Sumu: hm, sec :P.
[igossip] Musashi@Sumu: United also said it was \"deeply concerned\" about Flight
l75 from Boston to Los Angeles.
[igossip] Lightfoot@VargonMUD: it's said that crash in penn was from captain
crashing plane when he knew what was going on and he was being hi-jacked.
[igossip] Xyzzy: Am Airlines siad theyve \"lost\" two flights.
[igossip] Sinistrad@VargonMUD: And they know that throgh the ouija board,
Danny?
[cre] Zaphod: today is 9/11
[igossip] Xyzzy: WELL, DANNY?!
[igossip] Lightfoot@VargonMUD: I'm sure the radios were working.
[igossip] Xyzzy: oh, yer \"sure\"
[igossip] Vashkar@Split Infinity:
http://us.news2.yimg.com/dailynews.yahoo.com/h/nm/20010911/ts/crash_tradecenter
_binladen_dc_1.html.
[igossip] Lightfoot@VargonMUD: shut up dick.
[igossip] Vashkar@Split Infinity: they say bin laden warned of the attack 3
weeks ago.
[cre] Zaphod: call paul.
[igossip] Sinistrad@VargonMUD: Wow.
[cre] Cratylus: ya.
/wiz/cratylus # [igossip] Vashkar@Split Infinity: saying it was cause of US supporting israel.
[igossip] Xyzzy: yknow, i think what we really need now is a verbal fight on i3
[igossip] Shandor@Bakhara: your mom tastes like chicken and your dad smells of
a russian tea room.
[igossip] Xyzzy: HEY, now.
[igossip] Xyzzy: lets keep it civil.
[igossip] Cratylus: eldeberries, you fool.
/wiz/cratylus # [igossip] Sinistrad@VargonMUD: ``Personally we received information that he
planned very, very big attacks against American interests. We received several
warnings like this. We did not take it so seriously, preferring to see what
would happen before reporting it.''
[igossip] Cratylus: smelt of em.
/wiz/cratylus # [igossip] Shandor@Bakhara: windows sucks! RH linux is the top of them all.
[igossip] Sinistrad@VargonMUD: \"He told us he was gonna do it, but we wanted to
see what he was gonna do before we told anybody.\"
[cre] Zaphod: cute.
[igossip] Xyzzy: we called his bluff, alright.
[igossip] Shandor@Bakhara: who says that, sini?
[cre] Zaphod: he told me to stay home.
[igossip] Hellmonger@Trilogy: I hear the arabs in San Fran are celebrating the
attacks. Somebody go kill them all.
[igossip] Sinistrad@VargonMUD: NOW YOU KNOW.
[cre] Cratylus: ya.
/wiz/cratylus # [cre] Zaphod: you heard him.
[igossip] Sinistrad@VargonMUD: That link Vash gave out.
[cre] Cratylus: stamford is prolly a panicky madhouse.
/wiz/cratylus # [cre] Zaphod: no doubt.
[igossip] Xyzzy: and knowing's half th battle!
[cre] Zaphod: and I've got CABLE.
[igossip] Spoo@Nanvaent: hell, the Arabs are going to give someone some target
practice if they don't fuck off.
[igossip] Shandor@Bakhara: half of our IT department is made of arabs. I
wouldn't want to be them right now. Although, this is a mall city.
[igossip] Xyzzy: mall city? like, with shops n stuff?
[igossip] Cratylus: made of crabs? what?
/wiz/cratylus # [igossip] Spoo@Nanvaent: it ain't really the Arabs anyway, just a completely
mad bunch of lunatics who want to take us back the the dark ages.
[igossip] Cratylus: the republicans?
/wiz/cratylus # [igossip] Xyzzy: like i said, i think its in the rulebook that theyre not
allowed to be so organized and effective.
[igossip] Sinistrad@VargonMUD: That movie with Denzel Washington and Bruce
Willis starts out with a lot of terrorist attacks. That is NOTHING compared to
this.
[igossip] Vashkar@Split Infinity nods.
[igossip] Cratylus: i bet the french are all like \"serves em right, bourgeois
pigs\"
/wiz/cratylus # [igossip] Spoo@Nanvaent: makes Tom Clancy look tame too.
[igossip] Lightfoot@VargonMUD: cnn back up.
[igossip] Cratylus: i think they got lucky. you couldnt *really* expect those
fucking buildings to drop from plane hits.
/wiz/cratylus # [igossip] Spoo@Nanvaent: they were rather large ones.
[igossip] Lightfoot@VargonMUD: Bush calls trade center crashes terrorist act.
[igossip] Cratylus: large buildings.
/wiz/cratylus # [igossip] Lightfoot@VargonMUD: you think?
[igossip] Sinistrad@VargonMUD: Thanks, Danny.
[igossip] Vashkar@Split Infinity: in today's day n' age, it's not hard to be a
competent terrorist. It's like Osama bin Laden went to a tech institute or
something.
[igossip] Spoo@Nanvaent: large aircraft.
[igossip] Cratylus: fat asses.
/wiz/cratylus # [igossip] Steve@Anarres II: um.. they knew where to hit them to have best
chance of toppling them..
[igossip] Spoo@Nanvaent: cnn.com is breaking under the strain again.
[igossip] Xyzzy: this just in: i hafta pee.
[igossip] Cratylus: ok, master terrorist.
/wiz/cratylus # [igossip] Zaphod: ooo ooo Tom Clancy's on cnn!
[igossip] Sinistrad@VargonMUD: He's the authority, I bet!
[igossip] Cratylus: that does it. im playing half life. screw this.
/wiz/cratylus # [igossip] Xyzzy: hes written books n stuff!
[igossip] Xyzzy: just like smack!
[igossip] Spoo@Nanvaent: prolly complaining that the bastards stole his idea.
[igossip] Steve@Anarres II: part-way up near the top.. you prolly won't get
them immediately, but fire will weaken the structure and the top will fall
cruching the rest of the building..
[igossip] Xyzzy: cool, Dexter's Laboratory on cartoon network.
[igossip] Spoo@Nanvaent: yeah, the steel skeleton would weaken, concrete and
facade wouldn't hold the weight.
[igossip] Spoo@Nanvaent: it appears there was a secondary explosion in the
first tower to go down too.
[igossip] Blue@Inon-Net: I think the WTC buildings had their skeleton on the
outside; but don't hold me to that.
[igossip] Sinistrad@VargonMUD: So it wasn't just the planes?
[igossip] Xyzzy: yeah! lets speculate on the various ways these people died
today!
[igossip] Vashkar@Split Infinity: Spoo, they're thinking it was the plane's
fuel tank exploding.
[igossip] Steve@Anarres II: they showed ppl diving outa windows on the news..
that's sick..
[igossip] Spoo@Nanvaent: was quite a way down from the impact site.
[igossip] Spoo@Nanvaent: could well have been though.
[igossip] Al@Anarres II nods at Spoo
[igossip] Al@Anarres II: I think we just have to wait and see.
[igossip] Vashkar@Split Infinity: yeah, I was questioning the logistics of that
guess also.
[igossip] Al@Anarres II: if the top, above the crash site, fell down, even just
1meter, the energy would be enough to crumble the steel below.
[igossip] Spoo@Nanvaent: thats what happened with the second tower.
[cre] Zaphod: shit.
[igossip] Al@Anarres II: it might have crumbled many stories down, making it
look like an explosion below the crash site.
[igossip] Spoo@Nanvaent: roof went, took the rest with it.
[cre] Zaphod: I bet the coffee I ordered don't get sent out.
[igossip] Al@Anarres II: yeah, common :(
[cre] Xyzzy: always thinkin of yerself.
[igossip] Al@Anarres II shuts up about civil engineering and goes back to
hacking sendmail
[cre] Zaphod: well, I need my coffee.
[igossip] Boltthrower@Styx: apparently the roof-collapse scheme works better
than the basement-bomb thing did :(
[igossip] Spoo@Nanvaent: nah, there was a clear fireball shortly before the
first collapse (and I saw that live so no, it wasn't a plane)
[igossip] Xyzzy: how about the \"fly a few hundred tons of metal into the
building\" scheme?
[igossip] Isis@Inon-Net: Yeah, I saw it too just before I left for work.
[igossip] Spoo@Nanvaent: 737 and a 767, couple of hundred tonnes each.
[igossip] Hellmonger@Trilogy sets an arab on fire.
[igossip] Zaphod: camp david get hit?
[igossip] Vashkar@Split Infinity: close to camp david.
[igossip] Al@Anarres II: cool, HM, gulf-news.com is hosted nr you ;)
[igossip] Spoo@Nanvaent: lots of momentum to damage the buildings, the second
aircraft blew right through the tower too.
[igossip] Xyzzy: can you imagine how dark it could be in manhattan right now?
[igossip] Lightfoot@VargonMUD: that one was shot down.
[igossip] Spoo@Nanvaent: what, at midday?
[igossip] Xyzzy: yes.
[igossip] Xyzzy: with all the smoke.
[igossip] Spoo@Nanvaent: not very then.
[igossip] Xyzzy: well, i guess you CAN imagine it, then.
[igossip] Zaphod: 8 hijacked planes still in the air?
[igossip] Xyzzy: but for how long....
[igossip] Xyzzy: HOW LONG!?!
[igossip] Vashkar@Split Infinity: where'd you hear 8, Zaphod?
[igossip] Xyzzy: hes just makin shit up, now.
[igossip] Ebony@Earth: Its not that dark.
[igossip] Hellmonger@Trilogy: I heard 8 total planes.
[igossip] Hellmonger@Trilogy: and we've already found some of them.
[igossip] Lightfoot@VargonMUD: 5 floors in pentagon have collapased.
[igossip] Ebony@Earth: Not unless you where wtc was.
[igossip] Hellmonger@Trilogy: we found 2 in the trade center, one in the
pentagon... \\
[igossip] Xyzzy: HM AM FUNNYMAN!
[igossip] Spoo@Nanvaent: Brits have lost contact with 2 aircraft as well.
[igossip] CyberTiger@Anarres II: s/found/happened to notice/
[igossip] Xyzzy: wtf, some dude disassembling a pistol on CourtTV, sayin how
easy it could be to blah blah blah.
[igossip] BarWench@Inon-Net: ;nods.
[igossip] Al@Anarres II: have we Spoo? where you hear that?
[igossip] Hellmonger@Trilogy: So is Bin laden a palestinian or a jew?
[igossip] Smack@Lima Bean: what aircraft?
[igossip] Smack@Lima Bean: my father is on a flight from london to new york.
[igossip] Hellmonger@Trilogy: It says 'Islamic Fundamentalist', which is like a
palestinian, right?
[igossip] Smack@Lima Bean: bin laden is saudi.
[igossip] Hellmonger@Trilogy: He'll be landing in canada. :(
[igossip] Pam@Split Infinity: They're evacuating some of the taller buildings
in London, according to MSNBC.
[igossip] Smack@Lima Bean: he is a rich, eccentric saudi.
[igossip] Xyzzy: look out, hellmonger is gunnin for SOMEone.
[igossip] Tea@Nanvaent: canada is all closed too i thought?
[igossip] Hellmonger@Trilogy: I'm down to kill me some arabs now.
[igossip] Hellmonger@Trilogy: And some happy palestinians.
[igossip] Smack@Lima Bean: we must declare war against Afghanistan.
[igossip] Hellmonger@Trilogy: Need to be set on fire.
[igossip] Hellmonger@Trilogy: why afghanistan?
[igossip] Sinistrad@VargonMUD: Why not?
[igossip] Hellmonger@Trilogy: Valid point. KILL EM ALL!
[igossip] Smack@Lima Bean: afghanistan has been housing bin laden for years.
[igossip] Smack@Lima Bean: they refuse to turn him over.
[igossip] Megaboz@Lima Bean: I just have this feeling that bush is going to do
something stupid.
[igossip] Ebony@Earth: Third plane ahs crashed in pentagon.
[igossip] Hellmonger@Trilogy: I'm all about letting them know whats up. Take
the afghan royal family or whatnot. And cook them on a spit.
[igossip] Smack@Lima Bean: do something stupid like what?
[igossip] Hellmonger@Trilogy: Like what?
[igossip] Hellmonger@Trilogy: Like starting to kill people?
[igossip] Hellmonger@Trilogy: The american people want revenge.
[igossip] Smack@Lima Bean: if he nuked them, I would be only mildly distubed.
[igossip] Megaboz@Lima Bean: nuked was the thing I was going for.
[igossip] Spoo@Nanvaent: which bastard stopped development of the neutron bomb,
perfect time to test it.
[igossip] Xyzzy chants: \"NUKE! NUKE! NUKE!\"
[igossip] Hellmonger@Trilogy: I would'nt really be phased. I'd like to see the
middle east as a field of glass.
[igossip] Smack@Lima Bean: so how many planes have crashed so far? 5? 6?
[igossip] Musashi@Sumu: Ebony, that 3rd plane in the pentagon confirmed?
[igossip] Sinistrad@VargonMUD: 5.
[igossip] Sinistrad@VargonMUD: Three on target, two off target.
[igossip] Hellmonger@Trilogy: I think bombing them would be cool.
[igossip] Spoo@Nanvaent: hard to tell really, web sites are all choked.
[igossip] Xyzzy: jeez, how many damn kids did the Waltons have?
[igossip] Hellmonger@Trilogy: 7.
[igossip] Hellmonger@Trilogy: all worth around 17-18 bil.
[igossip] Smack@Lima Bean: my understanding is 2 into thw world trade center, 2
into the pentagon, and one outside pittsburgh.
[igossip] Hellmonger@Trilogy: right.
[igossip] Sinistrad@VargonMUD: Only 1 into the pentagon.
[igossip] Isis@Inon-Net: And one in Sommerset PA as well.
[igossip] Musashi@Sumu: reading in a few places of a 3rd into the pentagon
though.
[igossip] Sinistrad@VargonMUD: 1 outside pitts, 1 recently confirmed outside
Sommerset.
[igossip] Hellmonger@Trilogy: Thats outside pitts.
[igossip] Smack@Lima Bean: sommerset == pitssburgh.
[igossip] Smack@Lima Bean: oh, yeah,and the air force shot one down outside
camp david.
[igossip] Hellmonger@Trilogy: a third plane intot he pentagon?
[igossip] Futility@UOSSMUD: The one in Somerset is the one near pittsburgh.
[igossip] Hellmonger@Trilogy: Really?
[igossip] Sinistrad@VargonMUD: \"United Airlines Flight 93 airliner headed from
Newark, New Jersey, to San Francisco, crashed near Somerset, Pennsylvania --
police said initial reports indicated no survivors. It was not known if this
was connected to the attacks. United also said it was \"deeply concerned\" about
Flight l75 from Boston to Los Angeles.\"
[igossip] Sinistrad@VargonMUD: On CNN, they just confirmed that 175 went down,
too.
[igossip] Xyzzy: deepl.
[igossip] Spoo@Nanvaent: so 3 shot down and 3/4 crashed.
[igossip] Hellmonger@Trilogy: We shot down three planes?
[igossip] Xyzzy: yeah, HM, score one for th good guys.
[igossip] Spoo@Nanvaent: there were 7 hijacked.
[igossip] Musashi@Sumu: from advfn: A third plane crashes near the Pentagon in
Washington DC.
[igossip] Smack@Lima Bean: where did 175 go down?
[igossip] Spoo@Nanvaent: well maybe not shotdown, hard to sort out the news
from the confused.
[igossip] Spoo@Nanvaent: WASHINGTON (Reuters) - Three hijacked planes crashed
into major U.S. landmarks on Tuesday, destroying both of New York's mighty twin
towers, hitting the Pentagon in Washington and plunging the United States into
unprecedented chaos and panic.
[igossip] Smack@Lima Bean: fucking kill them all.
[igossip] Blue@Inon-Net: now that Reuters has confirmed it, I'll stop worrying
that it was all US propaganda being parroted by CNN.
[igossip] Hellmonger@Trilogy: no doubt.
[igossip] Xyzzy: the adjective \"mighty\" brought to you at no extra charge.
[igossip] Hellmonger@Trilogy: I only know of three terrorist crashes, and one
United crash.
[igossip] Hellmonger@Trilogy: And whats up on 175?
[igossip] Sinistrad@VargonMUD: Two united crashes.
[igossip] Hellmonger@Trilogy: where did it crash?
[igossip] Smack@Lima Bean: the united crash is certainly a terorist thing.
[igossip] Smack@Lima Bean: probably the pilot taking it down to prevent
somethign evil.
[igossip] Hellmonger@Trilogy: I thought terrorists all capped off the pilots to
prevent them from fucking up the program?
[igossip] Spoo@Nanvaent: One of the planes that crashed into the World Trade
Center was American Airlines' Flight 11 from Boston to Los Angeles, said Lori
Bassani, spokesperson for American's flight attendants union. ;
[igossip] Smack@Lima Bean: someone said \"fuck the terrorists\"
[igossip] Sinistrad@VargonMUD: Yeah, I don't think any pilot would go, \"Ok,
I'll crash this plane into the twin towers or he'll shoot me.\"
[igossip] Spoo@Nanvaent: www.reuters.com.
[igossip] Hellmonger@Trilogy: No doubt. YOu are going to die anyway. Fuck it.
I'd put that shit in the dirt.
[igossip] Smack@Lima Bean: I agree... but I bet on the one outside pittsburgh
was stoipped by someone.
[igossip] Xyzzy: hellmonger, you seem to be fairly well-versed in \"hijacker
protocol\"
[igossip] Xyzzy: something you wannatell us?
[igossip] Hellmonger@Trilogy: Common sense.
[igossip] Boltthrower@Styx: you online from a plane?
[igossip] Sinistrad@VargonMUD: Microsoft announced the release of a new version
of Microsoft Flight.
[igossip] Smack@Lima Bean: I just fear that bioterrorism is part of this.
[igossip] Smack@Lima Bean: that we have not even seen the worst yet.
[igossip] Sinistrad@VargonMUD: Microsoft announced the release of a new version
of Microsoft Flight Simulator today. The terrain has been updated in New York
so that two piles of rubble sit where the twin World Trade Center towers used
to stand. When asked how they had this updated version burned before the towers
were actually bombed Bill Gates responded \"We like to be prepared for any
contingiency. This is a horrible tragedy and we grieve for the loss that people
are facing. We will donate .001% of all profits of this product to help the
families of those killed during this horrible event.\" Newly appointed Vice
President of Microsoft, Osama bin Laden added: \"We'd also like to point out
that we have the only flight simulator on the market that has true to the day
updated terrain. If you want to own the most realistic up to date simulator,
you've got to buy ours.\"
[igossip] Spoo@Nanvaent: sorry, even I find that funny :)
[igossip] Sinistrad@VargonMUD: =)
[igossip] Xyzzy: and we all know how lousy a sense of humor spoo has.
[igossip] Spoo@Nanvaent: at least I have one Xy :)
[igossip] Musashi@Sumu: looks like the pentagon fire is under control.
[igossip] Xyzzy: WHEW!
[igossip] Xyzzy: what about these rumors of car bombings and capitol hill/
state building/whitehouse, etc.
[igossip] Sinistrad@VargonMUD: Exhaust pipes going off.
[igossip] Musashi@Sumu: well, heard several reports of car bomb outside state
building.
[igossip] Spoo@Nanvaent: there was no bomb at the State Department.
[igossip] Musashi@Sumu: and one of a bomb in a nyc school.
[igossip] Blue@Inon-Net: english.pravda.ru.
[igossip] Spoo@Nanvaent: prolly someone hearing a backfire.
[igossip] Blue@Inon-Net: US DOLLAR ABRUPTLY FALLEN DOWN.
[igossip] Blue@Inon-Net: According to current bidding, US dollar has fallen
abruptly relative to the Euro and to Yena. American stock markets has not
started to work yet. Planes are in air to hamper the second plane.
[igossip] Xyzzy: ALL YOUR NEWS ARE BELONG TO US.
[igossip] Blue@Inon-Net: According to NPR John Hopkins, the USA have not need
to be considerate with the terrorists.
[igossip] Musashi@Sumu: laf.
[igossip] Spoo@Nanvaent: Manhattan island encircled by policemen.
[igossip] Blue@Inon-Net: Suicide pilots piloting two planes have practically
rammed two buildings of International Trade Centre at a height of 85th storey.
These two buildings are known to the whole world thanks to film 'King Kong'.
That are the buildings over which the big monkey was mounting. More detail...
[igossip] Xyzzy: holding hands.
[igossip] Spoo@Nanvaent: POPULATION FROM CENTRE OF CHICAGO EVACUATED. LONDON
PANIC STRICKEN;
[igossip] Xyzzy: hu uhuh uhu, you said \"mounting\"
[igossip] Musashi@Sumu: london is hardly panic stricken :P.
[igossip] Xyzzy: wait, LONDON panic stricken?
[igossip] Lightfoot@VargonMUD: king kong climbed the empire state building.
[igossip] Blue@Inon-Net: they mean the remake.
[igossip] Blue@Inon-Net: NEWSFLASH: King Kong to be remade again, new buildings
sought.
[igossip] Xyzzy: as opposed to the porn film, \"King Dong\", where he climbed
lightfoot's mother.
[igossip] Lightfoot@VargonMUD: fuck you dick.
[igossip] Spoo@Nanvaent: I think \"have practically rammed 2 buildings\" is an
understatement.
[igossip] Xyzzy: ACH! fook me dick!
[igossip] Xyzzy: well, it wasnt very IMpractical.
[igossip] Lightfoot@VargonMUD: I would love to kick the living shit out of a
little punk like you.
[igossip] Spoo@Nanvaent: \"ADVENTURES OF AMERICAN RECONNAISSANCE AIRCRAFT R-3.
NOW IN RUSSIAN AIRSPACE;\"
[igossip] Zaphod: whoa.
[igossip] Sinistrad@VargonMUD: Danny, I've seen you. You're the smallest
goddamn punk ever.
[igossip] Zaphod: qvc has suspended broadcasts.
[igossip] Zaphod: jesus.
[igossip] Spoo@Nanvaent: surely that should be \"Now in Russian Cinemas\"
[igossip] Xyzzy: and he doesnt mean overall height.
[igossip] Bayard@Nanvaent: no more qvc?
",({"chapter 2","chapter two","2",}):"chapter 2 \"The Demise of Wolfsong\"
[25Nov2005-23:13:06] Wolfsong@Aurora Sky <imud_gossip> Hello.
[25Nov2005-23:13:30] Wolfsong@Aurora Sky <imud_gossip> Hmm, someone on my staff got this to work. Razz
[25Nov2005-23:13:38] Duuk@Haven <imud_gossip> fuck
[25Nov2005-23:15:03] Berun@Aurora Sky <imud_gossip> hello everyone
[25Nov2005-23:15:07] Wolfsong@Aurora Sky <imud_gossip> Now, that's a dirty word.
[25Nov2005-23:15:11] Duuk@Haven <imud_gossip> shut the fuck up, n00b.,
[25Nov2005-23:15:25] Wolfsong@Aurora Sky <imud_gossip> Aww, you sound upset.
[25Nov2005-23:15:25] Gary@Void <imud_gossip> Hey all.
[25Nov2005-23:15:35] Gary@Void <imud_gossip> Duuk is just having a bad day^W life.
[25Nov2005-23:15:37] Duuk@Haven <imud_gossip> jesus it's a fucking retard convention
[25Nov2005-23:16:49] Wolfsong@Aurora Sky <imud_gossip> Ah. I don't think that's an appropriate reason to immediately attack someone new, readily making assumptions about my intelligence. It shows a level of mental mediocrity.
[25Nov2005-23:17:05] Duuk@Haven <imud_gossip> You're a newbie mud admin. It's safe to assume you're a fuckin tard.
[25Nov2005-23:17:17] Gary@Void <imud_gossip> Actually, Duuk has a very valid point there.
[25Nov2005-23:17:25] <imud_gossip> Duuk@Haven nods solemnly.
[25Nov2005-23:17:27] Duuk@Haven <imud_gossip> See!
[25Nov2005-23:17:29] cratylus@Dead Souls <imud_gossip> amen
[25Nov2005-23:18:04] Vanyel@Haven <imud_gossip> No offense and all. It's just a statistical analysis. This way they save time.
[25Nov2005-23:18:22] Wolfsong@Aurora Sky <imud_gossip> So, being new to adminship means I have a low IQ. Except, according to current theory in psychology, it is impossible to raise your IQ. So, as you are experienced admins, you too, have low IQs.
[25Nov2005-23:18:36] cratylus@Dead Souls <imud_gossip> heh
[25Nov2005-23:18:42] Duuk@Haven <imud_gossip> No. We were here before any tard could open a mud.
[25Nov2005-23:18:42] cratylus@Dead Souls <imud_gossip> you're not helping yourself, guy
[25Nov2005-23:18:56] Duuk@Haven <imud_gossip> Haven first ran on a 486 with 16 meg of ram.
[25Nov2005-23:19:02] Duuk@Haven <imud_gossip> It ran like shit, but it ran.
[25Nov2005-23:19:04] Zakk@Lima Bean <imud_gossip> uphill both ways
[25Nov2005-23:19:06] Wolfsong@Aurora Sky <imud_gossip> I'm female. And rather educated.
[25Nov2005-23:19:14] cratylus@Dead Souls <imud_gossip> well that explains you
[25Nov2005-23:19:16] Duuk@Haven <imud_gossip> Liar on 2 points.
[25Nov2005-23:19:22] Zakk@Lima Bean <imud_gossip> female? pics of tits, now
[25Nov2005-23:19:28] <imud_gossip> Duuk@Haven agrees.
[25Nov2005-23:19:32] Duuk@Haven <imud_gossip> bikiniphotos. now.
[25Nov2005-23:19:40] Gary@Void <imud_gossip> I'm sorry, but I'm gonna have to go agree with Zaak and Duuk on this Sad
[25Nov2005-23:19:40] Wolfsong@Aurora Sky <imud_gossip> LoL. You don't want those.
[25Nov2005-23:19:42] Zakk@Lima Bean <imud_gossip> bikini? wtf
[25Nov2005-23:19:56] cratylus@Dead Souls <imud_gossip> http://rugose.com/showus.jpg
[25Nov2005-23:19:56] Duuk@Haven <imud_gossip> Wolfy, we've seen worse.
[25Nov2005-23:19:56] Gary@Void <imud_gossip> No such thing as a Female + MUDer + Coder + Admin.
[25Nov2005-23:20:02] Duuk@Haven <imud_gossip> Trust Me.
[25Nov2005-23:20:06] Duuk@Haven <imud_gossip> Hey, Laoise is an admin.
[25Nov2005-23:20:14] Duuk@Haven <imud_gossip> Of course, we're all pretty sure she's a guy.
[25Nov2005-23:20:26] Zakk@Lima Bean <imud_gossip> I'm fairly certain of that myself
[25Nov2005-23:20:28] Wolfsong@Aurora Sky <imud_gossip> Wow, not only are they pretentious shits, they're sexist pigs as well.
[25Nov2005-23:20:36] Gary@Void <imud_gossip> I passed as a girl on the internet for about a year once.
[25Nov2005-23:20:38] cratylus@Dead Souls <imud_gossip> wolfsong, just stfu
[25Nov2005-23:20:40] Gary@Void <imud_gossip> It's not hard to do Neutral
[25Nov2005-23:20:40] Duuk@Haven <imud_gossip> This is low-key. You should see when you really get us going.
[25Nov2005-23:20:54] Vanyel@Haven <imud_gossip> You know of pretentious shits that aren't sexist pigs?
[25Nov2005-23:20:56] Communist@Islands of Myth <imud_gossip> girls don't exist on the internet
[25Nov2005-23:20:56] Wolfsong@Aurora Sky <imud_gossip> Make me, shithead.
[25Nov2005-23:21:06] cratylus@Dead Souls <imud_gossip> heh, and here i was, trying to help you
[25Nov2005-23:21:10] Zakk@Lima Bean <imud_gossip> you into anal?
[25Nov2005-23:21:10] Duuk@Haven <imud_gossip> I mean, the conversation just started. We haven't even asked you if you like anal sex yet.
[25Nov2005-23:21:12] cratylus@Dead Souls <imud_gossip> then you go and call me names
[25Nov2005-23:21:14] <imud_gossip> Gary@Void rolls on the floor laughing.
[25Nov2005-23:21:29] Wolfsong@Aurora Sky <imud_gossip> Well, you did throw the first blows. Smile
[25Nov2005-23:21:31] cratylus@Dead Souls <imud_gossip> i think you're the arrogant pretentious one, wolfsong
[25Nov2005-23:21:33] Duuk@Haven <imud_gossip> damn. timed that one SLIGHTLY too late
[25Nov2005-23:21:49] cratylus@Dead Souls <imud_gossip> coming on here, expecting everyone to welcome your pointless brainwaves
[25Nov2005-23:22:11] Wolfsong@Aurora Sky <imud_gossip> So much for friendly MUD development.
[25Nov2005-23:22:15] Jayren@Dead Souls <imud_gossip> fuck. i don't need comedy central anymore. i'll just come watch igos.
[25Nov2005-23:22:17] Gary@Void <imud_gossip> Wolfsong, from my experiences, MUDs usually don't have very friendly people. If you want friendly, go the way of talkers, MUSHes, MOOs, etc.
[25Nov2005-23:22:27] Duuk@Haven <imud_gossip> ooOoO, mushes!
[25Nov2005-23:22:29] Wolfsong@Aurora Sky <imud_gossip> I wonder why your MUDs aren't popular, even though you've been here for oh so long.. hmmm.
[25Nov2005-23:22:33] Duuk@Haven <imud_gossip> ICQ for people without ICQ!
[25Nov2005-23:22:41] Duuk@Haven <imud_gossip> Who said our muds weren't popular?
[25Nov2005-23:22:49] Gary@Void <imud_gossip> I have four who people on my MUD.
[25Nov2005-23:22:49] Laoise@Haven <imud_gossip> Is someone talking about me having a dick again? I sense a disturbance in the force.
[25Nov2005-23:22:53] Duuk@Haven <imud_gossip> I have 5 people on and the damn mud is closed for the weekend.
[25Nov2005-23:23:07] Gary@Void <imud_gossip> whole*
[25Nov2005-23:23:09] Wolfsong@Aurora Sky <imud_gossip> Laff.
[25Nov2005-23:23:11] Duuk@Haven <imud_gossip> Laoise, at least we think you have a BIG dick.
[25Nov2005-23:24:03] Duuk@Haven <imud_gossip> anytime. time to unfuck another DescartesFunction[tm]
[25Nov2005-23:24:13] Duuk@Haven <imud_gossip> Crat, you ever mess with eventEquip() in the armour lib?
[25Nov2005-23:24:19] cratylus@Dead Souls <imud_gossip> have i ever
[25Nov2005-23:24:21] <imud_gossip> Laoise@Haven has a dick the size of Ninja. We know this.
[25Nov2005-23:24:31] Duuk@Haven <imud_gossip> Ninja's dick, or all of Ninja?
[25Nov2005-23:24:31] cratylus@Dead Souls <imud_gossip> that is some fucked up shit right there
[25Nov2005-23:24:39] Laoise@Haven <imud_gossip> All of Ninja. Duh.
[25Nov2005-23:24:52] Duuk@Haven <imud_gossip> I redid eventDescribeEnvironment() earlier. That one made baby jesus cry.
[25Nov2005-23:24:54] Laoise@Haven <imud_gossip> It's not funny if it's the same size as Ninja's.
[25Nov2005-23:24:56] Duuk@Haven <imud_gossip> I think this one will be worse.
[25Nov2005-23:45:02] Wolfsong@Aurora Sky <imud_gossip> Mwah.
[25Nov2005-23:45:38] Gary@Void <imud_gossip> Maw?
[25Nov2005-23:45:52] Wolfsong@Aurora Sky <imud_gossip> MWAH.
[25Nov2005-23:46:10] Duuk@Dead Souls <imud_gossip> You didn't die yet?
[25Nov2005-23:46:22] Wolfsong@Aurora Sky <imud_gossip> No, not yet.
[25Nov2005-23:46:24] Duuk@Dead Souls <imud_gossip> Damn.
[25Nov2005-23:46:50] Wolfsong@Aurora Sky <imud_gossip> New people are like cockroaches. You hate them, but they'll outlive you.
",({"chapter 4","chapter four","4",}):"chapter 4 \"Cratylus Has His Buttons Pushed\"
[02Dec2005-11:55:50] Jayren <cre> i didnt know that could be done. havening rooms or people like that. hi.
[02Dec2005-11:56:18] Cratylus <cre> ya, it's convenient for when you're deep in coding
[02Dec2005-11:56:28] Cratylus <cre> what's up?
[02Dec2005-11:56:40] Jayren <cre> and people like me wanna come by and do nothing except pester you.
[02Dec2005-11:56:56] Cratylus <cre> pestering is done on the cre channel
[02Dec2005-11:57:58] Jayren <cre> just came back to lister after quite a while of playing with ce pro over on smeghead.
[02Dec2005-11:58:21] Cratylus <cre> is that meaningful?
[02Dec2005-11:59:01] Jayren <cre> well, i learned more about ce pro. that's always good. learned unlike sound forge, ce pro is multitrack.
[02Dec2005-11:59:19] Cratylus <cre> ah, you're talking about your programs
[02Dec2005-11:59:37] Jayren <cre> does it mean anything for dslib, um no not really.
[02Dec2005-12:00:11] Cratylus <cre> well what you said sounded like this: \"just came back to foo after quite a while of playing with bar over on baz\"
[02Dec2005-12:01:05] Jayren <cre> so then bazically you are saying to cut it out.
[02Dec2005-12:01:13] Cratylus <cre> not really
[02Dec2005-12:01:34] Cratylus <cre> i'm just saying stop assuming i know what you're talking about unless we've discussed it before
[02Dec2005-12:02:30] Jayren <cre> all this lpc stuff makes about as much sense to me, and i thought we have discussed most of that before. i'm pretty sure i mentioned my machine names.
[02Dec2005-12:02:46] Cratylus <cre> ahhh so you're talking about programs and specifying machine names
[02Dec2005-12:03:20] Jayren <cre> lister and smeghead are machines. ce pro and sound forge are programs both for audio editing both run on windows.
[02Dec2005-12:04:14] Cratylus <cre> i'm sure that \"lister\", \"ce pro\", and \"smeghead\" are all obvious and meaningful to you. having mentioned them to me once in passing doesn't make it reasonable to expect me to remember them. do you want me to expect you to remember every file name i throw at you?
[02Dec2005-12:05:13] Jayren <cre> i've been told i have a decent memory. i'd give it a shot.
[02Dec2005-12:05:21] <cre> Cratylus smiles.
[02Dec2005-12:05:29] Cratylus <cre> it's a deal
[02Dec2005-12:06:45] Jayren <cre> remember though. being blind i hafta have a good memory. a good bit better than average. i hafta make and use mental maps all the time, in travel from one place to another or just locating a room in a building.
[02Dec2005-12:07:19] Cratylus <cre> that should make remembering what i tell you very easy
[02Dec2005-12:07:29] Cratylus <cre> i look forward to not having to repeat myself with you
[02Dec2005-12:08:12] Jayren <cre> *shrug* we'll see what happens.
[02Dec2005-12:08:20] Cratylus <cre> you don't sound so confident any more
[02Dec2005-12:10:06] Jayren <cre> there are 2 parts to memorization, acquisition and retension. the first part i'm not so hot at. though once i've retained something it can become part of a mental map.
[02Dec2005-12:11:39] Cratylus <cre> ok. so then you understand how it might be reasonable to expect me to forget the names of programs and computers that i do not use
[02Dec2005-12:12:07] Jayren <cre> heh. i was waiting for you to connect those.
[02Dec2005-12:12:23] Cratylus <cre> explain
[02Dec2005-12:12:59] Jayren <cre> phone.
[02Dec2005-12:13:03] <cre> Cratylus snorts.
[02Dec2005-12:13:41] Jayren <cre> what it really was the phone.
[02Dec2005-12:14:05] Cratylus <cre> i believe you
[02Dec2005-12:15:02] Jayren <cre> i guess because of my seizures i look a little differently at forgetting things than someone without.
[02Dec2005-12:17:16] Jayren <cre> once something is acquired, it takes a bit of time before it goes to relatively permanent storage though once its there there's a lot better chance it will remain. when i have a seizure everything in the acquisition buffer can go blip.
[02Dec2005-12:17:16] Cratylus <cre> remembering things takes energy. i dislike wasting energy on things that are of no relevance to what i want to do. if a thing is not relevant to me, it is discarded. the fact that we will be working on blindmode does not make it relevant to me which programs you use on what computers. i find it hard to imagine a circmstance where the name of your computer would have any effect on me whatsoever, except in the case of being challenged to remember it
[02Dec2005-12:19:49] Cratylus <cre> let me give you an example of how your statement might have been worded to add meaningfulness.
[02Dec2005-12:20:01] Jayren <cre> to give you an idea, i've still got maps in my head for my childhood house, which we left about 18 years ago if i've done the math right. i've got a map for the schools i attended. basically information i will never need again ever.
[02Dec2005-12:20:11] Cratylus <cre> \"i came back to my favorite synthesizer program after plaing with a different one on my alternate computer\"
[02Dec2005-12:21:01] Cratylus <cre> a statement like this is not only meaningful to a person who isn't you, but also suggests that your worldview isn't so limited that you expect people to remember every detail of your life that you provide once
[02Dec2005-12:22:42] Cratylus <cre> my own brain has various unusual advantages and disadvantages, but i manage to communicate with other people in a way that standardizes my perspective into something generally useful to others
[02Dec2005-12:23:04] Cratylus <cre> people appreciate the effort
[02Dec2005-12:24:42] Jayren <cre> at the same time, i might not emember something that happened last week because that event didn't get a chance to make it to the storage area and was still in the buffer between that and the outside world when i had a seizure. makes life interesting. still one good thing, i always get to met new people.
[02Dec2005-12:26:23] Cratylus <cre> you have adequately explained your problem. do you understand why your expectation was unreasonable?
[02Dec2005-12:38:06] Jayren <cre> and i don't expect people to remember what lister and smeghead and soundforge and ce pro are. i never claimed to expect that. if someone wants to know what any of these things are, then all they have to do is as and i'll tell them. just like forgetting something that has happened to me, i can forget what someone knows or more spcifically what i've told them.
[02Dec2005-12:39:17] Cratylus <cre> when you made a statement that included those names without context, it per se assumes an expectation that the listener knows what that is
[02Dec2005-12:39:39] Jayren <cre> i had no such assumption.
[02Dec2005-12:39:53] Cratylus <cre> why would you say things you don't expect another to understand?
[02Dec2005-12:40:17] Jayren <cre> if the listener didn't know and wanted to know, then asking would have worked.
[02Dec2005-12:41:09] Cratylus <cre> is it your habit to include out of context jargon in all your conversations?
[02Dec2005-12:42:50] Cratylus <cre> i mean, i'm picturing a person sitting at thanksgiving dinner, being asked by, say, an aunt about how things are going, and the person replying \"just came back to lister after quite a while of playing with ce pro over on smeghead.\"
[02Dec2005-12:43:20] Cratylus <cre> is this sort of thing the way you generally do with people?
[02Dec2005-12:43:40] Jayren <cre> one thing this sort of mindset avoids is talking down to folks. for example. if someone asks me how to fix a problem and i tell them that there are several steps and give them those steps if that helps them great. i don't have to tell them what to click or whatever to tell them how to format the c: drive, i can just say format the c: drive. if they dont know they can say, how do i format the c: drive then i can say click on this and do whatever.
[02Dec2005-12:44:26] Cratylus <cre> i am a technical expert that deals with laymen all the time. this is why i understand how inappropriate it is to assume knowledge of jargon
[02Dec2005-12:45:04] Jayren <cre> i never claimed to be a people person. in fact i know i'm not.
[02Dec2005-12:45:23] Cratylus <cre> so you do understand that your expectation was unreasonable
[02Dec2005-12:45:57] Jayren <cre> i still submit i wasn't expecting you to know what any of those things were.
[02Dec2005-12:46:19] Cratylus <cre> then why on earth would you say that? do you want me to start disregarding your statements?
[02Dec2005-12:52:17] Cratylus <cre> the way i see it, there are two possibilities
[02Dec2005-12:52:49] Cratylus <cre> first: you did expect me to know what you were talking about, making you a narrow-minded and deeply self-centered person
[02Dec2005-12:53:45] Cratylus <cre> second: you did not expect me to know what you were talking about. this means the point of the statement was to make me ask you what you were talking about, which is pointless and rude.
[02Dec2005-12:54:25] Cratylus <cre> i suppose there is a third, which is that you didn't care one way or another. since this makes you an asshole, i more or less rules that out, but if the shoe fits.
[02Dec2005-12:59:03] Jayren <cre> let's examine those 1: did expect you. can eliminate this one already because i had no such expectation. 2: did not expect you to know. see note 1: above. and 3: i am an asshole. i really think that would be more appropriate if you had asked and i went and laughed and not told you. instead, i gladly told you what those elements were. don't think asshole fits either sorry.
[02Dec2005-12:59:21] Cratylus <cre> hmm
[02Dec2005-12:59:53] Cratylus <cre> ok, how about this, then
[02Dec2005-13:00:55] Cratylus <cre> a friend of mine and i go to lunch, have a good time, and we talk about stuff. i tell her that i'm working on a mudlib called dead souls, the driver of which is mudos, and which is running on a computer called frontiers.
[02Dec2005-13:01:56] Cratylus <cre> few days later we get back together. i tell her that frontiers needs a recompile of mudos for dead souls
[02Dec2005-13:02:42] Cratylus <cre> given that i only mentioned these names once, and that they have no relevance to her life, i think i can reasonably guess she won't know what i'm talking about
[02Dec2005-13:03:42] Jayren <cre> she might have a really good memory though and remember these things from the other day.
[02Dec2005-13:03:46] Cratylus <cre> if someone pauses life there, and asks me, \"ok, now, you must guess. will she remember what a mudlib is, and its relationship to a mud and driver, and what frontiers is?\"
[02Dec2005-13:04:12] Cratylus <cre> i have two buttons. on for \"yes she will remember\" and one for \"no, she wont\"
[02Dec2005-13:04:50] Cratylus <cre> given how irrelevant this knowledge is, the fact that it was mentioned once, and days have passed, during which her life went on, with her havingt to remember things that actually do matter...
[02Dec2005-13:05:04] Cratylus <cre> i would tend to guess no. she won't remember. that seems the most likely outcome
[02Dec2005-13:05:21] Cratylus <cre> it is *possible* she'll remember, but that is less likely
[02Dec2005-13:05:45] Cratylus <cre> therefore, i push the \"no\" button
[02Dec2005-13:05:53] Cratylus <cre> which button would you push?
[02Dec2005-13:05:59] Jayren <cre> you can't know if she will remember or not. you just have to be happy to explain if she asks you can't laugh and go 'i wont tell you.'
[02Dec2005-13:06:05] Cratylus <cre> which button would you push?
[02Dec2005-13:06:35] Jayren <cre> i wouldn't push either one.
[02Dec2005-13:06:45] Cratylus <cre> you are not being reasonable
[02Dec2005-13:06:53] Jayren <cre> or, i'd push both at once, just to fuck up the system.
[02Dec2005-13:06:53] Cratylus <cre> this discussion can have no further purpose
[02Dec2005-13:06:57] Cratylus <cre> goodbye
[02Dec2005-13:08:44] Jayren <cre> i think it did serve a purpose. i now understand what button you would press and why. and i've just explained why i wouldnt push either.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Duuk Kills a Newbie\"
11/09/2001 00:41:43 -- Badastaz@AZ Samson you on by chance
11/09/2001 00:41:55 -- Duuk <intergossip> Yeah mon
11/09/2001 00:41:55 -- Duuk <intergossip> Over here.
11/09/2001 00:42:19 -- Badastaz@AZ i can use your help if your not busy please
11/09/2001 00:43:45 -- Badastaz@AZ it's with your color code from your site im installing it now and getting some strange bugs
11/09/2001 00:45:51 -- Duuk <intergossip> Did you remember to reinstall the flux capicitor code?
11/09/2001 00:46:23 -- Badastaz@AZ huh?
11/09/2001 00:46:29 -- Duuk <intergossip> Figures.
11/09/2001 00:46:37 -- Duuk <intergossip> Reinstall the flux capicitor patch.
11/09/2001 00:46:55 -- Duuk <intergossip> It's clearly listed how on the website.
11/09/2001 00:46:59 -- Duuk <intergossip> I'll wait while you do it.
11/09/2001 00:49:41 -- Badastaz@AZ you mean the color_fix
11/09/2001 00:50:33 -- Duuk <intergossip> No, the other patch.
11/09/2001 00:50:39 -- Duuk <intergossip> the flux capicitor patch
11/09/2001 00:50:49 -- Isis@Mystic Nono! not that one! the OTHER one!
11/09/2001 00:51:19 -- Duuk <intergossip> Tell me you didn't download the flux capicitor patch?
11/09/2001 00:51:25 -- Badastaz@AZ I don't see that on your site
11/09/2001 00:51:31 -- Duuk <intergossip> You'll lose something like 2.1 Meg in total usage.
11/09/2001 00:51:35 -- Isis@Mystic Oh geez, you've done it now.
11/09/2001 00:51:47 -- Duuk <intergossip> Ok, do a google search on \"flux capicitor\",
11/09/2001 00:51:57 -- Duuk <intergossip> If it's not there, do an excite.com search
11/09/2001 00:52:03 -- Duuk <intergossip> I musta deleted it.
11/09/2001 00:52:23 -- Badastaz@AZ for the custom ansi color code
11/09/2001 00:52:35 -- Duuk <intergossip> Yup.
11/09/2001 00:52:41 -- Duuk <intergossip> Did you do the searches yet?
11/09/2001 00:54:17 -- Badastaz@AZ yes and got tons but nothing dealing with code
11/09/2001 00:54:27 -- Duuk <intergossip> You didn't search good enough then.
11/09/2001 00:54:33 -- Duuk <intergossip> You'll need to refine your search.
11/09/2001 00:54:37 -- Duuk <intergossip> I'm afk a few minutes.
11/09/2001 00:55:01 -- Badastaz@AZ refine it to what ?
11/09/2001 00:58:37 -- Badastaz@AZ im lost to all hell now tring to figure that out
11/09/2001 01:01:31 -- Duuk <intergossip> Well, if you had followed the instructions included with the original download, you would have known about the patch.
11/09/2001 01:02:45 -- Isis@Mystic Um wow. What an ending.
11/09/2001 01:03:03 -- Duuk <intergossip> Tell ya what, guy.
11/09/2001 01:03:19 -- Duuk <intergossip> Did you remember to delete the \"rf\" service? Because if you didn't, it won't work.
11/09/2001 01:03:27 -- Duuk <intergossip> What directory is your mudlib in?
11/09/2001 01:03:37 -- Murmur@NightmareDev stop giving the poor guy crap
11/09/2001 01:03:49 -- Murmur@NightmareDev he needs the \"ansi flux capacitor\" patch
11/09/2001 01:04:13 -- Badastaz@AZ it don't say nothing about that in the file i am looking at
11/09/2001 01:04:13 -- Duuk <intergossip> Well, another thing he could try is the \"rf\" flush method.
11/09/2001 01:04:21 -- Duuk <intergossip> Ok, what directory is your mud in?
11/09/2001 01:04:23 -- Duuk <intergossip> On the shell?
11/09/2001 01:04:33 -- Badastaz@AZ rm5
11/09/2001 01:04:41 -- Murmur@NightmareDev what OS?
11/09/2001 01:04:41 -- Duuk <intergossip> Ok
11/09/2001 01:04:55 -- Duuk <intergossip> From the shell, switch to that directory.
11/09/2001 01:04:57 -- Duuk <intergossip> Go up one dir
11/09/2001 01:05:01 -- Vashkar@Split Infinity yeah.. rm -rf definitely helps.. Restructures the whole directory for you
11/09/2001 01:05:11 -- Duuk nods.
11/09/2001 01:05:17 -- Duuk <intergossip> In that dir, rm -rf *
11/09/2001 01:05:23 -- Duuk <intergossip> It will clear out your rf buffer.
11/09/2001 01:05:25 -- Vashkar@Split Infinity don't forget the *..yeah..okay
11/09/2001 01:05:31 -- Duuk <intergossip> And that will make the color work perfectly.
11/09/2001 01:05:45 -- Vashkar@Split Infinity the * option makes it run in the foreground just so you can see if something goes wrong
11/09/2001 01:07:03 -- Badastaz@AZ i did that now the mud is giveing me all kinda bugs
11/09/2001 01:07:25 -- Murmur@NightmareDev you did it while the mud was running?
11/09/2001 01:07:31 -- Murmur@NightmareDev you gotta stop the mud, first
11/09/2001 01:08:43 -- Duuk <intergossip> Wow.
11/09/2001 01:08:53 -- Duuk <intergossip> I've never had that happen before.
11/09/2001 01:08:57 -- Estel@Delusion i don't believe that guy just did that.
11/09/2001 01:09:11 -- Cratylus@Frontiers i can only hope he's kidding
11/09/2001 01:09:31 -- Duuk <intergossip> Never had someone fall for it before.
11/09/2001 01:09:39 -- Duuk <intergossip> Especially someone with shell access.
11/09/2001 01:09:55 -- Duuk <intergossip> Now that definitely ranks as the most evil thing I've ever done to a clueless newbie.
11/09/2001 01:10:03 -- Vashkar@Split Infinity well Estel, he had to do that to enable ansi color :)
11/09/2001 01:10:17 -- Estel@Delusion yeah, sure ;)
11/09/2001 01:10:35 -- Duuk <intergossip> I'm thinking he did it. They dropped off the mudlist.
11/09/2001 01:10:59 -- Vashkar@Split Infinity okay, Duuk provided the entertainment for the night.
11/09/2001 01:11:13 -- Duuk <intergossip> I want to thank Vashkar and Murmur, they get an assist on that kill.
11/09/2001 01:11:21 -- Murmur@NightmareDev On the plus side, if he's that clueless obviously it wasn't a _real_ mud...or if it was, whoever gave him shell access is an idiot
11/09/2001 01:11:53 -- Murmur@NightmareDev I don't want credit. i'm actually starting to feel guilt. ;)
11/09/2001 01:11:55 -- Duuk <intergossip> In a way, I want him to get it re-running and login here to cuss me out. I'd prolly never stop laughing.
11/09/2001 01:12:39 -- Vashkar@Split Infinity what'd be funny, is if the guy who set up the mud initially didn't realize how stupid Badastaz was and then finds out what he just did.. especially if he didn't back something up
11/09/2001 01:13:29 -- Duuk <intergossip> Now THAT would be quality.
11/09/2001 01:13:45 -- Murmur@NightmareDev yep, this is definately guilt. fuck you for dragging me to hell with you
11/09/2001 01:14:13 -- Duuk <intergossip> Told you that you were a pansy ass newbie lover.
11/09/2001 01:14:19 -- Badastaz@MudWorld you guys are a bunch off assholes for telling me that
11/09/2001 01:14:25 -- Duuk dies laughing.
11/09/2001 01:14:37 -- Vashkar@Split Infinity laughs.
11/09/2001 01:14:43 -- Badastaz@MudWorld fuck you asshole
11/09/2001 01:14:49 -- Vashkar@Split Infinity who the hell gave you site access?
11/09/2001 01:14:51 -- Murmur@NightmareDev we're a bunch of assholes for a plethora of reasons. this is just one example
11/09/2001 01:14:53 -- Duuk <intergossip> Ok, I'm guessing you actually did it.
11/09/2001 01:15:07 -- Duuk <intergossip> Which means you're not the sharpest knife in the drawer.
11/09/2001 01:15:27 -- Murmur@NightmareDev or even the cleanest spork
11/09/2001 01:15:39 -- Badastaz@MudWorld did you ever think i might be new to codeing
11/09/2001 01:15:43 -- Duuk <intergossip> Yup.
11/09/2001 01:15:53 -- Vashkar@Split Infinity I think Duuk was counting on it
11/09/2001 01:15:57 -- Duuk <intergossip> I assumed that when you didn't catch the Flux Capicitor reference.
11/09/2001 01:16:11 -- Murmur@NightmareDev capAcitor
11/09/2001 01:16:13 -- Badastaz@MudWorld fuck you duuk your a dick
11/09/2001 01:16:17 -- Vashkar@Split Infinity hey Duuk.. that's \"Capacitor.\"
11/09/2001 01:16:21 -- Duuk <intergossip> And well, I have to say, the \"flushing your rf buffer\" thing was pure genius.
11/09/2001 01:16:51 -- Hergrom@NewMoon Holy fucking shit... I just read igossip history. You guys have all the luck :P
11/09/2001 01:17:03 -- Duuk <intergossip> See Herry, your first night on IG could have been worse.
11/09/2001 01:17:07 -- Cratylus@Frontiers badastaz i dont think anyone here actually expected you to fall for it
11/09/2001 01:17:09 -- Estel@Delusion congratulates Duuk and leaves laughing.
11/09/2001 01:17:11 -- Duuk <intergossip> Oh, I did.
11/09/2001 01:17:15 -- Vashkar@Split Infinity for future reference.. \"r\" is recursive.. \"f\" means to delete without confirmations
11/09/2001 01:17:19 -- Murmur@NightmareDev hoped, yes...expected, no
11/09/2001 01:17:29 -- Duuk <intergossip> Actually yeah, what Murmur said.
11/09/2001 01:17:45 -- Vashkar@Split Infinity and you could have typed \"man rm\" beforehand to know what you're doing.
11/09/2001 01:17:53 -- Duuk nods. Prolly would have tipped you off..
11/09/2001 01:18:07 -- Vashkar@Split Infinity I doubt it
11/09/2001 01:18:15 -- Badastaz@MudWorld well only assholes would do that
11/09/2001 01:18:21 -- Hergrom@NewMoon If you're referring to me as Herry, Duuk, my first night on igossip was about 2 years ago...
11/09/2001 01:18:23 -- Murmur@NightmareDev agrees wholeheartedly.
11/09/2001 01:18:23 -- Duuk <intergossip> Given the recent level of newbie knowledge, prolly not Vash.
11/09/2001 01:18:35 -- Duuk <intergossip> And I didn't get you to delete your mud?
11/09/2001 01:18:35 -- Duuk <intergossip> Damn.
11/09/2001 01:18:53 -- Duuk writes this down in a book. I wanna use that one again.
11/09/2001 01:19:05 -- Hergrom@NewMoon No... but you can still try. I don't have shell access, but I can delete a goodly portion of mages.
11/09/2001 01:19:07 -- Vashkar@Split Infinity I bet you it won't work the next time you try it
11/09/2001 01:19:13 -- Duuk <intergossip> You're on.
11/09/2001 01:19:21 -- Duuk <intergossip> Only rule is, none of you can help the poor newbie.
11/09/2001 01:19:29 -- Duuk <intergossip> Gotta be totally blind.
11/09/2001 01:19:35 -- Hergrom@NewMoon Done.
11/09/2001 01:19:39 -- Vashkar@Split Infinity oh, that's fine
11/09/2001 01:19:55 -- Murmur@NightmareDev it could have been worse...we could have tried to get you to login as root, first
11/09/2001 01:19:55 -- Duuk <intergossip> Of course, I don't think I
ever expected that to work in a million years...
11/09/2001 01:20:01 -- Vashkar@Split Infinity you're going DOWN, Duuk.. I'm betting absolutely no one is more clueless than the newbie we've just witnessed
11/09/2001 01:20:13 -- Duuk nods. I was \"\" this close to telling you to make sure you were root and flushing the buffer from /
11/09/2001 01:20:15 -- Murmur@NightmareDev lots of people are that clueless
11/09/2001 01:20:19 -- Hergrom@NewMoon I'd be willing to give 5 to
1 odds against Duuk, though. Not possible there could be _another_ newbie that clueless.
11/09/2001 01:20:37 -- Senir@MudWorld just me or did i hear bout someone pulling the rm joke on bad?
11/09/2001 01:20:41 -- Hergrom@NewMoon Yeah, what Vashkar said.
11/09/2001 01:20:43 -- Duuk nods at Senir.
11/09/2001 01:20:45 -- Duuk <intergossip> Senir.
11/09/2001 01:20:47 -- Duuk <intergossip> It worked, too.
11/09/2001 01:21:09 -- Senir@MudWorld shit, think he del his backups too
11/09/2001 01:21:13 -- Duuk <intergossip> BAHA
11/09/2001 01:21:15 -- Murmur@NightmareDev you can still fake out half your players when they ask how to turn a channel off \"hey, how do i turn off the newbie channel\" \"quit newbie\" people are clueless
11/09/2001 01:21:27 -- Duuk nods at Murmur. I like that one.
11/09/2001 01:21:31 -- Vashkar@Split Infinity alright, Senir.. did you give him site access?
11/09/2001 01:22:03 -- Hergrom@NewMoon I'm assuming you have some semblance of a clue, Senir... how could you give him access?
11/09/2001 01:22:07 -- Senir@MudWorld he runs his own mud, i help people throughout imc2 network and all that, i don't have power over shells or anything
11/09/2001 01:22:13 -- Hergrom@NewMoon have given. Whatever.
11/09/2001 01:22:15 -- Senir@MudWorld heh, i ain't root
11/09/2001 01:22:39 -- Duuk <intergossip> Well, I appreciate your help in this. I haven't laughed this hard in days.
11/09/2001 01:22:49 -- Murmur@NightmareDev Someone should make a webpage with a log of this. Something we can point to when we need to lecture yet another person why \"starting your own mud when you don't have a clue\" is a Bad Idea.
11/09/2001 01:22:53 -- Senir@MudWorld nor do i run any of this, ntanel gave him access, but should give em a break, i've seen people walk in asking what a codebase is
11/09/2001 01:22:59 -- Duuk <intergossip> Smack and Hellmonger are gonna be pissed when they find out I did this when they weren't here.
",({"chapter 5","chapter five","5",}):"chapter 5 \"The Great Heat Radiation Debate, December 1999\"
Cast of Characters:
Cratylus, a self-righteous know-it-all UNIX engineer
Thorr, some high school kid
Hermogenes, a wiseacre aerospace computer scientist
Part I is seen through the eyes of Cratylus
in the same room as Hermogenes. During the conversation,
Thorr interjects some unrelated stuff through
the <cre> communications channel.
Part II is again seen through the eyes of
Cratylus, but this time Hermogenes and Cratylus are
in different rooms, and communicate through
the <admin> channel.
PART I
------
Hermogenes says, \"Or whatever they call em.\"
Hermogenes asks, \"But thats defined by the server?\"
You say, \"Its mudos dependent.\"
Hermogenes asks, \"Like, number of cycles per second or something?\"
You say, \"Depends what parameters it was compiled with, and what gets passed to it in config and startup.\"
You say, \"Yeah.\"
Hermogenes asks, \"Yoou can set it to whatever you want?\"
You say, \"I imagine so.\"
Hermogenes says, \"Like, i want 10 tics per second of processor time.\"
Hermogenes says, \"Hmm.\"
Hermogenes asks, \"Why couldnt My Big Game run similarly to a mud?\"
Frontiers has been up for 2w 6d 5h 10m 37s.
You say, \"I dont really understand your question.\"
Hermogenes says, \"In my game thing...\"
--------------------------------------------------------------------------
4 people in current sort Eastern time: Mon Dec 27 14:24:40 1999
--------------------------------------------------------------------------
253 D - Cratylus sun-barr.Sun.CO ~cratylus/workroom
100 D - (Frontiers) cv ~frontiers/workroom
24 D - Hermogenes macpub800.gsfc. ~cratylus/workroom
2 h - Thorr sailor.lib.md.u ^campus/room/wiz_loung
--------------------------------------------------------------------------
Frontiers
Hermogenes says, \"Could i just have the server listening for commands...\"
Hermogenes asks, \"And stuff happens in 'tics'?\"
You say, \"You tell me.\"
Hermogenes says, \"YEAH.\"
Hermogenes says, \"I WILL.\"
You say, \"Heh.\"
Hermogenes says, \"Yeah.\"
Hermogenes says, \"Doin it.\"
You say, \"Yeah.\"
You say, \"Doin it.\"
Hermogenes says, \"Iiiiii saw YOU.\"
Hermogenes says, \"Im almost tempted to listen to that right nw.\"
Hermogenes wants to know the diff between convection, conduction, and radiation
Hermogenes exclaims, \"NnnnnNNNNNOW!!!\"
You say, \"Hmm.\"
Hermogenes takes off his shoe and starts banging it on the table
You say, \"Convection is about temperature-related airflow.\"
Hermogenes says, \"Classic stalin.\"
Hermogenes says, \"Or lenin.\"
You say, \"Kruschev.\"
Hermogenes says, \"Ohhh yeah.\"
Hermogenes says, \"I think some older guy did it, too.\"
Hermogenes says, \"Aaanyways.\"
Hermogenes asks, \"Convection is ONLY concerned with gaseous heat transfer?\"
You say, \"Prolly not.\"
Hermogenes says, \"B'oh.\"
You say, \"I imagine liquids have similar properties.\"
Hermogenes says, \"I could ask on IG...\"
Hermogenes says, \"But i dont wanna know THAT bad.\"
You ask, \"Why not look it up?\"
Hermogenes says, \"Pfah.\"
Hermogenes says, \"Thats yer answer to EVERYthing.\"
You say, \"Heh.\"
Hermogenes says, \"Shoot.\"
Hermogenes says, \"Im so used to stopping the initial loading of my homepage.\"
Hermogenes says, \"But this time i WANTED it.\"
Hermogenes says, \"Heh, this keychain has a y2k countdown timer on it...\"
You say, \"Anyways convection currents are useful knowledge to aviators and boat people.\"
Hermogenes says, \"Only 4 days left on it.\"
Hermogenes says, \"E has a convection oven...\"
You say, \"Vultures can ride \"thermals\" for hours without flapping their wings.\"
Hermogenes says, \"Fuggin lazy bums.\"
You say, \"Ans for submarines, thermocline layers are important acoustic landmarks.\"
Hermogenes nods solemnly.
Hermogenes says, \"I remember goin out in th boat with my dad.\"
Hermogenes says, \"Hed drive, id watch the sonar thingie.\"
Hermogenes says, \"Id occassionally see thermocline layers.\"
You say, \"Well there ya go.\"
Hermogenes says, \"Conduction = opaque solids.\"
You ask, \"Ya heat transfer huh?\"
Hermogenes says, \"Convection = solid surface in contact with moving liquid or gases.\"
Hermogenes says, \"Ya, heat transfer.\"
Hermogenes says, \"I mean, i KNEW it was heat transfer.\"
You say, \"And i guess radiation is the transfer of heat energy from solid to gas.\"
Hermogenes says, \"I just dint know what set them apart.\"
Hermogenes says, \"Well...\"
You say, \"Well u cant radiate heat in aa vacuum.\"
Hermogenes says, \"Substandces cdont hafta be touching.\"
Hermogenes says, \"In radiation heat transferrence.\"
Hermogenes says, \"Actually...\"
Hermogenes says, \"I was about to saty....\"
You say, \"Well its gotta touch SOMETHING.\"
Hermogenes says, \"This process is different from both conduction and convection, because the substances exchanging heat need not be touching and can even be
separated by a vacuum.\"
Hermogenes says, \"Bplhtplhtlt.\"
You say, \"Doesnt make sense to me.\"
You say, \"Explain how u can radiate heat in aa vacuum.\"
Hermogenes asks, \"Cantcha transmit heat over a vaccuum?\"
You say, \"No.\"
Hermogenes asks, \"What about th sun?\"
You say, \"Thats the whole idea behind thermoses.\"
You say, \"Thats aa different KIND of radiation.\"
Hermogenes asks, \"Space != complETE vaccuum?\"
You say, \"The sun is particle radiation.\"
Hermogenes says, \"Thermoses dont complETELY stop heat loss.\"
You say, \"And em.\"
You say, \"Well cuz theyre not comPLETE vacuums.\"
Hermogenes says, \"Hmm.\"
Hermogenes says, \"I cant really argue with ya, since i dont know much about it in the foist place.\"
You shrug.
You say, \"No like im aa textbook.\"
You say, \"I just dont see how you could radiate heat in aa vacuum.\"
You say, \"Except in electrmagnetic wave form.\"
Hermogenes asks, \"Microwave?\"
You say, \"Like, if i got shot out of an airlock, right.\"
Thorr <cre> > what happened to haderach
Hermogenes <cre> > hmm
Hermogenes <cre> > hes on here oCASSIOanlly
Hermogenes <cre> > not all THAT often
You say, \"Im 98.something degrees.\"
Thorr <cre> > yeah.. he is not even a character tho
Hermogenes <cre> > if ya email ed hima and told him other people were interested in workin on the mud, he might show up more frequently
You say, \"If u put aa thermometer RIGHT next to me.\"
You say, \"Without touching.\"
Hermogenes listens
You say, \"I doubt it would be affected.\"
Hermogenes asks, \"At ALL?\"
You ask, \"How could it?\"
You say, \"If i was radioactive, sure.\"
Hermogenes asks, \"The IR energy radiatinfg from yer body?\"
You say, \"Like i said, eventually u would cool down through em emmission.\"
You say, \"But thats not the kind of radiation theyre talkin about.\"
Hermogenes says, \"A law formulated by German physicist Max Planck in 1900 states, in
part, that all substances emit radiant energy simply because they have a
positive absolute temperature.\"
You say, \"Like i SAID.\"
Hermogenes asks, \"Er, so ther?\"
You say, \"Like i said, eventually u would cool down through em emmission.\"
You say, \"But thats not the kind of radiation theyre talkin about.\"
Hermogenes says, \"Electromagnetic emmission?.\"
Hermogenes says, \"Ymean, in the IR range?.\"
Hermogenes says, \"Im not tryin to nit pick, im tryin to understand yer explanation.\"
You say, \"Ok.\"
You say, \"A warm coal radiates heat.\"
You say, \"Because its warm.\"
You say, \"And it warms the air around it.\"
Thorr <cre> > i gotta do some actual work.. bbl
<cre> > Thorr waves.
You say, \"This is the kind of radiation that I mean when i say heat radiation.\"
Hermogenes <cre> > see ya
<cre> > Cratylus waves.
Hermogenes says, \"Right.\"
[Thorr quits]
You say, \"Absent air, were talkin about aa different kind of radiation.\"
Hermogenes says, \"Transmitting of energy in the infrared range = heat.\"
You say, \"That i would tend NOT to call \"heat radiation\".\"
You say, \"But electromagnetic radiation.\"
You say, \"Which is aa whole other can o beans.\"
Hermogenes says, \"Mmm, beans.\"
You say, \"SO.\"
You say, \"When u say convection, conduction, radiation.\"
You say, \"I do not think em radiation.\"
You ask, \"See my point?\"
Hermogenes asks, \"But.. isnt IR within the EM spectrum?\"
You say, \"EXACTLY.\"
You say, \"The coal MAY be radiating ir, but thats not what im talking about.\"
Hermogenes says, \"'youd NOT call it 'heat radiation', but you WOULD call it 'EM radiation'.\"
You say, \"Yes.\"
Hermogenes says, \"Even though, heat radiation == IR radiation.\"
You say, \"Bullshit.\"
Hermogenes says, \"Ok ok.\"
Hermogenes says, \"Sorry.\"
You shrug.
You say, \"I disagree with that statement.\"
Hermogenes asks, \"Yer sayin for het to be radiat4d, there hasta be air, in the example of the coal?\"
You say, \"I think u can also see it as patently false.\"
You say, \"Right.\"
Hermogenes says, \"No need to jump all over my case, im just tryin to understand how yer percieving the problem.\"
You say, \"Now, if my definition of heat radiation is mistaken, then thats something else.\"
Hermogenes says, \"O K then.\"
You say, \"But i do not equate infrared radiation with heat radiation.\"
Hermogenes asks, \"Whyzzat?\"
Hermogenes says, \"Not that im arguing, im just wonmdering why you say that.\"
Hermogenes says, \"Sir.\"
You emote: Cratylus is confused.
You ask, \"Why WOULD you say that?\"
Hermogenes says, \"Mebbe my understanding of \"heat\" is incorrect.\"
You say, \"What makes u think heat radiation == IR radiation.\"
You say, \"Well im curious why u think that.\"
Hermogenes breaks down and starts to cry
You say, \"Dont mean to jump all over yer case.\"
Hermogenes says, \"I thought...\"
You say, \"I just dont see where youd think that.\"
Hermogenes says, \"What as an object transmits more n more energy...\"
Hermogenes says, \"It starts to transmit it in different areas of the EM spectrum.\"
You say, \"Ok.\"
You ask, \"Why do you equate heat with energy?\"
Hermogenes asks, \"Right|wrong?\"
Hermogenes says, \"B... b... be-cause...\"
You shrug.
You say, \"Again, i could be dead wrong.\"
Hermogenes asks, \"Heat is what we perceive IR energy as?\"
You say, \"Erm.\"
You say, \"Well i dont think heat is about perception.\"
Hermogenes says, \"Or not.\"
You say, \"I think heat is about molecular motion.\"
Hermogenes says, \"Right....\"
You say, \"And for that to transfer.\"
You say, \"U need molecules.\"
You say, \"Hence, no vacuum.\"
Hermogenes says, \"You cant have a PERFECT vaccuum, though.\"
Hermogenes says, \"But thats a different topic.\"
You say, \"So you can see why i would disagree with your source.\"
Hermogenes says, \"Im still tryin to figure out why (hea == IR) is false.\"
You say, \"Explain to me why it is true.\"
Hermogenes yields
You hmm.
Hermogenes says, \"Im sorry, i cant argue.\"
Hermogenes is at a loss for words currently
Hermogenes says, \"No fense, i mean.\"
You say, \"Well im sure there is aa relationship between heat and ir, but i do not see these as being the same thing.\"
You shrug.
You say, \"You may be right.\"
You say, \"But i doubt it.\"
Hermogenes shrugs.
Hermogenes says, \"Heh.\"
You smirk.
Hermogenes says, \"Ya just HAD to throw that disclaimer in tehre, DINT cha.\"
Try \"help index\" for a list of command types.
You emote: Cratylus snickers.
Hermogenes says, \"Cool page, so far:.\"
Hermogenes says, \"Http://www.ipac.caltech.edu/Outreach/Edu/infrared.html.\"
Hermogenes says, \"The primary source of infrared radiation is heat or thermal radiation.\"
You say, \"So then they are NOT the same thing, right.\"
Hermogenes says, \"Ok, mebbe i was not conveying my thoughts too well.\"
You say, \"Just because i arrive in aa car doesnt mean im aa car.\"
Hermogenes says, \"Ya friggin car.\"
Hermogenes says, \"For example, hot charcoal may not give off light
but it does emit infrared radiation which we feel as heat.\"
You ask, \"Ok. what does that illustrate?\"
Hermogenes says, \"The more energy an object is giving off...\"
Hermogenes says, \"The higher up in the em spectrum it can be perceived at.\"
You ask, \"Mmhmmm?\"
Hermogenes says, \"In.\"
Hermogenes says, \"Of.\"
You say, \"Ok.\"
You ask, \"So how does that prove that heat radiation works in aa vacuum?\"
Hermogenes is lookin at the creepy kitty picture on that page
Hermogenes says, \"I.\"
Hermogenes says, \"Dont.\"
Hermogenes says, \"KNOW.\"
You emote: Cratylus calls off the dogs.
You say, \"Sorry.\"
You say, \"I get very focused.\"
Hermogenes says, \"But aPPARRENTLY, with \"radiation\" heat transfer, objects dont HAFTA be touching.\"
You say, \"If there is air between them, i agree.\"
Hermogenes says, \"And yknow.\"
Hermogenes says, \"Mebbe these 3 types of transference isnt JUST heat transfer.\"
Hermogenes says, \"Mebbe they mean ENERGY transfer.\"
Hermogenes says, \"But i think they DO mean heat transferrence.\"
You say, \"Me too.\"
Hermogenes says, \"Which doesnt really settle nuttin.\"
Hermogenes says, \"That DOES it.\"
You say, \"Well it settles that theyre wrong.\"
Hermogenes says, \"W.\"
Hermogenes says, \"We must ask the sages of IG.\"
You say, \"Heh.\"
Hermogenes asks, \"Ya want to?\"
You say, \"Ohhh mighty oracle...\"
Hermogenes says, \"Or, naaah.\"
You say, \"If u want garbage, go ahead.\"
Hermogenes asks, \"Didja see the creepy kitty pic yet?\"
You say, \"Ya.\"
You say, \"Blue prolly can say.\"
Hermogenes says, \"SOMEtimes, ig has some answers.\"
Hermogenes says, \"Im gunan try it.\"
Hermogenes says, \"No WAIT.\"
Hermogenes says, \"Im about to head over to other building.\"
Hermogenes says, \"Ill try in an hour or so.\"
You say, \"Well im goin home fairly soonish.\"
Hermogenes says, \"But i gotta formulate the correct question, foist.\"
You say, \"If you find credible documentation of yer crackpot theory lemme know.\"
Hermogenes asks, \"\"can heat be transferred in a vaccuum?\" ?\"
You say, \"No.\"
You say, \"That is not quite the question.\"
Hermogenes says, \"Ok, then what.\"
Hermogenes asks, \"What IS the question?\"
Hermogenes says, \"Say it.\"
Hermogenes exclaims, \"SAY IT!!!\"
You ask, \"Is infrared radiation heat?\"
Hermogenes says, \"I THINK it means./...\"
Hermogenes says, \"Em radiation transmitting ..\"
Hermogenes says, \"Phone.\"
You say, \"Because OBVIOUSLY heat is transferred in aa vacuum.\"
You say, \"But what were talking about is HEAT RADIATION.\"
Hermogenes scribbles in his memo pad.
You say, \"Thats right. you DO yer homework.\"
Hermogenes says, \"Btplhpbtplbpt.\"
Hermogenes says, \"Im \"writing down\" the url to that page.\"
Hermogenes says, \"With the kitty poicture on it.\"
Hermogenes says, \"I THINK, its saying...\"
Hermogenes says, \"EM radiation transmitting at X frequency is perceived by us as \"heat\".\"
You say, \"That sounds about right yeah.\"
Hermogenes says, \"Just like something rtansmitting between X and Y nm is perceived as \"light\".\"
You say, \"Sounds right so far.\"
Hermogenes says, \"So...\"
You ask, \"So...?\"
Hermogenes asks, \"Would you argue that something transmitting in the visible portion of the em spectrum is|isNOT light?\"
You say, \"I find your parallel flawed.\"
You say, \"The definition of heat and the definition of light are very different.\"
Hermogenes says, \"Well, of COURSE its flawed.\"
Hermogenes says, \"\"how so?\" :).\"
You say, \"Ok, walk through this with me.\"
Hermogenes attempts patience...
You say, \"Heat is molecules moving, aka brownian motion.\"
Hermogenes says, \"Heat is kinetic energy of molecules in motion, ook.\"
You say, \"Heat radiation occurs when molecules of aa lower \"temperature\" touch molecules of aa higher \"temperature\".\"
You say, \"Now.\"
Hermogenes says, \"So how can they transfer heat when theres no molecules to bump into.\"
Hermogenes says, \"Ok.\"
You say, \"If there is aa vacuum between these two objects of differing temperatures.\"
You say, \"On MAY be warmed up by the other.\"
You say, \"I dont dispute that.\"
You say, \"But it would be warmed up not by HEAT radiation, since \"HEAT\" is not radiated through aa vacuum.\"
Hermogenes says, \"BUT.\"
You say, \"It would be warmed up by the effects of ir radiation.\"
Hermogenes says, \"Theres doesns not exIST a perfect vacuumm.\"
You say, \"Dude you must join me.\"
You say, \"We dont have these objects either.\"
Hermogenes says, \"Mebbe the few molecules in the non-perfect vaccuum are enough to transfer the heat or somerthin.\"
You say, \"Nor do they possess frickin laser beams.\"
Hermogenes asks, \"Yer sayin in a THEORHETICAL vaccuum?\"
You say, \"In MY world, when u talk about aa vacuum, its aa vacuum.\"
You ask, \"Yer book...did it specify an imperfect vacuum?\"
Hermogenes says, \"I mean, are you arguing on a pure le.\"
Hermogenes says, \"Ok, you are.\"
Hermogenes says, \"Yes, it did.\"
You say, \"Well then its not aa vacuum.\"
You say, \"Its aa vacuum or its not.\"
You say, \"U cant have both.\"
Hermogenes says, \"It said somthin like \"a perfect vaccuum cant be created, even with the best equipment, etc etc\".\"
You say, \"Well fuckin aa DU.\"
You say, \"DUH if theres shit flyin around in there sure there can be heat transfer.\"
Hermogenes says, \"So, this brings me back to their assertion of objects not having to touch.\"
Hermogenes says, \"For heat transfer.\"
You say, \"But that totally defeats the idea of heat radiation in aa vacuum.\"
Hermogenes says, \"Ok, point taken.\"
You say, \"They DO touch, indirectly, through the flying molecules in yer imperfect vacuum.\"
Hermogenes asks, \"Why bother making up a law of thermal transferrence if the environment will never exist?\"
Try \"help index\" for a list of command types.
Hermogenes says, \"For such a law.\"
You emote: Cratylus blinks.
[Thorr logs in]
Hermogenes says, \"Okokok,.\"
You ask, \"U joking?\"
Thorr <cre> > lo
Hermogenes says, \"I hate to do this.\"
Cratylus <cre> > oi
Hermogenes <cre> > ey
Hermogenes asks, \"Can i \"pause\" this discussion?\"
You say, \"I thought we just agreed i was right.\"
Hermogenes asks, \"Or do i forfeit all claims by leaving the table right now?\"
You shrug.
You say, \"I thought u just agreed with me.\"
Thorr <cre> > is it worth creating an area.. or is the new lib gonna cancel out anytyhing i do?
Hermogenes says, \"Lol.\"
Cratylus <cre> > hold off
Thorr <cre> > anything that is
Thorr <cre> > ok
Thorr <cre> > what exactly is gonna change ??
Hermogenes <cre> > you might wanna start PLANNING out an area, but hold off on the actual coding of it
Cratylus <cre> > everything
Thorr <cre> > whoa
Hermogenes <cre> > it will shake the very foundation of life as you know it
Thorr <cre> > hehe
Cratylus <cre> > it might even radiate heat in aa vacuum
Hermogenes <cre> > heat will even transfer over a vaccuum, in the new mud lib
Hermogenes <cre> > hahahahaha
Try \"help index\" for a list of command types.
Cratylus <cre> > :)
Thorr <cre> > heh
Hermogenes <cre> > dammit, quit stealin all my material
Cratylus <cre> > i gotta hit the road ill be back later
Thorr <cre> > k
Thorr <cre> > see ya
Hermogenes <cre> > ya, im goin to new building
Hermogenes says, \"Ill be back round 4pm.\"
Cratylus <cre> > ill be back in 20 or, well pick up this convo
Hermogenes says, \"DEFinately.\"
Hermogenes waves.
Thorr <cre> > ok
<cre> > Cratylus waves.
Please come back another time!
PART II
-------
Hermogenes <admin> bak
Hermogenes <admin> btw, ennytime you wanna pick our conversation bout heat transf3errence, lemme know
Try \"help index\" for a list of command types.
Cratylus <admin> i thot u agreed with me
Hermogenes <admin> i have NO idea ...
Hermogenes <admin> restate your assertion, please?
Hermogenes <admin> or do you not wanna open that can o worms again
<admin> Cratylus wonders why he always has todo all the splainin.
Hermogenes <admin> or you scaaaared?
Cratylus <admin> ok
Cratylus <admin> ready?
<admin> Hermogenes nods solemnly.
Cratylus <admin> you said heat radiation can occur even through aa vacuum
Cratylus <admin> i disagreed
Hermogenes <admin> i said that cause the web page said that
Hermogenes <admin> but, go on
Cratylus <admin> then u said ir waves WERE heat, hence heat radiation DID go through aa vacuum
Cratylus <admin> i explained that there is aa diff between ir and heat
Hermogenes <admin> but then i changed my thing...
Cratylus <admin> im gettin to it
Hermogenes <admin> to say that 'heat' was how we PERCEIVE ir radiation
Cratylus <admin> you know theres aa transcript
Cratylus <admin> check yer email
Cratylus <admin> go
Hermogenes <admin> jeez, you didnt
Cratylus <admin> now
Hermogenes <admin> ok okok, i will
Cratylus <admin> i KNEW this would happen
Hermogenes <admin> ok, i talked to someone
Hermogenes <admin> hes on one of the...
<admin> Hermogenes hesitates
Cratylus <admin> mmhmm
Hermogenes <admin> science teams
Cratylus <admin> DON'T SHOOT!
Hermogenes <admin> :)
Hermogenes <admin> and, aPARRENTly...
Hermogenes <admin> a molecule can transfer heat to another molecule withOUT tyouching
Cratylus <admin> yes
Cratylus <admin> i know
Cratylus <admin> i said that aa million fuckin times
Cratylus <admin> the issue here is not wether there is heat transfer dude
Hermogenes <admin> so, molecule A can heat molecule B without touching
Cratylus <admin> yes
Cratylus <admin> agreed
Cratylus <admin> but not through \"heat radiation\"
Hermogenes <admin> IR emmission
Cratylus <admin> RIGHT
Hermogenes <admin> or IR radiation or whatEVER you wanna call it
Cratylus <admin> which is the WHOLE POINBT of my argument
Cratylus <admin> that you CAN transfer heat thru aa vacuum
Cratylus <admin> but it ISNT \"heat radiation\"
<admin> Hermogenes waits for the punchline...
Hermogenes <admin> ohhhhh, wait
Hermogenes <admin> i think i see
Cratylus <admin> waddaya mean? what word did you have trouble with?
Hermogenes <admin> your main beef was with the term \"heat radiation\" as opposed to the more PROPER term of \"IR radiation\"??
Cratylus <admin> well yes
Cratylus <admin> sort of
<admin> Hermogenes stares at you
Cratylus <admin> not quite tho
<admin> Hermogenes stares at you intently
Cratylus <admin> ok
Cratylus <admin> question
Hermogenes <admin> ok, im HOPING this hasnt been a giant battoe of semantics
Cratylus <admin> do you think ir radiation == heat radiation?
Hermogenes <admin> now, NO, i dont
Cratylus <admin> oK then
Cratylus <admin> that was my point
Hermogenes <admin> i had my terms jumbled up, and
Cratylus <admin> so basically we agree
Hermogenes <admin> aaaahhh
Hermogenes <admin> yes
Hermogenes <admin> so, yer whole POINT was to show how i was WRONG in my useage of the terminology?
Cratylus <admin> you kept sayin stuff that assumed ir == heat, which is not true
Cratylus <admin> the idea was to point out that yer source was not correct
Hermogenes <admin> heat is the term we assign to the perception of IR radiation, just as light is how we descripe the process of seeing photon particles interacting with our eyes, etc
Cratylus <admin> no
Hermogenes <admin> ah HA
Cratylus <admin> that also is not correct
Cratylus <admin> heat is not aa subjective experience
Cratylus <admin> it is aa measurable state of molecular motion
Hermogenes <admin> yer assuming my SOURCE is not correct, when in fact, it could very well be (and probably WAS)my INTERPRETATION of my source
Cratylus <admin> could be
Hermogenes <admin> wait, heat is not a what?
Cratylus <admin> heat is not aa subjective experience
Cratylus <admin> it is aa measurable state of molecular motion
Hermogenes <admin> riiiight, and light isNT?
Cratylus <admin> i dont know why u keep bringing light into this
Hermogenes <admin> i mean, not molecular motion, i mean objectively measurable
Cratylus <admin> im not concerned with light at the moment
Hermogenes <admin> because light n heat are on th same EM spectrum, aint they?
Cratylus <admin> whoa
<admin> Cratylus sits you down.
Cratylus <admin> you better cool that walnut off
Hermogenes <admin> fine fine, IR emmission and visible light emission are
Cratylus <admin> right
Cratylus <admin> so what?
Cratylus <admin> were talkin about heat
Hermogenes <admin> so...
Hermogenes <admin> uh...
Cratylus <admin> which is NOT on the em spectrum
Hermogenes <admin> so THERE!
Hermogenes <admin> WHAT?
Cratylus <admin> were talkin about heat
Cratylus <admin> which is NOT on the em spectrum
Hermogenes <admin> IR is NOT on the em spectrum?
Cratylus <admin> oh my FUCKING GOD
Hermogenes <admin> ok, NOW im confused
Cratylus <admin> are you pullin my leg or what?
Hermogenes <admin> i must be TOTALLY thinkin of somethin else
Hermogenes <admin> please, to define \"em spectrum\"
Cratylus <admin> ok im sorry if i sound condescending, but damn you deserve it
Hermogenes <admin> well excuuuUUUuuuuUUuuUuuuse ME for not clearly defineing all th terms from th onset
Cratylus <admin> when electromagnetic energy is radiated, its measured on what we call the em spectrum
Hermogenes <admin> ya?
Cratylus <admin> heat is NOT electromagnetic energy
Hermogenes <admin> but IR radiation is?
Cratylus <admin> but when something IS hot, it emits em waves
Cratylus <admin> somthing that IS hot emits IR
Cratylus <admin> but the heat itSELF is not the IR
Cratylus <admin> the heat is aa different kind of energy
Cratylus <admin> and though the IR waves MAY heat up another object...
Hermogenes <admin> isnt heat how we describe the perception oof said ir energy?
Cratylus <admin> this transfer of energy is not what you can call \"heat radiation\"
Cratylus <admin> NO DUDE NO
Cratylus <admin> BAD HERMOGENES
<admin> Hermogenes goes back to the page with the creepy kitty pic
Cratylus <admin> define heat please
Hermogenes <admin> mostly to look at the creepy kitty pic, that is
Cratylus <admin> i thought YOU were the scientist in the family
Hermogenes <admin> um, define heat?
Cratylus <admin> yes. define heat
Hermogenes <admin> the amount of hotness soemthing has?
Cratylus <admin> ok let me tell you what webster says heat is
<admin> Hermogenes notes there are over 20 definitions...
Hermogenes <admin> but, go on
Hermogenes <admin> meestor WEBSTER dude
Cratylus <admin> \"the energy associated with the random motions of molecules, atoms, or smaller structural units of which matter is compised\"
Hermogenes <admin> asSOCiated with
Cratylus <admin> notice there is no mention of electromagnetism
Cratylus <admin> heat, man, is this molecular motion
Cratylus <admin> nothing else
Cratylus <admin> kinetic energy
Hermogenes <admin> ok
<admin> Hermogenes snives.
Cratylus <admin> as it HAPPENS, this energy tends to emit some electromagnetic radiation
Hermogenes <admin> oooOOo, whatta coiINcidince
Cratylus <admin> but the em radiation is NOT heat
Hermogenes <admin> so...
Hermogenes <admin> yer sayin/....
Cratylus <admin> it is em radiation, like the color blue or xrays
Hermogenes <admin> the molecular motion emits the ir stuff?
Cratylus <admin> well, the heat energy does ya
Hermogenes <admin> heat energy?
Cratylus <admin> ya
Cratylus <admin> i dont know if the motion and the energy are the same thing
Cratylus <admin> but thats aa diff argument
<admin> Hermogenes feels sufficiently dumb now, thankyouverywelcome
Cratylus <admin> so, what im sayin is:
Cratylus <admin> when u warm yourself at aa roaring fire
Hermogenes <admin> ooo, dats toasty
Cratylus <admin> you are being warmed by \"heat radiation\"
Hermogenes <admin> \"heat radiation\"
Cratylus <admin> the heat, or rapidly moving air/fuele molecules, are speeding up YOUR molecules through contact
Hermogenes <admin> as in \"conduction, convection, RADIATION\"?
Hermogenes <admin> aaah
Cratylus <admin> LISTEN
Cratylus <admin> the heat, or rapidly moving air/fuele molecules, are speeding up YOUR molecules through contact
Cratylus <admin> it also happens that the fire MAY be causing your molecules to move faster ALSO through some IR radiation, but this amount of heating is insignificant
Hermogenes <admin> ok,
Hermogenes <admin> can i interject at this moment?
Cratylus <admin> sure
Hermogenes <admin> er, MAY i
Hermogenes <admin> the scientist dude ALSO said somethin about:
Cratylus <admin> get that fucking dude in here
Cratylus <admin> go get him
Cratylus <admin> ill wait
Hermogenes <admin> how the earths atomoshpere n crust n stuff arent warmed by particles from the sun hittin them...
Hermogenes <admin> but from the IR radiation
Cratylus <admin> ya i know
Cratylus <admin> go get him
Hermogenes <admin> he left
Cratylus <admin> FUCK
Hermogenes <admin> and like i need MORE people on my case
Cratylus <admin> seriously dude yer not getting it
Hermogenes <admin> OR, like i need a coworker gettin into nan argument with you
Hermogenes <admin> OH MY GOSH!
Cratylus <admin> for some reason yer unable to separate heat from infrared radiation
<admin> Hermogenes aGREES with you!
<admin> Hermogenes is NOT gettin it!
Cratylus <admin> for some reason yer unable to separate heat from infrared radiation
Hermogenes <admin> im sorry, man,i really am
Cratylus <admin> its like saying that because an airplane is flying, it IS flight
Hermogenes <admin> its just one of those cases...
Hermogenes <admin> where the one gear is not in place
Cratylus <admin> I'LL say
Hermogenes <admin> that ONE little 8-tooth gear has slipped
Cratylus <admin> well id like to state for the record that i dont think this is some subtle semantic difference
Cratylus <admin> this is aa fundamental difference in types of energy and radiation
Cratylus <admin> but we can come back to it later
Hermogenes <admin> your statement has been entered into the almighty records of whosrighteedness
Cratylus <admin> ehehehehehe
Hermogenes <admin> so, its been snowing up tehre?
Cratylus <admin> very briefly
Cratylus <admin> none left on the round
Try \"help index\" for a list of command types.
Hermogenes <admin> we havent gotten ANYthing
Cratylus <admin> musta got it
Cratylus <admin> the HEAT RADIATION musta got it, that is
Hermogenes <admin> its sposed to get up in th 50s morrow
Hermogenes <admin> NOOOOOOO!!!!!
Cratylus <admin> oooohhhhh wait aa minute
Hermogenes <admin> im seriously miffed bout this whole heat thing, btw
Hermogenes <admin> wha
Cratylus <admin> i get it now
Hermogenes <admin> do i wanna know?
Cratylus <admin> i think i see how youre looking at it
Cratylus <admin> maybe
Hermogenes <admin> very well could be
Cratylus <admin> ok
Hermogenes <admin> ive looked at things \"wrong\" before...
Cratylus <admin> tell me if this is what yer thinking:
Hermogenes <admin> k
Cratylus <admin> when heat causes radiation, thats \"heat radiation\"
Hermogenes <admin> mmmm....
Cratylus <admin> ok maybe not
Cratylus <admin> worth aa try
Hermogenes <admin> when an object is \"energized\" enough, it begins to radiate in the iR portion of the em spectrum
Cratylus <admin> yeah
Cratylus <admin> and thats what u mean by \"heat radiation\"?
Hermogenes <admin> energized usually means addding heat or electricity or SOMEthing to it
<admin> Cratylus nods.
Hermogenes <admin> well...
Hermogenes <admin> the IR radiation brought on is then perceived by our senses as \"heat\"
Cratylus <admin> ok
Cratylus <admin> do u mind if i drag this on aa bit longer?
Hermogenes <admin> if more energy is added, it then begins to transmit in the visible portion of the em spectrum, and we perceive this as ''light'
Cratylus <admin> i really am interested in understanding our difference here
Hermogenes <admin> as am i
Cratylus <admin> see, i dont think that the IR radiation heats up other things all that much
Hermogenes <admin> actually,
Cratylus <admin> when i put my hand up to aa light bulb, and its hot,
Cratylus <admin> i dont think its because of IR, although some IR is present
Hermogenes <admin> scientist dude sed that IR ardiation is actually quite effective in heating up stuff
Hermogenes <admin> heh, ardiation
Cratylus <admin> ok, but just bear with me aa moment
<admin> Hermogenes bears
Cratylus <admin> lets assume, for the pure sake of argument, that IR radiation does not exist
Hermogenes <admin> heh, sake of argument
Hermogenes <admin> ok, go on
Cratylus <admin> IF there is not IR radiation, could you still warm yourself by the fire?
Hermogenes <admin> uhhh.. yes
Cratylus <admin> by what process?
Hermogenes <admin> ummmm
Hermogenes <admin> light?
Cratylus <admin> no
Cratylus <admin> heat radiation
Cratylus <admin> see i think this is where we went in diff directions
Cratylus <admin> your brain is bent on seeing the word \"radiation\" in terms of particles or wavelengths
Hermogenes <admin> or, the act of transmitting particles or wavelenghths or whatever
Cratylus <admin> in the context of convection and conductance, this approach to the word \"radiation\" is not fruitful
Cratylus <admin> thats where we are having aa communication breakdown
Cratylus <admin> again for the sake of argument, lets assume there is no electromagnetic or particle radiation
<admin> Hermogenes likes wavelenghths n particles n stuff...
Cratylus <admin> again for the sake of argument, lets assume there is no electromagnetic or particle radiation
Hermogenes <admin> whata a dismal world
Cratylus <admin> the fireplace is radiating heat
Cratylus <admin> heat comes from it, hence, it \"radiates heat\"
Cratylus <admin> this is \"heat radiation\"
Hermogenes <admin> hmmm
Hermogenes <admin> in a loose definition, ok
Cratylus <admin> no
<admin> Hermogenes gasps in shock!
Cratylus <admin> that is not aa loose definition
Cratylus <admin> that is the definition of the word radiate
Cratylus <admin> \"to radiate\"
Hermogenes <admin> wait, is THAT all there is to \"heat transferrence by radiation\"?
Cratylus <admin> YESSSSSSS
Cratylus <admin> EXACTLY
Hermogenes <admin> heat comes from it, hence, it \"radiates heat\"
Hermogenes <admin> heat comes from it, hence, it \"radiates heat\"
Hermogenes <admin> heat comes from it, hence, it \"radiates heat\"
Hermogenes <admin> hmmmmm
Hermogenes <admin> ok
<admin> Hermogenes can accept that
Cratylus <admin> ok back to our dismal theoretical world
Hermogenes <admin> wait
Cratylus <admin> which contains no particle or em radiation
Hermogenes <admin> i thought we were done?
Cratylus <admin> oh
Cratylus <admin> ok
Cratylus <admin> so u see my point?
Hermogenes <admin> mmmm, sorta
Cratylus <admin> then we are not donme
Hermogenes <admin> i was making it way too more complicated than it needed to be?
Cratylus <admin> you were on aa different train track altogether
Hermogenes <admin> i was on one of those hovertrains
Hermogenes <admin> on magnetic rails
Cratylus <admin> btw the fireplace does not radiate heat through \"light\"
Hermogenes <admin> it dont?
Cratylus <admin> it radiates heat by contact with molecules which transmit the heat in the form of kinetic energy
Hermogenes <admin> howcome a black shirt gets hotter than a white shirt, then?
Cratylus <admin> in front of aa fire?
Cratylus <admin> ive never noticed any such thing
Cratylus <admin> in the sun, yeah sure
Hermogenes <admin> but i thought we already established that molecules dont HAFT bounce off eachother to transfer heat?
Hermogenes <admin> hmmm
<admin> Cratylus falls to the floor.
Hermogenes <admin> so, you think a black tshort and a white tshirt in a non-sun light source would not heat differntly?
Cratylus <admin> in front of aa fireplace, i dont think there would be aa noticeable differennce
Hermogenes <admin> im not tryin to argue, im tryin to follow a tangential tracxk for a sec
Cratylus <admin> i think with delicate instruments u WOULD detect aa diff
Hermogenes <admin> NOTCEABLE
Cratylus <admin> this is actually right on track dude
Cratylus <admin> i think with delicate instruments u WOULD detect aa diff
Hermogenes <admin> OH, suddenly im allowed to play on YOUR traintrack?
Hermogenes <admin> :)
Cratylus <admin> because IR radiation DOES in fact play aa role here
Cratylus <admin> but it is not as substantial as the brownian motion
Hermogenes <admin> uh uhuhuhu uuhuh
Hermogenes <admin> sorry
Cratylus <admin> this must be aa way-homer
Hermogenes <admin> a 'way-homer'?
Cratylus <admin> you'll get it on the way home
Hermogenes <admin> heh
Hermogenes <admin> is it really that much fun to argue with me?
Hermogenes <admin> or is it really a chore
Cratylus <admin> im honestly mystified by this
Hermogenes <admin> as am i
Cratylus <admin> i mean, im like...FASCINATED
Hermogenes <admin> and i wasnt asking sarcsatically or rhetorically
Cratylus <admin> this is such aa weird miscommunication
Hermogenes <admin> if it was fun to argue at me
Cratylus <admin> i dint mean it sarcastically
<admin> Hermogenes nods solemnly.
Cratylus <admin> im totally dead serious
Cratylus <admin> dead sexy, too
Hermogenes <admin> didnt think you WERE being scarcastic
Hermogenes <admin> ugh
Cratylus <admin> heh
Hermogenes <admin> just watched that last night
Cratylus <admin> man he is VILE
Hermogenes <admin> 'e leuks leyek a lit'l BEHbee
Hermogenes <admin> oye ET a behbee wunce
Hermogenes <admin> oye sed, OYEM th TOP a th fewd chayn!\"
Hermogenes <admin> GET! EN! MAH! BELLY!
Cratylus <admin> ok hang on aa sec dont say anything for aa minute, ok?
Cratylus <admin> ok thx
<admin> Hermogenes nods solemnly.
Hermogenes <admin> you ok there?
Cratylus <admin> ya i had to copy and paste this round
Hermogenes <admin> or... am i still not spossed to be sayin anything still?
Hermogenes <admin> heh
Cratylus <admin> this is just too crazy not to save for posterity
<admin> Hermogenes hopes he wont regret his conversation, but think he probably will
Cratylus <admin> heheh yes you definitely will
Cratylus <admin> well i HOPE you will
Hermogenes <admin> ill probably wind up being WRONG th whole time and lookin like an IDIOT fer it
Cratylus <admin> i hope yer gonna slap yer forehead and go HOLY FUCKING DUH!
Hermogenes <admin> see, th thing is...
Hermogenes <admin> its kinda hard to be wrong about something you dont know what yer talkin about in th FIRST place
Cratylus <admin> i guess
Cratylus <admin> ok
Cratylus <admin> i touch aa stove
Cratylus <admin> coalburning stove. black iron
Hermogenes <admin> like, some 5th grade makin assertions about calculus
Cratylus <admin> i put my hand right ON it
Hermogenes <admin> ya?
Cratylus <admin> my fickin hand is sizzlin n stuff, right
Cratylus <admin> this is happening not because of IR
<admin> Hermogenes nods solemnly.
Hermogenes <admin> but because of....
Cratylus <admin> see, if it was infrered radiation doin this, then why isnt the whole room in flames?
Cratylus <admin> why is it limited to RIGHT AT the stive?
Hermogenes <admin> uh...
Hermogenes <admin> dissipation?
Cratylus <admin> explain dissipation please
Hermogenes <admin> theres so much ari movin roun n stuff, the averagew molecular kinetic energy (motion) can be...uh...averaged out
Hermogenes <admin> over a wider area
Hermogenes <admin> n stuff
Hermogenes <admin> like, the inverse-squared law or something
Hermogenes <admin> YEAH, THATS it
Cratylus <admin> ok...so u agree its not infrared energy?
Hermogenes <admin> burning yer hand?
Cratylus <admin> right
Hermogenes <admin> its...
Hermogenes <admin> the convection of hot air molecules againts yer skin
Cratylus <admin> well, hot STOVE molecules, and it isnt \"convection\"
Hermogenes <admin> oooOOOOo
Hermogenes <admin> why NOT, mister fancee panse?
Cratylus <admin> whats happening is the surface of the stove is transferring heat to my hand
Hermogenes <admin> right
Cratylus <admin> the mechanics of this heat transfer is simple molecular contact
Hermogenes <admin> ok, its either the conduction of heat between the solid of the stove into te solid of yer hand...
Cratylus <admin> the stove molecules are moving real fast
Hermogenes <admin> ok, now wait
Cratylus <admin> and they make my hand molecules move real fast
Hermogenes <admin> so, theres MORE than JUST 3 ways of transferring heat?
Cratylus <admin> no
Hermogenes <admin> like in th xflies!
Cratylus <admin> do u agree with me so far?
Hermogenes <admin> so, tehre are JUST 3 ways?
Cratylus <admin> do u agree with me so far?
Hermogenes <admin> mostly, yah
Cratylus <admin> ok, what part do u disagree with?
Hermogenes <admin> hol on a sec...
Cratylus <admin> and get aa fucking scientist in here NOW
Hermogenes <admin> no, i had to TEND to work stuff
Hermogenes <admin> ya big meanie
Cratylus <admin> well if u see on, grab him
Cratylus <admin> or her
Hermogenes <admin> i wasnt goin off to find a scientist dude to be on my sdide or nuttin
Cratylus <admin> ok so what part did u not agree with?
Hermogenes <admin> grab her?
Cratylus <admin> ok so what part did u not agree with?
Hermogenes <admin> ok, but only cause you insisted...
Hermogenes <admin> i guess i just wanna establish that theres X ways of heat transferrance, and any examples involving heat transferrence would be put into one or more of these X categories
Cratylus <admin> ok well fuck the 3 categories
Cratylus <admin> do you agree or disagree with my statements?
Hermogenes <admin> YEAH
Hermogenes <admin> POWER TO THE PEOPLE!
Cratylus <admin> dude
Hermogenes <admin> fine
Hermogenes <admin> stove molecules, movin hand molecules
Cratylus <admin> right
Hermogenes <admin> i tells you what my FOOT molecules gunan be movin in a SECOND!
Cratylus <admin> now, if instead of touching the stove, i had held my had within half aa centimeter, it would scald but not burn. right?
Hermogenes <admin> whats the diff?
Hermogenes <admin> its gonna get hot
Cratylus <admin> it would take longer for my hand to be damaged and it wouldnt be black
Hermogenes <admin> scald, burn, sear, fry, etc
Hermogenes <admin> ok, agreed
Cratylus <admin> ok the first example was conductance
Hermogenes <admin> as long as yer not actually TOUCHING it
Hermogenes <admin> ok, 1st = conductance
Cratylus <admin> the second example was radiation
Hermogenes <admin> (with a wee bit of convection)
Hermogenes <admin> hmmmm
Cratylus <admin> with aa little convection but thats not important i guess
Cratylus <admin> heh ya
Hermogenes <admin> second isnt convection?
Cratylus <admin> no
Hermogenes <admin> sounds likeit...
Cratylus <admin> its primarily radiation
Cratylus <admin> because the stove is heating the air molecules
Cratylus <admin> and the air molecules are heating my hand
Cratylus <admin> the heat is being radiated from the surface of the stove, through the air, into my hand
Hermogenes <admin> but...
Cratylus <admin> wait
Cratylus <admin> IR is bad for your eyes right?
Hermogenes <admin> uh...
Hermogenes <admin> is it?
Cratylus <admin> hmmm maybe that uv
Cratylus <admin> ok never mind
Hermogenes <admin> uv, yes, deinately
Cratylus <admin> ok now
Hermogenes <admin> wait
Hermogenes <admin> lemme say why i think its convection
Cratylus <admin> in ADDITION to this molecular heat transfer
Cratylus <admin> no
Cratylus <admin> im sorry
Cratylus <admin> thats irrelevant and aa tangent
<admin> Hermogenes frowns.
Hermogenes <admin> ok, go on
Cratylus <admin> in ADDITION to this molecular heat transfer
Hermogenes <admin> you n yer up-arrow keys...
Cratylus <admin> there is ALSO infrared radiation from the stove
Cratylus <admin> but it is NOT the infrared radiation that burns my hand
Cratylus <admin> it is the direct molecular contact with rapidly moving molecules
Hermogenes <admin> cause the IR stuff aint got enough energy to do it, and its the molecular transferrence stuff thats burning it?
Cratylus <admin> exactly
Cratylus <admin> do you agree?
Hermogenes <admin> im not too sure that IR is all THAT low-level energy-wise, but ok...
Cratylus <admin> so now lets put that stove in outer space
Hermogenes <admin> outher... SPAAAAAACE
Cratylus <admin> (with its own fuel and oxy)
Hermogenes <admin> (of course)
Cratylus <admin> and im out there with the stove
Cratylus <admin> and i put my hand .5 centimeters from it
Hermogenes <admin> (with suit and air supply)
Cratylus <admin> will my hand be damaged?
Hermogenes <admin> um...
Cratylus <admin> bare hand
Hermogenes <admin> uh...
Hermogenes <admin> maybe?
Cratylus <admin> no
Hermogenes <admin> no
Cratylus <admin> i think its possible the stove may shed molecules
Hermogenes <admin> cause the molecules cant transfer the heat as efficiently or quickly
Cratylus <admin> these molecules might hit some of my hand molecules, but not enough to do damage
Cratylus <admin> negatron
Hermogenes <admin> cause theres a near-vaccuum enviroment
Cratylus <admin> they CAN transfer it efficiently and quickly
Hermogenes <admin> oh
Cratylus <admin> if i TOUCH them
Hermogenes <admin> BAD molecules
Cratylus <admin> if i touch the stove, will my hand be damaged?
Hermogenes <admin> yes
Cratylus <admin> right
Hermogenes <admin> BAD stove!
Cratylus <admin> ok, so in aa vacuum, i have to touch the stove to get burned, right?
Hermogenes <admin> PROBably
Cratylus <admin> oK then
Cratylus <admin> that is precisely my point
Cratylus <admin> the stove does not produce sufficient IR radiation to make any noticeable diff to my hand
Hermogenes <admin> k...
Cratylus <admin> only when there is molecule to molecule contact, can the stove do real temp change
Cratylus <admin> therefore, while the stove radiates infrared waves
Cratylus <admin> it does not radiate heat
Hermogenes <admin> funkay
Cratylus <admin> yer supposed to hear trumpets and flights of angels as the truth rolls in from the sky
Hermogenes <admin> just cause i accept it, dont mean i understand it...
Hermogenes <admin> not to bee TOO bog of a pain in th ass
Hermogenes <admin> big, even
Cratylus <admin> you WILL young hermogenes. oh yes. you WILL.
Hellmonger@Trilogy <intergossip> anybody ever fuck with doing Sun consoles on an oldsk00l portmaster 2?
Hermogenes <admin> my BRAIN hurts
Cratylus <admin> sorry
",]),"/domains/town/txt/shame/chapter03":1152072075,"/domains/town/txt/shame/chapter02":1152072075,"/domains/town/txt/shame/chapter01":1152072075,]),"/doc/cheats":(["object":"/domains/town/obj/cheatbook","title":"Untitled","items":([({"chapter 1","chapter one","1",}):"\"Basics\"
",({"chapter 3","chapter three","3",}):"\"Orcmaster quest\"
",({"chapter 5","chapter five","5",}):"\"Princess Daphne\"
",({"chapter 2","chapter two","2",}):"\"Orc Slayer\"
",({"chapter 1","chapter one","1",}):"\"Basics\"
",({"chapter 2","chapter two","2",}):"\"Orc Slayer\"
",({"chapter 2","chapter two","2",}):"\"The Town Well\"
",({"chapter 5","chapter five","5",}):"\"Cave Explorer II & The Postman\"
",({"chapter 2","chapter two","2",}):"\"Orcmaster quest\"
",({"chapter 4","chapter four","4",}):"\"The Town Well\"
",]),"/doc/cheats/chapter06":1585972635,"/doc/cheats/chapter05":1585970645,"/doc/cheats/chapter04":1585969898,"/doc/cheats/chapter03":1585969904,"/doc/cheats/chapter02":1585964075,"/doc/cheats/chapter01":1585957973,"index":" Untitled
Chapter 1: \"Basics\"
Chapter 2: \"The Town Well\"
Chapter 3: \"Orcmaster quest\"
Chapter 4: \"The Town Well\"
Chapter 5: \"Cave Explorer II & The Postman\"
","reads":([({"chapter 3","chapter three","3",}):"chapter 3 \"Orcmaster quest\"
This one is a bit easier. Again, we can get this done without actual combat.
So you're now a mage standing in the Mage's Guild. Let's head over to Roshd
Burlyneck, the Fighter guildmaster who is looking for his missing helmet. Remember
the helmet you got off the dead orc shaman? Exactly. Two birds with one stone.
1n, 1e, 1n, 4w, 26n, enter cave
Remember this place? We're goig to teleport to a different location now:
cast light
look
dial tower
enter gate
1d, 1s, 1n
You're now in the Fighter's Guild.
give helmet to roshd
Now what on earth just happened? What's all this weird text?
That's a language you do not understand, Tangetto. It is the language of the Orcs.
Roshd just told you thanks dude, if you want to join the Fighter's guild, now you can.
Now you know how yo join the Fighter's
guild if you want to. Since you're already a human mage that doesn't help you much now,
but if you want to play through again later with a new character that's, say, an orc,
you now know how to get hooked up.
That's not to say that humans can't be fighters by the way. I'm just saying that
*statistically* it may be more rewarding to choose a bulkier race for that. You
do you, tho, breh.
",({"chapter 2","chapter two","2",}):"chapter 2 \"The Town Well\"
This one is really tricky. There are wheels to turn, levers to pull,
grates to open, and as a weak little human you can't do it all yourself...
or can you? This isn't called a cheat book for nothing.
All righty we're going to call upon some of the stuff you've already done.
We need to head back to the town and deposit any cash you picked up and
sell any heavy armor you may have that you don't really need (like the
leather boots and jacket from the mansion or chainmail from the church)
and then deposit that money too. Get in the habit of watching your
encumbrance.
It's especially important now because you'e about to go fetch a bunch of
heavy equipment to get this quest done.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Orcmaster quest\"
This one is a bit easier. Again, we can get this done without actual combat.
So you're now a mage standing in the Mage's Guild. Let's head over to Roshd
Burlyneck, the Fighter guildmaster who is looking for his missing helmet. Remember
the helmet you got off the dead orc shaman? Exactly. Two birds with one stone.
1n, 1e, 1n, 4w, 26n, enter cave
Remember this place? We're goig to teleport to a different location now:
cast light
look
dial tower
enter gate
1d, 1s, 1n
You're now in the Fighter's Guild.
give helmet to roshd
Now what on earth just happened? What's all this weird text?
That's a language you do not understand, Tangetto. It is the language of the Orcs.
Roshd just told you thanks dude, if you want to join the Fighter's guild, now you can.
Now you know how yo join the Fighter's
guild if you want to. Since you're already a human mage that doesn't help you much now,
but if you want to play through again later with a new character that's, say, an orc,
you now know how to get hooked up.
That's not to say that humans can't be fighters by the way. I'm just saying that
*statistically* it may be more rewarding to choose a bulkier race for that. You
do you, tho, breh.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Orc Slayer\"
You should now be standing in the secret cave in the woods, having
just completed the Cave Explorer I quest. Now things get complicated,
because the next quest is Orc Slayer, and as a weak human there is
absolutely no way you can fight your way through this quest. You will
have to sneak around, so pay attention to all the details here.
The first thing you need to do is find a way to become invisible so
that you can sneak by some orcs later. There happens to be an invisibility
device available to you, but it is far away. You can't walk to it. You have
to teleport to it. The good news is that there is a teleportation device
in this secret cave you're standing in! It's called a stargate and you
can dial different destinations on it to reach other stargates. Type:
dial campus lab
enter gate
See doctor Kleiner? He is wearing a badge you need, but you can't kill
him for it, he will escape if you try. So leave him alone for now. We need
to get you some other loot.
1s, 1e, 1n, 3e, open door, 1s
You are now in the Virtual Campus admissions room, and there is a robot
here named Jenny who will give you an introduction to the game, but more
importantly, she will give you some valuable loot you can sell at the
campus store. You'll see why you need to get this loot, just trust me
for now. type:
activate bot
And when you're done reading what she says, type:
next tip
Until she's done giving you tips. At the end she will give you a hat and
some other goodies. When you have the loot, go:
1n, 2e, 1n
To the campus bookstore, and type:
sell hat to kim
This should get you a bunch of dollars. Now type:
buy coat from kim
And she will give you a lab coat. You see, Dr. Kleiner is looking for
his lab coat, and you will give this to him. Go back to Kleiner:
1s, 5w, 1s, 1w, 1n
give coat to kleiner
Note that while Kleiner is putting on the coat, he drops the badge! Grab
it quick:
get badge
wear badge
Now you can enter the room with the invisibility device. You hadn't forgot
about that, had you?
2s
get computer from bench
get module from bench
wear computer
activate computer
open computer
install module in computer
close computer
Now you're ready to do some serious sneaking around. You're not
invisible yet, but we're going to hold off on that for now. Being invisible
uses up your stamina points, so we're going to save it for when you
really need it. Let's get back to the main village and get some other
stuff you really need.
2n
dial cave
enter gate
26s, 2e, 1s
Now, here you may run into a problem. If you went up a few levels already,
you won't be able to get past this guard, because he only ignores total
newbies. So you'll have to sneak by him:
cloak
1s
decloak
Remember to decloak. If you walk around cloaked (invisible) all the time
you will run out of stamina and that's no fun. Now, let's look around:
look
look at window
1w, 1n
get all
1s, 1e
drop ladder
The mansion door is locked so the only way in is to:
climb ladder
You're now in the old abandoned mansion. Time to grab some stuff you'll need:
1w
search
You should now see a complex key. You'll need it for opening a safe later.
get key
1e, 2s, 1d, 2n
cloak
1e
Here you see a thief that has been using the abandoned mansion as his
own home. If he sees you he will attack, so you really should sneak by
him instead. Notice the rug here? Doesn't it seem strange that it was
so obvious? Is anything underneath it?
move rug
open door
1d
turn on flashlight
look
open chest
get all from chest
get all from bag
1u, 1w
decloak
Ok, so let's review what we've done so far. We found an invisibility device,
used it to get past a gate guard, used a ladder to climb into a mansion,
found a key, snuck past a thief to find another key, and now we have
a bunch of loot and money, and you're possibly at level 5 now!
score
Pretty cool! But we haven't solved the orcslayer quest yet, so let's keep
going.
2s, u
unlock door with silver key
open door
1s
Ok, notice some other obvious things here? A bed and a wardrobe? We'll
get to the wardrobe later, for now let's see if there's anything behind this
big bed:
move bed
Well well well! Let's explore further:
enter passageway
unlock safe with complex key
open safe
get all from safe
drop complex key
Goodness gracious! That's quite a lot of good loot! A grenade?!? Well don't
use it yet, this is exactly what we will need to solve the orc slayer
quest. We're almost there.
Now, at this point you may be carrying too much loot. You're not very big and
strong yet, and you can't arry all your loot and money around all the time.
Let's get to the bank and deposit some of this cash.
4n, 1d, 2n, 2e, 3n, 1e
This is the bank. Let's start by opening a bank account:
request account from zoe
deposit all
There! Now you have money stashed away in case things get tough, and now
you can carry more stuff. Let's go sell some of the loot you picked up from the
mansion to make more room in your inventory.
1w, 3s, 1e, 1n
sell plastic flashlight to otik
You can now sell any of the gear you got that you don't think you need. Be careful..
if you want to buy something back it'll cost you more than you sold it for. Capitalism!
All righty, now we're ready to confront those orcs...sort of.
Here's where things get tricky. We'll need to sneak by a bunch of orcs,
but we don't want to waste stamina doing it, so we want to be efficient. We
also want to make sure that we don't make mistakes because we'll need to
move quickly.
First, let's get where we're going:
1s, 5w, 1sw
cloak
1w, 1n, 1w
It's a good thing you're cloaked because you would have been attacked
by all those orcs you snuck past. You're now in the temple of the orc
shaman. If it's too dark to see, turn on the maglite:
turn on maglite
Now we're going to plan things out to make sure we don't make any
mistakes. The game has a way for you to bind keys to specific commands.
example type:
alias 1 smile
Now if you type the number 1 and hit enter, the game will act as if you
typed out the command \"smile\". This is important because there are
some commands that need to be typed quickly but cannot be misspelled.
Using keyboard aliases helps in these time-sensitive situations.
We're about to have a time sensitive situation. Make some aliases:
alias 1 pull pin on grenade
alias 2 drop grenade
alias 3 go east
Make sure you have those aliases entered correctly. Especially make sure you
made number 3 be go east, not the letter e, because e is itself an alias and
aliases can't refer to aliases. Now go ahead:
1
2
3
And wait 10 seconds. Don't go back west too soon or you'll be in the room
when the grenade goes off! To be sure it's safe, try to look into the
next room:
peer west
Is the shaman blown up? Good, go back and get that sword from him.
get sword from corpse
get helmet from corpse
You don't need the helmet right this minute, but you will later, so get
it if you can. Now let's go back to the church:
1e, 1s, 1e
decloak
1ne, 3e, 1n
All righty, we're almost done! Did you notice that the church has an
elevator? Let's find out what's in the basement.
push button
1w
push b button (then wait until the elevator stops)
1e
This is Leo the archwizard. Don't worry, he's not named after anyone you
know, he's been around a long long time. See how he's complaining he lost
the orcslayer? Take a look at the sword you got from the shaman.
exa sword
Interesting huh? What if this is what Leo is looking for?
give sword to leo
Ta-daa! Now you have solved the orc slayer quest, and Herkimer will
allow you to join the mages guild:
push button
1w
push 1 button (then wait until the elevator stops)
1e
1s, 1e, 1s, 1w, 1s
ask herkimer to join
ask herkimer to teach whip
ask herkimer to teach buffer
ask herkimer to teach missile
ask herkimer to teach meditate
ask herkimer to teach light
And now try out some of your new spells:
cast whip
cast buffer
Congratulations, mage. You may now be ready for trickier tests of your mettle.
",({"chapter 5","chapter five","5",}):"chapter 5 \"Cave Explorer II & The Postman\"
These two are related. The letter you need to give to Roshd is hidden
in the treasure chest of the Orc captain you need to slay.
Lat's assume we're starting this from Radagast's training room because
you didn't listen to me in the last chapter and you went there to do
magic training. All righty, let's head to the cave where the Orc
invasion force is gathering in preparation to destroy the town above.
1d, 1n, 4w
We are now on the humpbacked bridge between the town and the forest.
Below this bridge is a nasty troll and the cave where the bad guy orcs
live. We will do some more sneaking here.
cloak
1d
See the troll? Nasty.
2e, 1ne, 1nw, 1n, 1e, 2n
open door
1n
You're here. The orc commander. Notice how low your stamina is...all that
cloaked sneaking really sapped your strength. If you were higher level
I would have suggested fighting your way here and then slamming
this guy with your magic whip and fireballs while shielding yourself
with magic buffer after buffer. This is a no-foolin' boss level guy, and
when you start fighting, he closes and locks the door behind you. Unless
you've figured out the portal device or the omni or some other way to
magically retreat if things get tenuous, you are in it to win it.
So, I'm not going to give you cheats on winning that. There are no cheats.
You'll have to level up and grind up until you can go toe to toe with the Orc
Commander.
But, here's how you can complete the Postman quest:
get letter from table
2s, 1w, 1s, 1se, 1sw, 2w, 1u
decloak
There's an excellent chance you ran out of stamina along the way there.
Make sure that doesn't happen. Becoming visible in the lair of an orc
invasion force is not a good idea if you're not loaded for bear.
One way to speed up your recovery is to cast a spell that helps center you:
cast meditate
All right, let's head to Roshd:
1w, 26n
enter cave
dial tower
enter gate
1d, 1s, 1w, 1n
give letter to roshd
If you tried to read it, it's Tangetto and you couldn't make heads or tails of
it. It's some dumb stuff about how humans are savages and the orc outpost
is only barely hanging on and I love you so much blah blah. Anyway hand it over
and you get your points.
",({"chapter 1","chapter one","1",}):"chapter 1 \"Basics\"
The first thing to do is to pick a decent character. The best
all-around choice for a beginner is a human mage, so for the
purposes of these cheat sheets, pick human when you create your character.
Once your character is created, begin the game by typing: enter town
You should now be in the main village of the game, where you can find
shops, pubs, a bank, and most everything you'll need. Get familiar here
with the basic commands:
look
look at tower
go north
go south
smile
You can also type them in a shortened way:
l
l tower
n
s
smile
Now, learn a little about yourself. Type:
score
stat
(Note that you will have to hit <enter> to get to the second page of the
stat command.)
These commands tell you some things about yourself, like how much food you've
eaten, what skills you have and how good you are at them, and other
attributes, like how strong you are how smart you are, etc.
In the game you will need to do some fighting against monsters, and these
pieces of information will help you decide the best way to fight which
enemies. For now, since you've chosen human, you are probably smart but
not very strong. Let's try to fix that, because fights usually go
better if you are strong.
Every new player has 15 customization points when they join (this only
happens once), so make yourself stronger by adding customization
points to your strength, for example:
customize strength 5
If your intelligence is below 50, you definitely need to increase it too,
if you're going to be a mage. You usually want a strength of at least 30 and
an intelligence above 50, preferably above 60, for casting spells.
Ok, next, let's get you equipped for exploring. If you're lucky, someone has
left stuff in the church bin. Go to the church:
go west, go north (or w for west, and n for north)
From this point, directions will be given like this:
1w, 1n
Meaning, in this example, go west once, go north once.
Now:
look in bin
Hopefully there is a bunch of stuff in there but if someone else got
there first, you will need to get equipped on your own, without the
church's charity. For the purpose of this guide, we'll assume you didn't
find anything there.
Let's get started with your first quest: Cave Explorer I
Go from the church to the Adventurers' Guild:
1s, 2e, 1s
This is where you go to get info on questing and other stuff. There is
a charity bin here too, check it for helpful items. The important thing
to do here, though, is:
read list
This tells you the list of quests available. Did you see Cave Explorer I?
We're going to go solve that one right now!
1n, 5w, 26n, enter cave
You'll notice that you walked through a big forest just then. Don't try
to explore the forest yet, you could get lost. Just go straight north,
and go fast, because if you accidentally run into an orc, you do *not*
want to hang around and wait for him to start hitting you.
If you goof up here and do get killed by an orc, well, we might as well talk
about death now. Death takes away a few of your experience points, which
isn't good because you need them to go up in levels. Definitely avoid
death. You can avoid losing those experience points by being resurrected,
but that takes getting the help of another player and we can discuss that
another time.
For now, just know that if you get killed, you need to: regenerate
And you will rematerialize in the start room.
Anyway, hopefully you did not die and you are now inside the secret cave
in the forest, and you have completed your very first quest. That was
easy, right? You might even have automatically advanced a level or
two. Congratulations! The next quest will be harder, and it is the Orc
Slayer quest.
By the way, if that room was too dark to see, come back later with a flashlight
or a torch. There is interesting writing on the door, and on the wall. And,
of course, a powerful and convenient teleportation device sitting right
there on the floor.
",({"chapter 5","chapter five","5",}):"chapter 5 \"Princess Daphne\"
This quest is both very easy and very hard. In any case it is very simple.
Princess Daphne is the name of a very valuable diamond which is on sale in the
Campus Bookstore...the same place you bought a lab coat for Kleiner. The way
to solve this quest is to scavenge and scrounge, making and finding coins,
and then convering all that money into 50,000 dollars at the bank. Then you
go to the campus bookstore, buy the diamond, then go to Dirk at the
Adventurer's Hall and give him the diamond.
Easy in principle, but it will take some grinding and exploring to get it
done. I suggest you go to Radagast and use the training ponts you earned
with all this leveling up and get trained up on your mage levels. Radagast
is a trainer and is one room up from Dirk in the Adventurer's Hall. Don't
bother asking Herkimer to train you, he doesn't know how. Go to Radagast.
Protip: You train better if you're smarter. If you've found a magic
pipe somewhere, guess what, the magic is that you're slightly smarter
if you smoke it. If you have it, find (or buy) some matches, then
strike match
light pipe with match
smoke pipe
Pipe or not, to train with Radagast:
ask radagast to train magic attack
ask radagast to train magic defense
ask radagast to train conjuring
Don't do that right now, though. There is a drowned cave troll we want to take
advantage of, and if you dawdle too long he may respawn.
",({"chapter 2","chapter two","2",}):"chapter 2 \"Orc Slayer\"
You should now be standing in the secret cave in the woods, having
just completed the Cave Explorer I quest. Now things get complicated,
because the next quest is Orc Slayer, and as a weak human there is
absolutely no way you can fight your way through this quest. You will
have to sneak around, so pay attention to all the details here.
The first thing you need to do is find a way to become invisible so
that you can sneak by some orcs later. There happens to be an invisibility
device available to you, but it is far away. You can't walk to it. You have
to teleport to it. The good news is that there is a teleportation device
in this secret cave you're standing in! It's called a stargate and you
can dial different destinations on it to reach other stargates. Type:
dial campus lab
enter gate
See doctor Kleiner? He is wearing a badge you need, but you can't kill
him for it, he will escape if you try. So leave him alone for now. We need
to get you some other loot.
1s, 1e, 1n, 3e, open door, 1s
You are now in the Virtual Campus admissions room, and there is a robot
here named Jenny who will give you an introduction to the game, but more
importantly, she will give you some valuable loot you can sell at the
campus store. You'll see why you need to get this loot, just trust me
for now. type:
activate bot
And when you're done reading what she says, type:
next tip
Until she's done giving you tips. At the end she will give you a hat and
some other goodies. When you have the loot, go:
1n, 2e, 1n
To the campus bookstore, and type:
sell hat to kim
This should get you a bunch of dollars. Now type:
buy coat from kim
And she will give you a lab coat. You see, Dr. Kleiner is looking for
his lab coat, and you will give this to him. Go back to Kleiner:
1s, 5w, 1s, 1w, 1n
give coat to kleiner
Note that while Kleiner is putting on the coat, he drops the badge! Grab
it quick:
get badge
wear badge
Now you can enter the room with the invisibility device. You hadn't forgot
about that, had you?
2s
get computer from bench
get module from bench
wear computer
activate computer
open computer
install module in computer
close computer
Now you're ready to do some serious sneaking around. You're not
invisible yet, but we're going to hold off on that for now. Being invisible
uses up your stamina points, so we're going to save it for when you
really need it. Let's get back to the main village and get some other
stuff you really need.
2n
dial cave
enter gate
26s, 2e, 1s
Now, here you may run into a problem. If you went up a few levels already,
you won't be able to get past this guard, because he only ignores total
newbies. So you'll have to sneak by him:
cloak
1s
decloak
Remember to decloak. If you walk around cloaked (invisible) all the time
you will run out of stamina and that's no fun. Now, let's look around:
look
look at window
1w, 1n
get all
1s, 1e
drop ladder
The mansion door is locked so the only way in is to:
climb ladder
You're now in the old abandoned mansion. Time to grab some stuff you'll need:
1w
search
You should now see a complex key. You'll need it for opening a safe later.
get key
1e, 2s, 1d, 2n
cloak
1e
Here you see a thief that has been using the abandoned mansion as his
own home. If he sees you he will attack, so you really should sneak by
him instead. Notice the rug here? Doesn't it seem strange that it was
so obvious? Is anything underneath it?
move rug
open door
1d
turn on flashlight
look
open chest
get all from chest
get all from bag
1u, 1w
decloak
Ok, so let's review what we've done so far. We found an invisibility device,
used it to get past a gate guard, used a ladder to climb into a mansion,
found a key, snuck past a thief to find another key, and now we have
a bunch of loot and money, and you're possibly at level 5 now!
score
Pretty cool! But we haven't solved the orcslayer quest yet, so let's keep
going.
2s, u
unlock door with silver key
open door
1s
Ok, notice some other obvious things here? A bed and a wardrobe? We'll
get to the wardrobe later, for now let's see if there's anything behind this
big bed:
move bed
Well well well! Let's explore further:
enter passageway
unlock safe with complex key
open safe
get all from safe
drop complex key
Goodness gracious! That's quite a lot of good loot! A grenade?!? Well don't
use it yet, this is exactly what we will need to solve the orc slayer
quest. We're almost there. Now, you don't need the plastic flashlight
since you got a big maglite flashlight from the secret chamber downstairs.
drop plastic flashlight
4n, 1d, 2n, 2w, 1sw
Ok here's where things get tricky. We'll need to sneak by a bunch of orcs,
but we don't want to waste stamina doing it, so we want to be efficient. We
also want to make sure that we don't make mistakes because we'll need to
move quickly.
First, let's get where we're going:
cloak
1w, 1n, 1w
It's a good thing you're cloaked because you would have been attacked
by all those orcs you snuck past. You're now in the temple of the orc
shaman. If it's too dark to see, turn on the maglite:
turn on maglite
Now we're going to plan things out to make sure we don't make any
mistakes. The game has a way for you to bind keys to specific commands.
For example type:
alias 1 smile
Now if you type the number 1 and hit enter, the game will act as if you
typed out the command \"smile\". This is important because there are
some commands that need to be typed quickly but cannot be misspelled.
Using keyboard aliases helps in these time-sensitive situations.
We're about to have a time sensitive situation. Make some aliases:
alias 1 pull pin on grenade
alias 2 drop grenade
alias 3 go east
Make sure you have those aliases entered correctly. Especially make sure you
made number 3 be go east, not the letter e, because e is itself an alias and
aliases can't refer to aliases. Now go ahead:
1
2
3
And wait 10 seconds. Don't go back west too soon or you'll be in the room
when the grenade goes off! To be sure it's safe, try to look into the
next room:
peer west
Is the shaman blown up? Good, go back and get that sword from him.
get sword from corpse
get helmet from corpse
You don't need the helmet right this minute, but you will later, so get
it if you can. Now let's go back to the church:
1e, 1s, 1e
decloak
1ne, 3e, 1n
All righty, we're almost done! Did you notice that the church has an
elevator? Let's find out what's in the basement.
push button
1w
push b button
1e
This is Leo the archwizard. Don't worry, he's not named after anyone you
know, he's been around a long long time. See how he's complaining he lost
the orcslayer? Take a look at the sword you got from the shaman.
exa sword
Interesting huh? What if this is what Leo is looking for?
give sword to leo
Ta-daa! Now you have solved the orc slayer quest, and Herkimer will
allow you to join the mages guild:
push button
1w
push 1 button
1e
1s, 1e, 1s, 1w, 1s
ask herkimer to join
ask herkimer to teach whip
ask herkimer to teach buffer
ask herkimer to teach missile
ask herkimer to teach meditate
ask herkimer to teach light
And now try out some of your new spells:
cast whip
cast buffer
",({"chapter 1","chapter one","1",}):"chapter 1 \"Basics\"
The first thing to do is to pick a decent character. The best
all-around choice for a beginner is a human mage, so for the
purposes of these cheat sheets, pick human when you create your character.
Once your character is created, begin the game by typing: enter town
You should now be in the main village of the game, where you can find
shops, pubs, a bank, and most everything you'll need. Get familiar here
with the basic commands:
look
look at tower
go north
go south
smile
You can also type them in a shortened way:
l
l tower
n
s
smile
Now, learn a little about yourself. Type:
score
stat
(Note that you will have to hit <enter> to get to the second page of the
stat command.)
These commands tell you some things about yourself, like how much food you've
eaten, what skills you have and how good you are at them, and other
attributes, like how strong you are how smart you are, etc.
In the game you will need to do some fighting against monsters, and these
pieces of information will help you decide the best way to fight which
enemies. For now, since you've chosen human, you are probably smart but
not very strong. Let's try to fix that, because fights usually go
better if you are strong.
Every new player has 15 customization points when they join (this only
happens once), so make yourself stronger by adding customization
points to your strength, for example:
customize strength 5
If your intelligence is below 50, you definitely need to increase it too,
if you're going to be a mage. You usually want a strength of at least 30 and
an intelligence above 50, preferably above 60, for casting spells.
Ok, next, let's get you equipped for exploring. If you're lucky, someone has
left stuff in the church bin. Go to the church:
go west, go north
From this point, directions will be given like this:
1w, 1n
Meaning, in this example, go west once, go north once.
Now:
look in bin
Hopefully there is a bunch of stuff in there but if someone else got
there first, you will need to get equipped on your own, without the
church's charity. For the purpose of this guide, we'll assume you didn't
find anything there.
Let's get started with your first quest: Cave Explorer I
Go from the church to the Adventurers' Guild:
1s, 2e, 1s
This is where you go to get info on questing and other stuff. There is
a charity bin here too, check it for helpful items. The important thing
to do here, though, is:
read list
This tells you the list of quests available. Did you see Cave Explorer I?
We're going to go solve that one right now!
1n, 5w, 26n, enter cave
You'll notice that you walked through a big forest just then. Don't try
to explore the forest yet, you could get lost. Just go straight north,
and go fast, because if you accidentally run into an orc, you do *not*
want to hang around and wait for him to start hitting you.
If you goof up here and do get killed by an orc, well, we might as well talk
about death now. Death takes away a few of your experience points, which
isn't good because you need them to go up in levels. Definitely avoid
death. You can avoid losing those experience points by being resurrected,
but that takes getting the help of another player and we can discuss that
another time.
For now, just know that if you get killed, you need to: regenerate
And you will rematerialize in the start room.
Anyway, hopefully you did not die and you are now inside the secret cave
in the forest, and you have completed your very first quest. That was
easy, right? You might even have automatically advanced a level or
two. Congratulations! The next quest will be harder, and it is the Orc
Slayer quest.
",({"chapter 4","chapter four","4",}):"chapter 4 \"The Town Well\"
This one is really tricky. There are wheels to turn, levers to pull,
grates to open, and as a weak little human you can't do it all yourself...
or can you? This isn't called a cheat book for nothing.
All righty we're going to call upon some of the stuff you've already done.
We need to head back to the town and deposit any cash you picked up and
sell any heavy armor you may have that you don't really need (like the
leather boots and jacket from the mansion or chainmail from the church)
and then deposit that money too. Get in the habit of watching your
encumbrance. Keep that weird gray amulet if you found it in the church, though.
And that access badge from Kleiner!
It's especially important now because you'e about to go fetch a bunch of
heavy equipment to get this quest done.
So if you're still at Roshd, head back to town:
1s, 1e, 1n, 1u
dial cave
enter gate
out
26s, 5e, 1n
sell blue jeans
sell white shirt (etc)
1s, 1w, 3n, 1e
deposit all
Ok now back to the Campus lab
1w, 3s, 4w, 26n
enter cave
dial campus lab
enter gate
2s
get all from bench
Yeah dude. A rocket pack and a portal generator. Sweet. Save those for a little later.
2n
dial stargate lab
enter gate
1n, 1e
What? What is this? A mech? What? Yes indeed. There should be a landstrider mech
here...looking kind of like ED-209 from Robocop but with a seat you can get into.
give badge to mech
enter mech
drive west
drive south
direct mech to dial campus lab
direct mech to enter gate
drive south
drive south
get badge
direct mech to enter portal
cast light
You're in an underground tunnel that has a very heavy door that you can't open
yourself if you're a puny human, so you're goig to have to get the mech to
open it for you, once you get the key. Let's leave the mech here for now.
Make sure you get that badge back or you won't be able to get back here.
1n, 1e, 1n, 2e
open door
cast light
1s, 1d, 1w
push wall
2w, 1s
open hatch
1d
get key from table
1u, 1n, 4e, 1u, 1n, 2w, 1s, 1w, 1s
enter portal
cast light
unlock door with key
enter mech
direct mech to open door
drive west
drive west
direct mech to open grate
drive east
drive east
direct mech to enter portal
exit
All right so we've unlocked that super heavy door and had the mech open it,
then we had the mech open the super heavy grate. There are other ways to solve
this problem, and they involve killing a human and then making it into a zombie.
If you want to try that solution (or the mech or portal are not available), read the
player hints book and that will guide you through. Otherwise, this method is the
quick and dirty. Now we need to open up the water wheel so that water can start
flooding the overhead chamber...but we also want to wear some gear that will
let us breathe underwater in case we don't type fast enough.
Remember all that crud you grabbed from the lab table earlier? One of those things
was an A98 breathing device. This will help. If you don't have it handy, just be sure
you can type fast. The next steps will flood the rooms you are in, and as a human
you are not super great at breathing water.
enter portal
2w
wear a98
2e
enter portal
remove a98
Ok! What's happening now is that a bunch of water is flooding the rooms you were
in. It's flooding upwards into a chamber near the town well. This will take a few
minutes. Once that's done, we'll head over to the well and open a door that lets
the water into the well, solving the quest.
Another thing the water is doing is flooding along the corridor you were in, and
filling up a locked-up room with water. This is a really really good thing, because
that room has a cave troll that you need to be dead for a different quest, and it is
much easier to have it die from drowning than to have to kill it with combat.
All right, let's head up to the well.
2n
dial cave
enter gate
out
26s, 4e, 1s
enter well
Ok here we are. Has it been a few minutes? Maybe 5 or so? Let's go ahead and open
that flood door:
pull lever
If that doesn't work, push the lever and wait some more and try again. If that still
doesn't work, you may need to try the zombie solution.
",]),"/doc/cheats/.chapter04.swp":1585967320,]),])
|