quest/
//*****************************************************************************
//
// quest.c
//
// This is a framework for setting up player quests. Quests are stored in the
// world database, like rooms, mobiles, objects, etc. Quests can have multiple
// stages. Each stage of a quest can also have multiple and varied objectives.
// Some common ones would be kill objectives (e.g. kill 20 orcs) and give 
// objectives (e.g. give 2 apples to your grandma). Quests must be started 
// before their progress can be tracked. When the objectives of a quest stage
// are completed, the stage is automatically completed. If the stage is the
// last stage of the quest, the entire quest is completed.
//
//*****************************************************************************
#include "../mud.h"
#include "../utils.h"
#include "../hooks.h"
#include "../storage.h"
#include "../world.h"
#include "../zone.h"
#include "../room.h"
#include "../auxiliary.h"
#include "../character.h"
#include "../object.h"
#include "qedit.h"
#include "quest.h"



//*****************************************************************************
// mandatory modules
//*****************************************************************************
#include "../scripts/scripts.h"
#include "../scripts/pychar.h"



//*****************************************************************************
// auxiliary data
//*****************************************************************************

//
// holds info for char's progress on one quest
typedef struct {
  int       stage; // what stage of the quest are we currently on?
  bool     failed; // have we failed the quest?
  HASHTABLE *vars; // tracks vals for things we've accomplished
} QUEST_PROGRESS;

QUEST_PROGRESS *newQuestProgress(void) {
  QUEST_PROGRESS *prog = malloc(sizeof(QUEST_PROGRESS));
  prog->stage          = 0;
  prog->failed         = FALSE;
  prog->vars           = newHashtable();
  return prog;
}

void deleteQuestProgress(QUEST_PROGRESS *prog) {
  if(hashSize(prog->vars) > 0) {
    HASH_ITERATOR *var_i = newHashIterator(prog->vars);
    const char      *key = NULL;
    char            *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      free(val);
    } deleteHashIterator(var_i);
  }
  deleteHashtable(prog->vars);
  free(prog);
}

void questProgressCopyTo(QUEST_PROGRESS *from, QUEST_PROGRESS *to) {
  // empty our current var table, if neccessary
  if(hashSize(to->vars) > 0) {
    HASH_ITERATOR *var_i = newHashIterator(to->vars);
    const char      *key = NULL;
    char            *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      free(hashRemove(to->vars, key));
    } deleteHashIterator(var_i);
  }

  // copy over our current contents, if neccessary
  if(hashSize(from->vars) > 0) {
    HASH_ITERATOR *var_i = newHashIterator(from->vars);
    const char      *key = NULL;
    char            *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      hashPut(to->vars, key, strdup(val));
    } deleteHashIterator(var_i);
  }

  to->stage  = from->stage;
  to->failed = from->failed;
}

QUEST_PROGRESS *questProgressCopy(QUEST_PROGRESS *prog) {
  QUEST_PROGRESS *newprog = newQuestProgress();
  questProgressCopyTo(prog, newprog);
  return newprog;
}

STORAGE_SET *questProgressStore(QUEST_PROGRESS *prog) {
  STORAGE_SET *set = new_storage_set();
  store_int(set,  "stage",  prog->stage);
  store_bool(set, "failed", prog->failed);
  if(hashSize(prog->vars) > 0) {
    STORAGE_SET_LIST *list = new_storage_list();
    HASH_ITERATOR   *var_i = newHashIterator(prog->vars);
    const char        *key = NULL;
    char              *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      STORAGE_SET *one_var = new_storage_set();
      store_string(one_var, "key", key);
      store_string(one_var, "val", val);
      storage_list_put(list, one_var);
    } deleteHashIterator(var_i);
    store_list(set, "vars", list);
  }
  return set;
}

QUEST_PROGRESS *questProgressRead(STORAGE_SET *set) {
  QUEST_PROGRESS   *prog = newQuestProgress();
  STORAGE_SET_LIST *vars = read_list(set, "vars");
  STORAGE_SET   *one_var = NULL;
  prog->stage            = read_int(set, "stage");
  prog->failed           = read_bool(set, "failed");

  // read in all of our values
  while( (one_var = storage_list_next(vars)) != NULL) {
    hashPut(prog->vars, read_string(one_var, "key"), 
	    strdup(read_string(one_var, "val")));
  }

  return prog;
}

const char *questProgressGetVar(QUEST_PROGRESS *prog, const char *var) {
  char *val = hashGet(prog->vars, var);
  return (val ? val : "");
}

int questProgressGetVarInt(QUEST_PROGRESS *prog, const char *var) {
  return atoi(questProgressGetVar(prog, var));
}

void questProgressSetVar(QUEST_PROGRESS *prog, const char *var,const char *val){
  if(hashIn(prog->vars, var))
    free(hashRemove(prog->vars, var));
  hashPut(prog->vars, var, strdupsafe(val));
}

void questProgressSetVarInt(QUEST_PROGRESS *prog, const char *var, int val) {
  char sval[20];
  sprintf(sval, "%d", val);
  questProgressSetVar(prog, var, sval);
}



//
// holds info for char's progress on all quests
typedef struct {
  HASHTABLE  *quests; // the quests we're on, and data for them
  LIST    *completed; // names of quests that we've completed
} QUEST_AUX_DATA;

QUEST_AUX_DATA *newQuestAuxData(void) {
  QUEST_AUX_DATA *data = malloc(sizeof(QUEST_AUX_DATA));
  data->quests         = newHashtable();
  data->completed      = newList();
  return data;
}

void deleteQuestAuxData(QUEST_AUX_DATA *data) {
  if(hashSize(data->quests) > 0) {
    HASH_ITERATOR *quest_i = newHashIterator(data->quests);
    const char        *key = NULL;
    QUEST_PROGRESS  *quest = NULL;
    ITERATE_HASH(key, quest, quest_i) {
      deleteQuestProgress(quest);
    } deleteHashIterator(quest_i);
  }
  deleteHashtable(data->quests);
  deleteListWith(data->completed, free);
  free(data);
}

void questAuxDataCopyTo(QUEST_AUX_DATA *from, QUEST_AUX_DATA *to) {
  // empty our quest table, if neccessary
  if(hashSize(to->quests) > 0) {
    HASH_ITERATOR *quest_i = newHashIterator(to->quests);
    const char        *key = NULL;
    QUEST_PROGRESS  *quest = NULL;
    ITERATE_HASH(key, quest, quest_i) {
      deleteQuestProgress(hashRemove(to->quests, key));
    } deleteHashIterator(quest_i);
  }

  // copy over our current contents, if neccessary
  if(hashSize(from->quests) > 0) {
    HASH_ITERATOR *quest_i = newHashIterator(from->quests);
    const char        *key = NULL;
    QUEST_PROGRESS  *quest = NULL;
    ITERATE_HASH(key, quest, quest_i) {
      hashPut(to->quests, key, questProgressCopy(quest));
    } deleteHashIterator(quest_i);
  }

  deleteListWith(to->completed, free);
  to->completed = listCopyWith(from->completed, strdup);
}

