/
roa/
roa/lib/boards/
roa/lib/config/
roa/lib/edits/
roa/lib/help/
roa/lib/misc/
roa/lib/plrobjs/
roa/lib/quests/
roa/lib/socials/
roa/lib/www/
roa/lib/www/LEDSign/
roa/lib/www/LEDSign/fonts/
roa/lib/www/LEDSign/scripts/
roa/src/s_inc/
roa/src/sclient/
roa/src/sclient/binary/
roa/src/sclient/text/
roa/src/util/
/************************************************************************
	Realms of Aurealis 		James Rhone aka Vall of RoA

quest.c					OLQC - OnLine Quest Creation using
					the RoAOLCv2.1 interface standard.
					Loading/saving quests to disk.
					Player/immortal/mob interface for
					interaction with quests.  
					Basically everything having to do
					with automated questing is in this
					file.


		******** 100% Completely Original Code ********
		*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
		******** 100% Completely Original Code ********
		        All rights reserved henceforth. 

    Please note that no guarantees are associated with any code from
Realms of Aurealis.  All code which has been released to the general
public has been done so with an 'as is' pretense.  RoA is based on both
Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well
as the RoA license.   *** Read, Learn, Understand, Improve ***
*************************************************************************/
#include "conf.h"
#include "sysdep.h"

#include "structures.h"
#include "utils.h"
#include "comm.h"
#include "handler.h"
#include "acmd.h"
#include "db.h"
#include "mudlimits.h"
#include "interpreter.h"
#include "roaolc.h"
#include "lists.h"
#include "quest.h"
#include "global.h"

ROA_MENU(qedit_top_menu);
ROA_MENU(qedit_confirm_quit);
ROA_MENU(qedit_confirm_save);
ROA_MENU(qedit_confirm_qend);

extern char *qbits[];
extern char *rcbits[];

char *qtypes[] = {
  "Object Recovery",
  "Slay Mob",
  "\n"
};

// quests will be saved in lib/quest and each quest will be labelled such as
// <qslot>.qst
void boot_quests(void)
{
  int qnum;
  FILE *fp;
  char fname[MAX_INPUT_LENGTH];
  qslot *qptr;

  for (qnum = 0; qnum < NUM_QUESTS; qnum++)
  {
    sprintf(fname, "%s/%d.qst", QUEST_DIR, qnum);
    if (!(fp = fopen(fname, "rt")))
      continue;

    qptr = &qarray[qnum];
  
    qptr->qname = fread_string(fp, buf);
    qptr->qdesc = fread_string(fp, buf);
    qptr->char_notification = fread_string(fp, buf);
    qptr->zone_notification = fread_string(fp, buf);
    qptr->world_notification = fread_string(fp, buf);

    fscanf(fp, "%d %d %d\n", &qptr->qtype, &qptr->bitvector, &qptr->race_class_bitv);
    fscanf(fp, "%d %d %d %d %d\n", &qptr->days_to_complete, &qptr->exp_reward,
                                   &qptr->gold_reward, &qptr->qpts_reward,
                                   &qptr->obj_reward);

    fscanf(fp, "%d %d %d %d\n", &qptr->minlevel, &qptr->maxlevel, &qptr->qobj_vnum,
                                &qptr->num_qobjs);

    fscanf(fp, "%d %d %d\n", &qptr->qslaymob_vnum, &qptr->num_qmobs, &qptr->enemy_vnum);
    fclose(fp);
  }
}

void save_single_quest(int qnum)
{
  qslot *qptr;
  FILE *fp;
  char fname[MAX_INPUT_LENGTH];

  sprintf(fname, "%s/%d.qst", QUEST_DIR, qnum);
  if (!(fp = fopen(fname, "wt")))
    return;

  qptr = &qarray[qnum];

  if (qptr->qname)
    strcpy(buf, qptr->qname);
  else
    strcpy(buf, "BLANK");
  fputs(strcat(killr(buf), "~\n"), fp);

  if (qptr->qdesc)
    strcpy(buf, qptr->qdesc);
  else
    strcpy(buf, "BLANK");
  fputs(strcat(killr(buf), "~\n"), fp);

  if (qptr->char_notification)
    strcpy(buf, qptr->char_notification);
  else
    strcpy(buf, "BLANK");
  fputs(strcat(killr(buf), "~\n"), fp);

  if (qptr->zone_notification)
    strcpy(buf, qptr->zone_notification);
  else
    strcpy(buf, "BLANK");
  fputs(strcat(killr(buf), "~\n"), fp);

  if (qptr->world_notification)
    strcpy(buf, qptr->world_notification);
  else
    strcpy(buf, "BLANK");
  fputs(strcat(killr(buf), "~\n"), fp);

  fprintf(fp, "%d %d %d\n", qptr->qtype, qptr->bitvector, qptr->race_class_bitv);
  fprintf(fp, "%d %d %d %d %d\n", qptr->days_to_complete, qptr->exp_reward,
                                  qptr->gold_reward, qptr->qpts_reward,
                                  qptr->obj_reward);

  fprintf(fp, "%d %d %d %d\n", qptr->minlevel, qptr->maxlevel, qptr->qobj_vnum,
                               qptr->num_qobjs);

  fprintf(fp, "%d %d %d\n", qptr->qslaymob_vnum, qptr->num_qmobs, qptr->enemy_vnum);
  fclose(fp);
}

// if qnum == -1, save them all
void save_quests(int qnum)
{
  qslot *qptr;

  if (qnum < 0)
  {
    for ( ; qnum < NUM_QUESTS; qnum++)
    {
      qptr = &qarray[qnum];
      if (!qptr->qname || !*qptr->qname) continue;
      save_single_quest(qnum);
    }
  }
  else // specifying a particular qnum to save
  {
    if (qnum >= NUM_QUESTS)
    {
      mudlog("SYSERR: qnum out of range to save_quest", BRF, LEV_IMM, TRUE);
      return;
    }

    qptr = &qarray[qnum];
    if (!qptr->qname || !*qptr->qname)
    {
      sprintf(buf, "SYSERR:  Attempt to qsave with no name (#%d).", qnum);
      mudlog(buf, BRF, LEV_IMM, TRUE);
      return;
    } 
    save_single_quest(qnum);
  }
}

