Welcome to the Builder Academy

Question Adding Classes

More
11 Jun 2012 03:36 #32 by Rumble
Adding Classes was created by Rumble
If you have any additions or corrections please stop by the
Builder Academy at telnet://tbamud.com:9091 or email:
rumble@tbamud.com -- Rumble

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
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


Since tbaMUD only comes with the four most basic character classes, a common addition is to add more classes. One of the more popular classes of characters for adventure games in general is the Ranger. Since this class is reasonably unique, it will not have too much overlap in terms of spells or skill with the other four classes, so I'll be using this class as a running example.

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.

Most changes will take place in 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 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 will succumb to certain effects, specifically paralysis, "rods", petrification, breath effects, and spells, with higher numbers meaning a greater likelihood of being affected. Look for const byte saving_throws[NUM_CLASSES][5][41] = {
38, 36, 35, 34, 33, 31, 30, 29, 28, 27, /* 21 - 30 */
25, 23, 21, 19, 17, 15, 13, 11, 9, 7} /* 31 - 40 */
}
};

Just copy one of the existing blocks to the back of the section. I'd say to copy the block for the Thief, 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 and remove the comma, if any, from after the closing brace in the section you just pasted. 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":

/* Whom will we not trade with (bitvector for SHOP_TRADE_WITH()) */
#define TRADE_NOGOOD (1 << 0)
#define TRADE_NOEVIL (1 << 1)
#define TRADE_NONEUTRAL (1 << 2)
#define TRADE_NOMAGIC_USER (1 << 3)
#define TRADE_NOCLERIC (1 << 4)
#define TRADE_NOTHIEF (1 << 5)
#define TRADE_NOWARRIOR (1 << 6)

We need to add an entry for Rangers, just in case someone decides that some shop or another shouldn't like rangers. So, after the last entry, insert a line and add
#define TRADE_NORANGER (1 << 7)

to 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 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 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:

#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:

/* 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 4

and increment the 4 in the NUM_CLASSES line to a 5 to reflect the addition of the class. Also:

#define ITEM_ANTI_THIEF 14 /* Not usable by thieves */
#define ITEM_ANTI_WARRIOR 15 /* Not usable by warriors */
#define ITEM_NOSELL 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 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 which is included in the /doc directory.

Create the rooms.

#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.
~
S

That 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 3012

Between 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 3070

and 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.

Create the mobs:
#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.
~
59419 0 0 0 16 0 0 0 1000 E
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.
~
256010 0 0 0 80 0 0 0 900 E
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.
~
256010 0 0 0 80 0 0 0 900 E
23 1 2 6d1+390 1d8+12
2000 80000
8 8 2

Then load everything with zedit:
M 0 3028 1 3072 Rangers' Guildmaster
M 0 3029 1 3070 Ranger Guard
E 1 3022 100 16 Long Sword
M 0 3030 1 3071 Rangers' Waitress
G 1 3003 100 Firebreather
G 1 3004 100 Local Bottle
E 1 3021 100 16 Small Sword
M 0 3059 6 3059 Peacekeeper
M 0 3060 10 3070 Cityguard
E 1 3022 100 16 Long Sword

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. After the last ATM, add the following two lines:
R 0 3070 3034
O 1 3034 10 3070 ATM

And finally, after the last Social Bulletin Boards, insert the following lines:
R 0 3071 3096
O 1 3096 5 3071 Social Bulletin Board

Create the shop:
#3040~
3000
3001
3002
3003
3004
-1
1.1
1.0
-1

That 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
0

The waitress will now be able to sell beverages.

/* 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. Insert the following line:
ASSIGNMOB(3029, guild_guard);

You can use a trigger to make the guildguard:
#3012
Ranger Guildguard~
0 q 100
~
* Check the direction the player must go to enter the guild.
if %direction% == north
* Stop them if they are not the appropriate class.
if %actor.class% != Ranger
return 0
%send% %actor% The guard humiliates you, and blocks your way.
%echoaround% %actor% The guard humiliates %actor.name%, and blocks %actor.hisher% way.
end
end
~

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 class related, the blocks being changed will appear pretty much one right after the other. Start with 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;

Now, skip down 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 */
};

Next, 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.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. 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.

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, 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.

/* 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. 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. 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}
}

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 for:

ACMD(do_kick)
{
struct char_data *vict;
int percent, prob;

if (GET_CLASS(ch) != CLASS_WARRIOR) {
send_to_char(ch, "You'd better leave all the martial arts to fighters.\r\n");
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 and find the following lines in do_rescue:

if (GET_CLASS(ch) != CLASS_WARRIOR)
send_to_char(ch, "But only true warriors can do this!");
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, 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 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", run make. If everything is in order, you should have a new, fully usable player class: the Ranger. As a finishing touch, make sure to go through all of your World files to customize the game for your new class. Be sure to balance the pros and cons of your new race to make it desirable without unbalancing your MUD.

Rumble
The Builder Academy
tbamud.com 9091
rumble@tbamud.com

Please Log in or Create an account to join the conversation.

Time to create page: 0.173 seconds