QUEST_AUX_DATA *questAuxDataCopy(QUEST_AUX_DATA *data) {
  QUEST_AUX_DATA *newdata = newQuestAuxData();
  questAuxDataCopyTo(data, newdata);
  return newdata;
}

STORAGE_SET *store_quest_completed(const char *quest) {
  STORAGE_SET *set = new_storage_set();
  store_string(set, "quest", quest);
  return set;
}

STORAGE_SET *questAuxDataStore(QUEST_AUX_DATA *data) {
  STORAGE_SET *set = new_storage_set();

  // if we have quests on the go, store them
  if(hashSize(data->quests) > 0) {
    STORAGE_SET_LIST *quests = new_storage_list(); 
    HASH_ITERATOR   *quest_i = newHashIterator(data->quests);
    const char          *key = NULL;
    QUEST_PROGRESS    *quest = NULL;
    ITERATE_HASH(key, quest, quest_i) {
      STORAGE_SET *one_quest = new_storage_set();
      store_string(one_quest, "name", key);
      store_set(one_quest,    "data", questProgressStore(quest));
      storage_list_put(quests, one_quest);
    } deleteHashIterator(quest_i);
    store_list(set, "quests", quests);
  }

  store_list(set, "completed", 
	     gen_store_list(data->completed, store_quest_completed));
  return set;
}

char *read_quest_completed(STORAGE_SET *set) {
  return strdup(read_string(set, "quest"));
}

QUEST_AUX_DATA *questAuxDataRead(STORAGE_SET *set) {
  QUEST_AUX_DATA     *data = newQuestAuxData();
  STORAGE_SET_LIST *quests = read_list(set, "quests");
  STORAGE_SET   *one_quest = NULL;

  while( (one_quest = storage_list_next(quests)) != NULL) {
    hashPut(data->quests, read_string(one_quest, "name"),
	    questProgressRead(read_set(one_quest, "data")));
  }

  deleteList(data->completed);
  data->completed = gen_read_list(read_list(set, "completed"), 
				  read_quest_completed);
  return data;
}



//*****************************************************************************
// local datastructures, variables, defines
//*****************************************************************************

// a table of our different objective types, and functions we need for them
HASHTABLE *ob_type_table = NULL;

typedef struct {
  bool (* ob_ok)(QUEST_PROGRESS *, QUEST_OBJECTIVE *);
  void (* append_ob_status)(BUFFER *, QUEST_PROGRESS *, QUEST_OBJECTIVE *);
} OBJECTIVE_FUNCS;

struct quest_data {
  char        *key;
  char       *name;
  BUFFER     *desc;
  LIST     *stages;
};

struct quest_stage {
  char        *name;
  LIST  *objectives;
  BUFFER *endscript;
  QUEST_DATA *quest;
};

struct quest_objective {
  char            *type;
  char            *desc;
  HASHTABLE       *vars;
  QUEST_STAGE    *stage;
};

//
// create a new struct for holding our objective functions
OBJECTIVE_FUNCS *newObjectiveFuncs(
    bool            (* ob_ok)(QUEST_PROGRESS *, QUEST_OBJECTIVE *),
    void (* append_ob_status)(BUFFER *, QUEST_PROGRESS *, QUEST_OBJECTIVE *)) {
  OBJECTIVE_FUNCS *funcs = malloc(sizeof(OBJECTIVE_FUNCS));
  funcs->ob_ok = ob_ok;
  funcs->append_ob_status = append_ob_status;
  return funcs;
}



//*****************************************************************************
// implementation of quest.h
//*****************************************************************************
QUEST_DATA *newQuest(void) {
  QUEST_DATA *quest = malloc(sizeof(QUEST_DATA));
  quest->key        = strdup("");
  quest->name       = strdup("");
  quest->desc       = newBuffer(1);
  quest->stages     = newList();
  return quest;
}

void deleteQuest(QUEST_DATA *quest) {
  if(quest->key)    free(quest->key);
  if(quest->name)   free(quest->name);
  if(quest->desc)   deleteBuffer(quest->desc);
  if(quest->stages) deleteListWith(quest->stages, deleteQuestStage);
  free(quest);
}

void questCopyTo(QUEST_DATA *from, QUEST_DATA *to) {
  if(to->key)  free(to->key);
  if(to->name) free(to->name);  
  deleteListWith(to->stages, deleteQuestStage);
  bufferClear(to->desc);
  to->key  = strdupsafe(from->key);
  to->name = strdupsafe(from->name);
  bufferCat(to->desc, bufferString(from->desc));
  to->stages = listCopyWith(from->stages, questStageCopy);
  LIST_ITERATOR *stage_i = newListIterator(to->stages);
  QUEST_STAGE     *stage = NULL;
  ITERATE_LIST(stage, stage_i) {
    stage->quest = to;
  } deleteListIterator(stage_i);
}

QUEST_DATA *questCopy(QUEST_DATA *quest) {
  QUEST_DATA *newquest = newQuest();
  questCopyTo(quest, newquest);
  return newquest;
}

STORAGE_SET *questStore(QUEST_DATA *quest) {
  STORAGE_SET *set = new_storage_set();
  store_string(set, "name", questGetName(quest));
  store_string(set, "desc", questGetDesc(quest));
  store_list(set, "stages", gen_store_list(quest->stages, questStageStore));
  return set;
}

QUEST_DATA *questRead(STORAGE_SET *set) {
  QUEST_DATA *quest = newQuest();
  questSetName(quest, read_string(set, "name"));
  questSetDesc(quest, read_string(set, "desc"));
  LIST *stages = gen_read_list(read_list(set, "stages"), questStageRead);
  LIST_ITERATOR *stage_i = newListIterator(stages);
  QUEST_STAGE     *stage = NULL;
  ITERATE_LIST(stage, stage_i) {
    questAddStage(quest, stage);
  } deleteListIterator(stage_i);
  deleteList(stages);
  return quest;
}

void questSetKey(QUEST_DATA *quest, const char *key) {
  if(quest->key) free(quest->key);
  quest->key = strdupsafe(key);
}

void questSetName(QUEST_DATA *quest, const char *name) {
  if(quest->name) free(quest->name);
  quest->name = strdupsafe(name);
}

