/* * Copyright (C) 2010 - 2016 Eluna Lua Engine * This program is free software licensed under GPL version 3 * Please see the included DOCS/LICENSE.md for more information */ #include "Hooks.h" #include "LuaEngine.h" #include "BindingMap.h" #include "ElunaEventMgr.h" #include "ElunaIncludes.h" #include "ElunaTemplate.h" #include "ElunaUtility.h" #include "ElunaCreatureAI.h" #include "ElunaInstanceAI.h" #if defined(TRINITY_PLATFORM) && defined(TRINITY_PLATFORM_WINDOWS) #if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS #define ELUNA_WINDOWS #endif #elif defined(AC_PLATFORM) && defined(AC_PLATFORM_WINDOWS) #if AC_PLATFORM == AC_PLATFORM_WINDOWS #define ELUNA_WINDOWS #endif #elif defined(PLATFORM) && defined(PLATFORM_WINDOWS) #if PLATFORM == PLATFORM_WINDOWS #define ELUNA_WINDOWS #endif #else #error Eluna could not determine platform #endif // Some dummy includes containing BOOST_VERSION: // ObjectAccessor.h Config.h Log.h #if !defined MANGOS #define USING_BOOST #endif #ifdef USING_BOOST #include #else #include #include #include #endif extern "C" { // Base lua libraries #include "lua.h" #include "lualib.h" #include "lauxlib.h" // Additional lua libraries }; Eluna::ScriptList Eluna::lua_scripts; Eluna::ScriptList Eluna::lua_extensions; std::string Eluna::lua_folderpath; std::string Eluna::lua_requirepath; Eluna* Eluna::GEluna = NULL; bool Eluna::reload = false; bool Eluna::initialized = false; Eluna::LockType Eluna::lock; extern void RegisterFunctions(Eluna* E); void Eluna::Initialize() { LOCK_ELUNA; ASSERT(!IsInitialized()); #if defined TRINITY || AZEROTHCORE // For instance data the data column needs to be able to hold more than 255 characters (tinytext) // so we change it to TEXT automatically on startup CharacterDatabase.DirectExecute("ALTER TABLE `instance` CHANGE COLUMN `data` `data` TEXT NOT NULL"); #endif LoadScriptPaths(); // Must be before creating GEluna // This is checked on Eluna creation initialized = true; // Create global eluna GEluna = new Eluna(); } void Eluna::Uninitialize() { LOCK_ELUNA; ASSERT(IsInitialized()); delete GEluna; GEluna = NULL; lua_scripts.clear(); lua_extensions.clear(); initialized = false; } void Eluna::LoadScriptPaths() { uint32 oldMSTime = ElunaUtil::GetCurrTime(); lua_scripts.clear(); lua_extensions.clear(); #if defined(AZEROTHCORE) lua_folderpath = eConfigMgr->GetOption("Eluna.ScriptPath", "lua_scripts"); #else lua_folderpath = eConfigMgr->GetStringDefault("Eluna.ScriptPath", "lua_scripts"); #endif #ifndef ELUNA_WINDOWS if (lua_folderpath[0] == '~') if (const char* home = getenv("HOME")) lua_folderpath.replace(0, 1, home); #endif ELUNA_LOG_INFO("[Eluna]: Searching scripts from `{}`", lua_folderpath); lua_requirepath.clear(); GetScripts(lua_folderpath); // Erase last ; if (!lua_requirepath.empty()) lua_requirepath.erase(lua_requirepath.end() - 1); ELUNA_LOG_DEBUG("[Eluna]: Loaded {} scripts in {} ms", lua_scripts.size() + lua_extensions.size(), ElunaUtil::GetTimeDiff(oldMSTime)); } void Eluna::_ReloadEluna() { LOCK_ELUNA; ASSERT(IsInitialized()); eWorld->SendServerMessage(SERVER_MSG_STRING, "Reloading Eluna..."); // Remove all timed events sEluna->eventMgr->SetStates(LUAEVENT_STATE_ERASE); // Close lua sEluna->CloseLua(); // Reload script paths LoadScriptPaths(); // Open new lua and libaraies sEluna->OpenLua(); // Run scripts from laoded paths sEluna->RunScripts(); reload = false; } Eluna::Eluna() : event_level(0), push_counter(0), enabled(false), L(NULL), eventMgr(NULL), httpManager(), ServerEventBindings(NULL), PlayerEventBindings(NULL), GuildEventBindings(NULL), GroupEventBindings(NULL), VehicleEventBindings(NULL), BGEventBindings(NULL), PacketEventBindings(NULL), CreatureEventBindings(NULL), CreatureGossipBindings(NULL), GameObjectEventBindings(NULL), GameObjectGossipBindings(NULL), ItemEventBindings(NULL), ItemGossipBindings(NULL), PlayerGossipBindings(NULL), MapEventBindings(NULL), InstanceEventBindings(NULL), CreatureUniqueBindings(NULL) { ASSERT(IsInitialized()); OpenLua(); // Replace this with map insert if making multithread version // Set event manager. Must be after setting sEluna // on multithread have a map of state pointers and here insert this pointer to the map and then save a pointer of that pointer to the EventMgr eventMgr = new EventMgr(&Eluna::GEluna); } Eluna::~Eluna() { ASSERT(IsInitialized()); CloseLua(); delete eventMgr; eventMgr = NULL; } void Eluna::CloseLua() { OnLuaStateClose(); DestroyBindStores(); // Must close lua state after deleting stores and mgr if (L) lua_close(L); L = NULL; instanceDataRefs.clear(); continentDataRefs.clear(); } void Eluna::OpenLua() { #if defined(AZEROTHCORE) enabled = eConfigMgr->GetOption("Eluna.Enabled", true); #else enabled = eConfigMgr->GetBoolDefault("Eluna.Enabled", true); #endif if (!IsEnabled()) { ELUNA_LOG_INFO("[Eluna]: Eluna is disabled in config"); return; } L = luaL_newstate(); lua_pushlightuserdata(L, this); lua_setfield(L, LUA_REGISTRYINDEX, ELUNA_STATE_PTR); CreateBindStores(); // open base lua libraries luaL_openlibs(L); // open additional lua libraries // Register methods and functions RegisterFunctions(this); // Set lua require folder paths (scripts folder structure) lua_getglobal(L, "package"); lua_pushstring(L, lua_requirepath.c_str()); lua_setfield(L, -2, "path"); lua_pushstring(L, ""); // erase cpath lua_setfield(L, -2, "cpath"); lua_pop(L, 1); } void Eluna::CreateBindStores() { DestroyBindStores(); ServerEventBindings = new BindingMap< EventKey >(L); PlayerEventBindings = new BindingMap< EventKey >(L); GuildEventBindings = new BindingMap< EventKey >(L); GroupEventBindings = new BindingMap< EventKey >(L); VehicleEventBindings = new BindingMap< EventKey >(L); BGEventBindings = new BindingMap< EventKey >(L); PacketEventBindings = new BindingMap< EntryKey >(L); CreatureEventBindings = new BindingMap< EntryKey >(L); CreatureGossipBindings = new BindingMap< EntryKey >(L); GameObjectEventBindings = new BindingMap< EntryKey >(L); GameObjectGossipBindings = new BindingMap< EntryKey >(L); ItemEventBindings = new BindingMap< EntryKey >(L); ItemGossipBindings = new BindingMap< EntryKey >(L); PlayerGossipBindings = new BindingMap< EntryKey >(L); MapEventBindings = new BindingMap< EntryKey >(L); InstanceEventBindings = new BindingMap< EntryKey >(L); CreatureUniqueBindings = new BindingMap< UniqueObjectKey >(L); } void Eluna::DestroyBindStores() { delete ServerEventBindings; delete PlayerEventBindings; delete GuildEventBindings; delete GroupEventBindings; delete VehicleEventBindings; delete PacketEventBindings; delete CreatureEventBindings; delete CreatureGossipBindings; delete GameObjectEventBindings; delete GameObjectGossipBindings; delete ItemEventBindings; delete ItemGossipBindings; delete PlayerGossipBindings; delete BGEventBindings; delete MapEventBindings; delete InstanceEventBindings; delete CreatureUniqueBindings; ServerEventBindings = NULL; PlayerEventBindings = NULL; GuildEventBindings = NULL; GroupEventBindings = NULL; VehicleEventBindings = NULL; PacketEventBindings = NULL; CreatureEventBindings = NULL; CreatureGossipBindings = NULL; GameObjectEventBindings = NULL; GameObjectGossipBindings = NULL; ItemEventBindings = NULL; ItemGossipBindings = NULL; PlayerGossipBindings = NULL; BGEventBindings = NULL; MapEventBindings = NULL; InstanceEventBindings = NULL; CreatureUniqueBindings = NULL; } void Eluna::AddScriptPath(std::string filename, const std::string& fullpath) { ELUNA_LOG_DEBUG("[Eluna]: AddScriptPath Checking file `{}`", fullpath); // split file name std::size_t extDot = filename.find_last_of('.'); if (extDot == std::string::npos) return; std::string ext = filename.substr(extDot); filename = filename.substr(0, extDot); // check extension and add path to scripts to load if (ext != ".lua" && ext != ".dll" && ext != ".so" && ext != ".ext") return; bool extension = ext == ".ext"; LuaScript script; script.fileext = ext; script.filename = filename; script.filepath = fullpath; script.modulepath = fullpath.substr(0, fullpath.length() - filename.length() - ext.length()); if (extension) lua_extensions.push_back(script); else lua_scripts.push_back(script); ELUNA_LOG_DEBUG("[Eluna]: AddScriptPath add path `{}`", fullpath); } // Finds lua script files from given path (including subdirectories) and pushes them to scripts void Eluna::GetScripts(std::string path) { ELUNA_LOG_DEBUG("[Eluna]: GetScripts from path `{}`", path); #ifdef USING_BOOST boost::filesystem::path someDir(path); boost::filesystem::directory_iterator end_iter; if (boost::filesystem::exists(someDir) && boost::filesystem::is_directory(someDir)) { lua_requirepath += path + "/?.lua;" + path + "/?.ext;" + path + "/?.dll;" + path + "/?.so;"; for (boost::filesystem::directory_iterator dir_iter(someDir); dir_iter != end_iter; ++dir_iter) { std::string fullpath = dir_iter->path().generic_string(); // Check if file is hidden #ifdef ELUNA_WINDOWS DWORD dwAttrib = GetFileAttributes(fullpath.c_str()); if (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_HIDDEN)) continue; #else std::string name = dir_iter->path().filename().generic_string().c_str(); if (name[0] == '.') continue; #endif // load subfolder if (boost::filesystem::is_directory(dir_iter->status())) { GetScripts(fullpath); continue; } if (boost::filesystem::is_regular_file(dir_iter->status())) { // was file, try add std::string filename = dir_iter->path().filename().generic_string(); AddScriptPath(filename, fullpath); } } } #else ACE_Dirent dir; if (dir.open(path.c_str()) == -1) // Error opening directory, return return; lua_requirepath += path + "/?.lua;" + path + "/?.ext;" + path + "/?.dll;" + path + "/?.so;"; ACE_DIRENT *directory = 0; while ((directory = dir.read())) { // Skip the ".." and "." files. if (ACE::isdotdir(directory->d_name)) continue; std::string fullpath = path + "/" + directory->d_name; // Check if file is hidden #ifdef ELUNA_WINDOWS DWORD dwAttrib = GetFileAttributes(fullpath.c_str()); if (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_HIDDEN)) continue; #else std::string name = directory->d_name; if (name[0] == '.') continue; #endif ACE_stat stat_buf; if (ACE_OS::lstat(fullpath.c_str(), &stat_buf) == -1) continue; // load subfolder if ((stat_buf.st_mode & S_IFMT) == (S_IFDIR)) { GetScripts(fullpath); continue; } // was file, try add std::string filename = directory->d_name; AddScriptPath(filename, fullpath); } #endif } static bool ScriptPathComparator(const LuaScript& first, const LuaScript& second) { return first.filepath < second.filepath; } void Eluna::RunScripts() { LOCK_ELUNA; if (!IsEnabled()) return; uint32 oldMSTime = ElunaUtil::GetCurrTime(); uint32 count = 0; ScriptList scripts; lua_extensions.sort(ScriptPathComparator); lua_scripts.sort(ScriptPathComparator); scripts.insert(scripts.end(), lua_extensions.begin(), lua_extensions.end()); scripts.insert(scripts.end(), lua_scripts.begin(), lua_scripts.end()); std::unordered_map loaded; // filename, path lua_getglobal(L, "package"); // Stack: package luaL_getsubtable(L, -1, "loaded"); // Stack: package, modules int modules = lua_gettop(L); for (ScriptList::const_iterator it = scripts.begin(); it != scripts.end(); ++it) { // Check that no duplicate names exist if (loaded.find(it->filename) != loaded.end()) { ELUNA_LOG_ERROR("[Eluna]: Error loading `{}`. File with same name already loaded from `{}`, rename either file", it->filepath, loaded[it->filename]); continue; } loaded[it->filename] = it->filepath; lua_getfield(L, modules, it->filename.c_str()); // Stack: package, modules, module if (!lua_isnoneornil(L, -1)) { lua_pop(L, 1); ELUNA_LOG_DEBUG("[Eluna]: `{}` was already loaded or required", it->filepath); continue; } lua_pop(L, 1); // Stack: package, modules if (luaL_loadfile(L, it->filepath.c_str())) { // Stack: package, modules, errmsg ELUNA_LOG_ERROR("[Eluna]: Error loading `{}`", it->filepath); Report(L); // Stack: package, modules continue; } // Stack: package, modules, filefunc if (ExecuteCall(0, 1)) { // Stack: package, modules, result if (lua_isnoneornil(L, -1) || (lua_isboolean(L, -1) && !lua_toboolean(L, -1))) { // if result evaluates to false, change it to true lua_pop(L, 1); Push(L, true); } lua_setfield(L, modules, it->filename.c_str()); // Stack: package, modules // successfully loaded and ran file ELUNA_LOG_DEBUG("[Eluna]: Successfully loaded `{}`", it->filepath); ++count; continue; } } // Stack: package, modules lua_pop(L, 2); ELUNA_LOG_INFO("[Eluna]: Executed {} Lua scripts in {} ms", count, ElunaUtil::GetTimeDiff(oldMSTime)); OnLuaStateOpen(); } void Eluna::InvalidateObjects() { ++callstackid; #ifdef TRINITY ASSERT(callstackid, "Callstackid overflow"); #else ASSERT(callstackid && "Callstackid overflow"); #endif } void Eluna::Report(lua_State* _L) { const char* msg = lua_tostring(_L, -1); ELUNA_LOG_ERROR("{}", msg); lua_pop(_L, 1); } // Borrowed from http://stackoverflow.com/questions/12256455/print-stacktrace-from-c-code-with-embedded-lua int Eluna::StackTrace(lua_State *_L) { // Stack: errmsg if (!lua_isstring(_L, -1)) /* 'message' not a string? */ return 1; /* keep it intact */ // Stack: errmsg, debug lua_getglobal(_L, "debug"); if (!lua_istable(_L, -1)) { lua_pop(_L, 1); return 1; } // Stack: errmsg, debug, traceback lua_getfield(_L, -1, "traceback"); if (!lua_isfunction(_L, -1)) { lua_pop(_L, 2); return 1; } lua_pushvalue(_L, -3); /* pass error message */ lua_pushinteger(_L, 1); /* skip this function and traceback */ // Stack: errmsg, debug, traceback, errmsg, 2 lua_call(_L, 2, 1); /* call debug.traceback */ // dirty stack? // Stack: errmsg, debug, tracemsg return 1; } bool Eluna::ExecuteCall(int params, int res) { int top = lua_gettop(L); int base = top - params; // Expected: function, [parameters] ASSERT(base > 0); // Check function type if (!lua_isfunction(L, base)) { ELUNA_LOG_ERROR("[Eluna]: Cannot execute call: registered value is {}, not a function.", luaL_tolstring(L, base, NULL)); ASSERT(false); // stack probably corrupt } #if defined(AZEROTHCORE) bool usetrace = eConfigMgr->GetOption("Eluna.TraceBack", false); #else bool usetrace = eConfigMgr->GetBoolDefault("Eluna.TraceBack", false); #endif if (usetrace) { lua_pushcfunction(L, &StackTrace); // Stack: function, [parameters], traceback lua_insert(L, base); // Stack: traceback, function, [parameters] } // Objects are invalidated when event_level hits 0 ++event_level; int result = lua_pcall(L, params, res, usetrace ? base : 0); --event_level; if (usetrace) { // Stack: traceback, [results or errmsg] lua_remove(L, base); } // Stack: [results or errmsg] // lua_pcall returns 0 on success. // On error print the error and push nils for expected amount of returned values if (result) { // Stack: errmsg Report(L); // Force garbage collect lua_gc(L, LUA_GCCOLLECT, 0); // Push nils for expected amount of results for (int i = 0; i < res; ++i) lua_pushnil(L); // Stack: [nils] return false; } // Stack: [results] return true; } void Eluna::Push(lua_State* luastate) { lua_pushnil(luastate); } void Eluna::Push(lua_State* luastate, const long long l) { ElunaTemplate::Push(luastate, new long long(l)); } void Eluna::Push(lua_State* luastate, const unsigned long long l) { ElunaTemplate::Push(luastate, new unsigned long long(l)); } void Eluna::Push(lua_State* luastate, const long l) { Push(luastate, static_cast(l)); } void Eluna::Push(lua_State* luastate, const unsigned long l) { Push(luastate, static_cast(l)); } void Eluna::Push(lua_State* luastate, const int i) { lua_pushinteger(luastate, i); } void Eluna::Push(lua_State* luastate, const unsigned int u) { lua_pushunsigned(luastate, u); } void Eluna::Push(lua_State* luastate, const double d) { lua_pushnumber(luastate, d); } void Eluna::Push(lua_State* luastate, const float f) { lua_pushnumber(luastate, f); } void Eluna::Push(lua_State* luastate, const bool b) { lua_pushboolean(luastate, b); } void Eluna::Push(lua_State* luastate, const std::string& str) { lua_pushstring(luastate, str.c_str()); } void Eluna::Push(lua_State* luastate, const char* str) { lua_pushstring(luastate, str); } void Eluna::Push(lua_State* luastate, Pet const* pet) { Push(luastate, pet); } void Eluna::Push(lua_State* luastate, TempSummon const* summon) { Push(luastate, summon); } void Eluna::Push(lua_State* luastate, Unit const* unit) { if (!unit) { Push(luastate); return; } switch (unit->GetTypeId()) { case TYPEID_UNIT: Push(luastate, unit->ToCreature()); break; case TYPEID_PLAYER: Push(luastate, unit->ToPlayer()); break; default: ElunaTemplate::Push(luastate, unit); } } void Eluna::Push(lua_State* luastate, WorldObject const* obj) { if (!obj) { Push(luastate); return; } switch (obj->GetTypeId()) { case TYPEID_UNIT: Push(luastate, obj->ToCreature()); break; case TYPEID_PLAYER: Push(luastate, obj->ToPlayer()); break; case TYPEID_GAMEOBJECT: Push(luastate, obj->ToGameObject()); break; case TYPEID_CORPSE: Push(luastate, obj->ToCorpse()); break; default: ElunaTemplate::Push(luastate, obj); } } void Eluna::Push(lua_State* luastate, Object const* obj) { if (!obj) { Push(luastate); return; } switch (obj->GetTypeId()) { case TYPEID_UNIT: Push(luastate, obj->ToCreature()); break; case TYPEID_PLAYER: Push(luastate, obj->ToPlayer()); break; case TYPEID_GAMEOBJECT: Push(luastate, obj->ToGameObject()); break; case TYPEID_CORPSE: Push(luastate, obj->ToCorpse()); break; default: ElunaTemplate::Push(luastate, obj); } } void Eluna::Push(lua_State* luastate, ObjectGuid const guid) { ElunaTemplate::Push(luastate, new unsigned long long(guid.GetRawValue())); } static int CheckIntegerRange(lua_State* luastate, int narg, int min, int max) { double value = luaL_checknumber(luastate, narg); char error_buffer[64]; if (value > max) { snprintf(error_buffer, 64, "value must be less than or equal to %i", max); return luaL_argerror(luastate, narg, error_buffer); } if (value < min) { snprintf(error_buffer, 64, "value must be greater than or equal to %i", min); return luaL_argerror(luastate, narg, error_buffer); } return static_cast(value); } static unsigned int CheckUnsignedRange(lua_State* luastate, int narg, unsigned int max) { double value = luaL_checknumber(luastate, narg); if (value < 0) return luaL_argerror(luastate, narg, "value must be greater than or equal to 0"); if (value > max) { char error_buffer[64]; snprintf(error_buffer, 64, "value must be less than or equal to %u", max); return luaL_argerror(luastate, narg, error_buffer); } return static_cast(value); } template<> bool Eluna::CHECKVAL(lua_State* luastate, int narg) { return lua_toboolean(luastate, narg) != 0; } template<> float Eluna::CHECKVAL(lua_State* luastate, int narg) { return static_cast(luaL_checknumber(luastate, narg)); } template<> double Eluna::CHECKVAL(lua_State* luastate, int narg) { return luaL_checknumber(luastate, narg); } template<> signed char Eluna::CHECKVAL(lua_State* luastate, int narg) { return CheckIntegerRange(luastate, narg, SCHAR_MIN, SCHAR_MAX); } template<> unsigned char Eluna::CHECKVAL(lua_State* luastate, int narg) { return CheckUnsignedRange(luastate, narg, UCHAR_MAX); } template<> short Eluna::CHECKVAL(lua_State* luastate, int narg) { return CheckIntegerRange(luastate, narg, SHRT_MIN, SHRT_MAX); } template<> unsigned short Eluna::CHECKVAL(lua_State* luastate, int narg) { return CheckUnsignedRange(luastate, narg, USHRT_MAX); } template<> int Eluna::CHECKVAL(lua_State* luastate, int narg) { return CheckIntegerRange(luastate, narg, INT_MIN, INT_MAX); } template<> unsigned int Eluna::CHECKVAL(lua_State* luastate, int narg) { return CheckUnsignedRange(luastate, narg, UINT_MAX); } template<> const char* Eluna::CHECKVAL(lua_State* luastate, int narg) { return luaL_checkstring(luastate, narg); } template<> std::string Eluna::CHECKVAL(lua_State* luastate, int narg) { return luaL_checkstring(luastate, narg); } template<> long long Eluna::CHECKVAL(lua_State* luastate, int narg) { if (lua_isnumber(luastate, narg)) return static_cast(CHECKVAL(luastate, narg)); return *(Eluna::CHECKOBJ(luastate, narg, true)); } template<> unsigned long long Eluna::CHECKVAL(lua_State* luastate, int narg) { if (lua_isnumber(luastate, narg)) return static_cast(CHECKVAL(luastate, narg)); return *(Eluna::CHECKOBJ(luastate, narg, true)); } template<> long Eluna::CHECKVAL(lua_State* luastate, int narg) { return static_cast(CHECKVAL(luastate, narg)); } template<> unsigned long Eluna::CHECKVAL(lua_State* luastate, int narg) { return static_cast(CHECKVAL(luastate, narg)); } template<> ObjectGuid Eluna::CHECKVAL(lua_State* luastate, int narg) { return ObjectGuid(uint64((CHECKVAL(luastate, narg)))); } template<> Object* Eluna::CHECKOBJ(lua_State* luastate, int narg, bool error) { Object* obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = ElunaTemplate::Check(luastate, narg, error); return obj; } template<> WorldObject* Eluna::CHECKOBJ(lua_State* luastate, int narg, bool error) { WorldObject* obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = ElunaTemplate::Check(luastate, narg, error); return obj; } template<> Unit* Eluna::CHECKOBJ(lua_State* luastate, int narg, bool error) { Unit* obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = CHECKOBJ(luastate, narg, false); if (!obj) obj = ElunaTemplate::Check(luastate, narg, error); return obj; } template<> ElunaObject* Eluna::CHECKOBJ(lua_State* luastate, int narg, bool error) { return CHECKTYPE(luastate, narg, NULL, error); } ElunaObject* Eluna::CHECKTYPE(lua_State* luastate, int narg, const char* tname, bool error) { if (lua_islightuserdata(luastate, narg)) { if (error) luaL_argerror(luastate, narg, "bad argument : userdata expected, got lightuserdata"); return NULL; } ElunaObject** ptrHold = static_cast(lua_touserdata(luastate, narg)); if (!ptrHold || (tname && (*ptrHold)->GetTypeName() != tname)) { if (error) { char buff[256]; snprintf(buff, 256, "bad argument : %s expected, got %s", tname ? tname : "ElunaObject", ptrHold ? (*ptrHold)->GetTypeName() : luaL_typename(luastate, narg)); luaL_argerror(luastate, narg, buff); } return NULL; } return *ptrHold; } template static int cancelBinding(lua_State *L) { uint64 bindingID = Eluna::CHECKVAL(L, lua_upvalueindex(1)); BindingMap* bindings = (BindingMap*)lua_touserdata(L, lua_upvalueindex(2)); ASSERT(bindings != NULL); bindings->Remove(bindingID); return 0; } template static void createCancelCallback(lua_State* L, uint64 bindingID, BindingMap* bindings) { Eluna::Push(L, bindingID); lua_pushlightuserdata(L, bindings); // Stack: bindingID, bindings lua_pushcclosure(L, &cancelBinding, 2); // Stack: cancel_callback } // Saves the function reference ID given to the register type's store for given entry under the given event int Eluna::Register(lua_State* L, uint8 regtype, uint32 entry, ObjectGuid guid, uint32 instanceId, uint32 event_id, int functionRef, uint32 shots) { uint64 bindingID; switch (regtype) { case Hooks::REGTYPE_SERVER: if (event_id < Hooks::SERVER_EVENT_COUNT) { auto key = EventKey((Hooks::ServerEvents)event_id); bindingID = ServerEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, ServerEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_PLAYER: if (event_id < Hooks::PLAYER_EVENT_COUNT) { auto key = EventKey((Hooks::PlayerEvents)event_id); bindingID = PlayerEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, PlayerEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_GUILD: if (event_id < Hooks::GUILD_EVENT_COUNT) { auto key = EventKey((Hooks::GuildEvents)event_id); bindingID = GuildEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, GuildEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_GROUP: if (event_id < Hooks::GROUP_EVENT_COUNT) { auto key = EventKey((Hooks::GroupEvents)event_id); bindingID = GroupEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, GroupEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_VEHICLE: if (event_id < Hooks::VEHICLE_EVENT_COUNT) { auto key = EventKey((Hooks::VehicleEvents)event_id); bindingID = VehicleEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, VehicleEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_BG: if (event_id < Hooks::BG_EVENT_COUNT) { auto key = EventKey((Hooks::BGEvents)event_id); bindingID = BGEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, BGEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_PACKET: if (event_id < Hooks::PACKET_EVENT_COUNT) { if (entry >= NUM_MSG_TYPES) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::PacketEvents)event_id, entry); bindingID = PacketEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, PacketEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_CREATURE: if (event_id < Hooks::CREATURE_EVENT_COUNT) { if (entry != 0) { if (!eObjectMgr->GetCreatureTemplate(entry)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::CreatureEvents)event_id, entry); bindingID = CreatureEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, CreatureEventBindings); } else { if (guid.IsEmpty()) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "guid was 0!"); return 0; // Stack: (empty) } auto key = UniqueObjectKey((Hooks::CreatureEvents)event_id, guid, instanceId); bindingID = CreatureUniqueBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, CreatureUniqueBindings); } return 1; // Stack: callback } break; case Hooks::REGTYPE_CREATURE_GOSSIP: if (event_id < Hooks::GOSSIP_EVENT_COUNT) { if (!eObjectMgr->GetCreatureTemplate(entry)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::GossipEvents)event_id, entry); bindingID = CreatureGossipBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, CreatureGossipBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_GAMEOBJECT: if (event_id < Hooks::GAMEOBJECT_EVENT_COUNT) { if (!eObjectMgr->GetGameObjectTemplate(entry)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a gameobject with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::GameObjectEvents)event_id, entry); bindingID = GameObjectEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, GameObjectEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_GAMEOBJECT_GOSSIP: if (event_id < Hooks::GOSSIP_EVENT_COUNT) { if (!eObjectMgr->GetGameObjectTemplate(entry)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a gameobject with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::GossipEvents)event_id, entry); bindingID = GameObjectGossipBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, GameObjectGossipBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_ITEM: if (event_id < Hooks::ITEM_EVENT_COUNT) { if (!eObjectMgr->GetItemTemplate(entry)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a item with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::ItemEvents)event_id, entry); bindingID = ItemEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, ItemEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_ITEM_GOSSIP: if (event_id < Hooks::GOSSIP_EVENT_COUNT) { if (!eObjectMgr->GetItemTemplate(entry)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a item with (ID: %d)!", entry); return 0; // Stack: (empty) } auto key = EntryKey((Hooks::GossipEvents)event_id, entry); bindingID = ItemGossipBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, ItemGossipBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_PLAYER_GOSSIP: if (event_id < Hooks::GOSSIP_EVENT_COUNT) { auto key = EntryKey((Hooks::GossipEvents)event_id, entry); bindingID = PlayerGossipBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, PlayerGossipBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_MAP: if (event_id < Hooks::INSTANCE_EVENT_COUNT) { auto key = EntryKey((Hooks::InstanceEvents)event_id, entry); bindingID = MapEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, MapEventBindings); return 1; // Stack: callback } break; case Hooks::REGTYPE_INSTANCE: if (event_id < Hooks::INSTANCE_EVENT_COUNT) { auto key = EntryKey((Hooks::InstanceEvents)event_id, entry); bindingID = InstanceEventBindings->Insert(key, functionRef, shots); createCancelCallback(L, bindingID, InstanceEventBindings); return 1; // Stack: callback } break; } luaL_unref(L, LUA_REGISTRYINDEX, functionRef); std::ostringstream oss; oss << "regtype " << static_cast(regtype) << ", event " << event_id << ", entry " << entry << ", guid " << guid.GetRawValue() << ", instance " << instanceId; luaL_error(L, "Unknown event type (%s)", oss.str().c_str()); return 0; } /* * Cleans up the stack, effectively undoing all Push calls and the Setup call. */ void Eluna::CleanUpStack(int number_of_arguments) { // Stack: event_id, [arguments] lua_pop(L, number_of_arguments + 1); // Add 1 because the caller doesn't know about `event_id`. // Stack: (empty) if (event_level == 0) InvalidateObjects(); } /* * Call a single event handler that was put on the stack with `Setup` and removes it from the stack. * * The caller is responsible for keeping track of how many times this should be called. */ int Eluna::CallOneFunction(int number_of_functions, int number_of_arguments, int number_of_results) { ++number_of_arguments; // Caller doesn't know about `event_id`. ASSERT(number_of_functions > 0 && number_of_arguments > 0 && number_of_results >= 0); // Stack: event_id, [arguments], [functions] int functions_top = lua_gettop(L); int first_function_index = functions_top - number_of_functions + 1; int arguments_top = first_function_index - 1; int first_argument_index = arguments_top - number_of_arguments + 1; // Copy the arguments from the bottom of the stack to the top. for (int argument_index = first_argument_index; argument_index <= arguments_top; ++argument_index) { lua_pushvalue(L, argument_index); } // Stack: event_id, [arguments], [functions], event_id, [arguments] ExecuteCall(number_of_arguments, number_of_results); --functions_top; // Stack: event_id, [arguments], [functions - 1], [results] return functions_top + 1; // Return the location of the first result (if any exist). } CreatureAI* Eluna::GetAI(Creature* creature) { if (!IsEnabled()) return NULL; for (int i = 1; i < Hooks::CREATURE_EVENT_COUNT; ++i) { Hooks::CreatureEvents event_id = (Hooks::CreatureEvents)i; auto entryKey = EntryKey(event_id, creature->GetEntry()); auto uniqueKey = UniqueObjectKey(event_id, creature->GET_GUID(), creature->GetInstanceId()); if (CreatureEventBindings->HasBindingsFor(entryKey) || CreatureUniqueBindings->HasBindingsFor(uniqueKey)) return new ElunaCreatureAI(creature); } return NULL; } InstanceData* Eluna::GetInstanceData(Map* map) { if (!IsEnabled()) return NULL; for (int i = 1; i < Hooks::INSTANCE_EVENT_COUNT; ++i) { Hooks::InstanceEvents event_id = (Hooks::InstanceEvents)i; auto key = EntryKey(event_id, map->GetId()); if (MapEventBindings->HasBindingsFor(key) || InstanceEventBindings->HasBindingsFor(key)) return new ElunaInstanceAI(map); } return NULL; } bool Eluna::HasInstanceData(Map const* map) { if (!map->Instanceable()) return continentDataRefs.find(map->GetId()) != continentDataRefs.end(); else return instanceDataRefs.find(map->GetInstanceId()) != instanceDataRefs.end(); } void Eluna::CreateInstanceData(Map const* map) { ASSERT(lua_istable(L, -1)); int ref = luaL_ref(L, LUA_REGISTRYINDEX); if (!map->Instanceable()) { uint32 mapId = map->GetId(); // If there's another table that was already stored for the map, unref it. auto mapRef = continentDataRefs.find(mapId); if (mapRef != continentDataRefs.end()) { luaL_unref(L, LUA_REGISTRYINDEX, mapRef->second); } continentDataRefs[mapId] = ref; } else { uint32 instanceId = map->GetInstanceId(); // If there's another table that was already stored for the instance, unref it. auto instRef = instanceDataRefs.find(instanceId); if (instRef != instanceDataRefs.end()) { luaL_unref(L, LUA_REGISTRYINDEX, instRef->second); } instanceDataRefs[instanceId] = ref; } } /* * Unrefs the instanceId related events and data * Does all required actions for when an instance is freed. */ void Eluna::FreeInstanceId(uint32 instanceId) { LOCK_ELUNA; if (!IsEnabled()) return; for (int i = 1; i < Hooks::INSTANCE_EVENT_COUNT; ++i) { auto key = EntryKey((Hooks::InstanceEvents)i, instanceId); if (MapEventBindings->HasBindingsFor(key)) MapEventBindings->Clear(key); if (InstanceEventBindings->HasBindingsFor(key)) InstanceEventBindings->Clear(key); if (instanceDataRefs.find(instanceId) != instanceDataRefs.end()) { luaL_unref(L, LUA_REGISTRYINDEX, instanceDataRefs[instanceId]); instanceDataRefs.erase(instanceId); } } } void Eluna::PushInstanceData(lua_State* L, ElunaInstanceAI* ai, bool incrementCounter) { // Check if the instance data is missing (i.e. someone reloaded Eluna). if (!HasInstanceData(ai->instance)) ai->Reload(); // Get the instance data table from the registry. if (!ai->instance->Instanceable()) lua_rawgeti(L, LUA_REGISTRYINDEX, continentDataRefs[ai->instance->GetId()]); else lua_rawgeti(L, LUA_REGISTRYINDEX, instanceDataRefs[ai->instance->GetInstanceId()]); ASSERT(lua_istable(L, -1)); if (incrementCounter) ++push_counter; }