Spec2 Docs: Parte the First To properly explain Spec2, let me first start with a description of Spec1. Spec1 is my name for the wide variety of "special procedures" or bits of C code compiled into the MUD to enhance the game. These special procedures were set up in an event-driven manner, that is they would only be enabled when a player typed a command, walked into a room, and so forth. These little code snippets gave birth to Sidana the Schoolmistress, the subway system, the ever-popular "combine the spheres" trick in the Crystal Maze, and so on. The problem with Spec1 was two-fold. First, it required a knowledge of the structural organization of the MUD code and a good ability with C. If you didn't have this knowledge or ability, it was very easy to write a special procedure that would crash the MUD. Second, it required the MUD to be shut down every time a proc was changed. This made Spec1 accessible to pretty much just me. That's where Spec2 comes in. Spec2 is the programming language I developed to make the code more accessible to the average, ordinary builder. The center of Spec2 is what I call the container-method interface. Let's start the discussion of the container-method interface with variables. There are (at present) seven kinds of variables: boolean - may be "true" or "false" integer - may be an integer from roughly -2 billion to +2 billion flag - a string of bits such as room flags or action flags on a mobile string - a character array that is at most 65534 bytes long character - a mobile or a player object - an item room - a room It is possible to declare a variable in any one of these types (more on that later). Let's pretend for a moment that we have done this and created two integer variables called "i" and "j". Now, i and j, as integer variables (containers) have a number of methods associated with them. A method is just a fancy name for a function, which is just a piece of code. For instance, all integers have a method called "op_plus" used for adding numbers together. i.op_plus is a function that takes an integer as an argument and returns an integer. To clarify, i.op_plus(j) returns "i+j", the sum of the integers stored in i and j. Of course, if we want to add j to i and store the result in i, we'll want to use another integer method "op_assign". i.op_assign takes an integer as an argument and sets the value of i to be the value of the argument. Let's put it all together: i.op_assign(i.op_plus(j)) Look, it's Spec2! That's a line of code that actually compiles, that is, if you've declared i and j beforehand (once again, more later). You can see that this container-method interface is a bit of a pain. Imagine if you wanted to write something that added a bunch of integers together. i.op_assign(i.op_plus(j.op_plus(k.op_plus(l... etc... That's messy. So, Spec2 has built-in operators that replace these methods in many cases. For instance "i+j" turns into "i.op_plus(j)", and "i=j" turns into "i.op_assign(k)". So, instead of i.op_assign(i.op_plus(j)), we can write "i=i+j". In addition to variables, Spec2 comes equipped with a number of constants. For instance, "1" is an integer constant whose value is 1. So, if I want to add 1 to the integer i, I can write "i = i + 1". For those of you who know any programming language, this should look familiar. Let's consider now the boolean variable type. Booleans may be either "true" or "false". The integer method "op_lt" takes an integer as an argument and returns a boolean. The operator "<" replaces "op_lt", so if I were to put "5 < 10", it would return a "true", and if I were to put "5 < 1", it would return a "false". You may have noticed that I've been referring to lines of code. This is not accidental. In Spec2, the return character is important. Returns separate different lines of code. So, you may write this: j = j + k i = i + j But you may NOT write this: j = j + k i = i + j Different statements must be on different lines. In addition to the container-method interface, there are some special words that the system uses: declare [], ... declare integer i This declares an integer named "i". declare integer i = 0 This declares an integer named "i" and sets its value to zero. declare integer i = 0, j = 2, k = 3 This declares integers "i", "j", and "k" and sets their values to 0, 2, and 3, respectively. if / else [] / endif "if/else/endif" statements allow you to choose what code is executed. if (i < 0) i = 0 endif This chunk of code will set i to 0 if it's less than zero. if (i < 0) i = 0 else i = 1 endif This will also set i to 0 if it's less than zero, however if it isn't less than zero, then i will get set to 1. Now, we need to introduce "op_equal" which will compare two integers and return a boolean indicating whether the two integers are equal. "op_equal" is replaced by "==". if (i == 0) i = 1 else (i == 1) i = 2 else i = 0 endif This proc will turn a 0 into a 1, a 1 into a 2, and anything else into a 0. Maybe something like this could be used to turn males into females, females into neuters, and neuters into males. That's how the alter sex spell works, at any rate. do [{while | until}] / loop [{while | until}] "do/loop" statements allow you to execute the same code multiple times. "while" means that the loop will work as long as the boolean condition is "true". "until" loops will work until the boolean is "false". declare integer i = 0 do while (i < 10) i = i + 1 loop When the loop is first run, i is 0. Then, i increases successively by 1 until i is 10. When i is 10 or more, the "do while (i < 10)" skips to the next line after the "loop" statement. This loop will run 10 times and i will be 10 when the loop is over. declare integer i = 0 do i = i + 1 loop while (i < 10) This loop is identical to the loop above. It's just that this loop checks to see if i is too big at the bottom of the loop, rather than at the top. function [] ([ ...]) Every Spec2 procedure (or "proc") must start with one of these statements. The function declaration tells Spec2 what kind of function you're making. function boolean r8002_do_stuff(character ch, string line) This function is called "r8002_do_stuff". It requires two arguments, the first must be a character, and the second must be a string. "ch" and "line" become variables in the function that you can use as if you had declared them. "r8002_do_stuff" becomes a boolean variable, as if you had declared it. So, if you assign something to r8002_do_stuff, you are modifying the function's return value. function integer r8002_increment(integer i) r8002_value = i + 1 function r8002_function() declare integer i = 0 i = r8002_increment(i) If you call r8002_function, it will make an integer variable called i, then add 1 to it by calling r8002_increment. Some functions are "special", meaning that they are called by the MUD. For instance, say a character in room 2800 types in a command, such as "smack walrus". Then, the MUD looks for a function named r2800_command. r2800_command needs to return a boolean, and it takes a character, a room, a string, and another string as arguments. Here's what the declaration could look like (the variable names can be whatever you want them to be): function boolean r2800_command(character ch, room r, string cmd, string line) ch will be the character who issued the command, r will be the room that the character's in, cmd will be "smack", and line will be "walrus", that is to say, the part of the command line after the first word and space. Finally, a quick word about IC and Spec2. To create Spec2 procedures, you need to use the "edit" command. For example, if I wanted to make a function called r8002_do_stuff, I would type "edit r 8002 spec do_stuff". That will automatically type in "function r8002_do_stuff()" at the beginning for you. This way, you can just start coding, and forget about the function declaration. Just as a warning, though, if you delete the function declaration, you won't be able to save your procedure until you insert it at the beginning. If you want to program a "special function", that is an event that is triggered by the MUD, then IC will fill in the appropriate function declaration. Congratulations! You now know the complete structure of Spec2! Unfulfilling, isn't it? You still haven't learned all of the different "special functions", all of the methods for the different variable types, and all of the constants. Without that knowledge, you can't make programs that are more sophisticated than just adding numbers together. Spec2 Docs: Parte the Seconde Integers Method Usage Return value ----------------- --------- ----------------------------------------------- boolean i.boolean false if zero op_assign i = j value contained in j, stored in i op_equal i == j true if i equals j op_gt i > j true if i is greater than j op_gt_equal i >= j true if i is greater than or equal to j op_lt i < j true if i is less than j op_lt_equal i <= j true if i is less than or equal to j op_minus i - j value of i - j op_minus_assign i -= j value of i - j, stored in i op_minus_unary -i value of the negative of i op_not_equal i != j true if i is not equal to j op_percent i % j remainder of i divided by j op_percent_assign i %= j remainder of i divided by j, stored in i op_plus i + j value of i + j op_plus_assign i += j value of i + j, stored in i op_plus_unary +i value of i op_slash i / j quotient of i divided by j op_slash_assign i /= j quotient of i divided by j, stored in i op_star i * j value of i times j op_star_assign i *= j value of i times j, stored in i room i.room room with virtual number i, otherwise nowhere string i.string string containing i, e.g. "42" Boolean Method Usage Return value ------------- ------ ------------------------------------------------------ op_and a & b true if both a and b are true op_and_assign a &= b true if both a and b are true, stored in a op_assign a = b value contained in b, stored in a op_bang_unary !a true if a is false op_equal a == b true if a equals b op_not_equal a != b true if a is not equal to b op_or a | b true if a, b, or both are true op_or_assign a |= b true if a, b, or both are true, stored in a op_xor a ^ b true if either a or b is true (not both) op_xor_assign a ^= b true if either a or b is true, stored in a Flag Method Usage Return value --------------- ----------- ----------------------------------------------- boolean f.boolean true if any bits in f are set is_set f.is_set(g) true if all bits in g are present in f op_and f & g each bit &'d together (as if boolean) op_and_assign f &= g each bit &'d together, stored in f op_assign f = g value contained in g, stored in f op_bang_unary !f value of f with all bits flipped op_equal f == g true if all bits in f match bits in g op_minus f - g value of f without g's bits op_minus_assign f -= g value of f without g's bits, stored in f op_not_equal f != g true if any bits in f differ with bits in g op_or f | g each bit |'d together (as if boolean) op_or_assign f |= g each bit |'d together, stored in f op_plus f + g value of f with g's bits op_plus_assign f + g value of f with g's bits, stored in f op_xor f ^ g each bit ^'d together (as if boolean) op_xor_assign f ^= g each bit ^'d together, stored in f string f.string string containing f, e.g. "aeopAB" String All string comparisons are case insensitive except where noted Method Usage Return value ------------ ----------------- ------------------------------------------ compare_case s ^= t true if s equals t (case sensitive) erase s.erase(i,j) value of s without j characters at i erase_line s.erase_line(i,j) value of s without j lines at line i erase_word s.erase_word(i,j) value of s without j words at word i find s.find(t) integer position of t in s find_any s.find_any(t) integer position of any char of t in s find_line s.find_line(t) integer line position of t of t in s find_none s.find_none(t) integer position first char of s not in t find_word s.find_word(t) integer word position of t of t in s flag s.flag flag containing s's bits insert s.insert(t,i) value of s with t inserted at position i insert_line s.insert_line(t,i) value of s with t inserted at length i insert_word s.insert_word(t,i) value of s with t inserted at word i integer s.integer integer from the contents of s left s.left(i) all of s before position i left_line s.left_line(i) all of s before line position i left_word s.left_word(i) all of s before word position i line s.line(i) the i'th return-delimited line of s line_end s.line_end(i) the end position of the i'th line of s line_start s.line_start(i) the start position of the i'th line of s op_assign s = t sets s equal to t op_equal s == t true if s equals t op_gt s > t true if s is after t (alphabetically) op_gt_equal s >= t true if s is after or equal to t op_lt s < t true if s is less than t op_lt_equal s <= t true if s is less than or equal to t reverse s.reverse value of s, just backwards replace s.replace(t,i) value of s with t inserted at position i replace_line s.insert_line(t,i) value of s with t inserted at length i replace_word s.insert_word(t,i) value of s with t inserted at word i right s.right(i) all of s at and after position i right_line s.right_line(i) all of s at and after line position i right_word s.right_word(i) all of s at and after word position i substr s.substr(i,j) j characters of s starting at position i substr_line s.substr_line(i,j) j lines of s starting at line position i substr_word s.substr_word(i,j) j words of s starting at word position i word s.word(i) the i'th space-delimited word of s word_end s.word_end(i) the end position of the i'th word of s word_start s.word_start(i) the start position of the i'th word of s Character These methods are integers that are read/write: alignment bank damroll gold hit hitroll mana maxhit maxmana maxmove move These methods are integers that are read only: class charisma constitution dexterity intelligence invis_level level sex stradd strength wisdom Usage Result ------------------------------- -------------------------------------------- f = ch.action ch's action flags will be stored in f. ch.add_equipment(obj, pos) obj will be equipped on ch in position pos. ch.add_inventory(obj) obj will be put in ch's inventory. b = ch.boolean if ch is nobody, b will be false. b = ch.can_see(v) if ch can see v, b will be true. ch.command(s) ch will do command s, as if it was typed. s = ch.data ch's data field will be stored in s, so that it may be accessed at a later time. s = ch.description s is the description of ch. o = ch.equipment(pos) o is the object on ch's position pos. ch.extract ch will be removed from the game. s = ch.field s is ch's IC field, edit with IC. ch.fight(v) ch will stop fighting, and if v is not nobody, then ch will start fighting v. v = ch.fighting v is the character ch is fighting. v = ch.find_character(s) v looks in room, all. v = ch.find_character_all(s) v finds the first character named s. v = ch.find_character_room(s) v finds the first character in room named s. v = ch.find_object(s) v looks in equipment, inventory, and room. v = ch.find_object_equipment(s) v finds the first object in eq named s. v = ch.find_object_inventory(s) v finds the first object in inv named s. v = ch.find_object_room(s) v finds the first object in room named s. o = ch.first_inventory o is the first object in ch's inventory, and for the next one, see o.next_inventory. ch.hunt(v) ch will stop hunting, and if v is not nobody, then ch will start hunting v. v = ch.hunting v is the character ch is fighting. r = ch.in_room r is the room that ch is in. i = ch.integer i is the vnum of ch, or -1 if a player. b = ch.is_mobile if ch is a mobile, b is true b = ch.is_player if ch is a player, b is true s = ch.keywords s is the keyword list for ch. ch.kill(v) ch will kill v and leave v's corpse behind. note: mob.kill(player) is a death point. o = ch.load_equipment(i, pos) o is an object of vnum i loaded in pos. o = ch.load_inventory(i) o is an object of vnum i loaded in inv. s = ch.name s is the name of ch. v = ch.next_character v is the character after ch in the world. v = ch.next_occupant v is the character after ch in the room. v = ch.next_player v is the player after ch in the world. ch = v sets ch equal to v, and returns v. ch == v if ch equals v, it returns true. ch != v if ch equals v, it returns false. s = ch.room_description s is the room description of ch. ch.send(s) sends s to ch. ch.to_room(r) teleports ch to r without a message. you may want ch.command("look") after this. Object Usage Result ------------------------ --------------------------------------------------- s = o.action_description s is the action description of o. o.add_content(p) p is put inside o. b = o.boolean if o is nobody, b will be false. s = o.data o's data field will be stored in s, so that it may be accessed at a later time. f = o.effect f is the effect flags of o. o.extract o will be removed from the game. s = o.field s is o's IC field, edit with IC. p = o.first_content p is the first object inside o. ch = o.in_equipment ch is the character whose equipment o is in. ch = o.in_inventory ch is the character whose inventory o is in. p = o.in_object p is the object that o is in. r = o.in_room r is the room that o is in. i = o.integer i is the vnum of o. s = o.keywords s is the keywords of o. p = o.load_content(i) p is an object of vnum i loaded inside o. s = o.name s is the name of o. p = o.next_content p is the object after o in the container. p = o.next_inventory p is the object after o in the inventory. p = o.next_object p is the object after o in the world. i = o.number(j) i is the j'th number of o, depends on type. i = o.on_position i is the equipment position of o. o = p sets o equal to p, and returns p. o == p if o equals p, it returns true. o != p if o equals p, it returns false. s = o.room_description s is the room_description of o. i = o.timer i is the timer of object o. o.to_equipment(ch, pos) puts o on ch's equipment position pos. o.to_inventory(ch) puts o in ch's inventory. o.to_object(p) puts o in object p. o.to_room(r) puts o to room r. i = o.type i is the type of object o. i = o.value i is the value of object o. f = o.wear f is the wear flags of object o. i = o.weight i is the weight of object o. Rooms Usage Result ----------------------- ---------------------------------------------------- r.add_content(o) o is moved to room r. r.add_occupant(c) c is moved to room r. s = r.data r's data field will be stored in s, so that it may be accessed at a later time. s = r.description s is the description of r. e = r.exit(dir) e is the room in the dir direction from r. c = r.find_character(s) c is the first character in r named s. o = r.find_object(s) o is the first object in r named s. o = r.first_content o is the first object in r. c = r.first_occupant c is the first character in r. f = r.flag f is the flags of r. i = r.integer i is the vnum of r. o = r.load_content(i) o is an object of vnum i loaded in r. c = r.load_occupant(i) c is a mobile of vnum i loaded in r. s = r.name s is the name of r. r = e sets r equal to e, and returns e. r == e if r equals e, it returns true. r != e if r equals e, it returns false. r.send(s) sends s to all occupants of room r. Built-In Functions Usage Result ------------------------------- -------------------------------------------- i = dice(n, m) i is the sum of n rolls of an m-sided die. c = find_character(s) c is the first character named s. o = find_object(s) o is the first object named s. p = find_player(s) p is the first player named s. c = first_character() c is the first character. o = first_object() o is the first object. p = first_player() p is the first player. i = number(lo, hi) i is a random number between lo and hi. r = random_room() r is a random linked room. write(s, b, c1, c2, o1, o2, f) See below. Write Write is essentially a rip-off of the standard DIKU act() function. I did it this way, because the syntax is logical and thus, not difficult to learn. Usage: write(s, b, c1, c2, o1, o2, f) s is the string to display. It may contain any of the following constants. As given, the constants apply to the first character (c1) or object (o1). If the letter is capitalized, then it applies to the second character (c2) or object (o2). For instance, $n is c1's name, but $N is c2's name. Use Result Examples --- -------------- -------- $n name the man the woman the eunuch $c capitalized $n The man The woman The eunuch $e nom. pronoun he she it $m obj. pronoun him her it $s pos. pronoun his hers its $o first keyword corn $p name a piece of corn $a article a (corn) b is the "hide invisible" bit. If c1 is invisible to the person receiving the message and b is true, then the message will not be displayed. If b is false, then $n will be "Someone". f is a flag that specifies who should receive the text. Flag Result ----------- ------ write_char writes to c1. write_vict writes to c2. write_room writes to the room c1 (or o1) is in, except c1 and c2. write_sleep use in combination with above. writes to sleeping players. write is the preferred way to display any information. The reason is that $n becomes "Someone" in the event that c1 is visible and b is false. Built-In Constants Equipment Positions (integers): about_body arms body feet finger_left finger_right hands head hold light legs neck_first neck_second shield waist wield wrist_left wrist_right Directions (integers): aft down east fore north northeast northwest port south southeast southwest starboard up west Name Result ------- ------ false boolean variable set to "false". nobody character variable set to NULL. nothing object variable set to NULL. nowhere room variable set to NULL. true boolean variable set to "true". Appendix A. String Confusion -- Field and Data You know, lots of people seem to be having trouble with strings in Spec2. So, here's how they work. There are two "special" strings for every object, mob, and room: "data" and "field". "data" is unique for every object, while "field" is unique only for each class of object. That is, "data" is different for every o8000 loaded in the MUD, while "field" is the same for all o8000's (but different for o8001's). You can only edit the "field" string in IC, since it's the same for each class of object. It is READ ONLY for Spec2, so a command like: obj.field = "duh" will do nothing. Think of "field" as a string table -- a way to store a large amount of constant data that can be quickly modified without recompiling. "data", however, can be modified in Spec2. In fact, that's the only way to find out what's in the data, e.g. find_player("Kyrol").send(obj.data) Think of "data" as memory -- a place you can store information from one function and use it in another. Here's an example, based off of m8096_*, that is, Grumpy Gus! function m8096_input(character ch, string s) # if someone says something, string s will look like: # So-and-so says, `I like fudge.' declare string token = "says, `I like" declare integer i = s.find(token) + token.length # s.find(token) finds the start of substring "token" in string s # by adding the length of token, integer i now points to the # space after "says, `I like", i.e. " fudge.'" if (i < s.length) # this is cheating, sort of. if token doesn't exist in string s, # then i will equal s.length. i < s.length means the token was found. declare integer length = s.right(i).find("'") # s.right(i) is the part of string s to the right of position i # the find tells us the position of "'" in the substring s.right(i), # or equivalently, the number of bytes from "I like" to "'". mob.data = s.substr(i, length) # now, we set the "data" string, that is the unique string for # this particular mob to equal the part of s from the token to the # final single-quote. i.e., "fudge." endif Now maybe we want Gus to say not that he hates stuff, but instead pick some random word. We could do something like: function m8096_tick(character mob) if (mob.data != "") declare integer i = number(1,3) if (i == 1) mob.command("say Oh yeah, well, I drink " + mob.data) else (i == 2) mob.command("say Oh yeah, well, I eat " + mob.data) else mob.command("say Oh yeah, well, I wear " + mob.data) endif mob.data = "" endif This tick proc checks to see if mob.data has been set by m8096_input. If so, it picks a random number, and displays a different message for each number. At the end, it sets mob.data back to the empty string, otherwise every tick, it would say the same thing over and over again. This is not too much typing for 3 words, but what about 50? That's where you want to use the mob's field. In this example, you could type "edit m 8096 field", then enter a list of phrases, e.g. drink eat would rather die than wear Then write the following spec: function m8096_tick(character mob) if (mob.data != "") # mob.field.length_line is the number of lines in string mob.field # number, then, picks a random number from 1 to the number of lines. declare integer i = number(1,mob.field.length_line) # mob.field.line(i) is therefore a random line in string "mob.field" # for instance, it could be "would rather die than wear" mob.command("say Oh yeah, well, I " + mob.field.line(i) + mob.data) mob.data = "" endif The advantage of the field is that it can be modified WHILE the spec is running, without having to recompile it. So, if you get bored with one list of words, you can put in new ones in IC without risk of errors.