1stMUD/corefiles/
1stMUD/gods/
1stMUD/notes/
1stMUD/player/
1stMUD/win32/
1stMUD/win32/ROM/
/**************************************************************************
*  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
*  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
*                                                                         *
*  Merc Diku Mud improvements 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.                                               *
*                                                                         *
*  Much time and thought has gone into this software and you are          *
*  benefiting.  We hope that you share your changes too.  What goes       *
*  around, comes around.                                                  *
***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                        *
*       ROM has been brought to you by the ROM consortium                 *
*           Russ Taylor (rtaylor@hypercube.org)                           *
*           Gabrielle Taylor (gtaylor@hypercube.org)                      *
*           Brian Moore (zump@rom.org)                                    *
*       By using this code, you have agreed to follow the terms of the    *
*       ROM license, in the file Rom24/doc/rom.license                    *
***************************************************************************
*       1stMUD ROM Derivative (c) 2001-2002 by Ryan Jennings              *
*            http://1stmud.dlmud.com/  <r-jenn@shaw.ca>                   *
***************************************************************************/
/****************************************************************************
*  Automated Quest code written by Vassago of MOONGATE, moongate.ams.com    *
*  4000. Copyright (c) 1996 Ryan Addams, All Rights Reserved. Use of this   *
*  code is allowed provided you add a credit line to the effect of:         *
*  "Quest Code (c) 1996 Ryan Addams" to your logon screen with the rest     *
*  of the standard diku/rom credits. If you use this or a modified version  *
*  of this code, let me know via email: moongate@moongate.ams.com. Further  *
*  updates will be posted to the rom mailing list. If you'd like to get     *
*  the latest version of quest.c, please send a request to the above add-   *
*  ress. Quest Code v2.03. Please do not remove this notice from this file. *
****************************************************************************/

/****************************************************************************
 * Updated Quest Code copyright 1999-2001                                   *
 * Markanth : dlmud@dlmud.com                                               *
 * Devil's Lament : dlmud.com port 3778                                     *
 * Web Page : http://www.dlmud.com                                          *
 ***************************************************************************/
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "merc.h"
#include "magic.h"
#include "interp.h"
#include "recycle.h"

struct quest_type
{
	char *name;
	char *descr;
	int vnum;
	int cost;
};

#define QUEST_TIME  20
/*
 * Object vnums for Quest Rewards 
 */
const struct quest_type quest_table[] = {
/*name *//*short description *//*vnum *//*cost */
	{"nohunger", "No Hunger/Thirst (quest buy nohunger)", 0, 3000},
	{"aura", "Aura of Sanctuary", QUEST_AURA, 2600},
	{"sword", "Sword of the Ancients", QUEST_SWORD, 2500},
	{"breastplate", "BreastPlate of the Ancients", QUEST_BPLATE, 2500},
	{"boots", "Boots of the Ancients", QUEST_BOOTS, 2500},
	{"gloves", "Gloves of Protection", QUEST_GLOVES, 2500},
	{"flame", "Flame of the Ancients", QUEST_FLAME, 2500},
	{"helm", "Helm of True Sight", QUEST_HELM, 2300},
	{"bag", "Bag of the Ancients", QUEST_BAG, 1000},
	{"shield", "Shield of the Ancients", QUEST_SHIELD, 750},
	{"regeneration", "Ring of Regeneration", QUEST_REGEN, 700},
	{"invisibility", "Ring of Invisibility", QUEST_INVIS, 500},
	{"trivia", "Trivia Pill", QUEST_TRIVIA, 100},
	{NULL, NULL, 0, 0}
};

/* Quests to find objects */
#define QUEST_OBJQUEST1 214
#define QUEST_OBJQUEST2 215
#define QUEST_OBJQUEST3 216
#define QUEST_OBJQUEST4 217

/*
 * CHANCE function. I use this everywhere in my code, very handy :> 
 */
bool chance(int num)
{
	if (number_range(1, 100) <= num)
		return TRUE;
	else
		return FALSE;
}

/* is object in quest table? */
int is_qobj(OBJ_DATA * obj)
{
	int i;

	if (!obj || !obj->pIndexData)
		return -1;

	for (i = 0; quest_table[i].name != NULL; i++)
	{
		if (obj->pIndexData->vnum == quest_table[i].vnum)
			return i;
	}
	return -1;
}

int qobj_cost(OBJ_DATA * obj)
{
	int i;

	if (!obj || !obj->pIndexData)
		return 0;

	for (i = 0; quest_table[i].name != NULL; i++)
	{
		if (obj->pIndexData->vnum == quest_table[i].vnum)
			return quest_table[i].cost;
	}
	return 0;
}

/*
 * * Add or enhance an obj affect. 
 */
void affect_join_obj(OBJ_DATA * obj, AFFECT_DATA * paf)
{
	AFFECT_DATA *paf_old;
	bool found;

	found = FALSE;
	for (paf_old = obj->first_affect; paf_old != NULL; paf_old = paf_old->next)
	{
		if (paf_old->location == paf->location &&
			paf_old->type == paf->type &&
			paf_old->bitvector == paf->bitvector &&
			paf_old->where == paf->where)
		{
			paf_old->level = paf->level;
			paf_old->modifier = paf->modifier;
			found = TRUE;
		}
	}
	if (!found)
		affect_to_obj(obj, paf);
	return;
}

void add_apply(OBJ_DATA * obj, int loc, int mod, int where, int type,
			   int dur, flag_t bit, int level)
{
	AFFECT_DATA pAf;

	if (obj == NULL)
		return;

	pAf.location = loc;
	pAf.modifier = mod;
	pAf.where = where;
	pAf.type = type;
	pAf.duration = dur;
	pAf.bitvector = bit;
	pAf.level = level;
	affect_join_obj(obj, &pAf);

	return;
}

/* Updates a quest object.  AFFECTS like sanctuary ect..
   are done in game or on area file. (Anything not level based) */