BOOL load_char_quests(chdata *ch)
{
  char fname[MAX_INPUT_LENGTH];
  int qnum;
  FILE *fp;

  if (!get_pdata_filename(GET_NAME(ch), "plrqst", fname))
    return FALSE;

  if (!(fp = fopen(fname, "rt")))
    return FALSE;

  while (!feof(fp))
  {
    fscanf(fp, "%d ", &qnum);
    if (qnum <= 0 || qnum >= NUM_QUESTS)
    {
      fscanf(fp, "%d %d %d %d %d\n", &qnum, &qnum, &qnum, &qnum, &qnum);  // discard
      continue;
    }
    else
      fscanf(fp, "%d %d %d %d %d\n", &PCQINFO(ch, qnum).in_progress,
			       &PCQINFO(ch, qnum).days_left,
			       &PCQINFO(ch, qnum).vnum_to_getslay,
			       &PCQINFO(ch, qnum).num_gottenslain ,
			       &PCQINFO(ch, qnum).num_times_completed);

    // this quest remains intact after logout
    if (PCQINFO(ch, qnum).in_progress && QFLAGGED(&qarray[qnum], Q_REMAINS))
    {
      ch->pc_specials->on_quest = qnum;
      ch->pc_specials->days_left= PCQINFO(ch, qnum).days_left;
      ch->pc_specials->vnum_to_getslay = PCQINFO(ch, qnum).vnum_to_getslay;
      ch->pc_specials->num_gottenslain = PCQINFO(ch, qnum).num_gottenslain;
    }
  }

  fclose(fp);
  return TRUE;
}

BOOL save_char_quests(chdata *ch)
{
  char fname[MAX_INPUT_LENGTH];
  int qnum;
  BOOL wrote = FALSE;
  FILE *fp;

  if (!get_pdata_filename(GET_NAME(ch), "plrqst", fname))
  {
    sprintf(buf, "SYSERR: Unable to qgetfname for %s.",GET_NAME(ch));
    mudlog(buf, BRF, GET_LEVEL(ch), TRUE);
    return FALSE;
  }

  if (!(fp = fopen(fname, "wt")))
  {
    sprintf(buf, "SYSERR: Unable to open %s for writing.",fname);
    mudlog(buf, BRF, GET_LEVEL(ch), TRUE);
    return FALSE;
  }

  for (qnum = 1; qnum < NUM_QUESTS; qnum++)
   if ((PCQINFO(ch, qnum).num_times_completed || PCQINFO(ch, qnum).in_progress))
   {
     // update current char pcqinfo
     if (ONQUEST(ch) == qnum)
     {
       PCQINFO(ch, qnum).in_progress = 1;
       PCQINFO(ch, qnum).days_left = ch->pc_specials->days_left;
       PCQINFO(ch, qnum).vnum_to_getslay = ch->pc_specials->vnum_to_getslay;
       PCQINFO(ch, qnum).num_gottenslain = ch->pc_specials->num_gottenslain;
     }

     wrote = TRUE;

     // if quest remains active, save everything, else just save qnum && times completed
     if (QFLAGGED(&qarray[qnum], Q_REMAINS))
       fprintf(fp, "%d %d %d %d %d ", qnum, PCQINFO(ch, qnum).in_progress,
               PCQINFO(ch, qnum).days_left, PCQINFO(ch, qnum).vnum_to_getslay,
               PCQINFO(ch, qnum).num_gottenslain);
     else
       fprintf(fp, "%d 0 0 0 0 ", qnum);

     fprintf(fp, "%d\n", PCQINFO(ch, qnum).num_times_completed);
   }
  
  fclose(fp);

  // if we didnt write anything, wax the file...
  if (!wrote)
    unlink(fname);

  return TRUE;
}

BOOL quest_being_editted(int qnum)
{
  dsdata *d = descriptor_list;

  for ( ; d; d=d->next)
  if (D_CHECK(d) && d->character->pc_specials->index == qnum &&
      d->character->pc_specials->q_editted)
    return TRUE;

  return FALSE;
}

/* yank those double subs chars out for act() */
void kill_quest_dollars(qslot *qptr)
{
  extern char *delete_doubledollar(char *string);

  qptr->char_notification = delete_doubledollar(qptr->char_notification);
  qptr->zone_notification = delete_doubledollar(qptr->zone_notification);
  qptr->world_notification = delete_doubledollar(qptr->world_notification);
}

void wax_quest(qslot *q)
{
  FREENULL(q->qname);
  FREENULL(q->qdesc);
  FREENULL(q->char_notification);
  FREENULL(q->zone_notification);
  FREENULL(q->world_notification);
}

void dupe_quest_over(qslot *src, qslot *dest)
{
  // first, wax the destination...
  wax_quest(dest);

  // memcpy will take care of the numbers
  memcpy(dest, src, sizeof(qslot));

  // now we have to dup the strings
  dest->qname = STR_DUP(src->qname);
  dest->qdesc = STR_DUP(src->qdesc);
  dest->char_notification = STR_DUP(src->char_notification);
  dest->zone_notification = STR_DUP(src->zone_notification);
  dest->world_notification= STR_DUP(src->world_notification);
}

// this simply 0s out all current quest info except num_times_completed for this character
// PCQINFO was not getting cleared properly, dont dust ONQUEST until 
// we zero out PCQINFO  6/23/98 -jtrhone
void dust_current_quest(chdata *ch)
{
  PCQINFO(ch, ONQUEST(ch)).in_progress = FALSE;
  PCQINFO(ch, ONQUEST(ch)).days_left = 0;
  PCQINFO(ch, ONQUEST(ch)).vnum_to_getslay = 0;
  PCQINFO(ch, ONQUEST(ch)).num_gottenslain = 0;

  QUEST_TIME(ch) = 0;
  QVNUM(ch) = 0;
  QGOTTEN(ch) = 0;  
  ONQUEST(ch) = FALSE;
}

