knightonline/server/GameServer/LuaEngine.cpp

500 lines
14 KiB
C++

#include "stdafx.h"
#include "LuaEngine.h"
#include "../shared/RWLock.h"
#include "User.h"
#include "Npc.h"
// define global functions to be called from Lua (e.g. myrand())
DEFINE_LUA_FUNCTION_TABLE(g_globalFunctions,
MAKE_LUA_FUNCTION(CheckPercent)
MAKE_LUA_FUNCTION(HowmuchItem)
MAKE_LUA_FUNCTION(ShowMap)
MAKE_LUA_FUNCTION(CheckNation)
MAKE_LUA_FUNCTION(CheckClass)
MAKE_LUA_FUNCTION(CheckLevel)
MAKE_LUA_FUNCTION(CheckSkillPoint)
MAKE_LUA_FUNCTION(SaveEvent)
MAKE_LUA_FUNCTION(CheckGiveSlot)
MAKE_LUA_FUNCTION(CheckExchange)
MAKE_LUA_FUNCTION(RunExchange)
MAKE_LUA_FUNCTION(RunSelectExchange)
MAKE_LUA_FUNCTION(SearchQuest)
MAKE_LUA_FUNCTION(NpcMsg)
MAKE_LUA_FUNCTION(ShowEffect)
MAKE_LUA_FUNCTION(ShowNpcEffect)
MAKE_LUA_FUNCTION(ExistMonsterQuestSub)
MAKE_LUA_FUNCTION(CountMonsterQuestSub)
MAKE_LUA_FUNCTION(CountMonsterQuestMain)
MAKE_LUA_FUNCTION(PromoteKnight)
MAKE_LUA_FUNCTION(CheckClanGrade)
MAKE_LUA_FUNCTION(CheckClanPoint)
MAKE_LUA_FUNCTION(CheckLoyalty)
MAKE_LUA_FUNCTION(SelectMsg)
MAKE_LUA_FUNCTION(CastSkill)
MAKE_LUA_FUNCTION(GetName)
MAKE_LUA_FUNCTION(GetAccountName)
MAKE_LUA_FUNCTION(GetZoneID)
MAKE_LUA_FUNCTION(GetX)
MAKE_LUA_FUNCTION(GetY)
MAKE_LUA_FUNCTION(GetZ)
MAKE_LUA_FUNCTION(GetNation)
MAKE_LUA_FUNCTION(GetLevel)
MAKE_LUA_FUNCTION(GetCoins)
MAKE_LUA_FUNCTION(GetInnCoins)
MAKE_LUA_FUNCTION(GetLoyalty)
MAKE_LUA_FUNCTION(GetRebLevel)
MAKE_LUA_FUNCTION(GetMonthlyLoyalty)
MAKE_LUA_FUNCTION(GetManner)
MAKE_LUA_FUNCTION(isWarrior)
MAKE_LUA_FUNCTION(isRogue)
MAKE_LUA_FUNCTION(isMage)
MAKE_LUA_FUNCTION(isPriest)
MAKE_LUA_FUNCTION(isInClan)
MAKE_LUA_FUNCTION(isClanLeader)
MAKE_LUA_FUNCTION(isInParty)
MAKE_LUA_FUNCTION(isPartyLeader)
MAKE_LUA_FUNCTION(isKing)
// Shortcuts for lazy people
MAKE_LUA_FUNCTION(hasCoins)
MAKE_LUA_FUNCTION(hasInnCoins)
MAKE_LUA_FUNCTION(hasLoyalty)
MAKE_LUA_FUNCTION(hasMonthlyLoyalty)
MAKE_LUA_FUNCTION(hasManner)
MAKE_LUA_FUNCTION(SendBoard)
MAKE_LUA_FUNCTION(SendReported)
// Here lie the useful methods.
MAKE_LUA_FUNCTION(GiveItem)
MAKE_LUA_FUNCTION(RobItem)
MAKE_LUA_FUNCTION(CheckExistItem)
MAKE_LUA_FUNCTION(GoldGain)
MAKE_LUA_FUNCTION(GoldLose)
MAKE_LUA_FUNCTION(ExpChange)
MAKE_LUA_FUNCTION(GiveCash)
MAKE_LUA_FUNCTION(TempleEventJoin)
MAKE_LUA_FUNCTION(GiveLoyalty)
MAKE_LUA_FUNCTION(RobLoyalty)
MAKE_LUA_FUNCTION(NpcSay) // dialog
MAKE_LUA_FUNCTION(CheckWeight)
MAKE_LUA_FUNCTION(CheckExistEvent)
MAKE_LUA_FUNCTION(isRoomForItem) // FindSlotForItem()
MAKE_LUA_FUNCTION(SendNameChange)
MAKE_LUA_FUNCTION(SendClanNameChange)
MAKE_LUA_FUNCTION(SendStatSkillDistribute)
MAKE_LUA_FUNCTION(SendPetUpgrade)
MAKE_LUA_FUNCTION(SendRepurchase)
MAKE_LUA_FUNCTION(ResetSkillPoints)
MAKE_LUA_FUNCTION(ResetStatPoints)
MAKE_LUA_FUNCTION(PromoteUserNovice)
MAKE_LUA_FUNCTION(PromoteUser)
MAKE_LUA_FUNCTION(RobAllItemParty)
MAKE_LUA_FUNCTION(CheckWaiting)
MAKE_LUA_FUNCTION(ZoneChange)
MAKE_LUA_FUNCTION(ZoneChangeParty)
MAKE_LUA_FUNCTION(ZoneChangeClan)
MAKE_LUA_FUNCTION(KissUser)
MAKE_LUA_FUNCTION(ChangeManner)
MAKE_LUA_FUNCTION(GetClass)
MAKE_LUA_FUNCTION(CheckKnight)
MAKE_LUA_FUNCTION(CheckStatPoint)
MAKE_LUA_FUNCTION(RobClanPoint)
MAKE_LUA_FUNCTION(CheckBeefRoastVictory)
MAKE_LUA_FUNCTION(RequestPersonalRankReward)
MAKE_LUA_FUNCTION(RequestReward)
MAKE_LUA_FUNCTION(RunCountExchange)
MAKE_LUA_FUNCTION(GetMaxExchange)
MAKE_LUA_FUNCTION(GetUserDailyOp)
MAKE_LUA_FUNCTION(GetEventTrigger)
MAKE_LUA_FUNCTION(GetPremium)
MAKE_LUA_FUNCTION(CheckWarVictory)
MAKE_LUA_FUNCTION(CheckMiddleStatueCapture)
MAKE_LUA_FUNCTION(MoveMiddleStatue)
MAKE_LUA_FUNCTION(LevelChange)
MAKE_LUA_FUNCTION(GivePremium)
MAKE_LUA_FUNCTION(GiveKnightCash)
MAKE_LUA_FUNCTION(RollDice)
MAKE_LUA_FUNCTION(CheckMonsterChallengeTime)
MAKE_LUA_FUNCTION(CheckMonsterChallengeUserCount)
MAKE_LUA_FUNCTION(CheckEventZoneTime)
MAKE_LUA_FUNCTION(CheckEventZoneUserCount)
MAKE_LUA_FUNCTION(GetPVPMonumentNation)
MAKE_LUA_FUNCTION(SendNationChange)
MAKE_LUA_FUNCTION(GetRace)
MAKE_LUA_FUNCTION(JobChange)
);
CLuaEngine::CLuaEngine() : m_lock(new RWLock())
{
}
CLuaScript::CLuaScript() : m_luaState(nullptr)
{
}
/**
* @brief Initialise Lua scripts.
*
* @return true if it succeeds, false if it fails.
*/
bool CLuaEngine::Initialise()
{
printf("Started up Lua engine (built with %s)\n", LUA_RELEASE);
// TODO: Initialise a pool of scripts (enough for 1 per worker thread).
return m_luaScript.Initialise();
}
/**
* @brief Initialises a Lua script state.
*
* @return true if it succeeds, false if it fails.
*/
bool CLuaScript::Initialise()
{
Guard lock(m_lock);
// Lua already initialised?
if (m_luaState != nullptr)
{
printf("ERROR: Lua script already initialised. Cannot reinitialise.\n");
return false;
}
// Create a new state.
m_luaState = luaL_newstate();
if (m_luaState == nullptr)
{
printf("ERROR: Failed to initialise Lua script. Not enough memory.\n");
return false;
}
// Expose scripts to Lua libraries
// May be preferable to limit these, but for now we won't stress too much.
luaL_openlibs(m_luaState);
/* globals */
// push the global table onto the stack so we can set our globals
lua_pushglobaltable(m_luaState);
// setup our global funcs...
luaL_setfuncs(m_luaState, g_globalFunctions, 0);
/* objects */
// bind our classes
lua_bindclass(m_luaState, CUser);
lua_bindclass(m_luaState, CNpc);
return true;
}
/**
* @brief TODO: Pull an available script for use.
*
* @return null if it fails, else.
*/
CLuaScript * CLuaEngine::SelectAvailableScript()
{
return &m_luaScript;
}
/**
* @brief Attempts to executes a Lua script.
* If it has not been compiled already, it will compile the script
* and cache it in the internal script map.
*
* @param pUser The user running the script.
* @param pNpc The NPC attached to the script.
* @param nEventID Identifier for the event.
* @param bSelectedReward The reward selected, if applicable.
* @param filename The script's filename.
*
* @return true if it succeeds, false if it fails.
*/
bool CLuaEngine::ExecuteScript(CUser * pUser, CNpc * pNpc, int32 nEventID, int8 bSelectedReward, const char * filename)
{
ScriptBytecodeMap::iterator itr;
bool result = false;
m_lock->AcquireReadLock();
itr = m_scriptMap.find(filename);
if (itr == m_scriptMap.end())
{
// Build full path to script
std::string szPath = LUA_SCRIPT_DIRECTORY;
szPath += filename;
// Release the read lock (we're not reading anymore)
m_lock->ReleaseReadLock();
// Attempt to compile
BytecodeBuffer bytecode;
bytecode.reserve(LUA_SCRIPT_BUFFER_SIZE);
if (!SelectAvailableScript()->CompileScript(szPath.c_str(), bytecode))
{
printf("ERROR: Could not compile Lua script.\n");
printf("FILE: %s\n", szPath.c_str());
printf("USER: %s\n", pUser->GetName().c_str());
printf("ZONE: %d\n", pUser->GetZoneID());
printf("NPC ID: %d\n", pNpc->m_sSid);
printf("-\n");
return false;
}
// Acquire the write lock (we're adding the compiled script)
m_lock->AcquireWriteLock();
// Add the script to our map
m_scriptMap[filename] = bytecode;
// Now that we have the bytecode, we can use it.
result = SelectAvailableScript()->ExecuteScript(pUser, pNpc, nEventID, bSelectedReward,
filename, bytecode);
// Done using the lock.
m_lock->ReleaseWriteLock();
}
else
{
// Already have the bytecode, so now we need to use it.
result = SelectAvailableScript()->ExecuteScript(pUser, pNpc, nEventID, bSelectedReward,
filename, itr->second);
// Done using the lock.
m_lock->ReleaseReadLock();
}
std::string EventMessage;
EventMessage = string_format("NpcID = %d - EventID = %d - Lua = %s", pNpc->m_sSid, nEventID, filename);
Packet EventNotice(WIZ_CHAT, uint8(PUBLIC_CHAT));
EventNotice << pUser->GetNation() << pUser->GetSocketID() << uint8(0) << EventMessage;
pUser->Send(&EventNotice);
return result;
}
/**
* @brief Attempts to compile a Lua script.
*
* @param filename Filename of the script.
* @param buffer The buffer to store the script's compiled bytecode.
*
* @return true if it succeeds, false if it fails.
*/
bool CLuaScript::CompileScript(const char * filename, BytecodeBuffer & buffer)
{
// ensure that we wait until the last user's done executing their script.
Guard lock(m_lock);
/* Attempt to load the file */
int err = luaL_loadfile(m_luaState, filename);
// If something bad happened, try to find an error.
if (err != LUA_OK)
{
RetrieveLoadError(err, filename);
return false;
}
// Everything's OK so far, the script has been loaded, now we need to start dumping it to bytecode.
err = lua_dump(m_luaState, (lua_Writer)LoadBytecodeChunk, &buffer);
if (err
|| buffer.empty())
{
printf("ERROR: Failed to dump the Lua script `%s` to bytecode.\n", filename);
return false;
}
// Compiled!
return true;
}
/**
* @brief Callback for lua_dump() to read in each chunk of bytecode.
*
* @param L The associated lua_State.
* @param bytes The chunk of bytecode being dumped.
* @param len The number of bytes of bytecode in this chunk.
* @param buffer The buffer to store this chunk of bytecode in.
*
* @return The bytecode chunk.
*/
int CLuaScript::LoadBytecodeChunk(lua_State * L, uint8 * bytes, size_t len, BytecodeBuffer * buffer)
{
for (size_t i = 0; i < len; i++)
buffer->push_back(bytes[i]);
return 0;
}
/**
* @brief Executes the Lua script from bytecode.
*
* @param pUser The user running the script.
* @param pNpc The NPC attached to the script.
* @param nEventID Identifier for the event.
* @param bSelectedReward The reward selected, if applicable.
* @param filename The script's filename for debugging purposes.
* @param bytecode The script's compiled bytecode.
*
* @return true if it succeeds, false if it fails.
*/
bool CLuaScript::ExecuteScript(CUser * pUser, CNpc * pNpc, int32 nEventID, int8 bSelectedReward, const char * filename, BytecodeBuffer & bytecode)
{
// Ensure that we wait until the last user's done executing their script.
Guard lock(m_lock);
/* Attempt to run the script. */
// Load the buffer with our bytecode.
int err = luaL_loadbuffer(m_luaState, reinterpret_cast<const char *>(&bytecode[0]), bytecode.size(), nullptr);
if (err != LUA_OK)
{
RetrieveLoadError(err, filename);
return false;
}
lua_tsetglobal(m_luaState, "UID", pUser->GetID());
lua_tsetglobal(m_luaState, "STEP", bSelectedReward);
lua_tsetglobal(m_luaState, "EVENT", nEventID);
// Try calling the script's entry point
err = lua_pcall(m_luaState,
0, // no arguments
0, // 0 returned values
0); // no error handler
// Nothing returned, so we can finish up here.
if (err == LUA_OK)
{
lua_settop(m_luaState, 0);
return true;
}
// Attempt to provide somewhat informative errors to help the user figure out what's wrong.
switch (err)
{
case LUA_ERRRUN:
printf("ERROR: A runtime error occurred within Lua script.\n");
printf("FILE: %s\n", filename);
printf("USER: %s\n", pUser->GetName().c_str());
printf("ZONE: %d\n", pUser->GetZoneID());
printf("NPC ID: %d\n", pNpc->m_sSid);
printf("-\n");
break;
case LUA_ERRMEM:
printf("ERROR: Unable to allocate memory during execution of Lua script.\n");
printf("FILE: %s\n", filename);
printf("USER: %s\n", pUser->GetName().c_str());
printf("ZONE: %d\n", pUser->GetZoneID());
printf("NPC ID: %d\n", pNpc->m_sSid);
printf("-\n");
break;
case LUA_ERRERR:
printf("ERROR: An error occurred during Lua script, Error handler failed.\n");
printf("FILE: %s\n", filename);
printf("USER: %s\n", pUser->GetName().c_str());
printf("ZONE: %d\n", pUser->GetZoneID());
printf("NPC ID: %d\n", pNpc->m_sSid);
printf("-\n");
break;
default:
printf("ERROR: An unknown error occurred in Lua script.\n");
printf("FILE: %s\n", filename);
printf("USER: %s\n", pUser->GetName().c_str());
printf("ZONE: %d\n", pUser->GetZoneID());
printf("NPC ID: %d\n", pNpc->m_sSid);
printf("-\n");
break;
}
// Is there an error set? That can be more useful than our generic error.
if (lua_isstring(m_luaState, -1))
{
printf("ERROR: [%s] The following error was provided.\n",filename);
printf("MESSAGE: %s\n", lua_to<const char *>(m_luaState, -1));
printf("-\n");
}
lua_settop(m_luaState, 0);
return false;
}
/**
* @brief Retrieves the associated error for a script load operation.
*
* @param err The error.
* @param filename Filename of the file.
*/
void CLuaScript::RetrieveLoadError(int err, const char * filename)
{
switch (err)
{
case LUA_ERRFILE:
printf("ERROR: Unable to load Lua script `%s`.\n", filename);
break;
case LUA_ERRSYNTAX:
printf("ERROR: There was a error with the syntax of Lua script `%s`.\n", filename);
break;
case LUA_ERRMEM:
printf("ERROR: Unable to allocate memory for Lua script `%s`.\n", filename);
break;
default:
printf("ERROR: An unknown error occurred while loading Lua script `%s`.\n", filename);
break;
}
// Is there an error set? That can be more useful than our generic error.
if (lua_isstring(m_luaState, -1))
{
printf("ERROR: %s", lua_to<const char *>(m_luaState, -1));
}
}
/**
* @brief Waits for & shuts down the current Lua script.
*/
void CLuaScript::Shutdown()
{
Guard lock(m_lock);
// Seems silly right now, but it ensures we wait
// until a script is finished its execution before
// we proceed. Cleanup will continue as normal.
}
/**
* @brief Shuts down the Lua script pool.
*/
void CLuaEngine::Shutdown()
{
m_lock->AcquireWriteLock();
// TODO: Script pool.
m_luaScript.Shutdown();
m_lock->ReleaseWriteLock();
}
CLuaScript::~CLuaScript()
{
Guard lock(m_lock);
if (m_luaState != nullptr)
lua_close(m_luaState);
}
CLuaEngine::~CLuaEngine()
{
m_scriptMap.clear();
delete m_lock;
}