void questSetDesc(QUEST_DATA *quest, const char *desc) {
  bufferClear(quest->desc);
  bufferCat(quest->desc, desc);
}

const char *questGetKey(QUEST_DATA *quest) {
  return quest->key;
}

const char *questGetName(QUEST_DATA *quest) {
  return quest->name;
}

const char *questGetDesc(QUEST_DATA *quest) {
  return bufferString(quest->desc);
}

BUFFER *questGetDescBuf(QUEST_DATA *quest) {
  return quest->desc;
}

void questAddStage(QUEST_DATA *quest, QUEST_STAGE *stage) {
  listQueue(quest->stages, stage);
  stage->quest = quest;
}

void questRemoveStage(QUEST_DATA *quest, QUEST_STAGE *stage) {
  if(listRemove(quest->stages, stage))
    stage->quest = NULL;
}

QUEST_STAGE *questRemoveStageNum(QUEST_DATA *quest, int num) {
  QUEST_STAGE *stage = listGet(quest->stages, num);
  if(stage != NULL)
    questRemoveStage(quest, stage);
  return stage;
}

LIST *questGetStages(QUEST_DATA *quest) {
  return quest->stages;
}

QUEST_STAGE *newQuestStage(void) {
  QUEST_STAGE *stage = malloc(sizeof(QUEST_STAGE));
  stage->name        = strdupsafe("");
  stage->objectives  = newList();
  stage->endscript   = newBuffer(1);
  stage->quest       = NULL;
  return stage;
}

void deleteQuestStage(QUEST_STAGE *stage) {
  if(stage->name)       free(stage->name);
  if(stage->objectives) deleteListWith(stage->objectives, deleteQuestObjective);
  if(stage->endscript)  deleteBuffer(stage->endscript);
  free(stage);
}

void questStageCopyTo(QUEST_STAGE *from, QUEST_STAGE *to) {
  questStageSetName(to, questStageGetName(from));
  questStageSetEndScript(to, questStageGetEndScript(from));
  deleteListWith(to->objectives, deleteQuestObjective);
  to->objectives = listCopyWith(from->objectives, questObjectiveCopy);
  LIST_ITERATOR *ob_i = newListIterator(to->objectives);
  QUEST_OBJECTIVE *ob = NULL;
  ITERATE_LIST(ob, ob_i) {
    ob->stage = to;
  } deleteListIterator(ob_i);
}

QUEST_STAGE *questStageCopy(QUEST_STAGE *stage) {
  QUEST_STAGE *newstage = newQuestStage();
  questStageCopyTo(stage, newstage);
  return newstage;
}

STORAGE_SET *questStageStore(QUEST_STAGE *stage) {
  STORAGE_SET *set = new_storage_set();
  store_string(set, "name",      questStageGetName(stage));
  store_string(set, "endscript", questStageGetEndScript(stage));
  store_list(set, "objectives", gen_store_list(stage->objectives, 
					       questObjectiveStore));
  return set;
}

QUEST_STAGE *questStageRead(STORAGE_SET *set) {
  QUEST_STAGE *stage = newQuestStage();
  questStageSetName(stage, read_string(set, "name"));
  questStageSetEndScript(stage, read_string(set, "endscript"));
  LIST *obs = gen_read_list(read_list(set, "objectives"), questObjectiveRead);
  LIST_ITERATOR *ob_i = newListIterator(obs);
  QUEST_OBJECTIVE *ob = NULL;
  ITERATE_LIST(ob, ob_i) {
    questStageAddObjective(stage, ob);
  } deleteListIterator(ob_i);
  deleteList(obs);
  return stage;
}

void questStageSetName(QUEST_STAGE *stage, const char *name) {
  if(stage->name) free(stage->name);
  stage->name = strdupsafe(name);
}

void questStageSetEndScript(QUEST_STAGE *stage, const char *script) {
  bufferClear(stage->endscript);
  bufferCat(stage->endscript, script);
}

const char *questStageGetName(QUEST_STAGE *stage) {
  return stage->name;
}

const char *questStageGetEndScript(QUEST_STAGE *stage) {
  return bufferString(stage->endscript);
}

BUFFER *questStageGetEndScriptBuf(QUEST_STAGE *stage) {
  return stage->endscript;
}

void questStageAddObjective(QUEST_STAGE *stage, QUEST_OBJECTIVE *ob) {
  listQueue(stage->objectives, ob);
  ob->stage = stage;
}

LIST *questStageGetObjectives(QUEST_STAGE *stage) {
  return stage->objectives;
}

void questStageRemoveObjective(QUEST_STAGE *stage, QUEST_OBJECTIVE *ob) {
  if(listRemove(stage->objectives, ob))
    ob->stage = NULL;
}

QUEST_OBJECTIVE *questStageRemoveObjectiveNum(QUEST_STAGE *stage, int num) {
  QUEST_OBJECTIVE *ob = listGet(stage->objectives, num);
  if(ob != NULL)
    questStageRemoveObjective(stage, ob);
  return ob;
}

QUEST_DATA *questStageGetQuest(QUEST_STAGE *stage) {
  return stage->quest;
}

QUEST_OBJECTIVE *newQuestObjective(void) {
  QUEST_OBJECTIVE *ob = malloc(sizeof(QUEST_OBJECTIVE));
  ob->vars            = newHashtable();
  ob->type            = strdup("");
  ob->desc            = strdup("");
  ob->stage           = NULL;
  return ob;
}

void deleteQuestObjective(QUEST_OBJECTIVE *ob) {
  if(ob->type)  free(ob->type);
  if(ob->desc)  free(ob->desc);
  if(ob->vars) {
    if(hashSize(ob->vars) > 0) {
      HASH_ITERATOR *hash_i = newHashIterator(ob->vars);
      const char       *key = NULL;
      char             *val = NULL;
      ITERATE_HASH(key, val, hash_i) {
	free(val);
      } deleteHashIterator(hash_i);
    }
    deleteHashtable(ob->vars);
  }
  free(ob);
}

void questObjectiveCopyTo(QUEST_OBJECTIVE *from, QUEST_OBJECTIVE *to) {
  if(to->type) free(to->type);
  if(to->desc) free(to->desc);
  // if we have contents at the moment, remove them
  if(hashSize(to->vars) > 0) {
    HASH_ITERATOR *var_i = newHashIterator(to->vars);
    const char      *key = NULL;
    char            *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      hashRemove(to->vars, key);
      free(val);
    } deleteHashIterator(var_i);
  }

  // if from has contents, copy them
  if(hashSize(from->vars) > 0) {
    HASH_ITERATOR *var_i = newHashIterator(from->vars);
    const char      *key = NULL;
    char            *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      hashPut(to->vars, key, strdup(val));
    } deleteHashIterator(var_i);
  }
  to->type  = strdupsafe(from->type);
  to->desc  = strdupsafe(from->desc);
}