ACMD(do_qedit)
{
  int qnum;
  char buf[MAX_INPUT_LENGTH];

  if (IS_NPC(ch))
    return;

  one_argument(argument, buf);

  if (!*buf)
  {
    send_to_char("Usage: qedit <quest slot #>\n\r", ch);
    return;
  }

  if (!is_number(buf) || (qnum = atoi(buf)) < 0 || qnum >= NUM_QUESTS)
  {
    send_to_char("Usage: qedit <quest slot #>\n\r", ch);
    return;
  }

  if (quest_being_editted(qnum))
  {
    send_to_char("That quest slot is currently being editted.\n\r",ch);
    return;
  }

  ch->pc_specials->index = qnum;
  CREATE(ch->pc_specials->q_editted, qslot, 1);
  dupe_quest_over(&qarray[qnum], QUEST_EDITTED(ch));

  SET_BIT(PLR_FLAGS(ch), PLR_BUILDING);
  MENU_DEPTH(ch) = 0;
  ch->pc_specials->field_changed = FALSE;
  menu_jump(ch, qedit_top_menu);
}

ROA_MENU(qedit_top_menu)
{
    char buf[MAX_STRING_LENGTH];
    char buf2[MAX_STRING_LENGTH];
    char *p;
    int field;
    qslot *q = QUEST_EDITTED(ch);
    int index = ch->pc_specials->index;

    if (!input_str)
    {
        menu_title_send("QuestEdit Main Menu", ch);
	sprintf(buf, " X.) %%6Qslot%%0: %d\n\r", index); S2C();
        sprintf(buf, " 1.) %%6QName%%0: %s\n\r", q->qname); S2C();
        sprintf(buf, " 2.) %%6QDesc%%0: \n\r%s\n\r", q->qdesc); S2C();
	sprinttype(q->qtype, qtypes, buf2);
        sprintf(buf, " 3.) %%6QType%%0: %s\n\r", buf2); S2C();
        sprintbit(q->bitvector, qbits, buf2);
        sprintf(buf, " 4.) %%6QFlags%%0: %%5%s%%0\n\r", buf2); S2C();
        sprintbit(q->race_class_bitv, rcbits, buf2);
        sprintf(buf, " 5.) %%6QRCFlags%%0: %%5%s%%0\n\r", buf2); S2C();

        if (q->qtype == QTYPE_OBJ_RETURN)
 	{
          sprintf(buf, " 6.) %%6QObj vnum%%0: %d\n\r", q->qobj_vnum); S2C();
          sprintf(buf, " 7.) %%6# of QObjs%%0: %d\n\r", q->num_qobjs); S2C();
	}
	else
	if (q->qtype == QTYPE_SLAYMOB)
	{
          sprintf(buf, " 6.) %%6QMob vnum%%0: %d\n\r", q->qslaymob_vnum); S2C();
          sprintf(buf, " 7.) %%6# of QMobs to slay%%0: %d\n\r", q->num_qmobs); S2C();
	}

        if (QFLAGGED(q, Q_HAS_TIMELIMIT))
	{
          sprintf(buf, " 8.) %%6Days to complete%%0: %d\n\r", q->days_to_complete);
	  S2C();
	}

        if (QFLAGGED(q, Q_MIN_LEVEL))
	{
          sprintf(buf, " 9.) %%6Quest Minlevel%%0: %d\n\r", q->minlevel);
	  S2C();
	}

        if (QFLAGGED(q, Q_MAX_LEVEL))
	{
          sprintf(buf, "10.) %%6Quest Maxlevel%%0: %d\n\r", q->maxlevel);
	  S2C();
	}

        if (QFLAGGED(q, Q_ENEMY_AFTER) || QFLAGGED(q, Q_ENEMY_DURING))
	{
          sprintf(buf, "11.) %%6QEnemy vnum%%0: %d\n\r", q->enemy_vnum);
	  S2C();
	}

        if (QFLAGGED(q, Q_REWARD_GOLD))
	{
          sprintf(buf, "12.) %%6%s Reward%%0: %d\n\r", currency_name_plural, q->gold_reward);
	  S2C();
	}
        if (QFLAGGED(q, Q_REWARD_EXP))
	{
          sprintf(buf, "13.) %%6Exp Reward%%0: %d\n\r", q->exp_reward);
	  S2C();
	}
        if (QFLAGGED(q, Q_REWARD_QPTS))
	{
          sprintf(buf, "14.) %%6Quest Pts Reward%%0: %d\n\r", q->qpts_reward);
	  S2C();
	}
        if (QFLAGGED(q, Q_REWARD_OBJECT))
	{
          sprintf(buf, "15.) %%6Obj Reward vnum%%0: %d\n\r", q->obj_reward);
	  S2C();
	}

	// before we send dollar possible strings, wax the dollars
	kill_quest_dollars(q);

	if (QFLAGGED(q, Q_NOTIFY_CHAR))
	{
          sprintf(buf, "16.) %%6Char Notification%%0:\n\r%s\n\r", q->char_notification);
	  S2C();
	}

	if (QFLAGGED(q, Q_NOTIFY_ZONE))
	{
          sprintf(buf, "17.) %%6Zone Notification%%0:\n\r%s\n\r", q->zone_notification);
	  S2C();
	}

	if (QFLAGGED(q, Q_NOTIFY_WORLD))
	{
          sprintf(buf, "18.) %%6World Notification%%0:\n\r%s\n\r",q->world_notification);
	  S2C();
	}

        send_to_char("\n\r", ch);
        MENU_PROMPT(ch) = "Enter field number to change or 0 to end: ";
        return;
    }

    strcpy(buf, input_str);
    p = strtok(buf, "   \n\r");
    if (p)
        field = atoi(p);
    else
        field = 0;

    switch (field)
    {
      case 0:
        menu_jump(ch, qedit_confirm_quit);
        break;

      case 1: 
        do_string_arg(ch, "Enter new quest name:\n\r", &q->qname, "");
	break;

      case 2:
        do_long_string_arg(ch, "Enter quest description (@):\n\r", &q->qdesc);
        break;

      case 3:
        get_integer_list(ch, "Enter number of the desired type: ",
                         &q->qtype, sizeof(q->qtype), qtypes);
        break;

      case 4:
        toggle_menu(ch, "Enter quest bit to toggle: ", &q->bitvector, qbits);
        break;

      case 5:
        toggle_menu(ch, "Enter quest race/class bit to toggle: ", &q->race_class_bitv, rcbits);
        break;

      case 6:
        if (q->qtype == QTYPE_OBJ_RETURN)
 	{
          GET_INTEGER_ARG(ch, "Enter vnum of quest object to recover: ",
                          q->qobj_vnum, 0, 99999);
        }
        else
        if (q->qtype == QTYPE_SLAYMOB)
 	{
          GET_INTEGER_ARG(ch, "Enter vnum of mob to slay: ",
                          q->qslaymob_vnum, 0, 99999);
        }
        break;

      case 7:
        if (q->qtype == QTYPE_OBJ_RETURN)
 	{
          GET_INTEGER_ARG(ch, "Enter number of quest objects to recover: ",
                          q->num_qobjs, 0, 100);
        }
        else
        if (q->qtype == QTYPE_SLAYMOB)
 	{
          GET_INTEGER_ARG(ch, "Enter number of mobs to slay: ",
                          q->num_qmobs, 0, 100);
        }
        break;

      case 8:
        if (QFLAGGED(q, Q_HAS_TIMELIMIT))
          GET_INTEGER_ARG(ch, "Enter number of days to complete: ",
                          q->days_to_complete, 1, 100);
        break;

      case 9:
        if (QFLAGGED(q, Q_MIN_LEVEL))
          GET_INTEGER_ARG(ch, "Enter quest minlevel: ", q->minlevel, 0, LEV_IMPL);
        break;

      case 10:
        if (QFLAGGED(q, Q_MAX_LEVEL))
          GET_INTEGER_ARG(ch, "Enter quest maxlevel: ", q->maxlevel, 0, LEV_IMPL);
        break;

      case 11:
        if (QFLAGGED(q, Q_ENEMY_AFTER) || QFLAGGED(q, Q_ENEMY_DURING))
          GET_INTEGER_ARG(ch, "Enter enemy mob vnum: ", q->enemy_vnum, 0, 99999);
        break;

      case 12:
        if (QFLAGGED(q, Q_REWARD_GOLD))
          GET_INTEGER_ARG(ch, "Enter monetary reward amount: ", q->gold_reward, 0, 999999);
        break;

      case 13:
        if (QFLAGGED(q, Q_REWARD_EXP))
          GET_INTEGER_ARG(ch, "Enter exp reward amount: ", q->exp_reward, 0, 999999);
        break;

      case 14:
        if (QFLAGGED(q, Q_REWARD_QPTS))
          GET_INTEGER_ARG(ch, "Enter quest pts reward amount: ",q->qpts_reward,0,99);
        break;

      case 15:
        if (QFLAGGED(q, Q_REWARD_OBJECT))
          GET_INTEGER_ARG(ch, "Enter reward obj vnum: ", q->obj_reward, 0, 99999);
        break;

      case 16:
	if (QFLAGGED(q, Q_NOTIFY_CHAR))
          do_long_string_arg(ch, "Enter char completion notification (@):\n\r", 
 			     &q->char_notification);
        break;

      case 17:
	if (QFLAGGED(q, Q_NOTIFY_ZONE))
          do_long_string_arg(ch, "Enter zone completion notification (@):\n\r", 
 			     &q->zone_notification);
        break;

      case 18:
	if (QFLAGGED(q, Q_NOTIFY_WORLD))
          do_long_string_arg(ch, "Enter world completion notification (@):\n\r", 
 			     &q->world_notification);
        break;

      default:
        send_to_char("That field cannot be changed.\n\r", ch);
        break;
    }

    ch->pc_specials->field_changed = TRUE;
}