void update_questobjs(CHAR_DATA * ch, OBJ_DATA * obj)
{
	int bonus, pbonus, cost;

	if (obj == NULL || obj->pIndexData == NULL)
	{
		bug("update_questobjs: NULL obj", 0);
		return;
	}
	if (ch == NULL)
	{
		bug("update_questobjs: NULL ch", 0);
		return;
	}

	if (!IS_OBJ_STAT(obj, ITEM_QUEST) && is_qobj(obj) == -1)
		return;

	bonus = UMAX(5, ch->level / 10);
	pbonus = UMAX(5, ch->level / 5);
	cost = qobj_cost(obj);

	if (obj->level != ch->level)
		obj->level = ch->level;
	if (obj->condition != -1)
		obj->condition = -1;
	if (obj->cost != cost)
		obj->cost = cost;
	if (!CAN_WEAR(obj, ITEM_NO_SAC))
		SET_BIT(obj->wear_flags, ITEM_NO_SAC);
	if (!IS_OBJ_STAT(obj, ITEM_BURN_PROOF))
		SET_BIT(obj->extra_flags, ITEM_BURN_PROOF);

	/* Bonuses could get fun here */
	switch (obj->pIndexData->vnum)
	{
	case QUEST_BPLATE:
		add_apply(obj, APPLY_DAMROLL, pbonus, TO_OBJECT, 0, -1, 0, ch->level);
		add_apply(obj, APPLY_HITROLL, pbonus, TO_OBJECT, 0, -1, 0, ch->level);
		break;

	case QUEST_SHIELD:
		add_apply(obj, APPLY_DAMROLL, pbonus, TO_OBJECT, 0, -1, 0, ch->level);
		add_apply(obj, APPLY_HITROLL, pbonus, TO_OBJECT, 0, -1, 0, ch->level);
		break;

	case QUEST_AURA:
		add_apply(obj, APPLY_HIT, UMAX(50, ch->level), TO_OBJECT, 0, -1,
				  0, ch->level);
		add_apply(obj, APPLY_MANA, UMAX(50, ch->level), TO_OBJECT, 0,
				  -1, 0, ch->level);
		add_apply(obj, APPLY_MOVE, UMAX(50, ch->level), TO_OBJECT, 0,
				  -1, 0, ch->level);
		break;
	}

	switch (obj->item_type)
	{
	case ITEM_CONTAINER:
		/* weight modifiers */
		obj->weight = -1 * (50 + (ch->level * 15 / 10));
		obj->value[0] = 1000 + (20 * ch->level);
		obj->value[3] = 1000 + (20 * ch->level);
		break;

	case ITEM_WEAPON:
		/* weapon values of levelD5 */
		obj->value[1] = UMAX(15, ch->level);
		obj->value[2] = ch->level < 80 ? 4 : 5;
		add_apply(obj, APPLY_DAMROLL, bonus, TO_OBJECT, 0, -1, 0, ch->level);
		add_apply(obj, APPLY_HITROLL, bonus, TO_OBJECT, 0, -1, 0, ch->level);
		break;

	case ITEM_ARMOR:
		/* AC value of player level */
		obj->value[0] = UMAX(20, ch->level);
		obj->value[1] = UMAX(20, ch->level);
		obj->value[2] = UMAX(20, ch->level);
		obj->value[3] = (5 * UMAX(20, ch->level)) / 6;
		break;
	}

	return;
}

/* why not check non-quest items as well - M. */
void update_all_qobjs(CHAR_DATA * ch)
{
	OBJ_DATA *obj;
	int iWear;

	for (obj = ch->first_carrying; obj != NULL; obj = obj->next_content)
	{
		if (IS_OBJ_STAT(obj, ITEM_QUEST))
		{
			update_questobjs(ch, obj);
			if ((iWear = obj->wear_loc) != WEAR_NONE)
			{
				unequip_char(ch, obj);
				equip_char(ch, obj, iWear);
			}
		}
	}
}

void unfinished_quest(CHAR_DATA * ch)
{
	if (IS_QUESTOR(ch))
	{
		bool found = FALSE;

		sprintf(log_buf, "Creating unfinished quest for %s.", ch->name);
		log_string(log_buf);
		wiznet(log_buf, NULL, NULL, 0, 0, 0);
		if (ch->pcdata->questobj > 0)
		{
			OBJ_DATA *obj = NULL;
			ROOM_INDEX_DATA *pRoom = NULL;

			for (obj = object_first; obj != NULL; obj = obj->next)
				if (obj->pIndexData->vnum == ch->pcdata->questobj
					&& is_name(ch->name, obj->owner))
					found = TRUE;

			if (!found)
			{
				if ((obj =
					 create_object(get_obj_index
								   (ch->pcdata->questobj), ch->level)) == NULL)
					end_quest(ch, 0);
				else
				{
					if ((pRoom = get_room_index(ch->pcdata->questloc)) == NULL)
					{
						pRoom = get_random_room(ch);
						ch->pcdata->questobj = pRoom->vnum;
					}
					obj_to_room(obj, pRoom);
				}
				do_quest(ch, "info");
				chprintln(ch, "");
			}
		}
		else if (ch->pcdata->questmob > 0)
		{
			CHAR_DATA *mob;

			for (mob = char_first; mob != NULL; mob = mob->next)
				if (IS_NPC(mob)
					&& mob->pIndexData->vnum == ch->pcdata->questmob)
					break;

			if (!mob)
				end_quest(ch, QUEST_TIME / 4);
			else
			{
				ch->pcdata->questloc = mob->in_room->vnum;
				do_quest(ch, "info");
				chprintln(ch, "");
			}
		}
	}
}

/* Usage info on the QUEST commands*/
/* Keep this in line with the do_quest function's keywords */
void quest_usage(CHAR_DATA * ch)
{
	chprintln(ch, "QUEST commands: INFO, REQUEST, COMPLETE, LIST,\n\r"
			  "                BUY, QUIT, SELL, IDENTIFY.");
	if (IS_IMMORTAL(ch))
		chprintln(ch, "QUEST RESET (player): resets players quest.");
	chprintln(ch, "For more information, type 'HELP QUEST'.");
	return;
}

