summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <thorbjorn@lindeijer.nl>2012-03-09 21:30:42 +0100
committerThorbjørn Lindeijer <thorbjorn@lindeijer.nl>2012-03-10 18:07:29 +0100
commit78c912fb4007c3e5f0b43de02646772acb21ecf2 (patch)
treee45409ff061de75b0e4273763a87f5a25de6a65b /src
parent2fa7d1f39b24714ee6dc72b6b9e61ec5a1997724 (diff)
downloadmanaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.gz
manaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.bz2
manaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.xz
manaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.zip
Moved the managing of NPC script coroutines into C++
Rather than wrapping NPC functions up in coroutines in the Lua side, they are now managed on the C++ side as "script threads", which are essentially the same thing. The main purpose is that the server can now know whether any of these long running script interactions are still active, which will probably be useful when adding the ability to reload scripts. Reviewed-by: Erik Schilling
Diffstat (limited to 'src')
-rw-r--r--src/game-server/autoattack.h2
-rw-r--r--src/game-server/character.cpp8
-rw-r--r--src/game-server/character.h9
-rw-r--r--src/game-server/npc.cpp106
-rw-r--r--src/game-server/npc.h27
-rw-r--r--src/game-server/postman.h7
-rw-r--r--src/game-server/quest.cpp5
-rw-r--r--src/game-server/quest.h6
-rw-r--r--src/scripting/lua.cpp137
-rw-r--r--src/scripting/luascript.cpp183
-rw-r--r--src/scripting/luascript.h40
-rw-r--r--src/scripting/luautil.cpp12
-rw-r--r--src/scripting/luautil.h5
-rw-r--r--src/scripting/script.cpp42
-rw-r--r--src/scripting/script.h76
15 files changed, 443 insertions, 222 deletions
diff --git a/src/game-server/autoattack.h b/src/game-server/autoattack.h
index e7b853d3..a1e22aee 100644
--- a/src/game-server/autoattack.h
+++ b/src/game-server/autoattack.h
@@ -26,8 +26,6 @@
#include "common/defines.h"
-#include "game-server/skillmanager.h"
-
/**
* Structure that describes the severity and nature of an attack a being can
* be hit by.
diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp
index 14b49bca..bf16e268 100644
--- a/src/game-server/character.cpp
+++ b/src/game-server/character.cpp
@@ -85,7 +85,8 @@ Character::Character(MessageIn &msg):
mUpdateLevelProgress(false),
mRecalculateLevel(true),
mParty(0),
- mTransaction(TRANS_NONE)
+ mTransaction(TRANS_NONE),
+ mNpcThread(0)
{
const AttributeManager::AttributeScope &attr =
attributeManager->getAttributeScope(CharacterScope);
@@ -111,6 +112,11 @@ Character::Character(MessageIn &msg):
giveSpecial(3);
}
+Character::~Character()
+{
+ delete mNpcThread;
+}
+
void Character::update()
{
// First, deal with being generic updates
diff --git a/src/game-server/character.h b/src/game-server/character.h
index 76498421..1b31c611 100644
--- a/src/game-server/character.h
+++ b/src/game-server/character.h
@@ -62,6 +62,8 @@ class Character : public Being
*/
Character(MessageIn &msg);
+ ~Character();
+
/**
* recalculates the level when necessary and calls Being::update
*/
@@ -346,6 +348,12 @@ class Character : public Being
void setCorrectionPoints(int points) { mCorrectionPoints = points; }
int getCorrectionPoints() const { return mCorrectionPoints; }
+ void setNpcThread(Script::Thread *thread)
+ { mNpcThread = thread; }
+
+ Script::Thread *getNpcThread() const
+ { return mNpcThread; }
+
/**
* Gets the way the actor is blocked by other things on the map
*/
@@ -464,6 +472,7 @@ class Character : public Being
int mParty; /**< Party id of the character */
TransactionType mTransaction; /**< Trade/buy/sell action the character is involved in. */
std::map<int, int> mKillCount; /**< How many monsters the character has slain of each type */
+ Script::Thread *mNpcThread; /**< Script thread executing NPC interaction, if any */
static Script::Ref mDeathCallback;
static Script::Ref mDeathAcceptedCallback;
diff --git a/src/game-server/npc.cpp b/src/game-server/npc.cpp
index 052b296e..aa032c98 100644
--- a/src/game-server/npc.cpp
+++ b/src/game-server/npc.cpp
@@ -21,17 +21,13 @@
#include "game-server/character.h"
#include "game-server/npc.h"
#include "scripting/script.h"
+#include "scripting/scriptmanager.h"
Script::Ref NPC::mStartCallback;
-Script::Ref NPC::mNextCallback;
-Script::Ref NPC::mChooseCallback;
-Script::Ref NPC::mIntegerCallback;
-Script::Ref NPC::mStringCallback;
Script::Ref NPC::mUpdateCallback;
-NPC::NPC(const std::string &name, int id, Script *s):
+NPC::NPC(const std::string &name, int id):
Being(OBJECT_NPC),
- mScript(s),
mID(id),
mEnabled(true)
{
@@ -45,53 +41,89 @@ void NPC::enable(bool enabled)
void NPC::update()
{
- if (!mScript || !mEnabled || !mUpdateCallback.isValid())
+ if (!mEnabled || !mUpdateCallback.isValid())
return;
- mScript->prepare(mUpdateCallback);
- mScript->push(this);
- mScript->execute();
+
+ Script *script = ScriptManager::currentState();
+ script->prepare(mUpdateCallback);
+ script->push(this);
+ script->execute();
}
void NPC::prompt(Character *ch, bool restart)
{
- if (!mScript || !mEnabled || !mStartCallback.isValid()
- || !mNextCallback.isValid())
+ if (!mEnabled || !mStartCallback.isValid())
return;
- mScript->prepare(restart ? mStartCallback : mNextCallback);
- mScript->push(this);
- mScript->push(ch);
- mScript->execute();
+
+ Script *script = ScriptManager::currentState();
+
+ if (restart)
+ {
+ Script::Thread *thread = script->newThread();
+ thread->mMap = getMap();
+ script->prepare(mStartCallback);
+ script->push(this);
+ script->push(ch);
+
+ if (!script->resume())
+ ch->setNpcThread(thread);
+ }
+ else
+ {
+ Script::Thread *thread = ch->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadPaused)
+ return;
+
+ script->prepareResume(thread);
+ if (script->resume())
+ ch->setNpcThread(0);
+ }
}
-void NPC::select(Character *ch, int v)
+void NPC::select(Character *ch, int index)
{
- if (!mScript || !mEnabled || !mChooseCallback.isValid())
+ if (!mEnabled)
+ return;
+
+ Script::Thread *thread = ch->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingNumber)
return;
- mScript->prepare(mChooseCallback);
- mScript->push(this);
- mScript->push(ch);
- mScript->push(v);
- mScript->execute();
+
+ Script *script = ScriptManager::currentState();
+ script->prepareResume(thread);
+ script->push(index);
+ if (script->resume())
+ ch->setNpcThread(0);
}
-void NPC::integerReceived(Character *ch, int v)
+void NPC::integerReceived(Character *ch, int value)
{
- if (!mScript || !mEnabled || !mIntegerCallback.isValid())
+ if (!mEnabled)
+ return;
+
+ Script::Thread *thread = ch->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingNumber)
return;
- mScript->prepare(mIntegerCallback);
- mScript->push(this);
- mScript->push(ch);
- mScript->push(v);
- mScript->execute();
+
+ Script *script = ScriptManager::currentState();
+ script->prepareResume(thread);
+ script->push(value);
+ if (script->resume())
+ ch->setNpcThread(0);
}
-void NPC::stringReceived(Character *ch, const std::string &v)
+void NPC::stringReceived(Character *ch, const std::string &value)
{
- if (!mScript || !mEnabled || !mStringCallback.isValid())
+ if (!mEnabled)
return;
- mScript->prepare(mStringCallback);
- mScript->push(this);
- mScript->push(ch);
- mScript->push(v);
- mScript->execute();
+
+ Script::Thread *thread = ch->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingString)
+ return;
+
+ Script *script = ScriptManager::currentState();
+ script->prepareResume(thread);
+ script->push(value);
+ if (script->resume())
+ ch->setNpcThread(0);
}
diff --git a/src/game-server/npc.h b/src/game-server/npc.h
index 1ca6b1bb..4bff9af0 100644
--- a/src/game-server/npc.h
+++ b/src/game-server/npc.h
@@ -32,7 +32,7 @@ class Character;
class NPC : public Being
{
public:
- NPC(const std::string &name, int id, Script *);
+ NPC(const std::string &name, int id);
void update();
@@ -49,17 +49,17 @@ class NPC : public Being
/**
* Selects an NPC proposition.
*/
- void select(Character *, int);
+ void select(Character *, int index);
/**
* The player has choosen an integer.
*/
- void integerReceived(Character *ch, int v);
+ void integerReceived(Character *ch, int value);
/**
* The player has entered an string.
*/
- void stringReceived(Character *ch, const std::string &v);
+ void stringReceived(Character *ch, const std::string &value);
/**
* Gets NPC ID.
@@ -76,18 +76,6 @@ class NPC : public Being
static void setStartCallback(Script *script)
{ script->assignCallback(mStartCallback); }
- static void setNextCallback(Script *script)
- { script->assignCallback(mNextCallback); }
-
- static void setChooseCallback(Script *script)
- { script->assignCallback(mChooseCallback); }
-
- static void setIntegerCallback(Script *script)
- { script->assignCallback(mIntegerCallback); }
-
- static void setStringCallback(Script *script)
- { script->assignCallback(mStringCallback); }
-
static void setUpdateCallback(Script *script)
{ script->assignCallback(mUpdateCallback); }
@@ -99,16 +87,11 @@ class NPC : public Being
{ return BLOCKTYPE_CHARACTER; } // blocks like a player character
private:
- Script *mScript; /**< Script describing NPC behavior. */
unsigned short mID; /**< ID of the NPC. */
bool mEnabled; /**< Whether NPC is enabled */
static Script::Ref mStartCallback;
- static Script::Ref mNextCallback;
- static Script::Ref mChooseCallback;
- static Script::Ref mIntegerCallback;
- static Script::Ref mStringCallback;
static Script::Ref mUpdateCallback;
};
-#endif
+#endif // GAMESERVER_NPC_H
diff --git a/src/game-server/postman.h b/src/game-server/postman.h
index 5669ebef..b9cf7bb2 100644
--- a/src/game-server/postman.h
+++ b/src/game-server/postman.h
@@ -29,8 +29,11 @@ class Script;
struct PostCallback
{
- void (*handler)(Character *, const std::string &sender,
- const std::string &letter, Script *script);
+ void (*handler)(Character *,
+ const std::string &sender,
+ const std::string &letter,
+ Script *);
+
Script *script;
};
diff --git a/src/game-server/quest.cpp b/src/game-server/quest.cpp
index 974fc4ce..3b415fe0 100644
--- a/src/game-server/quest.cpp
+++ b/src/game-server/quest.cpp
@@ -129,7 +129,8 @@ void recoverQuestVar(Character *ch, const std::string &name,
accountHandler->requestCharacterVar(ch, name);
}
-void recoveredQuestVar(int id, const std::string &name,
+void recoveredQuestVar(int id,
+ const std::string &name,
const std::string &value)
{
PendingQuests::iterator i = pendingQuests.find(id);
@@ -152,7 +153,7 @@ void recoveredQuestVar(int id, const std::string &name,
for (QuestCallbacks::const_iterator k = j->second.begin(),
k_end = j->second.end(); k != k_end; ++k)
{
- k->handler(ch, name, value, k->script);
+ k->handler(ch, value, k->script);
}
variables.erase(j);
diff --git a/src/game-server/quest.h b/src/game-server/quest.h
index 86d2be46..0125e84b 100644
--- a/src/game-server/quest.h
+++ b/src/game-server/quest.h
@@ -28,8 +28,10 @@ class Script;
struct QuestCallback
{
- void (*handler)(Character *, const std::string &name,
- const std::string &value, Script *script);
+ void (*handler)(Character *,
+ const std::string &value,
+ Script *script);
+
Script *script;
};
diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp
index 540925b1..5053070a 100644
--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -91,20 +91,6 @@ static int on_character_death_accept(lua_State *s)
return 0;
}
-static int on_npc_quest_reply(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- LuaScript::setQuestReplyCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_post_reply(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- LuaScript::setPostReplyCallback(getScript(s));
- return 0;
-}
-
static int on_being_death(lua_State *s)
{
luaL_checktype(s, 1, LUA_TFUNCTION);
@@ -133,34 +119,6 @@ static int on_npc_start(lua_State *s)
return 0;
}
-static int on_npc_next(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setNextCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_choose(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setChooseCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_integer(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setIntegerCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_string(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setStringCallback(getScript(s));
- return 0;
-}
-
static int on_npc_update(lua_State *s)
{
luaL_checktype(s, 1, LUA_TFUNCTION);
@@ -233,11 +191,15 @@ static int npc_message(lua_State *s)
size_t l;
const char *m = luaL_checklstring(s, 3, &l);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_MESSAGE);
msg.writeInt16(p->getPublicID());
msg.writeString(m, l);
gameHandler->sendTo(q, msg);
- return 0;
+
+ thread->mState = Script::ThreadPaused;
+ return lua_yield(s, 0);
}
/**
@@ -249,6 +211,8 @@ static int npc_choice(lua_State *s)
NPC *p = checkNPC(s, 1);
Character *q = checkCharacter(s, 2);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_CHOICE);
msg.writeInt16(p->getPublicID());
for (int i = 3, i_end = lua_gettop(s); i <= i_end; ++i)
@@ -282,11 +246,13 @@ static int npc_choice(lua_State *s)
}
}
gameHandler->sendTo(q, msg);
- return 0;
+
+ thread->mState = Script::ThreadExpectingNumber;
+ return lua_yield(s, 0);
}
/**
- * mana.npc_integer(NPC*, Character*, int min, int max, int defaut = min): void
+ * mana.npc_integer(NPC*, Character*, int min, int max, int default = min): void
* Callback for sending a NPC_INTEGER.
*/
static int npc_ask_integer(lua_State *s)
@@ -297,6 +263,8 @@ static int npc_ask_integer(lua_State *s)
int max = luaL_checkint(s, 4);
int defaultValue = luaL_optint(s, 5, min);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_NUMBER);
msg.writeInt16(p->getPublicID());
msg.writeInt32(min);
@@ -304,7 +272,8 @@ static int npc_ask_integer(lua_State *s)
msg.writeInt32(defaultValue);
gameHandler->sendTo(q, msg);
- return 0;
+ thread->mState = Script::ThreadExpectingNumber;
+ return lua_yield(s, 0);
}
/**
@@ -316,15 +285,18 @@ static int npc_ask_string(lua_State *s)
NPC *p = checkNPC(s, 1);
Character *q = checkCharacter(s, 2);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_STRING);
msg.writeInt16(p->getPublicID());
gameHandler->sendTo(q, msg);
- return 0;
+ thread->mState = Script::ThreadExpectingString;
+ return lua_yield(s, 0);
}
/**
- * mana.npc_create(string name, int id, int x, int y): NPC*
+ * mana.npc_create(string name, int id, int gender, int x, int y): NPC*
* Callback for creating a NPC on the current map with the current script.
*/
static int npc_create(lua_State *s)
@@ -335,10 +307,9 @@ static int npc_create(lua_State *s)
const int x = luaL_checkint(s, 4);
const int y = luaL_checkint(s, 5);
- Script *t = getScript(s);
- MapComposite *m = checkCurrentMap(s, t);
+ MapComposite *m = checkCurrentMap(s);
- NPC *q = new NPC(name, id, t);
+ NPC *q = new NPC(name, id);
q->setGender(getGender(gender));
q->setMap(m);
q->setPosition(Point(x, y));
@@ -514,7 +485,7 @@ static int chr_get_inventory(lua_State *s)
}
/**
- * Callback for gathering equiupment information.
+ * Callback for gathering equipment information.
* mana.chr_get_inventory(character): table[](slot, item id, name)
* Returns in the inventory slots order, the slot id, the item ids, and name.
* Only slots not empty are returned.
@@ -1295,6 +1266,8 @@ static int chr_get_quest(lua_State *s)
const char *name = luaL_checkstring(s, 2);
luaL_argcheck(s, name[0] != 0, 2, "empty variable name");
+ Script::Thread *thread = checkCurrentThread(s);
+
std::string value;
bool res = getQuestVar(q, name, value);
if (res)
@@ -1304,7 +1277,9 @@ static int chr_get_quest(lua_State *s)
}
QuestCallback f = { &LuaScript::getQuestCallback, getScript(s) };
recoverQuestVar(q, name, f);
- return 0;
+
+ thread->mState = Script::ThreadExpectingString;
+ return lua_yield(s, 0);
}
/**
@@ -1507,8 +1482,7 @@ static int get_beings_in_rectangle(lua_State *s)
int tableStackPosition = lua_gettop(s);
int tableIndex = 1;
Rectangle rect = {x, y ,w, h};
- for (BeingIterator i(
- m->getInsideRectangleIterator(rect)); i; ++i)
+ for (BeingIterator i(m->getInsideRectangleIterator(rect)); i; ++i)
{
Being *b = *i;
char t = b->getType();
@@ -1530,10 +1504,14 @@ static int chr_get_post(lua_State *s)
{
Character *c = checkCharacter(s, 1);
- PostCallback f = { &LuaScript::getPostCallback, getScript(s) };
+ Script *script = getScript(s);
+ Script::Thread *thread = checkCurrentThread(s, script);
+
+ PostCallback f = { &LuaScript::getPostCallback, script };
postMan->getPost(c, f);
- return 0;
+ thread->mState = Script::ThreadExpectingTwoStrings;
+ return lua_yield(s, 0);
}
/**
@@ -2135,31 +2113,26 @@ static int require_loader(lua_State *s)
LuaScript::LuaScript():
nbArgs(-1)
{
- mState = luaL_newstate();
- luaL_openlibs(mState);
+ mRootState = luaL_newstate();
+ mCurrentState = mRootState;
+ luaL_openlibs(mRootState);
// Register package loader that goes through the resource manager
// table.insert(package.loaders, 2, require_loader)
- lua_getglobal(mState, "package");
- lua_getfield(mState, -1, "loaders");
- lua_pushcfunction(mState, require_loader);
- lua_rawseti(mState, -2, 2);
- lua_pop(mState, 2);
+ lua_getglobal(mRootState, "package");
+ lua_getfield(mRootState, -1, "loaders");
+ lua_pushcfunction(mRootState, require_loader);
+ lua_rawseti(mRootState, -2, 2);
+ lua_pop(mRootState, 2);
// Put the callback functions in the scripting environment.
static luaL_Reg const callbacks[] = {
{ "on_character_death", &on_character_death },
{ "on_character_death_accept", &on_character_death_accept },
- { "on_npc_quest_reply", &on_npc_quest_reply },
- { "on_npc_post_reply", &on_npc_post_reply },
{ "on_being_death", &on_being_death },
{ "on_being_remove", &on_being_remove },
{ "on_update", &on_update },
{ "on_npc_start", &on_npc_start },
- { "on_npc_next", &on_npc_next },
- { "on_npc_choose", &on_npc_choose },
- { "on_npc_integer", &on_npc_integer },
- { "on_npc_string", &on_npc_string },
{ "on_npc_update", &on_npc_update },
{ "on_create_npc_delayed", &on_create_npc_delayed },
{ "on_map_initialize", &on_map_initialize },
@@ -2256,8 +2229,8 @@ LuaScript::LuaScript():
{ "announce", &announce },
{ NULL, NULL }
};
- luaL_register(mState, "mana", callbacks);
- lua_pop(mState, 1); // pop the 'mana' table
+ luaL_register(mRootState, "mana", callbacks);
+ lua_pop(mRootState, 1); // pop the 'mana' table
static luaL_Reg const members_ItemClass[] = {
{ "on", &item_class_on },
@@ -2283,20 +2256,20 @@ LuaScript::LuaScript():
{ NULL, NULL }
};
- LuaItemClass::registerType(mState, "ItemClass", members_ItemClass);
- LuaMapObject::registerType(mState, "MapObject", members_MapObject);
- LuaMonsterClass::registerType(mState, "MonsterClass", members_MonsterClass);
- LuaStatusEffect::registerType(mState, "StatusEffect", members_StatusEffect);
+ LuaItemClass::registerType(mRootState, "ItemClass", members_ItemClass);
+ LuaMapObject::registerType(mRootState, "MapObject", members_MapObject);
+ LuaMonsterClass::registerType(mRootState, "MonsterClass", members_MonsterClass);
+ LuaStatusEffect::registerType(mRootState, "StatusEffect", members_StatusEffect);
// Make script object available to callback functions.
- lua_pushlightuserdata(mState, const_cast<char *>(&registryKey));
- lua_pushlightuserdata(mState, this);
- lua_rawset(mState, LUA_REGISTRYINDEX);
+ lua_pushlightuserdata(mRootState, const_cast<char *>(&registryKey));
+ lua_pushlightuserdata(mRootState, this);
+ lua_rawset(mRootState, LUA_REGISTRYINDEX);
// Push the error handler to first index of the stack
- lua_getglobal(mState, "debug");
- lua_getfield(mState, -1, "traceback");
- lua_remove(mState, 1); // remove the 'debug' table
+ lua_getglobal(mRootState, "debug");
+ lua_getfield(mRootState, -1, "traceback");
+ lua_remove(mRootState, 1); // remove the 'debug' table
loadFile("scripts/lua/libmana.lua");
}
diff --git a/src/scripting/luascript.cpp b/src/scripting/luascript.cpp
index c9337d59..dfa64d0f 100644
--- a/src/scripting/luascript.cpp
+++ b/src/scripting/luascript.cpp
@@ -21,8 +21,8 @@
#include "luascript.h"
-
#include "scripting/luautil.h"
+#include "scripting/scriptmanager.h"
#include "game-server/character.h"
#include "utils/logger.h"
@@ -30,8 +30,6 @@
#include <cassert>
#include <cstring>
-Script::Ref LuaScript::mQuestReplyCallback;
-Script::Ref LuaScript::mPostReplyCallback;
Script::Ref LuaScript::mDeathNotificationCallback;
Script::Ref LuaScript::mRemoveNotificationCallback;
@@ -39,36 +37,59 @@ const char LuaScript::registryKey = 0;
LuaScript::~LuaScript()
{
- lua_close(mState);
+ lua_close(mRootState);
}
void LuaScript::prepare(Ref function)
{
assert(nbArgs == -1);
+
assert(function.isValid());
- lua_rawgeti(mState, LUA_REGISTRYINDEX, function.value);
- assert(lua_isfunction(mState, -1));
+ lua_rawgeti(mCurrentState, LUA_REGISTRYINDEX, function.value);
+ assert(lua_isfunction(mCurrentState, -1));
+ nbArgs = 0;
+}
+
+Script::Thread *LuaScript::newThread()
+{
+ assert(nbArgs == -1);
+ assert(!mCurrentThread);
+
+ LuaThread *thread = new LuaThread(this);
+
+ mCurrentThread = thread;
+ mCurrentState = thread->mState;
+ return thread;
+}
+
+void LuaScript::prepareResume(Thread *thread)
+{
+ assert(nbArgs == -1);
+ assert(!mCurrentThread);
+
+ mCurrentThread = thread;
+ mCurrentState = static_cast<LuaThread*>(thread)->mState;
nbArgs = 0;
}
void LuaScript::push(int v)
{
assert(nbArgs >= 0);
- lua_pushinteger(mState, v);
+ lua_pushinteger(mCurrentState, v);
++nbArgs;
}
void LuaScript::push(const std::string &v)
{
assert(nbArgs >= 0);
- lua_pushstring(mState, v.c_str());
+ lua_pushstring(mCurrentState, v.c_str());
++nbArgs;
}
void LuaScript::push(Thing *v)
{
assert(nbArgs >= 0);
- lua_pushlightuserdata(mState, v);
+ lua_pushlightuserdata(mCurrentState, v);
++nbArgs;
}
@@ -77,8 +98,8 @@ void LuaScript::push(const std::list<InventoryItem> &itemList)
assert(nbArgs >= 0);
int position = 0;
- lua_createtable(mState, itemList.size(), 0);
- int itemTable = lua_gettop(mState);
+ lua_createtable(mCurrentState, itemList.size(), 0);
+ int itemTable = lua_gettop(mCurrentState);
for (std::list<InventoryItem>::const_iterator i = itemList.begin();
i != itemList.end();
@@ -88,8 +109,8 @@ void LuaScript::push(const std::list<InventoryItem> &itemList)
std::map<std::string, int> item;
item["id"] = i->itemId;
item["amount"] = i->amount;
- pushSTLContainer<std::string, int>(mState, item);
- lua_rawseti(mState, itemTable, ++position);
+ pushSTLContainer<std::string, int>(mCurrentState, item);
+ lua_rawseti(mCurrentState, itemTable, ++position);
}
++nbArgs;
}
@@ -97,56 +118,106 @@ void LuaScript::push(const std::list<InventoryItem> &itemList)
int LuaScript::execute()
{
assert(nbArgs >= 0);
- int res = lua_pcall(mState, nbArgs, 1, 1);
+ assert(!mCurrentThread);
+
+ int res = lua_pcall(mCurrentState, nbArgs, 1, 1);
nbArgs = -1;
- if (res || !(lua_isnil(mState, -1) || lua_isnumber(mState, -1)))
+
+ if (res || !(lua_isnil(mCurrentState, -1) || lua_isnumber(mCurrentState, -1)))
{
- const char *s = lua_tostring(mState, -1);
+ const char *s = lua_tostring(mCurrentState, -1);
LOG_WARN("Lua Script Error" << std::endl
<< " Script : " << mScriptFile << std::endl
<< " Error : " << (s ? s : "") << std::endl);
- lua_pop(mState, 1);
+ lua_pop(mCurrentState, 1);
return 0;
}
- res = lua_tointeger(mState, -1);
- lua_pop(mState, 1);
+ res = lua_tointeger(mCurrentState, -1);
+ lua_pop(mCurrentState, 1);
return res;
}
+bool LuaScript::resume()
+{
+ assert(nbArgs >= 0);
+ assert(mCurrentThread);
+
+ setMap(mCurrentThread->mMap);
+ int result = lua_resume(mCurrentState, nbArgs);
+ nbArgs = -1;
+ setMap(0);
+
+ if (result == 0) // Thread is done
+ {
+ if (lua_gettop(mCurrentState) > 0)
+ LOG_WARN("Ignoring values returned by script thread!");
+ }
+ else if (result == LUA_YIELD) // Thread has yielded
+ {
+ if (lua_gettop(mCurrentState) > 0)
+ LOG_WARN("Ignoring values passed to yield!");
+ }
+ else // Thread encountered an error
+ {
+ // Make a traceback using the debug.traceback function
+ lua_getglobal(mCurrentState, "debug");
+ lua_getfield(mCurrentState, -1, "traceback");
+ lua_pushvalue(mCurrentState, -3); // error string as first parameter
+ lua_pcall(mCurrentState, 1, 1, 0);
+
+ LOG_WARN("Lua Script Error:" << std::endl
+ << lua_tostring(mCurrentState, -1));
+ }
+
+ lua_settop(mCurrentState, 0);
+ const bool done = result != LUA_YIELD;
+
+ if (done)
+ {
+ // Clean up the current thread (not sure if this is the best place)
+ delete mCurrentThread;
+ }
+
+ mCurrentThread = 0;
+ mCurrentState = mRootState;
+
+ return done;
+}
+
void LuaScript::assignCallback(Script::Ref &function)
{
- assert(lua_isfunction(mState, -1));
+ assert(lua_isfunction(mRootState, -1));
// If there is already a callback set, replace it
if (function.isValid())
- luaL_unref(mState, LUA_REGISTRYINDEX, function.value);
+ luaL_unref(mRootState, LUA_REGISTRYINDEX, function.value);
- function.value = luaL_ref(mState, LUA_REGISTRYINDEX);
+ function.value = luaL_ref(mRootState, LUA_REGISTRYINDEX);
}
void LuaScript::load(const char *prog, const char *name)
{
- int res = luaL_loadbuffer(mState, prog, std::strlen(prog), name);
+ int res = luaL_loadbuffer(mRootState, prog, std::strlen(prog), name);
if (res)
{
switch (res) {
case LUA_ERRSYNTAX:
LOG_ERROR("Syntax error while loading Lua script: "
- << lua_tostring(mState, -1));
+ << lua_tostring(mRootState, -1));
break;
case LUA_ERRMEM:
LOG_ERROR("Memory allocation error while loading Lua script");
break;
}
- lua_pop(mState, 1);
+ lua_pop(mRootState, 1);
}
- else if (lua_pcall(mState, 0, 0, 1))
+ else if (lua_pcall(mRootState, 0, 0, 1))
{
LOG_ERROR("Failure while initializing Lua script: "
- << lua_tostring(mState, -1));
- lua_pop(mState, 1);
+ << lua_tostring(mRootState, -1));
+ lua_pop(mRootState, 1);
}
}
@@ -179,31 +250,47 @@ void LuaScript::processRemoveEvent(Thing *being)
/**
* Called when the server has recovered the value of a quest variable.
*/
-void LuaScript::getQuestCallback(Character *q, const std::string &name,
- const std::string &value, Script *script)
+void LuaScript::getQuestCallback(Character *q,
+ const std::string &value,
+ Script *script)
{
- if (mQuestReplyCallback.isValid())
- {
- script->prepare(mQuestReplyCallback);
- script->push(q);
- script->push(name);
- script->push(value);
- script->execute();
- }
+ Script::Thread *thread = q->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingString)
+ return;
+
+ script->prepareResume(thread);
+ script->push(value);
+ script->resume();
}
/**
* Called when the server has recovered the post for a user.
*/
-void LuaScript::getPostCallback(Character *q, const std::string &sender,
- const std::string &letter, Script *script)
+void LuaScript::getPostCallback(Character *q,
+ const std::string &sender,
+ const std::string &letter,
+ Script *script)
{
- if (mPostReplyCallback.isValid())
- {
- script->prepare(mPostReplyCallback);
- script->push(q);
- script->push(sender);
- script->push(letter);
- script->execute();
- }
+ Script::Thread *thread = q->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingTwoStrings)
+ return;
+
+ script->prepareResume(thread);
+ script->push(sender);
+ script->push(letter);
+ script->resume();
+}
+
+
+LuaScript::LuaThread::LuaThread(LuaScript *script) :
+ Thread(script)
+{
+ mState = lua_newthread(script->mRootState);
+ mRef = luaL_ref(script->mRootState, LUA_REGISTRYINDEX);
+}
+
+LuaScript::LuaThread::~LuaThread()
+{
+ LuaScript *luaScript = static_cast<LuaScript*>(mScript);
+ luaL_unref(luaScript->mRootState, LUA_REGISTRYINDEX, mRef);
}
diff --git a/src/scripting/luascript.h b/src/scripting/luascript.h
index e26fd9ef..57b8dc1a 100644
--- a/src/scripting/luascript.h
+++ b/src/scripting/luascript.h
@@ -44,8 +44,12 @@ class LuaScript : public Script
void load(const char *prog, const char *name);
+ Thread *newThread();
+
void prepare(Ref function);
+ void prepareResume(Thread *thread);
+
void push(int);
void push(const std::string &);
@@ -56,25 +60,24 @@ class LuaScript : public Script
int execute();
+ bool resume();
+
void assignCallback(Ref &function);
- static void getQuestCallback(Character *, const std::string &,
- const std::string &, Script *);
+ static void getQuestCallback(Character *,
+ const std::string &value,
+ Script *);
- static void getPostCallback(Character *, const std::string &,
- const std::string &, Script *);
+ static void getPostCallback(Character *,
+ const std::string &sender,
+ const std::string &letter,
+ Script *);
void processDeathEvent(Being *thing);
void processRemoveEvent(Thing *thing);
- static void setQuestReplyCallback(Script *script)
- { script->assignCallback(mQuestReplyCallback); }
-
- static void setPostReplyCallback(Script *script)
- { script->assignCallback(mPostReplyCallback); }
-
static void setDeathNotificationCallback(Script *script)
{ script->assignCallback(mDeathNotificationCallback); }
@@ -84,13 +87,24 @@ class LuaScript : public Script
static const char registryKey;
private:
- lua_State *mState;
+ class LuaThread : public Thread
+ {
+ public:
+ LuaThread(LuaScript *script);
+ ~LuaThread();
+
+ lua_State *mState;
+ int mRef;
+ };
+
+ lua_State *mRootState;
+ lua_State *mCurrentState;
int nbArgs;
- static Ref mQuestReplyCallback;
- static Ref mPostReplyCallback;
static Ref mDeathNotificationCallback;
static Ref mRemoveNotificationCallback;
+
+ friend class LuaThread;
};
static Script *LuaFactory()
diff --git a/src/scripting/luautil.cpp b/src/scripting/luautil.cpp
index 0a49e834..8bc72d3f 100644
--- a/src/scripting/luautil.cpp
+++ b/src/scripting/luautil.cpp
@@ -265,6 +265,18 @@ MapComposite *checkCurrentMap(lua_State *s, Script *script /* = 0 */)
return mapComposite;
}
+Script::Thread *checkCurrentThread(lua_State *s, Script *script /* = 0 */)
+{
+ if (!script)
+ script = getScript(s);
+
+ Script::Thread *thread = script->getCurrentThread();
+ if (!thread)
+ luaL_error(s, "function requires threaded execution");
+
+ return thread;
+}
+
void push(lua_State *s, int val)
{
diff --git a/src/scripting/luautil.h b/src/scripting/luautil.h
index 2a255280..89ea4800 100644
--- a/src/scripting/luautil.h
+++ b/src/scripting/luautil.h
@@ -21,10 +21,13 @@
#ifndef SCRIPTING_LUAUTIL_H
#define SCRIPTING_LUAUTIL_H
+#include "scripting/script.h"
+
extern "C" {
#include <lualib.h>
#include <lauxlib.h>
}
+
#include <string>
#include <list>
#include <map>
@@ -39,7 +42,6 @@ class MapObject;
class Monster;
class MonsterClass;
class NPC;
-class Script;
class StatusEffect;
class Thing;
@@ -168,6 +170,7 @@ MonsterClass * checkMonsterClass(lua_State *s, int p);
NPC * checkNPC(lua_State *s, int p);
MapComposite * checkCurrentMap(lua_State *s, Script *script = 0);
+Script::Thread* checkCurrentThread(lua_State *s, Script *script = 0);
/* Polymorphic wrapper for pushing variables.
diff --git a/src/scripting/script.cpp b/src/scripting/script.cpp
index 9ef069d5..6312d1ce 100644
--- a/src/scripting/script.cpp
+++ b/src/scripting/script.cpp
@@ -25,6 +25,7 @@
#include "game-server/being.h"
#include "utils/logger.h"
+#include <cassert>
#include <cstdlib>
#include <map>
@@ -38,10 +39,17 @@ Script::Ref Script::mCreateNpcDelayedCallback;
Script::Ref Script::mUpdateCallback;
Script::Script():
- mMap(NULL),
+ mCurrentThread(0),
+ mMap(0),
mEventListener(&scriptEventDispatch)
{}
+Script::~Script()
+{
+ // There should be no remaining threads when the Script gets deleted
+ assert(mThreads.empty());
+}
+
void Script::registerEngine(const std::string &name, Factory f)
{
if (!engines)
@@ -120,3 +128,35 @@ void Script::loadNPC(const std::string &name, int id, int x, int y,
push(y);
execute();
}
+
+
+/**
+ * Removes one element matching the given value by overwriting it with the last
+ * element and then popping the last element.
+ */
+template<typename T>
+static void fastRemoveOne(std::vector<T> &vector, T value)
+{
+ for (size_t i = vector.size() - 1; i >= 0; --i)
+ {
+ if (vector.at(i) == value)
+ {
+ vector.at(i) = vector.back();
+ vector.pop_back();
+ break;
+ }
+ }
+}
+
+Script::Thread::Thread(Script *script) :
+ mScript(script),
+ mState(ThreadPending),
+ mMap(0)
+{
+ script->mThreads.push_back(this);
+}
+
+Script::Thread::~Thread()
+{
+ fastRemoveOne(mScript->mThreads, this);
+}
diff --git a/src/scripting/script.h b/src/scripting/script.h
index 2fa3a0bf..b55f204e 100644
--- a/src/scripting/script.h
+++ b/src/scripting/script.h
@@ -26,6 +26,7 @@
#include <list>
#include <string>
+#include <vector>
class MapComposite;
class Thing;
@@ -56,16 +57,40 @@ class Script
* custom initialization and a definition of valid. It also makes the
* purpose clear.
*/
- class Ref {
- public:
- Ref() : value(-1) {}
- bool isValid() const { return value != -1; }
- int value;
+ class Ref
+ {
+ public:
+ Ref() : value(-1) {}
+ bool isValid() const { return value != -1; }
+ int value;
+ };
+
+ enum ThreadState {
+ ThreadPending,
+ ThreadPaused,
+ ThreadExpectingNumber,
+ ThreadExpectingString,
+ ThreadExpectingTwoStrings
+ };
+
+ /**
+ * A script thread. Meant to be extended by the Script subclass to
+ * store additional information.
+ */
+ class Thread
+ {
+ public:
+ Thread(Script *script);
+ virtual ~Thread();
+
+ Script * const mScript;
+ ThreadState mState;
+ MapComposite *mMap;
};
Script();
- virtual ~Script() {}
+ virtual ~Script();
/**
* Loads a chunk of text into script context and executes its global
@@ -96,12 +121,28 @@ class Script
virtual void update();
/**
+ * Creates a new script thread and makes it the current one. Script
+ * threads do not execute in parallel, but they can suspend execution
+ * and be resumed later.
+ *
+ * The new thread should be prepared as usual, but instead of
+ * execute(), the resume() function should be called.
+ */
+ virtual Thread *newThread() = 0;
+
+ /**
* Prepares a call to the referenced function.
* Only one function can be prepared at once.
*/
virtual void prepare(Ref function) = 0;
/**
+ * Prepares for resuming the given script thread.
+ * Only one thread can be resumed at once.
+ */
+ virtual void prepareResume(Thread *thread) = 0;
+
+ /**
* Pushes an integer argument for the function being prepared.
*/
virtual void push(int) = 0;
@@ -120,8 +161,7 @@ class Script
virtual void push(Thing *) = 0;
/**
- * Pushes a list of items with amounts to the
- * script engine.
+ * Pushes a list of items with amounts to the script engine.
*/
virtual void push(const std::list<InventoryItem> &itemList) = 0;
@@ -132,6 +172,14 @@ class Script
virtual int execute() = 0;
/**
+ * Starts or resumes the current thread. Deletes the thread when it is
+ * done.
+ *
+ * @return whether the thread is done executing.
+ */
+ virtual bool resume() = 0;
+
+ /**
* Assigns the current callback to the given \a function.
*
* Where the callback exactly comes from is up to the script engine.
@@ -139,6 +187,13 @@ class Script
virtual void assignCallback(Ref &function) = 0;
/**
+ * Returns the currently executing thread, or null when no thread is
+ * currently executing.
+ */
+ Thread *getCurrentThread() const
+ { return mCurrentThread; }
+
+ /**
* Sets associated map.
*/
void setMap(MapComposite *m)
@@ -165,15 +220,18 @@ class Script
protected:
std::string mScriptFile;
+ Thread *mCurrentThread;
private:
MapComposite *mMap;
EventListener mEventListener; /**< Tracking of being deaths. */
+ std::vector<Thread*> mThreads;
static Ref mCreateNpcDelayedCallback;
static Ref mUpdateCallback;
friend struct ScriptEventDispatch;
+ friend class Thread;
};
struct ScriptEventDispatch: EventDispatch
@@ -188,4 +246,4 @@ struct ScriptEventDispatch: EventDispatch
static ScriptEventDispatch scriptEventDispatch;
-#endif
+#endif // SCRIPTING_SCRIPT_H