First commit (very unfinished)
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
#include "common.hpp"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <lua.hpp>
|
||||
|
||||
static std::list<NetPlayer> players;
|
||||
static Chunk world[MAX_WORLD_AREA];
|
||||
static ENetHost* server;
|
||||
|
||||
struct LuaPlugin {
|
||||
std::string name;
|
||||
lua_State* L;
|
||||
bool has_on_connect = false;
|
||||
bool has_on_disconnect = false;
|
||||
bool has_on_packet = false;
|
||||
};
|
||||
|
||||
static std::vector<std::unique_ptr<LuaPlugin>> plugins;
|
||||
|
||||
static int l_server_send(lua_State* L) {
|
||||
int peer_id = (int)luaL_checkinteger(L, 1);
|
||||
size_t len;
|
||||
const char* data = luaL_checklstring(L, 2, &len);
|
||||
for (const NetPlayer &p : players) {
|
||||
if (p.id == peer_id) {
|
||||
SendPacketR(p.peer, (void*)data, (int)len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_server_broadcast(lua_State* L) {
|
||||
size_t len;
|
||||
const char* data = luaL_checklstring(L, 1, &len);
|
||||
if (len > 0) {
|
||||
BroadcastPacketR(server, (void*)data, (int)len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_players(lua_State* L) {
|
||||
lua_newtable(L);
|
||||
int idx = 1;
|
||||
for (const NetPlayer &p : players) {
|
||||
lua_pushinteger(L, p.id);
|
||||
lua_rawseti(L, -2, idx++);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void RegisterLuaAPI(lua_State* L) {
|
||||
lua_newtable(L);
|
||||
lua_pushcfunction(L, l_server_send);
|
||||
lua_setfield(L, -2, "send");
|
||||
lua_pushcfunction(L, l_server_broadcast);
|
||||
lua_setfield(L, -2, "broadcast");
|
||||
lua_pushcfunction(L, l_get_players);
|
||||
lua_setfield(L, -2, "get_players");
|
||||
lua_setglobal(L, "server");
|
||||
}
|
||||
|
||||
static bool LoadPluginFile(const std::filesystem::path& path) {
|
||||
auto plugin = std::make_unique<LuaPlugin>();
|
||||
plugin->name = path.filename().string();
|
||||
plugin->L = luaL_newstate();
|
||||
if (!plugin->L) return false;
|
||||
luaL_openlibs(plugin->L);
|
||||
|
||||
RegisterLuaAPI(plugin->L);
|
||||
|
||||
int rc = luaL_dofile(plugin->L, path.string().c_str());
|
||||
if (rc != LUA_OK) {
|
||||
const char* err = lua_tostring(plugin->L, -1);
|
||||
std::cerr << "Error loading plugin " << plugin->name << ": " << (err ? err : "unknown") << std::endl;
|
||||
lua_close(plugin->L);
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_getglobal(plugin->L, "on_connect");
|
||||
if (lua_isfunction(plugin->L, -1)) plugin->has_on_connect = true;
|
||||
lua_pop(plugin->L, 1);
|
||||
|
||||
lua_getglobal(plugin->L, "on_disconnect");
|
||||
if (lua_isfunction(plugin->L, -1)) plugin->has_on_disconnect = true;
|
||||
lua_pop(plugin->L, 1);
|
||||
|
||||
lua_getglobal(plugin->L, "on_packet");
|
||||
if (lua_isfunction(plugin->L, -1)) plugin->has_on_packet = true;
|
||||
lua_pop(plugin->L, 1);
|
||||
|
||||
plugins.push_back(std::move(plugin));
|
||||
std::cout << "Loaded plugin: " << path.filename().string() << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void LoadPluginsFromFolder(const std::string& folder) {
|
||||
try {
|
||||
if (!std::filesystem::exists(folder)) {
|
||||
std::cout << "Plugins folder does not exist; skipping: " << folder << std::endl;
|
||||
return;
|
||||
}
|
||||
for (auto& p : std::filesystem::directory_iterator(folder)) {
|
||||
if (!p.is_regular_file()) continue;
|
||||
auto ext = p.path().extension().string();
|
||||
if (ext == ".lua") LoadPluginFile(p.path());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error enumerating plugins: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static void CallPluginOnConnect(int peer_id) {
|
||||
for (auto &pl : plugins) {
|
||||
if (!pl->has_on_connect) continue;
|
||||
lua_State* L = pl->L;
|
||||
lua_getglobal(L, "on_connect");
|
||||
lua_pushinteger(L, peer_id);
|
||||
if (lua_pcall(L, 1, 0, 0) != LUA_OK) {
|
||||
std::cerr << "Plugin " << pl->name << " on_connect error: " << lua_tostring(L, -1) << std::endl;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void CallPluginOnDisconnect(int peer_id) {
|
||||
for (auto &pl : plugins) {
|
||||
if (!pl->has_on_disconnect) continue;
|
||||
lua_State* L = pl->L;
|
||||
lua_getglobal(L, "on_disconnect");
|
||||
lua_pushinteger(L, peer_id);
|
||||
if (lua_pcall(L, 1, 0, 0) != LUA_OK) {
|
||||
std::cerr << "Plugin " << pl->name << " on_disconnect error: " << lua_tostring(L, -1) << std::endl;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void CallPluginOnPacket(int peer_id, const unsigned char* data, int len) {
|
||||
for (auto &pl : plugins) {
|
||||
if (!pl->has_on_packet) continue;
|
||||
lua_State* L = pl->L;
|
||||
lua_getglobal(L, "on_packet");
|
||||
lua_pushinteger(L, peer_id);
|
||||
lua_pushlstring(L, (const char*)data, len);
|
||||
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
|
||||
std::cerr << "Plugin " << pl->name << " on_packet error: " << lua_tostring(L, -1) << std::endl;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int PeerToPlayerID(ENetPeer* peer) {
|
||||
for (const NetPlayer &p : players) {
|
||||
if (p.addr == peer->address.host && p.port == peer->address.port) return p.id;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ParseData(ENetPacket* packet, ENetPeer* peer) {
|
||||
int len = packet->dataLength;
|
||||
unsigned char data[len];
|
||||
memcpy(&data, packet->data, len);
|
||||
|
||||
int pid = PeerToPlayerID(peer);
|
||||
if (pid >= 0) CallPluginOnPacket(pid, data, len);
|
||||
|
||||
unsigned char response[16];
|
||||
switch (data[0])
|
||||
{
|
||||
case NET_ARG_REQWORLD:
|
||||
puts("Client requested world; sending");
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
unsigned char responseChunk[2+CHUNK_DATA_SIZE+CHUNK_SIZE*CHUNK_SIZE];
|
||||
responseChunk[0] = NET_ARG_CHUNKF;
|
||||
responseChunk[1] = i;
|
||||
|
||||
memcpy(responseChunk+2, world[i].blocks, CHUNK_DATA_SIZE);
|
||||
memcpy(responseChunk+2+CHUNK_DATA_SIZE, world[i].highestBlock, CHUNK_SIZE*CHUNK_SIZE);
|
||||
SendPacket(peer, responseChunk, sizeof(responseChunk));
|
||||
}
|
||||
break;
|
||||
case NET_ARG_PLAYERMOVE:
|
||||
response[0] = NET_ARG_ECHOMOVE;
|
||||
for (NetPlayer &plr : players)
|
||||
{
|
||||
if(plr.addr == peer->address.host && plr.port == peer->address.port) {
|
||||
float x = DeserializeFloat(data[1], data[2]);
|
||||
float y = DeserializeFloat(data[3], data[4]);
|
||||
float z = DeserializeFloat(data[5], data[6]);
|
||||
plr.x = x;
|
||||
plr.y = y;
|
||||
plr.z = z;
|
||||
response[1] = plr.id;
|
||||
}
|
||||
}
|
||||
response[2] = data[1]; response[3] = data[2];
|
||||
response[4] = data[3]; response[5] = data[4];
|
||||
response[6] = data[5]; response[7] = data[6];
|
||||
response[8] = data[7];
|
||||
BroadcastPacketR(server, response, 9);
|
||||
break;
|
||||
case NET_ARG_PLAYERYAW:
|
||||
response[0] = NET_ARG_ECHOYAW;
|
||||
for (NetPlayer &plr : players)
|
||||
{
|
||||
if(plr.addr == peer->address.host && plr.port == peer->address.port) {
|
||||
response[1] = plr.id;
|
||||
plr.yaw = data[1];
|
||||
response[2] = plr.yaw;
|
||||
}
|
||||
}
|
||||
BroadcastPacketR(server, response, 3);
|
||||
break;
|
||||
case NET_ARG_REQPLAYERS:
|
||||
puts("Client requested players; sending");
|
||||
|
||||
for (NetPlayer &plr : players)
|
||||
{
|
||||
unsigned char responseP[9];
|
||||
responseP[0] = NET_ARG_PLRREG;
|
||||
if(plr.addr != peer->address.host || plr.port != peer->address.port) {
|
||||
SerializeFloat2Data(plr.x, responseP, 2);
|
||||
SerializeFloat2Data(plr.y, responseP, 4);
|
||||
SerializeFloat2Data(plr.z, responseP, 6);
|
||||
responseP[1] = plr.id;
|
||||
responseP[8] = plr.yaw;
|
||||
SendPacketR(peer, responseP, 9);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NET_ARG_BLOCKDELTA:
|
||||
response[0] = NET_ARG_BLOCK;
|
||||
for (NetPlayer &plr : players)
|
||||
{
|
||||
if(plr.addr == peer->address.host || plr.port == peer->address.port) {
|
||||
unsigned char responseB[6];
|
||||
responseB[0] = NET_ARG_BLOCK;
|
||||
responseB[1] = plr.id;
|
||||
responseB[2] = data[1];
|
||||
responseB[3] = data[2];
|
||||
responseB[4] = data[3];
|
||||
responseB[5] = data[4];
|
||||
SetBlock(world, data[1], data[2], data[3], data[4]);
|
||||
BroadcastPacketR(server, responseB, 6);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int FirstFreeID(const std::list<NetPlayer>& players) {
|
||||
std::unordered_set<int> used;
|
||||
used.reserve(players.size());
|
||||
for (const auto &p : players) used.insert(p.id);
|
||||
|
||||
int id = 0;
|
||||
while (used.find(id) != used.end()) ++id;
|
||||
return id;
|
||||
}
|
||||
static bool serverStarted = false;
|
||||
void StartInternalServer(bool& started) {
|
||||
int worldType = 0;
|
||||
InitWorldgen(worldType);
|
||||
std::cout << "Building chunks..." << std::endl;
|
||||
Chunk chunk = GenerateChunk(8, 8);
|
||||
for (int x = 0; x < MAX_WORLD_SIZE; x++)
|
||||
{
|
||||
for (int y = 0; y < MAX_WORLD_SIZE; y++)
|
||||
{
|
||||
memcpy(&world[x + y * MAX_WORLD_SIZE], &chunk, sizeof(Chunk));
|
||||
}
|
||||
}
|
||||
LoadPluginsFromFolder("plugins");
|
||||
std::cout << "Starting server..." << std::endl;
|
||||
server = StartServer();
|
||||
std::cout << "Serving on port: " << PORT << std::endl;
|
||||
serverStarted = true;
|
||||
started = true;
|
||||
}
|
||||
void ServerNetworkUpdate() {
|
||||
if(serverStarted) {
|
||||
ENetEvent event;
|
||||
while(enet_host_service (server, &event, 1) > 0)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
int id;
|
||||
char response[2];
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
printf ("A new client connected from %x:%u.\n",
|
||||
event.peer -> address.host,
|
||||
event.peer -> address.port);
|
||||
response[0] = NET_ARG_PLRID;
|
||||
id = FirstFreeID(players);
|
||||
response[1] = id;
|
||||
SendPacketR(event.peer, response, sizeof(response));
|
||||
{
|
||||
NetPlayer netPlayer;
|
||||
netPlayer.id = id;
|
||||
netPlayer.addr = event.peer->address.host;
|
||||
netPlayer.port = event.peer->address.port;
|
||||
netPlayer.peer = event.peer;
|
||||
players.push_back(netPlayer);
|
||||
|
||||
response[0] = NET_ARG_PLRCON;
|
||||
response[1] = netPlayer.id;
|
||||
BroadcastPacketR(server, response, sizeof(response));
|
||||
}
|
||||
// notify plugins
|
||||
CallPluginOnConnect(id);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
ParseData(event.packet, event.peer);
|
||||
enet_packet_destroy (event.packet);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
printf ("%s disconnected.\n", event.peer -> data);
|
||||
event.peer -> data = NULL;
|
||||
for (auto it = players.begin(); it != players.end(); ++it) {
|
||||
if (it->addr == event.peer->address.host && it->port == event.peer->address.port) {
|
||||
char responseD[2];
|
||||
responseD[0] = NET_ARG_PLRDCN;
|
||||
responseD[1] = it->id;
|
||||
BroadcastPacketR(server, (unsigned char*)responseD, sizeof(responseD));
|
||||
|
||||
int disconnected_id = it->id;
|
||||
it = players.erase(it);
|
||||
|
||||
// notify plugins
|
||||
CallPluginOnDisconnect(disconnected_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void StopInternalServer() {
|
||||
for (auto &pl : plugins) {
|
||||
lua_close(pl->L);
|
||||
}
|
||||
|
||||
enet_host_destroy(server);
|
||||
}
|
||||
Reference in New Issue
Block a user