ROA_MENU(qedit_confirm_quit)
{
  char buf[MAX_STRING_LENGTH];
  char *p;

  if (!input_str)
  {
    MENU_PROMPT(ch) = "Quit editting quest (yes/NO)? ";
    return;
  }

  strcpy(buf, input_str);
  p = strtok(buf, "   \n\r");
  if (p && strncasecmp("yes", p, strlen(p)) == 0)
    menu_jump(ch, qedit_confirm_save);
  else
    menu_jump(ch, qedit_top_menu);
}

ROA_MENU(qedit_confirm_save)
{
   char buf[MAX_STRING_LENGTH];
   char *p;
   qslot *q = QUEST_EDITTED(ch);
   int index = ch->pc_specials->index;

   if (!input_str)
   {
     MENU_PROMPT(ch) = "Save editted quest (YES/no)? ";
     return;
   }

   strcpy(buf, input_str);
   p = strtok(buf, "   \n\r");
   if (!p || strncasecmp("no", p, strlen(p)) != 0)
   {
     dupe_quest_over(q, &qarray[index]);
     save_quests(index);
     sprintf(buf, "PLRUPD: %s has editted quest %d", GET_NAME(ch), index);
     mudlog(buf, NRM, GET_LEVEL(ch), TRUE);
   }

   wax_quest(QUEST_EDITTED(ch));
   FREENULL(QUEST_EDITTED(ch));
   MENU_PROMPT(ch)  = NULL;
   MENU_HANDLER(ch) = NULL;
   MENU_DEPTH(ch)   = 0;
   ch->pc_specials->index = -1;
   REMOVE_BIT(PLR_FLAGS(ch), PLR_BUILDING);
}

