diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/game-server/autoattack.h | 2 | ||||
-rw-r--r-- | src/game-server/character.cpp | 8 | ||||
-rw-r--r-- | src/game-server/character.h | 9 | ||||
-rw-r--r-- | src/game-server/npc.cpp | 106 | ||||
-rw-r--r-- | src/game-server/npc.h | 27 | ||||
-rw-r--r-- | src/game-server/postman.h | 7 | ||||
-rw-r--r-- | src/game-server/quest.cpp | 5 | ||||
-rw-r--r-- | src/game-server/quest.h | 6 | ||||
-rw-r--r-- | src/scripting/lua.cpp | 137 | ||||
-rw-r--r-- | src/scripting/luascript.cpp | 183 | ||||
-rw-r--r-- | src/scripting/luascript.h | 40 | ||||
-rw-r--r-- | src/scripting/luautil.cpp | 12 | ||||
-rw-r--r-- | src/scripting/luautil.h | 5 | ||||
-rw-r--r-- | src/scripting/script.cpp | 42 | ||||
-rw-r--r-- | src/scripting/script.h | 76 |
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 *>(®istryKey)); - lua_pushlightuserdata(mState, this); - lua_rawset(mState, LUA_REGISTRYINDEX); + lua_pushlightuserdata(mRootState, const_cast<char *>(®istryKey)); + 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 |