QUEST_OBJECTIVE *questObjectiveCopy(QUEST_OBJECTIVE *ob) {
  QUEST_OBJECTIVE *newob = newQuestObjective();
  questObjectiveCopyTo(ob, newob);
  return newob;
}

STORAGE_SET *questObjectiveStore(QUEST_OBJECTIVE *ob) {
  STORAGE_SET *set = new_storage_set();
  store_string(set, "type", questObjectiveGetType(ob));
  store_string(set, "desc", questObjectiveGetDesc(ob));
  if(hashSize(ob->vars) > 0) {
    STORAGE_SET_LIST *list = new_storage_list();
    HASH_ITERATOR   *var_i = newHashIterator(ob->vars);
    const char        *key = NULL;
    char              *val = NULL;
    ITERATE_HASH(key, val, var_i) {
      STORAGE_SET *one_var = new_storage_set();
      store_string(one_var, "key", key);
      store_string(one_var, "val", val);
      storage_list_put(list, one_var);
    } deleteHashIterator(var_i);
    store_list(set, "vars", list);
  }
  return set;
}

QUEST_OBJECTIVE *questObjectiveRead(STORAGE_SET *set) {
  QUEST_OBJECTIVE    *ob = newQuestObjective();
  questObjectiveSetType(ob, read_string(set, "type"));
  questObjectiveSetDesc(ob, read_string(set, "desc"));
  STORAGE_SET_LIST *vars = read_list(set, "vars");
  STORAGE_SET   *one_var = NULL;
  while( (one_var = storage_list_next(vars)) != NULL) {
    hashPut(ob->vars, read_string(one_var, "key"),
	    strdup(read_string(one_var, "val")));
  }
  return ob;
}

const char *questObjectiveGetType(QUEST_OBJECTIVE *ob) {
  return ob->type;
}

const char *questObjectiveGetDesc(QUEST_OBJECTIVE *ob) {
  return ob->desc;
}

HASHTABLE *questObjectiveGetVars(QUEST_OBJECTIVE *ob) {
  return ob->vars;
}

const char *questObjectiveGetVar(QUEST_OBJECTIVE *ob, const char *var) {
  const char *val = hashGet(ob->vars, var);
  return (val ? val : "");
}

int questObjectiveGetVarInt(QUEST_OBJECTIVE *ob, const char *var) {
  return atoi(questObjectiveGetVar(ob, var));
}

void questObjectiveSetType(QUEST_OBJECTIVE *ob, const char *type) {
  if(ob->type) free(ob->type);
  ob->type = strdupsafe(type);
}

void questObjectiveSetDesc(QUEST_OBJECTIVE *ob, const char *desc) {
  if(ob->desc) free(ob->desc);
  ob->desc = strdupsafe(desc);
}

void questObjectiveSetVar(QUEST_OBJECTIVE *ob, const char *var,const char *val){
  questObjectiveDeleteVar(ob, var);
  hashPut(ob->vars, var, strdupsafe(val));
}

void questObjectiveSetVarInt(QUEST_OBJECTIVE *ob, const char *var, int val) {
  char buf[20];
  sprintf(buf, "%d", val);
  hashPut(ob->vars, var, strdup(buf));
}

void questObjectiveDeleteVar(QUEST_OBJECTIVE *ob, const char *var) {
  if(hashIn(ob->vars, var))
    free(hashRemove(ob->vars, var));
}

void questObjectiveClearVars(QUEST_OBJECTIVE *ob) {
  LIST           *vars = hashCollect(ob->vars);
  LIST_ITERATOR *var_i = newListIterator(vars);
  char            *var = NULL;
  ITERATE_LIST(var, var_i) {
    free(hashRemove(ob->vars, var));
  } deleteListIterator(var_i);
  deleteListWith(vars, free);
}

QUEST_STAGE *questObjectiveGetStage(QUEST_OBJECTIVE *ob) {
  return ob->stage;
}

void charStartQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  charCancelQuest(ch, quest);
  hashPut(data->quests, questGetKey(quest), newQuestProgress());
  send_to_char(ch, "{pYou gain the quest, %s{n\r\n", questGetName(quest));
}

void charFailQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  QUEST_PROGRESS *prog = hashGet(data->quests, questGetKey(quest));
  if(prog != NULL)
    prog->failed = TRUE;
}

void charCancelQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  char      *completed = listRemoveWith(data->completed, quest->key,strcasecmp);
  QUEST_PROGRESS *prog = hashRemove(data->quests, quest->key);
  if(completed) free(completed);
  if(prog)      deleteQuestProgress(prog);
}

void charAdvanceQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  QUEST_PROGRESS *prog = hashRemove(data->quests, questGetKey(quest));
  if(prog != NULL) {
    // run the advancement script
    QUEST_STAGE *stage = listGet(questGetStages(quest), prog->stage);
    if(stage != NULL && *questStageGetEndScript(stage)) {
      PyObject *dict = restricted_script_dict();
      PyObject *pych = charGetPyForm(ch);
      PyDict_SetItemString(dict, "ch", pych);

      // run the script
      run_script(dict, questStageGetEndScript(stage), 
		 get_key_locale(questGetKey(quest)));

      // garbage collection
      Py_DECREF(dict);
      Py_DECREF(pych);
    }

    // are we on the last stage of the quest?
    if(listSize(questGetStages(quest)) == prog->stage+1) {
      listQueue(data->completed, strdupsafe(questGetKey(quest)));
      send_to_char(ch, "{pYou complete the quest, %s{n\r\n", 
		   questGetName(quest));
      hookRun("complete_quest", hookBuildInfo("ch str", ch,questGetKey(quest)));
    }
    else {
      QUEST_PROGRESS *newprog = newQuestProgress();
      newprog->stage = prog->stage + 1;
      hashPut(data->quests, questGetKey(quest), newprog);
      send_to_char(ch, "{pYou advance on the quest, %s{n\r\n", 
		   questGetName(quest));
      hookRun("advance_quest", hookBuildInfo("ch str", ch, questGetKey(quest)));
    }

    // free up our old progress data
    deleteQuestProgress(prog);
  }
}

bool charCompletedQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  return (listGetWith(data->completed, questGetKey(quest), strcasecmp) != NULL);
}

bool charOnQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  return hashIn(data->quests, questGetKey(quest));
}