/* Obtain additional location information about sought item/mob */
void quest_where(CHAR_DATA * ch, char *what)
{
	ROOM_INDEX_DATA *room;

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

	if (ch->pcdata->questloc <= 0)
	{
		bug("QUEST INFO: ch->questloc = %d", ch->pcdata->questloc);
		return;
	}
	if (ch->in_room == NULL)
		return;

	room = get_room_index(ch->pcdata->questloc);
	if (room->area == NULL)
	{
		bug("QUEST INFO: room(%d)->area == NULL", ch->pcdata->questloc);
		return;
	}
	if (room->area->name == NULL)
	{
		bug("QUEST INFO: area->name == NULL", 0);
		return;
	}
	chprintlnf(ch,
			   "Rumor has it this %s was last seen in the area known as %s,",
			   what, room->area->name);
	if (room->name == NULL)
	{
		bug("QUEST INFO: room(%d)->name == NULL", ch->pcdata->questloc);
		return;
	}
	chprintlnf(ch, "near %s.", room->name);

}								/* end quest_where() */

OBJ_DATA *has_questobj(CHAR_DATA * ch)
{
	OBJ_DATA *obj;
	OBJ_INDEX_DATA *pObj;

	if (!ch || IS_NPC(ch) || ch->pcdata->questobj <= 0)
		return NULL;

	if ((pObj = get_obj_index(ch->pcdata->questobj)) == NULL)
		return NULL;

	for (obj = ch->first_carrying; obj != NULL; obj = obj->next_content)
		if (obj != NULL && obj->pIndexData == pObj)
			return obj;

	return NULL;
}

/*
 * The main quest function 
 */
