14 Commits
db ... lua

Author SHA1 Message Date
CakeLancelot
9af3b60d4d Fix compilation with MinGW on Windows
Prereqs: mingw-w64-x86_64-luajit, pkg-config
2021-05-12 20:55:40 -05:00
gsemaj
d5fc36f877 Fix bad null check and dynamic ID calculation 2021-05-09 09:36:50 -04:00
aa3dacc84b Fixed stack issue with lEvents 2021-05-09 08:40:39 -04:00
a29de394ee Updated NPCWrapper to use Vec3 2021-05-09 08:40:39 -04:00
ca299e0a5b Added NPC.speed setters & getters 2021-05-09 08:40:39 -04:00
794b881c4d Added NPCWrapper
- NPC.new, NPC:moveTo() & NPC.maxHealth have all been added
2021-05-09 08:40:39 -04:00
e8659962a5 Switched makefile to use pkg-config 2021-05-09 08:40:39 -04:00
2090062c75 Added Player:message() 2021-05-09 08:40:39 -04:00
b25b229eb2 Add Player.instance & Player:teleport() 2021-05-09 08:40:39 -04:00
c2853d9271 Added "/rscripts" command to reload all script states 2021-05-09 08:40:39 -04:00
cb85f7b7c9 Added Player.onChat event & Player:kick() 2021-05-09 08:40:39 -04:00
80f0ff7479 Added World, Entity & Player wrappers
- LuaWrapper.cpp/lua_autopush was updated to support CNSocket*
- Added basic getters added for Entity, (x, y, z & type)
- Added basic getter for Player, (name)
- Added bases getter for World, (version)
- Added World.onPlayerAdded & World.onPlayerRemoved events
2021-05-09 08:40:39 -04:00
2aa23a83de Added Lua Events
- LuaWrapper creates a simple event layer for lua states. Allowing events to have registered callbacks & be waited on until fired
- EventWrapper exposes this api to scripts
2021-05-09 08:40:39 -04:00
43aa4eaeb8 Added base lua manager
- CMakeLists.txt, Makefile, and appveyor now use luajit
- lua/LuaManager.[ch]pp has been added which includes a basic script loader & thread scheduler
- SCRIPTSDIR has been added to settings.[ch]pp, this specifies what directory lua scripts should be loaded from
- Makefile has been updated to include lua/LuaManager.[ch]pp
2021-05-09 08:40:39 -04:00
24 changed files with 1624 additions and 4 deletions

View File

@@ -44,6 +44,7 @@ add_executable(openfusion ${SOURCES})
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
target_link_libraries(openfusion sqlite3)
target_link_libraries(openfusion lua51)
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")

View File

@@ -5,8 +5,10 @@ CXX=clang++
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
CFLAGS=-O3 #-g3 -fsanitize=address
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor #-g3 -fsanitize=address
LDFLAGS=-lpthread -lsqlite3 #-g3 -fsanitize=address
LCFLAGS=`pkg-config --cflags luajit`
LLIBFLAGS=`pkg-config --libs luajit`
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor $(LCFLAGS) #-g3 -fsanitize=address
LDFLAGS=-lpthread -lsqlite3 $(LLIBFLAGS) -lstdc++fs #-g3 -fsanitize=address
# specifies the name of our exectuable
SERVER=bin/fusion
@@ -18,8 +20,8 @@ PROTOCOL_VERSION?=104
WIN_CC=x86_64-w64-mingw32-gcc
WIN_CXX=x86_64-w64-mingw32-g++
WIN_CFLAGS=-O3 #-g3 -fsanitize=address
WIN_CXXFLAGS=-D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor #-g3 -fsanitize=address
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3 #-g3 -fsanitize=address
WIN_CXXFLAGS=-D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor $(LCFLAGS) #-g3 -fsanitize=address
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3 $(LLIBFLAGS) -lstdc++fs #-g3 -fsanitize=address
WIN_SERVER=bin/winfusion.exe
# C code; currently exclusively from vendored libraries
@@ -43,6 +45,12 @@ CXXSRC=\
src/servers/CNLoginServer.cpp\
src/servers/CNShardServer.cpp\
src/servers/Monitor.cpp\
src/lua/LuaManager.cpp\
src/lua/EventWrapper.cpp\
src/lua/WorldWrapper.cpp\
src/lua/EntityWrapper.cpp\
src/lua/PlayerWrapper.cpp\
src/lua/NPCWrapper.cpp\
src/db/init.cpp\
src/db/login.cpp\
src/db/shard.cpp\
@@ -86,6 +94,13 @@ CXXHDR=\
src/servers/CNLoginServer.hpp\
src/servers/CNShardServer.hpp\
src/servers/Monitor.hpp\
src/lua/LuaManager.hpp\
src/lua/LuaWrapper.hpp\
src/lua/EventWrapper.hpp\
src/lua/WorldWrapper.hpp\
src/lua/EntityWrapper.hpp\
src/lua/PlayerWrapper.hpp\
src/lua/NPCWrapper.hpp\
src/db/Database.hpp\
src/db/internal.hpp\
vendor/bcrypt/BCrypt.hpp\

View File

@@ -18,6 +18,8 @@ for:
matrix:
only:
- image: GCP-Linux-Ubuntu2004
install:
- sh: sudo apt install libluajit-5.1-dev -y
build_script:
- ps: |
$versions = "104", "728", "1013"
@@ -48,6 +50,7 @@ for:
- image: GCP-Windows-VS2019
install:
- cmd: vcpkg install sqlite3:x64-windows
- cmd: vcpkg install luajit:x64-windows
- cmd: vcpkg integrate install
build_script:
- ps: |

28
scripts/test.lua Normal file
View File

@@ -0,0 +1,28 @@
print("Hello wtf!")
function onJoin(plr)
print(plr.type .. " " .. plr.name .. " joined from LUA!!")
plr.onChat:listen(function(msg)
print(plr.name .. " said : \'" .. msg .. "\'")
if msg == "kickme" then
plr:kick()
elseif msg == "hi" then
print("hello " .. plr.name)
elseif msg == "pet" then
local dog = NPC.new(plr.x, plr.y, plr.z, 3054)
while wait(2) and plr:exists() do
dog:moveTo(plr.x + math.random(-500, 500), plr.y + math.random(-500, 500), plr.z)
end
end
end)
end
World.onPlayerAdded:listen(onJoin)
for i, plr in ipairs(World.players) do
onJoin(plr)
end
wait(2)
print("Hello world ~2 seconds later! running protcol version " .. World.version)

