/************************************************************************ Realms of Aurealis James Rhone aka Vall of RoA quest.c OLQC - OnLine Quest Creation using the RoAOLCv2.1 interface standard. Loading/saving quests to disk. Player/immortal/mob interface for interaction with quests. Basically everything having to do with automated questing is in this file. ******** 100% Completely Original Code ******** *** BE AWARE OF ALL RIGHTS AND RESERVATIONS *** ******** 100% Completely Original Code ******** All rights reserved henceforth. Please note that no guarantees are associated with any code from Realms of Aurealis. All code which has been released to the general public has been done so with an 'as is' pretense. RoA is based on both Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well as the RoA license. *** Read, Learn, Understand, Improve *** *************************************************************************/ #include "conf.h" #include "sysdep.h" #include "structures.h" #include "utils.h" #include "comm.h" #include "handler.h" #include "acmd.h" #include "db.h" #include "mudlimits.h" #include "interpreter.h" #include "roaolc.h" #include "lists.h" #include "quest.h" #include "global.h" ROA_MENU(qedit_top_menu); ROA_MENU(qedit_confirm_quit); ROA_MENU(qedit_confirm_save); ROA_MENU(qedit_confirm_qend); extern char *qbits[]; extern char *rcbits[]; char *qtypes[] = { "Object Recovery", "Slay Mob", "\n" }; // quests will be saved in lib/quest and each quest will be labelled such as // <qslot>.qst void boot_quests(void) { int qnum; FILE *fp; char fname[MAX_INPUT_LENGTH]; qslot *qptr; for (qnum = 0; qnum < NUM_QUESTS; qnum++) { sprintf(fname, "%s/%d.qst", QUEST_DIR, qnum); if (!(fp = fopen(fname, "rt"))) continue; qptr = &qarray[qnum]; qptr->qname = fread_string(fp, buf); qptr->qdesc = fread_string(fp, buf); qptr->char_notification = fread_string(fp, buf); qptr->zone_notification = fread_string(fp, buf); qptr->world_notification = fread_string(fp, buf); fscanf(fp, "%d %d %d\n", &qptr->qtype, &qptr->bitvector, &qptr->race_class_bitv); fscanf(fp, "%d %d %d %d %d\n", &qptr->days_to_complete, &qptr->exp_reward, &qptr->gold_reward, &qptr->qpts_reward, &qptr->obj_reward); fscanf(fp, "%d %d %d %d\n", &qptr->minlevel, &qptr->maxlevel, &qptr->qobj_vnum, &qptr->num_qobjs); fscanf(fp, "%d %d %d\n", &qptr->qslaymob_vnum, &qptr->num_qmobs, &qptr->enemy_vnum); fclose(fp); } } void save_single_quest(int qnum) { qslot *qptr; FILE *fp; char fname[MAX_INPUT_LENGTH]; sprintf(fname, "%s/%d.qst", QUEST_DIR, qnum); if (!(fp = fopen(fname, "wt"))) return; qptr = &qarray[qnum]; if (qptr->qname) strcpy(buf, qptr->qname); else strcpy(buf, "BLANK"); fputs(strcat(killr(buf), "~\n"), fp); if (qptr->qdesc) strcpy(buf, qptr->qdesc); else strcpy(buf, "BLANK"); fputs(strcat(killr(buf), "~\n"), fp); if (qptr->char_notification) strcpy(buf, qptr->char_notification); else strcpy(buf, "BLANK"); fputs(strcat(killr(buf), "~\n"), fp); if (qptr->zone_notification) strcpy(buf, qptr->zone_notification); else strcpy(buf, "BLANK"); fputs(strcat(killr(buf), "~\n"), fp); if (qptr->world_notification) strcpy(buf, qptr->world_notification); else strcpy(buf, "BLANK"); fputs(strcat(killr(buf), "~\n"), fp); fprintf(fp, "%d %d %d\n", qptr->qtype, qptr->bitvector, qptr->race_class_bitv); fprintf(fp, "%d %d %d %d %d\n", qptr->days_to_complete, qptr->exp_reward, qptr->gold_reward, qptr->qpts_reward, qptr->obj_reward); fprintf(fp, "%d %d %d %d\n", qptr->minlevel, qptr->maxlevel, qptr->qobj_vnum, qptr->num_qobjs); fprintf(fp, "%d %d %d\n", qptr->qslaymob_vnum, qptr->num_qmobs, qptr->enemy_vnum); fclose(fp); } // if qnum == -1, save them all void save_quests(int qnum) { qslot *qptr; if (qnum < 0) { for ( ; qnum < NUM_QUESTS; qnum++) { qptr = &qarray[qnum]; if (!qptr->qname || !*qptr->qname) continue; save_single_quest(qnum); } } else // specifying a particular qnum to save { if (qnum >= NUM_QUESTS) { mudlog("SYSERR: qnum out of range to save_quest", BRF, LEV_IMM, TRUE); return; } qptr = &qarray[qnum]; if (!qptr->qname || !*qptr->qname) { sprintf(buf, "SYSERR: Attempt to qsave with no name (#%d).", qnum); mudlog(buf, BRF, LEV_IMM, TRUE); return; } save_single_quest(qnum); } } BOOL load_char_quests(chdata *ch) { char fname[MAX_INPUT_LENGTH]; int qnum; FILE *fp; if (!get_pdata_filename(GET_NAME(ch), "plrqst", fname)) return FALSE; if (!(fp = fopen(fname, "rt"))) return FALSE; while (!feof(fp)) { fscanf(fp, "%d ", &qnum); if (qnum <= 0 || qnum >= NUM_QUESTS) { fscanf(fp, "%d %d %d %d %d\n", &qnum, &qnum, &qnum, &qnum, &qnum); // discard continue; } else fscanf(fp, "%d %d %d %d %d\n", &PCQINFO(ch, qnum).in_progress, &PCQINFO(ch, qnum).days_left, &PCQINFO(ch, qnum).vnum_to_getslay, &PCQINFO(ch, qnum).num_gottenslain , &PCQINFO(ch, qnum).num_times_completed); // this quest remains intact after logout if (PCQINFO(ch, qnum).in_progress && QFLAGGED(&qarray[qnum], Q_REMAINS)) { ch->pc_specials->on_quest = qnum; ch->pc_specials->days_left= PCQINFO(ch, qnum).days_left; ch->pc_specials->vnum_to_getslay = PCQINFO(ch, qnum).vnum_to_getslay; ch->pc_specials->num_gottenslain = PCQINFO(ch, qnum).num_gottenslain; } } fclose(fp); return TRUE; } BOOL save_char_quests(chdata *ch) { char fname[MAX_INPUT_LENGTH]; int qnum; BOOL wrote = FALSE; FILE *fp; if (!get_pdata_filename(GET_NAME(ch), "plrqst", fname)) { sprintf(buf, "SYSERR: Unable to qgetfname for %s.",GET_NAME(ch)); mudlog(buf, BRF, GET_LEVEL(ch), TRUE); return FALSE; } if (!(fp = fopen(fname, "wt"))) { sprintf(buf, "SYSERR: Unable to open %s for writing.",fname); mudlog(buf, BRF, GET_LEVEL(ch), TRUE); return FALSE; } for (qnum = 1; qnum < NUM_QUESTS; qnum++) if ((PCQINFO(ch, qnum).num_times_completed || PCQINFO(ch, qnum).in_progress)) { // update current char pcqinfo if (ONQUEST(ch) == qnum) { PCQINFO(ch, qnum).in_progress = 1; PCQINFO(ch, qnum).days_left = ch->pc_specials->days_left; PCQINFO(ch, qnum).vnum_to_getslay = ch->pc_specials->vnum_to_getslay; PCQINFO(ch, qnum).num_gottenslain = ch->pc_specials->num_gottenslain; } wrote = TRUE; // if quest remains active, save everything, else just save qnum && times completed if (QFLAGGED(&qarray[qnum], Q_REMAINS)) fprintf(fp, "%d %d %d %d %d ", qnum, PCQINFO(ch, qnum).in_progress, PCQINFO(ch, qnum).days_left, PCQINFO(ch, qnum).vnum_to_getslay, PCQINFO(ch, qnum).num_gottenslain); else fprintf(fp, "%d 0 0 0 0 ", qnum); fprintf(fp, "%d\n", PCQINFO(ch, qnum).num_times_completed); } fclose(fp); // if we didnt write anything, wax the file... if (!wrote) unlink(fname); return TRUE; } BOOL quest_being_editted(int qnum) { dsdata *d = descriptor_list; for ( ; d; d=d->next) if (D_CHECK(d) && d->character->pc_specials->index == qnum && d->character->pc_specials->q_editted) return TRUE; return FALSE; } /* yank those double subs chars out for act() */ void kill_quest_dollars(qslot *qptr) { extern char *delete_doubledollar(char *string); qptr->char_notification = delete_doubledollar(qptr->char_notification); qptr->zone_notification = delete_doubledollar(qptr->zone_notification); qptr->world_notification = delete_doubledollar(qptr->world_notification); } void wax_quest(qslot *q) { FREENULL(q->qname); FREENULL(q->qdesc); FREENULL(q->char_notification); FREENULL(q->zone_notification); FREENULL(q->world_notification); } void dupe_quest_over(qslot *src, qslot *dest) { // first, wax the destination... wax_quest(dest); // memcpy will take care of the numbers memcpy(dest, src, sizeof(qslot)); // now we have to dup the strings dest->qname = STR_DUP(src->qname); dest->qdesc = STR_DUP(src->qdesc); dest->char_notification = STR_DUP(src->char_notification); dest->zone_notification = STR_DUP(src->zone_notification); dest->world_notification= STR_DUP(src->world_notification); } // this simply 0s out all current quest info except num_times_completed for this character // PCQINFO was not getting cleared properly, dont dust ONQUEST until // we zero out PCQINFO 6/23/98 -jtrhone void dust_current_quest(chdata *ch) { PCQINFO(ch, ONQUEST(ch)).in_progress = FALSE; PCQINFO(ch, ONQUEST(ch)).days_left = 0; PCQINFO(ch, ONQUEST(ch)).vnum_to_getslay = 0; PCQINFO(ch, ONQUEST(ch)).num_gottenslain = 0; QUEST_TIME(ch) = 0; QVNUM(ch) = 0; QGOTTEN(ch) = 0; ONQUEST(ch) = FALSE; } ACMD(do_qedit) { int qnum; char buf[MAX_INPUT_LENGTH]; if (IS_NPC(ch)) return; one_argument(argument, buf); if (!*buf) { send_to_char("Usage: qedit <quest slot #>\n\r", ch); return; } if (!is_number(buf) || (qnum = atoi(buf)) < 0 || qnum >= NUM_QUESTS) { send_to_char("Usage: qedit <quest slot #>\n\r", ch); return; } if (quest_being_editted(qnum)) { send_to_char("That quest slot is currently being editted.\n\r",ch); return; } ch->pc_specials->index = qnum; CREATE(ch->pc_specials->q_editted, qslot, 1); dupe_quest_over(&qarray[qnum], QUEST_EDITTED(ch)); SET_BIT(PLR_FLAGS(ch), PLR_BUILDING); MENU_DEPTH(ch) = 0; ch->pc_specials->field_changed = FALSE; menu_jump(ch, qedit_top_menu); } ROA_MENU(qedit_top_menu) { char buf[MAX_STRING_LENGTH]; char buf2[MAX_STRING_LENGTH]; char *p; int field; qslot *q = QUEST_EDITTED(ch); int index = ch->pc_specials->index; if (!input_str) { menu_title_send("QuestEdit Main Menu", ch); sprintf(buf, " X.) %%6Qslot%%0: %d\n\r", index); S2C(); sprintf(buf, " 1.) %%6QName%%0: %s\n\r", q->qname); S2C(); sprintf(buf, " 2.) %%6QDesc%%0: \n\r%s\n\r", q->qdesc); S2C(); sprinttype(q->qtype, qtypes, buf2); sprintf(buf, " 3.) %%6QType%%0: %s\n\r", buf2); S2C(); sprintbit(q->bitvector, qbits, buf2); sprintf(buf, " 4.) %%6QFlags%%0: %%5%s%%0\n\r", buf2); S2C(); sprintbit(q->race_class_bitv, rcbits, buf2); sprintf(buf, " 5.) %%6QRCFlags%%0: %%5%s%%0\n\r", buf2); S2C(); if (q->qtype == QTYPE_OBJ_RETURN) { sprintf(buf, " 6.) %%6QObj vnum%%0: %d\n\r", q->qobj_vnum); S2C(); sprintf(buf, " 7.) %%6# of QObjs%%0: %d\n\r", q->num_qobjs); S2C(); } else if (q->qtype == QTYPE_SLAYMOB) { sprintf(buf, " 6.) %%6QMob vnum%%0: %d\n\r", q->qslaymob_vnum); S2C(); sprintf(buf, " 7.) %%6# of QMobs to slay%%0: %d\n\r", q->num_qmobs); S2C(); } if (QFLAGGED(q, Q_HAS_TIMELIMIT)) { sprintf(buf, " 8.) %%6Days to complete%%0: %d\n\r", q->days_to_complete); S2C(); } if (QFLAGGED(q, Q_MIN_LEVEL)) { sprintf(buf, " 9.) %%6Quest Minlevel%%0: %d\n\r", q->minlevel); S2C(); } if (QFLAGGED(q, Q_MAX_LEVEL)) { sprintf(buf, "10.) %%6Quest Maxlevel%%0: %d\n\r", q->maxlevel); S2C(); } if (QFLAGGED(q, Q_ENEMY_AFTER) || QFLAGGED(q, Q_ENEMY_DURING)) { sprintf(buf, "11.) %%6QEnemy vnum%%0: %d\n\r", q->enemy_vnum); S2C(); } if (QFLAGGED(q, Q_REWARD_GOLD)) { sprintf(buf, "12.) %%6%s Reward%%0: %d\n\r", currency_name_plural, q->gold_reward); S2C(); } if (QFLAGGED(q, Q_REWARD_EXP)) { sprintf(buf, "13.) %%6Exp Reward%%0: %d\n\r", q->exp_reward); S2C(); } if (QFLAGGED(q, Q_REWARD_QPTS)) { sprintf(buf, "14.) %%6Quest Pts Reward%%0: %d\n\r", q->qpts_reward); S2C(); } if (QFLAGGED(q, Q_REWARD_OBJECT)) { sprintf(buf, "15.) %%6Obj Reward vnum%%0: %d\n\r", q->obj_reward); S2C(); } // before we send dollar possible strings, wax the dollars kill_quest_dollars(q); if (QFLAGGED(q, Q_NOTIFY_CHAR)) { sprintf(buf, "16.) %%6Char Notification%%0:\n\r%s\n\r", q->char_notification); S2C(); } if (QFLAGGED(q, Q_NOTIFY_ZONE)) { sprintf(buf, "17.) %%6Zone Notification%%0:\n\r%s\n\r", q->zone_notification); S2C(); } if (QFLAGGED(q, Q_NOTIFY_WORLD)) { sprintf(buf, "18.) %%6World Notification%%0:\n\r%s\n\r",q->world_notification); S2C(); } send_to_char("\n\r", ch); MENU_PROMPT(ch) = "Enter field number to change or 0 to end: "; return; } strcpy(buf, input_str); p = strtok(buf, " \n\r"); if (p) field = atoi(p); else field = 0; switch (field) { case 0: menu_jump(ch, qedit_confirm_quit); break; case 1: do_string_arg(ch, "Enter new quest name:\n\r", &q->qname, ""); break; case 2: do_long_string_arg(ch, "Enter quest description (@):\n\r", &q->qdesc); break; case 3: get_integer_list(ch, "Enter number of the desired type: ", &q->qtype, sizeof(q->qtype), qtypes); break; case 4: toggle_menu(ch, "Enter quest bit to toggle: ", &q->bitvector, qbits); break; case 5: toggle_menu(ch, "Enter quest race/class bit to toggle: ", &q->race_class_bitv, rcbits); break; case 6: if (q->qtype == QTYPE_OBJ_RETURN) { GET_INTEGER_ARG(ch, "Enter vnum of quest object to recover: ", q->qobj_vnum, 0, 99999); } else if (q->qtype == QTYPE_SLAYMOB) { GET_INTEGER_ARG(ch, "Enter vnum of mob to slay: ", q->qslaymob_vnum, 0, 99999); } break; case 7: if (q->qtype == QTYPE_OBJ_RETURN) { GET_INTEGER_ARG(ch, "Enter number of quest objects to recover: ", q->num_qobjs, 0, 100); } else if (q->qtype == QTYPE_SLAYMOB) { GET_INTEGER_ARG(ch, "Enter number of mobs to slay: ", q->num_qmobs, 0, 100); } break; case 8: if (QFLAGGED(q, Q_HAS_TIMELIMIT)) GET_INTEGER_ARG(ch, "Enter number of days to complete: ", q->days_to_complete, 1, 100); break; case 9: if (QFLAGGED(q, Q_MIN_LEVEL)) GET_INTEGER_ARG(ch, "Enter quest minlevel: ", q->minlevel, 0, LEV_IMPL); break; case 10: if (QFLAGGED(q, Q_MAX_LEVEL)) GET_INTEGER_ARG(ch, "Enter quest maxlevel: ", q->maxlevel, 0, LEV_IMPL); break; case 11: if (QFLAGGED(q, Q_ENEMY_AFTER) || QFLAGGED(q, Q_ENEMY_DURING)) GET_INTEGER_ARG(ch, "Enter enemy mob vnum: ", q->enemy_vnum, 0, 99999); break; case 12: if (QFLAGGED(q, Q_REWARD_GOLD)) GET_INTEGER_ARG(ch, "Enter monetary reward amount: ", q->gold_reward, 0, 999999); break; case 13: if (QFLAGGED(q, Q_REWARD_EXP)) GET_INTEGER_ARG(ch, "Enter exp reward amount: ", q->exp_reward, 0, 999999); break; case 14: if (QFLAGGED(q, Q_REWARD_QPTS)) GET_INTEGER_ARG(ch, "Enter quest pts reward amount: ",q->qpts_reward,0,99); break; case 15: if (QFLAGGED(q, Q_REWARD_OBJECT)) GET_INTEGER_ARG(ch, "Enter reward obj vnum: ", q->obj_reward, 0, 99999); break; case 16: if (QFLAGGED(q, Q_NOTIFY_CHAR)) do_long_string_arg(ch, "Enter char completion notification (@):\n\r", &q->char_notification); break; case 17: if (QFLAGGED(q, Q_NOTIFY_ZONE)) do_long_string_arg(ch, "Enter zone completion notification (@):\n\r", &q->zone_notification); break; case 18: if (QFLAGGED(q, Q_NOTIFY_WORLD)) do_long_string_arg(ch, "Enter world completion notification (@):\n\r", &q->world_notification); break; default: send_to_char("That field cannot be changed.\n\r", ch); break; } ch->pc_specials->field_changed = TRUE; } ROA_MENU(qedit_confirm_quit) { char buf[MAX_STRING_LENGTH]; char *p; if (!input_str) { MENU_PROMPT(ch) = "Quit editting quest (yes/NO)? "; return; } strcpy(buf, input_str); p = strtok(buf, " \n\r"); if (p && strncasecmp("yes", p, strlen(p)) == 0) menu_jump(ch, qedit_confirm_save); else menu_jump(ch, qedit_top_menu); } ROA_MENU(qedit_confirm_save) { char buf[MAX_STRING_LENGTH]; char *p; qslot *q = QUEST_EDITTED(ch); int index = ch->pc_specials->index; if (!input_str) { MENU_PROMPT(ch) = "Save editted quest (YES/no)? "; return; } strcpy(buf, input_str); p = strtok(buf, " \n\r"); if (!p || strncasecmp("no", p, strlen(p)) != 0) { dupe_quest_over(q, &qarray[index]); save_quests(index); sprintf(buf, "PLRUPD: %s has editted quest %d", GET_NAME(ch), index); mudlog(buf, NRM, GET_LEVEL(ch), TRUE); } wax_quest(QUEST_EDITTED(ch)); FREENULL(QUEST_EDITTED(ch)); MENU_PROMPT(ch) = NULL; MENU_HANDLER(ch) = NULL; MENU_DEPTH(ch) = 0; ch->pc_specials->index = -1; REMOVE_BIT(PLR_FLAGS(ch), PLR_BUILDING); } ROA_MENU(qedit_confirm_qend) { char buf[MAX_STRING_LENGTH]; char *p; int index = ONQUEST(ch); if (!input_str) { MENU_PROMPT(ch) = "\n\rConfirm quest end (YES/no)? "; return; } strcpy(buf, input_str); p = strtok(buf, " \n\r"); if (!p || strncasecmp("no", p, strlen(p)) != 0) { sprintf(buf, "PLRUPD: %s has manually ended quest %d.", GET_NAME(ch), index); mudlog(buf, BUG, GET_LEVEL(ch), TRUE); send_to_char("Quest end confirmed.\n\r",ch); dust_current_quest(ch); } else { send_to_char("Quest end not confirmed.\n\r",ch); } MENU_PROMPT(ch) = NULL; MENU_HANDLER(ch) = NULL; MENU_DEPTH(ch) = 0; } // ***************************************************** // Player/Mob/Immortal quest commands... // ***************************************************** // return TRUE if someone else is doing this quest currently... BOOL being_done(int qnum) { chdata *ch; for (ch = character_list; ch; ch=ch->next) if (IS_PC(ch) && ch->pc_specials->on_quest == qnum) return TRUE; return FALSE; } BOOL check_quest_rcflags(chdata *ch, qslot *qptr) { if (QRCFLAGGED(qptr, Q_NO_WARRIOR) && IS_NAT_WARRIOR(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_CLERIC) && IS_NAT_CLERIC(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_MAGE) && IS_NAT_MAGE(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_THIEF) && IS_NAT_THIEF(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_SHAMAN) && IS_NAT_SHAMAN(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_RANGER) && IS_NAT_RANGER(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_WARLOCK) && IS_NAT_WARLOCK(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_BARD) && IS_NAT_BARD(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_MONK) && IS_NAT_MONK(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_MADEPT) && IS_NAT_MADEPT(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_DRUID) && IS_NAT_DRUID(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_HUMAN) && IS_HUMAN(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_ELF) && IS_ELF(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_HALF_ELF) && IS_HALF_ELF(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_ORC) && IS_ORC(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_OGRE) && IS_OGRE(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_DROW) && IS_DROW(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_DWARF) && IS_DWARF(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_PIXIE) && IS_PIXIE(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_NIXIE) && IS_NIXIE(ch)) return FALSE; if (QRCFLAGGED(qptr, Q_NO_DRAGON) && IS_DRAGON(ch)) return FALSE; return TRUE; } ACMD(do_qscore) { int qnum; if (IS_NPC(ch)) { send_to_char("Go away, you're bugging me.\n\r",ch); return; } strcpy(buf, "%B%6Completed Quests%0:\n\r"); strcat(buf, "%6 # Quest Short Descrip Times Completed%0\n\r"); strcat(buf, "%6--- ------------------- ---------------%0\n\r"); for (qnum = 1; qnum < NUM_QUESTS; qnum++) if (PCQINFO(ch, qnum).num_times_completed > 0) { sprintf(buf+strlen(buf), "%%5%3d%%0 %-30.30s %2d\n\r", qnum, qarray[qnum].qname, PCQINFO(ch, qnum).num_times_completed); } if (ONQUEST(ch) && VALID_QUEST(ONQUEST(ch))) { sprintf(buf+strlen(buf), "\n\r%%B%%6Current Quest%%0:\n\r%%5%3d%%0 %-30.30s %2d\n\r", ONQUEST(ch), qarray[ONQUEST(ch)].qname, PCQINFO(ch, ONQUEST(ch)).num_times_completed); if (QFLAGGED(&qarray[ONQUEST(ch)], Q_HAS_TIMELIMIT)) sprintf(buf+strlen(buf),"%%B%%6Days left to complete quest%%0: %d\n\r", QUEST_TIME(ch)); } page_string(ch->desc, buf, 1); } // list short names and numbers of quests... ACMD(do_qlist) { int qnum; qslot *qptr; BOOL found = FALSE; char buf[20000]; sprintf(buf, "%%B%%6%s Quest Listing:%%0\n\r", shortmudname); for (qnum = 0; qnum < NUM_QUESTS; qnum++) { qptr = &qarray[qnum]; if (!qptr->qname || !*qptr->qname || !QFLAGGED(qptr, Q_ACTIVATED)) continue; found = TRUE; sprintf(buf+strlen(buf), "%%B%%6%3d%%0 - %-40.40s %%6Min%%0: %2d %%6Max%%0: %2d %%6Qpts%%0: %2d\n\r", qnum, qptr->qname, qptr->minlevel, qptr->maxlevel, qptr->qpts_reward); } if (!found) strcat(buf, "%B%1No active quests available.%0\n\r"); page_string(ch->desc, buf, 1); } void show_if_can_quest(chdata *ch, qslot *qptr, int qnum) { BOOL can = TRUE; if (ONQUEST(ch) == qnum) { send_to_char("\n\rYou are currently performing this quest.\n\r",ch); return; } send_to_char("\n\r", ch); if (QFLAGGED(qptr, Q_SOLO) && being_done(qnum)) { send_to_char("Quest is SOLO and is being performed by someone.\n\r",ch); can = FALSE; } if (!check_quest_rcflags(ch, qptr)) { send_to_char("Quest restricts your race or class.\n\r",ch); can = FALSE; } if (QFLAGGED(qptr, Q_MIN_LEVEL) && GET_LEVEL(ch) < qptr->minlevel) { send_to_char("You are too low a level to perform this quest.\n\r",ch); can = FALSE; } if (QFLAGGED(qptr, Q_MAX_LEVEL) && GET_LEVEL(ch) > qptr->maxlevel) { send_to_char("You are too high a level to perform this quest.\n\r",ch); can = FALSE; } if (QFLAGGED(qptr, Q_ONE_TIMEONLY) && PCQINFO(ch, qnum).num_times_completed) { send_to_char("Quest is one_time_only and you have completed it.\n\r",ch); can = FALSE; } if (can) send_to_char("You can perform this quest.\n\r",ch); else send_to_char("You cannot perform this quest.\n\r",ch); } ACMD(do_qdescribe) { char *argu = argument; qslot *qptr; int qnum; skip_spaces(&argu); if (!*argu || !is_number(argu)) { send_to_char("Usage: qdescribe <active quest #>.\n\r",ch); return; } qnum = atoi(argu); if (!VALID_QUEST(qnum)) { send_to_char("Usage: qdescribe <active quest #>.\n\r",ch); return; } qptr = &qarray[qnum]; if (!QFLAGGED(qptr, Q_ACTIVATED)) { send_to_char("Usage: qdescribe <active quest #>.\n\r",ch); return; } if (!qptr->qname || !*qptr->qname || !qptr->qdesc || !*qptr->qdesc ) { send_to_char("Invalid quest name or description. Notify immortal.\n\r",ch); sprintf(buf, "SYSERR: Invalid qname or qdesc on quest #%d.", qnum); mudlog(buf, BRF, LEV_IMM, TRUE); return; } sprintf(buf, "%%B%%6Quest Description%%0:\n\r%s (#%d)\n\r",qptr->qname,qnum); S2C(); page_string(ch->desc, qptr->qdesc, 1); show_if_can_quest(ch, qptr, qnum); } void start_mobvnum_hunting(int enemy_vnum, chdata *vict) { chdata *tmpvict; for (tmpvict = character_list; tmpvict; tmpvict = tmpvict->next) if (IS_NPC(tmpvict) && GET_MOB_VNUM(tmpvict) == enemy_vnum) { sprintf(buf, "SYSUPD: %s (#%d) setting hunt target to %s.", GET_NAME(tmpvict), GET_MOB_VNUM(tmpvict), GET_NAME(vict)); mudlog(buf, BUG, LEV_IMM, TRUE); HUNTING(tmpvict) = vict; } } #define QBUSAGE "Usage: qbegin <vict name> <qslot>.\n\r" ACMD(do_qbegin) { int qnum; qslot *qptr; char *argu = argument; chdata *vict; char name[MAX_INPUT_LENGTH], numstr[MAX_INPUT_LENGTH]; if (IS_PC(ch) && GET_LEVEL(ch) < LEV_GOD) { send_to_char("Huh?!?\n\r",ch); return; } skip_spaces(&argu); if (!*argu) { send_to_char(QBUSAGE, ch); return; } half_chop(argu, name, numstr); if (!(vict = get_char_room_vis(ch, name))) { send_to_char("Who?\n\r",ch); return; } if (vict->pc_specials->on_quest > 0) { sprintf(buf, "$N is already doing quest %d.", vict->pc_specials->on_quest); act(buf, FALSE, ch, 0, vict, TO_CHAR); act("You must finish your current quest or %Bqend%0 first to accept another.", FALSE, ch, 0, vict, TO_VICT); return; } if (IS_NPC(ch)) qnum = ch->npc_specials.qnum; else if (*numstr && is_number(numstr)) qnum = atoi(numstr); else { send_to_char(QBUSAGE, ch); return; } if (qnum <= 0 || qnum >= NUM_QUESTS) { send_to_char("You supplied an invalid quest number.\n\r",ch); if (IS_NPC(ch)) { sprintf(buf, "SYSERR: %s (#%d) supplied invalid quest number to %s.", GET_NAME(ch), GET_MOB_VNUM(ch), GET_NAME(vict)); mudlog(buf, BRF, LEV_IMM, TRUE); } return; } qptr = &qarray[qnum]; if (!QFLAGGED(qptr, Q_ACTIVATED)) { send_to_char("You supplied an inactive quest number.\n\r",ch); if (IS_NPC(ch)) { sprintf(buf, "SYSERR: %s (#%d) supplied inactive quest number (%d) to %s.", GET_NAME(ch), GET_MOB_VNUM(ch), qnum, GET_NAME(vict)); mudlog(buf, BRF, LEV_IMM, TRUE); } return; } if (QFLAGGED(qptr, Q_SOLO) && being_done(qnum)) { send_to_char("That quest is SOLO and is being performed by someone.\n\r",ch); return; } if (!check_quest_rcflags(vict, qptr)) return; if (QFLAGGED(qptr, Q_MIN_LEVEL) && GET_LEVEL(vict) < qptr->minlevel) { act("$N is too low a level to perform that quest.", FALSE, ch, 0, vict, TO_CHAR); return; } if (QFLAGGED(qptr, Q_MAX_LEVEL) && GET_LEVEL(vict) > qptr->maxlevel) { act("$N is too high a level to perform that quest.", FALSE, ch, 0, vict, TO_CHAR); return; } if (QFLAGGED(qptr, Q_ONE_TIMEONLY) && PCQINFO(vict, qnum).num_times_completed) { act("$N has already completed that ONE_TIME_ONLY quest.",FALSE,ch,0,vict,TO_CHAR); return; } // ok, checks passed transfer data to character switch (qptr->qtype) { case QTYPE_OBJ_RETURN: sprintf(buf, "SYSUPD: %s started %s on quest %d.", GET_NAME(ch), GET_NAME(vict),qnum); mudlog(buf, BUG, GET_LEVEL(ch), TRUE); break; case QTYPE_SLAYMOB: sprintf(buf, "SYSUPD: %s started %s on quest %d.", GET_NAME(ch), GET_NAME(vict),qnum); mudlog(buf, BUG, GET_LEVEL(ch), TRUE); break; default: sprintf(buf, "SYSERR: Invalid quest type %d on quest %d.",qptr->qtype,qnum); mudlog(buf, BRF, LEV_IMM, TRUE); return; } ONQUEST(vict) = qnum; if (QFLAGGED(qptr, Q_HAS_TIMELIMIT)) QUEST_TIME(vict) = qptr->days_to_complete; // if a specific mob begins the hunt, do it here if (QFLAGGED(qptr, Q_ENEMY_DURING)) start_mobvnum_hunting(qptr->enemy_vnum, vict); // let vict know they just started sprintf(buf, "%%BYou have just started quest #%d.%%0\n\r",qnum); send_to_char(buf, vict); send_to_char("Ok, quest assigned.\n\r",ch); } ACMD(do_qend) { if (IS_NPC(ch)) { send_to_char("Yeah.\n\r",ch); return; } if (!ONQUEST(ch)) { send_to_char("You're not currently performing any quests.\n\r",ch); return; } if (!VALID_QUEST(ONQUEST(ch))) { send_to_char("Invalid quest number. Report to an immortal.\n\r",ch); return; } menu_jump(ch, qedit_confirm_qend); } void give_quest_reward(chdata *ch, int qnum) { qslot *qptr = &qarray[qnum]; obdata *obj = NULL; sprintf(buf, "%%B%%6Quest Rewards%%0: %s (#%d)\n\r", qptr->qname ? qptr->qname : "INVALID QUEST NAME", qnum); if (QFLAGGED(qptr, Q_REWARD_QPTS)) { sprintf(buf+strlen(buf), "You receive %%6%d%%0 quest points.\n\r", qptr->qpts_reward); ch->pc_specials->saved.quest_pts += qptr->qpts_reward; } if (QFLAGGED(qptr, Q_REWARD_EXP)) { sprintf(buf+strlen(buf), "You receive %%6%d%%0 experience points.\n\r", qptr->exp_reward); gain_exp(ch, qptr->exp_reward); } if (QFLAGGED(qptr, Q_REWARD_GOLD)) { sprintf(buf+strlen(buf), "You receive %%6%d%%0 %s.\n\r", qptr->gold_reward, currency_name_plural); GET_GOLD(ch) += qptr->gold_reward; } if (QFLAGGED(qptr, Q_REWARD_OBJECT)) { if ((obj = read_object(qptr->obj_reward, VIRTUAL))) { sprintf(buf+strlen(buf),"You receive %s.\n\r",obj->shdesc); obj_to_char(obj, ch); } else { sprintf(buf+strlen(buf), "%%B%%1WARNING: Reward object vnum error (%d).%%0", qptr->obj_reward); sprintf(buf2, "SYSERR: Invalid obj_reward for quest #%d.",qnum); mudlog(buf2, BRF, LEV_IMM, TRUE); } } page_string(ch->desc, buf, 1); } // this gets called when character finishes the quest he/she is workin on // it could be immediately or after some other action like returning the // quest object to the rewarder void do_complete_quest(chdata *ch, chdata *rewarder) { int qnum = ONQUEST(ch); qslot *qptr = &qarray[qnum]; chdata *vict = NULL; obdata *obj = NULL; int rnum; switch (qptr->qtype) { case QTYPE_OBJ_RETURN: if ((rnum = real_object(qptr->qobj_vnum)) <= 0) { sprintf(buf, "SYSERR: Invalid qobj_vnum (%d) on quest #%d, not completed.", qptr->qobj_vnum, qnum); mudlog(buf, BRF, LEV_IMM, TRUE); sprintf(buf, "SYSERR: %s lost possible #%d quest completion.",GET_NAME(ch), qnum); mudlog(buf, BRF, LEV_IMM, TRUE); return; } else obj = &obj_proto[rnum]; break; case QTYPE_SLAYMOB: if ((rnum = real_mobile(qptr->qslaymob_vnum)) <= 0) { sprintf(buf, "SYSERR: Invalid qslaymob (%d) on quest #%d, not completed.", qptr->qslaymob_vnum, qnum); mudlog(buf, BRF, LEV_IMM, TRUE); sprintf(buf, "SYSERR: %s lost possible #%d quest completion.",GET_NAME(ch), qnum); mudlog(buf, BRF, LEV_IMM, TRUE); return; } else vict = &mob_proto[rnum]; break; default: break; } if (QFLAGGED(qptr, Q_NOTIFY_CHAR) && qptr->char_notification && *qptr->char_notification) act(qptr->char_notification, FALSE, ch, obj, vict, TO_CHAR); if (QFLAGGED(qptr, Q_NOTIFY_ZONE) && qptr->zone_notification && *qptr->zone_notification) act(qptr->zone_notification, FALSE, ch, obj, vict, TO_ZONE_MORTLOG); if (QFLAGGED(qptr, Q_NOTIFY_WORLD) && qptr->world_notification && *qptr->world_notification) act(qptr->world_notification, FALSE, ch, obj, vict, TO_WORLD_MORTLOG); if (QFLAGGED(qptr, Q_REWARD_EXP | Q_REWARD_GOLD | Q_REWARD_QPTS | Q_REWARD_OBJECT)) give_quest_reward(ch, qnum); if (QFLAGGED(qptr, Q_ENEMY_AFTER)) start_mobvnum_hunting(qptr->enemy_vnum, ch); if (QFLAGGED(qptr, Q_MOB_GOBYEBYE) && rewarder && IS_NPC(rewarder)) { act("$n suddenly vanishes!", TRUE, rewarder, 0, 0, TO_ROOM); extract_char(rewarder); } sprintf(buf, "SYSUPD: %s completed quest #%d.", GET_NAME(ch), qnum); mudlog(buf, BUG, GET_LEVEL(ch), TRUE); // update pcqinfo PCQINFO(ch, qnum).num_times_completed++; dust_current_quest(ch); } ACMD(do_qcomplete) { chdata *vict; char *argu = argument; if (IS_NPC(ch)) return; skip_spaces(&argu); if (!*argu || !(vict = get_char_vis(ch, argu))) { send_to_char("Who?\n\r",ch); return; } if (IS_NPC(vict) || !ONQUEST(vict)) { act("$N isn't performing any quests.",FALSE,ch,0,vict,TO_CHAR); return; } send_to_char("Ok.\n\r",ch); do_complete_quest(vict, ch); } void do_fail_quest(chdata *ch) { send_to_char("%B%1You have failed in your quest...%0\n\r",ch); sprintf(buf, "PLRUPD: %s has failed quest %d.", GET_NAME(ch), ONQUEST(ch)); mudlog(buf, BUG, GET_LEVEL(ch), TRUE); dust_current_quest(ch); } void check_player_quest(chdata *ch, BOOL day_passed) { qslot *qptr; qptr = &qarray[ONQUEST(ch)]; if (QFLAGGED(qptr, Q_HAS_TIMELIMIT)) { if (day_passed) QUEST_TIME(ch)--; if (QUEST_TIME(ch) <= 0) { send_to_char("%B%1Time has escaped you...%0\n\r",ch); do_fail_quest(ch); } else if (QUEST_TIME(ch) == 1) send_to_char("%B%6You have one day left to complete your quest, adventurer.%0\n\r",ch); else if (QUEST_TIME(ch) == 7) send_to_char("%B%6You have one week left to complete your quest, adventurer.%0\n\r",ch); } } // called from weather.c after a new day dawns void check_char_quests(void) { chdata *ch; for (ch=character_list; ch; ch=ch->next) if (IS_PC(ch) && ONQUEST(ch) && VALID_QUEST(ONQUEST(ch))) check_player_quest(ch, TRUE); } // ****************************************************** * * * * // The following functions are called from scattered points throughout // the mud. Different areas in which particular types of quests can // be completed... -roa // ****************************************************** * * * * // if char and mob quests sync up, return TRUE, else FALSE BOOL char_mob_questmatch(chdata *ch, chdata *vict) { if (IS_NPC(ch) || !IS_NPC(vict) || !MOB_FLAGGED(vict, MOB_QUESTOR)) return FALSE; if (!ONQUEST(ch) || !VALID_QUEST(ONQUEST(ch))) return FALSE; if (ONQUEST(ch) != vict->npc_specials.qnum) return FALSE; return TRUE; } // for QTYPE_OBJ_RETURN on give int qcheck_obj_give(chdata *ch, chdata *vict, obdata *obj) { int qnum; qslot *qptr; if (!char_mob_questmatch(ch, vict)) return FALSE; qnum = ONQUEST(ch); qptr = &qarray[qnum]; if (qptr->qtype != QTYPE_OBJ_RETURN) return FALSE; if (GET_OBJ_VNUM(obj) != qptr->qobj_vnum) return FALSE; if (!(++QGOTTEN(ch) >= qptr->num_qobjs)) return FALSE; do_complete_quest(ch, vict); return TRUE; } // for Q_IMMEDIATE QTYPE_OBJ_RETURN on get int qcheck_obj_get(chdata *ch, obdata *obj) { int qnum; qslot *qptr; qnum = ONQUEST(ch); if (!qnum || !VALID_QUEST(qnum)) return FALSE; qptr = &qarray[qnum]; if (qptr->qtype != QTYPE_OBJ_RETURN || !QFLAGGED(qptr, Q_IMMEDIATE_REWARD)) return FALSE; if (GET_OBJ_VNUM(obj) != qptr->qobj_vnum) return FALSE; if (!(++QGOTTEN(ch) >= qptr->num_qobjs)) return FALSE; do_complete_quest(ch, NULL); return TRUE; } // for Q_IMMEDIATE QTYPE_SLAYMOB quests, compare vs mob vnum ch just killed int qcheck_slaymob(chdata *ch, int mob_vnum) { int qnum; qslot *qptr; qnum = ONQUEST(ch); if (!qnum || !VALID_QUEST(qnum)) return FALSE; qptr = &qarray[qnum]; // SLAYMOBS M U S T be IMMEDIATE_REWARD for now if (qptr->qtype != QTYPE_SLAYMOB || !QFLAGGED(qptr, Q_IMMEDIATE_REWARD)) return FALSE; if (mob_vnum != qptr->qslaymob_vnum) return FALSE; if (!(++QGOTTEN(ch) >= qptr->num_qmobs)) return FALSE; do_complete_quest(ch, NULL); return TRUE; }