#include "common.hpp" #include #include #include #include #include #include #include #include #include static std::list 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> 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(); 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& players) { std::unordered_set 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); }