dystopia/doc/
dystopia/notes/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Dystopia Mud improvements copyright (C) 2000, 2001 by Brian Graversen  *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/***************************************************************************
 *  Dystopian Questcode copyright (C) 2001 by Brian Graversen, users must  *
 *  follow the DIKU, Merc and Godwars license as well as the license       *
 *  distributed with Dystopia                                              *
 ***************************************************************************/

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"

int  get_rand_mob              args (( void ));
int  get_rand_hard_mob         args (( void ));
int  get_rand_item             args (( void ));
int  load_special_item         args (( CHAR_DATA *ch ));
int  get_lbound                args (( void ));
int  get_ubound                args (( int bound ));
void give_token                args (( CHAR_DATA *questmaster, CHAR_DATA *ch, int value ));

QUEST_DATA *quest_free;

DECLARE_QUEST_FUN( questspec_generic		);
DECLARE_QUEST_FUN( questspec_special_item	);
DECLARE_QUEST_FUN( questspec_rand_mob		);
DECLARE_QUEST_FUN( questspec_mob_and_item	);
DECLARE_QUEST_FUN( questspec_hard_mob		);
DECLARE_QUEST_FUN( questspec_mass_kill		);
DECLARE_QUEST_FUN( questspec_pk                 );

const struct quest_type quest_table [] =
{
  { "questspec_generic",         questspec_generic          },
  { "questspec_special_item",    questspec_special_item     },
  { "questspec_rand_mob",        questspec_rand_mob         },
  { "questspec_mob_and_item",    questspec_mob_and_item     },
  { "questspec_hard_mob",        questspec_hard_mob         },
  { "questspec_mass_kill",       questspec_mass_kill        },
  { "questspec_pk",              questspec_pk               },

  /* end of table */
  { "", 0 }
};

/*
 * Used for show_char_to_char_0()
 */
bool is_quest_target(CHAR_DATA *ch, CHAR_DATA *victim)
{
  QUEST_DATA *quest;

  if (IS_NPC(ch) || !IS_NPC(victim)) return FALSE;

  for (quest = ch->pcdata->quests; quest; quest = quest->next)
  {
    switch(quest->type)
    {
      case QT_MOB:
        if (victim->pIndexData->vnum == quest->vnums[0])
          return TRUE;
        break;
      case QT_MOB_AND_OBJ:
        if (victim->pIndexData->vnum == quest->vnums[0])
          return TRUE;
        break;
      case QT_MASS_KILL:
        if (victim->pIndexData->vnum >= quest->vnums[0] &&
            victim->pIndexData->vnum <= quest->vnums[1])
          return TRUE;
        break;
      default: continue;
    }
  }
  return FALSE;
}

/* 
 * This is a temp function for creating the database of quest
 * monsters, that needs to be slain.
 */
void do_createbase(CHAR_DATA *ch, char *argument)
{
  AREA_DATA *area;
  ROOM_INDEX_DATA *pRoom;
  MOB_INDEX_DATA *pMobIndex;
  RESET_DATA *pReset;
  FILE *fp;
  char arg[MAX_INPUT_LENGTH];
  int vnum, vnum2, iArea;
  int i = 0, col = 0;
  bool found = FALSE;

  one_argument(argument, arg);
  iArea = atoi(arg);

  return; // a safe thingy.. don't use this!

  if ((fp = fopen("../txt/monster.txt", "a")) == NULL)
  {
    send_to_char("DIE!\n\r", ch);
    return;
  }

  for (area = area_first; area && !found; area = area->next)
  {
    if (++i == iArea) found = TRUE;
  }
  if (!found)
  {
    send_to_char("No such area.\n\r", ch);
    return;
  }
  fprintf(fp, "/* %s */\n", area->name);
  found = FALSE; // reset
  for (vnum = area->lvnum; vnum <= area->uvnum; vnum++)
  {
    if ((pMobIndex = get_mob_index(vnum)) != NULL)
    {
      if (pMobIndex->level < 800) continue;
      for (vnum2 = area->lvnum; vnum2 <= area->uvnum; vnum2++)
      {
        if ((pRoom = get_room_index(vnum2)) != NULL)
        {
          for (pReset = pRoom->reset_first; pReset; pReset = pReset->next)
          {
            switch (pReset->command)
            {
              default: break;
              case 'M':
                if ((pMobIndex = get_mob_index(pReset->arg1)) != NULL)
                {
                  if (pMobIndex->vnum == vnum) found = TRUE;
                }
            }
          }
        }
      }
      if (found)
      {
        found = FALSE; // reset for later use
        fprintf(fp, " %6d,", vnum);
        if (!(++col % 6)) fprintf(fp, "\n");
      }
    }
  }
  if (col % 6) fprintf(fp, "\n");
  fclose(fp);
  return;
}

