dotd-2.3.7/area/
dotd-2.3.7/clans/
dotd-2.3.7/classes/
dotd-2.3.7/councils/
dotd-2.3.7/deity/
dotd-2.3.7/dict/
dotd-2.3.7/doc/mudprogs/
dotd-2.3.7/player/a/
dotd-2.3.7/player/g/
/******************************************************
            Desolation of the Dragon MUD II
      (C) 1997-2002  Jesse DeFer
          http://www.dotd.com  dotd@dotd.com
 ******************************************************/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#include "mud.h"
#include "quest.h"


#ifdef KEY
#undef KEY
#endif

#define KEY( literal, field, value )                                    \
                                if ( !str_cmp( word, literal ) )        \
                                {                                       \
                                    field  = value;                     \
                                    fMatch = TRUE;                      \
                                    break;                              \
                                }
QUEST_DATA *first_quest;
QUEST_DATA *last_quest;
sh_int quests_active;

char *	const	quest_flags [32] =
{
    "active", "inorder", "autoreset", "allownpc", "onlyonce",
    "q6", "q7", "q8", "q9", "q10",
    "q11", "q12", "q13", "q14", "q15",
    "q16", "q17", "q18", "q19", "q20",
    "q21", "q22", "q23", "q24", "q25",
    "q26", "q27", "q28", "q29", "q30",
    "q31", "q32"
};

char *	const	task_flags [32] =
{
    "optional", "r2", "r3", "q4", "q5",
    "q6", "q7", "q8", "q9", "q10",
    "q11", "q12", "q13", "q14", "q15",
    "q16", "q17", "q18", "q19", "q20",
    "q21", "q22", "q23", "q24", "q25",
    "q26", "q27", "q28", "q29", "q30",
    "q31", "q32"
};

char *  const   quest_task_type_names [MAX_TASK_TYPE] =
{
    "mobfind", "objfind", "roomfind", "mobkill", "objgive", "objplace"
};

void free_quest_task(QUEST_DATA *quest, QUEST_TASK *task)
{
    if (task->completed_by)
        STRFREE(task->completed_by);
    if (task->description)
        DISPOSE(task->description);

    UNLINK(task, quest->first_task, quest->last_task, next, prev);
    DISPOSE(task);

    if (IS_QUEST_FLAG(quest, QUEST_ACTIVE) && !quest->first_task)
    {
        REMOVE_QUEST_FLAG(quest, QUEST_ACTIVE);
        quests_active--;
    }
}

void free_quest(QUEST_DATA *quest)
{
    QUEST_TASK *task;

    if (quest->name)
        STRFREE(quest->name);
    if (quest->owner)
        STRFREE(quest->owner);
    if (quest->creator)
        STRFREE(quest->creator);
    if (quest->description)
        DISPOSE(quest->description);
    if (quest->completed_by)
        STRFREE(quest->completed_by);

    while ((task = quest->first_task))
        free_quest_task(quest, task);

    if (IS_QUEST_FLAG(quest, QUEST_ACTIVE))
        quests_active--;

    UNLINK(quest, first_quest, last_quest, next, prev);
    DISPOSE(quest);
}

void free_quests(void)
{
    QUEST_DATA *quest;

    while ((quest = first_quest))
        free_quest(quest);
}

void reset_quest(QUEST_DATA *quest)
{
    QUEST_TASK *task;

    quest->completed = 0;
    if (quest->completed_by)
        STRFREE(quest->completed_by);
    for (task = quest->first_task; task; task = task->next)
    {
        if (task->completed_by)
            STRFREE(task->completed_by);
        task->completed = 0;
    }
    if (!IS_QUEST_FLAG(quest, QUEST_ACTIVE))
    {
        SET_QUEST_FLAG(quest, QUEST_ACTIVE);
        if (quest->first_task)
            quests_active++;
    }
}