int charGetQuestStage(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  QUEST_PROGRESS *prog = hashGet(data->quests, questGetKey(quest));
  return (prog ? prog->stage : -1);
}

bool charFailedQuest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  QUEST_PROGRESS *prog = hashGet(data->quests, questGetKey(quest));
  return (prog ? prog->failed : FALSE);
}



//*****************************************************************************
// local functions
//*****************************************************************************

//
// check to see if we've completed a kill objective
bool kill_objective_ok(QUEST_PROGRESS *prog, QUEST_OBJECTIVE *ob) {
  char var[SMALL_BUFFER];
  QUEST_DATA *quest = questStageGetQuest(questObjectiveGetStage(ob));
  const char *enemy = get_fullkey_relative(questObjectiveGetVar(ob, "enemy"),
					   get_key_locale(questGetKey(quest)));
  sprintf(var, "kill_%s", enemy);
  return (questProgressGetVarInt(prog, var) >= 
	  questObjectiveGetVarInt(ob, "times"));
}

//
// check to see if we've completed a greet objective
bool greet_objective_ok(QUEST_PROGRESS *prog, QUEST_OBJECTIVE *ob) {
  char var[SMALL_BUFFER];
  QUEST_DATA *quest = questStageGetQuest(questObjectiveGetStage(ob));
  const char   *tgt = get_fullkey_relative(questObjectiveGetVar(ob, "person"),
					   get_key_locale(questGetKey(quest)));
  sprintf(var, "approach_%s", tgt);
  return (questProgressGetVarInt(prog, var) == 1);
}

//
// check to see if we've completed an indefinite objective (we haven't)
bool indefinite_objective_ok(QUEST_PROGRESS *prog, QUEST_OBJECTIVE *ob) {
  return FALSE;
}

//
// check to see if we've completed a give objective
bool give_objective_ok(QUEST_PROGRESS *prog, QUEST_OBJECTIVE *ob) {
  char var[SMALL_BUFFER];
  QUEST_DATA *quest = questStageGetQuest(questObjectiveGetStage(ob));
  char    *receiver = 
    strdup(get_fullkey_relative(questObjectiveGetVar(ob, "person"),
				get_key_locale(questGetKey(quest))));
  char    *item = 
    strdup(get_fullkey_relative(questObjectiveGetVar(ob, "item"),
				get_key_locale(questGetKey(quest))));
  sprintf(var, "give_%s_%s", item, receiver);

  // garbage collection
  free(receiver);
  free(item);

  return (questProgressGetVarInt(prog, var) >=
	  questObjectiveGetVarInt(ob, "count"));
}

//
// returns whether or not the quest progress has said the objective is done
bool objective_ok(QUEST_PROGRESS *prog, QUEST_OBJECTIVE *ob) {
  OBJECTIVE_FUNCS *funcs = hashGet(ob_type_table, questObjectiveGetType(ob));
  if(funcs == NULL || funcs->ob_ok == NULL)
    return FALSE;
  else
    return funcs->ob_ok(prog, ob);
}

//
// looks at a character's progress on a quest. If all of the current stage's
// objectives are met, then advance the character on the quest
void try_advance_quest(CHAR_DATA *ch, QUEST_DATA *quest) {
  QUEST_AUX_DATA *data = charGetAuxiliaryData(ch, "quest_data");
  QUEST_PROGRESS *prog = hashGet(data->quests, questGetKey(quest));
  if(prog != NULL) {
    QUEST_STAGE *stage = listGet(questGetStages(quest), prog->stage);
    if(stage != NULL) {
      bool         obs_ok = TRUE;
      LIST_ITERATOR *ob_i = newListIterator(stage->objectives);
      QUEST_OBJECTIVE *ob = NULL;
      ITERATE_LIST(ob, ob_i) {
	obs_ok = objective_ok(prog, ob);
	if(obs_ok == FALSE)
	  break;
      } deleteListIterator(ob_i);
      if(obs_ok == TRUE)
	charAdvanceQuest(ch, quest);
    }
  }
}

//
// returns a list of all the objectives the character currently has. The list
// must be deleted after use
LIST *charGetQuestObjectives(CHAR_DATA *ch) {
  LIST       *objectives = newList();
  QUEST_AUX_DATA    *aux = charGetAuxiliaryData(ch, "quest_data");
  HASH_ITERATOR  *prog_i = newHashIterator(aux->quests);
  const char        *key = NULL;
  QUEST_PROGRESS   *prog = NULL;
  // go across all of the quests we're currently on
  ITERATE_HASH(key, prog, prog_i) {
    QUEST_DATA *quest = worldGetType(gameworld, "quest", key);
    if(quest != NULL) {
      // figure out what our current objectives are in the quest
      QUEST_STAGE *stage = listGet(questGetStages(quest), prog->stage);
      if(stage != NULL) {
	LIST_ITERATOR *ob_i = newListIterator(stage->objectives);
	QUEST_OBJECTIVE *ob = NULL;
	ITERATE_LIST(ob, ob_i) {
	  listQueue(objectives, ob);
	} deleteListIterator(ob_i);
      }
    }
  } deleteHashIterator(prog_i);
  return objectives;
}

//
// compares two quests by name
int questnamecmp(QUEST_DATA *q1, QUEST_DATA *q2) {
  return strcasecmp(questGetName(q1), questGetName(q2));
}

//
// return a list of quests (in order by completion status, and then alphabetical
// order) of the quests the character is completing or has completed.
LIST *charGetQuests(CHAR_DATA *ch) {
  QUEST_AUX_DATA *aux = charGetAuxiliaryData(ch, "quest_data");
  LIST   *master_list = newList();
  LIST     *prog_list = newList();
  LIST     *comp_list = newList();
  QUEST_DATA   *quest = NULL;

  // get all of the quests we are currently on
  HASH_ITERATOR *prog_i = newHashIterator(aux->quests);
  const char       *key = NULL;
  QUEST_PROGRESS  *prog = NULL;
  ITERATE_HASH(key, prog, prog_i) {
    if((quest = worldGetType(gameworld, "quest", key)) != NULL)
      listPutWith(prog_list, quest, questnamecmp);
  } deleteHashIterator(prog_i);

  // get all of the quests we've completed
  LIST_ITERATOR *comp_i = newListIterator(aux->completed);
  ITERATE_LIST(key, comp_i) {
    if((quest = worldGetType(gameworld, "quest", key)) != NULL)
      listPutWith(comp_list, quest, questnamecmp);
  } deleteListIterator(comp_i);

  // append the contents of both list to our master list
  while( (quest = listPop(prog_list)) != NULL)
    listQueue(master_list, quest);
  while( (quest = listPop(comp_list)) != NULL)
    listQueue(master_list, quest);

  // garbage collection
  deleteList(prog_list);
  deleteList(comp_list);
  return master_list;
}