int get_lbound()
{
  int vnum, count = 0;

  static const int mob_table[] = {
  14003, 31108, 1105, 1122, 1602, 1702, 1710, 2007,
  2101, 2201, 2307, 2315, 2801, 2904, 3406, 3601, 4150,
  4700, 5000, 5006, 2612, 6303, 6507, 6601, 7200, 7801,
  8000, 8308, 8904, 9203, 9401, 11006, 7705, 93000, 80001,

  /* END */
    -1
  };

  /* counting the size of the table */
  for (count = 0; mob_table[count] != -1; count++) ;

  vnum = number_range(0, count-1);
  return mob_table[vnum];
}

int get_ubound(int bound)
{
  int vnum;

  switch(bound)
  {
    case 14003 : vnum = 14011; break;
    case 31108 : vnum = 31118; break;
    case 1105 : vnum = 1109; break;
    case 1122 : vnum = 1125; break;
    case 1602 : vnum = 1605; break;
    case 1702 : vnum = 1708; break;
    case 1710 : vnum = 1716; break;
    case 2007 : vnum = 2013; break;
    case 2101 : vnum = 2108; break;
    case 2201 : vnum = 2205; break;
    case 2307 : vnum = 2311; break;
    case 2315 : vnum = 2321; break;
    case 2801 : vnum = 2808; break;
    case 2904 : vnum = 2913; break;
    case 3406 : vnum = 3408; break;
    case 3601 : vnum = 3604; break;
    case 4150 : vnum = 4154; break;
    case 4700 : vnum = 4708; break;
    case 5000 : vnum = 5003; break;
    case 5006 : vnum = 5008; break;
    case 2612 : vnum = 2616; break;
    case 6303 : vnum = 6310; break;
    case 6507 : vnum = 6510; break;
    case 6601 : vnum = 6606; break;
    case 7200 : vnum = 7202; break;
    case 7801 : vnum = 7808; break;
    case 8000 : vnum = 8005; break;
    case 8308 : vnum = 8312; break;
    case 8904 : vnum = 8908; break;
    case 9203 : vnum = 9207; break;
    case 9401 : vnum = 9407; break;
    case 11006 : vnum = 11008; break;
    case 7705 : vnum = 7709; break;
    case 93000 : vnum = 93004; break;
    case 80001 : vnum = 80005; break;
    default:
      vnum = bound;
      bug("Bad Quest Vnum : %d", bound);
      break;
  }
  return vnum;
}

/*
 * Returns the vnum of a random object that actually pops on the mud.
 * It's a bit ugly, but it works :)
 */
int get_rand_item()
{
  OBJ_INDEX_DATA *pObjIndex;
  ROOM_INDEX_DATA *pRoom;
  RESET_DATA *pReset;
  AREA_DATA *area;
  int vnum, i;
  bool found = FALSE;

  while (!found)
  {
    vnum = number_range(101, 32000);
    if ((pObjIndex = get_obj_index(vnum)) != NULL)
    {
      if (!CAN_WEAR(pObjIndex, ITEM_TAKE)) continue;
      if (pObjIndex->item_type == ITEM_MONEY) continue;
      if ((area = pObjIndex->area) != NULL)
      {
        for (i = area->lvnum; i <= area->uvnum; i++)
        {
          if ((pRoom = get_room_index(i)) != NULL)
          {
            for (pReset = pRoom->reset_first; pReset; pReset = pReset->next)
            {
              switch (pReset->command)
              {
                default: break;
                case 'O':
                  if ((pObjIndex = get_obj_index(pReset->arg1)) != NULL)
                  {
                    if (pObjIndex->vnum == vnum) found = TRUE;
                  }
              }
            }
          }
        }
      }
    }
  }
  return vnum;
}

int load_special_item(CHAR_DATA *ch)
{
  OBJ_DATA *obj;
  OBJ_INDEX_DATA *pObjIndex;
  int vnum;

  vnum = number_range(99501, 99505);
  if ((pObjIndex = get_obj_index(vnum)) == NULL)
  {
    bug("Load_special_item : %d", vnum);
    return 103;  // Just to be on the safe side
  }
  obj = create_object(pObjIndex, 50);
  obj->ownerid = ch->pcdata->playerid;
  SET_BIT(obj->extra_flags, ITEM_NOLOCATE);
  obj->timer = 25; // so it will eventually decay...
  obj_to_room(obj, get_rand_room());
  return vnum;
}