ROA_MENU(qedit_confirm_qend)
{
   char buf[MAX_STRING_LENGTH];
   char *p;
   int index = ONQUEST(ch);

   if (!input_str)
   {
     MENU_PROMPT(ch) = "\n\rConfirm quest end (YES/no)? ";
     return;
   }

   strcpy(buf, input_str);
   p = strtok(buf, "   \n\r");
   if (!p || strncasecmp("no", p, strlen(p)) != 0)
   {
     sprintf(buf, "PLRUPD: %s has manually ended quest %d.", GET_NAME(ch), index);
     mudlog(buf, BUG, GET_LEVEL(ch), TRUE);
     send_to_char("Quest end confirmed.\n\r",ch);
     dust_current_quest(ch);
   }
   else
   {
     send_to_char("Quest end not confirmed.\n\r",ch);
   }

   MENU_PROMPT(ch)  = NULL;
   MENU_HANDLER(ch) = NULL;
   MENU_DEPTH(ch)   = 0;
}

// *****************************************************
// Player/Mob/Immortal quest commands...
// *****************************************************

// return TRUE if someone else is doing this quest currently...
BOOL being_done(int qnum)
{
  chdata *ch;
  for (ch = character_list; ch; ch=ch->next)
    if (IS_PC(ch) && ch->pc_specials->on_quest == qnum)
      return TRUE;
  return FALSE;
}

BOOL check_quest_rcflags(chdata *ch, qslot *qptr)
{
  if (QRCFLAGGED(qptr, Q_NO_WARRIOR) && IS_NAT_WARRIOR(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_CLERIC) && IS_NAT_CLERIC(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_MAGE) && IS_NAT_MAGE(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_THIEF) && IS_NAT_THIEF(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_SHAMAN) && IS_NAT_SHAMAN(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_RANGER) && IS_NAT_RANGER(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_WARLOCK) && IS_NAT_WARLOCK(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_BARD) && IS_NAT_BARD(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_MONK) && IS_NAT_MONK(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_MADEPT) && IS_NAT_MADEPT(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_DRUID) && IS_NAT_DRUID(ch))
    return FALSE;


  if (QRCFLAGGED(qptr, Q_NO_HUMAN) && IS_HUMAN(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_ELF) && IS_ELF(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_HALF_ELF) && IS_HALF_ELF(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_ORC) && IS_ORC(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_OGRE) && IS_OGRE(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_DROW) && IS_DROW(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_DWARF) && IS_DWARF(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_PIXIE) && IS_PIXIE(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_NIXIE) && IS_NIXIE(ch))
    return FALSE;
  if (QRCFLAGGED(qptr, Q_NO_DRAGON) && IS_DRAGON(ch))
    return FALSE;
  return TRUE;
}

ACMD(do_qscore)
{
  int qnum;

  if (IS_NPC(ch))
  {
    send_to_char("Go away, you're bugging me.\n\r",ch);
    return;
  }

  strcpy(buf, "%B%6Completed Quests%0:\n\r"); 
  strcat(buf, "%6 #         Quest Short Descrip         Times Completed%0\n\r");
  strcat(buf, "%6---        -------------------         ---------------%0\n\r");
  for (qnum = 1; qnum < NUM_QUESTS; qnum++)
    if (PCQINFO(ch, qnum).num_times_completed > 0)
    {
      sprintf(buf+strlen(buf), "%%5%3d%%0        %-30.30s    %2d\n\r",
              qnum, qarray[qnum].qname, PCQINFO(ch, qnum).num_times_completed);
    }

  if (ONQUEST(ch) && VALID_QUEST(ONQUEST(ch)))
  {
    sprintf(buf+strlen(buf), 
	    "\n\r%%B%%6Current Quest%%0:\n\r%%5%3d%%0        %-30.30s    %2d\n\r",
	    ONQUEST(ch), qarray[ONQUEST(ch)].qname, 
	    PCQINFO(ch, ONQUEST(ch)).num_times_completed);

    if (QFLAGGED(&qarray[ONQUEST(ch)], Q_HAS_TIMELIMIT))
      sprintf(buf+strlen(buf),"%%B%%6Days left to complete quest%%0: %d\n\r", QUEST_TIME(ch));
  }

  page_string(ch->desc, buf, 1);
}

// list short names and numbers of quests...
ACMD(do_qlist)
{
  int qnum;
  qslot *qptr;
  BOOL found = FALSE;
  char buf[20000];

  sprintf(buf, "%%B%%6%s Quest Listing:%%0\n\r", shortmudname);

  for (qnum = 0; qnum < NUM_QUESTS; qnum++)
  {
    qptr = &qarray[qnum];
    if (!qptr->qname || !*qptr->qname || !QFLAGGED(qptr, Q_ACTIVATED))
      continue;
    found = TRUE; 

    sprintf(buf+strlen(buf), 
       "%%B%%6%3d%%0 - %-40.40s  %%6Min%%0: %2d  %%6Max%%0: %2d  %%6Qpts%%0: %2d\n\r",
       qnum, qptr->qname, qptr->minlevel, qptr->maxlevel, qptr->qpts_reward); 
  }

  if (!found)
    strcat(buf, "%B%1No active quests available.%0\n\r");
  page_string(ch->desc, buf, 1);
}

void show_if_can_quest(chdata *ch, qslot *qptr, int qnum)
{
  BOOL can = TRUE;

  if (ONQUEST(ch) == qnum)
  {
    send_to_char("\n\rYou are currently performing this quest.\n\r",ch);
    return;
  }

  send_to_char("\n\r", ch);

  if (QFLAGGED(qptr, Q_SOLO) && being_done(qnum))
  {
    send_to_char("Quest is SOLO and is being performed by someone.\n\r",ch);
    can = FALSE;
  }

  if (!check_quest_rcflags(ch, qptr))
  {
    send_to_char("Quest restricts your race or class.\n\r",ch);
    can = FALSE;
  }

  if (QFLAGGED(qptr, Q_MIN_LEVEL) && GET_LEVEL(ch) < qptr->minlevel)
  {
    send_to_char("You are too low a level to perform this quest.\n\r",ch);
    can = FALSE;
  }

  if (QFLAGGED(qptr, Q_MAX_LEVEL) && GET_LEVEL(ch) > qptr->maxlevel)
  {
    send_to_char("You are too high a level to perform this quest.\n\r",ch);
    can = FALSE;
  }

  if (QFLAGGED(qptr, Q_ONE_TIMEONLY) && PCQINFO(ch, qnum).num_times_completed)
  {
    send_to_char("Quest is one_time_only and you have completed it.\n\r",ch);
    can = FALSE;
  }

  if (can)
    send_to_char("You can perform this quest.\n\r",ch);
  else
    send_to_char("You cannot perform this quest.\n\r",ch);
}