void fread_quest_task(QUEST_TASK *task, FILE *fp)
{
    const char *word = NULL;
    bool fMatch = FALSE;

    for ( ; ; )
    {
        word   = feof( fp ) ? "EndTask" : fread_word( fp );
        fMatch = FALSE;

        switch ( UPPER(word[0]) )
        {
        case '*':
            fMatch = TRUE;
            fread_to_eol( fp );
            break;
        case 'E':
            if ( !str_cmp( word, "EndTask" ) )
                return;
            break;
        case 'C':
            KEY( "Completed",     task->completed,      fread_number( fp ) );
            KEY( "CompletedBy",   task->completed_by,   fread_string( fp ) );
            break;
        case 'D':
            KEY( "Description",   task->description,    fread_string_nohash( fp ) );
            break;
        case 'F':
            KEY( "Flags",         task->flags,          fread_number( fp ) );
            break;
        case 'G':
            KEY( "Glory",         task->glory,          fread_number( fp ) );
            break;
        case 'T':
            KEY( "Type",          task->type,           fread_number( fp ) );
            break;
        case 'V':
            KEY( "Vnum",          task->vnum1,          fread_number( fp ) );
            KEY( "Vnum1",         task->vnum1,          fread_number( fp ) );
            KEY( "Vnum2",         task->vnum2,          fread_number( fp ) );
            break;
        }

        if ( !fMatch )
        {
            bug( "fread_quest_task: no match: %s", word );
            fread_to_eol( fp );
        }
    }
}

void fread_quest(QUEST_DATA *quest, FILE *fp)
{
    const char *word = NULL;
    bool fMatch = FALSE;

    for ( ; ; )
    {
        word   = feof( fp ) ? "End" : fread_word( fp );
        fMatch = FALSE;

        switch ( UPPER(word[0]) )
        {
        case '*':
            fMatch = TRUE;
            fread_to_eol( fp );
            break;
        case 'E':
            if ( !str_cmp( word, "End" ) )
                return;
            break;
        case 'C':
            KEY( "Creator",      quest->creator,        fread_string( fp ) );
            KEY( "Completed",    quest->completed,      fread_number( fp ) );
            KEY( "CompletedBy",  quest->completed_by,   fread_string( fp ) );
            break;
        case 'D':
            KEY( "Description",  quest->description,    fread_string_nohash( fp ) );
            break;
        case 'F':
            KEY( "Flags",        quest->flags,          fread_number( fp ) );
            break;
        case 'G':
            KEY( "Glory",        quest->glory,          fread_number( fp ) );
            break;
        case 'N':
            KEY( "Name",         quest->name,           fread_string( fp ) );
            break;
        case 'O':
            KEY( "Owner",        quest->owner,          fread_string( fp ) );
            break;
        case 'T':
            if ( !str_cmp( word, "Task" ) )
            {
                QUEST_TASK *task;
                CREATE(task, QUEST_TASK, 1);
                fread_quest_task(task, fp);
                LINK(task, quest->first_task, quest->last_task, next, prev);
                fMatch = TRUE;
                break;
            }
            break;
        }

        if ( !fMatch )
        {
            bug( "fread_quest: no match: %s", word );
            fread_to_eol( fp );
        }
    }
}

void load_quests(void)
{
    FILE *fp;
    QUEST_DATA *quest;
    char letter, *word;

    first_quest = NULL;
    last_quest = NULL;
    quests_active = 0;

    if (!(fp = fopen(QUEST_FILE, "r")))
        return;

    while (!feof(fp))
    {
        letter = fread_letter( fp );

        if ( letter == '*' )
        {
            fread_to_eol( fp );
            continue;
        }

        if ( letter != '#' )
        {
            bug( "load_quests: # not found (%c(%d) instead)", letter, letter );
            break;
        }

        word = fread_word( fp );
        if ( !str_cmp( word, "QUEST" ) )
        {
            CREATE(quest, QUEST_DATA, 1);
            fread_quest(quest, fp);
            LINK(quest, first_quest, last_quest, next, prev);
            if (IS_QUEST_FLAG(quest, QUEST_ACTIVE) &&
                quest->first_task)
                quests_active++;
            continue;
        }

        if ( !str_cmp( word, "END" ) )
            break;

        bug( "load_quests: bad section %s", word );
        break;
    }

    FCLOSE(fp);

    boot_log("load_quests: %d active quests", quests_active);
}