int get_rand_mob()
{
  int vnum, count = 0;

  static const int questmob_table[] = {
/* DragonSpyre */
  14004,  14005,  14006,  14008,  14009,  14010,
  14011,  14012,
/* Castle Dragonheart */
  19101,  19103,  19108,
/* Sunyata */
  31053,  31055,  31056,  31057,  31059,  31060,
  31061,  31064,  31065,  31066,  31067,  31068,
/* Easy Evil Ones */
  11101,  11104,  11115,  11118,  11121,  11124,
  11125,  11126,  11127,  11133,  11136,  11137,
  11138,  11139,
/* Mountain of the Evil Ones */
  31100,  31101,  31102,  31103,  31104,  31105,
  31106,  31107,  31108,  31109,  31110,  31111,
  31113,  31114,  31115,  31119,  31120,  31121,
  31122,  31123,  31124,  31125,  31126,  31127,
/* Midgaard */
   3009,   3603,   4100,   4101,   4707,   5008,
   5010,   5100,   5111,   5315,   6311,   6600,
   6601,   6602,   6603,   6604,   6605,   6606,
   6607,   6608,   7008,   7013,   7703,   8000,
   8101,   8102,   8105,   8120,   8123,   8600,
   9208,   9233,   9322,  11001,  11004,  11005,
  11006,  11007,  11008,  11009,  11010,  11011,
  11101,  11104,  11115,  11118,  11121,  11124,
  11125,  11126,  11127,  11133,  11136,  11137,
  11138,  11139,  12000,  12002,  12004,  12007,
  12010,  12011,  12013,  12014,  12015,  12016,
  12018,  12022,  12023,  12024,  12026,  12028,
  12029,  12030,  12032,  12033,  12034,  14004,
  14005,  14006,  14008,  14009,  14010,  14011,
  14012,  19101,  19103,  19108,  25000,  25002,
  25003,  25007,
/* Atlantis */  
   8101,   8102,   8105,   8120,   8123,
/* Smurf Village */
    105,
/* Ultima */
   2410,   2433,   2436,   2437,   2446,   2452,
   2453,   2454,   2456,   2458,   2460,   2462,
   2463,   2464,   2468,   2469,   2470,
/* New Ofcol */
    600,
/* High Tower of Sorcery */
   1341,   1347,   1348,   1349,
/* Olympus */
    902,    903,    904,    905,    907,    908,
    912,    913,    914,    915,    916,    917,
    918,    922,    924,    926,
/* Gnome Village */   
   1512,
/* Dwarven Catacombs */
   2015,
/* Dangerous Neighbourhood */
   2108,
/* The Keep of Mahn-Tor */
   2301,   2323,   2325,
/* Troll Den */
   2803,   2804,
/* Land of the Fire Newts */
   2906,
/* Graveyard */
   3603,
/* Moria */
   4100,   4101,
/* Kingdom of Juargan */
   4707,
/* Great Eastern Desert */
   5008,   5010,
/* The Great Pyramid */ 
   2605,   2608,   2611,   2612,   2613,   2614,
   2615,   2617,
/* Drow City */
   5100,
/* Old Thalos */
   5315,
/* Arachnod */
   6311,
/* Dwarven Day Care */
   6600,   6601,   6602,   6603,   6604,   6605,
   6606,   6607,   6608,
/* Sewer */
   7008,   7013,
/* Mega-City One */
   8000,
/* Machine Dreams */
   8600,
/* Elemental Canyon */
   9208,   9233,
/* Galaxy */
   9322,
/* The Learning Centre */
  25000,  25002,  25003,  25007,
/* Artifacts */
    600,    902,    903,    904,    905,    907,
    908,    912,    913,    914,    915,    916,
    917,    918,    922,    924,    926,   1341,
   1347,   1348,   1349,   1512,   2015,   2108,
   2301,   2323,   2325,   2410,   2433,   2436,
   2437,   2446,   2452,   2453,   2454,   2456,
   2458,   2460,   2462,   2463,   2464,   2468,
   2469,   2470,   2605,   2608,   2611,   2612,
   2613,   2614,   2615,   2617,   2803,   2804,
   2906,   3009,   3603,   4100,   4101,   4707,
   5008,   5010,   5100,   5111,   5315,   6311,
   6600,   6601,   6602,   6603,   6604,   6605,
   6606,   6607,   6608,   7008,   7013,   7703,
   8000,   8101,   8102,   8105,   8120,   8123,
   8600,   9208,   9233,   9322,  11001,  11004,
  11005,  11006,  11007,  11008,  11009,  11010,
  11011,  11101,  11104,  11115,  11118,  11121,
  11124,  11125,  11126,  11127,  11133,  11136,
  11137,  11138,  11139,  12000,  12002,  12004,
  12007,  12010,  12011,  12013,  12014,  12015,
  12016,  12018,  12022,  12023,  12024,  12026,
  12028,  12029,  12030,  12032,  12033,  12034,
  14004,  14005,  14006,  14008,  14009,  14010,
  14011,  14012,  19101,  19103,  19108,  25000,
  25002,  25003,  25007,
/* Doom */
  11001,  11004,  11005,  11006,  11007,  11008,
  11009,  11010,  11011,
/* Vallandar's Tomb */
  30330,
/* Cannabis */ 
  30232,  30234,  30235,  30237,  30238,  30240,
/* Astral/Githyanki */
   7703,
/* Jobo's Hell */
  30100,  30101,  30102,  30103,  30104,  30105,
  30106,  30107,  30108,  30109,  30110,  30111,
/* Jobo's Heaven */
  99002,  99003,  99004,
/* Old Dystopia */
  30406,  30409,
/* Dome Ship StarSword */
  93000,  93001,  93002,  93003,  93004,  93007,
  93008,  93009,  93010,  93011,  93012,  93013,
  93014,  93015,  93016,  93017,  93018,  93029,
  93030,
/* Lair of the black Dragon */
  32000,
/* Disneyworld */
  50002,  50003,  50004,  50005,  50006,  50007,
  50008,  50011,  50012,  50013,  50014,  50015,
  50018,  50019,  50021,  50022,  50024,  50026,
  50027,  50029,
/* Santa's Workshop */  
  50201,  50202,  50204,  50205,  50206,  50207,
  50208,  50209,  50210,  50211,  50213,  50214,
  50216,  50217,  50218,  50219,
/* Realm of The Dawnbringers */
  80001,  80002,  80003,  80004,  80005,
/* Village of Kakakaru */
  77002,  77003,  77004,  77005,  77006,  77008,
  77009,  77010,  77011,  77012,  77013,  77014,
  77015,  77016,
/* Geometric Series */
  31000,  31001,  31002,  31003,  31004,  31005,
/* The House of Hell */
  31200,  31201,  31202,  31203,  31204,  31205,
  31206,  31208,  31209,  31210,  31211,  31212,
  31213,  31215,  31216,  31217,
/* Forest of Doom */
  12000,  12002,  12004,  12007,  12010,  12011,
  12013,  12014,  12015,  12016,  12018,  12022,
  12023,  12024,  12026,  12028,  12029,  12030,
  12032,  12033,  12034,
/* THE END */
  -1
  };

  /* counting the size of the table */
  for (count = 0; questmob_table[count] != -1; count++) ;

  vnum = number_range(0, count-1);
  return questmob_table[vnum];
}