//
// appends the kill status on a quest objective
void append_kill_objective_status(BUFFER *buf, QUEST_PROGRESS *prog,
				  QUEST_OBJECTIVE *ob) {
  QUEST_DATA *quest = questStageGetQuest(questObjectiveGetStage(ob));
  char var[SMALL_BUFFER];
  sprintf(var, "kill_%s", 
	  get_fullkey_relative(questObjectiveGetVar(ob, "enemy"),
			       get_key_locale(questGetKey(quest))));
  bprintf(buf, "%d/%d", 
	  questProgressGetVarInt(prog, var), 
	  questObjectiveGetVarInt(ob, "times"));
}

//
// appends the greet status on a quest objective
void append_greet_objective_status(BUFFER *buf, QUEST_PROGRESS *prog,
				  QUEST_OBJECTIVE *ob) {
  QUEST_DATA *quest = questStageGetQuest(questObjectiveGetStage(ob));
  char var[SMALL_BUFFER];
  sprintf(var, "approach_%s", 
	  get_fullkey_relative(questObjectiveGetVar(ob, "person"),
			       get_key_locale(questGetKey(quest))));
  bprintf(buf, "%s", (questProgressGetVarInt(prog, var) == 1 ? "complete" : "incomplete"));
}

//
// appends the indefinite status on a quest objective
void append_indefinite_objective_status(BUFFER *buf, QUEST_PROGRESS *prog,
					QUEST_OBJECTIVE *ob) {
  bprintf(buf, "incomplete");
}

//
// appends the give status on a quest objective
void append_give_objective_status(BUFFER *buf, QUEST_PROGRESS *prog,
				  QUEST_OBJECTIVE *ob) {
  QUEST_DATA *quest = questStageGetQuest(questObjectiveGetStage(ob));
  char var[SMALL_BUFFER];
  sprintf(var, "give_%s_", 
	  get_fullkey_relative(questObjectiveGetVar(ob, "item"),
			       get_key_locale(questGetKey(quest))));
  sprintf(var+strlen(var), "%s",
	  get_fullkey_relative(questObjectiveGetVar(ob, "person"),
			       get_key_locale(questGetKey(quest))));
  bprintf(buf, "%d/%d", 
	  questProgressGetVarInt(prog, var), 
	  questObjectiveGetVarInt(ob, "count"));
}

//
// appends info about the status of a quest objective to the buffer
void append_objective_info(BUFFER *buf, QUEST_PROGRESS *prog, 
			   QUEST_OBJECTIVE *ob) {
  OBJECTIVE_FUNCS *funcs = hashGet(ob_type_table, questObjectiveGetType(ob));
  bprintf(buf, "%s: {w", questObjectiveGetDesc(ob));
  if(funcs == NULL || funcs->append_ob_status == NULL)
    bprintf(buf, "incomplete");
  else
    funcs->append_ob_status(buf, prog, ob);
}

//
// shows the character his status on a quest
void show_incomplete_quest_to_char(QUEST_DATA *quest, CHAR_DATA *ch) {
  QUEST_AUX_DATA  *aux = charGetAuxiliaryData(ch, "quest_data");
  QUEST_PROGRESS *prog = hashGet(aux->quests, questGetKey(quest));
  QUEST_STAGE   *stage = listGet(questGetStages(quest), prog->stage);
  BUFFER          *buf = newBuffer(MAX_BUFFER);
  BUFFER         *dbuf = bufferCopy(questGetDescBuf(quest));
  bufferFormat(dbuf, SCREEN_WIDTH, PARA_INDENT);

  // put up our basic info
  bprintf(buf,
	  "{c%s%s\r\n"
	  "{g%s\r\n"
	  "{wObjectives:{g\r\n",
	  questGetName(quest), (charFailedQuest(ch, quest) ? "{R - FAILED":""),
	  bufferString(dbuf));

  // append info for each of our objectives
  LIST_ITERATOR *ob_i = newListIterator(questStageGetObjectives(stage));
  QUEST_OBJECTIVE *ob = NULL;
  ITERATE_LIST(ob, ob_i) {
    bprintf(buf, "  {c");
    append_objective_info(buf, prog, ob);
    bprintf(buf, "{n\r\n");
  } deleteListIterator(ob_i);
  bprintf(buf, "{n");

  // send the info
  if(charGetSocket(ch))
    page_string(charGetSocket(ch), bufferString(buf));
  
  // garbage collection
  deleteBuffer(buf);
  deleteBuffer(dbuf);
}

//
// shows the character info about a quest he has completed
void show_complete_quest_to_char(QUEST_DATA *quest, CHAR_DATA *ch) {
  BUFFER  *buf = newBuffer(MAX_BUFFER);
  BUFFER *dbuf = bufferCopy(questGetDescBuf(quest));
  bufferFormat(dbuf, SCREEN_WIDTH, PARA_INDENT);
  bprintf(buf, 
	  "{c%s\r\n"
	  "{g%s\r\n"
	  "{wYou have completed this quest.{n\r\n",
	  questGetName(quest), bufferString(dbuf));
  
  // send the info
  if(charGetSocket(ch))
    page_string(charGetSocket(ch), bufferString(buf));
  
  // garbage collection
  deleteBuffer(buf);
  deleteBuffer(dbuf);
}

//
// shows info for one quest to a character
void show_quest_to_char(QUEST_DATA *quest, CHAR_DATA *ch) {
  // have we already completed the quest?
  if(charCompletedQuest(ch, quest))
    show_complete_quest_to_char(quest, ch);
  // we're currently on the quest
  else if(charOnQuest(ch, quest))
    show_incomplete_quest_to_char(quest, ch);
  // we've never been on the quest
  else
    send_to_char(ch, "You know nothing about %s.\r\n", questGetName(quest));
}



//*****************************************************************************
// player commands
//*****************************************************************************

