summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog17
-rw-r--r--data/scripts/libtmw.lua110
-rw-r--r--data/test.lua5
-rw-r--r--src/Makefile.am2
-rw-r--r--src/account-server/dalstorage.cpp56
-rw-r--r--src/account-server/dalstorage.hpp11
-rw-r--r--src/account-server/dalstoragesql.hpp24
-rw-r--r--src/account-server/serverhandler.cpp21
-rw-r--r--src/account-server/storage.hpp11
-rw-r--r--src/defines.h3
-rw-r--r--src/game-server/accountconnection.cpp31
-rw-r--r--src/game-server/accountconnection.hpp13
-rw-r--r--src/game-server/character.hpp7
-rw-r--r--src/game-server/quest.cpp138
-rw-r--r--src/game-server/quest.hpp63
-rw-r--r--src/scripting/lua.cpp71
16 files changed, 537 insertions, 46 deletions
diff --git a/ChangeLog b/ChangeLog
index 7d764f08..4b1b7e80 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2007-08-28 Guillaume Melquiond <guillaume.melquiond@gmail.com>
+
+ * src/account-server/storage.hpp, src/account-server/dalstoragesql.hpp,
+ src/account-server/dalstorage.cpp, src/account-server/dalstorage.hpp:
+ Implemented storing and recovering quest variables from database.
+ * src/account-server/serverhandler.cpp, src/defines.h,
+ src/game-server/accountconnection.cpp,
+ src/game-server/accountconnection.hpp: Added protocol for sending quest
+ variables between servers.
+ * src/game-server/quest.cpp, src/game-server/quest.hpp,
+ src/game-server/character.hpp, src/Makefile.am: Added async handling of
+ quest variables.
+ * src/scripting/lua.cpp, data/scripts/libtmw.lua: Added helper
+ functions for querying quest variables from Lua scripts. Reworked state
+ machine of the NPC support library.
+ * data/test.lua: Modified for testing quest variables.
+
2007-08-27 Eugenio Favalli <elvenprogrammer@gmail.com>
* accountserver.cbp, gameserver.cbp, src/game-server/mapreader.cpp,
diff --git a/data/scripts/libtmw.lua b/data/scripts/libtmw.lua
index bb724799..3189da6a 100644
--- a/data/scripts/libtmw.lua
+++ b/data/scripts/libtmw.lua
@@ -8,10 +8,12 @@ local npcs = {}
-- Table that associates to each Character pointer its state with respect to
-- NPCs (only one at a time). A state is an array with four fields:
--- . 1: pointer of the NPC the player is currently talking to.
+-- . 1: pointer to the NPC the player is currently talking to.
-- . 2: coroutine running the NPC handler.
--- . 3: next query the NPC expects from a player (1 = next, 2 = choice).
+-- . 3: next event the NPC expects from the server.
+-- (1 = npc_next, 2 = npc_choose, 3 = quest_reply, 4 = 1+3)
-- . 4: countdown (in minutes) before the state is deleted.
+-- . 5: name of the expected quest variable. (optional)
local states = {}
-- Array containing the function registered by atinit.
@@ -46,34 +48,68 @@ function do_choice(npc, ch, ...)
return coroutine.yield(2)
end
+-- Gets the value of a quest variable.
+-- Calling this function while an acknowledment is pending is desirable, so
+-- that lag cannot be perceived by the player.
+function get_quest_var(npc, ch, name)
+ -- Query the server and return immediatly if a value is available.
+ local value = tmw.chr_get_quest(ch, name)
+ if value then return value end
+ -- Wait for database reply.
+ return coroutine.yield(3, name)
+end
+
-- Processes as much of an NPC handler as possible.
-local function process_npc(co, ...)
- -- First, resume with the arguments the coroutine was waiting for.
- local b, v = coroutine.resume(co, ...)
- if not b or not v then
- return
- end
- if v == 2 then
- return 2
- end
- -- Then, keep resuming until the coroutine expects the result of a choice
- -- or an acknowledgment to a message.
- local pending = (v == 1)
+local function process_npc(w, ...)
+ local co = w[2]
+ local pending = (w[3] == 4)
+ local first = true
while true do
- b, v = coroutine.resume(co)
+ local b, v, u
+ if first then
+ -- First time, resume with the arguments the coroutine was waiting for.
+ b, v, u = coroutine.resume(co, ...)
+ first = false
+ else
+ -- Otherwise, simply resume.
+ b, v, u = coroutine.resume(co)
+ end
if not b or not v then
+ -- Either there was an error, or the handler just finished its work.
return
end
if v == 2 then
- return 2
+ -- The coroutine needs a user choice from the server, so wait for it.
+ w[3] = 2
+ break
+ end
+ if v == 3 then
+ -- The coroutine needs the value of a quest variable from the server.
+ w[5] = u
+ if pending then
+ -- The coroutine has also sent a message to the user, so do not
+ -- forget about it, as it would flood the user with new messages.
+ w[3] = 4
+ else
+ w[3] = 3
+ end
+ break
end
if pending then
- return 1
+ -- The coroutine is about to interact with the user. But the previous
+ -- action has not been acknowledged by the user yet, so wait for it.
+ w[3] = 1
+ break
end
if v == 1 then
+ -- A message has just been sent. But the coroutine can keep going in case
+ -- there is still some work to do while waiting for user acknowledgment.
pending = true
end
end
+ -- Restore the countdown, as there was some activity.
+ w[4] = 5
+ return true
end
-- Called by the game whenever a player starts talking to an NPC.
@@ -82,10 +118,9 @@ function npc_start(npc, ch)
states[ch] = nil
local h = npcs[npc]
if not h then return end
- local co = coroutine.create(h)
- local v = process_npc(co, npc, ch)
- if v then
- states[ch] = {npc, co, v, 5}
+ local w = { npc, coroutine.create(h) }
+ if process_npc(w, npc, ch) then
+ states[ch] = w
if not timer then
timer = 600
end
@@ -96,27 +131,32 @@ end
-- Checks that the NPC expects it, and processes the respective coroutine.
function npc_next(npc, ch)
local w = states[ch]
- if w and w[1] == npc and w[3] == 1 then
- local v = process_npc(w[2])
- if v then
- w[3] = v
- w[4] = 5
- return
- end
+ if not (w and w[1] == npc and w[3] == 1 and process_npc(w)) then
+ states[ch] = nil
end
- states[ch] = nil
end
-- Called by the game whenever a player selects a particular reply.
-- Checks that the NPC expects it, and processes the respective coroutine.
function npc_choose(npc, ch, u)
local w = states[ch]
- if w and w[1] == npc and w[3] == 2 then
- local v = process_npc(w[2], u)
- if v then
- w[3] = v
- w[4] = 5
- return
+ if not (w and w[1] == npc and w[3] == 2 and process_npc(w, u)) then
+ states[ch] = nil
+ end
+end
+
+-- Called by the game whenever the value of a quest variable is known.
+-- Checks that the NPC expects it, and processes the respective coroutine.
+-- Note: the check for NPC correctness is missing, but it should never matter.
+function quest_reply(ch, name, value)
+ local w = states[ch]
+ if w then
+ local w3 = w[3]
+ if (w3 == 3 or w3 == 4) and w[5] == name then
+ w[5] = nil
+ if process_npc(w, value) then
+ return
+ end
end
end
states[ch] = nil
diff --git a/data/test.lua b/data/test.lua
index 6254225a..47b06519 100644
--- a/data/test.lua
+++ b/data/test.lua
@@ -36,6 +36,11 @@ function my_npc1(npc, ch)
elseif v == 5 then
if tmw.chr_money_change(ch, -100) then
do_message(npc, ch, string.format("Thank you for you patronage! You are left with %d gil.", tmw.chr_money(ch)))
+ local g = tonumber(get_quest_var(npc, ch, "001_donation"))
+ if not g then g = 0 end
+ g = g + 100
+ tmw.chr_set_quest(ch, "001_donation", g)
+ do_message(npc, ch, string.format("As of today, you have donated %d gil.", g))
else
do_message(npc, ch, "I would feel bad taking money from someone that poor.")
end
diff --git a/src/Makefile.am b/src/Makefile.am
index 6bc88be8..a2d22615 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -114,6 +114,8 @@ tmwserv_game_SOURCES = \
game-server/npc.hpp \
game-server/npc.cpp \
game-server/object.hpp \
+ game-server/quest.hpp \
+ game-server/quest.cpp \
game-server/spawnarea.hpp \
game-server/spawnarea.cpp \
game-server/state.hpp \
diff --git a/src/account-server/dalstorage.cpp b/src/account-server/dalstorage.cpp
index e9887fad..612d0565 100644
--- a/src/account-server/dalstorage.cpp
+++ b/src/account-server/dalstorage.cpp
@@ -184,6 +184,7 @@ void DALStorage::open()
createTable(CHANNELS_TBL_NAME, SQL_CHANNELS_TABLE);
createTable(GUILDS_TBL_NAME, SQL_GUILDS_TABLE);
createTable(GUILD_MEMBERS_TBL_NAME, SQL_GUILD_MEMBERS_TABLE);
+ createTable(QUESTS_TBL_NAME, SQL_QUESTS_TABLE);
}
catch (const DbConnectionFailure& e) {
LOG_ERROR("(DALStorage::open #1) Unable to connect to the database: "
@@ -1231,3 +1232,58 @@ std::list<Guild*> DALStorage::getGuildList()
return guilds;
}
+
+std::string DALStorage::getQuestVar(int id, std::string const &name)
+{
+ // connect to the database (if not connected yet).
+ open();
+
+ using namespace dal;
+
+ try
+ {
+ std::ostringstream query;
+ query << "select value from " << QUESTS_TBL_NAME
+ << " where owner_id = '" << id << "' and name = '"
+ << name << "';";
+ RecordSet const &info = mDb->execSql(query.str());
+
+ if (!info.isEmpty()) return info(0, 0);
+ }
+ catch (DbSqlQueryExecFailure const &e)
+ {
+ LOG_ERROR("(DALStorage::getQuestVar) SQL query failure: " << e.what());
+ }
+
+ return std::string();
+}
+
+void DALStorage::setQuestVar(int id, std::string const &name,
+ std::string const &value)
+{
+ // connect to the database (if not connected yet).
+ open();
+
+ using namespace dal;
+
+ try
+ {
+ std::ostringstream query1;
+ query1 << "delete from " << QUESTS_TBL_NAME
+ << " where owner_id = '" << id << "' and name = '"
+ << name << "';";
+ mDb->execSql(query1.str());
+
+ if (value.empty()) return;
+
+ std::ostringstream query2;
+ query2 << "insert into " << QUESTS_TBL_NAME
+ << " (owner_id, name, value) values ('"
+ << id << "', '" << name << "', '" << value << "');";
+ mDb->execSql(query2.str());
+ }
+ catch (DbSqlQueryExecFailure const &e)
+ {
+ LOG_ERROR("(DALStorage::setQuestVar) SQL query failure: " << e.what());
+ }
+}
diff --git a/src/account-server/dalstorage.hpp b/src/account-server/dalstorage.hpp
index 00a5b69b..c25b3fc2 100644
--- a/src/account-server/dalstorage.hpp
+++ b/src/account-server/dalstorage.hpp
@@ -203,6 +203,17 @@ class DALStorage: public Storage
void flushAll();
void flush(AccountPtr const &);
+ /**
+ * Gets the value of a quest variable.
+ */
+ std::string getQuestVar(int id, std::string const &);
+
+ /**
+ * Sets the value of a quest variable.
+ */
+ void setQuestVar(int id, std::string const &, std::string const &);
+
+
private:
/**
* Constructor.
diff --git a/src/account-server/dalstoragesql.hpp b/src/account-server/dalstoragesql.hpp
index 81ceaf46..c4d8ad7d 100644
--- a/src/account-server/dalstoragesql.hpp
+++ b/src/account-server/dalstoragesql.hpp
@@ -64,13 +64,6 @@
/**
* TABLE: tmw_accounts.
- *
- * Notes:
- * - the user levels are:
- * 0: normal user
- * 1: moderator (has medium level rights)
- * 2: administrator (i am god :))
- * - the 'banned' field contains the UNIX time of unban (default = 0)
*/
static char const *ACCOUNTS_TBL_NAME = "tmw_accounts";
static char const *SQL_ACCOUNTS_TABLE =
@@ -294,5 +287,22 @@ static char const *SQL_GUILD_MEMBERS_TABLE =
#endif
");";
+/**
+ * TABLE: tmw_quests.
+ */
+static char const *QUESTS_TBL_NAME = "tmw_quests";
+static char const *SQL_QUESTS_TABLE =
+ "CREATE TABLE tmw_quests ("
+#if defined (MYSQL_SUPPORT)
+#error "Missing definition. Please fill the blanks."
+#elif defined (SQLITE_SUPPORT)
+ "owner_id INTEGER NOT NULL,"
+ "name TEXT NOT NULL,"
+ "value TEXT NOT NULL,"
+ "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)"
+#elif defined (POSTGRESQL_SUPPORT)
+#error "Missing definition. Please fill the blanks."
+#endif
+ ");";
#endif // _TMWSERV_DALSTORAGE_SQL_H_
diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp
index 9e53356f..6c428e4a 100644
--- a/src/account-server/serverhandler.cpp
+++ b/src/account-server/serverhandler.cpp
@@ -189,6 +189,27 @@ void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg)
} break;
+ case GAMSG_GET_QUEST:
+ {
+ int id = msg.readLong();
+ std::string name = msg.readString();
+ Storage &store = Storage::instance("tmw");
+ std::string value = store.getQuestVar(id, name);
+ result.writeShort(AGMSG_GET_QUEST_RESPONSE);
+ result.writeLong(id);
+ result.writeString(name);
+ result.writeString(value);
+ } break;
+
+ case GAMSG_SET_QUEST:
+ {
+ int id = msg.readLong();
+ std::string name = msg.readString();
+ std::string value = msg.readString();
+ Storage &store = Storage::instance("tmw");
+ store.setQuestVar(id, name, value);
+ } break;
+
#if 0
case GAMSG_GUILD_CREATE:
{
diff --git a/src/account-server/storage.hpp b/src/account-server/storage.hpp
index 55a82abf..d436fa1f 100644
--- a/src/account-server/storage.hpp
+++ b/src/account-server/storage.hpp
@@ -319,6 +319,17 @@ class Storage
*/
virtual void flush(AccountPtr const &account) = 0;
+ /**
+ * Gets the value of a quest variable.
+ */
+ virtual std::string getQuestVar(int id, std::string const &) = 0;
+
+ /**
+ * Sets the value of a quest variable.
+ */
+ virtual void setQuestVar(int id, std::string const &,
+ std::string const &) = 0;
+
protected:
/**
diff --git a/src/defines.h b/src/defines.h
index 03eb60d0..fff197c5 100644
--- a/src/defines.h
+++ b/src/defines.h
@@ -235,6 +235,9 @@ enum {
GAMSG_REDIRECT = 0x0530, // L id
AGMSG_REDIRECT_RESPONSE = 0x0531, // L id, B*32 token, S game address, W game port
GAMSG_PLAYER_RECONNECT = 0x0532, // L id, B*32 token
+ GAMSG_SET_QUEST = 0x0540, // L id, S name, S value
+ GAMSG_GET_QUEST = 0x0541, // L id, S name
+ AGMSG_GET_QUEST_RESPONSE = 0x0542, // L id S name, S value
#if 0
GAMSG_GUILD_CREATE = 0x0550, // S name
diff --git a/src/game-server/accountconnection.cpp b/src/game-server/accountconnection.cpp
index 8a03bb24..b351890b 100644
--- a/src/game-server/accountconnection.cpp
+++ b/src/game-server/accountconnection.cpp
@@ -29,6 +29,7 @@
#include "game-server/map.hpp"
#include "game-server/mapcomposite.hpp"
#include "game-server/mapmanager.hpp"
+#include "game-server/quest.hpp"
#include "game-server/state.hpp"
#include "net/messagein.hpp"
#include "net/messageout.hpp"
@@ -94,6 +95,14 @@ void AccountConnection::processMessage(MessageIn &msg)
gameHandler->completeServerChange(id, token, address, port);
} break;
+ case AGMSG_GET_QUEST_RESPONSE:
+ {
+ int id = msg.readLong();
+ std::string name = msg.readString();
+ std::string value = msg.readString();
+ recoveredQuestVar(id, name, value);
+ } break;
+
// The client should directly talk with the chat server and not go through the game server.
#if 0
case AGMSG_GUILD_CREATE_RESPONSE:
@@ -216,15 +225,33 @@ void AccountConnection::processMessage(MessageIn &msg)
}
}
-void AccountConnection::playerReconnectAccount(int id, const std::string magic_token)
+void AccountConnection::playerReconnectAccount(int id, std::string const &magic_token)
{
- LOG_INFO("Send GAMSG_PLAYER_RECONNECT.");
+ LOG_DEBUG("Send GAMSG_PLAYER_RECONNECT.");
MessageOut msg(GAMSG_PLAYER_RECONNECT);
msg.writeLong(id);
msg.writeString(magic_token, MAGIC_TOKEN_LENGTH);
send(msg);
}
+void AccountConnection::requestQuestVar(Character *ch, std::string const &name)
+{
+ MessageOut msg(GAMSG_GET_QUEST);
+ msg.writeLong(ch->getDatabaseID());
+ msg.writeString(name);
+ send(msg);
+}
+
+void AccountConnection::updateQuestVar(Character *ch, std::string const &name,
+ std::string const &value)
+{
+ MessageOut msg(GAMSG_SET_QUEST);
+ msg.writeLong(ch->getDatabaseID());
+ msg.writeString(name);
+ msg.writeString(value);
+ send(msg);
+}
+
#if 0
void AccountConnection::playerCreateGuild(int id, const std::string &guildName)
{
diff --git a/src/game-server/accountconnection.hpp b/src/game-server/accountconnection.hpp
index ba9abe06..9899db14 100644
--- a/src/game-server/accountconnection.hpp
+++ b/src/game-server/accountconnection.hpp
@@ -48,7 +48,18 @@ class AccountConnection : public Connection
/**
* Prepares the account server for a reconnecting player
*/
- void playerReconnectAccount(int id, const std::string magic_token);
+ void playerReconnectAccount(int id, std::string const &magic_token);
+
+ /**
+ * Requests the value of a quest variable from the database.
+ */
+ void requestQuestVar(Character *, std::string const &);
+
+ /**
+ * Pushes a new quest value to the database.
+ */
+ void updateQuestVar(Character *, std::string const &name,
+ std::string const &value);
#if 0
/**
diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp
index 2cf4500c..eff99286 100644
--- a/src/game-server/character.hpp
+++ b/src/game-server/character.hpp
@@ -23,6 +23,7 @@
#ifndef _TMWSERV_CHARACTER_HPP_
#define _TMWSERV_CHARACTER_HPP_
+#include <map>
#include <string>
#include <vector>
@@ -207,6 +208,12 @@ class Character : public Being
*/
void modifiedAttribute(int);
+ /**
+ * Associative array containing all the quest variables known by the
+ * server.
+ */
+ std::map< std::string, std::string > questCache;
+
private:
Character(Character const &);
Character &operator=(Character const &);
diff --git a/src/game-server/quest.cpp b/src/game-server/quest.cpp
new file mode 100644
index 00000000..f6675127
--- /dev/null
+++ b/src/game-server/quest.cpp
@@ -0,0 +1,138 @@
+/*
+ * The Mana World Server
+ * Copyright 2007 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * $Id$
+ */
+
+#include <algorithm>
+#include <list>
+#include <map>
+#include <string>
+
+#include "game-server/quest.hpp"
+
+#include "defines.h"
+#include "game-server/accountconnection.hpp"
+#include "game-server/character.hpp"
+#include "game-server/deathlistener.hpp"
+#include "utils/logger.h"
+
+typedef std::list< QuestCallback > QuestCallbacks;
+typedef std::map< std::string, QuestCallbacks > PendingVariables;
+
+struct PendingQuest
+{
+ Character *character;
+ PendingVariables variables;
+};
+
+typedef std::map< int, PendingQuest > PendingQuests;
+
+static PendingQuests pendingQuests;
+
+bool getQuestVar(Character *ch, std::string const &name, std::string &value)
+{
+ std::map< std::string, std::string >::iterator
+ i = ch->questCache.find(name);
+ if (i == ch->questCache.end()) return false;
+ value = i->second;
+ return true;
+}
+
+void setQuestVar(Character *ch, std::string const &name,
+ std::string const &value)
+{
+ std::map< std::string, std::string >::iterator
+ i = ch->questCache.lower_bound(name);
+ if (i == ch->questCache.end() || i->first != name)
+ {
+ ch->questCache.insert(i, std::make_pair(name, value));
+ }
+ else if (i->second == value)
+ {
+ return;
+ }
+ else
+ {
+ i->second = value;
+ }
+ accountHandler->updateQuestVar(ch, name, value);
+}
+
+/**
+ * Listener for deleting related quests when a character disappears.
+ */
+struct QuestDeathListener: DeathListener
+{
+ void deleted(Being *b)
+ {
+ int id = static_cast< Character * >(b)->getDatabaseID();
+ pendingQuests.erase(id);
+ }
+};
+
+static QuestDeathListener questDeathListener;
+
+void recoverQuestVar(Character *ch, std::string const &name,
+ QuestCallback const &f)
+{
+ int id = ch->getDatabaseID();
+ PendingQuests::iterator i = pendingQuests.lower_bound(id);
+ if (i == pendingQuests.end() || i->first != id)
+ {
+ i = pendingQuests.insert(i, std::make_pair(id, PendingQuest()));
+ i->second.character = ch;
+ /* Register a listener, because we cannot afford to get invalid
+ pointers, when we finally recover the variable. */
+ ch->addDeathListener(&questDeathListener);
+ }
+ i->second.variables[name].push_back(f);
+ accountHandler->requestQuestVar(ch, name);
+}
+
+void recoveredQuestVar(int id, std::string const &name,
+ std::string const &value)
+{
+ PendingQuests::iterator i = pendingQuests.find(id);
+ if (i == pendingQuests.end()) return;
+ PendingVariables &variables = i->second.variables;
+ PendingVariables::iterator j = variables.find(name);
+ if (j == variables.end())
+ {
+ LOG_ERROR("Account server recovered an unexpected quest variable.");
+ return;
+ }
+
+ Character *ch = i->second.character;
+ ch->questCache[name] = value;
+
+ // Call the registered callbacks.
+ for (QuestCallbacks::const_iterator k = j->second.begin(),
+ k_end = j->second.end(); k != k_end; ++k)
+ {
+ k->handler(ch, name, value, k->data);
+ }
+
+ variables.erase(j);
+ if (variables.empty())
+ {
+ pendingQuests.erase(i);
+ }
+}
diff --git a/src/game-server/quest.hpp b/src/game-server/quest.hpp
new file mode 100644
index 00000000..720d79ae
--- /dev/null
+++ b/src/game-server/quest.hpp
@@ -0,0 +1,63 @@
+/*
+ * The Mana World Server
+ * Copyright 2007 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana World is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * $Id$
+ */
+
+#ifndef _TMWSERV_GAMESERVER_QUEST_
+#define _TMWSERV_GAMESERVER_QUEST_
+
+#include <string>
+
+class Character;
+
+struct QuestCallback
+{
+ void (*handler)(Character *, std::string const &name,
+ std::string const &value, void *data);
+ void *data;
+};
+
+/**
+ * Gets the value associated to a quest variable.
+ * @return false if no value was in cache.
+ */
+bool getQuestVar(Character *, std::string const &name, std::string &value);
+
+/**
+ * Sets the value associated to a quest variable.
+ */
+void setQuestVar(Character *, std::string const &name,
+ std::string const &value);
+
+/**
+ * Starts the recovery of a variable and returns immediatly. The callback will
+ * be called once the value has been recovered.
+ */
+void recoverQuestVar(Character *, std::string const &name,
+ QuestCallback const &);
+
+/**
+ * Called by the handler of the account server when a value is received.
+ */
+void recoveredQuestVar(int id, std::string const &name,
+ std::string const &value);
+
+#endif
diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp
index 50d36349..3eb3ecff 100644
--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -37,6 +37,7 @@ extern "C" {
#include "game-server/itemmanager.hpp"
#include "game-server/mapmanager.hpp"
#include "game-server/npc.hpp"
+#include "game-server/quest.hpp"
#include "game-server/state.hpp"
#include "net/messageout.hpp"
#include "scripting/script.hpp"
@@ -68,6 +69,9 @@ class LuaScript: public Script
int execute();
+ static void getQuestCallback(Character *, std::string const &,
+ std::string const &, void *);
+
private:
lua_State *mState;
@@ -320,7 +324,7 @@ static int LuaChr_InvCount(lua_State *s)
}
/**
- * Callback for trading between a a player and an NPC.
+ * Callback for trading between a player and an NPC.
* tmw.npc_trade(npc, character, bool sell, table items)
*/
static int LuaNpc_Trade(lua_State *s)
@@ -362,6 +366,69 @@ static int LuaNpc_Trade(lua_State *s)
return 0;
}
+/**
+ * Called when the server has recovered the value of a quest variable.
+ */
+void LuaScript::getQuestCallback(Character *q, std::string const &name,
+ std::string const &value, void *data)
+{
+ LuaScript *s = static_cast< LuaScript * >(data);
+ assert(s->nbArgs == -1);
+ lua_getglobal(s->mState, "quest_reply");
+ lua_pushlightuserdata(s->mState, q);
+ lua_pushstring(s->mState, name.c_str());
+ lua_pushstring(s->mState, value.c_str());
+ s->nbArgs = 3;
+ s->execute();
+}
+
+/**
+ * Callback for getting a quest variable. Starts a recovery and returns
+ * immediatly, if the variable is not known yet.
+ * tmw.chr_get_chest(character, string): nil or string
+ */
+static int LuaChr_GetQuest(lua_State *s)
+{
+ Character *q = getCharacter(s, 1);
+ char const *m = lua_tostring(s, 2);
+ if (!m || m[0] == 0)
+ {
+ LOG_WARN("LuaChr_GetQuest called with incorrect parameters.");
+ return 0;
+ }
+ std::string value, name = m;
+ bool res = getQuestVar(q, name, value);
+ if (res)
+ {
+ lua_pushstring(s, value.c_str());
+ return 1;
+ }
+ lua_pushlightuserdata(s, (void *)&registryKey);
+ lua_gettable(s, LUA_REGISTRYINDEX);
+ Script *t = static_cast<Script *>(lua_touserdata(s, -1));
+ QuestCallback f = { &LuaScript::getQuestCallback, t };
+ recoverQuestVar(q, name, f);
+ return 0;
+}
+
+/**
+ * Callback for setting a quest variable.
+ * tmw.chr_set_chest(character, string, string)
+ */
+static int LuaChr_SetQuest(lua_State *s)
+{
+ Character *q = getCharacter(s, 1);
+ char const *m = lua_tostring(s, 2);
+ char const *n = lua_tostring(s, 3);
+ if (!m || !n || m[0] == 0)
+ {
+ LOG_WARN("LuaChr_SetQuest called with incorrect parameters.");
+ return 0;
+ }
+ setQuestVar(q, m, n);
+ return 0;
+}
+
LuaScript::LuaScript():
nbArgs(-1)
{
@@ -377,6 +444,8 @@ LuaScript::LuaScript():
{ "chr_warp", &LuaChr_Warp },
{ "chr_inv_change", &LuaChr_InvChange },
{ "chr_inv_count", &LuaChr_InvCount },
+ { "chr_get_quest", &LuaChr_GetQuest },
+ { "chr_set_quest", &LuaChr_SetQuest },
{ NULL, NULL }
};
luaL_register(mState, "tmw", callbacks);