int get_rand_hard_mob()
{
  int vnum, count = 0;
  
  static const int questmob_table[] = {
/* Sunyata */
  31054,
/* Midgaard */
   3011,   5105,   5106,   5107,   5108,   5109,
   8103,   8106,   8107,   8108,   8109,   8112,
   8114,   8115,   8116,   8118,   8119,   8121,
   8124,
/* Atlantis */  
   8103,   8106,   8107,   8108,   8109,   8112,
   8114,   8115,   8116,   8118,   8119,   8121,
   8124,
/* Drow City */
   5105,   5106,   5107,   5108,   5109,
/* Artifacts */
   3011,   5105,   5106,   5107,   5108,   5109,
   8103,   8106,   8107,   8108,   8109,   8112,
   8114,   8115,   8116,   8118,   8119,   8121,
   8124,  12036,
/* Old Dystopia */ 
  30400,  30401,  30402,  30403,  30408,  30500,
  30501,  30502,  30503,  30506,  30508,  30509,
/* Forest of Doom */   
  12036,
/* THE END */
  -1 
  };

  /* counting the size of the table */
  for (count = 0; questmob_table[count] != -1; count++) ;
  
  vnum = number_range(0, count-1);
  return questmob_table[vnum];
}

void do_showquest(CHAR_DATA *ch, char *argument)
{
  QUEST_DATA *quest;
  OBJ_INDEX_DATA *pObjIndex;
  MOB_INDEX_DATA *pMobIndex1;
  MOB_INDEX_DATA *pMobIndex2;
  MOB_INDEX_DATA *pMobIndex3;
  CHAR_DATA *gch;
  CHAR_DATA *ich;
  char buf[MAX_STRING_LENGTH];
  bool found = FALSE;
  bool found2 = FALSE;

  if (IS_NPC(ch)) return;
  for (quest = ch->pcdata->quests; quest; quest = quest->next)
  {
    found = TRUE;
    switch(quest->type)
    {
      default:
        sprintf(buf, "Do_showquests: %s has bad quest type %d.", ch->name, quest->type);
        bug(buf, 0);
        break;
      case QT_MOB:
        if ((pMobIndex1 = get_mob_index(quest->vnums[0])) == NULL && quest->vnums[0] != -1)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->vnums[0]);
          bug(buf, 0);
          break;
        }
        if ((pMobIndex2 = get_mob_index(quest->giver)) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->giver);
          bug(buf, 0);
          break; 
        }
        sprintf(buf, "You have been charged by #G%s#n to complete the following quest\n\r",
          pMobIndex2->short_descr);
        send_to_char(buf, ch);
        if (quest->vnums[0] != -1)
        {
          sprintf(buf, " * Find and slay #R%s#n.\n\r", pMobIndex1->short_descr);
          send_to_char(buf, ch);
          sprintf(buf, " * You have #C%d#n hours to complete the quest.\n\r", quest->time);
          send_to_char(buf, ch);
        }
        else
        {
          send_to_char(" * This quest has been #Ccompleted#n.\n\r", ch);
          sprintf(buf, " * You have #C%d#n hours to return to the questgiver.\n\r", quest->time);
          send_to_char(buf, ch);
        }
        break;
      case QT_PK:
        if ((pMobIndex2 = get_mob_index(quest->giver)) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->giver);
          bug(buf, 0);
          break;
        }
        for (gch = char_list; gch && !found2; gch = gch->next)
        {
          if (IS_NPC(gch)) continue;
          if (gch->pcdata->playerid == quest->vnums[0])
          {
            ich = gch;
            found2 = TRUE;
          }
        }
        sprintf(buf, "You have been charged by #G%s#n to complete the following quest\n\r",
          pMobIndex2->short_descr);
        send_to_char(buf, ch);
        if (quest->vnums[0] != -1)
        {
          sprintf(buf, " * Hunt and slay #R%s#n.\n\r",
            found2 ? ich->name : "someone");
          send_to_char(buf, ch);
          sprintf(buf, " * You have #C%d#n hours to complete the quest.\n\r", quest->time);
          send_to_char(buf, ch);
        }
        else
        {
          send_to_char(" * This quest has been #Ccompleted#n.\n\r", ch);
          sprintf(buf, " * You have #C%d#n hours to return to the questgiver.\n\r", quest->time);
          send_to_char(buf, ch);
        }
        break;
      case QT_OBJ:
        if ((pObjIndex = get_obj_index(quest->vnums[0])) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest item %d.", ch->name, quest->vnums[0]);
          bug(buf, 0);
          break;
        }
        if ((pMobIndex2 = get_mob_index(quest->giver)) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->giver);
          bug(buf, 0);
          break;
        }
        sprintf(buf, "You have been charged by #G%s#n to complete the following quest\n\r",
          pMobIndex2->short_descr);
        send_to_char(buf, ch);
        sprintf(buf, " * Find and return #R%s#n.\n\r", pObjIndex->short_descr);
        send_to_char(buf, ch);
        sprintf(buf, " * You have #C%d#n hours to complete the quest.\n\r", quest->time);
        send_to_char(buf, ch);
        break;
      case QT_MOB_AND_OBJ:
        if ((pMobIndex1 = get_mob_index(quest->vnums[0])) == NULL && quest->vnums[0] != -1)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->vnums[0]); 
          bug(buf, 0);
          break;
        }
        if ((pObjIndex = get_obj_index(quest->vnums[1])) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest item %d.", ch->name, quest->vnums[1]);
          bug(buf, 0);
          break;
        }
        if ((pMobIndex2 = get_mob_index(quest->giver)) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->giver);
          bug(buf, 0);
          break;
        }
        sprintf(buf, "You have been charged by #G%s#n to complete the following quest\n\r",
          pMobIndex2->short_descr);
        send_to_char(buf, ch);
        if (quest->vnums[0] != -1)
        {
          sprintf(buf, " * Find and slay #R%s#n.\n\r", pMobIndex1->short_descr);
          send_to_char(buf, ch);
        }
        sprintf(buf, " * Find and return #R%s#n.\n\r", pObjIndex->short_descr);
        send_to_char(buf, ch);
        sprintf(buf, " * You have #C%d#n hours to complete the quest.\n\r", quest->time);
        send_to_char(buf, ch);
        break;
      case QT_MASS_KILL:
        if ((pMobIndex1 = get_mob_index(quest->vnums[0])) == NULL && quest->vnums[0] != -1)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->vnums[0]);
          bug(buf, 0);
          break;
        }
        if ((pMobIndex3 = get_mob_index(quest->vnums[1])) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->vnums[1]);
          bug(buf, 0);
          break;
        }
        if ((pMobIndex2 = get_mob_index(quest->giver)) == NULL)
        {
          sprintf(buf, "Do_showquests: %s has bad quest mob %d.", ch->name, quest->giver);
          bug(buf, 0);
          break;
        }
        sprintf(buf, "You have been charged by #G%s#n to complete the following quest\n\r",
          pMobIndex2->short_descr);
        send_to_char(buf, ch);
        if (quest->vnums[3] >= quest->vnums[2])
        {
          send_to_char(" * This quest has been #Ccompleted#n.\n\r", ch);
          sprintf(buf, " * You have #C%d#n hours to return to the questgiver.\n\r", quest->time);
          send_to_char(buf, ch);
        }
        else
        {
          sprintf(buf, " * Find and slay #C%d#n monsters ranging from\n\r", quest->vnums[2] - quest->vnums[3]);
          send_to_char(buf, ch);
          sprintf(buf, "   #R%s#n to #R%s#n.\n\r", pMobIndex1->short_descr, pMobIndex3->short_descr);
          send_to_char(buf, ch);
          sprintf(buf, " * You have #C%d#n hours to complete the quest.\n\r", quest->time);
          send_to_char(buf, ch);
        }
        break;
    }
  }
  if (!found) send_to_char("You are currently undertaking no quests.\n\r", ch);
  return;
}