View File

@@ -19,6 +19,10 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) {
return;
}
// if the player has an onChat Lua event registered, call it
if (plr->onChat != nullptr)
plr->onChat->call(fullChat.c_str());
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__MUTE_FREECHAT)
return;

View File

@@ -10,6 +10,8 @@
#include "Transport.hpp"
#include "Missions.hpp"
#include "lua/LuaManager.hpp"
#include <sstream>
#include <iterator>
#include <math.h>
@@ -1190,6 +1192,12 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
Chat::sendServerMessage(sock, "[PATH] Unknown argument '" + args[1] + "'");
}
static void reloadScriptsCommand(std::string full, std::vector<std::string>& args, CNSocket *sock) {
// reloads all scripts
LuaManager::stopScripts();
LuaManager::loadScripts();
}
static void registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help) {
commands[cmd] = ChatCommand(requiredLevel, handlr, help);
}
@@ -1229,4 +1237,5 @@ void CustomCommands::init() {
registerCommand("unregisterall", 50, unregisterallCommand, "clear all SCAMPER and MSS destinations");
registerCommand("redeem", 100, redeemCommand, "redeem a code item");
registerCommand("path", 30, pathCommand, "edit NPC paths");
registerCommand("rscripts", 30, reloadScriptsCommand, "stops all script states and reloads all scripts");
}

View File