CH_CMD(do_quest)
{
	CHAR_DATA *questman;
	OBJ_DATA *obj = NULL;
	char buf[MAX_STRING_LENGTH];
	char arg1[MAX_INPUT_LENGTH];
	char arg2[MAX_INPUT_LENGTH];
	int i = 0;

	if (IS_NPC(ch))
	{
		chprintln(ch, "I'm sorry, you can't quest.");
		return;
	}

	argument = one_argument(argument, arg1);
	argument = one_argument(argument, arg2);

	if (arg1[0] == '\0')
	{
		quest_usage(ch);
		return;
	}

	if (!str_prefix(arg1, "info"))
	{
		MOB_INDEX_DATA *qm_mob;
		OBJ_DATA *qinfoobj;
		CHAR_DATA *qinfomob;

		if (!IS_QUESTOR(ch))
		{
			chprintln(ch, "You aren't currently on a quest.");
			if (ch->pcdata->nextquest > 1)
			{
				chprintlnf(ch,
						   "There are %d minutes remaining until you can go on another quest.",
						   ch->pcdata->nextquest);
			}
			else if (ch->pcdata->nextquest == 1)
			{
				chprintln(ch,
						  "There is less than a minute remaining until you can go on another quest.");
			}
			chprintlnf(ch, "You have %d quest points.",
					   ch->pcdata->questpoints);
			return;
		}
		chprintln(ch, "");
		if ((qm_mob = get_mob_index(ch->pcdata->questgiver)) == NULL)
		{
			chprintln(ch,
					  "Your quest master has fallen very ill. Please contact an imm!");
			end_quest(ch, QUEST_TIME / 4);
			return;
		}
		if (ch->pcdata->questmob == -1 || has_questobj(ch) != NULL)
		{
			chprintln(ch, "Your quest is ALMOST complete!");
			chprintlnf(ch,
					   "You have %d minute%s to get back to %s\n\rbefore your time runs out!",
					   ch->pcdata->countdown,
					   ch->pcdata->countdown > 1 ? "s" : "",
					   qm_mob->short_descr);
			return;
		}
		else if (ch->pcdata->questobj > 0)
		{
			for (qinfoobj = object_first; qinfoobj != NULL;
				 qinfoobj = qinfoobj->next)
			{
				if (qinfoobj->pIndexData->vnum == ch->pcdata->questobj)
				{
					act("$n recalls the quest $N gave $m.",
						ch, NULL, qm_mob, TO_ROOM);
					chprintlnf(ch,
							   "You recall the quest which the %s gave you.",
							   qm_mob->short_descr);
					chprintlnf(ch,
							   "You are on a quest to recover the fabled %s!",
							   qinfoobj->short_descr);
					quest_where(ch, "treasure");
					if (ch->pcdata->countdown > 0)
					{
						chprintlnf(ch,
								   "You have %d minute%s to complete this quest.",
								   ch->pcdata->countdown,
								   ch->pcdata->countdown > 1 ? "s" : "");
					}
					return;
				}
			}
		}
		else if (ch->pcdata->questmob > 0)
		{
			for (qinfomob = char_first; qinfomob != NULL;
				 qinfomob = qinfomob->next)
			{
				if (IS_NPC(qinfomob)
					&& qinfomob->pIndexData->vnum == ch->pcdata->questmob)
				{
					act("$n recalls the quest $N gave $s.",
						ch, NULL, qm_mob, TO_ROOM);
					chprintlnf(ch,
							   "You recall the quest which the %s gave you.",
							   qm_mob->short_descr);
					chprintlnf(ch,
							   "You are on a quest to slay the dreaded %s!",
							   qinfomob->short_descr);
					quest_where(ch, "fiend");
					if (ch->pcdata->countdown > 0)
					{
						chprintlnf(ch,
								   "You have %d minute%s to complete this quest.",
								   ch->pcdata->countdown,
								   ch->pcdata->countdown > 1 ? "s" : "");
					}
					return;
				}
			}
		}

		bug("QUEST INFO: Questor with no kill, mob or obj", 0);
		end_quest(ch, QUEST_TIME / 4);
		return;
	}
	else if (!str_prefix(arg1, "reset"))
	{
		CHAR_DATA *victim;

		if (!IS_IMMORTAL(ch))
		{
			quest_usage(ch);
			return;
		}

		if (arg2[0] == '\0')
		{
			chprintln(ch, "Syntax: quest reset <player>");
			return;
		}

		if ((victim = get_char_world(ch, arg2)) == NULL)
		{
			chprintln(ch, "They aren't here.");
			return;
		}

		if (IS_NPC(victim))
		{
			chprintln(ch, "Mobs dont quest.");
			return;
		}

		end_quest(victim, 0);

		if (victim != ch)
			chprintln(ch, "You clear thier quest.");
		chprintlnf(victim, "%s has cleared your quest.", PERS(ch, victim));
		return;
	}

	/*
	 * Checks for a character in the room with spec_questmaster set. This
	 * special procedure must be defined in special.c. You could instead use
	 * an ACT_QUESTMASTER flag instead of a special procedure. 
	 */

	for (questman = ch->in_room->first_person; questman != NULL;
		 questman = questman->next_in_room)
	{
		if (!IS_NPC(questman))
			continue;
		if (questman->spec_fun == spec_lookup("spec_questmaster"))
			break;
	}

	if (questman == NULL
		|| questman->spec_fun != spec_lookup("spec_questmaster"))
	{
		chprintln(ch, "You can't do that here.");
		return;
	}

	if (questman->fighting != NULL)
	{
		chprintln(ch, "Wait until the fighting stops.");
		return;
	}

	/*
	 * And, of course, you will need to change the following lines for YOUR
	 * quest item information. Quest items on Moongate are unbalanced, very
	 * very nice items, and no one has one yet, because it takes awhile to
	 * build up quest points :> Make the item worth their while. 
	 */

	if (!str_cmp(arg1, "list"))
	{
		act("$n asks $N for a list of quest items.", ch, NULL, questman,
			TO_ROOM);
		chprintln(ch, "\tCurrent Quest Items available for Purchase:");
		for (i = 0; quest_table[i].descr != NULL; i++)
		{
			chprintlnf(ch, "\t%-4dqp ........ %s",
					   quest_table[i].cost, quest_table[i].descr);
		}
		chprintln(ch, "\tTo buy an item, type 'QUEST BUY <item>'.");
		chprintln(ch, "\tFor more info on quest items type 'help questitems'");
		return;
	}

	else if (!str_cmp(arg1, "buy"))
	{

		if (arg2[0] == '\0')
		{
			chprintln(ch, "To buy an item, type 'QUEST BUY <item>'.");
			return;
		}

		for (i = 0; quest_table[i].name != NULL; i++)
		{
			if (is_name(arg2, quest_table[i].name))
			{
				if (ch->pcdata->questpoints >= quest_table[i].cost)
				{
					if (quest_table[i].vnum == 0)
					{
						ch->pcdata->questpoints -= quest_table[i].cost;
						ch->pcdata->condition[COND_FULL] = -1;
						ch->pcdata->condition[COND_THIRST] = -1;
						act
							("$N calls upon the power of the gods to relieve your mortal burdens.",
							 ch, NULL, questman, TO_CHAR);
						act
							("$N calls upon the power of the gods to relieve $n's mortal burdens.",
							 ch, NULL, questman, TO_ROOM);
						return;
					}
					else if ((obj =
							  create_object(get_obj_index
											(quest_table
											 [i].vnum), ch->level)) == NULL)
					{
						chprintln(ch,
								  "That object could not be found, contact an immortal.");
						return;
					}
					else
					{
						ch->pcdata->questpoints -= quest_table[i].cost;
					}
					if (!IS_SET(obj->pIndexData->extra_flags, ITEM_QUEST))
					{
						SET_BIT(obj->pIndexData->extra_flags, ITEM_QUEST);
						SET_BIT(obj->extra_flags, ITEM_QUEST);
					}
					act("$N gives $p to $n.", ch, obj, questman, TO_ROOM);
					act("$N gives you $p.", ch, obj, questman, TO_CHAR);
					obj_to_char(obj, ch);
					save_char_obj(ch);
					return;
				}
				else
				{
					sprintf(buf,
							"Sorry, %s, but you need %d quest points for that.",
							ch->name, quest_table[i].cost);
					do_mob_tell(ch, questman, buf);
					return;
				}
			}
		}
		sprintf(buf, "I don't have that item, %s.", ch->name);
		do_mob_tell(ch, questman, buf);
		return;
	}

	else if (!str_cmp(arg1, "sell"))
	{
		if (arg2[0] == '\0')
		{
			chprintln(ch, "To sell an item, type 'QUEST SELL <item>'.");
			return;
		}
		if ((obj = get_obj_carry(ch, arg2, ch)) == NULL)
		{
			chprintln(ch, "Which item is that?");
			return;
		}

		if (!IS_OBJ_STAT(obj, ITEM_QUEST))
		{
			sprintf(buf, "That is not a quest item, %s.", ch->name);
			do_mob_tell(ch, questman, buf);
			return;
		}

		for (i = 0; quest_table[i].name != NULL; i++)
		{
			if (quest_table[i].vnum <= 0)
				continue;
			if (quest_table[i].vnum == obj->pIndexData->vnum)
			{
				ch->pcdata->questpoints += quest_table[i].cost / 3;
				act("$N takes $p from $n.", ch, obj, questman, TO_ROOM);
				sprintf(buf,
						"$N takes $p from you for %d quest points.",
						quest_table[i].cost / 3);
				act(buf, ch, obj, questman, TO_CHAR);
				extract_obj(obj);
				save_char_obj(ch);
				return;
			}
		}
		sprintf(buf, "I only take items I sell, %s.", ch->name);
		do_mob_tell(ch, questman, buf);
		return;
	}
	else if (!str_cmp(arg1, "identify"))
	{

		if (arg2[0] == '\0')
		{
			chprintln(ch, "To identify an item, type 'QUEST IDENTIFY <item>'.");
			return;
		}

		for (i = 0; quest_table[i].name != NULL; i++)
		{
			if (is_name(arg2, quest_table[i].name))
			{
				if (quest_table[i].vnum == 0)
				{
					chprintln(ch, "That isn't a quest item.");
					return;
				}
				else if ((obj =
						  create_object(get_obj_index
										(quest_table[i].vnum),
										ch->level)) == NULL)
				{
					chprintln(ch,
							  "That object could not be found, contact an immortal.");
					return;
				}
				else
				{
					if (!IS_SET(obj->pIndexData->extra_flags, ITEM_QUEST))
					{
						SET_BIT(obj->pIndexData->extra_flags, ITEM_QUEST);
						SET_BIT(obj->extra_flags, ITEM_QUEST);
					}
					obj_to_char(obj, ch);
					chprintlnf(ch, "%s costs %d questpoints.",
							   obj->short_descr, quest_table[i].cost);
					spell_identify(0, ch->level, ch, obj, TAR_OBJ_INV);
					extract_obj(obj);
					return;
				}
			}
		}
		sprintf(buf, "I don't have that item, %s.", ch->name);
		do_mob_tell(ch, questman, buf);
		return;
	}

	else if (!str_cmp(arg1, "request"))
	{
		act("$n asks $N for a quest.", ch, NULL, questman, TO_ROOM);
		act("You ask $N for a quest.", ch, NULL, questman, TO_CHAR);
		if (IS_SET(ch->act, PLR_QUESTOR))
		{
			do_mob_tell(ch, questman, "But you're already on a quest!");
			return;
		}
		if (ch->pcdata->nextquest > 0)
		{
			sprintf(buf,
					"You're very brave, %s, but let someone else have a chance.",
					ch->name);
			do_mob_tell(ch, questman, buf);
			do_mob_tell(ch, questman, "Come back later.");
			return;
		}

		sprintf(buf, "Thank you, brave %s!", ch->name);
		do_mob_tell(ch, questman, buf);
		ch->pcdata->questmob = 0;
		ch->pcdata->questobj = 0;

		generate_quest(ch, questman);
		return;
	}
	else if (!str_cmp(arg1, "complete"))
	{
		if (ch->pcdata->questgiver != questman->pIndexData->vnum)
		{
			do_mob_tell(ch, questman,
						"I never sent you on a quest! Perhaps you're thinking of someone else.");
			return;
		}

		if (IS_SET(ch->act, PLR_QUESTOR))
		{
			int reward, points;

			if (ch->pcdata->questmob == -1 && ch->pcdata->countdown > 0)
			{
				reward = number_range(125, 375);
				points = number_range(25, 70);
				act("$n informs $N $e has completed $s quest.",
					ch, NULL, questman, TO_ROOM);
				act("You inform $N you have completed $s quest.",
					ch, NULL, questman, TO_CHAR);

				do_mob_tell(ch, questman,
							"Congratulations on completing your quest!");

				sprintf(buf,
						"As a reward, I am giving you %d quest points, and %d gold.",
						points, reward);
				do_mob_tell(ch, questman, buf);
				if (chance(points / 5))
				{
					chprintln(ch, "You gain an extra Trivia Point!");
					ch->pcdata->trivia += 1;
				}
				end_quest(ch, QUEST_TIME);
				ch->gold += reward;
				ch->pcdata->questpoints += points;
				save_char_obj(ch);
				return;
			}
			else if (ch->pcdata->questobj > 0 && ch->pcdata->countdown > 0)
			{
				if ((obj = has_questobj(ch)) != NULL)
				{
					reward = number_range(125, 375);
					points = number_range(25, 75);
					act("$n informs $N $e has completed $s quest.",
						ch, NULL, questman, TO_ROOM);
					act("You inform $N you have completed $s quest.",
						ch, NULL, questman, TO_CHAR);

					act("You hand $p to $N.", ch, obj, questman, TO_CHAR);
					act("$n hands $p to $N.", ch, obj, questman, TO_ROOM);

					do_mob_tell(ch, questman,
								"Congratulations on completing your quest!");
					sprintf(buf,
							"As a reward, I am giving you %d quest points, and %d gold.",
							points, reward);
					do_mob_tell(ch, questman, buf);
					if (chance(points / 5))
					{
						chprintln(ch, "You gain an extra Trivia Point!");
						ch->pcdata->trivia += 1;
					}
					end_quest(ch, QUEST_TIME);
					ch->gold += reward;
					ch->pcdata->questpoints += points;
					extract_obj(obj);
					save_char_obj(ch);
					return;
				}
				else
				{
					do_mob_tell(ch, questman,
								"You haven't completed the quest yet, but there is still time!");
					return;
				}
				return;
			}
			else if ((ch->pcdata->questmob > 0
					  || ch->pcdata->questobj > 0) && ch->pcdata->countdown > 0)
			{
				do_mob_tell(ch, questman,
							"You haven't completed the quest yet, but there is still time!");
				return;
			}
		}
		if (ch->pcdata->nextquest > 0)
			sprintf(buf, "But you didn't complete your quest in time!");
		else
			sprintf(buf, "You have to REQUEST a quest first, %s.", ch->name);
		do_mob_tell(ch, questman, buf);
		return;
	}

	else if (!str_cmp(arg1, "quit") || !str_cmp(arg1, "fail"))
	{
		act("$n informs $N $e wishes to quit $s quest.", ch, NULL,
			questman, TO_ROOM);
		act("You inform $N you wish to quit $s quest.", ch, NULL,
			questman, TO_CHAR);
		if (ch->pcdata->questgiver != questman->pIndexData->vnum)
		{
			do_mob_tell(ch, questman,
						"I never sent you on a quest! Perhaps you're thinking of someone else.");
			return;
		}

		if (IS_SET(ch->act, PLR_QUESTOR))
		{
			end_quest(ch, QUEST_TIME - 2);
			do_mob_tell(ch, questman,
						"Your quest is over, but for your cowardly behavior, you may not quest again for 15 minutes.");
			return;
		}
		else
		{
			chprintln(ch, "You aren't on a quest!");
			return;
		}
	}

	quest_usage(ch);
	return;
}

#define MAX_QMOB_COUNT mobile_count

void generate_quest(CHAR_DATA * ch, CHAR_DATA * questman)
{
	CHAR_DATA *victim = NULL;
	ROOM_INDEX_DATA *room = NULL;
	CHAR_DATA **mobs;
	int mob_count;
	OBJ_DATA *questitem = NULL;
	char buf[MAX_STRING_LENGTH];
	int mrange;

	/*
	 * * find MAX_QMOB_COUNT quest mobs and store their vnums in mob_buf 
	 */
	alloc_mem(mobs, CHAR_DATA *, MAX_QMOB_COUNT);

	mob_count = 0;
	for (victim = char_first; victim; victim = victim->next)
	{

		if (!IS_NPC(victim)
			|| !quest_level_diff(ch, victim)
			|| victim->pIndexData == NULL
			|| victim->in_room == NULL
			|| victim->pIndexData->pShop != NULL
			|| (IS_EVIL(victim) && IS_EVIL(ch) && chance(50))
			|| (IS_GOOD(victim) && IS_GOOD(ch) && chance(50))
			|| victim->pIndexData->vnum < 100
			|| victim->in_room->clan != NULL
			|| IS_SET(victim->imm_flags, IMM_WEAPON | IMM_MAGIC)
			|| IS_SET(victim->act,
					  ACT_TRAIN | ACT_PRACTICE | ACT_IS_HEALER | ACT_PET
					  | ACT_PET | ACT_GAIN)
			|| IS_SET(victim->affected_by, AFF_CHARM)
			|| IS_SET(victim->in_room->room_flags, ROOM_PET_SHOP)
			|| questman->pIndexData == victim->pIndexData
			|| (IS_SET(victim->act, ACT_SENTINEL)
				&& IS_SET(victim->in_room->room_flags,
						  ROOM_PRIVATE | ROOM_SOLITARY | ROOM_SAFE)))
			continue;
		mobs[mob_count++] = victim;
		if (mob_count >= MAX_QMOB_COUNT)
			break;
	}

	if (mob_count == 0)			/* not likely but just in case */
	{
		do_mob_tell(ch, questman,
					"I'm sorry, but I don't have any quests for you at this time.");
		do_mob_tell(ch, questman, "Try again later.");
		end_quest(ch, QUEST_TIME / 10);
		free_mem(mobs);
		return;
	}

	/*
	 * at this point the player is sure to get a quest 
	 */
	ch->pcdata->questgiver = questman->pIndexData->vnum;
	mrange = number_range(0, mob_count - 1);
	while ((victim = mobs[mrange]) == NULL)
		mrange = number_range(0, mob_count - 1);
	room = victim->in_room;
	ch->pcdata->questloc = room->vnum;

	/* countdown set here so we can use it for object timers */
	ch->pcdata->countdown = number_range(12, 30);

	/*
	 * 20% chance it will send the player on a 'recover item' quest.
	 */

	if (chance(20))
	{
		vnum_t objvnum = 0;

		switch (number_range(0, 3))
		{
		case 0:
			objvnum = QUEST_OBJQUEST1;
			break;

		case 1:
			objvnum = QUEST_OBJQUEST2;
			break;

		case 2:
			objvnum = QUEST_OBJQUEST3;
			break;

		case 3:
			objvnum = QUEST_OBJQUEST4;
			break;
		}

		questitem = create_object(get_obj_index(objvnum), ch->level);
		obj_to_room(questitem, room);
		REMOVE_BIT(ch->act, PLR_CANLOOT);
		free_string(questitem->owner);
		questitem->owner = str_dup(ch->name);
		questitem->cost = 0;
		questitem->timer = (4 * ch->pcdata->countdown + 10) / 3;
		ch->pcdata->questobj = questitem->pIndexData->vnum;

		switch (number_range(0, 1))
		{
		default:
		case 0:
			sprintf(buf,
					"Vile pilferers have stolen %s from the royal treasury!",
					questitem->short_descr);
			do_mob_tell(ch, questman, buf);
			do_mob_tell(ch, questman,
						"My court wizardess, with her magic mirror, has pinpointed its location.");
			break;
		case 1:
			sprintf(buf,
					"A powerful wizard has stolen %s for his personal power!",
					questitem->short_descr);
			do_mob_tell(ch, questman, buf);
			break;
		}

		if (room->name != NULL)
		{
			sprintf(buf,
					"Look for %s somewhere in the vicinity of %s!",
					questitem->short_descr, room->name);
			do_mob_tell(ch, questman, buf);

			sprintf(buf,
					"That location is in the general area of %s.",
					room->area->name);
			do_mob_tell(ch, questman, buf);
		}
	}

	/*
	 * Quest to kill a mob 
	 */

	else
	{
		switch (number_range(0, 3))
		{
		default:
		case 0:
			sprintf(buf,
					"An enemy of mine, %s, is making vile threats against the crown.",
					victim->short_descr);
			do_mob_tell(ch, questman, buf);
			do_mob_tell(ch, questman, "This threat must be eliminated!");
			break;

		case 1:
			sprintf(buf,
					"Thera's most heinous criminal, %s, has escaped from the dungeon!",
					victim->short_descr);
			do_mob_tell(ch, questman, buf);
			sprintf(buf,
					"Since the escape, %s has murdered %d civillians!",
					victim->short_descr, number_range(2, 20));
			do_mob_tell(ch, questman, buf);
			do_mob_tell(ch, questman,
						"The penalty for this crime is death, and you are to deliver the sentence!");
			break;

		case 2:
			sprintf(buf,
					"The Mayor of Midgaard has recently been attacked by %s.  This is an act of war!",
					victim->short_descr);
			do_mob_tell(ch, questman, buf);
			sprintf(buf,
					"%s must be severly dealt with for this injustice.",
					victim->short_descr);
			do_mob_tell(ch, questman, buf);
			break;

		case 3:
			sprintf(buf,
					"%s has been stealing valuables from the citizens of Arkham.",
					victim->short_descr);
			do_mob_tell(ch, questman, buf);
			sprintf(buf,
					"Make sure that %s never has the chance to steal again.",
					victim->short_descr);
			do_mob_tell(ch, questman, buf);
			break;

		}

		if (room->name != NULL)
		{
			sprintf(buf,
					"Seek %s out somewhere in the vicinity of %s!",
					victim->short_descr, room->name);
			do_mob_tell(ch, questman, buf);

			sprintf(buf,
					"That location is in the general area of %s.",
					room->area->name);
			do_mob_tell(ch, questman, buf);
		}
		ch->pcdata->questmob = victim->pIndexData->vnum;
	}
	if (ch->pcdata->questmob > 0 || ch->pcdata->questobj > 0)
	{
		SET_BIT(ch->act, PLR_QUESTOR);
		sprintf(buf, "You have %d minutes to complete this quest.",
				ch->pcdata->countdown);
		do_mob_tell(ch, questman, buf);
		do_mob_tell(ch, questman, "May the gods go with you!");
	}
	else
		end_quest(ch, QUEST_TIME / 10);
	free_mem(mobs);
	return;
}

