452 lines
12 KiB
C++
452 lines
12 KiB
C++
#include "Server.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <thread>
|
|
#include <sstream>
|
|
|
|
#include <lua.h>
|
|
|
|
#include "Game.hpp"
|
|
#include "network/msgtypes/BlockUpdate.hpp"
|
|
#include "network/msgtypes/Chat.hpp"
|
|
#include "network/msgtypes/ChunkTransfer.hpp"
|
|
#include "network/msgtypes/PlayerUpdate.hpp"
|
|
#include "network/Network.hpp"
|
|
#include "network/NetHelper.hpp"
|
|
#include "scripting/lua/State.hpp"
|
|
#include "VersionInfo.hpp"
|
|
#include "CaveGenerator.hpp"
|
|
|
|
using std::cout;
|
|
using std::endl;
|
|
|
|
using namespace Diggler::Net;
|
|
|
|
namespace Diggler {
|
|
|
|
inline Player* Server::getPlayerByPeer(const Peer &peer) {
|
|
return G.players.getByPeer(peer);
|
|
}
|
|
inline Player *Server::getPlayerBySessId(uint32 id) {
|
|
return G.players.getBySessId(id);
|
|
}
|
|
inline Player* Server::getPlayerByName(const std::string &name) {
|
|
return G.players.getByName(name);
|
|
}
|
|
|
|
void Server::handlePlayerJoin(InMessage &msg, Peer &peer) {
|
|
std::string name = msg.readString();
|
|
getOutputStream() << peer.peerHost() << " is joining as " << name << std::endl;
|
|
|
|
// TODO: ban list
|
|
Player *possible = getPlayerByName(name);
|
|
if (possible != nullptr) {
|
|
// TODO: use kick() method
|
|
OutMessage kick(MessageType::PlayerQuit, QuitReason::UsernameAlreadyUsed);
|
|
kick.writeString("You are \faalready\f0 playing on this server");
|
|
H.send(peer, kick, Tfer::Rel);
|
|
getOutputStream() << peer.peerHost() << " tried to connect as " << name << ": name is taken" << endl;
|
|
return;
|
|
}
|
|
|
|
Player &plr = G.players.add();
|
|
plr.name = name;
|
|
plr.sessId = FastRand();
|
|
plr.peer = &peer;
|
|
plr.W = G.U->getLoadWorld(0);
|
|
|
|
// Confirm successful join
|
|
OutMessage join(MessageType::PlayerJoin);
|
|
join.writeU32(plr.sessId);
|
|
join.writeI16(plr.W->id);
|
|
H.send(peer, join, Tfer::Rel);
|
|
|
|
// Send the player list
|
|
for (Player &p : G.players) {
|
|
if (p.sessId == plr.sessId)
|
|
continue; // ok, he knows he's here
|
|
OutMessage playerMsg(MessageType::PlayerJoin);
|
|
playerMsg.writeU32(p.sessId);
|
|
playerMsg.writeString(p.name);
|
|
H.send(peer, playerMsg, Tfer::Rel);
|
|
}
|
|
|
|
// Broadcast player's join
|
|
OutMessage broadcast(MessageType::PlayerJoin);
|
|
broadcast.writeU32(plr.sessId);
|
|
broadcast.writeString(plr.name);
|
|
for (Player &p : G.players) {
|
|
if (p.sessId == plr.sessId)
|
|
continue; // don't send broadcast to the player
|
|
H.send(*p.peer, broadcast, Tfer::Rel);
|
|
}
|
|
getOutputStream() << plr.name << " joined from " << peer.peerHost() << endl;
|
|
for (int x = -2; x < 2; ++x)
|
|
for (int y = -2; y < 2; ++y)
|
|
for (int z = -2; z < 2; ++z)
|
|
schedSendChunk(G.U->getWorld(0)->getLoadChunk(x, y, z), plr);
|
|
}
|
|
|
|
void Server::handlePlayerQuit(Peer &peer, QuitReason reason) {
|
|
Player *plrPtr = getPlayerByPeer(peer);
|
|
if (plrPtr) {
|
|
Player &plr = *plrPtr;
|
|
// Broadcast disconnection
|
|
OutMessage broadcast(MessageType::PlayerQuit, reason);
|
|
broadcast.writeU32(plr.sessId);
|
|
for (Player &p : G.players) {
|
|
if (p.sessId == plr.sessId)
|
|
continue; // dont send broadcast to the player
|
|
H.send(*p.peer, broadcast, Tfer::Rel);
|
|
}
|
|
getOutputStream() << plr.name << " disconnected" << endl;
|
|
G.players.remove(plr);
|
|
} else {
|
|
getOutputStream() << peer.peerHost() << " disconnected" << endl;
|
|
}
|
|
}
|
|
|
|
void Server::handleDisconnect(Peer &peer) {
|
|
handlePlayerQuit(peer, QuitReason::Timeout);
|
|
}
|
|
|
|
void Server::handleChat(InMessage &msg, Player *plr) {
|
|
using namespace Net::MsgTypes;
|
|
using S = ChatSubtype;
|
|
switch (static_cast<S>(msg.getSubtype())) {
|
|
case S::Send: {
|
|
ChatSend cs;
|
|
cs.readFromMsg(msg);
|
|
|
|
ChatPlayerTalk cpt;
|
|
cpt.player.id = plr->sessId;
|
|
cpt.msg = cs.msg;
|
|
OutMessage omsg;
|
|
cpt.writeToMsg(omsg);
|
|
NetHelper::Broadcast(G, omsg, Tfer::Rel);
|
|
} break;
|
|
case S::Announcement:
|
|
case S::PlayerTalk:
|
|
; // No-op
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Server::handleCommand(Player *plr, const std::string &command, const std::vector<std::string> &args) {
|
|
getDebugStream() << "Command \"" << command << '"' << std::endl;
|
|
}
|
|
|
|
void Server::handlePlayerUpdate(InMessage &msg, Player &plr) {
|
|
using namespace Net::MsgTypes;
|
|
using S = PlayerUpdateSubtype;
|
|
switch (static_cast<S>(msg.getSubtype())) {
|
|
case S::Move: {
|
|
PlayerUpdateMove pum;
|
|
pum.readFromMsg(msg);
|
|
pum.plrSessId = plr.sessId;
|
|
// Broadcast movement
|
|
OutMessage bcast; pum.writeToMsg(bcast);
|
|
for (Player &p : G.players) {
|
|
if (p.sessId == plr.sessId)
|
|
continue; // dont send broadcast to the player
|
|
// TODO: confirm position to player
|
|
H.send(*p.peer, bcast, Tfer::Unrel);
|
|
}
|
|
} break;
|
|
case S::Die:
|
|
handlePlayerDeath(msg, plr);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Server::schedSendChunk(ChunkRef C, Player &P) {
|
|
P.pendingChunks.emplace_back(C);
|
|
}
|
|
|
|
void Server::sendChunks(const std::list<ChunkRef> &cs, Player &P) {
|
|
using namespace Net::MsgTypes;
|
|
ChunkTransferResponse ctr;
|
|
std::vector<OutMemoryStream> chunkBufs(cs.size());
|
|
size_t i = 0;
|
|
for (const ChunkRef &cr : cs) {
|
|
ctr.chunks.emplace_back();
|
|
ChunkTransferResponse::ChunkData &cd = ctr.chunks.back();
|
|
const Chunk &c = *cr;
|
|
cd.worldId = c.getWorld()->id;
|
|
cd.chunkPos = c.getWorldChunkPos();
|
|
c.write(chunkBufs[i]);
|
|
cd.dataLength = chunkBufs[i].length();
|
|
cd.data = chunkBufs[i].data();
|
|
++i;
|
|
}
|
|
OutMessage msg;
|
|
ctr.writeToMsg(msg);
|
|
H.send(*P.peer, msg, Tfer::Rel, Channels::MapTransfer);
|
|
}
|
|
|
|
void Server::handlePlayerChunkRequest(InMessage &msg, Player &plr) {
|
|
using namespace Net::MsgTypes;
|
|
using S = ChunkTransferSubtype;
|
|
switch (static_cast<S>(msg.getSubtype())) {
|
|
case S::Request: {
|
|
// TODO: distance & tool check, i.e. legitimate update
|
|
ChunkTransferRequest ctr;
|
|
ctr.readFromMsg(msg);
|
|
for (const ChunkTransferRequest::ChunkData &cd : ctr.chunks) {
|
|
WorldRef wld = G.U->getWorld(cd.worldId);
|
|
if (wld) {
|
|
schedSendChunk(wld->getLoadChunk(cd.chunkPos.x, cd.chunkPos.y, cd.chunkPos.z), plr);
|
|
}
|
|
}
|
|
} break;
|
|
case S::Response:
|
|
case S::Denied: {
|
|
; // No-op
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Server::handlePlayerMapUpdate(InMessage &msg, Player &plr) {
|
|
// TODO: distance & tool check, i.e. legitimate update
|
|
using namespace Net::MsgTypes;
|
|
using S = BlockUpdateSubtype;
|
|
switch (static_cast<S>(msg.getSubtype())) {
|
|
case S::Notify: {
|
|
; // No-op
|
|
} break;
|
|
case S::Place: {
|
|
BlockUpdatePlace bup;
|
|
bup.readFromMsg(msg);
|
|
WorldRef w = G.U->getWorld(bup.worldId);
|
|
if (w) {
|
|
ChunkRef c = w->getChunkAtCoords(bup.pos);
|
|
if (c) {
|
|
c->setBlock(rmod(bup.pos.x, CX), rmod(bup.pos.y, CY), rmod(bup.pos.z, CZ),
|
|
bup.id, bup.data);
|
|
if (!c->CH.empty()) {
|
|
BlockUpdateNotify bun;
|
|
c->CH.flush(bun);
|
|
OutMessage omsg;
|
|
bun.writeToMsg(omsg);
|
|
NetHelper::Broadcast(G, omsg, Tfer::Rel, Channels::MapUpdate);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case S::Break: {
|
|
BlockUpdateBreak bub;
|
|
bub.readFromMsg(msg);
|
|
WorldRef w = G.U->getWorld(bub.worldId);
|
|
if (w) {
|
|
ChunkRef c = w->getChunkAtCoords(bub.pos);
|
|
if (c) {
|
|
c->setBlock(rmod(bub.pos.x, CX), rmod(bub.pos.y, CY), rmod(bub.pos.z, CZ),
|
|
Content::BlockAirId, 0);
|
|
if (!c->CH.empty()) {
|
|
BlockUpdateNotify bun;
|
|
c->CH.flush(bun);
|
|
OutMessage omsg;
|
|
bun.writeToMsg(omsg);
|
|
NetHelper::Broadcast(G, omsg, Tfer::Rel, Channels::MapUpdate);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Server::handlePlayerDeath(InMessage &msg, Player &plr) {
|
|
using namespace Net::MsgTypes;
|
|
PlayerUpdateDie pud;
|
|
pud.readFromMsg(msg);
|
|
pud.plrSessId = plr.sessId;
|
|
plr.setDead(false, pud.reason);
|
|
OutMessage out; pud.writeToMsg(out);
|
|
for (Player &p : G.players) {
|
|
if (p.sessId != plr.sessId)
|
|
H.send(*p.peer, out, Tfer::Rel, Channels::Life);
|
|
}
|
|
|
|
// Respawn player later
|
|
Game *G = &this->G; Player::SessionID sid = plr.sessId;
|
|
std::thread respawn([G, sid] {
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
|
Player *plr = G->S->getPlayerBySessId(sid);
|
|
if (plr) {
|
|
plr->setDead(false);
|
|
PlayerUpdateRespawn pur;
|
|
pur.plrSessId = sid;
|
|
OutMessage out; pur.writeToMsg(out);
|
|
NetHelper::Broadcast(G, out, Tfer::Rel, Channels::Life);
|
|
}
|
|
});
|
|
respawn.detach();
|
|
}
|
|
|
|
Server::Server(Game &G, uint16 port) : G(G) {
|
|
G.init();
|
|
|
|
getOutputStream() << "Diggler v" << VersionString << " Server, port " << port << ", "
|
|
<< std::thread::hardware_concurrency() << " HW threads supported" << endl;
|
|
|
|
if (port >= 49152) {
|
|
getErrorStream() << "Warning: port is within the Ephemeral Port Range as defined by IANA!" <<
|
|
std::endl << " Nothing wrong with that, but for compatibility's sake please avoid this range." <<
|
|
std::endl;
|
|
}
|
|
|
|
try {
|
|
H.create(port);
|
|
} catch (Net::Exception &e) {
|
|
getErrorStream() << "Couldn't open port " << port << " for listening" << endl <<
|
|
"Make sure no other server instance is running" << endl;
|
|
if (port <= 1024) {
|
|
getErrorStream() << "The selected port is in range 1-1024, which typically require root "
|
|
"privileges. Make sure you have permission to bind to this port." << endl;
|
|
}
|
|
throw "Server init failed";
|
|
}
|
|
|
|
startInternals();
|
|
start();
|
|
}
|
|
|
|
void Server::startInternals() {
|
|
G.LS->initialize();
|
|
}
|
|
|
|
// FIXME ugly ugly hack to keep it in mem
|
|
static WorldRef World0Ref;
|
|
|
|
void Server::start() {
|
|
// TODO: loading
|
|
G.U = new Universe(&G, false);
|
|
|
|
WorldRef wr = G.U->createWorld(0);
|
|
World0Ref = wr;
|
|
|
|
for (int x = -2; x < 2; ++x)
|
|
for (int y = -2; y < 2; ++y)
|
|
for (int z = -2; z < 2; ++z)
|
|
holdChunksInMem.emplace_back(G.U->getWorld(0)->getLoadChunk(x, y, z));
|
|
}
|
|
|
|
void Server::stop() {
|
|
|
|
}
|
|
|
|
void Server::stopInternals() {
|
|
G.LS->finalize();
|
|
}
|
|
|
|
void Server::chunkUpdater(WorldRef WR, bool &continueUpdate) {
|
|
World &W = *WR;
|
|
while (continueUpdate) {
|
|
ChunkRef c;
|
|
for (auto pair : W)
|
|
if ((c = pair.second.lock()))
|
|
c->updateServer();
|
|
for (auto pair : W) {
|
|
if ((c = pair.second.lock()) && !c->CH.empty()) {
|
|
// TODO: view range
|
|
Net::MsgTypes::BlockUpdateNotify bun;
|
|
c->CH.flush(bun);
|
|
OutMessage msg;
|
|
bun.writeToMsg(msg);
|
|
NetHelper::Broadcast(G, msg, Tfer::Rel, Channels::MapUpdate);
|
|
}
|
|
}
|
|
std::list<ChunkRef> chunksToSend;
|
|
for (Player &p : G.players) {
|
|
chunksToSend.clear();
|
|
for (auto it = p.pendingChunks.begin();
|
|
it != p.pendingChunks.end() && chunksToSend.size() < 32; ++it) {
|
|
if ((*it)->getState() == Chunk::State::Ready) {
|
|
chunksToSend.push_back(std::move(*it));
|
|
it = --p.pendingChunks.erase(it);
|
|
}
|
|
}
|
|
sendChunks(chunksToSend, p);
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
}
|
|
}
|
|
|
|
void Server::run() {
|
|
InMessage msg;
|
|
Peer *peerPtr;
|
|
bool continueUpdate = true;
|
|
std::thread upd(&Server::chunkUpdater, this, G.U->getWorld(0), std::ref(continueUpdate));
|
|
Player *plr;
|
|
while (true) {
|
|
if (H.recv(msg, &peerPtr, 100)) {
|
|
Peer &peer = *peerPtr;
|
|
plr = getPlayerByPeer(peer);
|
|
if (plr != nullptr) {
|
|
switch (msg.getType()) {
|
|
case MessageType::Chat:
|
|
handleChat(msg, plr);
|
|
break;
|
|
case MessageType::PlayerUpdate:
|
|
handlePlayerUpdate(msg, *plr);
|
|
break;
|
|
case MessageType::ChunkTransfer:
|
|
handlePlayerChunkRequest(msg, *plr);
|
|
break;
|
|
case MessageType::BlockUpdate:
|
|
handlePlayerMapUpdate(msg, *plr);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
switch (msg.getType()) {
|
|
case MessageType::NetConnect:
|
|
getOutputStream() << peer.peerHost() << " NEWCONN" << std::endl;
|
|
break;
|
|
case MessageType::NetDisconnect:
|
|
handleDisconnect(peer);
|
|
break;
|
|
|
|
case MessageType::PlayerJoin:
|
|
handlePlayerJoin(msg, peer);
|
|
break;
|
|
case MessageType::PlayerQuit:
|
|
handlePlayerQuit(peer);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
continueUpdate = false;
|
|
upd.join();
|
|
getDebugStream() << "chunk updater thread joined" << std::endl;
|
|
}
|
|
|
|
bool Server::isPlayerOnline(const std::string &playername) const {
|
|
for (const Player &p : G.players) {
|
|
if (p.name == playername)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Server::kick(Player &p, Net::QuitReason r, const std::string &message) {
|
|
OutMessage msg(MessageType::PlayerQuit, r);
|
|
msg.writeU32(p.sessId);
|
|
msg.writeString(message);
|
|
H.send(*p.peer, msg, Tfer::Rel);
|
|
p.peer->disconnect();
|
|
}
|
|
|
|
Server::~Server() {
|
|
}
|
|
|
|
}
|