void quest_to_char(CHAR_DATA *ch, QUEST_DATA *quest)
{
  QUEST_DATA *quest_new;

  if (IS_NPC(ch))
  {
    bug("Quest_to_char: on npc", 0);
    return;
  }

  if (quest_free == NULL)
    quest_new = alloc_perm(sizeof(*quest_new));
  else
  {
    quest_new  = quest_free;
    quest_free = quest_free->next;
  }

 *quest_new          = *quest;
  quest_new->next    = ch->pcdata->quests;
  ch->pcdata->quests = quest_new;

  return;
}

/*
 * Remove an affect from a char.
 */
void quest_from_char(CHAR_DATA *ch, QUEST_DATA *quest)
{
  if (IS_NPC(ch))
  {
    bug("Quest_from_char: on npc", 0);
    return;
  }
  if (ch->pcdata->quests == NULL)
  {
    bug("Quest_from_char: No quest", 0);
    return;
  }
  if (quest == ch->pcdata->quests)
    ch->pcdata->quests = quest->next;
  else
  {
    QUEST_DATA *prev;
   
    for (prev = ch->pcdata->quests; prev; prev = prev->next)
    {
      if (prev->next == quest)
      {
        prev->next = quest->next;
        break;
      }
    }
    if (prev == NULL)
    {
      bug("Quest_from_char: No quest", 0);
      return;
    }
  }
  quest->next = quest_free;
  quest_free  = quest;
  return;
}

