/* * Copyright (C) 2010 - 2014 Eluna Lua Engine * This program is free software licensed under GPL version 3 * Please see the included DOCS/LICENSE.md for more information */ #include #include #include "HookMgr.h" #include "LuaEngine.h" #include "Includes.h" Eluna::ScriptPaths Eluna::scripts; Eluna* Eluna::GEluna = NULL; extern void RegisterFunctions(lua_State* L); void Eluna::Initialize() { uint32 oldMSTime = GetCurrTime(); scripts.clear(); std::string folderpath = eConfigMgr->GetStringDefault("Eluna.ScriptPath", "lua_scripts"); #if PLATFORM == PLATFORM_UNIX || PLATFORM == PLATFORM_APPLE if (folderpath[0] == '~') if (const char* home = getenv("HOME")) folderpath.replace(0, 1, home); #endif ELUNA_LOG_INFO("[Eluna]: Searching scripts from `%s`", folderpath.c_str()); GetScripts(folderpath, scripts); GetScripts(folderpath + "/extensions", scripts); ELUNA_LOG_INFO("[Eluna]: Loaded %u scripts in %u ms", uint32(scripts.size()), GetTimeDiff(oldMSTime)); // Create global eluna new Eluna(); } void Eluna::Uninitialize() { delete GEluna; } void Eluna::ReloadEluna() { Uninitialize(); Initialize(); } Eluna::Eluna(): L(luaL_newstate()), m_EventMgr(new EventMgr(*this)), ServerEventBindings(new EventBind(*this)), PlayerEventBindings(new EventBind(*this)), GuildEventBindings(new EventBind(*this)), GroupEventBindings(new EventBind(*this)), VehicleEventBindings(new EventBind(*this)), PacketEventBindings(new EntryBind(*this)), CreatureEventBindings(new EntryBind(*this)), CreatureGossipBindings(new EntryBind(*this)), GameObjectEventBindings(new EntryBind(*this)), GameObjectGossipBindings(new EntryBind(*this)), ItemEventBindings(new EntryBind(*this)), ItemGossipBindings(new EntryBind(*this)), playerGossipBindings(new EntryBind(*this)) { // open base lua luaL_openlibs(L); RegisterFunctions(L); // Create hidden table with weak values lua_newtable(L); lua_newtable(L); lua_pushstring(L, "v"); lua_setfield(L, -2, "__mode"); lua_setmetatable(L, -2); userdata_table = luaL_ref(L, LUA_REGISTRYINDEX); // Replace this with map insert if making multithread version ASSERT(!Eluna::GEluna); Eluna::GEluna = this; // run scripts RunScripts(scripts); } Eluna::~Eluna() { OnLuaStateClose(); // Replace this with map remove if making multithread version Eluna::GEluna = NULL; delete m_EventMgr; 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; // Must close lua state after deleting stores and mgr lua_close(L); } // Finds lua script files from given path (including subdirectories) and pushes them to scripts void Eluna::GetScripts(std::string path, ScriptPaths& scripts) { ELUNA_LOG_DEBUG("[Eluna]: GetScripts from path `%s`", path.c_str()); ACE_Dirent dir; if (dir.open(path.c_str()) == -1) { ELUNA_LOG_ERROR("[Eluna]: Error No `%s` directory found, creating it", path.c_str()); ACE_OS::mkdir(path.c_str()); return; } 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; 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, scripts); continue; } // was file, check extension ELUNA_LOG_DEBUG("[Eluna]: GetScripts Checking file `%s`", fullpath.c_str()); std::string ext = fullpath.substr(fullpath.length() - 4, 4); if (ext != ".lua" && ext != ".dll") continue; // was correct, add path to scripts to load ELUNA_LOG_DEBUG("[Eluna]: GetScripts add path `%s`", fullpath.c_str()); scripts.erase(fullpath); scripts.insert(fullpath); } } void Eluna::RunScripts(ScriptPaths& scripts) { uint32 count = 0; // load last first to load extensions first for (ScriptPaths::const_reverse_iterator it = scripts.rbegin(); it != scripts.rend(); ++it) { if (!luaL_loadfile(L, it->c_str()) && !lua_pcall(L, 0, 0, 0)) { // successfully loaded and ran file ELUNA_LOG_DEBUG("[Eluna]: Successfully loaded `%s`", it->c_str()); ++count; continue; } ELUNA_LOG_ERROR("[Eluna]: Error loading file `%s`", it->c_str()); report(L); } ELUNA_LOG_DEBUG("[Eluna]: Loaded %u Lua scripts", count); } void Eluna::RemoveRef(const void* obj) { lua_rawgeti(sEluna->L, LUA_REGISTRYINDEX, sEluna->userdata_table); lua_pushfstring(sEluna->L, "%p", obj); lua_gettable(sEluna->L, -2); if (!lua_isnoneornil(sEluna->L, -1)) { lua_pushfstring(sEluna->L, "%p", obj); lua_pushnil(sEluna->L); lua_settable(sEluna->L, -4); } lua_pop(sEluna->L, 2); } void Eluna::report(lua_State* L) { const char* msg = lua_tostring(L, -1); while (msg) { lua_pop(L, 1); ELUNA_LOG_ERROR("%s", msg); msg = lua_tostring(L, -1); } } void Eluna::ExecuteCall(lua_State* L, int params, int res) { int top = lua_gettop(L); luaL_checktype(L, top - params, LUA_TFUNCTION); if (lua_pcall(L, params, res, 0)) report(L); } void Eluna::Push(lua_State* L) { lua_pushnil(L); } void Eluna::Push(lua_State* L, const uint64 l) { std::ostringstream ss; ss << l; Push(L, ss.str()); } void Eluna::Push(lua_State* L, const int64 l) { std::ostringstream ss; ss << l; Push(L, ss.str()); } void Eluna::Push(lua_State* L, const uint32 u) { lua_pushunsigned(L, u); } void Eluna::Push(lua_State* L, const int32 i) { lua_pushinteger(L, i); } void Eluna::Push(lua_State* L, const double d) { lua_pushnumber(L, d); } void Eluna::Push(lua_State* L, const float f) { lua_pushnumber(L, f); } void Eluna::Push(lua_State* L, const bool b) { lua_pushboolean(L, b); } void Eluna::Push(lua_State* L, const std::string str) { lua_pushstring(L, str.c_str()); } void Eluna::Push(lua_State* L, const char* str) { lua_pushstring(L, str); } void Eluna::Push(lua_State* L, Pet const* pet) { Push(L, pet->ToCreature()); } void Eluna::Push(lua_State* L, TempSummon const* summon) { Push(L, summon->ToCreature()); } void Eluna::Push(lua_State* L, Unit const* unit) { if (!unit) { Push(L); return; } switch (unit->GetTypeId()) { case TYPEID_UNIT: Push(L, unit->ToCreature()); break; case TYPEID_PLAYER: Push(L, unit->ToPlayer()); break; default: ElunaTemplate::push(L, unit); } } void Eluna::Push(lua_State* L, WorldObject const* obj) { if (!obj) { Push(L); return; } switch (obj->GetTypeId()) { case TYPEID_UNIT: Push(L, obj->ToCreature()); break; case TYPEID_PLAYER: Push(L, obj->ToPlayer()); break; case TYPEID_GAMEOBJECT: Push(L, obj->ToGameObject()); break; case TYPEID_CORPSE: Push(L, obj->ToCorpse()); break; default: ElunaTemplate::push(L, obj); } } void Eluna::Push(lua_State* L, Object const* obj) { if (!obj) { Push(L); return; } switch (obj->GetTypeId()) { case TYPEID_UNIT: Push(L, obj->ToCreature()); break; case TYPEID_PLAYER: Push(L, obj->ToPlayer()); break; case TYPEID_GAMEOBJECT: Push(L, obj->ToGameObject()); break; case TYPEID_CORPSE: Push(L, obj->ToCorpse()); break; default: ElunaTemplate::push(L, obj); } } template<> bool Eluna::CHECKVAL(lua_State* L, int narg) { return lua_isnumber(L, narg) ? luaL_optnumber(L, narg, 1) ? true : false : lua_toboolean(L, narg); } template<> bool Eluna::CHECKVAL(lua_State* L, int narg, bool def) { return lua_isnone(L, narg) ? def : lua_isnumber(L, narg) ? luaL_optnumber(L, narg, 1) ? true : false : lua_toboolean(L, narg); } template<> float Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checknumber(L, narg); } template<> float Eluna::CHECKVAL(lua_State* L, int narg, float def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optnumber(L, narg, def); } template<> double Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checknumber(L, narg); } template<> double Eluna::CHECKVAL(lua_State* L, int narg, double def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optnumber(L, narg, def); } template<> int8 Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkint(L, narg); } template<> int8 Eluna::CHECKVAL(lua_State* L, int narg, int8 def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optint(L, narg, def); } template<> uint8 Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkunsigned(L, narg); } template<> uint8 Eluna::CHECKVAL(lua_State* L, int narg, uint8 def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optunsigned(L, narg, def); } template<> int16 Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkint(L, narg); } template<> int16 Eluna::CHECKVAL(lua_State* L, int narg, int16 def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optint(L, narg, def); } template<> uint16 Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkunsigned(L, narg); } template<> uint16 Eluna::CHECKVAL(lua_State* L, int narg, uint16 def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optunsigned(L, narg, def); } template<> uint32 Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkunsigned(L, narg); } template<> uint32 Eluna::CHECKVAL(lua_State* L, int narg, uint32 def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optunsigned(L, narg, def); } template<> int32 Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checklong(L, narg); } template<> int32 Eluna::CHECKVAL(lua_State* L, int narg, int32 def) { if (lua_isnoneornil(L, narg) || !lua_isnumber(L, narg)) return def; return luaL_optlong(L, narg, def); } template<> const char* Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkstring(L, narg); } template<> const char* Eluna::CHECKVAL(lua_State* L, int narg, const char* def) { if (lua_isnoneornil(L, narg) || !lua_isstring(L, narg)) return def; return luaL_optstring(L, narg, def); } template<> std::string Eluna::CHECKVAL(lua_State* L, int narg) { return luaL_checkstring(L, narg); } template<> std::string Eluna::CHECKVAL(lua_State* L, int narg, std::string def) { if (lua_isnoneornil(L, narg) || !lua_isstring(L, narg)) return def; return luaL_optstring(L, narg, def.c_str()); } template<> uint64 Eluna::CHECKVAL(lua_State* L, int narg) { const char* c_str = CHECKVAL(L, narg, NULL); if (!c_str) return luaL_argerror(L, narg, "uint64 (as string) expected"); uint64 l = 0; sscanf(c_str, UI64FMTD, &l); return l; } template<> uint64 Eluna::CHECKVAL(lua_State* L, int narg, uint64 def) { const char* c_str = CHECKVAL(L, narg, NULL); if (!c_str) return def; uint64 l = 0; sscanf(c_str, UI64FMTD, &l); return l; } template<> int64 Eluna::CHECKVAL(lua_State* L, int narg) { const char* c_str = CHECKVAL(L, narg, NULL); if (!c_str) return luaL_argerror(L, narg, "int64 (as string) expected"); int64 l = 0; sscanf(c_str, SI64FMTD, &l); return l; } template<> int64 Eluna::CHECKVAL(lua_State* L, int narg, int64 def) { const char* c_str = CHECKVAL(L, narg, NULL); if (!c_str) return def; int64 l = 0; sscanf(c_str, SI64FMTD, &l); return l; } #define TEST_OBJ(T, O, E, F)\ {\ if (!O || !O->F())\ {\ if (E)\ {\ std::string errmsg(ElunaTemplate::tname);\ errmsg += " expected";\ luaL_argerror(L, narg, errmsg.c_str());\ }\ return NULL;\ }\ return O->F();\ } template<> Unit* Eluna::CHECKOBJ(lua_State* L, int narg, bool error) { WorldObject* obj = CHECKOBJ(L, narg, false); TEST_OBJ(Unit, obj, error, ToUnit); } template<> Player* Eluna::CHECKOBJ(lua_State* L, int narg, bool error) { WorldObject* obj = CHECKOBJ(L, narg, false); TEST_OBJ(Player, obj, error, ToPlayer); } template<> Creature* Eluna::CHECKOBJ(lua_State* L, int narg, bool error) { WorldObject* obj = CHECKOBJ(L, narg, false); TEST_OBJ(Creature, obj, error, ToCreature); } template<> GameObject* Eluna::CHECKOBJ(lua_State* L, int narg, bool error) { WorldObject* obj = CHECKOBJ(L, narg, false); TEST_OBJ(GameObject, obj, error, ToGameObject); } template<> Corpse* Eluna::CHECKOBJ(lua_State* L, int narg, bool error) { WorldObject* obj = CHECKOBJ(L, narg, false); TEST_OBJ(Corpse, obj, error, ToCorpse); } #undef TEST_OBJ // Saves the function reference ID given to the register type's store for given entry under the given event void Eluna::Register(uint8 regtype, uint32 id, uint32 evt, int functionRef) { switch (regtype) { case REGTYPE_PACKET: if (evt < NUM_MSG_TYPES) { PacketEventBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_SERVER: if (evt < SERVER_EVENT_COUNT) { ServerEventBindings->Insert(evt, functionRef); return; } break; case REGTYPE_PLAYER: if (evt < PLAYER_EVENT_COUNT) { PlayerEventBindings->Insert(evt, functionRef); return; } break; case REGTYPE_GUILD: if (evt < GUILD_EVENT_COUNT) { GuildEventBindings->Insert(evt, functionRef); return; } break; case REGTYPE_GROUP: if (evt < GROUP_EVENT_COUNT) { GroupEventBindings->Insert(evt, functionRef); return; } break; case REGTYPE_VEHICLE: if (evt < VEHICLE_EVENT_COUNT) { VehicleEventBindings->Insert(evt, functionRef); return; } break; case REGTYPE_CREATURE: if (evt < CREATURE_EVENT_COUNT) { if (!eObjectMgr->GetCreatureTemplate(id)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", id); return; } CreatureEventBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_CREATURE_GOSSIP: if (evt < GOSSIP_EVENT_COUNT) { if (!eObjectMgr->GetCreatureTemplate(id)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", id); return; } CreatureGossipBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_GAMEOBJECT: if (evt < GAMEOBJECT_EVENT_COUNT) { if (!eObjectMgr->GetGameObjectTemplate(id)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a gameobject with (ID: %d)!", id); return; } GameObjectEventBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_GAMEOBJECT_GOSSIP: if (evt < GOSSIP_EVENT_COUNT) { if (!eObjectMgr->GetGameObjectTemplate(id)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a gameobject with (ID: %d)!", id); return; } GameObjectGossipBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_ITEM: if (evt < ITEM_EVENT_COUNT) { if (!eObjectMgr->GetItemTemplate(id)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a item with (ID: %d)!", id); return; } ItemEventBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_ITEM_GOSSIP: if (evt < GOSSIP_EVENT_COUNT) { if (!eObjectMgr->GetItemTemplate(id)) { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a item with (ID: %d)!", id); return; } ItemGossipBindings->Insert(id, evt, functionRef); return; } break; case REGTYPE_PLAYER_GOSSIP: if (evt < GOSSIP_EVENT_COUNT) { playerGossipBindings->Insert(id, evt, functionRef); return; } break; } luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Unknown event type (regtype %d, id %d, event %d)", regtype, id, evt); } void EventBind::Clear() { for (ElunaEntryMap::iterator itr = Bindings.begin(); itr != Bindings.end(); ++itr) { for (ElunaBindingMap::iterator it = itr->second.begin(); it != itr->second.end(); ++it) luaL_unref(E.L, LUA_REGISTRYINDEX, (*it)); itr->second.clear(); } Bindings.clear(); } void EventBind::Insert(int eventId, int funcRef) { Bindings[eventId].push_back(funcRef); } bool EventBind::HasEvents(int eventId) const { if (Bindings.empty()) return false; if (Bindings.find(eventId) == Bindings.end()) return false; return true; } void EntryBind::Clear() { for (ElunaEntryMap::iterator itr = Bindings.begin(); itr != Bindings.end(); ++itr) { for (ElunaBindingMap::const_iterator it = itr->second.begin(); it != itr->second.end(); ++it) luaL_unref(E.L, LUA_REGISTRYINDEX, it->second); itr->second.clear(); } Bindings.clear(); } void EntryBind::Insert(uint32 entryId, int eventId, int funcRef) { if (Bindings[entryId][eventId]) { luaL_unref(E.L, LUA_REGISTRYINDEX, funcRef); // free the unused ref luaL_error(E.L, "A function is already registered for entry (%d) event (%d)", entryId, eventId); } else Bindings[entryId][eventId] = funcRef; } EventMgr::LuaEvent::LuaEvent(Eluna& _E, EventProcessor* _events, int _funcRef, uint32 _delay, uint32 _calls, Object* _obj): E(_E), events(_events), funcRef(_funcRef), delay(_delay), calls(_calls), obj(_obj) { if (_events) E.m_EventMgr->LuaEvents[_events].insert(this); // Able to access the event if we have the processor } EventMgr::LuaEvent::~LuaEvent() { if (events) { // Attempt to remove the pointer from LuaEvents EventMgr::EventMap::const_iterator it = E.m_EventMgr->LuaEvents.find(events); // Get event set if (it != E.m_EventMgr->LuaEvents.end()) E.m_EventMgr->LuaEvents[events].erase(this);// Remove pointer } luaL_unref(E.L, LUA_REGISTRYINDEX, funcRef); // Free lua function ref } bool EventMgr::LuaEvent::Execute(uint64 time, uint32 diff) { bool remove = (calls == 1); if (!remove) events->AddEvent(this, events->CalculateTime(delay)); // Reschedule before calling incase RemoveEvents used lua_rawgeti(E.L, LUA_REGISTRYINDEX, funcRef); Eluna::Push(E.L, funcRef); Eluna::Push(E.L, delay); Eluna::Push(E.L, calls); if (!remove && calls) --calls; Eluna::Push(E.L, obj); Eluna::ExecuteCall(E.L, 4, 0); return remove; // Destory (true) event if not run }