mud/lib/save/books.o
2020-09-06 05:43:07 -07:00

3 lines
1.5 MiB
Raw Blame History

#/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,]),])