/*
* 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(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 && !defined AZEROTHCORE
#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();
lua_folderpath = eConfigMgr->GetStringDefault("Eluna.ScriptPath", "lua_scripts");
#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 `%s`", lua_folderpath.c_str());
lua_requirepath.clear();
GetScripts(lua_folderpath);
// Erase last ;
if (!lua_requirepath.empty())
lua_requirepath.erase(lua_requirepath.end() - 1);
ELUNA_LOG_DEBUG("[Eluna]: Loaded %u scripts in %u ms", uint32(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),
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()
{
enabled = eConfigMgr->GetBoolDefault("Eluna.Enabled", true);
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 `%s`", fullpath.c_str());
// 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 `%s`", fullpath.c_str());
}
// 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 `%s`", path.c_str());
#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 `%s`. File with same name already loaded from `%s`, rename either file", it->filepath.c_str(), loaded[it->filename].c_str());
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]: `%s` was already loaded or required", it->filepath.c_str());
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 `%s`", it->filepath.c_str());
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 `%s`", it->filepath.c_str());
++count;
continue;
}
}
// Stack: package, modules
lua_pop(L, 2);
ELUNA_LOG_INFO("[Eluna]: Executed %u Lua scripts in %u 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("%s", 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 %s, not a function.", luaL_tolstring(L, base, NULL));
ASSERT(false); // stack probably corrupt
}
bool usetrace = eConfigMgr->GetBoolDefault("Eluna.TraceBack", false);
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