ACMD(do_qdescribe)
{
  char *argu = argument;
  qslot *qptr;
  int qnum;

  skip_spaces(&argu);
  if (!*argu || !is_number(argu))
  {
    send_to_char("Usage: qdescribe <active quest #>.\n\r",ch);
    return;
  }

  qnum = atoi(argu);

  if (!VALID_QUEST(qnum))
  {
    send_to_char("Usage: qdescribe <active quest #>.\n\r",ch);
    return;
  }
 
  qptr = &qarray[qnum];
  if (!QFLAGGED(qptr, Q_ACTIVATED))
  {
    send_to_char("Usage: qdescribe <active quest #>.\n\r",ch);
    return;
  }

  if (!qptr->qname || !*qptr->qname || !qptr->qdesc || !*qptr->qdesc )
  {
    send_to_char("Invalid quest name or description.  Notify immortal.\n\r",ch);
    sprintf(buf, "SYSERR: Invalid qname or qdesc on quest #%d.", qnum);
    mudlog(buf, BRF, LEV_IMM, TRUE);
    return;
  }

  sprintf(buf, "%%B%%6Quest Description%%0:\n\r%s (#%d)\n\r",qptr->qname,qnum);
  S2C();
  page_string(ch->desc, qptr->qdesc, 1);
  show_if_can_quest(ch, qptr, qnum);
}

void start_mobvnum_hunting(int enemy_vnum, chdata *vict)
{
  chdata *tmpvict;

  for (tmpvict = character_list; tmpvict; tmpvict = tmpvict->next)
    if (IS_NPC(tmpvict) && GET_MOB_VNUM(tmpvict) == enemy_vnum)
    {
	sprintf(buf, "SYSUPD: %s (#%d) setting hunt target to %s.", GET_NAME(tmpvict),
		GET_MOB_VNUM(tmpvict), GET_NAME(vict));
        mudlog(buf, BUG, LEV_IMM, TRUE); 
	HUNTING(tmpvict) = vict;
    }
}

#define QBUSAGE "Usage: qbegin <vict name> <qslot>.\n\r"

ACMD(do_qbegin)
{
  int qnum;
  qslot *qptr;
  char *argu = argument;
  chdata *vict;
  char name[MAX_INPUT_LENGTH], numstr[MAX_INPUT_LENGTH];

  if (IS_PC(ch) && GET_LEVEL(ch) < LEV_GOD)
  {
    send_to_char("Huh?!?\n\r",ch);
    return;
  }

  skip_spaces(&argu);
  if (!*argu)
  {
    send_to_char(QBUSAGE, ch);
    return;
  }

  half_chop(argu, name, numstr);
  if (!(vict = get_char_room_vis(ch, name)))
  {
    send_to_char("Who?\n\r",ch);
    return;
  }

  if (vict->pc_specials->on_quest > 0)
  {
    sprintf(buf, "$N is already doing quest %d.", vict->pc_specials->on_quest);
    act(buf, FALSE, ch, 0, vict, TO_CHAR);
    act("You must finish your current quest or %Bqend%0 first to accept another.", 
	FALSE, ch, 0, vict, TO_VICT);
    return;
  }

  if (IS_NPC(ch))
    qnum = ch->npc_specials.qnum;
  else
  if (*numstr && is_number(numstr))
    qnum = atoi(numstr);
  else
  {
    send_to_char(QBUSAGE, ch);
    return;
  }

  if (qnum <= 0 || qnum >= NUM_QUESTS)
  {
    send_to_char("You supplied an invalid quest number.\n\r",ch);
    if (IS_NPC(ch))
    {
      sprintf(buf, "SYSERR: %s (#%d) supplied invalid quest number to %s.", 
              GET_NAME(ch), GET_MOB_VNUM(ch), GET_NAME(vict));
      mudlog(buf, BRF, LEV_IMM, TRUE);
    }
    return;
  }

  qptr = &qarray[qnum];
  if (!QFLAGGED(qptr, Q_ACTIVATED))
  {
    send_to_char("You supplied an inactive quest number.\n\r",ch);
    if (IS_NPC(ch))
    {
      sprintf(buf, "SYSERR: %s (#%d) supplied inactive quest number (%d) to %s.", 
              GET_NAME(ch), GET_MOB_VNUM(ch), qnum, GET_NAME(vict));
      mudlog(buf, BRF, LEV_IMM, TRUE);
    }
    return;
  }

  if (QFLAGGED(qptr, Q_SOLO) && being_done(qnum))
  {
    send_to_char("That quest is SOLO and is being performed by someone.\n\r",ch);
    return;
  }

  if (!check_quest_rcflags(vict, qptr))
    return;

  if (QFLAGGED(qptr, Q_MIN_LEVEL) && GET_LEVEL(vict) < qptr->minlevel)
  {
    act("$N is too low a level to perform that quest.", FALSE, ch, 0, vict, TO_CHAR);
    return;
  }

  if (QFLAGGED(qptr, Q_MAX_LEVEL) && GET_LEVEL(vict) > qptr->maxlevel)
  {
    act("$N is too high a level to perform that quest.", FALSE, ch, 0, vict, TO_CHAR);
    return;
  }

  if (QFLAGGED(qptr, Q_ONE_TIMEONLY) && PCQINFO(vict, qnum).num_times_completed)
  {
    act("$N has already completed that ONE_TIME_ONLY quest.",FALSE,ch,0,vict,TO_CHAR);
    return;
  }

  // ok, checks passed transfer data to character
  switch (qptr->qtype) {
    case QTYPE_OBJ_RETURN:  
      sprintf(buf, "SYSUPD: %s started %s on quest %d.", GET_NAME(ch),
	      GET_NAME(vict),qnum);
      mudlog(buf, BUG, GET_LEVEL(ch), TRUE); 
      break;

    case QTYPE_SLAYMOB:  
      sprintf(buf, "SYSUPD: %s started %s on quest %d.", GET_NAME(ch),
              GET_NAME(vict),qnum);
      mudlog(buf, BUG, GET_LEVEL(ch), TRUE); 
      break;

    default:  
      sprintf(buf, "SYSERR: Invalid quest type %d on quest %d.",qptr->qtype,qnum);
      mudlog(buf, BRF, LEV_IMM, TRUE); 
      return;
  }

  ONQUEST(vict) = qnum;

  if (QFLAGGED(qptr, Q_HAS_TIMELIMIT))
    QUEST_TIME(vict) = qptr->days_to_complete;
 
  // if a specific mob begins the hunt, do it here
  if (QFLAGGED(qptr, Q_ENEMY_DURING))
    start_mobvnum_hunting(qptr->enemy_vnum, vict);

  // let vict know they just started
  sprintf(buf, "%%BYou have just started quest #%d.%%0\n\r",qnum);
  send_to_char(buf, vict);

  send_to_char("Ok, quest assigned.\n\r",ch);
}

