/* * Version 0.2 (03/01/01) * * a short guide to coding class powers for godwars muds, it is highly recommended that you read * the guide to coding classes first, since we'll be refering to some of the code in that howto. * * Howto by Jobo, coder for the Godwars mud Dystopia. */ In the following, we will try to give some examples on different types of class powers, and explain what is important to check to avoid bugs/exploits. We will be using the Titan class coded in the class howto for the examples. When making a new command, we need to define it, and set the rules for the command to be used, this is done in the two files merc.h and interp.c, just as the do_learn command in the class howto. It is a good idea to call commands that players can type in the game something with do_, because it allows us to quickly see whether a piece of code is a command or some internal code when we read it later (also allows for easy searching of the command, should we forget in which file we put it). Theres a standard definition for player commands when declaring them in merc.h, typedef void DO_FUN args( ( CHAR_DATA *ch, char *argument ) ); by declaring a new command with DECLARE_DO_FUN( do_newcommand ); we actually define a function that returns void and takes a CHAR_DATA and a char as the two arguments, this is how all player commands should be defined. In interp.c we set the rules for using the command, we remember the rules for do_learn : { "learn", do_learn, POS_STANDING, 3, LOG_NORMAL, CLASS_TITAN, 0 ,0 }, 1. The name of the command, this is what players type to use it. 2. The coded function that's linked with the command. 3. The position, the most commonly used are POS_STANDING and POS_FIGHTING, you can find a list in merc.h 4. The level needed to use it, if you set the level to 0, then mobs can also use the command, so beware. 5. LOG_NORMAL, LOG_NEVER or LOG_ALWAYS. LOG_NORMAL for most commands, it's up to you wheter you want to log certain commands. 6. The class that should use the command (0 for all classes). By setting a class here, we don't actually prevent other classes from using the command, the only thing that happens here is whether the command shows on 'powers' or not. 7. The discipline that the power is located in. 8. The level of the discipline needed to use the command. 7 & 8 shouldn't be used unless you make some changes to the structure of playerfiles, or want to give some of the old disciplines to your new class (or want to code new powers for old disciplines). As always, the skeleton for a command has the following form : void do_newcommand(CHAR_DATA *ch, char *argument) { // your code goes here. } Now let's open titan.c and code some commands for the Titans. First let's make a simple travel command, since this is something most classes have. void do_travel(CHAR_DATA *ch, char *argument) { /* * we need to define the variables we are going to use in this command. * * arg : The argument given by the player (one word) * victim : The target player or mob. * location : The room victim is standing in. * */ char arg[MAX_INPUT_LENGTH]; CHAR_DATA *victim; ROOM_INDEX_DATA *location; /* * To compare the argument with the possibly targets, we need to cut it down to one word, * this is done with one_argument), since we don't need to use argument later on, we don't * copy the rest of argument back into argument. */ one_argument (argument, arg); /* * As always we make sure that the players is really a Titan, and not a mob or other class. */ if (IS_NPC(ch)) return; if (!IS_CLASS(ch, CLASS_TITAN)) { send_to_char("Huh?\n\r", ch ); return; } /* * We don't want Titans to go traveling without first learning the skill, but since * a travel power is important, we only require that he has level 1 in knowledge; * remember the two powers we defined for Titans earlier. */ if (ch->pcdata->powers[TITAN_KNOWLEDGE] < 1) { send_to_char("You need level 1 in knowledge before you can use travel.\n\r",ch); return; } /* * We can't use the variabel 'victim' before we make it point to something, * an undeclared pointer is NULL untill it actually points to something, and * the function get_char_world() returns a NULL if it can't find the target we * are searching for. We let 'victim' point to the return value from get_char_world, * which takes two argument (the player looking for the target, and the name of * the target 'arg'), it then checks if 'arg' exists, and if 'ch' can see the target, * this is all done in get_char_world(), all we need to do is check whether the return * value is NULL, because if it isn't, then the target exists and the player can see the target. * * Should the return value be NULL, we tell the player we couldn't find the target, and returns. */ if ((victim = get_char_world(ch, arg)) == NULL) { send_to_char("You are unable to find them.\n\r", ch ); return; } /* * Since the return value wasn't NULL, 'victim' actually points to a CHAR_DATA, and we can * check things about 'victim'. We don't allow players to travel to astral rooms, so let's * see if the victims room is astral. This is done using the command IS_SET() which is one * of three important commands when using bitvectors (Remember bitvectors from the class howto). * * IS_SET(ch->flag, value) checks if 'value' is set on the bitvector 'ch->flag' * SET_BIT(ch->flag, value) sets the bit 'value' on the bitvector 'ch->flag' * REMOVE_BIT(ch->flag, value) removes the bit 'value' from the bitvector 'ch->flag' * * We want to check if the room the victim is standing in has the ROOM_ASTRAL bit set. */ if (IS_SET(victim->in_room->room_flags, ROOM_ASTRAL)) { send_to_char( "You cannot enter the Astral sphere.\n\r",ch); return; } /* * Players have the option to become immune to travel powers, that means that if the victim * is immune to travelling powers, then our Titans will not be able to travel to them. * Again we use bitvectors to check whether the victim has the IMM_TRAVEL flag set. * Also note that we don't allow monsters to be immune to travelling powers. */ if (IS_SET(victim->immune, IMM_TRAVEL) && !IS_NPC(victim)) { send_to_char("You cannot travel to them, they are immune.\n\r",ch); return; } /* * Travelling comes at a cost, a Titan should pay 250 movement points to use the power, * so let's check if the Titan can pay the 250 move. */ if (ch->move < 250) { send_to_char("Your to tired to go travelling.\n\r", ch ); return; } /* * So the victim exists, and it isn't located in a astral room, and neither is it immune to travel. * Let's get the location, and copy it into our location variable. */ location = victim->in_room; /* * Now let's spice things a bit up with the act() command. This is a DIKU command that sends * a parsed message to the set of players defined in act, first we send a message to the Titan, * telling him that he has used the travel command. You will notice that the last argument * that act() takes is TO_CHAR, this means that the only person getting the message is the * player himself. In this case we don't use any parsing, so I'll wait with the explaining. */ act("You concentrate on the journey ahead.", ch, NULL, NULL, TO_CHAR); /* * We need to tell the other players standing in the same room as the Titan, that he is * trying to travel somewhere, here $n is the name of the Titan, the full set of $ values * that can be used in act is located in const.c, and you need to use this command to make * everything look more realistic (wouldn't be good if a female char was refered to as 'him') * * This is a copy/paste of the definitions in const.c $t Result is the 'arg1' argument interpreted as a string. $T Result is the 'arg2' argument interpreted as a string. $n Result is the name of 'ch'. If 'ch' is not visible to the target character, result is the string 'someone'. $N Result is the name of 'arg2' (considered as a victim). If 'arg2' is not visible to the target character, result is the string 'someone'. $e Result is 'he', 'she', or 'it', depending on the sex of 'ch'. $E Result is 'he', 'she', or 'it', depending on the sex of 'arg2' (considered as a victim). $m Result is 'him', 'her', or 'it', depending on the sex of 'ch'. $M Result is 'him', 'her', or 'it', depending on the sex of 'arg2' (considered as a victim). $s Result is 'his', 'her', or 'its', depending on the sex of 'ch'. $S Result is 'his', 'her', or 'its', depending on the sex of 'arg2' (considered as a victim). $p Result is the short description of 'arg1' (considered as an object). If 'arg1' is invisible to the target character, result is the string 'something'. $P Result is the short description of 'arg2' (considered as an object). If 'arg2' is invisible to the target character, result is the string 'something'. $d Result is the first word in 'arg2', considered as a string. If 'arg2' is NULL, result is the string 'door'. This is meant for extracting the name from a door's keyword list, but may be used in general for other keyword lists. * * we use the $n symbol, to get the name of the 'ch', that is the Titan, * and the $s to get his/her/its. The message is then send to everyone in * the same room as the Titan. */ act("$n uses $s travel power.", ch, NULL, NULL, TO_ROOM); /* * The Titan pays the price of 250 move, since the travel was succesful. */ ch->move -= 250; /* * char_from_room() and char_to_room() is two functions that used to move players around, * we simply remove the player from the room he is in, and then put him into the room we * found before ('location'). */ char_from_room(ch); char_to_room(ch, location); /* * It is possibly to call other do_* functions from within another, and we need to let * the player view the room he is entering, so we call do_look() with the arguments * 'ch' and '""'. ch is simply the Titan (as always), and the empty string "", * simply lets the player act as if he typed 'look' with no arguments. */ do_look(ch, ""); /* * Again we send a message to both the Titan and the players in the room he is entering, * using a well parsed act(). */ act("You materialize.", ch, NULL, NULL, TO_CHAR); act("$n suddenly appear from out of nowhere.", ch, NULL, NULL, TO_ROOM); /* * Again my habit of always ending a function with return, your choice, I think it looks better. */ return; } One of the more common command types are attacks, let's code a simple midround attack that our Titan can use during combat. We wan't the command to take an argument, so the player can decide who to use it on. Should the player not give an argument, we should chose the person the player is fighting as the target, and if the player isn't fighting anyone, we should tell him to pick a target. With all this in mind, we code the do_crushingblow command (and define it in merc.h and interp.c so we can use it on the mud). void do_crushingblow(CHAR_DATA *ch, char *argument) { /* * We need two variables, the victim and a one word argument from the player. */ char arg[MAX_INPUT_LENGTH]; CHAR_DATA *victim; /* * we take the first word in the argument given by the player (if he typed 'crushingblow garfield the cat' * we would only need 'garfield' as the argument). */ one_argument( argument, arg ); /* * Again, let's make sure we only have Titans using the command. */ if (IS_NPC(ch)) return; if (!IS_CLASS(ch, CLASS_TITAN)) { send_to_char("Huh?\n\r", ch ); return; } /* * This is a powerful attack, so the Titan needs knowledge level 3 to use it. */ if (ch->pcdata->powers[TITAN_KNOWLEDGE] < 3) { send_to_char("You need level 3 in knowledge before you can use crushingblow.\n\r",ch); return; } /* * Like with the travelling power, we need to find the target using the argument given by the player, * but this time we don't want the player to target anyone not in the same room as himself, so we use * get_char_room() instead of get_char_world(). This will check if the target is in the same room as * the player. */ if (( victim = get_char_room( ch, arg )) == NULL) { /* * Bad luck, the target wasn't in the room, but should the player be fighting someone, * we let the person that the player is fighting be the target of crushingblow. * should a player give the argument "" (if a Titan types 'crushingblow' without a target), * the above check also gives NULL. */ if (ch->fighting == NULL) { send_to_char( "They aren't here.\n\r", ch ); return; } /* * If the player was fighting someone, we let victim be that person, if not, we return. */ else victim = ch->fighting; } /* * Some people are a bit silly, and they try and attack themself, to avoid this, we compare * the victim with the player, should they match, we tell the Titan to try something else and returns. */ if ( victim == ch ) { send_to_char( "You really don't want to attack yourself.\n\r", ch ); return; } /* * Before we go ahead and let the Titan hurt someone, we should check to see if the target is * safe from attacks, this is done with the is_safe() function, which takes two players as arguments * and checks for safe_rooms, specials powers like ethereal, etc. We don't need to tell the Titan * why we returned when using is_safe, since is_safe always tell the player why. */ if (is_safe( ch, victim )) return; /* * Now let's call one_hit with the attack type sweeping blow (remember gsn_sweepblow from the class howto). * We let the attack be modified by the right hand weapon, and since this is a powerful command, we let the * Titan get two attacks. */ one_hit(ch, victim, gsn_sweepblow, 1); one_hit(ch, victim, gsn_sweepblow, 1); /* * Now for something new and important. We want certain commands to "lag" the player, making sure that * the player doesn't just spam the command. One full combatround is 12 ticks, and we want the Titan * to be unable to use any commands for one full round after having used crushing blow. Thus we set * the players WAIT_STATE to 12. * * Midround commands usually give a waitstate of 12 or maybe 6-10 if they are weak. * More powerful commands have higher waitstates, ie. do_berserk has a waitstate of 36. */ WAIT_STATE( ch, 12 ); /* * Again my habit of always ending a function with return, your choice, I think it looks better. */ return; } Now it's time to try something a little harder, the ability to make an area based attack. Our Titans should have the ability to call down a fireburst from the sky to smite it's enemies. Basicly we want an attack to hit everyone in the room except the Titan himself. Let's call the command 'flameburst'. Now area attacks are a bit tricky, since they open for several ways to crash the mud, so let's make sure we don't accidently do something illegal. void do_flameburst(CHAR_DATA *ch, char *argument) { /* * This time we need a CHAR_DATA (gch) variable to hold the target currently being damages, as * well as another CHAR_DATA (gch_next) to hold the next person to be damaged (we can't just make * a loop, since the person being damaged might get killed, and thus end the loop before we where * done with damaging everyone in the room. This time we don't need any arguments, since the * attack will simply hit everyone in the room. */ CHAR_DATA *gch; CHAR_DATA *gch_next; /* * As always we make sure that the players is really a Titan, and not a mob or other class. */ if (IS_NPC(ch)) return; if (!IS_CLASS(ch, CLASS_TITAN)) { send_to_char("Huh?\n\r", ch ); return; } /* * This is a level 2 attack. */ if (ch->pcdata->powers[TITAN_KNOWLEDGE] < 2) { send_to_char("You need level 2 in knowledge before you can use flameburst.\n\r",ch); return; } /* * Let's make sure the Titan can pay the 1000 mana required to summon the flameburst. */ if (ch->mana < 1000) { send_to_char("You cannot harness the magical energies.\n\r",ch); return; } /* * Let's do some nifty messages, both to the player, and those in the room. * Remember the $ symbols we used earlier. */ act("You send a plea to the gods, calling for their almighty wrath to smite thos who oppose you.", ch, NULL, NULL, TO_CHAR); act("$n raises $s hands to the sky and call for the gods to smite $s enemies.", ch, NULL, NULL, TO_ROOM); /* * So far everything has been rather trivial, nothing hard to code. * Now for the part where we should avoid bad code. */ /* * First we let both 'gch' and 'gch_next' point to the first person in the room where 'ch' is standing * We need 'gch_next' to point to someone, to make sure we actually enter the while loop, and 'gch' * should point to the first person we are going to damage. */ gch_next = ch->in_room->people; gch = ch->in_room->people; /* * Now let's make a while loop with the exit statement 'gch_next != NULL', which basicly makes * sure we'll exit the loop when there's noone left to damage in the room. */ while (gch_next != NULL) { /* * The first important thing to do, is let 'gch_next' point to the person after 'gch' in the room, * since 'gch_next' is later used to decide who to damage after 'gch'. */ gch_next = gch->next_in_room; /* * Now let's make sure we don't accidently damage our Titan. */ if (gch != ch) { /* * If 'gch' wasn't the Titan, we first check to see if the target is safe from attacks, * but we don't want to return from the function, just break this attempt to hurt the person, * and continue with the next person in the room. */ if (is_safe(ch, gch)) break; /* * Three balls of fire will hit the player. We are not using weapon attacks, so we add a 0 in the end. */ one_hit(ch, gch, gsn_fireball, 0); one_hit(ch, gch, gsn_fireball, 0); one_hit(ch, gch, gsn_fireball, 0); } /* * Now for the vital point, we let 'gch' point to 'gch_next', which actually points to * the person after 'gch' in the room. We DON'T use gch->next which would be the first choice, * since we don't know whether 'gch' was killed or not (if 'gch' was killed, gch becomes NULL, * and doesn't point to anyone in the room anymore. */ gch = gch_next; } /* * Damage dealt, we make sure the Titan pays for the magic, and give the player a good solid * waitstate for using the power (2 rounds), */ ch->mana -= 2000; WAIT_STATE(ch, 24); /* * Again my habit of always ending a function with return, your choice, I think it looks better. */ return; } Some of the things that could have gone wrong was using is_safe before letting gch_next point to the next person in the room (thus entering a never ending loop, crashing the mud), or we could have tried to hurt a person that was NULL if we didn't check first, causing another crash. The above code makes sure nothing can go wrong. We all know how different powers can be toggled on/off like truesight and shield, let's try and make a toggle power for Titans. Since Titans are a big class, the toggle power could be something that increases damage. Let's call the power 'supermight'. void do_supermight(CHAR_DATA *ch, char *argument) { /* * we don't need any arguments for this function, since we aren't going to target anyone, * and all changes done are to the player himself. But we will declare a buffer anyway, since * it's time to learn how to use the sprintf command. We let it be of the size MAX_STRING_LENGTH. * Later we'll need to send a mesage to everyone in the room, so we need a pointer to the persons * in the room, let's call that gch. */ char buf[MAX_STRING_LENGTH]; CHAR_DATA *gch; /* * Only Titans. Trivial, but still important. */ if (IS_NPC(ch)) return; if (!IS_CLASS(ch, CLASS_TITAN)) { send_to_char("Huh?\n\r", ch ); return; } /* * Before we go any further we need to define the bit we want to set in our main header file, * that's merc.h for us. We need to use bitvectors for this (read the class howto for details * on those). We cannot set a bit higher than 2147483648, which gives us 32 bits per flag, * first we locate a flag that has a bit to spare, in merc.h I located a free spot in newbits, * (it's common sense to call all bits with the same fistname, so we can easily see what flag * the bit belongs to, all newbits are called something with NEW_), here we see that the last * bit defined was NEW_FIGHTDANCE at the value 536870912, the next bit would then be the double * of that, so we define NEW_TITANMIGHT as 1073741824 just below the definition for fightdance. */ /* * Now we need to check if Titan has the knowledge needed to invoke the might of titans. */ if (ch->pcdata->powers[TITAN_KNOWLEDGE] < 1) { send_to_char("You need level 1 in knowledge before you can use supermight.\n\r",ch); return; } /* * This is a toggle power, so let's see if it's off, if it is, we should turn it on. */ if (!IS_SET(ch->newbits, NEW_TITANMIGHT)) { SET_BIT(ch->newbits, NEW_TITANMIGHT); /* * Now for parsing a fun message with sprintf(). It works much like act() does, but it's part * of the C library and can do many funny things. The important symbol is % instead of $ with * act(), and we need to change the flag after % regarding to the datatype we wish to parse. * First an example, then we'll explain. */ sprintf(buf, "The Titan called %s growls with insane glee, as the might of Titans runs through it's veins.", ch->name); /* * above we used %s for the players name, the values %s and %d refers to the two different datastructes char and integer, * so if we need to parse a name, we use %s, and should we need a number, we use %d. sprintf() then goes through the * string with the %'s in it, and copies the data defined after the string (in this case ch->name) into the correct places. */ /* * Now we need to send the message to everyone in the room with the Titan. It would have been alot easier just using act(), * but then we wouldn't have learned to use sprintf(). Just remember it would be smarter to use act() here if you should * code a toggle power later. */ for (gch = ch->in_room->people; gch ; gch = gch->next) { if (gch != ch) send_to_char(buf,gch); } /* * For the Titan himself, we just send a boring 'Ok' message, so he knows it was turned on. */ send_to_char("Ok, the power is now active.\n\r",ch); } /* * If the power was on, we should remove it. */ else { REMOVE_BIT(ch->newbits, NEW_TITANMIGHT); /* * Lesson learned, so no reason not to use act() here. */ act("$n looks weaker, as if the power of the Titans have left $m.", ch, NULL, NULL, TO_ROOM); act("Ok, power turned off.", ch, NULL, NULL, TO_CHAR); } /* * Again my habit of always ending a function with return, your choice, I think it looks better. */ return; } We should add a check in fight.c to see if the Titan has supermight, and then up the damage some, in one_hit() we add the following check to add 50% extra damage : if (!IS_NPC(ch) && IS_SET(ch->newbits, NEW_TITANMIGHT) && IS_CLASS(ch, CLASS_TITAN)) dam *= 1.5; It is possibly to make most functions that toggles a power on/off in much the same way as the above function. ie. shadowplane, ethereal, truesight are all toggled using this type of function. Good luck! regards Jobo