Skip to content
Permalink
Browse files
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.
  • Loading branch information
aleos89 committed Apr 3, 2014
1 parent cc59315 commit 2fe8140f9613fe410a23ea20030c02b75dec6309
Showing with 580 additions and 358 deletions.
  1. +135 −90 src/char/int_quest.c
  2. +0 −3 src/char/int_quest.h
  3. +11 −8 src/common/mmo.h
  4. +30 −25 src/map/clif.c
  5. +3 −3 src/map/clif.h
  6. +58 −37 src/map/intif.c
  7. +1 −1 src/map/intif.h
  8. +1 −0 src/map/map.c
  9. +7 −2 src/map/pc.c
  10. +5 −6 src/map/pc.h
  11. +298 −169 src/map/quest.c
  12. +19 −8 src/map/quest.h
  13. +7 −6 src/map/script.c
  14. +5 −0 src/map/unit.c
@@ -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);

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]);

if( j < (--num2) )
{
memmove(&qd2[j],&qd2[j+1],sizeof(struct quest)*(num2-j));
memset(&qd2[num2], 0, sizeof(struct quest));
if( new_n > 0 )
new_qd = (struct quest*)RFIFOP(fd,8);

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 < (--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];

memset(tmp_questlog, 0, sizeof(tmp_questlog));
memset(complete, 0, sizeof(complete));
struct quest *tmp_questlog = NULL;
int num_quests;

num_quests = mapif_quests_fromsql(char_id, tmp_questlog);
tmp_questlog = mapif_quests_fromsql(char_id, &num_quests);

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:
@@ -4,9 +4,6 @@
#ifndef _QUEST_H_
#define _QUEST_H_

/*questlog system*/
struct quest;

int inter_quest_parse_frommap(int fd);

#endif
@@ -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 {

0 comments on commit 2fe8140

Please sign in to comment.