void fwrite_quest(FILE *fp, QUEST_DATA *quest)
{
    QUEST_TASK *task;

    fprintf(fp, "#QUEST\n");
    fprintf(fp, "Name          %s~\n", strip_cr(quest->name));
    if (quest->description)
        fprintf(fp, "Description   %s~\n", strip_cr(quest->description));
    fprintf(fp, "Creator       %s~\n", quest->creator);
    if (quest->owner)
        fprintf(fp, "Owner         %s~\n", quest->owner);
    if (quest->completed_by)
        fprintf(fp, "CompletedBy   %s~\n", quest->completed_by);
    if (quest->completed)
        fprintf(fp, "Completed     %ld\n", (unsigned long)quest->completed);
    if (quest->flags)
        fprintf(fp, "Flags         %d\n",  quest->flags);
    if (quest->glory)
        fprintf(fp, "Glroy         %d\n",  quest->glory);

    for (task = quest->first_task; task; task = task->next)
    {
        fprintf(fp, "Task\n");
        if (task->description)
            fprintf(fp, "Description   %s~\n", strip_cr(task->description));
        if (task->completed_by)
            fprintf(fp, "CompletedBy   %s~\n", task->completed_by);
        if (task->completed)
            fprintf(fp, "Completed     %ld\n", (unsigned long)task->completed);
        if (task->flags)
            fprintf(fp, "Flags         %d\n",  task->flags);
        fprintf(fp, "Type          %d\n",  task->type);
        if (task->vnum1)
            fprintf(fp, "Vnum1         %d\n",  task->vnum1);
        if (task->vnum2)
            fprintf(fp, "Vnum2         %d\n",  task->vnum2);
        if (task->glory)
            fprintf(fp, "Glory         %d\n",  task->glory);
        fprintf(fp, "EndTask\n");
    }
    fprintf(fp, "End\n\n");
}

void save_quests(void)
{
    FILE *fp;
    QUEST_DATA *quest;

    if (!(fp = fopen(QUEST_FILE, "w")))
    {
        bug("Unable to write quest data to " QUEST_FILE);
        return;
    }

    for (quest = first_quest; quest; quest = quest->next)
        fwrite_quest(fp, quest);

    fprintf(fp, "#END\n");

    FCLOSE(fp);
}

QUEST_DATA *find_quest(char *name)
{
    QUEST_DATA *quest;

    for (quest = first_quest; quest; quest = quest->next)
        if (!str_prefix(name, quest->name))
            return quest;

    return NULL;
}

int get_questflag( char *flag )
{
    int x;

    for ( x = 0; x < 32; x++ )
        if ( !str_cmp( flag, quest_flags[x] ) )
            return x;
    return -1;
}

int get_taskflag( char *flag )
{
    int x;

    for ( x = 0; x < 32; x++ )
        if ( !str_cmp( flag, task_flags[x] ) )
            return x;
    return -1;
}

int get_questtasktype( char *flag )
{
    int x;

    for ( x = 0; x < MAX_TASK_TYPE; x++ )
        if ( !str_cmp( flag, quest_task_type_names[x] ) )
            return x;
    return -1;
}

char *quest_onlyonce_makevarname(QUEST_DATA *quest)
{
    static char varname[MAX_VAR_NAME_LEN];
    unsigned int x, y = 0;

    strcpy(varname, "qoo_");
    x = strlen(varname);

    while (x < MAX_VAR_NAME_LEN-1 && y < strlen(quest->name))
    {
        if (isalnum(quest->name[y]))
            varname[x++] = quest->name[y++];
        else
            y++;
    }
    varname[x] = '\0';

    return varname;
}

void quest_onlyonce_add(CHAR_DATA *ch, QUEST_DATA *quest)
{
    char *varname;

    if (!(varname = quest_onlyonce_makevarname(quest)))
    {
        bug("quest_onlyonce_add: bad variable name");
        return;
    }

    if (get_var(ch->vars, varname))
    {
        bug("quest_onlyonce_add: ch already has onlyonce variable %s",
            varname);
        return;
    }

    set_var(&ch->vars, varname, "completed");
}

bool quest_onlyonce_check(CHAR_DATA *ch, QUEST_DATA *quest)
{
    char *varname;

    if (!IS_QUEST_FLAG(quest, QUEST_ONLYONCE))
        return FALSE;

    if (!(varname = quest_onlyonce_makevarname(quest)))
    {
        bug("quest_onlyonce_check: bad variable name");
        return FALSE;
    }

    if (check_var_equals(ch->vars, varname, "completed"))
        return TRUE;

    return FALSE;
}

void do_quests(CHAR_DATA *ch, char *argument)
{
    QUEST_DATA *quest;
    QUEST_TASK *task;
    int icnt = 0;
    char s1[16], s2[16], s3[16], s4[16];

    sprintf(s1, "%s", color_str(AT_SCORE, ch));
    sprintf(s2, "%s", color_str(AT_SCORE2, ch));
    sprintf(s3, "%s", color_str(AT_SCORE3, ch));
    sprintf(s4, "%s", color_str(AT_SCORE4, ch));

    if (!str_cmp(argument, "list"))
    {
        pager_printf(ch, "%-40.40s %-10.10s %-16.16s\n\r",
                     "[Quest Name]", "[Glory]", "[Creator]");
        for (quest = first_quest; quest; quest = quest->next)
        {
            if (!IS_QUEST_FLAG(quest, QUEST_ACTIVE) ||
                !quest->name || !quest->owner ||
                quest->completed)
                continue;

            if (str_cmp(quest->owner, GET_NAME(ch)))
                continue;

            if (quest_onlyonce_check(ch, quest))
                continue;

            pager_printf(ch, "%-40.40s %-10d %-16.16s\n\r",
                         quest->name?quest->name:"(none)",
                         quest->glory,
                         quest->creator?quest->creator:"(nobody)");
            icnt++;
        }

        pager_printf(ch, "\n\rQuests that anybody can complete:\n\r");
        for (quest = first_quest; quest; quest = quest->next)
        {
            if (!IS_QUEST_FLAG(quest, QUEST_ACTIVE) ||
                !quest->name || !quest->owner ||
                quest->completed)
                continue;

            if (str_cmp(quest->owner, "Anybody"))
                continue;

            if (quest_onlyonce_check(ch, quest))
                continue;

            pager_printf(ch, "%-40.40s %-10d %-16.16s\n\r",
                         quest->name,
                         quest->glory,
                         quest->creator?quest->creator:"(nobody)");
            icnt++;
        }
        pager_printf(ch, "%d quests available.\n\r", icnt);
        return;
    }

    if (!str_cmp(argument, "completed"))
    {
        pager_printf(ch, "%-40.40s %s\n\r",
                     "[Quest Name]", "[Completed]");
        for (quest = first_quest; quest; quest = quest->next)
        {
            if (!quest->name || !quest->owner ||
                !quest->completed ||
                str_cmp(quest->completed_by, GET_NAME(ch)) ||
                str_cmp(quest->owner, GET_NAME(ch)))
                continue;

            pager_printf(ch, "%-40.40s %.24s\n\r",
                         quest->name,
                         ctime(&quest->completed));
        }

        return;
    }

    if (argument && *argument && (quest = find_quest(argument)))
    {
        if (!quest->name || !quest->owner ||
            (str_cmp(quest->owner, GET_NAME(ch)) &&
             str_cmp(quest->owner, "Anybody")))
        {
            send_to_char("You cannot view that quest.\n\r", ch);
            return;
        }

        ch_printf(ch,
                  "%sName         : %s%s\n\r"
                  "%sCreator      : %s%s\n\r"
                  "%sOwner        : %s%s\n\r",
                  s1, s3, quest->name?quest->name:"(none)",
                  s1, s3, quest->creator?quest->creator:"(nobody)",
                  s1, s3, quest->owner?quest->owner:"(nobody)"
                 );

        if (quest->completed)
            ch_printf(ch,
                      "%sCompleted By : %s%s\n\r"
                      "%sCompleted    : %s%.24s\n\r",
                      s1, s3, quest->completed_by?quest->completed_by:"Nobody",
                      s1, s3, quest->completed?ctime(&quest->completed):"Never"
                     );

        if (quest->glory)
            ch_printf(ch,
                      "%sGlory Awarded: %s%d\n\r",
                      s1, s2, quest->glory
                     );


        icnt = 0;
	for (task = quest->first_task; task; task = task->next)
	    icnt++;

        ch_printf(ch,
                  "%sDescription  :\n\r%s%s\n\r"
                  "%sTasks (%s%d%s total):\n\r",
                  s1, s3, quest->description?quest->description:"(none)",
		  s1, s2, icnt, s1
                 );

        if (!quest->first_task)
        {
            send_to_char(" None\n\r", ch);
            return;
        }

        icnt = 0;
        for (task = quest->first_task; task; task = task->next)
        {
            if (IS_QUEST_FLAG(quest, QUEST_INORDER) &&
                task->completed)
                continue;

            ch_printf(ch,
                      " %s%2d%s) %s%s\n\r",
                      task->completed?s4:s2,
                      ++icnt, s1,
                      s3, task->description?task->description:"No Description"
                     );

            if (IS_QUEST_FLAG(quest, QUEST_INORDER) &&
                !IS_TASK_FLAG(task, TASK_OPTIONAL))
                break;
        }
        return;
    }

    send_to_char("Usage:\n\r"
                 "  quests list\n\r"
                 "  quests completed\n\r"
                 "  quests <name>\n\r",
                 ch);
}