QUEST_FUN *quest_lookup(const char *name)
{
  int cmd;

  for (cmd = 0; *quest_table[cmd].quest_name; cmd++)
    if (!str_cmp(name, quest_table[cmd].quest_name))
      return quest_table[cmd].quest_fun;
  return 0;
}

char *quest_string(QUEST_FUN *fun)
{
  int cmd;

  for (cmd = 0; *quest_table[cmd].quest_fun; cmd++)
    if (fun == quest_table[cmd].quest_fun)
      return quest_table[cmd].quest_name;

  return 0;
}

void give_token(CHAR_DATA *questmaster, CHAR_DATA *ch, int value)
{
  OBJ_DATA *obj;
  char buf[MAX_STRING_LENGTH];

  if (global_qp) value *= ccenter[CCENTER_QD_MULT];
  if (IS_SET(ch->pcdata->tempflag, TEMP_EDGE)) value *= 1.1;
  if (ch->pcdata->faith > 0) value += ch->pcdata->faith/10;
  if (ch->pcdata->time_tick > 49)
  {
    value *= 100 + ch->pcdata->time_tick/10;
    value /= 100;
  }

  obj = create_object(get_obj_index(OBJ_VNUM_PROTOPLASM), 50);
  obj->value[0] = value;
  obj->level = value; 
  obj->cost = 1000;
  obj->item_type = ITEM_QUEST;
  obj_to_char(obj, ch);
  free_string(obj->name);
  obj->name = str_dup("quest token");
  free_string(obj->short_descr);
  sprintf(buf, "a %d point quest token", value);
  obj->short_descr = str_dup(buf);
  free_string(obj->description);
  sprintf(buf, "A %d point quest token lies on the floor.", value);
  obj->description = str_dup(buf);
  act("You recieve $p from $N.", ch, obj, questmaster, TO_CHAR);
  act("$n recieves $p from $N.", ch, obj, questmaster, TO_ROOM);
  ch->pcdata->questsrun++;
  ch->pcdata->questtotal += value;
  do_autosave(ch, "");
}