ACMD(do_qend)
{
  if (IS_NPC(ch))
  {
    send_to_char("Yeah.\n\r",ch);
    return;
  }

  if (!ONQUEST(ch))
  {
    send_to_char("You're not currently performing any quests.\n\r",ch);
    return;
  }

  if (!VALID_QUEST(ONQUEST(ch)))
  {
    send_to_char("Invalid quest number.  Report to an immortal.\n\r",ch);
    return;
  }

  menu_jump(ch, qedit_confirm_qend);
}

void give_quest_reward(chdata *ch, int qnum)
{
  qslot *qptr = &qarray[qnum];
  obdata *obj = NULL;

  sprintf(buf, "%%B%%6Quest Rewards%%0: %s (#%d)\n\r", 
	  qptr->qname ? qptr->qname : "INVALID QUEST NAME", qnum);

  if (QFLAGGED(qptr, Q_REWARD_QPTS))
  {
    sprintf(buf+strlen(buf), "You receive %%6%d%%0 quest points.\n\r", qptr->qpts_reward);
    ch->pc_specials->saved.quest_pts += qptr->qpts_reward;
  }

  if (QFLAGGED(qptr, Q_REWARD_EXP))
  {
    sprintf(buf+strlen(buf), "You receive %%6%d%%0 experience points.\n\r", qptr->exp_reward);
    gain_exp(ch, qptr->exp_reward);
  }

  if (QFLAGGED(qptr, Q_REWARD_GOLD))
  {
    sprintf(buf+strlen(buf), "You receive %%6%d%%0 %s.\n\r", qptr->gold_reward, currency_name_plural);
    GET_GOLD(ch) += qptr->gold_reward;
  }

  if (QFLAGGED(qptr, Q_REWARD_OBJECT))
  {
    if ((obj = read_object(qptr->obj_reward, VIRTUAL)))
    {
      sprintf(buf+strlen(buf),"You receive %s.\n\r",obj->shdesc);
      obj_to_char(obj, ch);
    }
    else
    {
      sprintf(buf+strlen(buf), "%%B%%1WARNING:  Reward object vnum error (%d).%%0",
	      qptr->obj_reward);
      sprintf(buf2, "SYSERR: Invalid obj_reward for quest #%d.",qnum); 
      mudlog(buf2, BRF, LEV_IMM, TRUE);
    } 
  }

  page_string(ch->desc, buf, 1);
}

// this gets called when character finishes the quest he/she is workin on
// it could be immediately or after some other action like returning the
// quest object to the rewarder
void do_complete_quest(chdata *ch, chdata *rewarder)
{
  int qnum = ONQUEST(ch);
  qslot *qptr = &qarray[qnum];
  chdata *vict = NULL;
  obdata *obj = NULL;
  int rnum;

  switch (qptr->qtype) {
    case QTYPE_OBJ_RETURN:  
      if ((rnum = real_object(qptr->qobj_vnum)) <= 0)
      {
        sprintf(buf, "SYSERR: Invalid qobj_vnum (%d) on quest #%d, not completed.", qptr->qobj_vnum, qnum);
        mudlog(buf, BRF, LEV_IMM, TRUE);
        sprintf(buf, "SYSERR: %s lost possible #%d quest completion.",GET_NAME(ch), qnum);
        mudlog(buf, BRF, LEV_IMM, TRUE);
        return;
      }
      else
        obj = &obj_proto[rnum];
      break;

    case QTYPE_SLAYMOB:  
      if ((rnum = real_mobile(qptr->qslaymob_vnum)) <= 0)
      {
        sprintf(buf, "SYSERR: Invalid qslaymob (%d) on quest #%d, not completed.", qptr->qslaymob_vnum, qnum);
        mudlog(buf, BRF, LEV_IMM, TRUE);
        sprintf(buf, "SYSERR: %s lost possible #%d quest completion.",GET_NAME(ch), qnum);
        mudlog(buf, BRF, LEV_IMM, TRUE);
        return;
      }
      else
        vict = &mob_proto[rnum];
      break;

    default:
      break;
  }

  if (QFLAGGED(qptr, Q_NOTIFY_CHAR) && qptr->char_notification && *qptr->char_notification)
    act(qptr->char_notification, FALSE, ch, obj, vict, TO_CHAR);

  if (QFLAGGED(qptr, Q_NOTIFY_ZONE) && qptr->zone_notification && *qptr->zone_notification)
    act(qptr->zone_notification, FALSE, ch, obj, vict, TO_ZONE_MORTLOG);

  if (QFLAGGED(qptr, Q_NOTIFY_WORLD) && qptr->world_notification && *qptr->world_notification)
    act(qptr->world_notification, FALSE, ch, obj, vict, TO_WORLD_MORTLOG);

  if (QFLAGGED(qptr, Q_REWARD_EXP | Q_REWARD_GOLD | Q_REWARD_QPTS | Q_REWARD_OBJECT))
    give_quest_reward(ch, qnum);

  if (QFLAGGED(qptr, Q_ENEMY_AFTER))
    start_mobvnum_hunting(qptr->enemy_vnum, ch);

  if (QFLAGGED(qptr, Q_MOB_GOBYEBYE) && rewarder && IS_NPC(rewarder))
  {
    act("$n suddenly vanishes!", TRUE, rewarder, 0, 0, TO_ROOM);
    extract_char(rewarder);
  }

  sprintf(buf, "SYSUPD: %s completed quest #%d.", GET_NAME(ch), qnum);
  mudlog(buf, BUG, GET_LEVEL(ch), TRUE);

  // update pcqinfo
  PCQINFO(ch, qnum).num_times_completed++;
  dust_current_quest(ch);
}

ACMD(do_qcomplete)
{
  chdata *vict;
  char *argu = argument;

  if (IS_NPC(ch))
    return;

  skip_spaces(&argu);
  if (!*argu || !(vict = get_char_vis(ch, argu)))
  {
    send_to_char("Who?\n\r",ch);
    return;
  }

  if (IS_NPC(vict) || !ONQUEST(vict))
  {
    act("$N isn't performing any quests.",FALSE,ch,0,vict,TO_CHAR);
    return;
  }

  send_to_char("Ok.\n\r",ch);
  do_complete_quest(vict, ch);
}

void do_fail_quest(chdata *ch)
{
  send_to_char("%B%1You have failed in your quest...%0\n\r",ch);
  sprintf(buf, "PLRUPD: %s has failed quest %d.", GET_NAME(ch), ONQUEST(ch));
  mudlog(buf, BUG, GET_LEVEL(ch), TRUE);
  dust_current_quest(ch);
}

void check_player_quest(chdata *ch, BOOL day_passed)
{
  qslot *qptr;
  
  qptr = &qarray[ONQUEST(ch)];
  if (QFLAGGED(qptr, Q_HAS_TIMELIMIT))
  {
    if (day_passed)
      QUEST_TIME(ch)--;
    if (QUEST_TIME(ch) <= 0)
    {
      send_to_char("%B%1Time has escaped you...%0\n\r",ch);
      do_fail_quest(ch); 
    }
    else
    if (QUEST_TIME(ch) == 1)
      send_to_char("%B%6You have one day left to complete your quest, adventurer.%0\n\r",ch);
    else
    if (QUEST_TIME(ch) == 7)
      send_to_char("%B%6You have one week left to complete your quest, adventurer.%0\n\r",ch);
  }
}

// called from weather.c after a new day dawns
void check_char_quests(void)
{
  chdata *ch;

  for (ch=character_list; ch; ch=ch->next)
    if (IS_PC(ch) && ONQUEST(ch) && VALID_QUEST(ONQUEST(ch)))
      check_player_quest(ch, TRUE);
}

// ****************************************************** * * * * 
// The following functions are called from scattered points throughout
// the mud.  Different areas in which particular types of quests can
// be completed... -roa
// ****************************************************** * * * * 

// if char and mob quests sync up, return TRUE, else FALSE
BOOL char_mob_questmatch(chdata *ch, chdata *vict)
{
  if (IS_NPC(ch) || !IS_NPC(vict) || !MOB_FLAGGED(vict, MOB_QUESTOR))
    return FALSE;

  if (!ONQUEST(ch) || !VALID_QUEST(ONQUEST(ch)))
    return FALSE;
  
  if (ONQUEST(ch) != vict->npc_specials.qnum)
    return FALSE;

  return TRUE;
}

// for QTYPE_OBJ_RETURN on give
int qcheck_obj_give(chdata *ch, chdata *vict, obdata *obj)
{
  int qnum;
  qslot *qptr;

  if (!char_mob_questmatch(ch, vict))
    return FALSE;

  qnum = ONQUEST(ch);
  qptr = &qarray[qnum]; 

  if (qptr->qtype != QTYPE_OBJ_RETURN)
    return FALSE;  
 
  if (GET_OBJ_VNUM(obj) != qptr->qobj_vnum)
    return FALSE;

  if (!(++QGOTTEN(ch) >= qptr->num_qobjs))
    return FALSE;
  
  do_complete_quest(ch, vict);
  
  return TRUE;
}

// for Q_IMMEDIATE QTYPE_OBJ_RETURN on get
int qcheck_obj_get(chdata *ch, obdata *obj)
{
  int qnum;
  qslot *qptr;

  qnum = ONQUEST(ch);
  
  if (!qnum || !VALID_QUEST(qnum))
    return FALSE;

  qptr = &qarray[qnum]; 

  if (qptr->qtype != QTYPE_OBJ_RETURN || !QFLAGGED(qptr, Q_IMMEDIATE_REWARD))
    return FALSE;  
 
  if (GET_OBJ_VNUM(obj) != qptr->qobj_vnum)
    return FALSE;

  if (!(++QGOTTEN(ch) >= qptr->num_qobjs))
    return FALSE;
  
  do_complete_quest(ch, NULL);
  
  return TRUE;
}

//  for Q_IMMEDIATE QTYPE_SLAYMOB quests, compare vs mob vnum ch just killed
int qcheck_slaymob(chdata *ch, int mob_vnum)
{
  int qnum;
  qslot *qptr;

  qnum = ONQUEST(ch);
  
  if (!qnum || !VALID_QUEST(qnum))
    return FALSE;

  qptr = &qarray[qnum]; 

  // SLAYMOBS M U S T be IMMEDIATE_REWARD for now
  if (qptr->qtype != QTYPE_SLAYMOB || !QFLAGGED(qptr, Q_IMMEDIATE_REWARD))
    return FALSE;  
 
  if (mob_vnum != qptr->qslaymob_vnum)
    return FALSE;

  if (!(++QGOTTEN(ch) >= qptr->num_qmobs))
    return FALSE;
  
  do_complete_quest(ch, NULL);
  
  return TRUE;
}