void do_quest(CHAR_DATA *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];
    QUEST_DATA *quest;
    QUEST_TASK *task;
    int icnt = 0;

    if (!IS_IMMORTAL(ch))
    {
        do_quests(ch, argument);
        return;
    }

    if (!argument || !*argument)
    {
        send_to_char("Usage:\n\r"
                     "  quest list\n\r"
                     "  quest create <name>\n\r\n\r"
                     "  quest <name> <command>\n\r"
                     "  Command being: delete show reset name desc owner flags glory task\n\r\n\r"
                     "  quest <name> task <number> <subcommand>\n\r"
                     "  Subcommand being: delete insert desc flags type vnum vnum2 glory\n\r",
                     ch);
        return;
    }

    if (!str_cmp(argument, "list"))
    {
        int acnt=0;

        pager_printf(ch, " %-40.40s %-16.16s %-16.16s\n\r",
                     "[Name]", "[Owner]", "[Creator]");
        for (quest = first_quest; quest; quest = quest->next)
        {
            pager_printf(ch, "%c%-40.40s %-16.16s %-16.16s\n\r",
                         (IS_QUEST_FLAG(quest, QUEST_ACTIVE) &&
                          quest->first_task)?'*':' ',
                         quest->name?quest->name:"(none)",
                         quest->owner?quest->owner:"(nobody)",
                         quest->creator?quest->creator:"(nobody)");
            if (IS_QUEST_FLAG(quest, QUEST_ACTIVE) &&
                quest->first_task)
                acnt++;
            icnt++;
        }
        pager_printf(ch, "%d matches found, %d active.\n\r", icnt, acnt);
        if (quests_active != acnt)
            bug("do_quests: quests_active (%d) is not equal to counted active quests (%d)", quests_active, acnt);
        return;
    }

    argument = one_argument(argument, arg);

    if (!str_cmp(arg, "create"))
    {
        CREATE(quest, QUEST_DATA, 1);
        LINK(quest, first_quest, last_quest, next, prev);

        if (argument && *argument)
            quest->name = STRALLOC(argument);
        else
            quest->name = STRALLOC("New Quest");
        quest->creator = STRALLOC(GET_NAME(ch));

        save_quests();

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

    if (!(quest = find_quest(arg)))
    {
        send_to_char("Can't find that quest.\n\r", ch);
        return;
    }

    if (!argument || !*argument || !str_cmp(argument, "show"))
    {
        ch_printf(ch,
                  "Name        : %s\n\r"
                  "Description : %s\n\r"
                  "Creator     : %s\n\r"
                  "Owner       : %s\n\r"
                  "Completed By: %s\n\r"
                  "Completed   : %.24s\n\r"
                  "Glory       : %d\n\r"
                  "Flags       : %s\n\r"
                  "Tasks:\n\r",
                  quest->name?quest->name:"(none)",
                  quest->description?quest->description:"(none)",
                  quest->creator?quest->creator:"(nobody)",
                  quest->owner?quest->owner:"(nobody)",
                  quest->completed_by?quest->completed_by:"Nobody",
                  quest->completed?ctime(&quest->completed):"Never",
                  quest->glory,
                  flag_string(quest->flags, quest_flags));

        if (!quest->first_task)
        {
            send_to_char(" None\n\r", ch);
            return;
        }

        for (task = quest->first_task; task; task = task->next)
            ch_printf(ch,
                      " %2d) %-8.8s %-6d %-6d %3dG   Completed: %s, %s\n\r"
                      "     %s\n\r",
                      ++icnt,
                      quest_task_type_names[task->type],
                      task->vnum1,
                      task->vnum2,
                      task->glory,
                      task->completed_by?task->completed_by:"Nobody",
                      task->completed?sec_to_hms_short(current_time-task->completed):"Never",
                      task->description?task->description:"(none)"
                     );

        return;
    }

    if (!str_cmp(argument, "delete"))
    {
        free_quest(quest);
        send_to_char("Ok.\n\r", ch);
        save_quests();
        return;
    }

    if (!str_cmp(argument, "reset"))
    {
        reset_quest(quest);
        send_to_char("Ok.\n\r", ch);
        save_quests();
        return;
    }

    argument = one_argument(argument, arg);

    if (!argument || !*argument)
    {
        do_quest(ch, NULL);
        return;
    }

    if (!str_cmp(arg, "name"))
    {
        if (quest->name)
            STRFREE(quest->name);
        quest->name = STRALLOC(argument);
        save_quests();
        send_to_char("Ok.\n\r", ch);
        return;
    }

    if (!str_cmp(arg, "owner"))
    {
        if (quest->owner)
            STRFREE(quest->owner);
        quest->owner = STRALLOC(argument);
        save_quests();
        send_to_char("Ok.\n\r", ch);
        return;
    }

    if (!str_cmp(arg, "desc"))
    {
        if (quest->description)
            DISPOSE(quest->description);
        quest->description = str_dup(argument);
        save_quests();
        send_to_char("Ok.\n\r", ch);
        return;
    }

    if (!str_cmp(arg, "flags"))
    {
        int value;

        if ( !argument || argument[0] == '\0' )
        {
            send_to_char("Available quest flags: ", ch);
            for ( value = 0; value < 32; value++ )
                ch_printf(ch, "%s ", quest_flags[value]);
            send_to_char("\n\r", ch);
        }

        while ( argument[0] != '\0' )
        {
            argument = one_argument( argument, arg );
            value = get_questflag( arg );
            if ( value < 0 || value > 31 )
                ch_printf( ch, "Unknown quest flag: %s\n\r", arg );
            else
                TOGGLE_BIT(quest->flags, 1 << value);
        }

        quests_active=0;
        for (quest = first_quest; quest; quest = quest->next)
            if (IS_QUEST_FLAG(quest, QUEST_ACTIVE) &&
                quest->first_task)
                quests_active++;

        save_quests();
        send_to_char("Ok.\n\r", ch);
        return;
    }

    if (!str_cmp(arg, "glory"))
    {
        quest->glory = atoi(argument);
        save_quests();
        send_to_char("Ok.\n\r", ch);
        return;
    }

    if (!str_cmp(arg, "creator") && get_trust(ch) >= LEVEL_IMPLEMENTOR)
    {
        if (quest->creator)
            STRFREE(quest->creator);
        quest->creator = STRALLOC(argument);
        save_quests();
        send_to_char("Ok.\n\r", ch);
        return;
    }

    if (!str_cmp(arg, "task"))
    {
        QUEST_TASK *temp_task;
        int tnum, count=0;

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

        if (!argument || !*argument)
        {
            do_quest(ch, NULL);
            return;
        }
        argument = one_argument(argument, arg);

        for (task = quest->first_task; task; task = task->next)
        {
            count++;
            if (count == tnum)
                break;
        }
        if (!task || count != tnum)
        {
            if (!str_cmp(arg, "delete"))
            {
                send_to_char("Task not found.\n\r", ch);
                return;
            }
            send_to_char("Creating new task...\n\r", ch);
            CREATE(task, QUEST_TASK, 1);
            LINK(task, quest->first_task, quest->last_task, next, prev);
        }
        else if (!str_cmp(arg, "delete"))
        {
            free_quest_task(quest,task);
            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }
        else if (!str_cmp(arg, "insert"))
        {
            count = 0;
            send_to_char("Inserting new task...\n\r", ch);
            CREATE(task, QUEST_TASK, 1);
            for (temp_task = quest->first_task; temp_task; temp_task = temp_task->next)
            {
                count++;
                if (count >= tnum)
                {
                    INSERT(task, temp_task, quest->first_task, next, prev);
                    break;
                }
            }
            if (quest->first_task != task && !task->next && !task->prev)
                LINK(task, quest->first_task, quest->last_task, next, prev);

            argument = one_argument(argument, arg);
        }

        /*
         * be careful here, if any of the below task commands need to use
         * quest it'll be goofed up after this
         */
        quests_active=0;
        for (quest = first_quest; quest; quest = quest->next)
            if (IS_QUEST_FLAG(quest, QUEST_ACTIVE) &&
                quest->first_task)
                quests_active++;

        if (!str_cmp(arg, "desc"))
        {
            if (task->description)
                DISPOSE(task->description);
            task->description = str_dup(argument);
            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }

        if (!str_cmp(arg, "type"))
        {
            task->type = URANGE(0, get_questtasktype(argument), MAX_TASK_TYPE-1);
            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }

        if (!str_cmp(arg, "vnum") || !str_cmp(arg, "vnum1"))
        {
            task->vnum1 = atoi(argument);
            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }

        if (!str_cmp(arg, "vnum2"))
        {
            task->vnum2 = atoi(argument);
            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }

        if (!str_cmp(arg, "flags"))
        {
            int value;

            if ( !argument || argument[0] == '\0' )
            {
                send_to_char("Available task flags: ", ch);
                for ( value = 0; value < 32; value++ )
                    ch_printf(ch, "%s ", task_flags[value]);
                send_to_char("\n\r", ch);
            }

            while ( argument[0] != '\0' )
            {
                argument = one_argument( argument, arg );
                value = get_taskflag( arg );
                if ( value < 0 || value > 31 )
                    ch_printf( ch, "Unknown task flag: %s\n\r", arg );
                else
                    TOGGLE_BIT(task->flags, 1 << value);
            }

            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }


        if (!str_cmp(arg, "glory"))
        {
            task->glory = atoi(argument);
            save_quests();
            send_to_char("Ok.\n\r", ch);
            return;
        }

        if (arg[0] == '\0')
        {
            send_to_char("Default values set for task.\n\r", ch);
            return;
        }
    }

    do_quest(ch, NULL);
}

