500 lines
14 KiB
C++
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;
|
|
}
|