Mobprog quests snippet ---------------------- This code adds 2 new mpcommands and one new ifcheck that give the ability to write very detailed and interactive quests for your mud. This code is based off of a snippet by Mike Counts called autoquesting. The original used a quest file for each player on the mud to store if a quest had been completed or not, and while I liked the code, it was restrictive in a few ways to me. Firstly, I wanted the ability in my pre-auth area to gives new users little quests to complete, and this would cause a problem if a newbie made and then quit before actually getting into the game and saving their character. The quest file would be left behind and say a new character creating would possibly have such and such quest marked as complete even though it was a completely different user. While not likely I don't like loose ends. Secondly it gave no easy way to clear a quest from a player if you later wanted to give them the ability to do the quest again. While I coded a way to make it work, it was cludgy code and I wanted to make it better. Thirdly I wanted to add the ability for a quest to 'wear off' after a given amount of time without other intervention, merely as a way to keep the same player from doing the same quests over and over again frequently. In short, I rewrote the code so that it uses a linked list attached to the player in memory so that files aren't being accessed all the time. Instead the quests completed are saved to a character's pfile. There's an easy way to remove quests in mprogs, as well as the ability for them to 'wear off' after the given number of ticks in game. Mpcommands and Ifcheck ----------------------- These are the commands and the ifcheck and a brief description of what they do and a small (useless) example program to sort of show how they can be used, although in actual application you can be as detailed as you want. The commands: mpquestcomplete: Syntax: mpquestcomplete $n QUEST (timer) What this command does is mark a particular quest as having been completed by $n, where QUEST is the name given the quest by the person writing it. The optional parameter time can allow for the quest to 'wear off' after the specified number of ticks. If no time is specified, the quest is permanent unless cleared by mpquestclear. mpquestclear: Syntax: mpquestclear $n QUEST This command allows you to clear QUEST from a player. To actually be of any use, the quest must actually exist (that is it must be completed to work). If it's not there's no error, but a call to the questcomplete ifcheck shown below will let you know if you should call this in the first place. questcomplete: Syntax: if questcomplete($n) == QUEST This ifcheck evaluates to TRUE if QUEST has been completed by $n. before using either mpquestcomplete or mpquestclear, it's a good idea to make sure you use this ifcheck first to determine if the player has already completed the quest or not. The operator doesn't matter at all, this ifcheck will always return TRUE or FALSE depending on if QUEST is completed or not. A simple example program follows: >speech_prog p test if questcomplete($n) == testquest1 say You've already completed testquest1! else say You've just completed testquest1! mpquestcomplete $n testquest1 endif ---------------------- In the above program, which is triggered by a player saying the word 'test', what happens should be fairly clear if you've used smaug mudprogs a lot. If not here's the basic gist, first check to see if they've completed our test quest, which is named 'testquest1', by using the questcomplete ifcheck. If the ifcheck is true, the player has already done this particular quest (yes it's simple but it's for an example) so all we do is tell them they've already completed the quest. Otherwise, we tell them that they have just now completed the quest, and use the mpquestcomplete command to mark 'testquest1' as being done. So the first time a player says test, they see 'You've just completed...' and the second time, and every other time after that, they'll see 'You've already completed...'. >speech_prog p test if questcomplete($n) == testquest1 say You've already completed testquest1! else say You've just completed testquest1! mpquestcomplete $n testquest1 10 endif The above program is exactly the same except that it uses the timer ability. This means that testquest1 will 'wear off' in 10 ticks. Installation ------------- In SMAUG.H add the following to the DO_FUN declarations: DECLARE_DO_FUN(do_mpquestcomplete); DECLARE_DO_FUN(do_mpquestclear); and add these to the other function prototypes for mud_prog.cpp: //Declarations for the new mobprog questing snippet - Kurgan void MarkQuestComplete(char *quest, int timer, CCharacter *player); BOOL CheckQuestComplete(char *quest, CCharacter *player); void ClearQuest (char *quest, CCharacter *player); add the following along with the other class declarations: class CQuestData; ------------------------------------------------------- In SKILLS.CPP add the following lines to the function declarations along with all the others (make sure they are in alphabetical order): //Next two for mobprog questing snippet - Kurgan if (!str_cmp (name, "do_mpquestclear")) return do_mpquestclear; if (!str_cmp (name, "do_mpquestcomplete")) return do_mpquestcomplete; //Next two for mobprog quest snippet - Kurgan if (skill == do_mpquestclear) return "do_mpquestclear"; if (skill == do_mpquestcomplete) return "do_mpquestcomplete"; -------------------------------------------------------- In CHARACTER.H: add the following near the top someplace, I added it just before the pc_data class define // Mprog quest data structure - allows a mob to 'remember' when a player // does a certain thing. Can have the quest wear off after a certain number // of ticks or it can be permanent for one time only quests. - Kurgan // Note: This idea was taken from a snippet that originally used a file // for each player to keep track of quests completed. The system now works // in memory for each player and saves in the pfile class CQuestData { public: CQuestData () { memset (this, 0, sizeof (CQuestData)); } ~CQuestData () { STRFREE (name); } CQuestData *GetNext () { return m_pNext; } void SetNext (CQuestData* n) { m_pNext = n; } CQuestData *GetPrev () { return m_pPrev; } void SetPrev (CQuestData* n) { m_pPrev = n; } char *GetName () {return name;} void SetName (const char* n) { if (name) STRFREE (name); name = STRALLOC (n); } int GetTimer () {return timer;} void SetTimer (int t) {timer = t;} CQuestData *m_pNext; CQuestData *m_pPrev; char *name; //name of the quest int timer; //How many ticks until the quest 'wears off', -1 for permanent }; add the following to the pc_data public: section: //Added for mprog questing system - Kurgan CQuestData *first_mpquest; CQuestData *last_mpquest; ---------------------------------------------------------- In MUD_COMM.CPP add the following two functions anyplace: // mpquestcomplete - Used to mark a quest as 'completed' - Kurgan // Syntax mpquestcomplete $n QUEST (timer) // timer is optional and will make the quest wear off after // the indicated number of ticks. void do_mpquestcomplete(CCharacter *ch, char *argument) { char arg1[ MAX_INPUT_LENGTH ]; char arg2[ MAX_INPUT_LENGTH ]; int timer = -1; CCharacter *victim; if ( !ch->IsNpc () || ch->IsAffected (AFF_CHARM)) { ch->SendText( "Huh?\n\r"); return; } argument = one_argument( argument, arg1 ); argument = one_argument( argument, arg2 ); if ( arg1[0] == '\0' ) { progbug( "Mpquestcomplete - No arg1 (target)", ch ); return; } if ( !( victim = get_char_room( ch, arg1 ))) { progbug( "Mpquestcomplete - victim does not exist", ch ); return; } if(victim->IsNpc ()) { ch->SendText("Mobs can't have quests!\n\r"); return; } if ( arg2[0] == '\0' ) { progbug( "Mpquestcomplete - no quest to complete",ch); return; } //see if we need to set a timer for this quest //if you want to add a check to keep timers from //accidentally being set very high, this would be //where to do it, You could change the UMAX to //use URANGE instead, I just wanted to make sure //no negative numbers accidentally get used. if (is_number (argument)) { timer = UMAX (-1, atoi (argument)); } else timer = -1; MarkQuestComplete(arg2, timer, victim); } // mpquestclear - Used to clear a quest from a player - Kurgan void do_mpquestclear(CCharacter *ch, char *argument) { char arg[ MAX_INPUT_LENGTH ]; CCharacter *victim; if ( !ch->IsNpc () || ch->IsAffected (AFF_CHARM)) { ch->SendText( "Huh?\n\r"); return; } argument = one_argument( argument, arg ); if ( arg[0] == '\0' ) { progbug( "Mpquestclear - No argument", ch ); return; } if ( !( victim = get_char_room( ch, arg ) ) ) { progbug( "Mpquestclear - victim does not exist", ch ); return; } if(victim->IsNpc ()) { ch->SendText("Mobs can't have quests!\n\r"); return; } if ( argument[0] == '\0' ) { progbug( "Mpquestclear - no quest to clear",ch); return; } ClearQuest (argument, victim); } --------------------------------------------------------------- In MUD_PROG.CPP: add the following function prototypes at the beginning of the file: //Declarations for the new mobprog questing snippet - Kurgan void MarkQuestComplete(char *quest, CCharacter *player); BOOL CheckQuestComplete(char *quest, CCharacter *player); void ClearQuest (char *quest, CCharacter *player); add the following three functions anywhere: void MarkQuestComplete(char *quest, int timer, CCharacter *player) { CQuestData *new_quest = NULL; //Trying to make this function a little smarter. //Before you write it, just use the function already made for //it to see if this particular quest is already marked as done. - Kurgan if (!CheckQuestComplete (quest, player)) { pc_data &vPc = *player->GetPcData (); new_quest = new CQuestData; new_quest->SetName (quest); new_quest->SetTimer (timer); LINK (new_quest, vPc.first_mpquest, vPc.last_mpquest); } } BOOL CheckQuestComplete(char *quest, CCharacter *player) { CQuestData *cur_quest = NULL; //If they don't have any quests at all, safe to return FALSE if (player->GetPcData ()->first_mpquest == NULL) return FALSE; //Otherwise go throught the quests they have and see if quest matches //any of them cur_quest = player->GetPcData ()->first_mpquest; for (;cur_quest;cur_quest = cur_quest->GetNext ()) { if (!str_cmp (cur_quest->GetName (), quest)) return TRUE; } return FALSE; } void ClearQuest (char *quest, CCharacter *player) { CQuestData *cur_quest = NULL; pc_data &vPc = *player->GetPcData (); //safety check if (vPc.first_mpquest == NULL) return; //this function assumes that a player can only have one quest of any given //name, there are safety checks in the other functions for this reason for (cur_quest = vPc.first_mpquest;cur_quest;cur_quest = cur_quest->GetNext ()) { if (!str_cmp (cur_quest->GetName (), quest)) { UNLINK (cur_quest, vPc.first_mpquest, vPc.last_mpquest); delete cur_quest; return; } } return; } In the function mprog_do_ifcheck add the following code in with the other ifchecks (If you're not sure where to add this just look down through the function, you should be able to tell where the other ifchecks are being handled): //New mobprog quest snippet if( !str_cmp(chck, "questcomplete" )) { /* usage: if questcomplete($n) == QuestName */ return(CheckQuestComplete(rval,chkchar)); } -------------------------------------------- In CHARACTER.CPP: add the following code to the pc_data destructor. The start of this looks like pc_data::~pc_data() //added for mprog quests - Kurgan if (first_mpquest != NULL) { CQuestData *cur_quest = NULL; CQuestData *next_quest = NULL; for (cur_quest = first_mpquest;cur_quest;cur_quest = next_quest) { next_quest = cur_quest->GetNext (); UNLINK (cur_quest, first_mpquest, last_mpquest); delete cur_quest; } } In the CCharacter::Read function find - case 'M': and add the following code to that section: //Read any quest tags the player has - Kurgan if (!str_cmp (word, "MpQuest")) { CQuestData *new_quest = new CQuestData; new_quest->timer = ParseNumber (pLine); new_quest->SetName (ParseStringNohash (pLine, fp)); LINK (new_quest, Pc.first_mpquest, Pc.last_mpquest); fMatch = TRUE; break; } In the CCharacter::Write function find the following code: fprintf (fp, "Password %s~\n", Pc.GetPassWord ()); and directly under this add: //Save any quest tags the player has - Kurgan if (Pc.first_mpquest != NULL) { CQuestData *cur_quest = NULL; for (cur_quest = Pc.first_mpquest;cur_quest;cur_quest = cur_quest->GetNext ()) { fprintf (fp, "MpQuest %d %s~\n", cur_quest->timer, cur_quest->GetName ()); } } ------------------------------------ In UPDATE.CPP: In the char_update function find the following code: // See if player should be auto-saved. if (!ch->IsNpc () && (!ch->GetDesc () || ch->GetDesc ()->m_Connected == CON_PLAYING) && ch->GetLevel () >= 2 && (CurrentTime - ch->GetSaveTime ()).GetTotalMinutes () > (SysData.SaveFrequency)) then ch_save = ch; else ch_save = NULL; After it add the following: //count down any quest tags that are on a timer - kurgan if (!ch->IsNpc () && ch->GetPcData ()->first_mpquest != NULL) { CQuestData *cquest = NULL; CQuestData *nquest = NULL; cquest = ch->GetPcData ()->first_mpquest; for (;cquest;cquest = nquest) { nquest = cquest->GetNext (); if (cquest->timer > 0) { if (--cquest->timer <= 0) { UNLINK (cquest, ch->GetPcData ()->first_mpquest, ch->GetPcData ()->last_mpquest); delete cquest; } } } } ------------------------------------ Optionally, you can install the following code which adds a command called qstat to your mud that lets you check a player and see what (if any) quest tags a player has on them, and how long they will last for (A value of -1 means they are permanent and anything over 0 means in that many ticks they will wear off) You don't need to do this, but it's kind of handy sometimes. In Smaug.h add the following to the DO_FUN declarations: DECLARE_DO_FUN(do_qstat); In Skills.cpp add the following lines (in alphabetical order with the rest) to the table declarations: if (!str_cmp (name, "do_qstat")) return do_qstat; if (skill == do_qstat) return "do_qstat"; In Act_wiz.cpp add the following function anywhere: //Immortal command to view any quest tags //a player might have - Kurgan void do_qstat (CCharacter *ch, char *argument) { CCharacter *victim = NULL; CQuestData *quest = NULL; if (argument[0] == '\0') { ch->SendText ("QStat who?\n\r"); return; } if ((victim = get_char_world (ch, argument)) == NULL) { ch->SendText ("They aren't here.\n\r"); return; } if (victim->IsNpc ()) { ch->SendText ("You can't QStat NPCs.\n\r"); return; } pc_data &vPc = *victim->GetPcData (); ch->SendTextf ("Qstat for %s\n\r", victim->GetName ()); ch->SendText ("-----------------------------\n\r"); for (quest = vPc.first_mpquest;quest;quest = quest->GetNext ()) { ch->SendTextf ("(%d)%s ", quest->GetTimer (), quest->GetName ()); } ch->SendText ("\n\r"); } This isn't very prettily formatted or anything, it was just a quick write to let me see things more clearly during development that I ended up keeping. ---------------------------------------- That should be about it. Clean, recompile and start the mud up. Use cedit to create mpquestcomplete and mpquestclear and make sure to set their levels to 0. Also use cedit to create qstat if you added the code for it, this was intended to be an immortal level command so for most muds this should be set to level 51 or higher, depending on if you've changed how many levels you have. You should be all good to go :) This code was developed on and for a SmaugWiz 2.02 codebase using Microsoft Visual C++ 6. Our mud is highly modified but it should work fine on the stock code as well. I freely admit that this snippet started its life as someone else's work (Mike Counts aka GenMac) but aside from the name of the functions being the same the code is my own work. Use it as you wish, change it, give it to others. I offer no warranty or support, you use this code at your own risk. I double checked everything and think it's all there so good luck and enjoy as this can make for some pretty realistic mobs if you use it well :)