void complete_task(CHAR_DATA *ch, QUEST_DATA *quest, QUEST_TASK *task)
{
    QUEST_TASK *next_task;

    task->completed    = current_time;
    task->completed_by = STRALLOC(GET_NAME(ch));

    for (next_task = quest->first_task; next_task; next_task = next_task->next)
        if (!next_task->completed && !IS_TASK_FLAG(next_task, TASK_OPTIONAL))
            break;

    set_char_color(AT_GREEN, ch);
    if (next_task && !next_task->completed && !IS_TASK_FLAG(next_task, TASK_OPTIONAL))
    {
        send_to_char("You have completed part of a quest!\n\r", ch);
        if (task->glory != 0 && !IS_NPC(ch))
        {
            ch->pcdata->quest_curr  += task->glory;
            ch->pcdata->quest_accum += task->glory;
            ch_printf(ch, "You have earned %d glory!\n\r", task->glory);
        }

        if (IS_QUEST_FLAG(quest, QUEST_INORDER))
        {
            set_char_color(AT_YELLOW, ch);
            ch_printf(ch, "Your next task:\n\r%s\n\r",
                      next_task->description);
        }

        sprintf(log_buf, "%s completed a task from quest '%s'", GET_NAME(ch), quest->name);
        log_string_plus(log_buf, LOG_MONITOR, LEVEL_LOG_CSET, SEV_NOTICE);
    }
    else
    {
        quest->completed    = current_time;
        quest->completed_by = STRALLOC(GET_NAME(ch));
        if (IS_QUEST_FLAG(quest, QUEST_AUTORESET))
        {
            reset_quest(quest);
        }
        else
        {
            REMOVE_QUEST_FLAG(quest, QUEST_ACTIVE);
            quests_active--;
        }

        /* only allow players to do this quest once */
        if (IS_QUEST_FLAG(quest, QUEST_ONLYONCE))
            quest_onlyonce_add(ch, quest);
 
        send_to_char("You have completed a quest!\n\r", ch);
        if ((task->glory + quest->glory) != 0 && !IS_NPC(ch))
        {
            ch->pcdata->quest_curr  += (task->glory + quest->glory);
            ch->pcdata->quest_accum += (task->glory + quest->glory);
            ch_printf(ch, "You have earned %d glory!\n\r", task->glory + quest->glory);
        }

        sprintf(log_buf, "%s completed quest '%s'", GET_NAME(ch), quest->name);
        log_string_plus(log_buf, LOG_MONITOR, LEVEL_LOG_CSET, SEV_NOTICE);

        sprintf(log_buf, "%d active quests", quests_active);
        log_string_plus(log_buf, LOG_MONITOR, LEVEL_LOG_CSET, SEV_NOTICE);
    }

    save_quests();
}

static bool quest_trigger_vnumfind(CHAR_DATA *ch, int type, int vnum1, int vnum2)
{
    QUEST_DATA *quest;
    QUEST_TASK *task;
    bool completed = FALSE;

    if (!quests_active)
        return FALSE;

    for (quest = first_quest; quest; quest = quest->next)
    {
        if (!IS_QUEST_FLAG(quest, QUEST_ACTIVE))
            continue;

        if (!IS_QUEST_FLAG(quest, QUEST_ALLOWNPC) &&
            IS_NPC(ch))
            continue;

        if (str_cmp(quest->owner, "Anybody") &&
            str_cmp(quest->owner, GET_NAME(ch)))
            continue;

        if (quest_onlyonce_check(ch, quest))
            continue;

        for (task = quest->first_task; task; task = task->next)
        {
            if (task->completed)
                continue;

            if (task->type == type &&
                task->vnum1 == vnum1 &&
                task->vnum2 == vnum2)
            {
                complete_task(ch, quest, task);
                completed = TRUE;
                continue;
            }

            if (IS_QUEST_FLAG(quest, QUEST_INORDER) &&
                !IS_TASK_FLAG(task, TASK_OPTIONAL))
                break;
        }
    }

    return completed;
}

bool quest_trigger_objfind(CHAR_DATA *ch, OBJ_DATA *obj)
{
    if (!obj->pIndexData)
        return FALSE;

    if (quest_trigger_vnumfind(ch, QUEST_OBJ_FIND, obj->vnum, 0))
    {
        oprog_quest_trigger(ch, obj);
        return TRUE;
    }
    return FALSE;
}

