SMAUG Coding Tutorial By Gangien (gangien@hotmail.com) Homepage http://tdod.org/gangien Introduction This tutorial is intended for those who have some familiarity with C or at the very least programming experience. I have been coding on a mud for the past 5+ years so I think I know a thing or two, though I'm sure there's lots about SMAUG I have yet to learn. Though this will be centered on coding for a SMAUG mud, it can probably be applied to any mud, but the functions and code samples will probably not work without a little bit of modification. If someone would like to 'port' my tutorial to another code base, I'll gladly post it here. What this is not, this is not a learn C tutorial. I assume that you have a basic understanding of C. I will explain pointers a little bit since they seem to confuse people, but beyond that, I'll assume you know how to use C. This is also not a guide for installing SMAUG or other code bases, or how to compile them. I assume that you have a working copy of SMAUG and that you know how to compile it. If you wish to post this tutorial someplace else, I hope that you will at least email me. I have no problems with it being anywhere else, as long as the credit for me writing it is kept, and my email/homepage address is kept. Making Your own Command One of the most basic things you can do with a mud, coding wise, is create a new command. So what we're going to do is to create a simple command that will send the character some output. So open up a file, (recremmended file: player.c) and go to the end of it, or In between some functions that make sense and type this line: void do_showstats(CHAR_DATA *ch, char *argument) { ch_printf(ch, "Str: %d\tDex: %d\tWis: %d\tInt: %d\n\r", get_curr_str(ch), get_curr_dex(ch), get_curr_wis(ch), get_curr_int(ch)); ch_printf(ch, "Cha: %d\tCon: %d\tLck: %d\n\r", get_curr_cha(ch), get_curr_con(ch), get_curr_lck(ch)); return; } ch is a pointer to the CHAR_DATA of the user who typed the command, and argument is a pointer to the string that was typed after the command, and before the user hit enter. We'll get into how to parse the argument pointer later. The ch_printf function, prints the text to the character. similar to fprintf and printf, excelt it requires a CHAR_DATA pointer as the first parameter. The functions get_curr_str, and the others, are functions that can be found in handler.c. They return the current strength, and other attributes of the player. So a player with all attributes at 18, would have the following displayed if he entered the command. Str: 18 Dex: 18 Wis: 18 Int: 18 Cha: 18 Con: 18 Lck: 18 If you wish to spice things up a bit (add color) use the ch_printf_color function instead, it has the same parameters as ch_printf. Now then, we must make 3 more additions to the code base, before we recompile. The first is to mud.h. Do a search for do_showrace and you'll see a line like this: DECLARE_DO_FUN(do_showrace); As you've probably guessed, we need to add a line like that, ECLARE_DO_FUN(do_showstats); Now edit tables.c and again search for do_show race, and again make a new line similar to the last if ( !str_cmp( name, "do_showstats" )) return do_showstats; Then further down the file (do another search) make another addition if ( skill == do_showstats ) return "do_showstats"; Now, make clean and recompile, there shouldn't be any problems, and reboot the mud. Now the command is in the code base, but SMAUG doesn't know that yet, so you have to tell it where to look for it, so you have to give the following commands cedit showstats create do_showstats cedit save cmdtable There are other commands you can give, like the level, but you'll have to look them up on your own. Now then, type the command showstats and you should see two lines that list your stats. And there you have it your first command. Getting Input from a command Getting input from a command is very easy, since a string pointer is passed through all do_functions. But let's go back to that command we just made, do_showstats, at the beging add the lines char arg[MAX_INPUT_LENGTH]; argument = one_argument(argument, arg); If you say that last line out loud you may sound like a pirate to those around (stupid joke). Now you should know what the first line is, MAX_INPUT_LENGTH is in mud.h. but the second line, is similar to ones you'll see all throughout the code. What it does is it takes the string, argument, takes out the next word, puts that word into the arg pointer, and returns a pointer to the next word in the argument function. For example (cus I know I rambled there a bit) argument = "this is fun", you pass this through that function and this is what you get in return. argument = "is fun" arg = "this" one_argument will also parse anything inside quotes as a word and ignore leading white space so if argument = " 'this is fun' yeah!" after you pass it through one_argument arg = "this is fun" argument = "yeah!" Now add the following code after the one_argument call if(!str_cmp(arg, "str") { send_to_char("You only asked for strength.\n\r", ch); ch_printf(ch, "Str: %d\n\r", get_curr_str(ch)); return; } The str_cmp function, is similar to the ANSI C function strcmp, except str_cmp ignores case, so "This" == "tHIS". The send_to_char function, takes a string pointer, and a CHAR_DATA pointer as parameters, and will show the character the string. Recompile and reboot. Now if you type showstats str, it will show this: You only asked for strength. Str: 18 If you noticed, the one_argument function, isn't really necessary here, since the original argument pointer already points to the first word. But I thought I'd add it in for practice. Now your command looks like almost every other command in the SMAUG code, though somewhat smaller. Say you want to make it so that only players (not mobs) halfway to avatar or under and immortals, to be able to use this command. You would add this code, to preferable just after the declartion of variables. if(IS_NPC(ch)) { send_to_char("Mobs may not use this command.\n\r", ch); return; } if(get_trust(ch) > LEVEL_AVATAR / 2 && !IS_IMMORTAL(ch)) { send_to_char("Cannot be more than halfway to avatar to use this command.\n\r", ch); return; } You are probably thinking these are some very stupid restrictions to a piece of code. And you're right, but that's not the point. The first check should be obvious once you look at it. It checks if the character is an NPC, and returns TRUE if it is. This can be very important in some commands. if you have a command that accesses pcdata, like channels for instance, You must return before that data is accessed, otherwise the mud will crash, because mobs don't have pcdata. Because of the trust attribute that a person can have, I usually use it instead of ch->level. Although since mortals probably don't have a trust, You could put ch->level instead of get-Trust. But get_trust returns ch->level if there is no trust. The IS_IMMORTAL check should, like IS_NPC, be obvious, it returns TRUE if the player is an immortal. So basically if you typed in the command on a stock mud you would have to meet the following requirements: Be level 25 or less, or be level 101 or more And you would have to be a character, so if you ordered a MOB to do it, it wouldn't work. Getting Objects, Mobs and PCs from a string Creating a command, you'll often times need to find a char with the input string. This is easy enough. Say you wish to create a glory transfer command (It was the first command I ever made :) with a syntax of glorytransfer so You'll have to get the second argument as the 'victim'. All the SMAUG code using the victim as the target of the code, so it's probably best to keep that consistent. So first you'll need to parse the string into two variables. char arg[MAX_INPUT_LENGTH]; argument = one_argument(argument, arg); Now you have arg, being the name of the victim, and argument being the amount in a string. Now for clarity, you might make an arg2 or something and have that be the amount (have a second one_argument call), but you don't need too. Now there are two functions that you can use here, get_char_world and get_char_room, I think their names make them obvious of how to use them. They both have the same parameters as well and both return the resulting CHAR_DATA pointer, if a victim is found. So let's add some more code: if(((victim = get_char_room(ch, arg)) == NULL) { send_to_char("That character is not in this room.\n\r", ch); return; } and there we have it, now victim is pointing to the correct player and you can use it however you like. You'd probably make another int, and assign it atoi(argument) then add the glory and take away the glory from the ch. Also these functions work for mobs and PC's alike. For objects, it's very similar, here are the function prototypes, you should be able to understand what they mean. They use the exact same parameters as the get_char functions, (except they return type OBJ_DATA *) OBJ_DATA * get_obj_carry args( ( CHAR_DATA *ch, char *argument ) ); OBJ_DATA * get_obj_wear args( ( CHAR_DATA *ch, char *argument ) ); OBJ_DATA * get_obj_here args( ( CHAR_DATA *ch, char *argument ) ); OBJ_DATA * get_obj_world args( ( CHAR_DATA *ch, char *argument ) ); The one exception is OBJ_DATA * get_obj_vnum args( ( CHAR_DATA *ch, int vnum ) ); which obviously you have to pass the vnum in an integer, instead of searching for it by name. Using Other do_functions Say you want to add a command, like a glory purchase command, and you want to add a bit of rp to it. You could make the mobile that does 'enchanting of items' do emotes, or do says, or you could use the act function to make it seem like he's saying things. Personally, I like to use other functions, to keep things consistent, IE if you decide to change the color of say someday, but your code uses what it used to be, then it would look weird. so you could create a command like enchant , first things first you would have to check if the proper mobile is in the room, you should be able to figure out a way to do that by now. And you need to check if the item is in the character's inventory. So if it's all good, then you could use the do_give and do_say functions to make it look more realistic. And you could also check to see if the character can hold the item again, if he can't you could just drop it and say something like you can't hold it or whatever. Anyhow the main point of this section is to point out you can use the other do functions, like in the glorytranfer command, if the player enters the wrong set of strings you could have do_help(ch, "glorytranfers"); Pointers Because of the intended audience, I'm going to take a moment and attempt to explain pointers. Pointers are wonderful, I love them, I hate java because it doesn't have pointers (among other reasons). They also can be confusing if you've never used them, or if you come from a background of Qbasic or Visual Basic type programming. This is basically how pointers work. You define an integer by using int x; now say you have a function that you want it to have some parameters that you want to modify their values. Pointers work perfectly in this case (unless your using C++ and can pass by value but I'm assuming not). So say you want a function that tells the level range of a pkiller. So you create the function void get_range(CHAR_DATA *ch, int *low, int *high) { *low = ch->level - 5; *high = ch->level + 5; if(*low < 5) *low = 5; if(*high > LEVEL_AVATAR) *high = LEVEL_AVATAR; return; } Now all you have to do is, pass the address of two integers and the CHAR_DATA, which is already refered to by a pointer, and those intengers will then have the values aassigned to them in the get_range function. Example: int lo, hi; get_range(ch, &lo, &hi)l Now lo has the low level, and hi has the high level. But you may be asking what do all those things represent. Here's what they do in a nutshell. int *x; defines a pointer, x, to a type integer. So the parameters in the function get_range are expecting two pointers to integers. The way to access the address of a non pointer, is to use the & operand. So if you have int x, to access it's pointer you do &x, like I did in the example above. Strings in C are also pointers, for instance in the functions that you used ch_printf, and then a string, it's really just a pointer to a char type. String must end with a '\0' or null bit (The end of a literal string like "test" automatically have a terminating null bit). Arrays are also a series of pointers. Basically say you have int array[10]; *array is the same as array[0]; and you can do pointer arithmetic, which I won't go into detail about. Anyhow I hope that clarifies some things, here's some code to think about, if you understand it, you understand pointers :) char buf[100], buf2[100]; char *ptr1 = buf, *ptr2 = buf2; while(*(ptr1++)=*(ptr2++)); What this does is copy the contents of buf2 into buf. I'd strongly recremmend learning pointers from a real source though, like a book or website tutorial or something, because they can be handy, useful and they are used all throughout the code, though you could get along without understanding them, it would help. From here on in, it is assumed that you understand pointers. Memory Functions SMAUG has a nice amount of wrappers that allow for easy creation of stuff. For example if you want to create a new obj, you would do like this: OBJ_DATA *obj; CREATE(obj, OBJ_DATA, 1); nice and simple right? now you've got a new obj that's been allocated and is free to edit. I'm going to run down a list of wrappers/functions that is included in SMAUG and try to explain them CREATE( pointer, data_type, amount) - allocates memory for data_type and assigns pointer the address of the memory it creates LINK (node, first_node, last_node, next, prev); will insert node in the doubly linked list, at the end of it. UNLINK(node, first_node, last_node, next, prev); will remove node from the doubly linked list it's in. INSERT(node, node_before, first_node, next, prev); will insert node, before node_before in a doubly linked list. DISPOSE(pointer); frees pointer that was created with CREATE Here comes the fun part hashed/unhashed strings. If you don't understand what hashing is, let's just say it's a way to improve the speed of finding things. For instance if you have a linked list, and you want to find something you may have to go through the whole list, or the first item, so your average amount is number of linked lists / 2. Hast improves this by using ways of 'sorting' the list, so when you search for a string, it has to go through less to find the string. Although the worst and best case scenarios are the same as a linked list, the average time it takes to find it, is much less. QUICKLINK: increments the internal usage counter for a hashed string and returns a pointer to said string STRALLOC: duplicates a string. searches the string hash table for the string, and if it exists, performs the equivalent of a QUICKLINK. if it doesn't exist, it adds it to the hash table and returns a pointer to the new (hashed) copy of the string. can use this on either hashed or unhashed strings, but it returns a hashed string. STRFREE: frees a hashed string. QUICKMATCH: compares two hashed strings ("quickly") by comparing their memory addresses. it wont crash you if you use it on an unhashed string, but it will always return false. (hashed strings share memory which is why this works for them, and the whole purpose of hashing in the first place really) fread_string: reads a tilde-terminated(~) string from a file, hashes it, and returns a pointer to the string str_dup: duplicates a string. can be used on either hashed or unhashed, but returns an unhashed string. fread_string_nohash: does the same as fread_string, but the string doesn't get hashed. Altrag was the one who wrote those explanations of the functions, you can find the email in the SMAUG Mailing list archives. It is important to know whether a string is hashed or not, because if you used the wrong functions, you can end up with problems, and I know this first hand. Lists One of the things SMAUG has done, is most structs are in a doubly linked list. So you can traverse lists with ease. For example if you wanted to send to every character (mobs included) some text, you would have a loop like this: for(rch = first_char; rch; rch = rch->next) ch_printf(ch, "%s\n\r", txt_pointer); You could also traverse through all the areas in the game: for(area = first_area; area; area = area->next); The same holds true of just about anything that comes in stock SMAUG. Now if you want to display a message to all the characters, you could use the code above and add if(IS_NPC(rch)) continue; but a more practical, faster, way of doing that, is going through the descriptors. DESCRIPTOR_DATA *d; for(d=first_descriptor; d; d = d->next) { if(!CH(d)) continue; send_to_ch_printf(CH(d), "%s\n\r". argument) ; } The CH is just a define that points to d->character or d->original. Bit Vectors SMAUG has a nice implementation of bit vectors, that can be very useful. Don't know what a bit vector is? well think of it this way, it's a bunch of Booleans. Let me explain. Since, data is stored in binary form, there are 32 bits that make up an integer type. That means that there basically 32 on/off, true/false variables inside an int, if you use it correctly. Making the functions to do all this stuff, isn't hard, but why do that, when they are already there. Here are the functions IS_SET(int, bit) TOGGLE_BIT(int, bit) SET_BIT(int, bit) REMOVE_BIT(int, bit) now this may not look useful, but let's see what it can do. Say you wish to add a flag to players, that this player would be unable to be seen All you would have to do is, go into mud.h, at the end of all the PCFLAGS, add a new one, then check for it wherever you set the flag, or need to check if the flag is there. So you might add a new command, to do the following TOGGLE_BIT(victim->pcdata->flags, PCFLAG_VISABLE) then in the can_see function, if(IS_SET(victim, PCFLAG_VISABLE)). You don't have to add another Boolean to the char data, and bloat the pfile up even more, it's all automatically handled through these bitvectors. Of coruse if you want to create a new bitvector, then you have to write it to the pfile, but that's just like writing and reading and integer type, so it's no big deal. Say you want to add an arena, that has some of the flags like, noflee, noheal, nowait ect.. So you set up an arena struct and add it in a similar way that sysdata is setup. You'll need to add (if you want to keep consistent with the SMAUG way of doing things) 2 dimensiolnalarray of strings like char * const arena_flags [] = { "noheal", "nowait", "noflee" }; and add some defines for the flags in mud.h #define ARENAFLAG_NOHEAL BV00 #define ARENAFLAG_NOWAIT BV01 #define ARENAFLAG_NOFLEE BV02 You already know how to toggle bits, but here it gets a bit more complicated. You want a command to set the arena with certain flags, so how do you display all the flags, in a readable fashion, and how to you take an input line and get the flags out of that? Well the answer to the latter is easy, since SMAUG comes with a function to do that. ch_printf(ch, "Flags: %s\n\r", flag_string(arena.flags, arena_flags)); this line, takes the bitvector, arena.flags and the strings that it represents, arena_flags, and parses them. Now to make this work, the first #define must relate to the first string in the area_flags array. Now, how do you parse an input line that has a list of flags, like mset does. It's a bit more complicated, but still not all that hard. char arg[MAX_INPUT_LENGTH]; while ( argument[0] != '\0' ) { int value; argument = one_argument( argument, arg ); value = get_arenaflag( arg ); if ( value < 0 || value > MAX_BITS ) ch_printf( ch, "Unknown flag: %s\n\r", arg3 ); else TOGGLE_BIT( arena.flags, 1 << value ); } send_to_char("Ok.\n\r", ch); return; } The only thing that shouldn't make sense (unless you don't understand bit manipulation) is the get_arenaflag, function. Well we haven't quite made that yet, so let's do that now. int get_arenaflag( char *flag ) { int x; for ( x = 0; x < (sizeof(arena_flags) / sizeof(arena_flags[0])); x++ ) if ( !str_cmp(flag, arena_flags[x]) ) return x; return -1; } Say you put the snippet of code that parses the user input, you could do like this: arena flags noheal nowait noflee and that would toggle all those flags, which you could then check for accordingly.