void do_qcomplete(CHAR_DATA *ch, char *argument)  
{
  CHAR_DATA *questmaster;
  QUEST_DATA *quest;
  OBJ_DATA *obj;
  char arg[MAX_INPUT_LENGTH];
  char arg2[MAX_INPUT_LENGTH];
  bool found = FALSE;

  if (IS_NPC(ch)) return;
  argument = one_argument(argument, arg);
  one_argument(argument, arg2);
  if ((questmaster = get_char_room(ch, arg)) == NULL)
  {
    send_to_char("You cannot seem to find that questmaster.\n\r", ch);
    return;
  }
  if (!IS_NPC(questmaster))   
  {
    send_to_char("Players cannot give quests.\n\r", ch);
    return;
  }
  if (questmaster->quest_fun != 0)
  {
    for (quest = ch->pcdata->quests; quest && !found; quest = quest->next)
    {
      if (quest->giver != questmaster->pIndexData->vnum) continue;
      found = TRUE;

      /* Let's check if the quest is actually completed */
      switch (quest->type)
      {
        default:
          bug("Quest_complete: Bad Quest Type", 0);
          return;
        case QT_MOB:
          if (quest->vnums[0] != -1)
          {
            send_to_char("You have not completed that quest yet.\n\r", ch);
            return;
          }
          break;
        case QT_PK:
          if (quest->vnums[0] != -1)
          {
            send_to_char("You have not completed that quest yet.\n\r", ch);
            return;
          }
          break;
        case QT_OBJ:
          if ((obj = get_obj_carry(ch, arg2)) == NULL)
          {
            send_to_char("What object do you wish to return?\n\r", ch);
            return;
          }
          if (obj->pIndexData->vnum != quest->vnums[0])
          {
            send_to_char("That is not the object of the quest.\n\r", ch);
            return;
          }
          extract_obj(obj);
          break;
        case QT_MOB_AND_OBJ:
          if (quest->vnums[0] != -1)
          {
            send_to_char("You have not completed that quest yet.\n\r", ch);
            return;
          }
          if ((obj = get_obj_carry(ch, arg2)) == NULL)
          {
            send_to_char("What object do you wish to return?\n\r", ch);
            return;   
          }
          if (obj->pIndexData->vnum != quest->vnums[1])
          {
            send_to_char("That is not the object of the quest.\n\r", ch);
            return;
          }
          extract_obj(obj);
          break;
        case QT_MASS_KILL:
          if (quest->vnums[2] > quest->vnums[3])
          {
            send_to_char("You have not completed that quest yet.\n\r", ch);
            return;
          }
          break;
      }
      (*questmaster->quest_fun)(questmaster, ch, "complete");
      quest_from_char(ch, quest);
    }
    if (!found) send_to_char("You are not questing for this questmaster.\n\r", ch);
  }
  else
    send_to_char("Doesn't seem like that's a questmaster.\n\r", ch);
  return;
}

void do_qgain(CHAR_DATA *ch, char *argument)
{
  CHAR_DATA *questmaster;
  QUEST_DATA *quest;
  char arg[MAX_INPUT_LENGTH];

  if (IS_NPC(ch)) return;
  one_argument(argument, arg);
  if ((questmaster = get_char_room(ch, arg)) == NULL)
  {
    send_to_char("You cannot seem to find that questmaster.\n\r", ch);
    return;
  }
  if (!IS_NPC(questmaster))
  {
    send_to_char("Players cannot give quests.\n\r", ch);
    return;
  }
  if (questmaster->quest_fun != 0)
  {
    for (quest = ch->pcdata->quests; quest; quest = quest->next)
    {
      if (quest->giver == questmaster->pIndexData->vnum)
      {
        send_to_char("You have already been given a quest from this questmaster.\n\r", ch);
        return;
      }
    }
    /* ROCK THEM! */
    (*questmaster->quest_fun)(questmaster, ch, "gain");  
  }
  else
    send_to_char("Doesn't seem like that's a questmaster.\n\r", ch);
}