bool quest_trigger_mobfind(CHAR_DATA *ch, CHAR_DATA *mob)
{
    if (!mob->pIndexData)
        return FALSE;
    if (quest_trigger_vnumfind(ch, QUEST_MOB_FIND, mob->vnum, 0))
    {
        mprog_quest_trigger(mob, ch);
        return TRUE;
    }
    return FALSE;
}

bool quest_trigger_roomfind(CHAR_DATA *ch, ROOM_INDEX_DATA *room)
{
    if (quest_trigger_vnumfind(ch, QUEST_ROOM_FIND, room->vnum, 0))
    {
        rprog_quest_trigger(ch);
        return TRUE;
    }
    return FALSE;
}

bool quest_trigger_mobkill(CHAR_DATA *ch, CHAR_DATA *victim)
{
    if (!victim->pIndexData)
        return FALSE;
    if (quest_trigger_vnumfind(ch, QUEST_MOB_KILL, victim->vnum, 0))
    {
        mprog_quest_trigger(victim, ch);
        return TRUE;
    }
    return FALSE;
}

bool quest_trigger_objgive(CHAR_DATA *ch, OBJ_DATA *obj, CHAR_DATA *victim)
{
    if (!obj->pIndexData || !victim->pIndexData)
        return FALSE;
    if (quest_trigger_vnumfind(ch, QUEST_OBJ_GIVE, obj->vnum, victim->vnum))
    {
        oprog_quest_trigger(ch, obj);
        if (char_died(ch))
            return TRUE;
        mprog_quest_trigger(victim, ch);
        return TRUE;
    }
    return FALSE;
}

bool quest_trigger_objplace(CHAR_DATA *ch, OBJ_DATA *obj, ROOM_INDEX_DATA *room)
{
    if (!obj->pIndexData)
        return FALSE;
    if (quest_trigger_vnumfind(ch, QUEST_OBJ_PLACE, obj->vnum, room->vnum))
    {
        oprog_quest_trigger(ch, obj);
        if (char_died(ch))
            return TRUE;
        rprog_quest_trigger(ch);
        return TRUE;
    }
    return FALSE;
}