summaryrefslogtreecommitdiff
path: root/src/char
diff options
context:
space:
mode:
authorHaru <haru@dotalux.com>2013-11-27 03:21:45 +0100
committerHaru <haru@dotalux.com>2013-12-03 16:28:08 +0100
commit6f55c00e72ca6db130a84fe92218f73a777428f4 (patch)
treee3f25d1ad5b3dbb06371941fc9fd8cb66a5411a9 /src/char
parent470ab15023f09dc823e8ce8ac818e0bbe8a95aef (diff)
downloadhercules-6f55c00e72ca6db130a84fe92218f73a777428f4.tar.gz
hercules-6f55c00e72ca6db130a84fe92218f73a777428f4.tar.bz2
hercules-6f55c00e72ca6db130a84fe92218f73a777428f4.tar.xz
hercules-6f55c00e72ca6db130a84fe92218f73a777428f4.zip
Questlog fixes
- Improved memory usage of the quest log system. (saves up to 75kB per online character). Fixes issue #133. - 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 Ind. Signed-off-by: Haru <haru@dotalux.com>
Diffstat (limited to 'src/char')
-rw-r--r--src/char/int_quest.c229
-rw-r--r--src/char/int_quest.h3
2 files changed, 139 insertions, 93 deletions
diff --git a/src/char/int_quest.c b/src/char/int_quest.c
index af8f83a5d..b40cc97f8 100644
--- a/src/char/int_quest.c
+++ b/src/char/int_quest.c
@@ -19,45 +19,76 @@
#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 = SQL->StmtMalloc(sql_handle);
- if( stmt == NULL )
- {
+ if (stmt == NULL) {
SqlStmt_ShowDebug(stmt);
return 0;
}
memset(&tmp_quest, 0, sizeof(struct quest));
- if( SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT `quest_id`, `state`, `time`, `count1`, `count2`, `count3` FROM `%s` WHERE `char_id`=? LIMIT %d", quest_db, MAX_QUEST_DB)
- || SQL_ERROR == SQL->StmtBindParam(stmt, 0, SQLDT_INT, &char_id, 0)
- || SQL_ERROR == SQL->StmtExecute(stmt)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 0, SQLDT_INT, &tmp_quest.quest_id, 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &tmp_quest.state, 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 2, SQLDT_UINT, &tmp_quest.time, 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 3, SQLDT_INT, &tmp_quest.count[0], 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 4, SQLDT_INT, &tmp_quest.count[1], 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 5, SQLDT_INT, &tmp_quest.count[2], 0, NULL, NULL) )
+ if (SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT `quest_id`, `state`, `time`, `count1`, `count2`, `count3` FROM `%s` WHERE `char_id`=?", quest_db)
+ || SQL_ERROR == SQL->StmtBindParam(stmt, 0, SQLDT_INT, &char_id, 0)
+ || SQL_ERROR == SQL->StmtExecute(stmt)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 0, SQLDT_INT, &tmp_quest.quest_id, 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &tmp_quest.state, 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 2, SQLDT_UINT, &tmp_quest.time, 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 3, SQLDT_INT, &tmp_quest.count[0], 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 4, SQLDT_INT, &tmp_quest.count[1], 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 5, SQLDT_INT, &tmp_quest.count[2], 0, NULL, NULL)
+ ) {
SqlStmt_ShowDebug(stmt);
+ SQL->StmtFree(stmt);
+ *count = 0;
+ return NULL;
+ }
- for( i = 0; i < MAX_QUEST_DB && SQL_SUCCESS == SQL->StmtNextRow(stmt); ++i )
- memcpy(&questlog[i], &tmp_quest, sizeof(tmp_quest));
+ *count = (int)SQL->NumRows(sql_handle);
+ if (*count > 0) {
+ int i = 0;
+ questlog = (struct quest *)aCalloc(*count, sizeof(struct quest));
+
+ while (SQL_SUCCESS == SQL->StmtNextRow(stmt)) {
+ if (i >= *count) // Sanity check, should never happen
+ break;
+ memcpy(&questlog[i++], &tmp_quest, sizeof(tmp_quest));
+ }
+ if (i < *count) {
+ // Should never. Compact array
+ *count = i;
+ questlog = aRealloc(questlog, sizeof(struct quest)*i);
+ }
+ }
SQL->StmtFree(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;
}
@@ -65,11 +96,18 @@ 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;
}
@@ -77,11 +115,18 @@ 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;
}
@@ -89,42 +134,52 @@ 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)
+ 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, new_qd[i]);
}
- else // Add new quests
- success &= mapif_quest_add(char_id, qd1[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;
@@ -134,48 +189,42 @@ int mapif_parse_quest_save(int fd)
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));
-
- 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);
WFIFOW(fd,0) = 0x3860;
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));
- }
-
- // 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));
+ if (num_quests > 0)
+ memcpy(WFIFOP(fd,8), tmp_questlog, sizeof(struct quest)*num_quests);
WFIFOSET(fd,num_quests*sizeof(struct quest)+8);
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:
diff --git a/src/char/int_quest.h b/src/char/int_quest.h
index f2a0b626e..b0403f436 100644
--- a/src/char/int_quest.h
+++ b/src/char/int_quest.h
@@ -4,9 +4,6 @@
#ifndef _QUEST_H_
#define _QUEST_H_
-/*questlog system*/
-struct quest;
-
int inter_quest_parse_frommap(int fd);
#endif