void questspec_generic(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;

    do_say(questmaster, "Will you smurf this smurf for me?");
    new_quest.type      = QT_MOB;
    new_quest.time      = 20;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = number_range(103, 109);
    quest_to_char(ch, &new_quest);
    return;
  }
  else if (!str_cmp(argument, "complete"))
  {
    int value = 20;

    give_token(questmaster, ch, value);
    do_say(questmaster, "That's the spirit, thanks for solving my quest.");
    return;
  }
}

void questspec_rand_mob(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;

    do_say(questmaster, "I charge you to find and kill this monster!");
    new_quest.type      = QT_MOB;
    new_quest.time      = 15;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = get_rand_mob();
    quest_to_char(ch, &new_quest);
    return;
  } 
  else if (!str_cmp(argument, "complete"))
  {
    int value = 30;

    give_token(questmaster, ch, value);
    do_say(questmaster, "Thanks for solving my quest, come back again if you want.");
    return;
  }
}

void questspec_hard_mob(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;

    do_say(questmaster, "I charge you to find and kill this horrible monster!");
    new_quest.type      = QT_MOB;
    new_quest.time      = 25;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = get_rand_hard_mob();        
    quest_to_char(ch, &new_quest);
    return;
  }
  else if (!str_cmp(argument, "complete"))
  {
    int value = 60;

    give_token(questmaster, ch, value);
    do_say(questmaster, "Thanks for solving my quest, come back again if you want.");
    return;
  } 
}  

void questspec_special_item(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;

    do_say(questmaster, "I charge you to retrive this ancient artifact of mine!");
    new_quest.type      = QT_OBJ;   
    new_quest.time      = 25;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = load_special_item(ch);
    quest_to_char(ch, &new_quest);
    return;
  }
  else if (!str_cmp(argument, "complete"))
  {
    int value = 120;

    give_token(questmaster, ch, value);
    do_say(questmaster, "Thanks for solving my quest, come back again if you want.");
    return;
  }
}

void questspec_mob_and_item(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;   

    do_say(questmaster, "Please slay this monster and retrive this item for me!");
    new_quest.type      = QT_MOB_AND_OBJ;
    new_quest.time      = 25;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = get_rand_mob();
    new_quest.vnums[1]  = get_rand_item();
    quest_to_char(ch, &new_quest);
    return;
  }
  else if (!str_cmp(argument, "complete"))
  {
    int value = 90;

    give_token(questmaster, ch, value);
    do_say(questmaster, "Thanks for solving my quest, come back again if you want.");
    return;
  }
}

void questspec_mass_kill(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;
    int bound;

    bound = get_lbound();

    do_say(questmaster, "Please slay these monsters.");
    new_quest.type      = QT_MASS_KILL;
    new_quest.time      = 30;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = bound;
    new_quest.vnums[1]  = get_ubound(bound);
    new_quest.vnums[2]  = number_range(8, 16);   // amount needed to be killed
    new_quest.vnums[3]  = 0;                     // counter
    quest_to_char(ch, &new_quest);
    return;
  }
  else if (!str_cmp(argument, "complete"))
  {
    int value = 75;

    give_token(questmaster, ch, value);
    do_say(questmaster, "Thanks for solving my quest, come back again if you want.");
    return;
  }
}  

void questspec_pk(CHAR_DATA *questmaster, CHAR_DATA *ch, char *argument)
{
  if (!str_cmp(argument, "gain"))
  {
    QUEST_DATA new_quest;
    DESCRIPTOR_DATA *d;
    CHAR_DATA *gch;
    bool found = FALSE;

    for (d = descriptor_list; d && !found; d = d->next)
    {
      if (d->connected == CON_PLAYING && d->lookup_status == STATUS_DONE)
      {
        if ((gch = d->character) == NULL) continue;
        if (fair_fight(ch, gch) && fair_fight(gch, ch)) found = TRUE;
      }
    }
    if (!found)
    {
      do_say(questmaster, "Sorry, I have no quest for you at this time.");
      return;
    }

    do_say(questmaster, "I charge you to find and destroy this player!");
    new_quest.type      = QT_PK;
    new_quest.time      = 8;
    new_quest.giver     = questmaster->pIndexData->vnum;
    new_quest.vnums[0]  = gch->pcdata->playerid;
    quest_to_char(ch, &new_quest);
    return;
  }
  else if (!str_cmp(argument, "complete"))
  {
    int value = 200;

    give_token(questmaster, ch, value);
    do_say(questmaster, "Thanks for solving my quest, come back again if you want.");
    return;
  }
}