@@ -7,6 +7,8 @@
#include "Chunking.hpp"
#include "Entities.hpp"
#include "lua/LuaWrapper.hpp"
#define ACTIVE_MISSION_COUNT 6
#define PC_MAXHEALTH(level) (925 + 75 * (level))
@@ -87,7 +89,15 @@ struct Player : public Entity {
time_t lastShot = 0;
std::vector<sItemBase> buyback = {};
// lua events
lEvent *onChat = nullptr;
Player() { type = EntityType::PLAYER; }
~Player() {
// if an event was registered, free it
if (onChat != nullptr)
delete onChat;
}
virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override;

View File

@@ -18,6 +18,8 @@
#include "settings.hpp"
#include "lua/LuaManager.hpp"
#include <assert.h>
#include <algorithm>
@@ -40,12 +42,18 @@ static void addPlayer(CNSocket* key, Player plr) {
std::cout << getPlayerName(p) << " has joined!" << std::endl;
std::cout << players.size() << " players" << std::endl;
// call events
LuaManager::playerAdded(key);
}
void PlayerManager::removePlayer(CNSocket* key) {
Player* plr = getPlayer(key);
uint64_t fromInstance = plr->instanceID;
// call events
LuaManager::playerRemoved(key);
Groups::groupKickPlayer(plr);
// remove player's bullets

174
src/lua/EntityWrapper.cpp Normal file
View File

@@ -0,0 +1,174 @@
#include "lua/LuaWrapper.hpp"
#include "lua/LuaManager.hpp"
#include "lua/EntityWrapper.hpp"
#include "Entities.hpp"
#define LIBNAME "Entity"
#define SUPERTBL "__entSUPERCLASSES"
#define ENTYGONESTR "Entity doesn't exist anymore!"
EntityRef* grabBaseEntityRef(lua_State *state, int indx) {
// first, make sure its a userdata
luaL_checktype(state, indx, LUA_TUSERDATA);
// grab the super class table
lua_getfield(state, LUA_REGISTRYINDEX, SUPERTBL);
// grab the userdata
EntityRef *data = (EntityRef*)lua_touserdata(state, indx);
// check if it doesn't have a metatable
if (!lua_getmetatable(state, indx)) {
luaL_typerror(state, indx, LIBNAME);
return NULL;
}
// index the super class table
lua_gettable(state, -2);
// if the index was nil, it doesn't exist
if (lua_isnil(state, -1)) {
lua_pop(state, 1);
luaL_typerror(state, indx, LIBNAME);
return NULL;
}
// it's good :)
lua_pop(state, 1);
return data;
}
// check at index
EntityRef* grabEntityRef(lua_State *state, int indx) {
EntityRef *ref = grabBaseEntityRef(state, indx);
if (ref == NULL || !ref->isValid())
return NULL;
return ref;
}
// =============================================== [[ GETTERS ]] ===============================================
static int ent_getX(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
if (ref == NULL)
return 0;
lua_pushnumber(state, ref->getEntity()->x);
return 1;
}
static int ent_getY(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
if (ref == NULL)
return 0;
lua_pushnumber(state, ref->getEntity()->y);
return 1;
}
static int ent_getZ(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
if (ref == NULL)
return 0;
lua_pushnumber(state, ref->getEntity()->z);
return 1;
}
static int ent_getType(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
if (ref == NULL)
return 0;
// push the type string
switch(ref->type) {
case EntityType::PLAYER:
lua_pushstring(state, "Player");
break;
case EntityType::MOB:
lua_pushstring(state, "Mob");
break;
case EntityType::EGG:
lua_pushstring(state, "Egg");
break;
case EntityType::BUS:
lua_pushstring(state, "Bus");
break;
default: // INVALID, COMBAT_NPC, SIMPLE_NPC
lua_pushstring(state, "Entity");
break;
}
return 1;
}
static luaL_Reg ent_getters[] = {
{"x", ent_getX},
{"y", ent_getY},
{"z", ent_getZ},
{"type", ent_getType},
{0, 0}
};
// =============================================== [[ METHODS ]] ===============================================
static int ent_exists(lua_State *state) {
EntityRef *data = (EntityRef*)grabBaseEntityRef(state, 1);
lua_pushboolean(state, !(data == NULL || !data->isValid()));
return 1;
}
static luaL_Reg ent_methods[] = {
{"exists", ent_exists},
{0, 0}
};
void LuaManager::Entity::init(lua_State *state) {
// register our library as a global (and leave it on the stack)
luaL_register(state, LIBNAME, ent_methods);
lua_pop(state, 1); // pop library table
// will hold our super classes
lua_pushstring(state, SUPERTBL);
lua_newtable(state);
lua_rawset(state, LUA_REGISTRYINDEX);
}
void LuaManager::Entity::registerSuper(lua_State *state, const char *tname) {
// grab the super class table
lua_getfield(state, LUA_REGISTRYINDEX, SUPERTBL);
// grab the metatable
lua_getfield(state, LUA_REGISTRYINDEX, tname);
lua_pushboolean(state, 1);
// finally, set the index
lua_rawset(state, -3);
lua_pop(state, 1); // pop the super class table
}
void LuaManager::Entity::addGetters(lua_State *state) {
luaL_register(state, NULL, ent_getters);
}
void LuaManager::Entity::addMethods(lua_State *state) {
luaL_register(state, NULL, ent_methods);
}
void LuaManager::Entity::push(lua_State *state, EntityRef ref, const char *tname) {
// creates the udata and copies the reference to the udata
EntityRef *ent = (EntityRef*)lua_newuserdata(state, sizeof(EntityRef));
*ent = ref;
// attaches our metatable from the registry to the udata
luaL_getmetatable(state, tname);
lua_setmetatable(state, -2);
}

19
src/lua/EntityWrapper.hpp Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "lua/LuaWrapper.hpp"
#include "lua/LuaManager.hpp"
#include "Entities.hpp"
namespace LuaManager {
namespace Entity {
void init(lua_State *state);
void registerSuper(lua_State *state, const char *tname);
void addGetters(lua_State *state);
void addMethods(lua_State *state);
void push(lua_State *state, EntityRef ref, const char *tname);
}
}

190
src/lua/EventWrapper.cpp Normal file
View File

@@ -0,0 +1,190 @@
/*
This loads the Event library, basically a wrapper for lEvents, allows the lua script to register callbacks or wait for an event to be fired
*/
#include "lua/EventWrapper.hpp"
#include "lua/LuaWrapper.hpp"
#include <unordered_set>
#define LIBNAME "Event"
#define LISTNR "Listener"
typedef lEvent* eventData;
std::unordered_set<lEvent*> activeEvents;
struct lstnrData {
lEvent *event;
uint32_t rawListener;
};
static void pushListener(lua_State *state, lEvent *event, uint32_t listener) {
lstnrData *lstnr = (lstnrData*)lua_newuserdata(state, sizeof(lstnrData));
lstnr->event = event;
lstnr->rawListener = listener;
// attaches our metatable from the registry to the udata
luaL_getmetatable(state, LISTNR);
lua_setmetatable(state, -2);
}
static void pushEvent(lua_State *state, lEvent *event) {
eventData *eData = (eventData*)lua_newuserdata(state, sizeof(eventData));
*eData = event;
// attaches our metatable from the registry to the udata
luaL_getmetatable(state, LIBNAME);
lua_setmetatable(state, -2);
}
static lEvent *grabEvent(lua_State *state, int indx) {
// first, make sure its a userdata
luaL_checktype(state, indx, LUA_TUSERDATA);
// now, check and make sure its our libraries metatable attached to this userdata
eventData *event = (eventData*)luaL_checkudata(state, indx, LIBNAME);
if (event == NULL) {
luaL_typerror(state, indx, LIBNAME);
return NULL;
}
// if the event is not active return NULL and don't throw an error (this is an easily forgivable user-error)
if (activeEvents.find(*event) == activeEvents.end())
return NULL;
// return the pointer to the lEvent
return *event;
}
static lstnrData *grabListener(lua_State *state, int indx) {
// first, make sure its a userdata
luaL_checktype(state, indx, LUA_TUSERDATA);
// now, check and make sure its our libraries metatable attached to this userdata
lstnrData *lstnr = (lstnrData*)luaL_checkudata(state, indx, LISTNR);
if (lstnr == NULL) {
luaL_typerror(state, indx, LISTNR);
return NULL;
}
// if the event is not active return NULL and don't throw an error (this is an easily forgivable user-error)
if (activeEvents.find(lstnr->event) == activeEvents.end())
return NULL;
// return the pointer to the lEvent
return lstnr;
}
// connects a callback to an event
static int evnt_listen(lua_State *state) {
int nargs = lua_gettop(state);
lEvent *event = grabEvent(state, 1);
// sanity check
if (event == NULL)
return 0;
// for each argument passed, check that it's a function and add it to the event
for (int i = 2; i <= nargs; i++) {
luaL_checktype(state, i, LUA_TFUNCTION);
lua_pushvalue(state, i);
pushListener(state, event, event->addCallback(state, luaL_ref(state, LUA_REGISTRYINDEX)));
}
// we return this many listeners
return nargs - 1;
}
// yields the thread until the event is triggered
static int evnt_wait(lua_State *state) {
lEvent *event = grabEvent(state, 1);
// sanity check
if (event == NULL)
return 0;
event->addWait(state);
return lua_yield(state, 0);
}
static int lstnr_disconnect(lua_State *state) {
lstnrData *lstnr = grabListener(state, 1);
// sanity check
if (lstnr == NULL)
return 0;
// disconnect the event
lstnr->event->disconnectEvent(lstnr->rawListener);
return 0;
}
static int lstnr_reconnect(lua_State *state) {
lstnrData *lstnr = grabListener(state, 1);
// sanity check
if (lstnr == NULL)
return 0;
// disconnect the event
lstnr->event->reconnectEvent(lstnr->rawListener);
return 0;
}
static int lstnr_gc(lua_State *state) {
lstnrData *lstnr = grabListener(state, 1);
// sanity check
if (lstnr == NULL)
return 0;
// if the listener is disabled, clear it
if (lstnr->event->isDisabled(lstnr->rawListener))
lstnr->event->clear(lstnr->rawListener);
return 0;
}
static luaL_Reg evnt_methods[] = {
{"listen", evnt_listen},
{"wait", evnt_wait},
{0, 0}
};
static luaL_Reg lstnr_methods[] = {
{"disconnect", lstnr_disconnect},
{"reconnect", lstnr_reconnect},
{0, 0}
};
void LuaManager::Event::init(lua_State *state) {
// register the library & pop it
luaL_register(state, LIBNAME, evnt_methods);
// create the event metatable
luaL_newmetatable(state, LIBNAME);
lua_pushstring(state, "__index");
lua_pushvalue(state, -3);
lua_rawset(state, -3);
// create the listener metatable
luaL_newmetatable(state, LISTNR);
lua_pushstring(state, "__index");
lua_newtable(state);
luaL_register(state, NULL, lstnr_methods);
lua_rawset(state, -3);
lua_pushstring(state, "__gc");
lua_pushcfunction(state, lstnr_gc);
lua_rawset(state, -3);
// pop the tables off the stack
lua_pop(state, 3);
activeEvents = std::unordered_set<lEvent*>();
}
// just a wrapper for pushEvent()
void LuaManager::Event::push(lua_State *state, lEvent *event) {
pushEvent(state, event);
}

10
src/lua/EventWrapper.hpp Normal file
View File

@@ -0,0 +1,10 @@
#include "lua/LuaManager.hpp"
#include "lua/LuaWrapper.hpp"
namespace LuaManager {
namespace Event {
void init(lua_State *state);
void push(lua_State *state, lEvent *event);
}
}

185
src/lua/LuaManager.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "lua/LuaManager.hpp"
#include "lua/LuaWrapper.hpp"
#include "lua/EventWrapper.hpp"
#include "lua/WorldWrapper.hpp"
#include "lua/PlayerWrapper.hpp"
#include "lua/NPCWrapper.hpp"
#include "PlayerManager.hpp"
#include "servers/CNShardServer.hpp"
#include "settings.hpp"
#include <filesystem>
#include <vector>
time_t getTime();
class Script;
// our "main" state, holds our environment
lua_State *LuaManager::global;
std::map<lua_State*, Script*> activeScripts;
/*
Basically each script is treated as a coroutine, when wait() is called it gets yielded and is pushed onto the scheduler queue to be resumed.
*/
class Script {
private:
lua_State *thread;
lRegistry threadRef; // we'll need to unref this when closing this state
public:
Script(std::string source) {
// make the thread & register it in the registry
thread = lua_newthread(LuaManager::global);
threadRef = luaL_ref(LuaManager::global, LUA_REGISTRYINDEX);
// add this script to the map
activeScripts[thread] = this;
// compile & run the script, if it error'd, print the error
int _retCode;
if (luaL_loadfile(thread, source.c_str()) || ((_retCode = lua_resume(thread, 0)) != 0 && (_retCode != LUA_YIELD))) {
std::cout << "[LUA ERROR]: " << lua_tostring(thread, -1) << std::endl;
}
}
// unregister all of our events from the wrappers
~Script() {
LuaManager::clearState(thread);
// remove it from the global registry
luaL_unref(LuaManager::global, LUA_REGISTRYINDEX, threadRef);
}
// c++ moment....
lua_State* getState() {
return thread;
}
};
struct scheduledThread {
lRegistry ref; // ref that should be unref'd before resuming
time_t time;
};
std::map<lua_State*, scheduledThread> scheduleQueue;
// pauses the script for x seconds, not very accurate but should be
// called within ~60ms or less of when it was schedueled
// will also return the time the thread was paused
int OF_wait(lua_State *state) {
double seconds = luaL_checknumber(state, 1);
// register the thread in the global registry so we don't get GC'd
lua_pushthread(state);
lRegistry ref = luaL_ref(state, LUA_REGISTRYINDEX); // threads decentant of a global state all share the global registry
// yield the state and push the state onto our scheduler queue
scheduleQueue[state] = {ref, (int)(seconds*1000) + getTime()};
return lua_yield(state, 0);
}
void luaScheduler(CNServer *serv, time_t currtime) {
for (auto iter = scheduleQueue.begin(); iter != scheduleQueue.end();) {
time_t event = (*iter).second.time;
lRegistry ref = (*iter).second.ref;
lua_State *thread = (*iter).first;
// is it time to run the event?
if (event <= currtime) {
// remove from the scheduler queue
scheduleQueue.erase(iter++);
// unregister the thread
luaL_unref(thread, LUA_REGISTRYINDEX, ref);
// resume the state, (wait() returns the delta time since call)
lua_pushnumber(thread, ((double)currtime - event)/10);
yieldCall(thread, 1);
} else // go to the next iteration
++iter;
}
}
void LuaManager::printError(std::string err) {
std::cerr << "[LUA ERR]: " << err << std::endl;
}
void LuaManager::init() {
// allocate our state
global = luaL_newstate();
// open lua's base libraries (excluding the IO for now)
luaopen_base(global);
luaopen_table(global);
luaopen_string(global);
luaopen_math(global);
luaopen_debug(global);
// add wait()
lua_register(global, "wait", OF_wait);
// register our libraries
Event::init(global);
World::init(global);
Entity::init(global);
Player::init(global);
NPC::init(global);
activeScripts = std::map<lua_State*, Script*>();
// we want to be called after every poll(), so our timer delta is set to 0
REGISTER_SHARD_TIMER(luaScheduler, 0);
// load our scripts
loadScripts();
}
void LuaManager::runScript(std::string filename) {
new Script(filename);
}
void LuaManager::stopScripts() {
// clear the scheduler queue
scheduleQueue.clear();
// free all the scripts, they'll take care of everything for us :)
for (auto as : activeScripts) {
delete as.second;
}
// finally clear the map
activeScripts.clear();
// walk through each player and unregister each event
for (auto pair: PlayerManager::players) {
if (pair.second->onChat != nullptr) {
delete pair.second->onChat;
pair.second->onChat = nullptr;
}
}
}
void LuaManager::loadScripts() {
if (!std::filesystem::exists(settings::SCRIPTSDIR)) {
std::cout << "[WARN] scripts directory \"" << settings::SCRIPTSDIR << "\" doesn't exist!" << std::endl;
return;
}
// for each file in the scripts director, load the script
std::filesystem::path dir(settings::SCRIPTSDIR);
for (auto &d : std::filesystem::directory_iterator(dir)) {
if (d.path().extension().u8string() == ".lua")
runScript(d.path().u8string());
}
}
void LuaManager::clearState(lua_State *state) {
// TODO
}
void LuaManager::playerAdded(CNSocket *sock) {
World::playerAdded(sock);
}
void LuaManager::playerRemoved(CNSocket *sock) {
World::playerRemoved(sock);
}

28
src/lua/LuaManager.hpp Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include "core/CNProtocol.hpp"
#include <string>
#ifdef _MSC_VER
#include <luajit/lua.hpp>
#else
#include <lua.hpp>
#endif
typedef int lRegistry;
namespace LuaManager {
extern lua_State *global;
void init();
void printError(std::string err);
// runs the script in the passed file
void runScript(std::string filename);
void stopScripts();
void loadScripts();
// unregisters the events tied to this state with all wrappers
void clearState(lua_State *state);
void playerAdded(CNSocket *sock);
void playerRemoved(CNSocket *sock);
}

223
src/lua/LuaWrapper.hpp Normal file
View File

@@ -0,0 +1,223 @@
#pragma once
/*
This is a very simple header that adds a small "event" layer, where scripts can register callbacks & "waits" on events. The actual
Event metatables & library is in EventWrapper.[hc]pp
*/
#include <string>
#include <map>
#include <unordered_set>
#include <cassert>
#include "lua/LuaManager.hpp"
#include "lua/PlayerWrapper.hpp"
#define yieldCall(state, nargs) \
int _retCode = lua_resume(state, nargs); \
if (_retCode != 0 && _retCode != LUA_YIELD) \
LuaManager::printError(lua_tostring(state, -1)); \
inline static int lua_autoPush(lua_State* state, int nargs) {
// return the number of pushed arguments :)
return nargs;
}
/*
This function will automatically push all of the passed arguments onto the stack and returns the # of pushed values
Supported datatypes are:
double or int : LUA_TNUMBER
char* or const char* : LUA_TSTRING
bool : LUA_TBOOLEAN
lRegistry : grabs the object from the lua registry and pushes it onto the stack
CNSocket* : Pushes the Player Entity
*/
template<typename T, class... Rest>
inline static int lua_autoPush(lua_State* state, int nargs, T arg, Rest... rest) {
// pick which branch to compile based on the type of arg
if constexpr(std::is_same<T, int>::value || std::is_same<T, double>::value) {
lua_pushnumber(state, (lua_Number)arg);
} else if constexpr(std::is_same<T, char*>::value || std::is_same<T, const char*>::value) {
lua_pushstring(state, (const char*)arg);
} else if constexpr(std::is_same<T, lRegistry>::value) {
// grab the value from the registry
lua_rawgeti(state, LUA_REGISTRYINDEX, (int)arg);
} else if constexpr(std::is_same<T, CNSocket*>::value) { // pushes a Player Entity
LuaManager::Player::push(state, arg);
} else if constexpr(std::is_same<T, bool>::value) {
lua_pushboolean(state, arg);
}
// recursively call, expanding rest and pushing the left-most rvalue into arg
return lua_autoPush(state, ++nargs, rest...);
}
enum eventType {
EVENT_CALLBACK, // standard callback
EVENT_WAIT // state needs to be resumed with the arguments
};
class lEvent;
extern std::unordered_set<lEvent*> activeEvents;
class lEvent {
private:
struct rawEvent {
lua_State *state;
eventType type;
lRegistry ref;
bool disabled;
};
uint32_t nextId; // next ID
std::map<uint32_t, rawEvent> events;
uint32_t registerEvent(lua_State *state, lRegistry ref, eventType type) {
uint32_t id = nextId++;
assert(nextId < UINT32_MAX);
events[id] = {state, type, ref, false};
return id;
}
void freeEvent(rawEvent *event) {
// the registry of all states is the same!
luaL_unref(event->state, LUA_REGISTRYINDEX, event->ref);
}
public:
lEvent() {
events = std::map<uint32_t, rawEvent>();
nextId = 0; // start at 0
activeEvents.insert(this);
}
~lEvent() {
// remove from the active set and disable all existing callbacks
activeEvents.erase(this);
clear();
}
uint32_t addCallback(lua_State *state, lRegistry ref) {
return registerEvent(state, ref, EVENT_CALLBACK);
}
// yields the thread until the event is called
uint32_t addWait(lua_State *state) {
// push the thread onto the stack and register it in the global registry
lua_pushthread(state);
lRegistry ref = luaL_ref(state, LUA_REGISTRYINDEX);
return registerEvent(state, ref, EVENT_WAIT);
}
// walks through the events and unregister them from the state
void clear() {
for (auto &pair : events) {
freeEvent(&pair.second);
}
events.clear();
}
// free the event based on id
void clear(uint32_t id) {
auto iter = events.find(id);
// sanity check
if (iter == events.end())
return;
// free the event
freeEvent(&(*iter).second);
events.erase(iter);
}
// frees all events to this state
void clear(lua_State *state) {
for (auto iter = events.begin(); iter != events.end();) {
rawEvent *event = &(*iter).second;
// if this is our state, free this event and erase it from the map
if (event->state == state) {
freeEvent(event);
events.erase(iter++);
} else
++iter;
}
}
void disconnectEvent(uint32_t id) {
auto iter = events.find(id);
// sanity check
if (iter == events.end())
return;
(*iter).second.disabled = true;
}
void reconnectEvent(uint32_t id) {
auto iter = events.find(id);
// sanity check
if (iter == events.end())
return;
(*iter).second.disabled = false;
}
bool isDisabled(uint32_t id) {
auto iter = events.find(id);
// sanity check
if (iter == events.end())
return true;
return (*iter).second.disabled;
}
template<class... Args> inline void call(Args... args) {
auto eventsClone = events; // so if a callback is added, we don't iterate into undefined behavior
for (auto &pair : eventsClone) {
rawEvent *event = &pair.second;
// if the event is disabled, skip it
if (event->disabled)
continue;
switch (event->type) {
case EVENT_CALLBACK: {
// make thread for this callback
if (!lua_checkstack(event->state, 1)) {
std::cout << "[FATAL] Failed to create new lua thread! out of memory!" << std::endl;
terminate(0);
}
lua_State *nThread = lua_newthread(event->state);
// push the callable first, the push all the arguments
lua_rawgeti(nThread, LUA_REGISTRYINDEX, (int)event->ref);
int nargs = lua_autoPush(nThread, 0, args...);
// then call it :)
yieldCall(nThread, nargs);
break;
}
case EVENT_WAIT: {
// erase this event
freeEvent(&pair.second);
events.erase(pair.first);
// the :wait() will return the passed arguments
int nargs = lua_autoPush(event->state, 0, args...);
yieldCall(event->state, nargs);
break;
}
default:
std::cout << "[WARN] INVALID EVENT TYPE : " << event->type << std::endl;
break;
}
}
}
};

266
src/lua/NPCWrapper.cpp Normal file
View File

@@ -0,0 +1,266 @@
#include "lua/LuaWrapper.hpp"
#include "lua/NPCWrapper.hpp"
#include "NPC.hpp"
#include "NPCManager.hpp"
#include "Transport.hpp"
#define LIBNAME "NPC"
#define NPCGONESTR "NPC was destoryed and no longer exists!"
#define GETTERTBL "__npcGETTERS"
#define SETTERTBL "__npcSETTERS"
#define METHODTBL "__npcMETHODS"
static EntityRef* grabEntityRef(lua_State *state, int indx) {
// first, make sure its a userdata
luaL_checktype(state, indx, LUA_TUSERDATA);
// now, check and make sure its our library's metatable attached to this userdata
EntityRef *ref = (EntityRef*)luaL_checkudata(state, indx, LIBNAME);
if (ref == NULL) {
luaL_typerror(state, indx, LIBNAME);
return NULL;
}
// check if the npc exists still & return NULL if it doesn't
if (!ref->isValid()) {
luaL_argerror(state, indx, NPCGONESTR);
return NULL;
}
return ref;
}
static CombatNPC* grabNPC(lua_State *state, int indx) {
EntityRef *ref = grabEntityRef(state, indx);
// if grabEntityRef failed, return error result
return (ref == NULL) ? NULL : (CombatNPC*)ref->getEntity();
}
static int32_t grabID(lua_State *state, int indx) {
EntityRef *ref = grabEntityRef(state, indx);
// if grabEntityRef failed, return error result
return (ref == NULL) ? -1 : ref->id;
}
// =============================================== [[ GETTERS ]] ===============================================
static int npc_getMaxHealth(lua_State *state) {
CombatNPC *npc = grabNPC(state, 1);
// sanity check
if (npc == NULL)
return 0;
lua_pushinteger(state, npc->maxHealth);
return 1;
}
static int npc_getSpeed(lua_State *state) {
CombatNPC *npc = grabNPC(state, 1);
// sanity check
if (npc == NULL)
return 0;
lua_pushinteger(state, npc->speed);
return 1;
}
static const luaL_Reg npc_getters[] = {
{"maxHealth", npc_getMaxHealth},
{"speed", npc_getSpeed},
{0, 0}
};
// =============================================== [[ SETTERS ]] ===============================================
static int npc_setMaxHealth(lua_State *state) {
CombatNPC *npc = grabNPC(state, 1);
int newMH = luaL_checkint(state, 2);
// sanity check
if (npc != NULL)
npc->maxHealth = newMH;
return 0;
}
static int npc_setSpeed(lua_State *state) {
CombatNPC *npc = grabNPC(state, 1);
int newSpeed = luaL_checkint(state, 2);
// sanity check
if (npc != NULL)
npc->speed = newSpeed;
return 0;
}
static const luaL_Reg npc_setters[] = {
{"maxHealth", npc_setMaxHealth},
{"speed", npc_setSpeed},
{0, 0}
};
// =============================================== [[ METHODS ]] ===============================================
static int npc_moveto(lua_State *state) {
CombatNPC *npc = grabNPC(state, 1);
int X = luaL_checkint(state, 2);
int Y = luaL_checkint(state, 3);
int Z = luaL_checkint(state, 4);
// sanity check
if (npc == NULL)
return 0;
std::queue<Vec3> queue;
Vec3 from = { npc->x, npc->y, npc->z };
Vec3 to = { X, Y, Z };
// add a route to the queue; to be processed in Transport::stepNPCPathing()
Transport::lerp(&queue, from, to, npc->speed);
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = queue;
return 0;
}
static int npc_say(lua_State *state) {
CombatNPC *npc = grabNPC(state, 1);
std::string msg = std::string(luaL_checkstring(state, 2));
// sanity check
if (npc != NULL) {
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, res);
U8toU16(msg, (char16_t*)&res.szFreeChat, sizeof(res.szFreeChat));
res.iPC_ID = npc->appearanceData.iNPC_ID;
res.iEmoteCode = 421;
// send the packet
NPCManager::sendToViewable(npc, &res, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
}
return 0;
}
static const luaL_Reg npc_methods[] = {
{"moveTo", npc_moveto},
{"say", npc_say},
{0, 0}
};
// in charge of calling the correct getter method
static int plr_index(lua_State *state) {
// grab the function from the getters lookup table
lua_pushstring(state, GETTERTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// if it's not nil, call it and run the getter method
if (!lua_isnil(state, -1)) {
// push userdata & call the function
lua_pushvalue(state, 1);
lua_call(state, 1, 1);
// return # of results
return 1;
}
// grab the function from the methods lookup table
lua_pop(state, 1);
lua_pushstring(state, METHODTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// return result
return 1;
}
// in charge of calling the correct setter method
static int plr_newindex(lua_State *state) {
// grab the function from the getters lookup table
lua_pushstring(state, SETTERTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// if it's nil return
if (lua_isnil(state, -1))
return 0;
// push userdata & call the function
lua_pushvalue(state, 1);
lua_call(state, 1, 0);
// return # of results
return 0;
}
static int npc_new(lua_State *state) {
int X = luaL_checkint(state, 1);
int Y = luaL_checkint(state, 2);
int Z = luaL_checkint(state, 3);
int type = luaL_checkint(state, 4);
int id = NPCManager::nextId++;
// create & initalize the NPC
CombatNPC *NPC = new CombatNPC(X, Y, Z, 0, INSTANCE_OVERWORLD, type, id, 1000);
NPCManager::NPCs[id] = NPC;
NPCManager::updateNPCPosition(id, X, Y, Z, INSTANCE_OVERWORLD, 0);
// push it to the lua stack & return
LuaManager::NPC::push(state, NPC);
return 1;
}
void LuaManager::NPC::init(lua_State *state) {
// register our library as a global (and leave it on the stack)
luaL_register(state, LIBNAME, npc_methods);
// sets NPC.new
lua_pushstring(state, "new");
lua_pushcfunction(state, npc_new);
lua_rawset(state, -3);
// create the meta table and populate it with our functions
luaL_newmetatable(state, LIBNAME);
lua_pushstring(state, "__index");
lua_pushcfunction(state, plr_index);
lua_rawset(state, -3); // sets meta.__index = plr_index
lua_pushstring(state, "__newindex");
lua_pushcfunction(state, plr_newindex);
lua_rawset(state, -3); // sets meta.__newindex = plr_newindex
lua_pop(state, 2); // pop meta & library table
// create the methods table
lua_pushstring(state, METHODTBL);
lua_newtable(state);
Entity::addMethods(state); // register the base Entity methods
luaL_register(state, NULL, npc_methods);
lua_rawset(state, LUA_REGISTRYINDEX);
// create the getters table
lua_pushstring(state, GETTERTBL);
lua_newtable(state);
Entity::addGetters(state); // register the base Entity getters
luaL_register(state, NULL, npc_getters);
lua_rawset(state, LUA_REGISTRYINDEX);
// create the setters table
lua_pushstring(state, SETTERTBL);
lua_newtable(state);
luaL_register(state, NULL, npc_setters);
lua_rawset(state, LUA_REGISTRYINDEX);
LuaManager::Entity::registerSuper(state, LIBNAME);
}
void LuaManager::NPC::push(lua_State *state, CombatNPC *npc) {
Entity::push(state, EntityRef(npc->appearanceData.iNPC_ID), LIBNAME);
}

11
src/lua/NPCWrapper.hpp Normal file
View File

@@ -0,0 +1,11 @@
#include "lua/LuaWrapper.hpp"
#include "lua/LuaManager.hpp"
#include "lua/EntityWrapper.hpp"
namespace LuaManager {
namespace NPC {
void init(lua_State *state);
void push(lua_State *state, CombatNPC *npc);
}
}

278
src/lua/PlayerWrapper.cpp Normal file
View File

@@ -0,0 +1,278 @@
#include "lua/EntityWrapper.hpp"
#include "lua/EventWrapper.hpp"
#include "lua/PlayerWrapper.hpp"
#include "core/CNProtocol.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp"
#include "Chat.hpp"
#define LIBNAME "Player"
#define PLRGONESTR "Player doesn't exist anymore, they left!"
#define GETTERTBL "__plrGETTERS"
#define SETTERTBL "__plrSETTERS"
#define METHODTBL "__plrMETHODS"
static EntityRef* grabEntityRef(lua_State *state, int indx) {
// first, make sure its a userdata
luaL_checktype(state, indx, LUA_TUSERDATA);
// now, check and make sure its our library's metatable attached to this userdata
EntityRef *ref = (EntityRef*)luaL_checkudata(state, indx, LIBNAME);
if (ref == NULL) {
luaL_typerror(state, indx, LIBNAME);
return NULL;
}
// check if the player exists still & return NULL if it doesn't
if (!ref->isValid()) {
luaL_argerror(state, indx, PLRGONESTR);
return NULL;
}
return ref;
}
static Player* grabPlayer(lua_State *state, int indx) {
EntityRef *ref = grabEntityRef(state, indx);
if (ref == NULL)
return NULL;
return (Player*)ref->getEntity();
}
static CNSocket* grabSock(lua_State *state, int indx) {
EntityRef *ref = grabEntityRef(state, indx);
if (ref == NULL)
return NULL;
return ref->sock;
}
// =============================================== [[ GETTERS ]] ===============================================
static int plr_getName(lua_State *state) {
Player *plr = grabPlayer(state, 1);
if (plr == NULL)
return 0;
lua_pushstring(state, PlayerManager::getPlayerName(plr).c_str());
return 1;
}
static int plr_getInstance(lua_State *state) {
Player *plr = grabPlayer(state, 1);
if (plr == NULL)
return 0;
lua_pushnumber(state, plr->instanceID);
return 1;
}
static int plr_getChatted(lua_State *state) {
Player *plr = grabPlayer(state, 1);
if (plr == NULL)
return 0;
// the Player* entity doesn't actually have an lEvent setup until a lua script asks for it, so
// if Player->onChat is nullptr, create the lEvent and then push it :D
if (plr->onChat == nullptr)
plr->onChat = new lEvent();
LuaManager::Event::push(state, plr->onChat);
return 1;
}
static const luaL_Reg plr_getters[] = {
{"name", plr_getName},
{"instance", plr_getInstance},
{"onChat", plr_getChatted},
{0, 0}
};
// =============================================== [[ SETTERS ]] ===============================================
static int plr_setInstance(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
int newInst = luaL_checkint(state, 2);
Player *plr;
CNSocket *sock;
// sanity check
if (ref == NULL)
return 0;
plr = (Player*)ref->getEntity();
sock = ref->sock;
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, newInst);
return 0;
}
static const luaL_Reg plr_setters[] = {
{"instance", plr_setInstance},
{0, 0}
};
// =============================================== [[ METHODS ]] ===============================================
static int plr_kick(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
Player *plr;
CNSocket *sock;
// sanity check
if (ref == NULL)
return 0;
plr = (Player*)ref->getEntity();
sock = ref->sock;
// construct packet
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
response.iID = plr->iID;
response.iExitCode = 3; // "a GM has terminated your connection"
// send to target player
sock->sendPacket(response, P_FE2CL_REP_PC_EXIT_SUCC);
// ensure that the connection has terminated
sock->kill();
return 0;
}
static int plr_teleport(lua_State *state) {
EntityRef *ref = grabEntityRef(state, 1);
Player *plr;
CNSocket *sock;
// sanity check
if (ref == NULL)
return 0;
plr = (Player*)ref->getEntity();
sock = ref->sock;
int X = luaL_checkint(state, 2);
int Y = luaL_checkint(state, 3);
int Z = luaL_checkint(state, 4);
int inst = luaL_optint(state, 5, plr->instanceID);
PlayerManager::sendPlayerTo(sock, X, Y, Z, inst);
return 0;
}
static int plr_msg(lua_State *state) {
CNSocket *sock = grabSock(state, 1);
const char *msg = luaL_checkstring(state, 2);
// sanity check
if (sock != NULL)
Chat::sendServerMessage(sock, std::string(msg));
return 0;
}
static const luaL_Reg plr_methods[] = {
{"kick", plr_kick},
{"teleport", plr_teleport},
{"message", plr_msg},
{0, 0}
};
// in charge of calling the correct getter method
static int plr_index(lua_State *state) {
// grab the function from the getters lookup table
lua_pushstring(state, GETTERTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// if it's not nil, call it and run the getter method
if (!lua_isnil(state, -1)) {
// push userdata & call the function
lua_pushvalue(state, 1);
lua_call(state, 1, 1);
// return # of results
return 1;
}
// grab the function from the methods lookup table
lua_pop(state, 1);
lua_pushstring(state, METHODTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// return result
return 1;
}
// in charge of calling the correct setter method
static int plr_newindex(lua_State *state) {
// grab the function from the getters lookup table
lua_pushstring(state, SETTERTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// if it's nil return
if (lua_isnil(state, -1))
return 0;
// push userdata & call the function
lua_pushvalue(state, 1);
lua_call(state, 1, 0);
// return # of results
return 0;
}
void LuaManager::Player::init(lua_State *state) {
// register our library as a global (and leave it on the stack)
luaL_register(state, LIBNAME, plr_methods);
// create the meta table and populate it with our functions
luaL_newmetatable(state, LIBNAME);
lua_pushstring(state, "__index");
lua_pushcfunction(state, plr_index);
lua_rawset(state, -3); // sets meta.__index = plr_index
lua_pushstring(state, "__newindex");
lua_pushcfunction(state, plr_newindex);
lua_rawset(state, -3); // sets meta.__newindex = plr_newindex
lua_pop(state, 2); // pop meta & library table
// create the methods table
lua_pushstring(state, METHODTBL);
lua_newtable(state);
Entity::addMethods(state); // register the base Entity methods
luaL_register(state, NULL, plr_methods);
lua_rawset(state, LUA_REGISTRYINDEX);
// create the getters table
lua_pushstring(state, GETTERTBL);
lua_newtable(state);
Entity::addGetters(state); // register the base Entity getters
luaL_register(state, NULL, plr_getters);
lua_rawset(state, LUA_REGISTRYINDEX);
// create the setters table
lua_pushstring(state, SETTERTBL);
lua_newtable(state);
luaL_register(state, NULL, plr_setters);
lua_rawset(state, LUA_REGISTRYINDEX);
LuaManager::Entity::registerSuper(state, LIBNAME);
}
void LuaManager::Player::push(lua_State *state, CNSocket *sock) {
Entity::push(state, EntityRef(sock), LIBNAME);
}

11
src/lua/PlayerWrapper.hpp Normal file
View File

@@ -0,0 +1,11 @@
#include "lua/LuaWrapper.hpp"
#include "lua/LuaManager.hpp"
#include "lua/EntityWrapper.hpp"
namespace LuaManager {
namespace Player {
void init(lua_State *state);
void push(lua_State *state, CNSocket *sock);
}
}

130
src/lua/WorldWrapper.cpp Normal file
View File

@@ -0,0 +1,130 @@
#include "lua/LuaManager.hpp"
#include "lua/LuaWrapper.hpp"
#include "lua/EventWrapper.hpp"
#include "lua/WorldWrapper.hpp"
#include "core/CNStructs.hpp"
#include "PlayerManager.hpp"
static lEvent *addedEvent;
static lEvent *removedEvent;
#define LIBNAME "World"
#define GETTERTBL "__wrldGETTERS"
#define METHODTBL "__wrldMETHODS"
// =============================================== [[ GETTERS ]] ===============================================
int wrld_getPlrAdded(lua_State *state) {
LuaManager::Event::push(state, addedEvent);
return 1;
}
int wrld_getPlrRemoved(lua_State *state) {
LuaManager::Event::push(state, removedEvent);
return 1;
}
int wrld_getVersion(lua_State *state) {
lua_pushnumber(state, PROTOCOL_VERSION);
return 1;
}
int wrld_getPlayers(lua_State *state) {
// create a new lua table and push it onto the stack
int entries = 0;
lua_newtable(state);
// walk through the current list of players and add them to the table
for (auto pair : PlayerManager::players) {
lua_pushinteger(state, ++entries);
LuaManager::Player::push(state, pair.first);
lua_rawset(state, -3);
}
// returns the player table :)
return 1;
}
// =============================================== [[ METHODS ]] ===============================================
int wrld_index(lua_State *state) {
// grab the function from the getters lookup table
lua_pushstring(state, GETTERTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// if it's not nil, call it and run the getter method
if (!lua_isnil(state, -1)) {
// push userdata & call the function
lua_pushvalue(state, 1);
lua_call(state, 1, 1);
// return # of results
return 1;
}
// grab the function from the methods lookup table
lua_pop(state, 1);
lua_pushstring(state, METHODTBL);
lua_rawget(state, LUA_REGISTRYINDEX);
lua_pushvalue(state, 2);
lua_rawget(state, -2);
// return result
return 1;
}
static const luaL_Reg getters[] {
{"onPlayerAdded", wrld_getPlrAdded},
{"onPlayerRemoved", wrld_getPlrRemoved},
{"players", wrld_getPlayers},
{"version", wrld_getVersion},
{0, 0}
};
// TODO
static const luaL_Reg methods[] = {
//{"getNearbyPlayers", wrld_getNPlrs},
{0, 0}
};
void LuaManager::World::init(lua_State *state) {
lua_newtable(state);
luaL_newmetatable(state, LIBNAME);
lua_pushstring(state, "__index");
lua_pushcfunction(state, wrld_index);
lua_rawset(state, -3); // sets meta.__index = wrld_index
lua_setmetatable(state, -2); // sets world.__metatable = meta
lua_setglobal(state, LIBNAME);
// setup the __wrldGETTERS table in the registry
lua_pushstring(state, GETTERTBL);
lua_newtable(state);
luaL_register(state, NULL, getters);
lua_rawset(state, LUA_REGISTRYINDEX);
// setup the __wrldMETHODS table in the registry
lua_pushstring(state, METHODTBL);
lua_newtable(state);
luaL_register(state, NULL, methods);
lua_rawset(state, LUA_REGISTRYINDEX);
addedEvent = new lEvent();
removedEvent = new lEvent();
}
void LuaManager::World::clearState(lua_State *state) {
addedEvent->clear(state);
removedEvent->clear(state);
}
void LuaManager::World::playerAdded(CNSocket *sock) {
addedEvent->call(sock);
}
void LuaManager::World::playerRemoved(CNSocket *sock) {
removedEvent->call(sock);
}

13
src/lua/WorldWrapper.hpp Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "lua/LuaManager.hpp"
namespace LuaManager {
namespace World {
void init(lua_State *state);
void clearState(lua_State *state);
void playerAdded(CNSocket *sock);
void playerRemoved(CNSocket *sock);
}
}

View File

@@ -1,5 +1,6 @@
#include "servers/CNLoginServer.hpp"
#include "servers/CNShardServer.hpp"
#include "lua/LuaManager.hpp"
#include "PlayerManager.hpp"
#include "PlayerMovement.hpp"
#include "BuiltinCommands.hpp"
@@ -120,6 +121,7 @@ int main() {
Racing::init();
Database::open();
Trading::init();
LuaManager::init();
switch (settings::EVENTMODE) {
case 0: break; // no event

View File

@@ -35,6 +35,7 @@ int settings::SPAWN_ANGLE = 130;
std::string settings::DBPATH = "database.db";
std::string settings::TDATADIR = "tdata/";
std::string settings::PATCHDIR = "tdata/patch/";
std::string settings::SCRIPTSDIR = "scripts";
std::string settings::NPCJSON = "NPCs.json";
std::string settings::MOBJSON = "mobs.json";

View File

@@ -27,6 +27,7 @@ namespace settings {
extern std::string PATCHDIR;
extern std::string ENABLEDPATCHES;
extern std::string TDATADIR;
extern std::string SCRIPTSDIR;
extern int EVENTMODE;
extern bool MONITORENABLED;
extern int MONITORPORT;