/* * The Mana Server * Copyright (C) 2007-2010 The Mana World Development Team * Copyright (C) 2010 The Mana Developers * * This file is part of The Mana Server. * * The Mana Server is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * The Mana Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with The Mana Server. If not, see . */ #include "luascript.h" #include "scripting/luautil.h" #include "scripting/scriptmanager.h" #include "game-server/character.h" #include "utils/logger.h" #include #include Script::Ref LuaScript::mDeathNotificationCallback; Script::Ref LuaScript::mRemoveNotificationCallback; const char LuaScript::registryKey = 0; LuaScript::~LuaScript() { lua_close(mRootState); } void LuaScript::prepare(Ref function) { assert(nbArgs == -1); assert(function.isValid()); 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(thread)->mState; nbArgs = 0; } void LuaScript::push(int v) { assert(nbArgs >= 0); ::push(mCurrentState, v); ++nbArgs; } void LuaScript::push(const std::string &v) { assert(nbArgs >= 0); ::push(mCurrentState, v); ++nbArgs; } void LuaScript::push(Entity *v) { assert(nbArgs >= 0); ::push(mCurrentState, v); ++nbArgs; } void LuaScript::push(const std::list &itemList) { assert(nbArgs >= 0); int position = 0; lua_createtable(mCurrentState, itemList.size(), 0); int itemTable = lua_gettop(mCurrentState); for (const InventoryItem &inventoryItem : itemList) { // create the item structure std::map item; item["id"] = inventoryItem.itemId; item["amount"] = inventoryItem.amount; pushSTLContainer(mCurrentState, item); lua_rawseti(mCurrentState, itemTable, ++position); } ++nbArgs; } int LuaScript::execute(const Context &context) { assert(nbArgs >= 0); const Context *previousContext = mContext; mContext = &context; const int tmpNbArgs = nbArgs; nbArgs = -1; int res = lua_pcall(mCurrentState, tmpNbArgs, 1, 1); if (res || !(lua_isnil(mCurrentState, -1) || lua_isnumber(mCurrentState, -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(mCurrentState, 1); return 0; } res = lua_tointeger(mCurrentState, -1); lua_pop(mCurrentState, 1); mContext = previousContext; return res; } bool LuaScript::resume() { assert(nbArgs >= 0); assert(mCurrentThread); const Context *previousContext = mContext; mContext = &mCurrentThread->getContext(); const int tmpNbArgs = nbArgs; nbArgs = -1; #if LUA_VERSION_NUM < 502 int result = lua_resume(mCurrentState, tmpNbArgs); #else int result = lua_resume(mCurrentState, nullptr, tmpNbArgs); #endif 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); mContext = previousContext; 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(mRootState, -1)); // If there is already a callback set, replace it if (function.isValid()) luaL_unref(mRootState, LUA_REGISTRYINDEX, function.value); function.value = luaL_ref(mRootState, LUA_REGISTRYINDEX); } void LuaScript::unref(Ref &ref) { if (ref.isValid()) { luaL_unref(mRootState, LUA_REGISTRYINDEX, ref.value); ref.value = -1; } } void LuaScript::load(const char *prog, const char *name, const Context &context) { const Context *previousContext = mContext; mContext = &context; 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(mRootState, -1)); break; case LUA_ERRMEM: LOG_ERROR("Memory allocation error while loading Lua script"); break; } lua_pop(mRootState, 1); } else if (lua_pcall(mRootState, 0, 0, 1)) { LOG_ERROR("Failure while initializing Lua script: " << lua_tostring(mRootState, -1)); lua_pop(mRootState, 1); } mContext = previousContext; } void LuaScript::processDeathEvent(Entity *entity) { if (mDeathNotificationCallback.isValid()) { prepare(mDeathNotificationCallback); push(entity); //TODO: get and push a list of creatures who contributed to killing the // being. This might be very interesting for scripting quests. Script::execute(entity->getMap()); } } void LuaScript::processRemoveEvent(Entity *entity) { if (mRemoveNotificationCallback.isValid()) { prepare(mRemoveNotificationCallback); push(entity); //TODO: get and push a list of creatures who contributed to killing the // being. This might be very interesting for scripting quests. Script::execute(entity->getMap()); } } /** * Called when the server has recovered the value of a quest variable. */ void LuaScript::getQuestCallback(Entity *q, const std::string &value, Script *script) { auto *characterComponent = q->getComponent(); Script::Thread *thread = characterComponent->getNpcThread(); if (!thread || thread->mState != Script::ThreadExpectingString) return; script->prepareResume(thread); script->push(value); characterComponent->resumeNpcThread(); } /** * Called when the server has recovered the post for a user. */ void LuaScript::getPostCallback(Entity *q, const std::string &sender, const std::string &letter, Script *script) { auto *characterComponent = q->getComponent(); Script::Thread *thread = characterComponent->getNpcThread(); if (!thread || thread->mState != Script::ThreadExpectingTwoStrings) return; script->prepareResume(thread); script->push(sender); script->push(letter); characterComponent->resumeNpcThread(); } 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(mScript); luaL_unref(luaScript->mRootState, LUA_REGISTRYINDEX, mRef); }