forked from mjsamet/KOServerProjectv1886
-
Notifications
You must be signed in to change notification settings - Fork 8
/
LuaEngine.cpp
469 lines (412 loc) · 13 KB
/
LuaEngine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
#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(CheckExchange)
MAKE_LUA_FUNCTION(RunExchange)
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(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)
// 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(GiveLoyalty)
MAKE_LUA_FUNCTION(RobLoyalty)
MAKE_LUA_FUNCTION(NpcSay) // dialog
MAKE_LUA_FUNCTION(CheckWeight)
MAKE_LUA_FUNCTION(isRoomForItem) // FindSlotForItem()
MAKE_LUA_FUNCTION(SendNameChange)
MAKE_LUA_FUNCTION(SendStatSkillDistribute)
MAKE_LUA_FUNCTION(ResetSkillPoints)
MAKE_LUA_FUNCTION(ResetStatPoints)
MAKE_LUA_FUNCTION(PromoteUserNovice)
MAKE_LUA_FUNCTION(PromoteUser)
MAKE_LUA_FUNCTION(RobAllItemParty)
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)
);
CLuaEngine::CLuaEngine() : m_lock(new RWLock())
{
}
CLuaScript::CLuaScript() : m_luaState(nullptr), m_lock(new FastMutex())
{
}
/**
* @brief Initialise Lua scripts.
*
* @return true if it succeeds, false if it fails.
*/
bool CLuaEngine::Initialise()
{
printf("Started up Lua engine in %s mode (built with %s)\n", LUA_ENGINE_MODE, 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()
{
FastGuard 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();
#if !defined(LUA_SCRIPT_CACHE_DISABLED)
// Add the script to our map
m_scriptMap[filename] = bytecode;
#endif
// 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();
}
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.
FastGuard 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.
FastGuard 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()
{
m_lock->Acquire();
// Seems silly right now, but it ensures we wait
// until a script is finished its execution before
// we proceed. Cleanup will continue as normal.
m_lock->Release();
}
/**
* @brief Shuts down the Lua script pool.
*/
void CLuaEngine::Shutdown()
{
m_lock->AcquireWriteLock();
// TODO: Script pool.
m_luaScript.Shutdown();
m_lock->ReleaseWriteLock();
}
CLuaScript::~CLuaScript()
{
m_lock->Acquire();
if (m_luaState != nullptr)
lua_close(m_luaState);
m_lock->Release();
delete m_lock;
}
CLuaEngine::~CLuaEngine()
{
m_scriptMap.clear();
delete m_lock;
}