Quest System Update

* Added support for the new quests that grant item drops from mobs.
This commit is contained in:
aleos89 2015-09-17 13:24:42 -04:00
parent 2fc20c460d
commit 039e1b65a3
12 changed files with 3237 additions and 3085 deletions

View File

@ -32,6 +32,7 @@
// 0x080000 - (F) Removed bound items when guild/party is broken
// 0x100000 - (Y) Roulette Lottery
// 0x200000 - (Z) Merged items from item mergers process.
// 0x400000 - (Q) Log items given from quest-granted drops.
// Example: Log trades+vending+script items+created items: 1+2+32+1024 = 1059
// Please note that moving items from inventory to cart and back is not logged by design.
enable_logs: 0xFFFFFF

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
# (C)onsumable Items, (A)dministrators Create/Delete, Sto(R)age, (G)uild Storage,
# (E)mail attachment,(B)uying Store, Pr(O)duced Items/Ingredients, Auct(I)oned Items,
# (X) Other, (D) Stolen from mobs, (U) MVP Prizes, (F) Guild/Party Bound retrieval
# Lotter(Y), (Z) Merged Items
# Lotter(Y), (Z) Merged Items, (Q)uest
#Database: ragnarok
#Table: picklog
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `picklog` (
`id` int(11) NOT NULL auto_increment,
`time` datetime NOT NULL default '0000-00-00 00:00:00',
`char_id` int(11) NOT NULL default '0',
`type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Z') NOT NULL default 'P',
`type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Z','Q') NOT NULL default 'P',
`nameid` smallint(5) unsigned NOT NULL default '0',
`amount` int(11) NOT NULL default '1',
`refine` tinyint(3) unsigned NOT NULL default '0',

View File

@ -0,0 +1 @@
ALTER TABLE `picklog` MODIFY `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Z','Q') NOT NULL default 'P';

View File

@ -70,6 +70,7 @@
#define MAX_GUILDLEVEL 50 ///Max Guild level
#define MAX_GUARDIANS 8 ///Local max per castle. If this value is increased, need to add more fields on MySQL `guild_castle` table [Skotlex]
#define MAX_QUEST_OBJECTIVES 3 ///Max quest objectives for a quest
#define MAX_QUEST_DROPS 3 ///Max quest drops for a quest
#define MAX_PC_BONUS_SCRIPT 50 ///Max bonus script can be fetched from `bonus_script` table on player load [Cydh]
// for produce

View File

@ -15608,20 +15608,35 @@ void clif_parse_PartyTick(int fd, struct map_session_data* sd)
/// Sends list of all quest states (ZC_ALL_QUEST_LIST).
/// 02b1 <packet len>.W <num>.L { <quest id>.L <active>.B }*num
void clif_quest_send_list(struct map_session_data * sd)
void clif_quest_send_list(struct map_session_data *sd)
{
int fd = sd->fd;
int i;
int len = sd->avail_quests*5+8;
#if PACKETVER >= 20141022
int info_len = 15;
int len = sd->avail_quests*info_len+8;
WFIFOHEAD(fd,len);
WFIFOW(fd, 0) = 0x97a;
#else
int info_len = 5;
int len = sd->avail_quests*info_len+8;
WFIFOHEAD(fd,len);
WFIFOW(fd, 0) = 0x2b1;
#endif
WFIFOW(fd, 2) = len;
WFIFOL(fd, 4) = sd->avail_quests;
for( i = 0; i < sd->avail_quests; i++ ) {
WFIFOL(fd, i*5+8) = sd->quest_log[i].quest_id;
WFIFOB(fd, i*5+12) = sd->quest_log[i].state;
for (i = 0; i < sd->avail_quests; i++) {
#if PACKETVER >= 20141022
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
#endif
WFIFOL(fd, i*info_len+8) = sd->quest_log[i].quest_id;
WFIFOB(fd, i*info_len+12) = sd->quest_log[i].state;
#if PACKETVER >= 20141022
WFIFOL(fd, i*info_len+13) = sd->quest_log[i].time - qi->time;
WFIFOL(fd, i*info_len+17) = sd->quest_log[i].time;
WFIFOW(fd, i*info_len+21) = qi->objectives_count;
#endif
}
WFIFOSET(fd, len);
@ -15630,7 +15645,7 @@ void clif_quest_send_list(struct map_session_data * sd)
/// Sends list of all quest missions (ZC_ALL_QUEST_MISSION).
/// 02b2 <packet len>.W <num>.L { <quest id>.L <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 }*num
void clif_quest_send_mission(struct map_session_data * sd)
void clif_quest_send_mission(struct map_session_data *sd)
{
int fd = sd->fd;
int i, j;
@ -15642,18 +15657,18 @@ void clif_quest_send_mission(struct map_session_data * sd)
WFIFOW(fd, 2) = len;
WFIFOL(fd, 4) = sd->avail_quests;
for( i = 0; i < sd->avail_quests; i++ ) {
for (i = 0; i < sd->avail_quests; i++) {
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
WFIFOL(fd, i*104+8) = sd->quest_log[i].quest_id;
WFIFOL(fd, i*104+12) = sd->quest_log[i].time - qi->time;
WFIFOL(fd, i*104+16) = sd->quest_log[i].time;
WFIFOW(fd, i*104+20) = qi->num_objectives;
WFIFOW(fd, i*104+20) = qi->objectives_count;
for( j = 0 ; j < qi->num_objectives; j++ ) {
WFIFOL(fd, i*104+22+j*30) = qi->mob[j];
for (j = 0 ; j < qi->objectives_count; j++) {
WFIFOL(fd, i*104+22+j*30) = qi->objectives[j].mob;
WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j];
mob = mob_db(qi->mob[j]);
mob = mob_db(qi->objectives[j].mob);
memcpy(WFIFOP(fd, i*104+28+j*30), mob->jname, NAME_LENGTH);
}
}
@ -15664,7 +15679,7 @@ void clif_quest_send_mission(struct map_session_data * sd)
/// Notification about a new quest (ZC_ADD_QUEST).
/// 02b3 <quest id>.L <active>.B <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3
void clif_quest_add(struct map_session_data * sd, struct quest * qd)
void clif_quest_add(struct map_session_data *sd, struct quest *qd)
{
int fd = sd->fd;
int i;
@ -15674,15 +15689,15 @@ void clif_quest_add(struct map_session_data * sd, struct quest * qd)
WFIFOW(fd, 0) = 0x2b3;
WFIFOL(fd, 2) = qd->quest_id;
WFIFOB(fd, 6) = qd->state;
WFIFOB(fd, 7) = qi->time;
WFIFOB(fd, 7) = qd->time - qi->time;
WFIFOL(fd, 11) = qd->time;
WFIFOW(fd, 15) = qi->num_objectives;
WFIFOW(fd, 15) = qi->objectives_count;
for( i = 0; i < qi->num_objectives; i++ ) {
for (i = 0; i < qi->objectives_count; i++) {
struct mob_db *mob;
WFIFOL(fd, i*30+17) = qi->mob[i];
WFIFOL(fd, i*30+17) = qi->objectives[i].mob;
WFIFOW(fd, i*30+21) = qd->count[i];
mob = mob_db(qi->mob[i]);
mob = mob_db(qi->objectives[i].mob);
memcpy(WFIFOP(fd, i*30+23), mob->jname, NAME_LENGTH);
}
@ -15692,7 +15707,7 @@ void clif_quest_add(struct map_session_data * sd, struct quest * qd)
/// Notification about a quest being removed (ZC_DEL_QUEST).
/// 02b4 <quest id>.L
void clif_quest_delete(struct map_session_data * sd, int quest_id)
void clif_quest_delete(struct map_session_data *sd, int quest_id)
{
int fd = sd->fd;
@ -15705,22 +15720,22 @@ void clif_quest_delete(struct map_session_data * sd, int quest_id)
/// Notification of an update to the hunting mission counter (ZC_UPDATE_MISSION_HUNT).
/// 02b5 <packet len>.W <mobs>.W { <quest id>.L <mob id>.L <total count>.W <current count>.W }*3
void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd)
void clif_quest_update_objective(struct map_session_data *sd, struct quest *qd)
{
int fd = sd->fd;
int i;
struct quest_db *qi = quest_search(qd->quest_id);
int len = qi->num_objectives * 12 + 6;
int len = qi->objectives_count * 12 + 6;
WFIFOHEAD(fd, len);
WFIFOW(fd, 0) = 0x2b5;
WFIFOW(fd, 2) = len;
WFIFOW(fd, 4) = qi->num_objectives;
WFIFOW(fd, 4) = qi->objectives_count;
for( i = 0; i < qi->num_objectives; i++ ) {
for (i = 0; i < qi->objectives_count; i++) {
WFIFOL(fd, i*12+6) = qd->quest_id;
WFIFOL(fd, i*12+10) = qi->mob[i];
WFIFOW(fd, i*12+14) = qi->count[i];
WFIFOL(fd, i*12+10) = qi->objectives[i].mob;
WFIFOW(fd, i*12+14) = qi->objectives[i].count;
WFIFOW(fd, i*12+16) = qd->count[i];
}
@ -15730,7 +15745,7 @@ void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd
/// Request to change the state of a quest (CZ_ACTIVE_QUEST).
/// 02b6 <quest id>.L <active>.B
void clif_parse_questStateAck(int fd, struct map_session_data * sd)
void clif_parse_questStateAck(int fd, struct map_session_data *sd)
{
struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
quest_update_status(sd, RFIFOL(fd,info->pos[0]),
@ -15740,7 +15755,7 @@ void clif_parse_questStateAck(int fd, struct map_session_data * sd)
/// Notification about the change of a quest state (ZC_ACTIVE_QUEST).
/// 02b7 <quest id>.L <active>.B
void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active)
void clif_quest_update_status(struct map_session_data *sd, int quest_id, bool active)
{
int fd = sd->fd;

View File

@ -77,6 +77,7 @@ static char log_picktype2char(e_log_pick_type type)
case LOG_TYPE_BOUND_REMOVAL: return 'F'; // Removed bound items when guild/party is broken
case LOG_TYPE_ROULETTE: return 'Y'; // Roulette Lotter(Y)
case LOG_TYPE_MERGE_ITEM: return 'Z'; // Merged Item
case LOG_TYPE_QUEST: return 'Q'; // (Q)uest Item
}
// should not get here, fallback

View File

@ -46,6 +46,7 @@ typedef enum e_log_pick_type
LOG_TYPE_BOUND_REMOVAL = 0x080000,
LOG_TYPE_ROULETTE = 0x100000,
LOG_TYPE_MERGE_ITEM = 0x200000,
LOG_TYPE_QUEST = 0x400000,
// combinations
LOG_TYPE_LOOT = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
// all

View File

@ -80,6 +80,14 @@ struct s_mob_skill {
};
static DBMap *mob_skill_db; /// Monster skill temporary db. s_mob_skill -> mobid
struct mob_db *mobdb_exists(uint16 mob_id) {
struct mob_db *db = mob_db(mob_id);
if (db == mob_dummy)
return NULL;
return db;
}
static struct eri *item_drop_ers; //For loot drops delay structures.
static struct eri *item_drop_list_ers;

View File

@ -254,7 +254,8 @@ struct item_drop_list {
struct item_drop* item; // linked list of drops
};
struct mob_db* mob_db(int mob_id);
struct mob_db *mob_db(int mob_id);
struct mob_db *mobdb_exists(uint16 mob_id);
int mobdb_searchname(const char *str);
int mobdb_searchname_array(struct mob_db** data, int size, const char *str);
int mobdb_checkid(const int id);

View File

@ -5,18 +5,22 @@
#include "../common/socket.h"
#include "../common/malloc.h"
#include "../common/nullpo.h"
#include "../common/random.h"
#include "../common/showmsg.h"
#include "../common/strlib.h"
#include "itemdb.h"
#include "map.h"
#include "pc.h"
#include "party.h"
#include "quest.h"
#include "chrif.h"
#include "intif.h"
#include <stdlib.h>
static DBMap *questdb;
static void questdb_free_sub(struct quest_db *quest, bool free);
/**
* Searches a quest by ID.
@ -205,30 +209,30 @@ int quest_delete(TBL_PC *sd, int quest_id)
int quest_update_objective_sub(struct block_list *bl, va_list ap)
{
struct map_session_data *sd;
int mob, party;
int mob_id, party_id;
nullpo_ret(bl);
nullpo_ret(sd = (struct map_session_data *)bl);
party = va_arg(ap,int);
mob = va_arg(ap,int);
party_id = va_arg(ap,int);
mob_id = va_arg(ap,int);
if( !sd->avail_quests )
return 0;
if( sd->status.party_id != party )
if( sd->status.party_id != party_id )
return 0;
quest_update_objective(sd, mob);
quest_update_objective(sd, mob_id);
return 1;
}
/**
* Updates the quest objectives for a character after killing a monster.
* Updates the quest objectives for a character after killing a monster, including the handling of quest-granted drops.
* @param sd : Character's data
* @param mob_id : Monster ID
*/
void quest_update_objective(TBL_PC *sd, int mob)
void quest_update_objective(TBL_PC *sd, int mob_id)
{
int i, j;
@ -240,13 +244,46 @@ void quest_update_objective(TBL_PC *sd, int mob)
qi = quest_search(sd->quest_log[i].quest_id);
for( j = 0; j < qi->num_objectives; j++ ) {
if( qi->mob[j] == mob && sd->quest_log[i].count[j] < qi->count[j] ) {
for( j = 0; j < qi->objectives_count; j++ ) {
if( qi->objectives[j].mob == mob_id && sd->quest_log[i].count[j] < qi->objectives[j].count ) {
sd->quest_log[i].count[j]++;
sd->save_quest = true;
clif_quest_update_objective(sd, &sd->quest_log[i]);
}
}
// process quest-granted extra drop bonuses
for (j = 0; j < qi->dropitem_count; j++) {
struct quest_dropitem *dropitem = &qi->dropitem[j];
struct item item;
int temp;
if (dropitem->mob_id != 0 && dropitem->mob_id != mob_id)
continue;
// TODO: Should this be affected by server rates?
if (dropitem->rate < 10000 && rnd()%10000 >= dropitem->rate)
continue;
if (!itemdb_exists(dropitem->nameid))
continue;
memset(&item,0,sizeof(item));
item.nameid = dropitem->nameid;
item.identify = itemdb_isidentified(dropitem->nameid);
item.amount = dropitem->count;
#ifdef BOUND_ITEMS
item.bound = dropitem->bound;
#endif
if (dropitem->isGUID)
item.unique_id = pc_generate_unique_id(sd);
if ((temp = pc_additem(sd, &item, 1, LOG_TYPE_QUEST)) != 0) // Failed to obtain the item
clif_additem(sd, 0, 0, temp);
else if (dropitem->isAnnounced) {
char output[CHAT_SIZE_MAX];
sprintf(output, msg_txt(sd, 717), sd->status.name, itemdb_jname(item.nameid), StringBuf_Value(&qi->name));
intif_broadcast(output, strlen(output) + 1, BC_DEFAULT);
}
}
}
}
@ -273,7 +310,7 @@ int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status)
sd->save_quest = true;
if( status < Q_COMPLETE ) {
clif_quest_update_status(sd, quest_id, status == Q_ACTIVE);
clif_quest_update_status(sd, quest_id, status == Q_ACTIVE ? true : false);
return 0;
}
@ -326,8 +363,8 @@ int quest_check(TBL_PC *sd, int quest_id, enum quest_check_type type)
int j;
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
ARR_FIND(0, MAX_QUEST_OBJECTIVES, j, sd->quest_log[i].count[j] < qi->count[j]);
if( j == MAX_QUEST_OBJECTIVES )
ARR_FIND(0, qi->objectives_count, j, sd->quest_log[i].count[j] < qi->objectives[j].count);
if( j == qi->objectives_count )
return 2;
if( sd->quest_log[i].time < (unsigned int)time(NULL) )
return 1;
@ -342,11 +379,12 @@ int quest_check(TBL_PC *sd, int quest_id, enum quest_check_type type)
}
/**
* Loads quests from the quest db.
* Loads quests from the quest db.txt
* @return Number of loaded quests, or -1 if the file couldn't be read.
*/
int quest_read_db(void)
int quest_read_txtdb(void)
{
uint32 count = 0;
const char* dbsubpath[] = {
"",
DBIMPORT"/",
@ -356,71 +394,106 @@ int quest_read_db(void)
for (f = 0; f < ARRAYLENGTH(dbsubpath); f++) {
FILE *fp;
char line[1024];
uint32 count = 0;
uint32 ln = 0;
char filename[256];
sprintf(filename, "%s/%s%s", db_path, dbsubpath[f], "quest_db.txt");
if( (fp = fopen(filename, "r")) == NULL ) {
if ((fp = fopen(filename, "r")) == NULL) {
if (f == 0)
ShowError("Can't read %s\n", filename);
return -1;
}
while( fgets(line, sizeof(line), fp) ) {
struct quest_db *quest = NULL, entry;
char *str[9], *p, *np;
while(fgets(line, sizeof(line), fp)) {
struct quest_db *quest = NULL;
char *str[19], *p;
uint16 quest_id = 0;
uint8 i;
if( line[0] == '/' && line[1] == '/' )
++ln;
if (line[0] == '\0' || (line[0] == '/' && line[1] == '/'))
continue;
p = trim(line);
if (*p == '\0')
continue; // empty line
memset(str, 0, sizeof(str));
for(i = 0, p = line; i < 18 && p; i++) {
str[i] = p;
p = strchr(p,',');
if (p)
*p++ = 0;
}
if (str[0] == NULL)
continue;
if (i < 18) {
ShowError("quest_read_txtdb: Insufficient columns in line %d (%d of %d)\n", ln, i, 18);
continue;
}
for( i = 0, p = line; i < 8; i++ ) {
if( (np = strchr(p, ',')) != NULL ) {
str[i] = p;
*np = 0;
p = np + 1;
} else if( str[0] == NULL )
break;
else {
ShowError("quest_read_db: Insufficient columns in line %s\n", line);
quest_id = atoi(str[0]);
if (quest_id < 0 || quest_id >= INT_MAX) {
ShowError("quest_read_txtdb: Invalid quest ID '%d' in line '%s' (min: 0, max: %d.)\n", quest_id, ln, INT_MAX);
continue;
}
if (!(quest = (struct quest_db *)idb_get(questdb, quest_id)))
CREATE(quest, struct quest_db, 1);
else {
if (quest->objectives) {
aFree(quest->objectives);
quest->objectives = NULL;
quest->objectives_count = 0;
}
if (quest->dropitem) {
aFree(quest->dropitem);
quest->dropitem = NULL;
quest->dropitem_count = 0;
}
}
quest->time = atoi(str[1]);
for(i = 0; i < MAX_QUEST_OBJECTIVES; i++) {
uint16 mob_id = (uint16)atoi(str[2 * i + 2]);
if (!mob_id)
continue;
if (mobdb_exists(mob_id) == NULL) {
ShowWarning("quest_read_txtdb: Invalid monster as objective '%d' in line %d.\n", mob_id, ln);
continue;
}
RECREATE(quest->objectives, struct quest_objective, quest->objectives_count++);
quest->objectives[quest->objectives_count].mob = mob_id;
quest->objectives[quest->objectives_count].count = (uint16)atoi(str[2 * i + 3]);
quest->objectives_count++;
}
if( str[0] == NULL )
continue;
for(i = 0; i < MAX_QUEST_DROPS; i++) {
uint16 mob_id = (uint16)atoi(str[3 * i + (2 * MAX_QUEST_OBJECTIVES + 2)]), nameid = (uint16)atoi(str[3 * i + (2 * MAX_QUEST_OBJECTIVES + 3)]);
memset(&entry, 0, sizeof(struct quest_db));
entry.id = atoi(str[0]);
if( entry.id < 0 || entry.id >= INT_MAX ) {
ShowError("quest_read_db: Invalid quest ID '%d' in line '%s' (min: 0, max: %d.)\n", entry.id, line, INT_MAX);
continue;
}
if (!(quest = (struct quest_db *)idb_get(questdb, entry.id))) {
CREATE(quest, struct quest_db, 1);
}
entry.time = atoi(str[1]);
for( i = 0; i < MAX_QUEST_OBJECTIVES; i++ ) {
entry.mob[i] = (uint16)atoi(str[2 * i + 2]);
entry.count[i] = (uint16)atoi(str[2 * i + 3]);
if( !entry.mob[i] || !entry.count[i] )
break;
}
entry.num_objectives = i;
if (!nameid)
continue;
if (!itemdb_exists(nameid) || (mob_id && mobdb_exists(mob_id) == NULL)) {
ShowWarning("quest_read_txtdb: Invalid item reward '%d' (mob %d, optional) in line %d.\n", nameid, mob_id, ln);
continue;
}
RECREATE(quest->dropitem, struct quest_dropitem, quest->dropitem_count++);
quest->dropitem[quest->dropitem_count].mob_id = mob_id;
quest->dropitem[quest->dropitem_count].nameid = nameid;
quest->dropitem[quest->dropitem_count].rate = atoi(str[3 * i + (2 * MAX_QUEST_OBJECTIVES + 4)]);
quest->dropitem_count++;
}
//StringBuf_Init(&entry.name);
//StringBuf_Printf(&entry.name, "%s", str[7]);
//StringBuf_Printf(&entry.name, "%s", str[17]);
if (!quest->id) {
memcpy(quest, &entry, sizeof(entry));
quest->id = quest_id;
idb_put(questdb, quest->id, quest);
}
count++;
@ -430,7 +503,15 @@ int quest_read_db(void)
ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename);
}
return 0;
return count;
}
/**
* Loads Quest DB
*/
static void quest_read_db(void)
{
quest_read_txtdb();
}
/**
@ -471,17 +552,40 @@ int quest_reload_check_sub(struct map_session_data *sd, va_list ap)
return 1;
}
/**
* Clear quest single entry
* @param quest
* @param free Will free quest from memory
**/
static void questdb_free_sub(struct quest_db *quest, bool free)
{
if (quest->objectives) {
aFree(quest->objectives);
quest->objectives = NULL;
quest->objectives_count = 0;
}
if (quest->dropitem) {
aFree(quest->dropitem);
quest->dropitem = NULL;
quest->dropitem_count = 0;
}
if (&quest->name)
StringBuf_Destroy(&quest->name);
if (free)
aFree(quest);
}
/**
* Clears the quest database for shutdown or reload.
*/
static int questdb_free(DBKey key, DBData *data, va_list ap) {
static int questdb_free(DBKey key, DBData *data, va_list ap)
{
struct quest_db *quest = db_data2ptr(data);
if (!quest)
return 0;
//if (&quest->name)
// StringBuf_Destroy(&quest->name);
aFree(quest);
questdb_free_sub(quest, true);
return 1;
}

View File

@ -6,13 +6,29 @@
#define MAX_QUEST_DB (62238 + 1) // Highest quest ID + 1
struct quest_dropitem {
uint16 nameid;
uint16 count;
uint16 rate;
uint16 mob_id;
uint8 bound;
bool isAnnounced;
bool isGUID;
};
struct quest_objective {
uint16 mob;
uint16 count;
};
struct quest_db {
int id;
uint16 id;
unsigned int time;
uint16 mob[MAX_QUEST_OBJECTIVES];
uint16 count[MAX_QUEST_OBJECTIVES];
uint8 num_objectives;
//StringBuf name;
uint8 objectives_count;
struct quest_objective *objectives;
uint8 dropitem_count;
struct quest_dropitem *dropitem;
StringBuf name;
};
struct quest_db quest_dummy; ///< Dummy entry for invalid quest lookups
@ -30,7 +46,7 @@ int quest_add(TBL_PC * sd, int quest_id);
int quest_delete(TBL_PC * sd, int quest_id);
int quest_change(TBL_PC * sd, int qid1, int qid2);
int quest_update_objective_sub(struct block_list *bl, va_list ap);
void quest_update_objective(TBL_PC * sd, int mob);
void quest_update_objective(TBL_PC * sd, int mob_id);
int quest_update_status(TBL_PC * sd, int quest_id, enum quest_state status);
int quest_check(TBL_PC * sd, int quest_id, enum quest_check_type type);
void quest_clear(void);