WTFAQ - Adding a New Class
Files needed: constants.c -- to add bitvectors (!CLASSNAME, etc) magic.c -- to add magic saving throws shop.h -- to add NO_TRADE with your new class shop.c -- to add NO_TRADE with your new class utils.h -- to define class and bitvectors structs.h -- to define class and bitvectors spec_assign.c -- to assign spec procs for guildmaster/guard class.c -- adding class, spells, THAC0, etc. act.informative.c -- fixing up do_kick for Class_Ranger (not necessary) limits.c -- increase Ranger mana_regen 30.wld -- adding the guild 30.mob -- adding guildmaster, guildguard, waitress 30.zon -- loading guild mobs 30.shp -- guild's bar
Before you begin, I strongly suggest that if you already have a MUD up and running that you do not make any of these changes directly to your existing MUD, but rather make a copy and work on the copy. You don't want players to inadvertently try to use any of what you are adding, until you are satisfied that it is ready for them. In moving to version 3.0, Jeremy Elson spun off most of the classes-related code into a file called "class.c". However, small changes still need to be made to several other files for a new class to be usable by the rest of the game, so we will start with these. Most of these are of the simple little copy-paste-modify kind of addition, and they are all rather generic, but still there are a lot of them and you will need to make them all. In the file "constants.c", look at about line 335 in the original code for a block of code that looks like:
/* ITEM_x (extra bits) */ const char *extra_bits[] = { "GLOW", "HUM", "!RENT", "!DONATE", "!INVIS", "INVISIBLE", "MAGIC", "!DROP", "BLESS", "!GOOD", "!EVIL", "!NEUTRAL", "!MAGE", "!CLERIC", "!THIEF", "!WARRIOR", "!SELL", "\n" };You need to add the new class into end of this list. Insert a line between "!SELL" and "\n", and add in "!RANGER" for the new class after the "!SELL" line, as follows: "!THIEF", "!WARRIOR", "!SELL", "!RANGER", "\n"Next, let's move on to "magic.c", where we need to define the "magic saving throws", which, as far as I can tell, are the values for how likely a character is succumb to certain effects, specifically paralysis, "rods", petrification, breath effects, and spells, with higher numbers meaning a greater likelihood of being affected. Go to about line 62 and find a block that begins with const byte saving_throws[NUM_CLASSES][5][41] = {and runs to about line 171: 38, 36, 35, 34, 33, 31, 30, 29, 28, 27, /* 21 - 30 */ 25, 23, 21, 19, 17, 15, 13, 11, 9, 7} /* 31 - 40 */ } };As per the recommendation of Jesper Donnis (class.doc), just copy one of the existing blocks to the back of the section, between the two lines which contain the last two closing braces of the block, roughly lines 170 and 171. For a class like the Ranger, I'd say to copy the block for the Thief, lines 118 through 144, and maybe change the values later if you feel it's appropriate. You are free to choose any of these blocks to work from; the reason I chose the thief as a template for the ranger for these values is that, like the thief, the ranger's strengths typically come from his or her skills, rather than from spells or from brute strength and big weapons. Also, make sure you put a comma after the closing brace on line 170 and remove the comma, if any, from after the closing brace in the section you just pasted, which should be at around line 197, give or take a line or two. If you choose to revise the numbers, try to follow some decent kind of curve. A pattern based on a fragment of the bell curve is recommended, with a sharp drop in the number between the lower levels, and the smallest changes approaching the end of the third row. Remember to leave the fourth row as all zeros, unless you want your administrators to be affected by these. Okay, now let's move on to the shop code files. Let's start with the header file, "shop.h". Go to about line 71, and find the following section:
/* Whom will we not trade with (bitvector for SHOP_TRADE_WITH()) */ #define TRADE_NOGOOD 1 #define TRADE_NOEVIL 2 #define TRADE_NONEUTRAL 4 #define TRADE_NOMAGIC_USER 8 #define TRADE_NOCLERIC 16 #define TRADE_NOTHIEF 32 #define TRADE_NOWARRIOR 64We need to add an entry for Rangers, just in case someone writing scenery ("building") for your MUD, in their infinite wisdom (or lack thereof), decides that some shop or another shouldn't like rangers. For reasons computer, the values are powers of two, and the next available value is 128. So, after the last entry, insert a line and add #define TRADE_NORANGER 128to make this possible. Just as a reminder, be certain you tell anyone else who is building for your MUD about any changes you are making to any of the bitvectors, such as this one. While they may not need to know about all of them, there are a number that they should know about because they may want to use them. Okay, now move down to about line 128, and look for the following block of #define statements: #define NOTRADE_GOOD(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NOGOOD)) #define NOTRADE_EVIL(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NOEVIL)) #define NOTRADE_NEUTRAL(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NONEUTRAL)) #define NOTRADE_MAGIC_USER(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NOMAGIC_USER)) #define NOTRADE_CLERIC(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NOCLERIC)) #define NOTRADE_THIEF(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NOTHIEF)) #define NOTRADE_WARRIOR(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NOWARRIOR))At the end of the list, add a line for NOTRADE_RANGER: #define NOTRADE_RANGER(i) (IS_SET(SHOP_TRADE_WITH((i)), TRADE_NORANGER))Now move down to about line 138 and look for the following block of code: /* Constant list for printing out who we sell to */ const char *trade_letters[] = { "Good", /* First, the alignment based ones */ "Evil", "Neutral", "Magic User", /* Then the class based ones */ "Cleric", "Thief", "Warrior", "\n" } ; We once again need to add the entry for rangers. Insert the following line "Ranger",between the line containing "Warrior" and the line containing the newline character. Now that we've finished with the header file, we can move on to the "shop.c" file, to update the is_ok_char function, which starts at about line 52. Go down to about line 73 and find the following "if" statement:
if ((IS_MAGIC_USER(ch) && NOTRADE_MAGIC_USER(shop_nr)) || (IS_CLERIC(ch) && NOTRADE_CLERIC(shop_nr)) || (IS_THIEF(ch) && NOTRADE_THIEF(shop_nr)) || (IS_WARRIOR(ch) && NOTRADE_WARRIOR(shop_nr))) { sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_CLASS); do_tell(keeper, buf, cmd_tell, 0); return (FALSE); }In the parentheses between the "if" and the opening brace, we need to add a line to take advantage of the changes we've made in "shop.h". So, change the line containing IS_WARRIOR to the following two lines: (IS_WARRIOR(ch) && NOTRADE_WARRIOR(shop_nr)) || (IS_RANGER(ch) && NOTRADE_RANGER(shop_nr))) {This done, we can now move on to "utils.h", and look for the following block of code at around line 409:
#define CLASS_ABBR(ch) (IS_NPC(ch) ? "--" : class_abbrevs[(int)GET_CLASS(ch)]) #define IS_MAGIC_USER(ch) (!IS_NPC(ch) && \ (GET_CLASS(ch) == CLASS_MAGIC_USER)) #define IS_CLERIC(ch) (!IS_NPC(ch) && \ (GET_CLASS(ch) == CLASS_CLERIC)) #define IS_THIEF(ch) (!IS_NPC(ch) && \ (GET_CLASS(ch) == CLASS_THIEF)) #define IS_WARRIOR(ch) (!IS_NPC(ch) && \ (GET_CLASS(ch) == CLASS_WARRIOR))At the end of this block, add the following lines: #define IS_RANGER(ch) (!IS_NPC(ch) && \ (GET_CLASS(ch) == CLASS_RANGER))Now move on to "structs.h", and at about line 77 look for the following block of code:
/* PC classes */ #define CLASS_UNDEFINED -1 #define CLASS_MAGIC_USER 0 #define CLASS_CLERIC 1 #define CLASS_THIEF 2 #define CLASS_WARRIOR 3 #define NUM_CLASSES 4 /* This must be the number of classes!! */We will assign CLASS_RANGER the next available value. Insert the following line after the CLASS_WARRIOR line: #define CLASS_RANGER 4and increment the 4 in the NUM_CLASSES line to a 5 to reflect the addition of the class. Now move down to about line 297 and look for the following block of bitvectors:
/* Extra object flags: used by obj_data.obj_flags.extra_flags */ #define ITEM_GLOW (1 << 0) /* Item is glowing */ #define ITEM_HUM (1 << 1) /* Item is humming */ #define ITEM_NORENT (1 << 2) /* Item cannot be rented */ #define ITEM_NODONATE (1 << 3) /* Item cannot be donated */ #define ITEM_NOINVIS (1 << 4) /* Item cannot be made invis */ #define ITEM_INVISIBLE (1 << 5) /* Item is invisible */ #define ITEM_MAGIC (1 << 6) /* Item is magical */ #define ITEM_NODROP (1 << 7) /* Item is cursed: can't drop */ #define ITEM_BLESS (1 << 8) /* Item is blessed */ #define ITEM_ANTI_GOOD (1 << 9) /* Not usable by good people */ #define ITEM_ANTI_EVIL (1 << 10) /* Not usable by evil people */ #define ITEM_ANTI_NEUTRAL (1 << 11) /* Not usable by neutral people */ #define ITEM_ANTI_MAGIC_USER (1 << 12) /* Not usable by mages */ #define ITEM_ANTI_CLERIC (1 << 13) /* Not usable by clerics */ #define ITEM_ANTI_THIEF (1 << 14) /* Not usable by thieves */ #define ITEM_ANTI_WARRIOR (1 << 15) /* Not usable by warriors */ #define ITEM_NOSELL (1 << 16) /* Shopkeepers won't touch it */We will assign ITEM_ANTIRANGER the next unused bitvector. Insert the following line after the ITEM_NOSELL entry: #define ITEM_ANTI_RANGER (1 << 17) /* Not usable by rangers */At this point, it's time to play builder. You need to add a guild for the new class, along with the guildmaster to teach rangers their skills, the guildguard to bar the way to the guildmaster against members of other classes, and whatever additional characters are appropriate to the scenery. Typically, this would include a waiter or waitress for the guild bar, and a number of "Peacekeepers" and "Cityguards." To do this, you need to examine and modify one each of the .wld, .mob and .zon files. Since the default starting point is Temple of Midgaard, which is in area 30, you will need to edit "30.wld", "30.mob", "30.zon" and "30.shp", and add the rooms and the new characters. The reason to do this now rather than after the changes are made to "class.c" is that you need the identification numbers for the rooms in order to make one of the additions. While normally you could leave building up to a separate builder (unless you are your own builder anyway), adding a guild involves a couple of changes in the code as well as in the world files, and those changes, at last check, are not documented elsewhere. Most of the world file information that will be added at this point is copied from existing objects, or is a composite of characteristics of similar world objects. I will not go into detail about how to build; there is already a very good technical document on the subject by Jeremy Elson, which is included in the CircleMUD 3.0 distribution as "building.doc", and there are a number of documents out on the Internet which give advice about designing areas. Feel free to make changes here and there, but do so responsibly and with "building.doc" available. The world files are located in a group of directories contained in the "world" section of the "lib" directory, which contains the games data library. The "lib" directory can be found in the same directory as the "src" directory containing the source code files we've been working with. Each type of world file has its own directory: one for room data ("wld"), one for objects ("obj"), one for shop rules ("shp"), one for mobile definitions ("mob"), and one for the initial placement information for mobiles and objects in the various rooms ("zon"). The first thing to do is to add the rooms, so go into the "wld" directory and open "30.wld" and look for a good gap in the room numbers, and a good place to attach the guild. This example attaches the guild to the north of a room called "Inside the West Gate of Midgaard." In an unaltered copy of the file, the room numbers in area 30 stop at 3066, so this example will start with room 3070. Go the end of the file, and insert the following between the last two lines, one of which contains only the letter "S" and the other contains only a dollar sign: #3070 The Entrance to the Rangers' Guild~ The entrance to the Rangers' Guild looks like the inside of a log cabin with no furniture windows and no furniture. The lighting is coming from sconces on the walls, and a fire burns in a stone hearth. A doorway that seems to have been just cut out of the west wall leads to the bar. An archway to the south leads out to the West Gate of Midgaard. ~ 30 d 0 D2 You can see the West Gate of Midgaard out there. ~ ~ 0 -1 3040 D3 The bar looks like it might be only slightly more comfortable than the room you are in. ~ ~ 0 -1 3071 E fireplace hearth fire~ You see nothing special about the hearth. ~ sconce sconces~ You see nothing special about the sconces. ~ S #3071 The Bar of the Feathered Cap~ The bar looked a lot less interesting from the outside. Now that you're here, you can see a couple of couches, a wooden Indian, and the trophy heads from the Sackman family's latest trip to Africa. The room is lit by sconces, and a fire burns in a stone hearth. The stone archway to the north leads to your Guildmaster's Inner Sanctum. The guild entrance is through the doorway cut into the east wall. There's a sign hanging on the west wall. ~ 30 d 0 D0 To the north lies the Inner Sanctum of your Guildmaster. ~ ~ 0 -1 3072 D1 You get a glimpse of the guild entrance hall. ~ ~ 0 -1 3070 E fireplace hearth fire~ You see nothing special about the hearth. ~ E sconce sconces~ You see nothing special about the sconces. ~ E head trophy heads trophies~ You see a zebra, an elephant, and MY GOD that must have been one HUGE rhino. ~ E couch couches~ The couches seem comfortable enough. ~ E wooden indian~ He probably should be holding cigars or something, but he isn't. He looks almost alive, but a quick knock confirms that he's made of solid wood. ~ E sign~ The sign reads: Here's the drill: Buy - Buy something drinkable from the waitress. List - The waitress will read off the price list. The waitress, by the way, is not for sale. ~ S #3072 The Rangers' Inner Sanctum~ There is a decidedly...foresty feel to the place. The walls are made of tree trunks interspersed with bookshelves. Above you is a ceiling of branches and leaves. In the middle of one huge tree trunk in the south wall is an archway that leads out to the bar. The floor is carpeted with flowers. A hollowed-out tree trunk is in the center of the room. The vapors coming up from it would probably smell horrid, except for the overwhelmingly sweet scent of forest flowers. ~ 30 d 0 D2 Through the archway in the really big tree you see...the bar. ~ ~ 0 -1 3071 D5 There's a well down that tree trunk. It goes straight down about ten feet and then takes a sharp turn that probably leads into another well. The sides are sheer, and if you went down there's no way you could climb back up. Also, it's dark down there. ~ ~ 0 -1 7026 E tree trunk hollow well~ There's a well down that tree trunk. It goes straight down about ten feet and then takes a sharp turn that probably leads into another well. The sides are sheer, and if you went down there's no way you could climb back up. Also, it's dark down there. ~ SThat should complete the addition of the rooms themselves, along with a few interesting pieces of scenery. To make them accessible, go to about line 797 and find the section for room #3040, which starts with: #3040 Inside The West Gate Of Midgaard~ You are by two small towers that have been built into the city wall and connected with a footbridge across the heavy wooden gate. Main Street leads east and Wall Road leads south from here. ~ 30 0 1 D1 You see Main Street. ~ ~ 0 -1 3012Between the line that says "D1" and the line above it, insert the following lines: D0 You see the entrance to the Rangers' Guild. ~ ~ 0 -1 3070and amend the description of the room as follows: You are by two small towers that have been built into the city wall and connected with a footbridge across the heavy wooden gate. Main Street leads east and Wall Road leads south from here. To the north is the entrance to the Rangers' Guild.The rooms are now connected to the rest of the map. The next step is to create the characters who inhabit them. Close "30.wld", and open up "30.mob". Go down to about line 240, where the section for mob #3027, the knight, ends and the section for mob #3040, the bartender, begins. Insert the following between the last line of #3027 and the first line of #3040: #3028 guildmaster master frog kermit~ the rangers' guildmaster~ Your guildmaster is a tall, lanky frog, perched on a lily pad in the corner. ~ In a strange way it almost makes sense, since a frog is a creature of nature, that he'd be your guildmaster. Except that he's singing and playing a banjo. Still, he's got a nice voice, and the heartfelt strains of "The Rainbow Connection" give you a warm, fuzzy feeling. He's dressed all in green, a slightly darker shade than his skin. ~ abelnop dh 1000 S 34 0 -10 6d10+640 1d8+24 18794 100000 8 8 1 #3029 guard huntress~ the huntress~ A huntress plays with her sword as she guards the entrance. ~ The huntress is tall, attractive and deadly. Don't mess with her. She doesn't like troublemakers. Rumor has it she got this job after she single-handedly cleaned all of the high-level monsters out of what is now a newbie zone. The only reason she's not a guildmaster is that she can't sing. ~ ablnop dfh 1000 S 33 0 -5 6d10+990 1d8+22 2000 16000 8 8 2 #3030 waitress girl~ the waitress~ A rather young waitress in green is standing here. ~ This 14-year-old girl might fool a member of another guild, but you know that she's one tough cookie. She very friendly, a little flirtatious even, but woe to anyone who angers her. She specializes in bruised egos and severed limbs. ~ bno 0 600 S 23 1 2 6d1+390 1d8+12 2000 80000 8 8 2This creates the template for the guildmaster, guildguard and waitress for the guild bar. Close the mob file and open "30.zon". This is where the characters, rooms and objects all get tied together. Go to about line 61 and find the following entries:
M 0 3020 1 3019 Magic Users' Guildmaster M 0 3021 1 3002 Clerics' Guildmaster M 0 3022 1 3029 Thieves' Guildmaster M 0 3023 1 3023 Warriors' Guildmaster M 0 3024 1 3017 Mage Guard E 1 3022 100 16 Long Sword M 0 3025 1 3004 Priest Templar Guard E 1 3022 100 16 Long Sword M 0 3026 1 3027 Thief Guard E 1 3022 100 16 Long Sword M 0 3027 1 3021 Warrior Guard E 1 3022 100 16 Long SwordBetween the lines for the Warriors' Guildmaster and the Mage Guard, add the following line to place the Rangers' guildmaster in the Inner Sanctum: M 0 3028 1 3072 Rangers' GuildmasterAt the end of the block, after the Warrior Guard and his Long Sword, add the following lines to place the Huntress we've created as the guildguard at the entrance to the guild: M 0 3029 1 3070 Ranger Guard E 1 3022 100 16 Long SwordNow, skip down to about line 83 and look for the following section: M 0 3042 1 3018 Magic Users' Waiter G 1 3002 100 Dark Ale Bottle G 1 3003 100 Firebreather E 1 3020 100 16 Dagger M 0 3043 1 3003 Clerics' Waiter G 1 3002 100 Dark Ale Bottle G 1 3004 100 Local Bottle E 1 3024 100 16 Warhammer M 0 3044 1 3028 Thieves' Waiter G 1 3003 100 Firebreather G 1 3004 100 Local Bottle E 1 3021 100 16 Small Sword M 0 3045 1 3022 Fighters' Waiter G 1 3002 100 Dark Ale Bottle G 1 3003 100 Firebreather G 1 3004 100 Local Bottle E 1 3022 100 16 Long SwordAfter these entries, insert the following lines to add the waitress: M 0 3030 1 3071 Rangers' Waitress G 1 3003 100 Firebreather G 1 3004 100 Local Bottle E 1 3021 100 16 Small SwordNow find the block Peacekeepers and Cityguards, starting at about line 108. Change the five in the Peacekeeper lines to a six, and add the following line after the existing Peacekeepers: M 0 3059 6 3059 PeacekeeperAt the end of the batch of Cityguards, insert the following lines: M 0 3060 10 3070 Cityguard E 1 3022 100 16 Long SwordThe number to be changed in the Peacekeeper entries is the limit on how many the system can have active at any one time. If left as it was, the added Peacekeeper would not be used, since the limit would be five and the additional one would be a sixth. It is not necessary to change the number for the Cityguards, since there are few enough already.
The last things we need to add in this file are a bulletin board and an
ATM, and then our new class's guild will be outfitted the same as the
other four. Go down to about line 157, and find the block paired lines
that remove and re-add the ATM's. After the last one, add the following
two lines: R 0 3070 3034 O 1 3034 10 3070 ATMAnd finally, at about line 175, find the batch of lines that do the same thing for a group of Social Bulletin Boards. After them, insert the following lines: R 0 3071 3096 O 1 3096 5 3071 Social Bulletin BoardNow close "30.zon" and open "30.shp" so we can define the shopkeeper information for the waitress, and exclude Rangers from the bars in the other guilds. The shop handling routines have been completely rewritten for version 3.0 of CircleMUD by Jeff Fink, (building.doc, shop.c), and this file is in the new format. The first priority here is to make the waitress a shopkeeper. Go to about line 252 and find the following block: #3040~ 3000 3001 3002 3003 3004 -1 1.1 1.0 -1That block is the start of shopkeeper number 3040. Since the waitress is shopkeeper number 3030, to follow the existing pattern, insert the following code before the start of shopkeeper 3040: #3030~ 3003 3004 -1 1.7 1.0 -1 %s Haven't got that on storage - try list!~ %s I don't buy!~ %s I don't buy!~ %s I don't buy!~ %s I'm sorry, I don't do tabs.~ %s That'll be %d coins.~ %s Oops - %d a minor bug - please report!~ 0 2 3030 120 3071 -1 0 28 0 0The waitress will now be able to sell beverages. All that remains before tackling "class.c" is to find the other guild waiters, and adjust their bitvectors to disallow rangers. So, go down to about line 279 and find the top of shopkeeper number 3042, who is the waiter in the mages' guild: #3042~ 3002 3003 -1 1.0 1.0 -1 %s Haven't got that on storage - try list!~ %s I don't buy!~ %s I don't buy!~ %s I don't buy!~ %s If you have no money, you'll have to go!~ %s That'll be %d coins.~ %s Oops - %d a minor bug - please report!~ 0 2 3042 112 3018 -1The fourth line after the last line starting with a %s is the bitvector. Since the bitvector value for TRADE_NORANGERS is 128, we need to add 128 to this value. Since the bitvector value for this shopkeeper is 112, replace the 112 with a 250 to prevent this waiter from selling to rangers. Also add 128 to the bitvectors for shopkeeper numbers 3043, 3044 and 3045, who are the clerics' thieves', and fighters' guild waiters, respectively. In case you haven't figured it out by now, every object defined in a .wld, .mob, .zon and .shp file starts with a # sign followed immediately by the object number. Then close "30.shp". The last thing that must be done before moving on to "class.c" is to assign the guildmaster and guildguard their special powers in the source code. Open "spec_assign.c", and look around line 92 for the following block of code:
/* Midgaard */ ASSIGNMOB(3005, receptionist); ASSIGNMOB(3010, postmaster); ASSIGNMOB(3020, guild); ASSIGNMOB(3021, guild); ASSIGNMOB(3022, guild); ASSIGNMOB(3023, guild); ASSIGNMOB(3024, guild_guard); ASSIGNMOB(3025, guild_guard); ASSIGNMOB(3026, guild_guard); ASSIGNMOB(3027, guild_guard); ASSIGNMOB(3059, cityguard); ASSIGNMOB(3060, cityguard); ASSIGNMOB(3061, janitor); ASSIGNMOB(3062, fido); ASSIGNMOB(3066, fido); ASSIGNMOB(3067, cityguard); ASSIGNMOB(3068, janitor); ASSIGNMOB(3095, cryogenicist); ASSIGNMOB(3105, mayor);The ASSIGNMOB function, which is defined in this file, is used to assign special powers to groups of mobiles who are based on the same entry in a .mob file. In this case, we want to assign guild powers to mob #3028, and guild_guard powers to mob #3029, who are our guildmaster and guildguard, respectively. After the line that assigns guild_guard powers to mob #3027, insert the following two lines: ASSIGNMOB(3028, guild); ASSIGNMOB(3029, guild_guard);We are now ready to move on to "class.c", where we are going to make the rest of the changes. Many of these are once again of the copy-paste-modify variety, but a few of them allow for a little creativity. Since everything here is classes- related, the blocks being changed will appear pretty much one right after the other. We can start at about line 32, where we find the declaration for the class_abbrevs and pc_class_types array constants:
const char *class_abbrevs[] = { "Mu", "Cl", "Th", "Wa", "\n" };
const char *pc_class_types[] = { "Magic User", "Cleric", "Thief", "Warrior", "\n" };
Between the "Wa", and "\n" lines in class_abbrevs, insert the following
line with the abbreviation for Ranger: "Ra",and add the following line between the "Warrior", and "\n" lines in pc_class_types: "Ranger",Next we have the array constant class_menu, which contains the menu entries for new players choosing a class: const char *class_menu = "\r\n" "Select a class:\r\n" " [C]leric\r\n" " [T]hief\r\n" " [W]arrior\r\n" " [M]agic-user\r\n";Take the semicolon off of the end of the " [M]agic-user\r\n" line, and insert the following line below it: " [R]anger\r\n";Right below class_menu in the function parse_class, whose job it is to interpret the response to the menu:
int parse_class(char arg) { arg = LOWER(arg); switch (arg) { case 'm': return CLASS_MAGIC_USER; break; case 'c': return CLASS_CLERIC; break; case 'w': return CLASS_WARRIOR; break; case 't': return CLASS_THIEF; break; default: return CLASS_UNDEFINED; break; } }Insert the following lines above the default: case: case 'r': return CLASS_RANGER; break;After parse_class is the function find_class_bitvector, which takes the same argument as parse_class but returns the bitvector rather than the class value:
long find_class_bitvector(char arg) { arg = LOWER(arg); switch (arg) { case 'm': return (1 << 0); break; case 'c': return (1 << 1); break; case 't': return (1 << 2); break; case 'w': return (1 << 3); break; default: return 0; break; } }Insert the following lines above the default: case: case 'r': return (1 << 4); break;Now, skip down to about line 161 and find the following definition for prac_params:
int prac_params[4][NUM_CLASSES] = { /* MAG CLE THE WAR */ {95, 95, 85, 80}, /* learned level */ {100, 100, 12, 12}, /* max per prac */ {25, 25, 0, 0,}, /* min per pac */ {SPELL, SPELL, SKILL, SKILL} /* prac name */ };This one is a little different. Instead of adding a line, this definition needs a column added. Also, this is the point at which we need to decide if our class uses skills or spells. Selecting one or the other now does not preclude the ability to use the other, it is merely cosmetic. For the rangers, my personal preference would be to choose skills, but to make them easier to learn than the skills of a thief or warrior, and with a greater knowledge required to be considered "learned", which is the point beyond which the spell or skill cannot be practiced. Replace the above section with the following:
int prac_params[4][NUM_CLASSES] = { /* MAG CLE THE WAR RAN */ { 95, 95, 85, 80, 90}, /* learned level */ { 100, 100, 12, 12, 30}, /* max per prac */ { 25, 25, 0, 0, 10}, /* min per pac */ { SPELL, SPELL, SKILL, SKILL, SKILL} /* prac name */ };Below this, at about line 176, is the definition for guild_info, which controls the behavior of the guildguards:
int guild_info[][3] = { /* Midgaard */ {CLASS_MAGIC_USER, 3017, SCMD_SOUTH}, {CLASS_CLERIC, 3004, SCMD_NORTH}, {CLASS_THIEF, 3027, SCMD_EAST}, {CLASS_WARRIOR, 3021, SCMD_EAST}, /* Brass Dragon */ {-999 /* all */ , 5065, SCMD_WEST}, /* New Sparta */ {CLASS_MAGIC_USER, 21075, SCMD_NORTH}, {CLASS_CLERIC, 21019, SCMD_WEST}, {CLASS_THIEF, 21014, SCMD_SOUTH}, {CLASS_WARRIOR, 21023, SCMD_SOUTH}, /* this must go last -- add new guards above! */ {-1, -1, -1}};The format of these entries are: {class, room, direction}where class is the class constant, room is the room ID number of the entrance to the guild, and direction is the direction constant for the direction from that room to the bar. Since the only guild that we've created specifically for the rangers is in Midgaard, insert the following line below the CLASS_WARRIOR entry for Midgaard: {CLASS_RANGER, 3070, SCMD_WEST}, The stock game distribution does not come with files for area 210, which contain the guilds in New Sparta. The area is, however, available for download from the CircleMUD ftp site, ftp.circlemud.org, in the /pub/CircleMUD/submissions/areas directory. The file new_sparta.tar.gz contains the area. If you want to use New Sparta, download the file and extract the five files within. Place them in the appropriate directories, add their names to the index files as appropriate, and then build (or have someone build) the connections between this area and the other areas in the MUD. If you want to place a guild in New Sparta, you can follow the a similar procedure to the guild addition above. Also, while the stock game contains the references for the guilds for "class.c", the references needed in "spec_assign.c" will still need to be added. There are other pre-built areas available for download from the CircleMUD ftp site, and some of them do include towns with guilds. The guilds in any areas you install or all will need to have the appropriate references placed in "class.c" and "spec_assign.c". To continue adding the ranger class, below the guilds you will find the thaco (to hit armor class zero) array constant:
/* [class], [level] (all) */ const int thaco[NUM_CLASSES][LVL_IMPL + 1] = { /* MAGE */ /* 0 5 10 15 */ {100, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9}, /* 20 25 30 */ /* CLERIC */ /* 0 5 10 15 */ {100, 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 1, 1, 1, 1}, /* 20 25 30 */ /* THIEF */ /* 0 5 10 15 */ {100, 20, 20, 19, 19, 18, 18, 17, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3}, /* 20 25 30 */ /* WARRIOR */ /* 0 5 10 15 */ {100, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} /* 20 25 30 */ };The THAC0 controls how easy it is for a mob to hit a member of given class at a given level, with higher numbers being easier hits. A section for the rangers is also needed here. After the closing brace in the warrior section (not the closing brace with the semicolon after it), which should be on about line 226, add a comma, and insert the following section between the warrior section and the line containing the closing brace and the semicolon:
/* RANGER */ /* 0 5 10 15 */ {100, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1}, /* 20 25 30 */While the other classes have roughly constant rates of improvement in their THAC0 from level to level, the rangers will have steady improvements in the first eleven levels at the same rate as the Warrior, and then the rate at which their THAC0 improves will gradually reduce along the way. The next change is in the roll_real_abils function. When a player character is created, a set of random numbers is selected and assigned to the character's statistics, which are intelligence (int or intel), wisdom (wis), strength (str), dexterity (dex), charisma (cha) and conation (con). Depending on the player's class, the random numbers are then assigned with the highest values being placed in those statistics given highest priority for that class. For example, the mage gives top priority to intelligence, followed by wisdom, dexterity, strength, etc., while the warrior gives top priority to strength, then dexterity, conation, wisdom, etc. Find the switch statement in this function that determines which class and jumps to the assignment of stats, at about line 269. This switch statement should end at about 304 with a closing brace after the section for warriors. Above that brace, insert the following lines to assign the statistics based on the rangers' priorities:
case CLASS_RANGER: ch->real_abils.str = table[0]; ch->real_abils.con = table[1]; ch->real_abils.dex = table[2]; ch->real_abils.intel = table[3]; ch->real_abils.wis = table[4]; ch->real_abils.cha = table[5]; break;After the roll_real_abils function is the do_start function, which initializes new characters. At about line 329 is a switch statement which is responsible for conducting class-specific start-up actions on the characters:
switch (GET_CLASS(ch)) { case CLASS_MAGIC_USER: break; case CLASS_CLERIC: break; case CLASS_THIEF: SET_SKILL(ch, SKILL_SNEAK, 10); SET_SKILL(ch, SKILL_HIDE, 5); SET_SKILL(ch, SKILL_STEAL, 15); SET_SKILL(ch, SKILL_BACKSTAB, 10); SET_SKILL(ch, SKILL_PICK_LOCK, 10); SET_SKILL(ch, SKILL_TRACK, 10); break; case CLASS_WARRIOR: break; }You will notice that the only class which actually has startup actions here is the thief. At this point, there is no reason for any startup activities for the ranger, so we will insert an empty entry. Between the two lines for the warrior and the closing brace, insert the following: case CLASS_RANGER: break;The next function, advance_level, is triggered when a player acquires enough experience to advance to the next level. The switch statement for class-specific actions starts at about line 382:
switch (GET_CLASS(ch)) { case CLASS_MAGIC_USER: add_hp += number(3, 8); add_mana = number(GET_LEVEL(ch), (int) (1.5 * GET_LEVEL(ch))); add_mana = MIN(add_mana, 10); add_move = number(0, 2); break; case CLASS_CLERIC: add_hp += number(5, 10); add_mana = number(GET_LEVEL(ch), (int) (1.5 * GET_LEVEL(ch))); add_mana = MIN(add_mana, 10); add_move = number(0, 2); break; case CLASS_THIEF: add_hp += number(7, 13); add_mana = 0; add_move = number(1, 3); break; case CLASS_WARRIOR: add_hp += number(10, 15); add_mana = 0; add_move = number(1, 3); break; }Between the section for the warrior and the closing brace, insert the following code:
case CLASS_RANGER: add_hp += number(7, 13); add_mana = number((int) (0.5 * GET_LEVEL(ch)), GET_LEVEL(ch)); add_mana = MIN(add_mana, 5); add_move = number(2, 4); break;Now, jump down to about line 472 and find the function invalid_class, which is used to determine if a member of a given class can use a piece of equipment:
int invalid_class(struct char_data *ch, struct obj_data *obj) { if ((IS_OBJ_STAT(obj, ITEM_ANTI_MAGIC_USER) && IS_MAGIC_USER(ch)) || (IS_OBJ_STAT(obj, ITEM_ANTI_CLERIC) && IS_CLERIC(ch)) || (IS_OBJ_STAT(obj, ITEM_ANTI_WARRIOR) && IS_WARRIOR(ch)) || (IS_OBJ_STAT(obj, ITEM_ANTI_THIEF) && IS_THIEF(ch))) return 1; else return 0; }We need to add an entry for rangers, so replace the thief line with the following two lines: (IS_OBJ_STAT(obj, ITEM_ANTI_THIEF) && IS_THIEF(ch)) || (IS_OBJ_STAT(obj, ITEM_ANTI_RANGER) && IS_RANGER(ch)))The next function is init_spell_levels, which configures which classes can learn which spells or skills at which levels. Since we don't have any spells or skills specific to the Ranger at this time, we'll just borrow a couple from some of the other classes for now, and make them available for practice at different levels than they would be to their own class. We can come back and replace or supplement them with some more unique abilities later. Above the closing brace for the function, at about line 561, insert the following:
/* RANGERS */ spell_level(SKILL_KICK, CLASS_RANGER, 1); spell_level(SPELL_CURE_LIGHT, CLASS_RANGER, 3); spell_level(SKILL_RESCUE, CLASS_RANGER, 5); spell_level(SKILL_TRACK, CLASS_RANGER, 5); spell_level(SKILL_HIDE, CLASS_RANGER, 9); spell_level(SPELL_INFRAVISION, CLASS_RANGER, 9); spell_level(SPELL_CREATE_FOOD, CLASS_RANGER, 12); spell_level(SPELL_DETECT_POISON, CLASS_RANGER, 12); spell_level(SPELL_CREATE_WATER, CLASS_RANGER, 12); spell_level(SPELL_REMOVE_POISON, CLASS_RANGER, 15); spell_level(SPELL_HEAL, CLASS_RANGER, 16); spell_level(SPELL_CONTROL_WEATHER, CLASS_RANGER, 25);This should give the ranger a small selection of spells and skills borrowed from other classes. You can always come back and add to the list, although I'm not certain what the side-effects would be if you remove any entries from the list. Below init_spell_levels is the definition of the array constant title_type, which contains the titles for male and female members of the various classes at each level, along with the amount of experience the player will need to reach that level. Go to about line 726, which contains a closing brace followed by semicolon. This should be the last actual line of the file. Put a comma after the closing brace two lines up which finishes the definitions for the warriors, and insert the following code between the two lines with closing braces:
{{"the Man", "the Woman", 0}, {"the Fledgling", "the Fledgling", 1}, {"the Tenderfoot", "the Tenderfoot", 1500}, {"the Cub", "the Kitten", 3000}, {"the Fox Pup", "the Fox Kit", 6000}, {"the Wolf Cub", "the Wolf Cub", 13000}, {"the Bear Cub", "the Bear Cub", 27500}, {"the Tiger Cub", "the Tiger Kitten", 55000}, {"the Lion Cub", "the Lion Kitten", 110000}, {"the Fledgling Hawk", "the Fledgling Hawk", 225000}, {"the Fledgling Eagle", "the Fledgling Eagle", 450000}, {"the Ferret", "the Ferret", 675000}, {"the Fox", "the Vixen", 900000}, {"the Bobcat", "the Bobcat", 1125000}, {"the Wolf", "the Wolf", 1350000}, {"the Bear", "the Bear", 1575000}, {"the Tiger", "the Tigress", 1800000}, {"the Lion", "the Lioness", 2100000}, {"the Falcon", "the Peregrine", 2400000}, {"the Hawk", "the Lady Hawk", 2700000}, {"the Eagle", "the Lady Eagle", 3000000}, {"the Scout", "the Lady Scout", 3250000}, {"the Woodsman", "the Woodswoman", 3500000}, {"the Pathfinder", "the Pathfinder", 3800000}, {"the Trailblazer", "the Trailblazer", 4100000}, {"the Mountaineer", "the Mountainere", 4400000}, {"the Ranger", "the Lady Ranger", 4800000}, {"the Hunter", "the Huntress", 5200000}, {"the High Ranger", "the High Lady Ranger", 5600000}, {"the Great Lord Ranger", "the Great Lady Ranger", 6000000}, {"the Great Lord Hunter", "the Great Lady Huntress", 6400000}, {"the Immortal Lord Hunter", "the Immortal Lady Huntress", 7000000}, {"the Patron of the Hunt", "the Matron of the Hunt", 9000000}, {"the God of the Forests", "the Goddess of the Forests", 9500000}, {"the Implementor", "the Implementress", 10000000} }Now, save and close "class.c". There are only a few more changes we need to make before we can compile and build the program, changes which are side-effects of the abilities we've given the new class. The "kick" and "rescue" abilities we've given the rangers are currently reserved for the warrior class, so we need to enable them for rangers as well. Otherwise any ranger player who tries to make use of these skills will just end up wasting their practice sessions. Open "act.offensive.c" and look around line 372 for the following block of code:
ACMD(do_kick) { struct char_data *vict; int percent, prob; if (GET_CLASS(ch) != CLASS_WARRIOR) { send_to_char("You'd better leave all the martial arts to fighters.\r\n", ch); return; }The if statement in the sixth line of do_kick needs to be modified to allow rangers as well as warriors to use the spell. Modify this line as follows: if ((GET_CLASS(ch) != CLASS_WARRIOR) && (GET_CLASS(ch) != CLASS_RANGER)) { Now go down to about line 341, and find the following lines in do_rescue: if (GET_CLASS(ch) != CLASS_WARRIOR) send_to_char("But only true warriors can do this!", ch); else { percent = number(1, 101); /* 101% is a complete failure */ prob = GET_SKILL(ch, SKILL_RESCUE);Amend the if statement as follows: if ((GET_CLASS(ch) != CLASS_WARRIOR) && (GET_CLASS(ch) != CLASS_RANGER))Close "act.offensive.c", and open up "limits.c". Because our rangers have use of spells, it would be nice if they gained back their mana points at an accelerated rate, as with mages and clerics. These two classes regain their mana at double the rate of the other two. We don't want rangers to gain back mana quite so fast, we just want them to recover about fifty percent faster than the warrior or thief. Look around line 95 for the following if statement in the mana_gain function:
if ((GET_CLASS(ch) == CLASS_MAGIC_USER) || (GET_CLASS(ch) == CLASS_CLERIC)) gain <<= 1;Right below this if statement, add the following: if (GET_CLASS(ch) == CLASS_RANGER) gain = gain + (gain >> 1);The mages and clerics have a price to pay for their accelerated mana regeneration. They gain back hit points more slowly than other classes. While we will not be charging the Rangers this price, make note of a very similar if statement to the one above in the hit_gain function, around line 143, for future reference. However, the nature of the ranger makes it desirable for them to regain movement points a little faster than members of the other classes, say about one-quarter faster. Go down to about line 186 in the move_gain function, and find a closing brace that ends a switch statement, followed on the next line by a closing brace that ends an else clause. Between these two lines, insert the following:
if (GET_CLASS(ch) == CLASS_RANGER) gain = gain + (gain >> 2);Finally, close "limits.c", and delete any compiler object files you might have in your "src" directory from previous compiles, and make or re-make the program. If everything is in order, you should have a new, fully usable player class: the Ranger. As a finishing touch, you or your builder should go through your shop and object files to make some of the equipment and stores unavailable to rangers.
|