bool quest_level_diff(CHAR_DATA * ch, CHAR_DATA * mob)
{
	int bonus = 20;				/* can modify this */

	if (IS_IMMORTAL(ch))
		return TRUE;
	else if (ch->level > (mob->level + bonus)
			 || ch->level < (mob->level - bonus))
		return FALSE;
	else
		return TRUE;
}

void quest_update(void)
{
	DESCRIPTOR_DATA *d;
	CHAR_DATA *ch;

	for (d = descriptor_first; d != NULL; d = d->next)
	{
		if (d->connected == CON_PLAYING
			&& (ch = d->original ? d->original : d->character) != NULL)
		{

			if (ch->pcdata->nextquest > 0)
			{
				ch->pcdata->nextquest--;
				if (ch->pcdata->nextquest == 0)
				{
					chprintln(ch, "You may now quest again.");
					return;
				}
			}
			else if (IS_SET(ch->act, PLR_QUESTOR))
			{
				if (--ch->pcdata->countdown <= 0)
				{
					end_quest(ch, QUEST_TIME - 2);
					chprintlnf(ch,
							   "You have run out of time for your quest!\n\rYou may quest again in %d minutes.",
							   ch->pcdata->nextquest);
				}
				if (ch->pcdata->countdown > 0 && ch->pcdata->countdown < 6)
				{
					chprintln(ch,
							  "Better hurry, you're almost out of time for your quest!");
					return;
				}
			}
		}
	}
	return;
}

void end_quest(CHAR_DATA * ch, int time)
{
	REMOVE_BIT(ch->act, PLR_QUESTOR);
	ch->pcdata->questgiver = 0;
	ch->pcdata->countdown = 0;
	ch->pcdata->questmob = 0;
	ch->pcdata->questobj = 0;
	ch->pcdata->questloc = 0;
	ch->pcdata->nextquest = time;
}

/* handy dandy, victim must be an NPC */
void do_mob_tell(CHAR_DATA * ch, CHAR_DATA * victim, char *argument)
{

	if (!victim)
		return;

	chprintlnf(ch, "%s tells you '%s'", victim->short_descr, argument);
	return;
}

CH_CMD(do_tpspend)
{
	CHAR_DATA *triviamob;
	char buf[MAX_STRING_LENGTH];
	char arg1[MAX_INPUT_LENGTH];
	char arg2[MAX_INPUT_LENGTH];
	int sav_trust;

	argument = one_argument(argument, arg1);
	strcpy(arg2, argument);

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

	if (arg1[0] == '\0')
	{
		chprintln(ch, "Syntax: Tpspend <item>");
		chprintln(ch, "        Tpspend list");
		return;
	}

	if (is_name(arg1, "list"))
	{
		chprintln(ch, "Trivia Point Options");
		chprintln(ch, "corpse retrival......1tp");
		chprintln(ch, "transfer.............1tp");
		chprintln(ch, "restore..............1tp");
		chprintln(ch, "5 trains.............1tp");
		chprintln(ch, "40 practices.........1tp");
		chprintln(ch, "75 questpoints.......1tp");
		chprintln(ch, "1 Trivia Pill........1tp");
		chprintln(ch, "See HELP TRIVIA for important info before buying.");
		chprintln(ch,
				  "For transfers and corpses, you do not have to be at a trivia shop.");
		return;
	}
	else if (is_name(arg1, "corpse"))
	{
		if (ch->pcdata->trivia >= 1)
		{
			OBJ_DATA *c;
			int count = 0;

			for (c = object_first; c != NULL; c = c->next)
			{
				if (is_name(ch->name, c->owner)
					&& c->item_type == ITEM_CORPSE_PC)
				{
					if (c->in_room)
						obj_from_room(c);
					else if (c->carried_by)
						obj_from_char(c);
					else if (c->in_obj)
						obj_from_obj(c->in_obj);
					obj_to_room(c, ch->in_room);
					count++;
				}
			}
			if (count == 0)
				chprintln(ch, "You have no corpses in the game.");
			else
			{
				ch->pcdata->trivia -= 1;
				if (count == 1)
				{
					chprintln(ch, "Your corpse appears in the room.");
					act("$n's corpse appears in the room.",
						ch, NULL, NULL, TO_ROOM);
				}
				else
				{
					chprintln(ch, "All your corpses appear in the room.");
					act("All of $n's corpses appear in the room.",
						ch, NULL, NULL, TO_ROOM);
				}
			}
			return;
		}
		else
		{
			chprintln(ch, "You don't have enough trivia points for that.");
			return;
		}
	}
	else if (is_name(arg1, "transfer"))
	{
		if (ch->pcdata->trivia >= 1)
		{
			ROOM_INDEX_DATA *oldroom;

			if (arg2[0] == '\0')
			{
				chprintln(ch,
						  "Transfer you where? [recall/room name/character name]");
				return;
			}
			else if (!str_cmp(arg2, "recall"))
				sprintf(arg2, "%d", ROOM_VNUM_TEMPLE);

			oldroom = ch->in_room;
			sprintf(buf, "self '%s'", arg2);
			sav_trust = ch->trust;
			ch->trust = MAX_LEVEL;
			do_function(ch, &do_transfer, buf);
			ch->trust = sav_trust;
			if (oldroom != ch->in_room)
				ch->pcdata->trivia -= 1;
			else
				chprintln(ch,
						  "Whoops! You were not charged for that transfer.");
			return;
		}
		else
		{
			chprintln(ch, "You don't have enough trivia points for that.");
			return;
		}
	}
	for (triviamob = ch->in_room->first_person; triviamob != NULL;
		 triviamob = triviamob->next_in_room)
	{
		if (!IS_NPC(triviamob))
			continue;
		if (triviamob->spec_fun == spec_lookup("spec_triviamob"))
			break;
	}

	if (triviamob == NULL
		|| triviamob->spec_fun != spec_lookup("spec_triviamob"))
	{
		chprintln(ch, "You can't do that here.");
		return;
	}

	if (triviamob->fighting != NULL)
	{
		chprintln(ch, "Wait until the fighting stops.");
		return;
	}

	if (is_name(arg1, "practices pracs practice"))
	{
		if (ch->pcdata->trivia >= 1)
		{
			ch->pcdata->trivia -= 1;
			ch->practice += 40;
			act("$N gives 40 practices to $n.", ch, NULL, triviamob, TO_ROOM);
			act("$N gives you 40 practices.", ch, NULL, triviamob, TO_CHAR);
			return;
		}
		else
		{
			sprintf(buf,
					"Sorry, %s, but you don't have enough trivia points for that.",
					ch->name);
			do_mob_tell(ch, triviamob, buf);
			return;
		}
	}
	else if (is_name(arg1, "trains train"))
	{
		if (ch->pcdata->trivia >= 1)
		{
			ch->pcdata->trivia -= 1;
			ch->train += 5;
			act("$N gives 5 training sessions to $n.", ch, NULL,
				triviamob, TO_ROOM);
			act("$N gives you 5 training sessions.", ch, NULL,
				triviamob, TO_CHAR);
			return;
		}
		else
		{
			sprintf(buf,
					"Sorry, %s, but you don't have enough trivia points for that.",
					ch->name);
			do_mob_tell(ch, triviamob, buf);
			return;
		}
	}
	else if (is_name(arg1, "questpoints points"))
	{
		if (ch->pcdata->trivia >= 1)
		{
			ch->pcdata->trivia -= 1;
			ch->pcdata->questpoints += 75;	/* a trivia point costs 100 quest points 25% conversion */
			act("$N gives 75 questpoints to $n.", ch, NULL, triviamob, TO_ROOM);
			act("$N gives you 75 questpoints.", ch, NULL, triviamob, TO_CHAR);
			return;
		}
		else
		{
			sprintf(buf,
					"Sorry, %s, but you don't have enough trivia points for that.",
					ch->name);
			do_mob_tell(ch, triviamob, buf);
			return;
		}
	}
	else if (is_name(arg1, "pill"))
	{
		OBJ_DATA *obj = NULL;

		if (ch->pcdata->trivia >= 1)
		{
			obj = create_object(get_obj_index(OBJ_VNUM_TRIVIA_PILL), 1);
			if (obj != NULL)
			{
				act("$N gives $p to $n.", ch, obj, triviamob, TO_ROOM);
				act("$N gives you $p.", ch, obj, triviamob, TO_CHAR);
				obj_to_char(obj, ch);
				ch->pcdata->trivia -= 1;
				return;
			}
			else
			{
				sprintf(buf,
						"I don't any more trivia pills to give, %s.", ch->name);
				do_mob_tell(ch, triviamob, buf);
			}
			return;
		}

		else
		{
			sprintf(buf,
					"Sorry, %s, but you don't have enough trivia points for that.",
					ch->name);
			do_mob_tell(ch, triviamob, buf);
			return;
		}
	}
	else if (is_name(arg1, "restore"))
	{
		if (ch->pcdata->trivia >= 1)
		{
			sav_trust = ch->trust;
			ch->trust = MAX_LEVEL;
			do_function(ch, &do_restore, "all");
			ch->trust = sav_trust;
			ch->pcdata->trivia -= 1;
			return;
		}
		else
		{
			sprintf(buf,
					"Sorry, %s, but you don't have enough trivia points for that.",
					ch->name);
			do_mob_tell(ch, triviamob, buf);
			return;
		}
	}
	else
		do_tpspend(ch, "list");
}

