summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Schilling <ablu.erikschilling@googlemail.com>2013-09-23 22:37:15 +0200
committerErik Schilling <ablu.erikschilling@googlemail.com>2013-09-26 18:09:32 +0200
commit73f1933f94bcadb8c3b3f9763e6afad5f02891f4 (patch)
tree6cd028fcfe3d4a8675f00d5a4a6566d62f3f679a
parent825bc65e0328367c26bebbf68066de3800372f84 (diff)
downloadmanaserv-73f1933f94bcadb8c3b3f9763e6afad5f02891f4.tar.gz
manaserv-73f1933f94bcadb8c3b3f9763e6afad5f02891f4.tar.bz2
manaserv-73f1933f94bcadb8c3b3f9763e6afad5f02891f4.tar.xz
manaserv-73f1933f94bcadb8c3b3f9763e6afad5f02891f4.zip
Store questlog values in the database
-rw-r--r--src/account-server/character.cpp22
-rw-r--r--src/account-server/character.h10
-rw-r--r--src/account-server/storage.cpp61
-rw-r--r--src/common/manaserv_protocol.h4
-rw-r--r--src/game-server/charactercomponent.cpp112
-rw-r--r--src/game-server/charactercomponent.h41
-rw-r--r--src/scripting/lua.cpp57
-rw-r--r--src/sql/mysql/createTables.sql17
-rw-r--r--src/sql/mysql/updates/update_25_to_26.sql24
-rw-r--r--src/sql/sqlite/createTables.sql19
-rw-r--r--src/sql/sqlite/updates/update_25_to_26.sql22
11 files changed, 341 insertions, 48 deletions
diff --git a/src/account-server/character.cpp b/src/account-server/character.cpp
index cafc256b..35a8b079 100644
--- a/src/account-server/character.cpp
+++ b/src/account-server/character.cpp
@@ -91,6 +91,16 @@ void CharacterData::serialize(MessageOut &msg)
msg.writeInt32(abilityId);
}
+
+ // questlog
+ msg.writeInt16(mQuests.size());
+ for (QuestInfo &quest : mQuests) {
+ msg.writeInt16(quest.id);
+ msg.writeInt8(quest.state);
+ msg.writeString(quest.title);
+ msg.writeString(quest.description);
+ }
+
// inventory - must be last because size isn't transmitted
const Possessions &poss = getPossessions();
const EquipData &equipData = poss.getEquipment();
@@ -166,6 +176,18 @@ void CharacterData::deserialize(MessageIn &msg)
giveAbility(id);
}
+ // questlog
+ int questlogSize = msg.readInt16();
+ mQuests.clear();
+ for (int i = 0; i < questlogSize; ++i) {
+ QuestInfo quest;
+ quest.id = msg.readInt16();
+ quest.state = msg.readInt8();
+ quest.title = msg.readString();
+ quest.description = msg.readString();
+ mQuests.push_back(quest);
+ }
+
// inventory - must be last because size isn't transmitted
Possessions &poss = getPossessions();
diff --git a/src/account-server/character.h b/src/account-server/character.h
index f61b8380..05fce77b 100644
--- a/src/account-server/character.h
+++ b/src/account-server/character.h
@@ -64,6 +64,14 @@ struct Status
unsigned time;
};
+struct QuestInfo
+{
+ int id;
+ int state;
+ std::string title;
+ std::string description;
+};
+
/**
* Stores attributes by their id.
*/
@@ -263,6 +271,8 @@ class CharacterData
std::vector<std::string> mGuilds; //!< All the guilds the player
//!< belongs to.
+ std::vector<QuestInfo> mQuests;
+
friend class AccountHandler;
friend class Storage;
};
diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp
index 91ceec94..b6268179 100644
--- a/src/account-server/storage.cpp
+++ b/src/account-server/storage.cpp
@@ -78,6 +78,7 @@ static const char *CHAR_ATTR_TBL_NAME = "mana_char_attr";
static const char *CHAR_STATUS_EFFECTS_TBL_NAME = "mana_char_status_effects";
static const char *CHAR_KILL_COUNT_TBL_NAME = "mana_char_kill_stats";
static const char *CHAR_ABILITIES_TBL_NAME = "mana_char_abilities";
+static const char *QUESTLOG_TBL_NAME = "mana_questlog";
static const char *INVENTORIES_TBL_NAME = "mana_inventories";
static const char *ITEMS_TBL_NAME = "mana_items";
static const char *GUILDS_TBL_NAME = "mana_guilds";
@@ -467,6 +468,27 @@ CharacterData *Storage::getCharacterBySQL(Account *owner)
character->giveAbility(toUint(abilitiesInfo(row, 0)));
}
}
+
+ // Load the questlog
+ s.clear();
+ s.str("");
+ s << "SELECT quest_id, quest_state, quest_title, quest_description "
+ << "FROM " << QUESTLOG_TBL_NAME
+ << " WHERE char_id = " << character->getDatabaseID();
+ const dal::RecordSet &quests = mDb->execSql(s.str());
+ if (!quests.isEmpty())
+ {
+ const unsigned nRows = quests.rows();
+ for (unsigned row = 0; row < nRows; row++)
+ {
+ QuestInfo quest;
+ quest.id = toUint(quests(row, 0));
+ quest.state = toUint(quests(row, 1));
+ quest.title = quests(row, 2);
+ quest.description = quests(row, 3);
+ character->mQuests.push_back(quest);
+ }
+ }
}
catch (const dal::DbSqlQueryExecFailure &e)
{
@@ -760,6 +782,45 @@ bool Storage::updateCharacter(CharacterData *character)
"SQL query failure: ", e);;
}
+ // Character's questlog
+ try
+ {
+ // Out with the old
+ std::ostringstream deleteSql("");
+ std::ostringstream insertSql;
+ deleteSql << "DELETE FROM " << QUESTLOG_TBL_NAME
+ << " WHERE char_id='"
+ << character->getDatabaseID() << "';";
+ mDb->execSql(deleteSql.str());
+ // In with the new
+ for (QuestInfo &quest : character->mQuests)
+ {
+ insertSql.str("");
+ insertSql << "INSERT INTO " << QUESTLOG_TBL_NAME
+ << " (char_id, quest_id, quest_state, "
+ << "quest_title, quest_description)"
+ << " VALUES ("
+ << character->getDatabaseID() << ","
+ << " " << quest.id << ","
+ << " " << quest.state << ","
+ << " ?,"
+ << " ?"
+ << ")";
+ if (mDb->prepareSql(insertSql.str()))
+ {
+ mDb->bindValue(1, quest.title);
+ mDb->bindValue(2, quest.description);
+
+ mDb->processSql();
+ }
+ }
+ }
+ catch (const dal::DbSqlQueryExecFailure& e)
+ {
+ utils::throwError("(DALStorage::updateCharacter #5) "
+ "SQL query failure: ", e);;
+ }
+
// Character's inventory
// Delete the old inventory and equipment table first
try
diff --git a/src/common/manaserv_protocol.h b/src/common/manaserv_protocol.h
index 80863273..26db32c6 100644
--- a/src/common/manaserv_protocol.h
+++ b/src/common/manaserv_protocol.h
@@ -30,7 +30,7 @@ namespace ManaServ {
enum {
PROTOCOL_VERSION = 8,
- SUPPORTED_DB_VERSION = 25
+ SUPPORTED_DB_VERSION = 26
};
/**
@@ -255,7 +255,7 @@ enum {
PCMSG_KICK_USER = 0x0466, // W channel id, S name
// -- Questlog
- GPMSG_QUESTLOG_STATUS = 0x0470, // W quest id, B flags, [B status], [S questname], [S questdescription]
+ GPMSG_QUESTLOG_STATUS = 0x0470, // {W quest id, B flags, [B status], [S questname], [S questdescription]}*
// Inter-server
GAMSG_REGISTER = 0x0500, // S address, W port, S password, D items db revision
diff --git a/src/game-server/charactercomponent.cpp b/src/game-server/charactercomponent.cpp
index 11e90a65..344a8959 100644
--- a/src/game-server/charactercomponent.cpp
+++ b/src/game-server/charactercomponent.cpp
@@ -176,6 +176,16 @@ void CharacterComponent::deserialize(Entity &entity, MessageIn &msg)
entity.getComponent<AbilityComponent>()->giveAbility(id);
}
+ // questlog
+ int questlogSize = msg.readInt16();
+ for (int i = 0; i < questlogSize; ++i) {
+ unsigned id = msg.readInt16();
+ QuestState state = (QuestState) msg.readInt8();
+ std::string title = msg.readString();
+ std::string description = msg.readString();
+
+ setQuestlog(id, state, title, description);
+ }
Possessions &poss = getPossessions();
@@ -256,6 +266,16 @@ void CharacterComponent::serialize(Entity &entity, MessageOut &msg)
msg.writeInt32(abilityIt.first);
}
+ // questlog
+ msg.writeInt16(mQuestlog.size());
+ for (auto questlogIt : mQuestlog) {
+ QuestInfo &quest = questlogIt.second;
+ msg.writeInt16(quest.id);
+ msg.writeInt8(quest.state);
+ msg.writeString(quest.title);
+ msg.writeString(quest.description);
+ }
+
// inventory - must be last because size isn't transmitted
const Possessions &poss = getPossessions();
const EquipData &equipData = poss.getEquipment();
@@ -359,6 +379,44 @@ void CharacterComponent::sendAttributePointsStatus(Entity &entity)
mSendAttributePointsStatus = false;
}
+void CharacterComponent::sendQuestUpdate()
+{
+ MessageOut msg(GPMSG_QUESTLOG_STATUS);
+ for (auto &questIt : mModifiedQuests) {
+ const QuestInfo *quest = questIt.first;
+ bool notify = questIt.second;
+ msg.writeInt16(quest->id);
+ int flags = QUESTLOG_UPDATE_STATE |
+ QUESTLOG_UPDATE_TITLE |
+ QUESTLOG_UPDATE_DESCRIPTION;
+ if (notify)
+ flags |= QUESTLOG_SHOW_NOTIFICATION;
+ msg.writeInt8(flags);
+ msg.writeInt8(quest->state);
+ msg.writeString(quest->title);
+ msg.writeString(quest->description);
+ }
+ mModifiedQuests.clear();
+ gameHandler->sendTo(mClient, msg);
+}
+
+void CharacterComponent::markQuestAsModified(const QuestInfo *quest,
+ bool sendNotification)
+{
+ const auto &it = mModifiedQuests.find(quest);
+ if (it == mModifiedQuests.end()) {
+ mModifiedQuests.insert(std::make_pair(quest, sendNotification));
+ return;
+ }
+ it->second = sendNotification;
+}
+void CharacterComponent::markAllQuestsAsModified()
+{
+ for (auto &questIt : mQuestlog) {
+ mModifiedQuests[&questIt.second] = false;
+ }
+}
+
void CharacterComponent::cancelTransaction()
{
TransactionType t = mTransaction;
@@ -428,7 +486,8 @@ void CharacterComponent::sendStatus(Entity &entity)
attribMsg.writeInt32(beingComponent->getAttributeBase(attribute) * 256);
attribMsg.writeInt32(beingComponent->getModifiedAttribute(attribute) * 256);
}
- if (attribMsg.getLength() > 2) gameHandler->sendTo(mClient, attribMsg);
+ if (attribMsg.getLength() > 2)
+ gameHandler->sendTo(mClient, attribMsg);
mModifiedAttributes.clear();
if (!mModifiedAbilities.empty())
@@ -439,6 +498,9 @@ void CharacterComponent::sendStatus(Entity &entity)
if (mSendAttributePointsStatus)
sendAttributePointsStatus(entity);
+
+ if (!mModifiedQuests.empty())
+ sendQuestUpdate();
}
void CharacterComponent::modifiedAllAbilities(Entity &entity)
@@ -581,4 +643,52 @@ void CharacterComponent::markAllInfoAsChanged(Entity &entity)
{
modifiedAllAbilities(entity);
modifiedAllAttributes(entity);
+ markAllQuestsAsModified();
+}
+
+void CharacterComponent::setQuestlog(unsigned id, QuestState state,
+ const std::string &title,
+ const std::string &description,
+ bool sendNotification)
+{
+ auto &quest = mQuestlog[id];
+ quest.id = id;
+ quest.state = state;
+ quest.title = title;
+ quest.description = description;
+
+ markQuestAsModified(&quest, sendNotification);
+}
+
+void CharacterComponent::setQuestlogState(unsigned id,
+ QuestState state,
+ bool sendNotification)
+{
+ auto &quest = mQuestlog[id];
+ quest.id = id;
+ quest.state = state;
+
+ markQuestAsModified(&quest, sendNotification);
+}
+
+void CharacterComponent::setQuestlogTitle(unsigned id,
+ const std::string &title,
+ bool sendNotification)
+{
+ auto &quest = mQuestlog[id];
+ quest.id = id;
+ quest.title = title;
+
+ markQuestAsModified(&quest, sendNotification);
+}
+
+void CharacterComponent::setQuestlogDescription(unsigned id,
+ const std::string &description,
+ bool sendNotification)
+{
+ auto &quest = mQuestlog[id];
+ quest.id = id;
+ quest.description = description;
+
+ markQuestAsModified(&quest, sendNotification);
}
diff --git a/src/game-server/charactercomponent.h b/src/game-server/charactercomponent.h
index fe3f028d..d43f0fe4 100644
--- a/src/game-server/charactercomponent.h
+++ b/src/game-server/charactercomponent.h
@@ -47,6 +47,26 @@ class MessageOut;
class Point;
class Trade;
+enum QuestState
+{
+ QUEST_OPEN = 0,
+ QUEST_FINISHED,
+ QUEST_FAILED,
+};
+
+struct QuestInfo
+{
+ QuestInfo()
+ : id(0)
+ , state(QUEST_OPEN)
+ {}
+
+ unsigned id;
+ QuestState state;
+ std::string title;
+ std::string description;
+};
+
/**
* The representation of a player's character in the game world.
*/
@@ -278,6 +298,19 @@ class CharacterComponent : public Component
void markAllInfoAsChanged(Entity &entity);
+ void setQuestlog(unsigned id, QuestState state,
+ const std::string &title,
+ const std::string &description,
+ bool sendNotification = false);
+ void setQuestlogState(unsigned id, QuestState state,
+ bool sendNotification = false);
+ void setQuestlogTitle(unsigned id,
+ const std::string &title,
+ bool sendNotification = false);
+ void setQuestlogDescription(unsigned id,
+ const std::string &description,
+ bool sendNotification = false);
+
sigc::signal<void, Entity &> signal_disconnected;
void serialize(Entity &entity, MessageOut &msg);
@@ -294,6 +327,10 @@ class CharacterComponent : public Component
void sendAbilityUpdate(Entity &entity);
void sendAbilityCooldownUpdate(Entity &entity);
void sendAttributePointsStatus(Entity &entity);
+ void sendQuestUpdate();
+
+ void markQuestAsModified(const QuestInfo *quest, bool sendNotification);
+ void markAllQuestsAsModified();
enum TransactionType
{ TRANS_NONE, TRANS_TRADE, TRANS_BUYSELL };
@@ -313,8 +350,8 @@ class CharacterComponent : public Component
/** Attributes modified since last update. */
std::set<AttributeInfo *> mModifiedAttributes;
-
std::set<unsigned> mModifiedAbilities;
+ std::map<const QuestInfo *, bool> mModifiedQuests;
int mDatabaseID; /**< Character's database ID. */
unsigned char mHairStyle; /**< Hair Style of the character. */
@@ -335,6 +372,8 @@ class CharacterComponent : public Component
Timeout mMuteTimeout; /**< Time until the character is no longer muted */
+ std::map<unsigned, QuestInfo> mQuestlog;
+
Entity *mBaseEntity; /**< The entity this component is part of
this is ONLY required to allow using
the serialization routine without many
diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp
index af666bb8..60b3e89c 100644
--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -2345,9 +2345,9 @@ static int entity_use_ability(lua_State *s)
return 1;
}
-/** LUA set_questlog_status (being)
- * entity:set_questlog_status(int id, int state, string name,
- * string description[, bool notify = true])
+/** LUA set_questlog (being)
+ * entity:set_questlog(int id, int state, string name,
+ * string description[, bool notify = true])
**
* Sets the questlog status.
*
@@ -2358,28 +2358,18 @@ static int entity_use_ability(lua_State *s)
* * `QUEST_FINISHED`
* * `QUEST_FAILED`
*/
-static int set_questlog_status(lua_State *s)
+static int set_questlog(lua_State *s)
{
const Entity *character = checkCharacter(s, 1);
const int questId = luaL_checkinteger(s, 2);
- const int questState = luaL_checkinteger(s, 3);
+ const QuestState questState = (QuestState)luaL_checkinteger(s, 3);
const char *questTitle = luaL_checkstring(s, 4);
const char *questDescription = luaL_checkstring(s, 5);
const bool questNotification = checkOptionalBool(s, 6, true);
- MessageOut out(GPMSG_QUESTLOG_STATUS);
- out.writeInt16(questId);
- int flags = QUESTLOG_UPDATE_STATE |
- QUESTLOG_UPDATE_TITLE |
- QUESTLOG_UPDATE_DESCRIPTION;
- if (questNotification)
- flags |= QUESTLOG_SHOW_NOTIFICATION;
- out.writeInt8(flags);
- out.writeInt8(questState);
- out.writeString(questTitle);
- out.writeString(questDescription);
-
- character->getComponent<CharacterComponent>()->getClient()->send(out);
+ auto *characterComponent = character->getComponent<CharacterComponent>();
+ characterComponent->setQuestlog(questId, questState, questTitle,
+ questDescription, questNotification);
return 0;
}
@@ -2399,16 +2389,11 @@ static int set_questlog_state(lua_State *s)
{
const Entity *character = checkCharacter(s, 1);
const int questId = luaL_checkinteger(s, 2);
- const int questState = luaL_checkinteger(s, 3);
+ const QuestState questState = (QuestState)luaL_checkinteger(s, 3);
const bool questNotification = checkOptionalBool(s, 4, true);
- MessageOut out(GPMSG_QUESTLOG_STATUS);
- out.writeInt16(questId);
- out.writeInt8(QUESTLOG_UPDATE_STATE |
- questNotification ? QUESTLOG_SHOW_NOTIFICATION : 0);
- out.writeInt8(questState);
-
- character->getComponent<CharacterComponent>()->getClient()->send(out);
+ auto *characterComponent = character->getComponent<CharacterComponent>();
+ characterComponent->setQuestlogState(questId, questState, questNotification);
return 0;
}
@@ -2426,13 +2411,8 @@ static int set_questlog_title(lua_State *s)
const char *questTitle = luaL_checkstring(s, 3);
const bool questNotification = checkOptionalBool(s, 4, true);
- MessageOut out(GPMSG_QUESTLOG_STATUS);
- out.writeInt16(questId);
- out.writeInt8(QUESTLOG_UPDATE_TITLE |
- questNotification ? QUESTLOG_SHOW_NOTIFICATION : 0);
- out.writeString(questTitle);
-
- character->getComponent<CharacterComponent>()->getClient()->send(out);
+ auto *characterComponent = character->getComponent<CharacterComponent>();
+ characterComponent->setQuestlogTitle(questId, questTitle, questNotification);
return 0;
}
@@ -2450,13 +2430,8 @@ static int set_questlog_description(lua_State *s)
const char *questDescription = luaL_checkstring(s, 3);
const bool questNotification = checkOptionalBool(s, 4, true);
- MessageOut out(GPMSG_QUESTLOG_STATUS);
- out.writeInt16(questId);
- out.writeInt8(QUESTLOG_UPDATE_DESCRIPTION |
- questNotification ? QUESTLOG_SHOW_NOTIFICATION : 0);
- out.writeString(questDescription);
-
- character->getComponent<CharacterComponent>()->getClient()->send(out);
+ auto *characterComponent = character->getComponent<CharacterComponent>();
+ characterComponent->setQuestlogDescription(questId, questDescription, questNotification);
return 0;
}
@@ -3553,7 +3528,7 @@ LuaScript::LuaScript():
{ "status_time", entity_get_status_time },
{ "set_status_time", entity_set_status_time },
{ "add_hit_taken", entity_add_hit_taken },
- { "set_questlog_status", set_questlog_status },
+ { "set_questlog", set_questlog },
{ "set_questlog_state", set_questlog_state },
{ "set_questlog_title", set_questlog_title },
{ "set_questlog_description", set_questlog_description },
diff --git a/src/sql/mysql/createTables.sql b/src/sql/mysql/createTables.sql
index 763a7191..54594bd6 100644
--- a/src/sql/mysql/createTables.sql
+++ b/src/sql/mysql/createTables.sql
@@ -402,11 +402,26 @@ CREATE TABLE IF NOT EXISTS `mana_transactions` (
)
AUTO_INCREMENT=0 ;
+CREATE TABLE IF NOT EXISTS `mana_questlog` (
+ `char_id` int(10) unsigned NOT NULL,
+ `quest_id` INTEGER NOT NULL,
+ `quest_state` INTEGER NOT NULL,
+ `quest_title` TEXT NOT NULL,
+ `quest_description` TEXT NOT NULL,
+ --
+
+ PRIMARY KEY (`char_id`, `quest_id`)
+ FOREIGN KEY (`char_id`)
+ REFERENCES `mana_characters` (`id`)
+ ON DELETE CASCADE
+) ENGINE=InnoDB
+DEFAULT CHARSET=utf8 ;
+
-- initial world states and database version
INSERT INTO mana_world_states VALUES('accountserver_startup',-1,'0', NOW());
INSERT INTO mana_world_states VALUES('accountserver_version',-1,'0', NOW());
-INSERT INTO mana_world_states VALUES('database_version', -1,'25', NOW());
+INSERT INTO mana_world_states VALUES('database_version', -1,'26', NOW());
-- all known transaction codes
diff --git a/src/sql/mysql/updates/update_25_to_26.sql b/src/sql/mysql/updates/update_25_to_26.sql
new file mode 100644
index 00000000..6bed2e6b
--- /dev/null
+++ b/src/sql/mysql/updates/update_25_to_26.sql
@@ -0,0 +1,24 @@
+START TRANSACTION;
+
+CREATE TABLE IF NOT EXISTS `mana_questlog` (
+ `char_id` int(10) unsigned NOT NULL,
+ `quest_id` INTEGER NOT NULL,
+ `quest_state` INTEGER NOT NULL,
+ `quest_title` TEXT NOT NULL,
+ `quest_description` TEXT NOT NULL,
+ --
+
+ PRIMARY KEY (`char_id`, `quest_id`)
+ FOREIGN KEY (`char_id`)
+ REFERENCES `mana_characters` (`id`)
+ ON DELETE CASCADE
+) ENGINE=InnoDB
+DEFAULT CHARSET=utf8 ;
+
+-- Update database version.
+UPDATE mana_world_states
+ SET value = '26',
+ moddate = UNIX_TIMESTAMP()
+ WHERE state_name = 'database_version';
+
+COMMIT;
diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql
index 95dc5a23..437a0546 100644
--- a/src/sql/sqlite/createTables.sql
+++ b/src/sql/sqlite/createTables.sql
@@ -110,7 +110,7 @@ CREATE TABLE mana_char_abilities
(
char_id INTEGER NOT NULL,
ability_id INTEGER NOT NULL,
- PRIMARY KEY (char_id, ability_id),
+ PRIMARY KEY (char_id, ability_id),
FOREIGN KEY (char_id) REFERENCES mana_characters(id)
);
@@ -348,6 +348,21 @@ CREATE TABLE mana_online_list
);
-----------------------------------------------------------------------------
+
+CREATE TABLE mana_questlog
+(
+ char_id INTEGER NOT NULL,
+ quest_id INTEGER NOT NULL,
+ quest_state INTEGER NOT NULL,
+ quest_title TEXT NOT NULL,
+ quest_description TEXT NOT NULL,
+ --
+ FOREIGN KEY (char_id) REFERENCES mana_characters(id)
+);
+CREATE INDEX mana_questlog_char_id ON mana_questlog ( char_id );
+CREATE INDEX mana_questlog_quest_id ON mana_questlog ( quest_id );
+
+-----------------------------------------------------------------------------
-- Views
-----------------------------------------------------------------------------
@@ -396,7 +411,7 @@ AS
INSERT INTO mana_world_states VALUES('accountserver_startup',-1,'0', strftime('%s','now'));
INSERT INTO mana_world_states VALUES('accountserver_version',-1,'0', strftime('%s','now'));
-INSERT INTO mana_world_states VALUES('database_version', -1,'25', strftime('%s','now'));
+INSERT INTO mana_world_states VALUES('database_version', -1,'26', strftime('%s','now'));
-- all known transaction codes
diff --git a/src/sql/sqlite/updates/update_25_to_26.sql b/src/sql/sqlite/updates/update_25_to_26.sql
new file mode 100644
index 00000000..fa60031e
--- /dev/null
+++ b/src/sql/sqlite/updates/update_25_to_26.sql
@@ -0,0 +1,22 @@
+BEGIN;
+
+CREATE TABLE mana_questlog
+(
+ char_id INTEGER NOT NULL,
+ quest_id INTEGER NOT NULL,
+ quest_state INTEGER NOT NULL,
+ quest_title TEXT NOT NULL,
+ quest_description TEXT NOT NULL,
+ --
+ FOREIGN KEY (char_id) REFERENCES mana_characters(id)
+);
+CREATE INDEX mana_questlog_char_id ON mana_questlog ( char_id );
+CREATE INDEX mana_questlog_quest_id ON mana_questlog ( quest_id );
+
+-- Update the database version, and set date of update
+UPDATE mana_world_states
+ SET value = '26',
+ moddate = strftime('%s','now')
+ WHERE state_name = 'database_version';
+
+END;