Quest Log system improvement. (Hercules 6f55c00)

- Improved memory usage of the quest log system. (saves up to 75kB per online character).
- Fixed various issues with quest entries disappearing from characters without an apparent reason, or monster kill counters getting stuck - the issues were caused by a de-synchronization between the two parallel questlog arrays in map_session_data.
- Added some code documentation.
- Thanks to @shennetsind and @MishimaHaruna.
This commit is contained in:
aleos89 2014-04-03 09:39:34 -04:00
parent cc59315323
commit 2fe8140f96
14 changed files with 592 additions and 370 deletions

View File

@ -18,44 +18,77 @@
#include <string.h>
#include <stdlib.h>
//Load entire questlog for a character
int mapif_quests_fromsql(int char_id, struct quest questlog[])
{
int i;
/**
* Loads the entire questlog for a character.
*
* @param char_id Character ID
* @param count Pointer to return the number of found entries.
* @return Array of found entries. It has *count entries, and it is care of the
* caller to aFree() it afterwards.
*/
struct quest *mapif_quests_fromsql(int char_id, int *count) {
struct quest *questlog = NULL;
struct quest tmp_quest;
SqlStmt * stmt;
SqlStmt *stmt;
if( !count )
return NULL;
stmt = SqlStmt_Malloc(sql_handle);
if( stmt == NULL )
{
if( stmt == NULL ) {
SqlStmt_ShowDebug(stmt);
return 0;
*count = 0;
return NULL;
}
memset(&tmp_quest, 0, sizeof(struct quest));
if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `quest_id`, `state`, `time`, `count1`, `count2`, `count3` FROM `%s` WHERE `char_id`=? LIMIT %d", quest_db, MAX_QUEST_DB)
if( SQL_ERROR == SqlStmt_Prepare(stmt, "SELECT `quest_id`, `state`, `time`, `count1`, `count2`, `count3` FROM `%s` WHERE `char_id`=?", quest_db)
|| SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0)
|| SQL_ERROR == SqlStmt_Execute(stmt)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &tmp_quest.quest_id, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &tmp_quest.state, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_UINT, &tmp_quest.time, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_INT, &tmp_quest.count[0], 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_INT, &tmp_quest.count[1], 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_INT, &tmp_quest.count[2], 0, NULL, NULL) )
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &tmp_quest.quest_id, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_INT, &tmp_quest.state, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_UINT, &tmp_quest.time, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_INT, &tmp_quest.count[0], 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_INT, &tmp_quest.count[1], 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_INT, &tmp_quest.count[2], 0, NULL, NULL)
) {
SqlStmt_ShowDebug(stmt);
SqlStmt_Free(stmt);
*count = 0;
return NULL;
}
for( i = 0; i < MAX_QUEST_DB && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i )
memcpy(&questlog[i], &tmp_quest, sizeof(tmp_quest));
*count = (int)SqlStmt_NumRows(stmt);
if( *count > 0 ) {
int i = 0;
questlog = (struct quest *)aCalloc(*count, sizeof(struct quest));
while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) {
if( i >= *count ) //Sanity check, should never happen
break;
memcpy(&questlog[i++], &tmp_quest, sizeof(tmp_quest));
}
if( i < *count ) {
//Should never happen. Compact array
*count = i;
questlog = (struct quest *)aRealloc(questlog, sizeof(struct quest) * i);
}
}
SqlStmt_Free(stmt);
return i;
return questlog;
}
//Delete a quest
bool mapif_quest_delete(int char_id, int quest_id)
{
if ( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `quest_id` = '%d' AND `char_id` = '%d'", quest_db, quest_id, char_id) )
/**
* Deletes a quest from a character's questlog.
*
* @param char_id Character ID
* @param quest_id Quest ID
* @return false in case of errors, true otherwise
*/
bool mapif_quest_delete(int char_id, int quest_id) {
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `quest_id` = '%d' AND `char_id` = '%d'", quest_db, quest_id, char_id) )
{
Sql_ShowDebug(sql_handle);
return false;
@ -64,10 +97,15 @@ bool mapif_quest_delete(int char_id, int quest_id)
return true;
}
//Add a quest to a questlog
bool mapif_quest_add(int char_id, struct quest qd)
{
if ( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`quest_id`, `char_id`, `state`, `time`, `count1`, `count2`, `count3`) VALUES ('%d', '%d', '%d','%d', '%d', '%d', '%d')", quest_db, qd.quest_id, char_id, qd.state, qd.time, qd.count[0], qd.count[1], qd.count[2]) )
/**
* Adds a quest to a character's questlog.
*
* @param char_id Character ID
* @param qd Quest data
* @return false in case of errors, true otherwise
*/
bool mapif_quest_add(int char_id, struct quest qd) {
if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`quest_id`, `char_id`, `state`, `time`, `count1`, `count2`, `count3`) VALUES ('%d', '%d', '%d','%d', '%d', '%d', '%d')", quest_db, qd.quest_id, char_id, qd.state, qd.time, qd.count[0], qd.count[1], qd.count[2]) )
{
Sql_ShowDebug(sql_handle);
return false;
@ -76,10 +114,15 @@ bool mapif_quest_add(int char_id, struct quest qd)
return true;
}
//Update a questlog
bool mapif_quest_update(int char_id, struct quest qd)
{
if ( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `state`='%d', `count1`='%d', `count2`='%d', `count3`='%d' WHERE `quest_id` = '%d' AND `char_id` = '%d'", quest_db, qd.state, qd.count[0], qd.count[1], qd.count[2], qd.quest_id, char_id) )
/**
* Updates a quest in a character's questlog.
*
* @param char_id Character ID
* @param qd Quest data
* @return false in case of errors, true otherwise
*/
bool mapif_quest_update(int char_id, struct quest qd) {
if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `state`='%d', `count1`='%d', `count2`='%d', `count3`='%d' WHERE `quest_id` = '%d' AND `char_id` = '%d'", quest_db, qd.state, qd.count[0], qd.count[1], qd.count[2], qd.quest_id, char_id) )
{
Sql_ShowDebug(sql_handle);
return false;
@ -88,93 +131,95 @@ bool mapif_quest_update(int char_id, struct quest qd)
return true;
}
//Save quests
int mapif_parse_quest_save(int fd)
{
int i, j, k, num2, num1 = (RFIFOW(fd,2)-8)/sizeof(struct quest);
/**
* Handles the save request from mapserver for a character's questlog.
*
* Received quests are saved, and an ack is sent back to the map server.
*
* @see inter_parse_frommap
*/
int mapif_parse_quest_save(int fd) {
int i, j, k, old_n, new_n = (RFIFOW(fd,2) - 8) / sizeof(struct quest);
int char_id = RFIFOL(fd,4);
struct quest qd1[MAX_QUEST_DB],qd2[MAX_QUEST_DB];
struct quest *old_qd = NULL, *new_qd = NULL;
bool success = true;
memset(qd1, 0, sizeof(qd1));
memset(qd2, 0, sizeof(qd2));
if( num1 ) memcpy(&qd1, RFIFOP(fd,8), RFIFOW(fd,2)-8);
num2 = mapif_quests_fromsql(char_id, qd2);
if( new_n > 0 )
new_qd = (struct quest*)RFIFOP(fd,8);
for( i = 0; i < num1; i++ )
{
ARR_FIND( 0, num2, j, qd1[i].quest_id == qd2[j].quest_id );
if( j < num2 ) // Update existed quests
{ // Only states and counts are changable.
ARR_FIND( 0, MAX_QUEST_OBJECTIVES, k, qd1[i].count[k] != qd2[j].count[k] );
if( k != MAX_QUEST_OBJECTIVES || qd1[i].state != qd2[j].state )
success &= mapif_quest_update(char_id, qd1[i]);
old_qd = mapif_quests_fromsql(char_id, &old_n);
for( i = 0; i < new_n; i++ ) {
ARR_FIND(0, old_n, j, new_qd[i].quest_id == old_qd[j].quest_id);
if( j < old_n ) { //Update existing quests
//Only states and counts are changable.
ARR_FIND(0, MAX_QUEST_OBJECTIVES, k, new_qd[i].count[k] != old_qd[j].count[k]);
if( k != MAX_QUEST_OBJECTIVES || new_qd[i].state != old_qd[j].state )
success &= mapif_quest_update(char_id, new_qd[i]);
if( j < (--num2) )
{
memmove(&qd2[j],&qd2[j+1],sizeof(struct quest)*(num2-j));
memset(&qd2[num2], 0, sizeof(struct quest));
if( j < (--old_n) ) {
//Compact array
memmove(&old_qd[j], &old_qd[j + 1], sizeof(struct quest) * (old_n - j));
memset(&old_qd[old_n], 0, sizeof(struct quest));
}
}
else // Add new quests
success &= mapif_quest_add(char_id, qd1[i]);
} else //Add new quests
success &= mapif_quest_add(char_id, new_qd[i]);
}
for( i = 0; i < num2; i++ ) // Quests not in qd1 but in qd2 are to be erased.
success &= mapif_quest_delete(char_id, qd2[i].quest_id);
for( i = 0; i < old_n; i++ ) //Quests not in new_qd but in old_qd are to be erased.
success &= mapif_quest_delete(char_id, old_qd[i].quest_id);
if( old_qd )
aFree(old_qd);
//Send ack
WFIFOHEAD(fd,7);
WFIFOW(fd,0) = 0x3861;
WFIFOL(fd,2) = char_id;
WFIFOB(fd,6) = success?1:0;
WFIFOB(fd,6) = success ? 1 : 0;
WFIFOSET(fd,7);
return 0;
}
//Send questlog to map server
int mapif_parse_quest_load(int fd)
{
/**
* Sends questlog to the map server
*
* NOTE: Completed quests (state == Q_COMPLETE) are guaranteed to be sent last
* and the map server relies on this behavior (once the first Q_COMPLETE quest,
* all of them are considered to be Q_COMPLETE)
*
* @see inter_parse_frommap
*/
int mapif_parse_quest_load(int fd) {
int char_id = RFIFOL(fd,2);
struct quest tmp_questlog[MAX_QUEST_DB];
int num_quests, i, num_complete = 0;
int complete[MAX_QUEST_DB];
struct quest *tmp_questlog = NULL;
int num_quests;
memset(tmp_questlog, 0, sizeof(tmp_questlog));
memset(complete, 0, sizeof(complete));
tmp_questlog = mapif_quests_fromsql(char_id, &num_quests);
num_quests = mapif_quests_fromsql(char_id, tmp_questlog);
WFIFOHEAD(fd,num_quests*sizeof(struct quest)+8);
WFIFOHEAD(fd,num_quests * sizeof(struct quest) + 8);
WFIFOW(fd,0) = 0x3860;
WFIFOW(fd,2) = num_quests*sizeof(struct quest)+8;
WFIFOW(fd,2) = num_quests * sizeof(struct quest) + 8;
WFIFOL(fd,4) = char_id;
//Active and inactive quests
for( i = 0; i < num_quests; i++ )
{
if( tmp_questlog[i].state == Q_COMPLETE )
{
complete[num_complete++] = i;
continue;
}
memcpy(WFIFOP(fd,(i-num_complete)*sizeof(struct quest)+8), &tmp_questlog[i], sizeof(struct quest));
}
if( num_quests > 0 )
memcpy(WFIFOP(fd,8), tmp_questlog, sizeof(struct quest) * num_quests);
// Completed quests
for( i = num_quests - num_complete; i < num_quests; i++ )
memcpy(WFIFOP(fd,i*sizeof(struct quest)+8), &tmp_questlog[complete[i-num_quests+num_complete]], sizeof(struct quest));
WFIFOSET(fd,num_quests * sizeof(struct quest) + 8);
WFIFOSET(fd,num_quests*sizeof(struct quest)+8);
if( tmp_questlog )
aFree(tmp_questlog);
return 0;
}
int inter_quest_parse_frommap(int fd)
{
switch(RFIFOW(fd,0))
{
/**
* Parses questlog related packets from the map server.
*
* @see inter_parse_frommap
*/
int inter_quest_parse_frommap(int fd) {
switch(RFIFOW(fd,0)) {
case 0x3060: mapif_parse_quest_load(fd); break;
case 0x3061: mapif_parse_quest_save(fd); break;
default:

View File

@ -4,9 +4,6 @@
#ifndef _QUEST_H_
#define _QUEST_H_
/*questlog system*/
struct quest;
int inter_quest_parse_frommap(int fd);
#endif

View File

@ -72,7 +72,6 @@
#define MAX_GUILDSKILL 15 ///Increased max guild skills because of new skills [Sara-chan]
#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_DB 2800 ///Max quests that the server will load
#define MAX_QUEST_OBJECTIVES 3 ///Max quest objectives for a quest
// for produce
@ -157,15 +156,19 @@ enum item_types {
IT_MAX
};
// Questlog states
enum quest_state {
Q_INACTIVE, ///< Inactive quest (the user can toggle between active and inactive quests)
Q_ACTIVE, ///< Active quest
Q_COMPLETE, ///< Completed quest
};
//Questlog system [Kevin] [Inkfish]
typedef enum quest_state { Q_INACTIVE, Q_ACTIVE, Q_COMPLETE } quest_state;
/// Questlog entry
struct quest {
int quest_id;
unsigned int time;
int count[MAX_QUEST_OBJECTIVES];
quest_state state;
int quest_id; ///< Quest ID
unsigned int time; ///< Expiration time
int count[MAX_QUEST_OBJECTIVES]; ///< Kill counters of each quest objective
enum quest_state state; ///< Current quest state
};
struct item {

View File

@ -11042,19 +11042,19 @@ void clif_parse_GetItemFromCart(int fd,struct map_session_data *sd)
}
/// Request to remove cart/falcon/peco/dragon (CZ_REQ_CARTOFF).
/// Request to remove cart/falcon/peco/dragon/mado (CZ_REQ_CARTOFF).
/// 012a
void clif_parse_RemoveOption(int fd,struct map_session_data *sd)
{
if( !(sd->sc.option&(OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR))
#ifdef NEW_CARTS
&& sd->sc.data[SC_PUSH_CART] ){
&& sd->sc.data[SC_PUSH_CART] )
pc_setcart(sd,0);
#else
){
)
pc_setoption(sd,sd->sc.option&~OPTION_CART);
#endif
} else // priority to remove this option before we can clear cart
else // priority to remove this option before we can clear cart
pc_setoption(sd,sd->sc.option&~(OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR));
}
@ -11095,7 +11095,7 @@ void clif_parse_ChangeCart(int fd,struct map_session_data *sd)
/// status id:
/// SP_STR ~ SP_LUK
/// amount:
/// Old client send always 1 for this, even when using /str+ and the like.
/// Old clients always send 1 for this, even when using /str+ and the like.
/// Newer clients (2013-12-23 and newer) send the correct amount.
void clif_parse_StatusUp(int fd,struct map_session_data *sd)
{
@ -15225,16 +15225,18 @@ void clif_quest_send_mission(struct map_session_data * sd)
WFIFOL(fd, 4) = sd->avail_quests;
for( i = 0; i < sd->avail_quests; i++ ) {
WFIFOL(fd, i*104+8) = sd->quest_log[i].quest_id;
WFIFOL(fd, i*104+12) = sd->quest_log[i].time - quest_db[sd->quest_index[i]].time;
WFIFOL(fd, i*104+16) = sd->quest_log[i].time;
WFIFOW(fd, i*104+20) = quest_db[sd->quest_index[i]].num_objectives;
struct quest_db *qi = quest_db(sd->quest_log[i].quest_id);
for( j = 0 ; j < quest_db[sd->quest_index[i]].num_objectives; j++ ) {
WFIFOL(fd, i*104+22+j*30) = quest_db[sd->quest_index[i]].mob[j];
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;
for( j = 0 ; j < qi->num_objectives; j++ ) {
WFIFOL(fd, i*104+22+j*30) = qi->mob[j];
WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j];
mob = mob_db(quest_db[sd->quest_index[i]].mob[j]);
memcpy(WFIFOP(fd, i*104+28+j*30), mob?mob->jname:"NULL", NAME_LENGTH);
mob = mob_db(qi->mob[j]);
memcpy(WFIFOP(fd, i*104+28+j*30), mob->jname, NAME_LENGTH);
}
}
@ -15244,25 +15246,26 @@ 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, int index)
void clif_quest_add(struct map_session_data * sd, struct quest * qd)
{
int fd = sd->fd;
int i;
struct mob_db *mob;
struct quest_db *qi = quest_db(qd->quest_id);
WFIFOHEAD(fd, packet_len(0x2b3));
WFIFOW(fd, 0) = 0x2b3;
WFIFOL(fd, 2) = qd->quest_id;
WFIFOB(fd, 6) = qd->state;
WFIFOB(fd, 7) = qd->time - quest_db[index].time;
WFIFOB(fd, 7) = qi->time;
WFIFOL(fd, 11) = qd->time;
WFIFOW(fd, 15) = quest_db[index].num_objectives;
WFIFOW(fd, 15) = qi->num_objectives;
for( i = 0; i < quest_db[index].num_objectives; i++ ) {
WFIFOL(fd, i*30+17) = quest_db[index].mob[i];
for( i = 0; i < qi->num_objectives; i++ ) {
WFIFOL(fd, i*30+17) = qi->mob[i];
WFIFOW(fd, i*30+21) = qd->count[i];
mob = mob_db(quest_db[index].mob[i]);
memcpy(WFIFOP(fd, i*30+23), mob?mob->jname:"NULL", NAME_LENGTH);
mob = mob_db(qi->mob[i]);
memcpy(WFIFOP(fd, i*30+23), mob->jname, NAME_LENGTH);
}
WFIFOSET(fd, packet_len(0x2b3));
@ -15284,21 +15287,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, int index)
void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd)
{
int fd = sd->fd;
int i;
int len = quest_db[index].num_objectives*12+6;
struct quest_db *qi = quest_db(qd->quest_id);
int len = qi->num_objectives * 12 + 6;
WFIFOHEAD(fd, len);
WFIFOW(fd, 0) = 0x2b5;
WFIFOW(fd, 2) = len;
WFIFOW(fd, 4) = quest_db[index].num_objectives;
WFIFOW(fd, 4) = qi->num_objectives;
for( i = 0; i < quest_db[index].num_objectives; i++ ) {
for( i = 0; i < qi->num_objectives; i++ ) {
WFIFOL(fd, i*12+6) = qd->quest_id;
WFIFOL(fd, i*12+10) = quest_db[index].mob[i];
WFIFOW(fd, i*12+14) = quest_db[index].count[i];
WFIFOL(fd, i*12+10) = qi->mob[i];
WFIFOW(fd, i*12+14) = qi->count[i];
WFIFOW(fd, i*12+16) = qd->count[i];
}
@ -15308,7 +15312,8 @@ 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]),
RFIFOB(fd,info->pos[1])?Q_ACTIVE:Q_INACTIVE);

View File

@ -47,7 +47,7 @@ enum e_packet_ack {
ZC_PERSONAL_INFOMATION,
ZC_PERSONAL_INFOMATION_CHN,
ZC_CLEAR_DIALOG,
//add otehr here
//add other here
MAX_ACK_FUNC //auto upd len
};
@ -692,10 +692,10 @@ void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id);
//quest system [Kevin] [Inkfish]
void clif_quest_send_list(struct map_session_data * sd);
void clif_quest_send_mission(struct map_session_data * sd);
void clif_quest_add(struct map_session_data * sd, struct quest * qd, int index);
void clif_quest_add(struct map_session_data * sd, struct quest * qd);
void clif_quest_delete(struct map_session_data * sd, int quest_id);
void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active);
void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int index);
void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd);
void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, short state, short color);
void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest);

View File

@ -1408,54 +1408,73 @@ QUESTLOG SYSTEM FUNCTIONS
***************************************/
int intif_request_questlog(TBL_PC *sd)
/**
* Requests a character's quest log entries to the inter server.
*
* @param sd Character's data
*/
void intif_request_questlog(TBL_PC *sd)
{
WFIFOHEAD(inter_fd,6);
WFIFOW(inter_fd,0) = 0x3060;
WFIFOL(inter_fd,2) = sd->status.char_id;
WFIFOSET(inter_fd,6);
return 0;
}
int intif_parse_questlog(int fd)
void intif_parse_questlog(int fd)
{
int char_id = RFIFOL(fd, 4);
int i;
TBL_PC * sd = map_charid2sd(char_id);
int char_id = RFIFOL(fd,4), num_received = (RFIFOW(fd,2) - 8) / sizeof(struct quest);
TBL_PC *sd = map_charid2sd(char_id);
//User not online anymore
if(!sd)
return -1;
if(!sd) // User not online anymore
return;
sd->avail_quests = sd->num_quests = (RFIFOW(fd, 2)-8)/sizeof(struct quest);
sd->num_quests = sd->avail_quests = 0;
memset(&sd->quest_log, 0, sizeof(sd->quest_log));
for( i = 0; i < sd->num_quests; i++ )
{
memcpy(&sd->quest_log[i], RFIFOP(fd, i*sizeof(struct quest)+8), sizeof(struct quest));
sd->quest_index[i] = quest_search_db(sd->quest_log[i].quest_id);
if( sd->quest_index[i] < 0 )
{
ShowError("intif_parse_questlog: quest %d not found in DB.\n",sd->quest_log[i].quest_id);
sd->avail_quests--;
sd->num_quests--;
i--;
continue;
if(num_received == 0) {
if(sd->quest_log) {
aFree(sd->quest_log);
sd->quest_log = NULL;
}
} else {
struct quest *received = (struct quest *)RFIFOP(fd,8);
int i, k = num_received;
if( sd->quest_log[i].state == Q_COMPLETE )
sd->avail_quests--;
if(sd->quest_log)
RECREATE(sd->quest_log, struct quest, num_received);
else
CREATE(sd->quest_log, struct quest, num_received);
for(i = 0; i < num_received; i++) {
if(quest_db(received[i].quest_id) == &quest_dummy) {
ShowError("intif_parse_QuestLog: quest %d not found in DB.\n", received[i].quest_id);
continue;
}
if(received[i].state != Q_COMPLETE) // Insert at the beginning
memcpy(&sd->quest_log[sd->avail_quests++], &received[i], sizeof(struct quest));
else // Insert at the end
memcpy(&sd->quest_log[--k], &received[i], sizeof(struct quest));
sd->num_quests++;
}
if(sd->avail_quests < k) {
// sd->avail_quests and k didn't meet in the middle: some entries were skipped
if(k < num_received) // Move the entries at the end to fill the gap
memmove(&sd->quest_log[k], &sd->quest_log[sd->avail_quests], sizeof(struct quest) * (num_received - k));
sd->quest_log = aRealloc(sd->quest_log, sizeof(struct quest) * sd->num_quests);
}
}
quest_pc_login(sd);
return 0;
}
int intif_parse_questsave(int fd)
/**
* Parses the quest log save ack for a character from the inter server.
*
* Received in reply to the requests made by intif_quest_save.
*
* @see intif_parse
*/
void intif_parse_questsave(int fd)
{
int cid = RFIFOL(fd, 2);
TBL_PC *sd = map_id2sd(cid);
@ -1464,25 +1483,27 @@ int intif_parse_questsave(int fd)
ShowError("intif_parse_questsave: Failed to save quest(s) for character %d!\n", cid);
else if( sd )
sd->save_quest = false;
return 0;
}
/**
* Requests to the inter server to save a character's quest log entries.
*
* @param sd Character's data
* @return 0 in case of success, nonzero otherwise
*/
int intif_quest_save(TBL_PC *sd)
{
int len;
int len = sizeof(struct quest) * sd->num_quests + 8;
if(CheckForCharServer())
return 0;
len = sizeof(struct quest)*sd->num_quests + 8;
return 1;
WFIFOHEAD(inter_fd, len);
WFIFOW(inter_fd,0) = 0x3061;
WFIFOW(inter_fd,2) = len;
WFIFOL(inter_fd,4) = sd->status.char_id;
if( sd->num_quests )
memcpy(WFIFOP(inter_fd,8), &sd->quest_log, sizeof(struct quest)*sd->num_quests);
memcpy(WFIFOP(inter_fd,8), sd->quest_log, sizeof(struct quest)*sd->num_quests);
WFIFOSET(inter_fd, len);
return 0;

View File

@ -80,7 +80,7 @@ int intif_homunculus_requestsave(int account_id, struct s_homunculus* sh);
int intif_homunculus_requestdelete(int homun_id);
/******QUEST SYTEM*******/
int intif_request_questlog(struct map_session_data * sd);
void intif_request_questlog(struct map_session_data * sd);
int intif_quest_save(struct map_session_data * sd);
// MERCENARY SYSTEM

View File

@ -3832,6 +3832,7 @@ void do_final(void)
do_final_chrif();
do_final_clif();
do_final_npc();
do_final_quest();
do_final_script();
do_final_instance();
do_final_itemdb();

View File

@ -1101,6 +1101,11 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim
for( i = 0; i < 3; i++ )
sd->hate_mob[i] = -1;
sd->quest_log = NULL;
sd->num_quests = 0;
sd->avail_quests = 0;
sd->save_quest = false;
//warp player
if ((i=pc_setpos(sd,sd->status.last_point.map, sd->status.last_point.x, sd->status.last_point.y, CLR_OUTSIGHT)) != 0) {
ShowError ("Last_point_map %s - id %d not found (error code %d)\n", mapindex_id2name(sd->status.last_point.map), sd->status.last_point.map, i);
@ -6275,7 +6280,7 @@ int pc_maxparameterincrease(struct map_session_data* sd, int type)
}
final_val--;
return final_val > base ? final_val-base : 0;
return (final_val > base ? final_val-base : 0);
}
/**
@ -6309,7 +6314,7 @@ bool pc_statusup(struct map_session_data* sd, int type, int increase)
clif_statusupack(sd, type, 0, 0);
return false;
}
// check status points
needed_points = pc_need_status_point(sd, type, increase);
if (needed_points < 0 || needed_points > sd->status.status_point) { // Sanity check

View File

@ -507,12 +507,11 @@ struct map_session_data {
bool changed; // if true, should sync with charserver on next mailbox request
} mail;
//Quest log system [Kevin] [Inkfish]
int num_quests;
int avail_quests;
int quest_index[MAX_QUEST_DB];
struct quest quest_log[MAX_QUEST_DB];
bool save_quest;
//Quest log system
int num_quests; ///< Number of entries in quest_log
int avail_quests; ///< Number of Q_ACTIVE and Q_INACTIVE entries in quest log (index of the first Q_COMPLETE entry)
struct quest *quest_log; ///< Quest log entries (note: Q_COMPLETE quests follow the first <avail_quests>th enties
bool save_quest; ///< Whether the quest_log entries were modified and are waitin to be saved
// temporary debug [flaviojs]
const char* debug_file;

View File

@ -32,80 +32,84 @@
#include <stdarg.h>
#include <time.h>
struct s_quest_db quest_db[MAX_QUEST_DB];
int quest_search_db(int quest_id)
{
int i;
ARR_FIND(0, MAX_QUEST_DB,i,quest_id == quest_db[i].id);
if( i == MAX_QUEST_DB )
return -1;
return i;
/**
* Searches a quest by ID.
*
* @param quest_id ID to lookup
* @return Quest entry (equals to &quest_dummy if the ID is invalid)
*/
struct quest_db *quest_db(int quest_id) {
if( quest_id < 0 || quest_id > MAX_QUEST_DB || quest_db_data[quest_id] == NULL )
return &quest_dummy;
return quest_db_data[quest_id];
}
//Send quest info on login
int quest_pc_login(TBL_PC * sd)
{
/**
* Sends quest info to the player on login.
*
* @param sd Player's data
* @return 0 in case of success, nonzero otherwise (i.e. the player has no quests)
*/
int quest_pc_login(TBL_PC *sd) {
int i;
if(sd->avail_quests == 0)
if( sd->avail_quests == 0 )
return 1;
clif_quest_send_list(sd);
clif_quest_send_mission(sd);
for( i = 0; i < sd->avail_quests; i++ ){
clif_quest_update_objective(sd, &sd->quest_log[i], sd->quest_index[i]);
}
//@TODO[Haru]: Is this necessary? Does quest_send_mission not take care of this?
for( i = 0; i < sd->avail_quests; i++ )
clif_quest_update_objective(sd, &sd->quest_log[i]);
return 0;
}
int quest_add(TBL_PC * sd, int quest_id)
{
/**
* Adds a quest to the player's list.
*
* New quest will be added as Q_ACTIVE.
*
* @param sd Player's data
* @param quest_id ID of the quest to add.
* @return 0 in case of success, nonzero otherwise
*/
int quest_add(TBL_PC *sd, int quest_id) {
int n;
struct quest_db *qi = quest_db(quest_id);
int i, j;
if( sd->num_quests >= MAX_QUEST_DB )
{
ShowError("quest_add: Character %d has got all the quests.(max quests: %d)\n", sd->status.char_id, MAX_QUEST_DB);
return 1;
}
if( quest_check(sd, quest_id, HAVEQUEST) >= 0 )
{
ShowError("quest_add: Character %d already has quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
if( (j = quest_search_db(quest_id)) < 0 )
{
if( qi == &quest_dummy ) {
ShowError("quest_add: quest %d not found in DB.\n", quest_id);
return -1;
}
i = sd->avail_quests;
memmove(&sd->quest_log[i+1], &sd->quest_log[i], sizeof(struct quest)*(sd->num_quests-sd->avail_quests));
memmove(sd->quest_index+i+1, sd->quest_index+i, sizeof(int)*(sd->num_quests-sd->avail_quests));
if( quest_check(sd, quest_id, HAVEQUEST) >= 0 ) {
ShowError("quest_add: Character %d already has quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
memset(&sd->quest_log[i], 0, sizeof(struct quest));
sd->quest_log[i].quest_id = quest_db[j].id;
if( quest_db[j].time )
sd->quest_log[i].time = (unsigned int)(time(NULL) + quest_db[j].time);
sd->quest_log[i].state = Q_ACTIVE;
n = sd->avail_quests; //Insertion point
sd->quest_index[i] = j;
sd->num_quests++;
sd->avail_quests++;
RECREATE(sd->quest_log, struct quest, sd->num_quests);
//The character has some completed quests, make room before them so that they will stay at the end of the array
if( sd->avail_quests != sd->num_quests )
memmove(&sd->quest_log[n + 1], &sd->quest_log[n], sizeof(struct quest) * (sd->num_quests-sd->avail_quests));
memset(&sd->quest_log[n], 0, sizeof(struct quest));
sd->quest_log[n].quest_id = qi->id;
if( qi->time )
sd->quest_log[n].time = (unsigned int)(time(NULL) + qi->time);
sd->quest_log[n].state = Q_ACTIVE;
sd->save_quest = true;
clif_quest_add(sd, &sd->quest_log[i], sd->quest_index[i]);
clif_quest_update_objective(sd, &sd->quest_log[i], sd->quest_index[i]);
clif_quest_add(sd, &sd->quest_log[n]);
clif_quest_update_objective(sd, &sd->quest_log[n]);
if( save_settings&64 )
chrif_save(sd,0);
@ -113,49 +117,50 @@ int quest_add(TBL_PC * sd, int quest_id)
return 0;
}
int quest_change(TBL_PC * sd, int qid1, int qid2)
{
/**
* Replaces a quest in a player's list with another one.
*
* @param sd Player's data
* @param qid1 Current quest to replace
* @param qid2 New quest to add
* @return 0 in case of success, nonzero otherwise
*/
int quest_change(TBL_PC *sd, int qid1, int qid2) {
int i;
struct quest_db *qi = quest_db(qid2);
int i, j;
if( qi == &quest_dummy ) {
ShowError("quest_change: quest %d not found in DB.\n", qid2);
return -1;
}
if( quest_check(sd, qid2, HAVEQUEST) >= 0 )
{
if( quest_check(sd, qid2, HAVEQUEST) >= 0 ) {
ShowError("quest_change: Character %d already has quest %d.\n", sd->status.char_id, qid2);
return -1;
}
if( quest_check(sd, qid1, HAVEQUEST) < 0 )
{
if( quest_check(sd, qid1, HAVEQUEST) < 0 ) {
ShowError("quest_change: Character %d doesn't have quest %d.\n", sd->status.char_id, qid1);
return -1;
}
if( (j = quest_search_db(qid2)) < 0 )
{
ShowError("quest_change: quest %d not found in DB.\n",qid2);
return -1;
}
ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == qid1);
if(i == sd->avail_quests)
{
ShowError("quest_change: Character %d has completed quests %d.\n", sd->status.char_id, qid1);
if( i == sd->avail_quests ) {
ShowError("quest_change: Character %d has completed quest %d.\n", sd->status.char_id, qid1);
return -1;
}
memset(&sd->quest_log[i], 0, sizeof(struct quest));
sd->quest_log[i].quest_id = quest_db[j].id;
if( quest_db[j].time )
sd->quest_log[i].time = (unsigned int)(time(NULL) + quest_db[j].time);
sd->quest_log[i].quest_id = qi->id;
if( qi->time )
sd->quest_log[i].time = (unsigned int)(time(NULL) + qi->time);
sd->quest_log[i].state = Q_ACTIVE;
sd->quest_index[i] = j;
sd->save_quest = true;
clif_quest_delete(sd, qid1);
clif_quest_add(sd, &sd->quest_log[i], sd->quest_index[i]);
clif_quest_update_objective(sd, &sd->quest_log[i], sd->quest_index[i]);
clif_quest_add(sd, &sd->quest_log[i]);
clif_quest_update_objective(sd, &sd->quest_log[i]);
if( save_settings&64 )
chrif_save(sd,0);
@ -163,27 +168,31 @@ int quest_change(TBL_PC * sd, int qid1, int qid2)
return 0;
}
int quest_delete(TBL_PC * sd, int quest_id)
{
/**
* Removes a quest from a player's list
*
* @param sd Player's data
* @param quest_id ID of the quest to remove
* @return 0 in case of success, nonzero otherwise
*/
int quest_delete(TBL_PC *sd, int quest_id) {
int i;
//Search for quest
ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].quest_id == quest_id);
if(i == sd->num_quests)
{
if( i == sd->num_quests ) {
ShowError("quest_delete: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
if( sd->quest_log[i].state != Q_COMPLETE )
sd->avail_quests--;
if( sd->num_quests-- < MAX_QUEST_DB && sd->quest_log[i+1].quest_id )
{
memmove(&sd->quest_log[i], &sd->quest_log[i+1], sizeof(struct quest)*(sd->num_quests-i));
memmove(sd->quest_index+i, sd->quest_index+i+1, sizeof(int)*(sd->num_quests-i));
}
memset(&sd->quest_log[sd->num_quests], 0, sizeof(struct quest));
sd->quest_index[sd->num_quests] = 0;
if( i < --sd->num_quests ) //Compact the array
memmove(&sd->quest_log[i], &sd->quest_log[i + 1], sizeof(struct quest) * (sd->num_quests - i));
if( sd->num_quests == 0 ) {
aFree(sd->quest_log);
sd->quest_log = NULL;
} else
RECREATE(sd->quest_log, struct quest, sd->num_quests);
sd->save_quest = true;
clif_quest_delete(sd, quest_id);
@ -194,9 +203,16 @@ int quest_delete(TBL_PC * sd, int quest_id)
return 0;
}
int quest_update_objective_sub(struct block_list *bl, va_list ap)
{
struct map_session_data * sd;
/**
* Map iterator subroutine to update quest objectives for a party after killing a monster.
*
* @see map_foreachinrange
* @param ap Argument list, expecting:
* int Party ID
* int Mob ID
*/
int quest_update_objective_sub(struct block_list *bl, va_list ap) {
struct map_session_data *sd;
int mob, party;
nullpo_ret(bl);
@ -215,29 +231,48 @@ int quest_update_objective_sub(struct block_list *bl, va_list ap)
return 1;
}
void quest_update_objective(TBL_PC * sd, int mob) {
int i,j;
/**
* Updates the quest objectives for a character after killing a monster.
*
* @param sd Character's data
* @param mob_id Monster ID
*/
void quest_update_objective(TBL_PC *sd, int mob) {
int i, j;
for( i = 0; i < sd->avail_quests; i++ ) {
if( sd->quest_log[i].state != Q_ACTIVE )
struct quest_db *qi = NULL;
if( sd->quest_log[i].state != Q_ACTIVE ) // Skip inactive quests
continue;
for( j = 0; j < MAX_QUEST_OBJECTIVES; j++ )
if( quest_db[sd->quest_index[i]].mob[j] == mob && sd->quest_log[i].count[j] < quest_db[sd->quest_index[i]].count[j] ) {
qi = quest_db(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] ) {
sd->quest_log[i].count[j]++;
sd->save_quest = true;
clif_quest_update_objective(sd,&sd->quest_log[i],sd->quest_index[i]);
clif_quest_update_objective(sd, &sd->quest_log[i]);
}
}
}
}
int quest_update_status(TBL_PC * sd, int quest_id, quest_state status) {
/**
* Updates a quest's state.
*
* Only status of active and inactive quests can be updated. Completed quests can't (for now). [Inkfish]
*
* @param sd Character's data
* @param quest_id Quest ID to update
* @param qs New quest state
* @return 0 in case of success, nonzero otherwise
*/
int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status) {
int i;
//Only status of active and inactive quests can be updated. Completed quests can't (for now). [Inkfish]
ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == quest_id);
if(i == sd->avail_quests) {
if( i == sd->avail_quests ) {
ShowError("quest_update_status: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
@ -246,15 +281,17 @@ int quest_update_status(TBL_PC * sd, int quest_id, quest_state status) {
sd->save_quest = true;
if( status < Q_COMPLETE ) {
clif_quest_update_status(sd, quest_id, (bool)status);
clif_quest_update_status(sd, quest_id, status == Q_ACTIVE ? true : false);
return 0;
}
if( i != (--sd->avail_quests) ) {
// The quest is complete, so it needs to be moved to the completed quests block at the end of the array.
if( i < (--sd->avail_quests) ) {
struct quest tmp_quest;
memcpy(&tmp_quest, &sd->quest_log[i],sizeof(struct quest));
memcpy(&sd->quest_log[i], &sd->quest_log[sd->avail_quests],sizeof(struct quest));
memcpy(&sd->quest_log[sd->avail_quests], &tmp_quest,sizeof(struct quest));
memcpy(&tmp_quest, &sd->quest_log[i], sizeof(struct quest));
memcpy(&sd->quest_log[i], &sd->quest_log[sd->avail_quests], sizeof(struct quest));
memcpy(&sd->quest_log[sd->avail_quests], &tmp_quest, sizeof(struct quest));
}
clif_quest_delete(sd, quest_id);
@ -265,7 +302,22 @@ int quest_update_status(TBL_PC * sd, int quest_id, quest_state status) {
return 0;
}
int quest_check(TBL_PC * sd, int quest_id, quest_check_type type) {
/**
* Queries quest information for a character.
*
* @param sd Character's data
* @param quest_id Quest ID
* @param type Check type
* @return -1 if the quest was not found, otherwise it depends on the type:
* HAVEQUEST: The quest's state
* PLAYTIME: 2 if the quest's timeout has expired
* 1 if the quest was completed
* 0 otherwise
* HUNTING: 2 if the quest has not been marked as completed yet, and its objectives have been fulfilled
* 1 if the quest's timeout has expired
* 0 otherwise
*/
int quest_check(TBL_PC *sd, int quest_id, enum quest_check_type type) {
int i;
ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].quest_id == quest_id);
@ -277,18 +329,18 @@ int quest_check(TBL_PC * sd, int quest_id, quest_check_type type) {
return sd->quest_log[i].state;
case PLAYTIME:
return (sd->quest_log[i].time < (unsigned int)time(NULL) ? 2 : sd->quest_log[i].state == Q_COMPLETE ? 1 : 0);
case HUNTING: {
if( sd->quest_log[i].state == 0 || sd->quest_log[i].state == 1 ) {
int j;
ARR_FIND(0, MAX_QUEST_OBJECTIVES, j, sd->quest_log[i].count[j] < quest_db[sd->quest_index[i]].count[j]);
if( j == MAX_QUEST_OBJECTIVES )
return 2;
if( sd->quest_log[i].time > 0 && sd->quest_log[i].time < (unsigned int)time(NULL) )
return 1;
return 0;
} else
return 0;
case HUNTING:
if( sd->quest_log[i].state == Q_INACTIVE || sd->quest_log[i].state == Q_ACTIVE ) {
int j;
struct quest_db *qi = quest_db(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 )
return 2;
if( sd->quest_log[i].time < (unsigned int)time(NULL) )
return 1;
}
return 0;
default:
ShowError("quest_check_quest: Unknown parameter %d",type);
break;
@ -297,79 +349,156 @@ int quest_check(TBL_PC * sd, int quest_id, quest_check_type type) {
return -1;
}
/**
* Loads quests from the quest db.
*
* @return Number of loaded quests, or -1 if the file couldn't be read.
*/
int quest_read_db(void) {
const char* dbsubpath[] = {
"",
DBIMPORT"/",
};
int f;
//@TODO[Haru]: This duplicates some sv_readdb functionalities, and it would be
//nice if it could be replaced by it. The reason why it wasn't is probably
//because we need to accept commas (which is also used as delimiter) in the
//last field (quest name), and sv_readdb isn't capable of doing so.
FILE *fp;
char line[1024];
int i, count = 0;
char *str[20], *p, *np;
struct quest_db entry;
for(f=0; f<ARRAYLENGTH(dbsubpath); f++){
FILE *fp;
char line[1024];
int i,j,k = 0;
char *str[20],*p,*np;
char filename[256];
sprintf(filename, "%s/%s%s", db_path,dbsubpath[f],"quest_db.txt");
if( (fp=fopen(filename,"r"))==NULL ){
if(f==0) ShowError("can't read %s\n", filename);
return -1;
}
while(fgets(line, sizeof(line), fp)) {
if (k == MAX_QUEST_DB) {
ShowError("quest_read_db: Too many entries specified in %s/quest_db.txt!\n", db_path);
break;
}
if(line[0]=='/' && line[1]=='/')
continue;
memset(str,0,sizeof(str));
for( j = 0, p = line; j < 8; j++ ) {
if( ( np = strchr(p,',') ) != NULL ) {
str[j] = p;
*np = 0;
p = np + 1;
}
else if (str[0] == NULL)
continue;
else {
ShowError("quest_read_db: insufficient columns in line %s\n", line);
continue;
}
}
if(str[0]==NULL)
continue;
memset(&quest_db[k], 0, sizeof(quest_db[0]));
quest_db[k].id = atoi(str[0]);
quest_db[k].time = atoi(str[1]);
for( i = 0; i < MAX_QUEST_OBJECTIVES; i++ ) {
quest_db[k].mob[i] = atoi(str[2*i+2]);
quest_db[k].count[i] = atoi(str[2*i+3]);
if( !quest_db[k].mob[i] || !quest_db[k].count[i] )
break;
}
quest_db[k].num_objectives = i;
k++;
}
fclose(fp);
ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", k, filename);
sprintf(line, "%s/quest_db.txt", db_path);
if( (fp = fopen(line, "r")) == NULL ) {
ShowError("can't read %s\n", line);
return -1;
}
while( fgets(line, sizeof(line), fp) ) {
if( line[0] == '/' && line[1] == '/' )
continue;
memset(str, 0, sizeof(str));
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);
continue;
}
}
if( str[0] == NULL )
continue;
memset(&entry, 0, sizeof(entry));
entry.id = atoi(str[0]);
if( entry.id < 0 || entry.id >= MAX_QUEST_DB ) {
ShowError("quest_read_db: Invalid quest ID '%d' in line '%s' (min: 0, max: %d.)\n", entry.id, line, MAX_QUEST_DB);
continue;
}
entry.time = atoi(str[1]);
for( i = 0; i < MAX_QUEST_OBJECTIVES; i++ ) {
entry.mob[i] = atoi(str[2 * i + 2]);
entry.count[i] = atoi(str[2 * i + 3]);
if( !entry.mob[i] || !entry.count[i] )
break;
}
entry.num_objectives = i;
if( quest_db_data[entry.id] == NULL )
quest_db_data[entry.id] = aMalloc(sizeof(struct quest_db));
memcpy(quest_db_data[entry.id], &entry, sizeof(struct quest_db));
count++;
}
fclose(fp);
ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, "quest_db.txt");
return 0;
}
/**
* Map iterator to ensures a player has no invalid quest log entries.
*
* Any entries that are no longer in the db are removed.
*
* @see map_foreachpc
* @param ap Ignored
*/
int quest_reload_check_sub(struct map_session_data *sd, va_list ap) {
int i, j;
nullpo_ret(sd);
j = 0;
for( i = 0; i < sd->num_quests; i++ ) {
struct quest_db *qi = quest_db(sd->quest_log[i].quest_id);
if( qi == &quest_dummy ) { //Remove no longer existing entries
if( sd->quest_log[i].state != Q_COMPLETE ) //And inform the client if necessary
clif_quest_delete(sd, sd->quest_log[i].quest_id);
continue;
}
if( i != j ) {
//Move entries if there's a gap to fill
memcpy(&sd->quest_log[j], &sd->quest_log[i], sizeof(struct quest));
}
j++;
}
sd->num_quests = j;
ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].state == Q_COMPLETE);
sd->avail_quests = i;
return 1;
}
/**
* Clears the quest database for shutdown or reload.
*/
void quest_clear_db(void) {
int i;
for( i = 0; i < MAX_QUEST_DB; i++ ) {
if( quest_db_data[i] ) {
aFree(quest_db_data[i]);
quest_db_data[i] = NULL;
}
}
}
/**
* Initializes the quest interface.
*/
void do_init_quest(void) {
quest_read_db();
}
void do_reload_quest(void) {
memset(&quest_db, 0, sizeof(quest_db));
quest_read_db();
/**
* Finalizes the quest interface before shutdown.
*/
void do_final_quest(void) {
memset(&quest_dummy, 0, sizeof(quest_dummy));
quest_clear_db();
}
/**
* Reloads the quest database.
*/
void do_reload_quest(void) {
memset(&quest_dummy, 0, sizeof(quest_dummy));
quest_clear_db();
quest_read_db();
//Update quest data for players, to ensure no entries about removed quests are left over.
map_foreachpc(&quest_reload_check_sub);
}

View File

@ -4,7 +4,9 @@
#ifndef _QUEST_H_
#define _QUEST_H_
struct s_quest_db {
#define MAX_QUEST_DB (62238 + 1) // Highest quest ID + 1
struct quest_db {
int id;
unsigned int time;
int mob[MAX_QUEST_OBJECTIVES];
@ -12,23 +14,32 @@ struct s_quest_db {
int num_objectives;
//char name[NAME_LENGTH];
};
extern struct s_quest_db quest_db[MAX_QUEST_DB];
typedef enum quest_check_type { HAVEQUEST, PLAYTIME, HUNTING } quest_check_type;
struct quest_db *quest_db_data[MAX_QUEST_DB]; ///< Quest database
struct quest_db quest_dummy; ///< Dummy entry for invalid quest lookups
int quest_pc_login(TBL_PC * sd);
// Questlog check types
enum quest_check_type {
HAVEQUEST, ///< Query the state of the given quest
PLAYTIME, ///< Check if the given quest has been completed or has yet to expire
HUNTING, ///< Check if the given hunting quest's requirements have been met
};
int quest_pc_login(TBL_PC *sd);
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);
int quest_update_status(TBL_PC * sd, int quest_id, quest_state status);
int quest_check(TBL_PC * sd, int quest_id, quest_check_type type);
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);
int quest_search_db(int quest_id);
struct quest_db *quest_db(int quest_id);
void do_init_quest();
void do_init_quest(void);
void do_final_quest(void);
void do_reload_quest(void);
#endif

View File

@ -16723,10 +16723,11 @@ BUILDIN_FUNC(questinfo)
BUILDIN_FUNC(setquest)
{
struct map_session_data *sd = script_rid2sd(st);
int i, quest_id;
unsigned short i;
int quest_id;
nullpo_retr(1, sd);
nullpo_ret(sd);
quest_id = script_getnum(st, 2);
quest_add(sd, quest_id);
@ -16775,12 +16776,12 @@ BUILDIN_FUNC(changequest)
BUILDIN_FUNC(checkquest)
{
struct map_session_data *sd = script_rid2sd(st);
quest_check_type type = HAVEQUEST;
enum quest_check_type type = HAVEQUEST;
nullpo_ret(sd);
if( script_hasdata(st, 3) )
type = (quest_check_type)script_getnum(st, 3);
type = (enum quest_check_type)script_getnum(st, 3);
script_pushint(st, quest_check(sd, script_getnum(st, 2), type));
@ -16794,7 +16795,7 @@ BUILDIN_FUNC(isbegin_quest)
nullpo_ret(sd);
i = quest_check(sd, script_getnum(st, 2), (quest_check_type) HAVEQUEST);
i = quest_check(sd, script_getnum(st, 2), (enum quest_check_type) HAVEQUEST);
script_pushint(st, i + (i < 1));
return SCRIPT_CMD_SUCCESS;

View File

@ -2659,6 +2659,11 @@ int unit_free(struct block_list *bl, clr_type clrtype)
aFree(sd->sc_display);
sd->sc_display = NULL;
}
if( sd->quest_log != NULL ) {
aFree(sd->quest_log);
sd->quest_log = NULL;
sd->num_quests = sd->avail_quests = 0;
}
break;
}
case BL_PET: