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