CH_CMD(do_qpgive)
{
	CHAR_DATA *victim;
	char arg[MAX_INPUT_LENGTH], buf[MAX_INPUT_LENGTH];
	int amount;

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

	argument = one_argument(argument, arg);

	if (argument[0] == '\0' || !is_number(arg))
	{
		chprintln(ch, "Syntax: qpgive [amount] [person]");
		return;
	}

	if ((amount = atoi(arg)) <= 0)
	{
		chprintln(ch, "Give how many questpoints?");
		return;
	}

	if (amount > ch->pcdata->questpoints)
	{
		chprintln(ch, "You don't have that many questpoints to give.");
		return;
	}

	if ((victim = get_char_room(ch, NULL, argument)) == NULL)
	{
		chprintln(ch, "That person is not here.");
		return;
	}

	if (IS_NPC(victim))
	{
		chprintln(ch, "NPC's don't need quest points.");
		return;
	}

	if (victim == ch)
	{
		sprintf(buf,
				"You give yourself %d questpoints..... don't you feel better?\n\r",
				amount);
		return;
	}

	ch->pcdata->questpoints -= amount;
	victim->pcdata->questpoints += amount;

	sprintf(buf, "%d", amount);
	act("$n gives you $t questpoints.", ch, buf, victim, TO_VICT);
	act("You give $N $t questpoints.", ch, buf, victim, TO_CHAR);
	act("$n gives $N $t questpoints.", ch, buf, victim, TO_ROOM);

	return;
}

CH_CMD(do_tpgive)
{
	CHAR_DATA *victim;
	char arg[MAX_INPUT_LENGTH], buf[MAX_INPUT_LENGTH];
	int amount;

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

	argument = one_argument(argument, arg);

	if (argument[0] == '\0' || !is_number(arg))
	{
		chprintln(ch, "Syntax: tpgive [amount] [person]");
		return;
	}

	if ((amount = atoi(arg)) <= 0)
	{
		chprintln(ch, "Give how many trivia points?");
		return;
	}

	if (amount > ch->pcdata->trivia)
	{
		chprintln(ch, "You don't have that many trivia points to give!!");
		return;
	}

	if ((victim = get_char_room(ch, NULL, argument)) == NULL)
	{
		chprintln(ch, "That person is not here.");
		return;
	}

	if (IS_NPC(victim))
	{
		chprintln(ch, "NPC's don't need trivia points.");
		return;
	}

	if (victim == ch)
	{
		sprintf(buf,
				"You give yourself %d trivia points..... don't you feel better?\n\r",
				amount);
		return;
	}

	ch->pcdata->trivia -= amount;
	victim->pcdata->trivia += amount;

	sprintf(buf, "%d", amount);
	act("$n gives you $t questpoints.", ch, buf, victim, TO_VICT);
	act("You give $N $t questpoints.", ch, buf, victim, TO_CHAR);
	act("$n gives $N $t questpoints.", ch, buf, victim, TO_ROOM);

	return;
}