summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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