Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * Eluna Lua Engine
- * TrinityCore
- * Sympathy
- */
- // The way ScriptMgr in Trinity works is very simple;
- // When a event is fired it will execute every corrosponding class with that method.
- // That is, it has it's own call back system making the hard work done for us - we just need to hook that up from the Lua Engine to create a new instance of a class every time we create a hook.
- // we can't inject directly into script mgr because there's no way for us to pass a C function to it, so we have to make the ScriptMgr call the LuaEventMgr.
- #ifndef __ELUNA__H
- #define __ELUNA__H
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <string>
- #include <set>
- #include <hash_map>
- #include "Common.h"
- #include "Scripting/ScriptMgr.h"
- // includes for types..
- #include "Entities/Unit/Unit.h"
- #include "Entities/Player/Player.h"
- #include "Entities/Creature/Creature.h"
- #include "Entities/Item/Item.h"
- // AI imports
- #include "CreatureAI.h"
- #include "ScriptedCreature.h"
- extern "C" {
- #include "../lua/core/lua.h"
- #include "../lua/core/lualib.h"
- #include "../lua/core/lauxlib.h"
- };
- class LuaCreatureAI;
- struct LoadedScripts
- {
- std::set<std::string> luaFiles;
- };
- enum REGISTER_TYPE
- {
- REGTYPE_PLAYER,
- REGTYPE_GOSSIP,
- REGTYPE_COUNT
- };
- enum PlayerEvents
- {
- PLAYER_EVENT_ON_LOGIN = 1,
- PLAYER_EVENT_ON_LOGOUT = 2,
- PLAYER_EVENT_ON_CHARACTER_CREATE= 3,
- PLAYER_EVENT_ON_CHARACTER_DELETE= 4,
- PLAYER_EVENT_ON_CHAT = 5,
- PLAYER_EVENT_ON_KILL_PLAYER = 6,
- PLAYER_EVENT_COUNT
- };
- enum CreatureEvents
- {
- CREATURE_EVENT_ON_COMBAT = 1,
- CREATURE_EVENT_COUNT
- };
- enum GossipEvents
- {
- GOSSIP_EVENT_ON_HELLO = 1,
- GOSSIP_EVENT_ON_SELECT = 2,
- GOSSIP_EVENT_COUNT
- };
- template<class T>
- struct ElunaRegister
- {
- const char* name;
- int(*mfunc)(lua_State*, T*);
- };
- template<typename T> ElunaRegister<T>* GetTableForType();
- template<typename T> const char* GetTName();
- class Eluna
- {
- friend class ElunaScript;
- friend class ScriptMgr;
- static Eluna* LuaEngine;
- static ElunaScript* Script;
- public:
- static Eluna* get() { return LuaEngine; }
- static ElunaScript* getScript() { return Script; }
- // Maps to hold the binds
- typedef std::map<int, std::vector<uint16>> ElunaBindingMap; // Stores general-purpose hooks
- ElunaBindingMap m_playerEventBinds;
- ElunaBindingMap m_gossipEventBinds;
- static void initialize_tables()
- {
- // PlayerEvents
- for(int i = 0; i < PLAYER_EVENT_COUNT; i++) {
- std::vector<uint16> vector;
- get()->m_playerEventBinds.insert(std::pair<int, std::vector<uint16>>(i, vector));
- }
- // GossipEvents
- for(int i = 0; i < GOSSIP_EVENT_COUNT; i++) {
- std::vector<uint16> vector;
- get()->m_gossipEventBinds.insert(std::pair<int,std::vector<uint16>>(i, vector));
- }
- }
- static void restart()
- {
- // unref everything
- for(ElunaBindingMap::iterator itr = get()->m_playerEventBinds.begin(); itr != get()->m_playerEventBinds.end();
- itr++)
- {
- for(std::vector<uint16>::iterator _itr = itr->second.begin(); _itr != itr->second.end(); _itr++)
- {
- lua_unref(get()->m_luaState, (*_itr));
- }
- itr->second.clear(); // clear the vector
- }
- for(ElunaBindingMap::iterator itr = get()->m_gossipEventBinds.begin(); itr != get()->m_gossipEventBinds.end();
- itr++)
- {
- for(std::vector<uint16>::iterator _itr = itr->second.begin(); _itr != itr->second.end(); _itr++)
- {
- lua_unref(get()->m_luaState, (*_itr));
- }
- itr->second.clear(); // clear the vector
- }
- // close state
- lua_close(get()->m_luaState);
- // restart
- get()->Start();
- }
- ~Eluna()
- {
- m_playerEventBinds.clear();
- delete Script;
- }
- lua_State* m_luaState;
- void Start();
- void Register(uint8 regtype, uint32 id, uint32 evt, uint16 functionRef);
- void Unload();
- static void report(lua_State* /*L*/);
- void BeginCall(uint16 fReference)
- {
- lua_settop(m_luaState, 0); //stack should be empty
- lua_getref(m_luaState, fReference);
- }
- bool ExecuteCall(uint8 params, uint8 res)
- {
- bool ret = true;
- int top = lua_gettop(m_luaState);
- if(strcmp(luaL_typename(m_luaState,top-params), "function") )
- {
- ret = false;
- if(params > 0)
- {
- for(int i = top; i >= (top-params); i--)
- {
- if(!lua_isnone(m_luaState, i) )
- lua_remove(m_luaState, i);
- }
- }
- }
- else
- {
- if(lua_pcall(m_luaState,params,res,0) )
- {
- report(m_luaState);
- ret = false;
- }
- }
- return ret;
- }
- void EndCall(uint8 res)
- {
- for(int i = res; i > 0; i--)
- {
- if(!lua_isnone(m_luaState,res))
- lua_remove(m_luaState,res);
- }
- }
- void registerGlobalVariables(lua_State* L);
- void PushPlayer(lua_State*L, Player* pPlayer);
- void PushCreature(lua_State* L, Creature*);
- void PushLong(lua_State*L, uint64);
- void PushInteger(lua_State*, uint32);
- void PushString(lua_State*, const char*);
- void PushGroup(lua_State*, Group*);
- void PushGuild(lua_State*, Guild*);
- void PushUnit(lua_State*, Unit*);
- void LoadEngine();
- void LoadDirectory(char* Dirname, LoadedScripts* lscr);
- static void initialize();
- Player* CHECK_PLAYER(lua_State* L, int narg)
- {
- if(L == NULL) return ElunaTemplate<Player>::check(m_luaState, narg);
- else return ElunaTemplate<Player>::check(L, narg);
- }
- Group* CHECK_GROUP(lua_State* L, int narg)
- {
- if(L == NULL) return ElunaTemplate<Group>::check(m_luaState, narg);
- else return ElunaTemplate<Group>::check(L, narg);
- }
- Guild* CHECK_GUILD(lua_State* L, int narg)
- {
- if(L == NULL) return ElunaTemplate<Guild>::check(m_luaState, narg);
- else return ElunaTemplate<Guild>::check(L, narg);
- }
- Creature* CHECK_CREATURE(lua_State* L, int narg)
- {
- if(L == NULL) return ElunaTemplate<Creature>::check(m_luaState, narg);
- else return ElunaTemplate<Creature>::check(L, narg);
- }
- protected:
- template<typename T>
- class ElunaTemplate
- {
- public:
- typedef int (*m_funcptr)(lua_State* L, T* ptr);
- typedef struct { const char* name; m_funcptr mfunc; } ElunaRegister;
- static void Register(lua_State* L)
- {
- lua_newtable(L);
- int methods = lua_gettop(L);
- luaL_newmetatable(L, GetTName<T>());
- int metatable = lua_gettop(L);
- // store method table in globals so that
- // scripts can add functions in Lua
- lua_pushvalue(L, methods);
- lua_setglobal(L, GetTName<T>());
- // hide metatable
- lua_pushvalue(L, methods);
- lua_setfield(L, metatable, "__metatable");
- lua_pushvalue(L, methods);
- lua_setfield(L, metatable, "__index");
- lua_pushcfunction(L, tostringT);
- lua_setfield(L, metatable, "__tostring");
- lua_pushcfunction(L, gcT);
- lua_setfield(L, metatable, "__gc");
- lua_newtable(L);
- lua_setmetatable(L, methods);
- // fill method table.
- if(GetMethodTable<T>() == NULL)
- {
- lua_pop(L, 2);
- return;
- }
- for (ElunaRegister* l = ((ElunaRegister*)GetMethodTable<T>()); l->name; l++)
- {
- lua_pushstring(L, l->name);
- lua_pushlightuserdata(L, (void*)l);
- lua_pushcclosure(L, thunk, 1);
- lua_settable(L, methods);
- }
- lua_pop(L, 2);
- }
- static int push(lua_State* L, T* obj, bool gc = false)
- {
- if (!obj)
- {
- lua_pushnil(L);
- return lua_gettop(L);
- }
- luaL_getmetatable(L, GetTName<T>());
- if(lua_isnil(L, -1))
- luaL_error(L, "%s missing metatable", GetTName<T>());
- int idxMt = lua_gettop(L);
- T** ptrHold = (T**)lua_newuserdata(L, sizeof(T**));
- int ud = lua_gettop(L);
- if(ptrHold != NULL)
- {
- *ptrHold = obj;
- lua_pushvalue(L, idxMt);
- lua_setmetatable(L, -2);
- char name[32];
- tostring(name, obj);
- lua_getfield(L, LUA_REGISTRYINDEX, "DO NOT TRASH");
- if(lua_isnil(L, -1))
- {
- luaL_newmetatable(L, "DO NOT TRASH");
- lua_pop(L, 1);
- }
- lua_getfield(L, LUA_REGISTRYINDEX, "DO NOT TRASH");
- if(gc == false)
- {
- lua_pushboolean(L, 1);
- lua_setfield(L, -2, name);
- }
- lua_pop(L, 1);
- }
- lua_settop(L, ud);
- lua_replace(L, idxMt);
- lua_settop(L, idxMt);
- return idxMt;
- }
- static T* check(lua_State* L, int narg)
- {
- T** ptrHold = static_cast<T**>(lua_touserdata(L, narg));
- if(ptrHold == NULL)
- return NULL;
- return *ptrHold;
- }
- private:
- static int thunk(lua_State* L)
- {
- T* obj = check(L, 1); // get self
- lua_remove(L, 1); // remove self
- ElunaRegister* l = static_cast<ElunaRegister*>(lua_touserdata(L, lua_upvalueindex(1)));
- return l->mfunc(L, obj);
- }
- static int gcT(lua_State* L)
- {
- T* obj = check(L, 1);
- if(obj == NULL) return 0;
- lua_getfield(L, LUA_REGISTRYINDEX, "DO NO TRASH");
- if(lua_istable(L, -1))
- {
- char name[32];
- tostring(name, obj);
- lua_getfield(L, -1, std::string(name).c_str());
- if(lua_isnil(L, -1))
- {
- delete obj;
- obj = NULL;
- }
- }
- return 1;
- }
- static int tostringT(lua_State* L)
- {
- char buff[32];
- T** ptrHold = (T**)lua_touserdata(L, 1);
- T* obj = *ptrHold;
- sprintf(buff, "%p", obj);
- lua_pushfstring(L, "%s (%s)", GetTName<T>(), buff);
- return 1;
- }
- inline static void tostring(char* buff, void* obj)
- {
- sprintf(buff, "%p", obj);
- }
- static int index(lua_State* L)
- {
- lua_getglobal(L, GetTName<T>());
- const char* key = lua_tostring(L, 2);
- if(lua_istable(L, - 1))
- {
- lua_pushvalue(L, 2);
- lua_rawget(L, -2);
- if(lua_isnil(L, -1))
- {
- lua_getmetatable(L, -2);
- if(lua_istable(L, -1))
- {
- lua_getfield(L, -1, "__index");
- if(lua_isfunction(L, -1))
- {
- lua_pushvalue(L, 1);
- lua_pushvalue(L, 2);
- lua_pcall(L, 2, 1, 0);
- }
- else if(lua_istable(L, -1))
- lua_getfield(L, -1, key);
- else
- lua_pushnil(L);
- }
- else
- lua_pushnil(L);
- }
- else if(lua_istable(L, -1))
- {
- lua_pushvalue(L, 2);
- lua_rawget(L, -2);
- }
- }
- else
- lua_pushnil(L);
- lua_insert(L, 1);
- lua_settop(L, 1);
- return 1;
- }
- };
- };
- class GUID_MGR
- {
- static const char * GetName() { return "WoWGUID"; }
- public:
- static void Register(lua_State * L) {
- luaL_newmetatable(L,GetName());
- int mt = lua_gettop(L);
- //Hide metatable.
- lua_pushnil(L);
- lua_setfield(L,mt,"__metatable");
- //nil gc method
- lua_pushnil(L);
- lua_setfield(L,mt,"__gc");
- //set our tostring method
- lua_pushcfunction(L,_tostring);
- lua_setfield(L,mt,"__tostring");
- //nil __index field
- lua_pushnil(L);
- lua_setfield(L,mt,"__index");
- //set __newindex method
- lua_pushcfunction(L,_newindex);
- lua_setfield(L,mt,"__newindex");
- //no call method
- lua_pushnil(L);
- lua_setfield(L,mt,"__call");
- //pop metatable
- lua_pop(L,1);
- }
- static uint64 check(lua_State * L, int narg)
- {
- uint64 GUID = 0;
- uint64 * ptrHold = (uint64*)lua_touserdata(L,narg);
- if(ptrHold != NULL)
- GUID = *ptrHold;
- return GUID;
- }
- static int push(lua_State *L, uint64 guid)
- {
- int index = 0;
- if(guid == 0)
- {
- lua_pushnil(L);
- index = lua_gettop(L);
- }
- else
- {
- luaL_getmetatable(L,GetName());
- if(lua_isnoneornil(L,-1) )
- luaL_error(L,"%s metatable not found!. \n",GetName());
- else
- {
- int mt = lua_gettop(L);
- uint64* guidHold = (uint64*)lua_newuserdata(L,sizeof(uint64));
- int ud = lua_gettop(L);
- if(guidHold == NULL)
- luaL_error(L,"Lua tried to allocate size %d of memory and failed! \n",sizeof(uint64*));
- else
- {
- (*guidHold) = guid;
- lua_pushvalue(L,mt);
- lua_setmetatable(L,ud);
- lua_replace(L,mt);
- lua_settop(L,mt);
- index = mt;
- }
- }
- }
- return index;
- }
- private:
- GUID_MGR() {}
- //This method prints formats the GUID in hexform and pushes to the stack.
- static int _tostring(lua_State * L)
- {
- uint64 GUID = GUID_MGR::check(L,1);
- if(GUID == 0)
- lua_pushnil(L);
- else {
- char buff[32];
- sprintf(buff,"%X",GUID);
- lua_pushfstring(L,"%s",buff);
- }
- return 1;
- }
- static int _newindex(lua_State *L)
- {
- //Remove table, key, and value
- lua_remove(L,1);
- lua_remove(L,1);
- lua_remove(L,1);
- luaL_error(L,"OPERATION PROHIBITED!\n");
- return 0;
- }
- };
- class ElunaScript : public ScriptObject
- {
- public:
- static ElunaScript* get() {
- return Eluna::Script;
- }
- ElunaScript(char const* name) : ScriptObject(name)
- {
- ScriptMgr::ScriptRegistry<ElunaScript>::_scriptIdCounter = 0;
- ScriptMgr::ScriptRegistry<ElunaScript>::AddScript(this);
- }
- bool IsDatabaseBound() const { return false; }
- ~ElunaScript()
- {
- delete this;
- }
- /// PLAYER EVENTS
- void OnPlayerLogin(Player* pPlayer)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_LOGIN).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_LOGIN).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->ExecuteCall(1, 0);
- }
- }
- void OnPlayerLogout(Player* pPlayer)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_LOGOUT).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_LOGOUT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->ExecuteCall(1, 0);
- }
- }
- void OnCharacterCreate(Player* pPlayer)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHARACTER_CREATE).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHARACTER_CREATE).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->ExecuteCall(1, 0);
- }
- }
- void OnCharacterDeleted(uint64 guid)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHARACTER_DELETE).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHARACTER_DELETE).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- GUID_MGR::push(Eluna::get()->m_luaState, guid);
- Eluna::get()->ExecuteCall(1, 0);
- }
- }
- void OnChat(Player* pPlayer, uint32 type, uint32 language, std::string& msg)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, type);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, language);
- Eluna::get()->PushString(Eluna::get()->m_luaState, msg.c_str());
- Eluna::get()->ExecuteCall(4, 0);
- }
- }
- void OnChat(Player* pPlayer, uint32 type, uint32 language, std::string& msg, Player* reciever)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, type);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, language);
- Eluna::get()->PushString(Eluna::get()->m_luaState, msg.c_str());
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, reciever);
- Eluna::get()->ExecuteCall(5, 0);
- }
- }
- void OnChat(Player* pPlayer, uint32 type, uint32 language, std::string& msg, Group* pGroup)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, type);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, language);
- Eluna::get()->PushString(Eluna::get()->m_luaState, msg.c_str());
- Eluna::get()->PushGroup(Eluna::get()->m_luaState, pGroup);
- Eluna::get()->ExecuteCall(5, 0);
- }
- }
- void OnChat(Player* pPlayer, uint32 type, uint32 language, std::string& msg, Guild* pGuild)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_CHAT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, type);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, language);
- Eluna::get()->PushString(Eluna::get()->m_luaState, msg.c_str());
- Eluna::get()->PushGuild(Eluna::get()->m_luaState, pGuild);
- Eluna::get()->ExecuteCall(5, 0);
- }
- }
- void OnPvPKill(Player* pKiller, Player* pKilled)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_KILL_PLAYER).begin(); itr != Eluna::get()->m_playerEventBinds.at(PLAYER_EVENT_ON_KILL_PLAYER).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pKiller);
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pKilled);
- Eluna::get()->ExecuteCall(2, 0);
- }
- }
- /// GOSSIP EVENTS
- bool OnGossipHello(Player* pPlayer, Creature* pCreature)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_gossipEventBinds.at(GOSSIP_EVENT_ON_HELLO).begin(); itr != Eluna::get()->m_gossipEventBinds.at(GOSSIP_EVENT_ON_HELLO).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushCreature(Eluna::get()->m_luaState, pCreature);
- Eluna::get()->ExecuteCall(2, 0);
- }
- return true;
- }
- bool OnGossipSelect(Player* pPlayer, Creature* pCreature, uint32 sender, uint32 action)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_gossipEventBinds.at(GOSSIP_EVENT_ON_SELECT).begin(); itr != Eluna::get()->m_gossipEventBinds.at(GOSSIP_EVENT_ON_SELECT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushCreature(Eluna::get()->m_luaState, pCreature);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, sender);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, action);
- Eluna::get()->ExecuteCall(4, 0);
- }
- return true;
- }
- bool OnGossipSelectCode(Player* pPlayer, Creature* pCreature, uint32 sender, uint32 action, const char* code)
- {
- std::vector<uint16>::iterator itr;
- for(itr = Eluna::get()->m_gossipEventBinds.at(GOSSIP_EVENT_ON_SELECT).begin(); itr != Eluna::get()->m_gossipEventBinds.at(GOSSIP_EVENT_ON_SELECT).end();
- itr++)
- {
- Eluna::get()->BeginCall((*itr));
- Eluna::get()->PushPlayer(Eluna::get()->m_luaState, pPlayer);
- Eluna::get()->PushCreature(Eluna::get()->m_luaState, pCreature);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, sender);
- Eluna::get()->PushInteger(Eluna::get()->m_luaState, action);
- Eluna::get()->PushString(Eluna::get()->m_luaState, code);
- Eluna::get()->ExecuteCall(5, 0);
- }
- return true;
- }
- };
- #endif
Add Comment
Please, Sign In to add comment