summaryrefslogtreecommitdiffstats
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
parent2fa7d1f39b24714ee6dc72b6b9e61ec5a1997724 (diff)
downloadmanaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.gz
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
-rw-r--r--example/scripts/npcs/banker.lua2
-rw-r--r--scripts/lua/libmana.lua268
-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
17 files changed, 455 insertions, 480 deletions
diff --git a/example/scripts/npcs/banker.lua b/example/scripts/npcs/banker.lua
index 4e04865..790f9a5 100644
--- a/example/scripts/npcs/banker.lua
+++ b/example/scripts/npcs/banker.lua
@@ -20,7 +20,6 @@ function Banker(npc, ch)
end
local account = tonumber(get_quest_var(ch, "BankAccount"))
local result = -1
- do_wait()
if (account == nil) then --Initial account creation, if needed
do_message(npc, ch, "Hello! Would you like to setup a bank account? There is a sign-on bonus right now!")
@@ -45,7 +44,6 @@ function Banker(npc, ch)
if (money > 0) then --Make sure they have money to deposit
do_message(npc, ch, "How much would you like to deposit? (0 will cancel)")
input = do_ask_integer(npc, ch, 0, money, 1)
- do_wait()
money = mana.chr_money(ch)
if (input > 0 and input <= money) then --Make sure something weird doesn't happen and they try to deposit more than they have
mana.chr_money_change(ch, -input)
diff --git a/scripts/lua/libmana.lua b/scripts/lua/libmana.lua
index cb92276..94d1f99 100644
--- a/scripts/lua/libmana.lua
+++ b/scripts/lua/libmana.lua
@@ -22,22 +22,9 @@ require "scripts/lua/libmana-constants"
local npc_talk_functs = {}
local npc_update_functs = {}
--- 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 to the NPC the player is currently talking to.
--- . 2: coroutine running the NPC handler.
--- . 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.
local init_fun = {}
--- Tick timer used during update to clean obsolete states.
-local timer
-
-- Creates an NPC and associates the given handler.
-- Note: Cannot be called until map initialization has started.
function create_npc(name, id, gender, x, y, talkfunct, updatefunct)
@@ -45,235 +32,31 @@ function create_npc(name, id, gender, x, y, talkfunct, updatefunct)
if talkfunct then
npc_talk_functs[npc] = function(npc, ch)
talkfunct(npc, ch)
- do_npc_close(npc, ch)
+ mana.npc_end(npc, ch)
end
end
if updatefunct then npc_update_functs[npc] = updatefunct end
return npc
end
--- Waits for the player to acknowledge the previous message, if any.
-function do_wait()
- coroutine.yield(0)
-end
-
--- Sends an npc message to a player.
--- Note: Does not wait for the player to acknowledge the message.
-function do_message(npc, ch, msg)
- -- Wait for the arrival of a pending acknowledgment, if any.
- coroutine.yield(0)
- mana.npc_message(npc, ch, msg)
- -- An acknowledgment is pending, but do not wait for its arrival.
- coroutine.yield(1)
-end
-
--- Sends an NPC question to a player and waits for its answer.
-function do_choice(npc, ch, ...)
- -- Wait for the arrival of a pending acknowledgment, if any.
- coroutine.yield(0)
- mana.npc_choice(npc, ch, ...)
- -- Wait for player choice.
- return coroutine.yield(2)
-end
-
--- Sends an NPC integer ask to a player and waits for its answer.
-function do_ask_integer(npc, ch, min_num, max_num, ...)
- -- Wait for the arrival of a pending acknowledgment, if any.
- coroutine.yield(0)
- mana.npc_ask_integer(npc, ch, min_num, max_num, ...)
- -- Wait for player answer.
- return coroutine.yield(2)
-end
-
--- Sends an NPC string ask to a player and waits for its answer.
-function do_ask_string(npc, ch)
- -- Wait for the arrival of a pending acknowledgment, if any.
- coroutine.yield(0)
- mana.npc_ask_string(npc, ch)
- -- Wait for player answer.
- return coroutine.yield(2)
-end
-
--- Sends an NPC request to send letter to a player and waits for them to
--- send the letter.
-function do_post(npc, ch)
- coroutine.yield(0)
- mana.npc_post(npc, ch)
- return coroutine.yield(1)
-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(ch, name)
- -- Query the server and return immediatly if a value is available.
- local value = mana.chr_get_quest(ch, name)
- if value then return value end
- -- Wait for database reply.
- return coroutine.yield(3, name)
-end
-
--- Gets the post for a user.
-function getpost(ch)
- mana.chr_get_post(ch)
- return coroutine.yield(3)
-end
-
--- Processes as much of an NPC handler as possible.
-local function process_npc(w, ...)
- local co = w[2]
- local pending = (w[3] == 4)
- local first = true
- while true do
- 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 then print("LUA error: ", v)end
-
- if not b or not v then
- -- Either there was an error, or the handler just finished its work.
- return
- elseif v == 2 then
- -- The coroutine needs a user choice from the server, so wait for it.
- w[3] = 2
- break
- elseif 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
- elseif pending then
- -- 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
- elseif 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
+-- These are deprecated and only provided for compatibility!
+do_message = mana.npc_message
+do_choice = mana.npc_choice
+do_ask_integer = mana.npc_ask_integer
+do_ask_string = mana.npc_ask_string
+do_post = mana.npc_post
+get_quest_var = mana.chr_get_quest
+getpost = mana.chr_get_post
-- Registered as the function to call whenever a player starts talking to an
--- NPC. Creates a coroutine based on the registered NPC handler.
+-- NPC. Calls the registered NPC handler.
local function npc_start(npc, ch)
- states[ch] = nil
local h = npc_talk_functs[npc]
- if not h then return end
- local w = { npc, coroutine.create(h) }
- if process_npc(w, npc, ch) then
- states[ch] = w
- if not timer then
- timer = 600
- end
- end
- -- coroutine.resume(w)
- -- do_npc_close(npc, ch)
-end
-
-function do_npc_close(npc, ch)
- mana.npc_end(npc, ch)
-end
-
--- Registered as the function to call whenever a player continues talking to an
--- NPC. Checks that the NPC expects it, and processes the respective coroutine.
-local function npc_next(npc, ch)
- local w = states[ch]
- if w then
- local w3 = w[3]
- if w3 == 4 then
- w[3] = 3
- return
- elseif w3 == 1 and process_npc(w) then
- return
- end
- end
- states[ch] = nil
-end
-
--- Registered as the function to call whenever a player selects a particular
--- reply. Checks that the NPC expects it, and processes the respective
--- coroutine.
-local function npc_choose(npc, ch, u)
- local w = states[ch]
- if not (w and w[1] == npc and w[3] == 2 and process_npc(w, u)) then
- states[ch] = nil
- end
-end
-
-local function npc_integer(npc, ch, u)
- local w = states[ch]
- if not (w and w[1] == npc and w[3] == 2 and process_npc(w, u)) then
- states[ch] = nil
+ if h then
+ h(npc, ch)
end
end
-local function npc_string(npc, ch, u)
- local w = states[ch]
- 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 when a player sends a letter.
--- TODO: Actually this function isn't called, probably unfinished implementation
-local function npc_post(npc, ch, sender, letter)
- local w = states[ch]
- if not (w and w[1] == npc and w[3] == 1 and process_npc(w, sender, letter)) then
- states[ch] = nil
- end
-end
-
--- Registered as the function to call whenever a value of a quest variable is
--- retrieved. Checks that the NPC expects it, and processes the respective
--- coroutine.
--- Note: the check for NPC correctness is missing, but it should never matter.
-local function npc_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
-end
-
--- Registered as the function to call whenever the server has recovered a
--- post for a user.
-local function npc_post_reply(ch, sender, letter)
- local w = states[ch]
- if w then
- local w3 = w[3]
- if (w3 == 3 or w3 == 4) then
- if process_npc(w, sender, letter) then
- return
- end
- end
- end
- states[ch] = nil
-end
-
-- Registered as the function to call every tick for each NPC.
local function npc_update(npc)
local h = npc_update_functs[npc];
@@ -308,28 +91,7 @@ end
-- Registered as the function to call every tick.
-- Checks for scheduled function calls and cleans obsolete connections.
local function update()
- -- check the scheduler
check_schedule()
-
- -- Run every minute only, in order not to overload the server.
- if not timer then return end
- timer = timer - 1
- if timer ~= 0 then return end
- -- Free connections that have been inactive for 3-4 minutes.
- for k, w in pairs(states) do
- local t = w[4] - 1
- if t == 0 then
- states[k] = nil
- else
- w[4] = t
- end
- end
- -- Restart timer if there are still some pending states.
- if next(states) then
- timer = 600
- else
- timer = nil
- end
end
-- Registers a function so that is is executed during map initialization.
@@ -459,12 +221,6 @@ end
mana.on_update(update)
mana.on_npc_start(npc_start)
-mana.on_npc_next(npc_next)
-mana.on_npc_choose(npc_choose)
-mana.on_npc_integer(npc_integer)
-mana.on_npc_string(npc_string)
-mana.on_npc_quest_reply(npc_quest_reply)
-mana.on_npc_post_reply(npc_post_reply)
mana.on_npc_update(npc_update)
mana.on_create_npc_delayed(create_npc_delayed)
diff --git a/src/game-server/autoattack.h b/src/game-server/autoattack.h
index e7b853d..a1e22ae 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 14b49bc..bf16e26 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 7649842..1b31c61 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 052b296..aa032c9 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 1ca6b1b..4bff9af 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 5669ebe..b9cf7bb 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 974fc4c..3b415fe 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 86d2be4..0125e84 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 540925b..5053070 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 c9337d5..dfa64d0 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 e26fd9e..57b8dc1 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 0a49e83..8bc72d3 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 2a25528..89ea480 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 9ef069d..6312d1c 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 2fa3a0b..b55f204 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