//
// Display info about the quests we have going on at the moment
COMMAND(cmd_quests) {
  int num = -1;
  if(!parse_args(ch, TRUE, cmd, arg, "| [info] int", &num))
    return;

  // show info for a specific quest
  else if(num >= 0) {
    LIST      *quests = charGetQuests(ch);
    QUEST_DATA *quest = listGet(quests, num);
    if(quest == NULL)
      send_to_char(ch, "You have no quest numbered %d!\r\n", num);
    else
      show_quest_to_char(quest, ch);
    // garbage collection
    deleteList(quests);
  }

  // show all quests we currently have recorded
  else {
    BUFFER            *buf = newBuffer(MAX_BUFFER);
    LIST           *quests = charGetQuests(ch);
    LIST_ITERATOR *quest_i = newListIterator(quests);
    QUEST_DATA      *quest = NULL;
    int              count = 0;

    bprintf(buf, "{w%-70s %9s\r\n"
	    "{b--------------------------------------------------------------------------------\r\n", " Quest", "Status "); 

    // build a list of the quests we need to complete
    ITERATE_LIST(quest, quest_i) {
      if(charFailedQuest(ch, quest))
	bprintf(buf,"{c %3d) %-63s    FAILED \r\n", count, questGetName(quest));
      else if(charOnQuest(ch, quest))
	bprintf(buf,"{c %3d) %-63sINCOMPLETE \r\n", count, questGetName(quest));
      else
	bprintf(buf,"{c %3d) %-63s  COMPLETE \r\n", count, questGetName(quest));
      count++;
    } deleteListIterator(quest_i);

    // page our string
    if(charGetSocket(ch)) {
      if(listSize(quests) > 0) {
	bprintf(buf, "\r\n{gTo view info for a specific quest, use: "
		"quest info <num>{n\r\n");
	page_string(charGetSocket(ch), bufferString(buf));
      }
      else
	send_to_char(ch, "You have never been on a quest.\r\n");
    }
  
    // garbage collection
    deleteList(quests);
    deleteBuffer(buf);
  }
}

//
// used to let an admin force-start a quest for a person
COMMAND(cmd_qstart) {
  CHAR_DATA    *tgt = ch;
  char         *key = NULL;
  QUEST_DATA *quest = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word | ch.world", &key, &tgt))
    return;

  quest = worldGetType(gameworld, "quest", 
		       get_fullkey_relative(key, 
			   get_key_locale(roomGetClass(charGetRoom(ch)))));
  if(quest == NULL)
    send_to_char(ch, "The quest, %s, does not exist.\r\n", key);
  else {
    send_to_char(ch, "Ok.\r\n");
    charStartQuest(tgt, quest);
  }
}

//
// cancel a quest in progress/completed quest
COMMAND(cmd_qcancel) {
  CHAR_DATA    *tgt = ch;
  char         *key = NULL;
  QUEST_DATA *quest = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word | ch.world", &key, &tgt))
    return;

  quest = worldGetType(gameworld, "quest", 
		       get_fullkey_relative(key, 
			   get_key_locale(roomGetClass(charGetRoom(ch)))));
  if(quest == NULL)
    send_to_char(ch, "The quest, %s, does not exist.\r\n", key);
  else {
    send_to_char(ch, "Ok.\r\n");
    charCancelQuest(tgt, quest);
  }
}



//*****************************************************************************
// hooks
//*****************************************************************************

//
// whenever we kill something, see if it increases our progress on one of
// our current quests
void quest_kill_hook(const char *info) {
  CHAR_DATA   *ch = NULL;
  CHAR_DATA *vict = NULL;
  hookParseInfo(info, &ch, &vict);

  LIST           *obs = charGetQuestObjectives(ch);
  LIST_ITERATOR *ob_i = newListIterator(obs);
  QUEST_OBJECTIVE *ob = NULL;
  QUEST_AUX_DATA *aux = charGetAuxiliaryData(ch, "quest_data");
  ITERATE_LIST(ob, ob_i) {
    QUEST_STAGE   *stage = questObjectiveGetStage(ob);
    QUEST_DATA    *quest = questStageGetQuest(stage);
    QUEST_PROGRESS *prog = hashGet(aux->quests, questGetKey(quest));
    if(prog->failed)
      continue;
    if(!strcasecmp("kill", questObjectiveGetType(ob))) {
      const char *enemy = 
	get_fullkey_relative(questObjectiveGetVar(ob, "enemy"),
			     get_key_locale(questGetKey(quest)));
      // if it's the right enemy, up our progress and try to advance
      if(charIsInstance(vict, enemy)) {
	char var[SMALL_BUFFER];
	sprintf(var, "kill_%s", enemy);
	int val = questProgressGetVarInt(prog, var) + 1;
	int max = questObjectiveGetVarInt(ob, "times");
	questProgressSetVarInt(prog, var, MIN(val, max));
	try_advance_quest(ch, quest);
      }
    }
  } deleteListIterator(ob_i);
  deleteList(obs);
}

//
// whenever we greet someone, see if it increases our progress on one of
// our current quests
void quest_greet_hook(const char *info) {
  CHAR_DATA   *ch = NULL;
  CHAR_DATA  *tgt = NULL;
  hookParseInfo(info, &ch, &tgt);

  LIST           *obs = charGetQuestObjectives(ch);
  LIST_ITERATOR *ob_i = newListIterator(obs);
  QUEST_OBJECTIVE *ob = NULL;
  QUEST_AUX_DATA *aux = charGetAuxiliaryData(ch, "quest_data");
  ITERATE_LIST(ob, ob_i) {
    QUEST_STAGE   *stage = questObjectiveGetStage(ob);
    QUEST_DATA    *quest = questStageGetQuest(stage);
    QUEST_PROGRESS *prog = hashGet(aux->quests, questGetKey(quest));
    if(prog->failed)
      continue;
    if(!strcasecmp("approach", questObjectiveGetType(ob))) {
      const char *target = 
	get_fullkey_relative(questObjectiveGetVar(ob, "person"),
			     get_key_locale(questGetKey(quest)));
      // if it's the right person, up our progress and try to advance
      if(charIsInstance(tgt, target)) {
	char var[SMALL_BUFFER];
	sprintf(var, "approach_%s", target);
	questProgressSetVarInt(prog, var, 1);
	try_advance_quest(ch, quest);
      }
    }
  } deleteListIterator(ob_i);
  deleteList(obs);
}

//
// whenever we give an object to someone, see if it advances any of our quests
void quest_give_hook(const char *info) {
  CHAR_DATA   *ch = NULL;
  CHAR_DATA *recv = NULL;
  OBJ_DATA   *obj = NULL;
  hookParseInfo(info, &ch, &recv, &obj);

  LIST           *obs = charGetQuestObjectives(ch);
  LIST_ITERATOR *ob_i = newListIterator(obs);
  QUEST_OBJECTIVE *ob = NULL;
  QUEST_AUX_DATA *aux = charGetAuxiliaryData(ch, "quest_data");
  ITERATE_LIST(ob, ob_i) {
    QUEST_STAGE   *stage = questObjectiveGetStage(ob);
    QUEST_DATA    *quest = questStageGetQuest(stage);
    QUEST_PROGRESS *prog = hashGet(aux->quests, questGetKey(quest));
    if(prog->failed)
      continue;
    if(!strcasecmp("give", questObjectiveGetType(ob))) {
      char var[SMALL_BUFFER];
      sprintf(var, "give_");

      // make sure our object matches
      const char *obj_key = 
	get_fullkey_relative(questObjectiveGetVar(ob, "item"),
			     get_key_locale(questGetKey(quest)));
      if(!objIsInstance(obj, obj_key))
	continue;
      else
	sprintf(var+strlen(var), "%s_", obj_key);

      // make sure our receiver matches
      const char *recv_key =
	get_fullkey_relative(questObjectiveGetVar(ob, "person"),
			     get_key_locale(questGetKey(quest)));
      if(!charIsInstance(recv, recv_key))
	continue;
      else
	sprintf(var+strlen(var), "%s", recv_key);

      // up our progress, and try advancing in the quest
      int val = questProgressGetVarInt(prog, var) + 1;
      int max = questObjectiveGetVarInt(ob, "count");
      questProgressSetVarInt(prog, var, MIN(val, max));
      try_advance_quest(ch, quest);
    }
  } deleteListIterator(ob_i);
  deleteList(obs);
}



//*****************************************************************************
// python extensions
//*****************************************************************************
PyObject *PyChar_CompletedQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }

  return Py_BuildValue("i", charCompletedQuest(ch, quest));
}


PyObject *PyChar_IsOnQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  
  return Py_BuildValue("i", charOnQuest(ch, quest));
}


PyObject *PyChar_InvolvedQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  
  return Py_BuildValue("i", (charOnQuest(ch, quest) || 
			     charCompletedQuest(ch, quest)));
}


PyObject *PyChar_AdvanceQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  if(!charOnQuest(ch, quest)) {
    PyErr_Format(PyExc_StandardError, "Cannot advance character on quest, %s, "
		 "if character has not started the quest!", questGetKey(quest));
    return NULL;
  }

  charAdvanceQuest(ch, quest);
  return Py_BuildValue("i", 1);
}


PyObject *PyChar_FailQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  if(!charOnQuest(ch, quest)) {
    PyErr_Format(PyExc_StandardError, "Cannot fail character on quest, %s, "
		 "if character has not started the quest!", questGetKey(quest));
    return NULL;
  }

  charFailQuest(ch, quest);
  return Py_BuildValue("i", 1);
}


PyObject *PyChar_CancelQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  
  charCancelQuest(ch, quest);
  return Py_BuildValue("i", 1);
}


PyObject *PyChar_StartQuest(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  if(charOnQuest(ch, quest) || charCompletedQuest(ch, quest)) {
    PyErr_Format(PyExc_StandardError, "Character has already started quest, %s",
		 questGetKey(quest));
    return NULL;
  }

  charStartQuest(ch, quest);
  return Py_BuildValue("i", 1);
}


PyObject *PyChar_QuestStage(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  
  return Py_BuildValue("i", charGetQuestStage(ch, quest));
}


PyObject *PyChar_QuestFailed(PyObject *self, PyObject *args) {
  CHAR_DATA *ch = PyChar_AsChar(self);
  char     *key = NULL;
  if(!PyArg_ParseTuple(args, "s", &key)) {
    PyErr_Format(PyExc_TypeError, "A quest string key must be provided.");
    return NULL;
  }
  if(ch == NULL) {
    PyErr_Format(PyExc_StandardError, 
		 "Char %d does not exist.", PyChar_AsUid(self)); 
    return NULL;
  }

  QUEST_DATA *quest = 
    worldGetType(gameworld, "quest",
		 get_fullkey_relative(key, get_script_locale()));

  if(quest == NULL) {
    PyErr_Format(PyExc_StandardError, "Quest, %s, does not exist", key);
    return NULL;
  }
  
  return Py_BuildValue("i", charFailedQuest(ch, quest));
}



//*****************************************************************************
// initialization
//*****************************************************************************
void init_quests(void) {
  // set up our local variables
  ob_type_table = newHashtable();

  // set up our new datatype
  worldAddType(gameworld, "quest", questRead, questStore, deleteQuest, 
  	       questSetKey);

  // set up our auxiliary data
  auxiliariesInstall("quest_data",
		     newAuxiliaryFuncs(AUXILIARY_TYPE_CHAR,
				       newQuestAuxData,    deleteQuestAuxData,
				       questAuxDataCopyTo, questAuxDataCopy,
				       questAuxDataStore,  questAuxDataRead));

  // add our Python extensions
  PyChar_addMethod("quest_start",    PyChar_StartQuest,     METH_VARARGS, NULL);
  PyChar_addMethod("quest_cancel",   PyChar_CancelQuest,    METH_VARARGS, NULL);
  PyChar_addMethod("quest_advance",  PyChar_AdvanceQuest,   METH_VARARGS, NULL);
  PyChar_addMethod("quest_on",       PyChar_IsOnQuest,      METH_VARARGS, NULL);
  PyChar_addMethod("quest_completed",PyChar_CompletedQuest, METH_VARARGS, NULL);
  PyChar_addMethod("quest_involved", PyChar_InvolvedQuest,  METH_VARARGS, NULL);
  PyChar_addMethod("quest_stage",    PyChar_QuestStage,     METH_VARARGS, NULL);
  PyChar_addMethod("quest_failed",   PyChar_QuestFailed,    METH_VARARGS, NULL);
  PyChar_addMethod("quest_fail",     PyChar_FailQuest,      METH_VARARGS, NULL);

  // attach hooks
  hookAdd("post_death", quest_kill_hook);
  hookAdd("give",       quest_give_hook);
  hookAdd("greet",      quest_greet_hook);

  // add our functions for handling objective types
  hashPut(ob_type_table, "kill", 
	  newObjectiveFuncs(kill_objective_ok, append_kill_objective_status));
  hashPut(ob_type_table, "give", 
	  newObjectiveFuncs(give_objective_ok, append_give_objective_status));
  hashPut(ob_type_table, "approach",
	  newObjectiveFuncs(greet_objective_ok, append_greet_objective_status));
  hashPut(ob_type_table, "indefinite",
	  newObjectiveFuncs(indefinite_objective_ok, append_indefinite_objective_status));

  // add our commands
  add_cmd("quests",  NULL, cmd_quests,  "player", FALSE);
  add_cmd("qstart",  NULL, cmd_qstart,  "admin",  FALSE);
  add_cmd("qcancel", NULL, cmd_qcancel, "admin",  FALSE);

  // set up quest OLC
  init_qedit();
}