Block selection Chunk de/compression now uses internal buffer directly (0-copy) Optimized Chunk vertices list order (faster vert access from GPU cache) F5 Debug info: added triangle count Implemented ladder climb Road + jump pad makes you jump farther Fixed bad fog color blending (alpha-channel) Changed LZFX and enet compilation to Release, -O3
368 lines
9.9 KiB
C++
368 lines
9.9 KiB
C++
#include "Server.hpp"
|
|
#include "Game.hpp"
|
|
#include "network/Network.hpp"
|
|
#include "network/NetHelper.hpp"
|
|
#include "VersionInfo.hpp"
|
|
#include "CaveGenerator.hpp"
|
|
#include "ChunkChangeHelper.hpp"
|
|
#include <iterator>
|
|
#include <algorithm>
|
|
#include <thread>
|
|
#include <sstream>
|
|
|
|
using std::cout;
|
|
using std::endl;
|
|
|
|
using namespace Diggler::Net;
|
|
|
|
namespace Diggler {
|
|
|
|
Player* Server::getPlayerByPeer(const Peer &peer) {
|
|
try {
|
|
return &G.players.getByPeer(peer);
|
|
} catch (const std::out_of_range &e) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
Player* Server::getPlayerById(uint32 id) {
|
|
try {
|
|
return &G.players.getById(id);
|
|
} catch (const std::out_of_range &e) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
Player* Server::getPlayerByName(const std::string &name) {
|
|
try {
|
|
return &G.players.getByName(name);
|
|
} catch (const std::out_of_range &e) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void Server::handlePlayerJoin(InMessage &msg, Peer &peer) {
|
|
std::string name = msg.readString();
|
|
getOutputStream() << peer.getHost() << " 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.getHost() << " tried to connect as " << name << ": name is taken" << endl;
|
|
return;
|
|
}
|
|
|
|
Player &plr = G.players.add();
|
|
plr.name = name;
|
|
plr.id = FastRand();
|
|
plr.P = peer;
|
|
|
|
// Confirm successful join
|
|
OutMessage join(MessageType::PlayerJoin);
|
|
join.writeU32(plr.id);
|
|
H.send(peer, join, Tfer::Rel);
|
|
|
|
// Send the player list
|
|
for (Player &p : G.players) {
|
|
if (p.id == plr.id)
|
|
continue; // ok, he knows he's here
|
|
OutMessage playerMsg(MessageType::PlayerJoin);
|
|
playerMsg.writeU32(p.id);
|
|
playerMsg.writeString(p.name);
|
|
H.send(peer, playerMsg, Tfer::Rel);
|
|
}
|
|
|
|
OutMessage map(MessageType::MapTransfer);
|
|
G.SC->write(map);
|
|
H.send(peer, map, Tfer::Rel);
|
|
|
|
// Broadcast player's join
|
|
OutMessage broadcast(MessageType::PlayerJoin);
|
|
broadcast.writeU32(plr.id);
|
|
broadcast.writeString(plr.name);
|
|
for (Player &p : G.players) {
|
|
if (p.id == plr.id)
|
|
continue; // dont send broadcast to the player
|
|
H.send(p.P, broadcast, Tfer::Rel);
|
|
}
|
|
getOutputStream() << plr.name << " joined from " << peer.getHost() << endl;
|
|
}
|
|
|
|
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.id);
|
|
for (Player &p : G.players) {
|
|
if (p.id == plr.id)
|
|
continue; // dont send broadcast to the player
|
|
H.send(p.P, broadcast, Tfer::Rel);
|
|
}
|
|
getOutputStream() << plr.name << " disconnected" << endl;
|
|
G.players.remove(plr);
|
|
} else {
|
|
getOutputStream() << peer.getHost() << " disconnected" << endl;
|
|
}
|
|
}
|
|
|
|
void Server::handleDisconnect(Peer &peer) {
|
|
handlePlayerQuit(peer, QuitReason::Timeout);
|
|
}
|
|
|
|
void Server::handleEvent(InMessage &msg, Peer &peer) {
|
|
Player &plr = G.players.getByPeer(peer);
|
|
switch (msg.getSubtype()) {
|
|
case Net::EventType::PlayerJumpOnPad: {
|
|
OutMessage out;
|
|
NetHelper::MakeEvent(out, (Net::EventType)msg.getSubtype(), plr);
|
|
NetHelper::Broadcast(G, out);
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Server::handleChat(InMessage &msg, Peer &peer) {
|
|
try {
|
|
// TODO: implement codecvt_utf8<utf32> when libstdc++ supports it
|
|
Player &plr = G.players.getByPeer(peer);
|
|
std::string chatMsg = msg.readString();
|
|
getOutputStream() << plr.name << ": " << chatMsg << endl;
|
|
std::ostringstream contentFormatter;
|
|
contentFormatter << plr.name << "> " << chatMsg;
|
|
std::string content = contentFormatter.str();
|
|
OutMessage newMsg(MessageType::Chat);
|
|
newMsg.writeString(content);
|
|
NetHelper::Broadcast(G, newMsg, Tfer::Rel);
|
|
} catch (const std::out_of_range &e) {
|
|
getErrorStream() << peer.getHost() << " sent chat message but is not connected" << std::endl;
|
|
}
|
|
}
|
|
|
|
void Server::handlePlayerUpdate(InMessage &msg, Peer &peer) {
|
|
try {
|
|
Player &plr = G.players.getByPeer(peer);
|
|
|
|
switch (msg.getSubtype()) {
|
|
case PlayerUpdateType::Move: {
|
|
// Broadcast movement
|
|
OutMessage bcast(MessageType::PlayerUpdate, PlayerUpdateType::Move);
|
|
bcast.writeU32(plr.id);
|
|
glm::vec3 pos = msg.readVec3(),
|
|
vel = msg.readVec3(),
|
|
acc = msg.readVec3();
|
|
plr.setPosVel(pos, vel, acc);
|
|
plr.angle = msg.readFloat();
|
|
bcast.writeVec3(pos);
|
|
bcast.writeVec3(vel);
|
|
bcast.writeVec3(acc);
|
|
bcast.writeFloat(plr.angle);
|
|
for (Player &p : G.players) {
|
|
if (p.id == plr.id)
|
|
continue; // dont send broadcast to the player
|
|
// TODO: confirm position to player
|
|
H.send(p.P, bcast, Tfer::Unrel);
|
|
}
|
|
} break;
|
|
case PlayerUpdateType::Die:
|
|
handlePlayerDeath(msg, plr);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} catch (std::out_of_range &e) {
|
|
// TODO: log?
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Server::handlePlayerMapUpdate(InMessage &msg, Peer &peer) {
|
|
// TODO: distance & tool check, i.e. legitimate update
|
|
int x = msg.readU16(), y = msg.readU16(), z = msg.readU16();
|
|
BlockType b = (BlockType)msg.readU8();
|
|
G.SC->set(x, y, z, b);
|
|
// FIXME: v This might interfere with the ticker
|
|
if (!G.CCH->empty()) {
|
|
OutMessage msg(MessageType::MapUpdate, G.CCH->count());
|
|
G.CCH->flush(msg);
|
|
NetHelper::Broadcast(G, msg, Tfer::Rel, Channels::MapUpdate);
|
|
}
|
|
}
|
|
|
|
void Server::handlePlayerDeath(InMessage &msg, Player &plr) {
|
|
uint8 drb = msg.readU8();
|
|
Player::DeathReason dr = (Player::DeathReason)drb;
|
|
plr.setDead(false, dr);
|
|
OutMessage out(MessageType::PlayerUpdate, PlayerUpdateType::Die);
|
|
out.writeU32(plr.id);
|
|
out.writeU8(drb);
|
|
for (Player &p : G.players) {
|
|
if (p.id != plr.id)
|
|
H.send(p.P, out, Tfer::Rel, Channels::Life);
|
|
}
|
|
|
|
// Respawn player later
|
|
Game *G = &this->G; uint32 id = plr.id;
|
|
std::thread respawn([G, id] {
|
|
getDebugStream() << "Respawn " << id << " in 2 secs " << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count() << std::endl;
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
|
getDebugStream() << "Respawn " << id << " in 2 secs " << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count() << std::endl;
|
|
Player *plr = G->S->getPlayerById(id);
|
|
if (plr) {
|
|
plr->setDead(false);
|
|
OutMessage out(MessageType::PlayerUpdate, PlayerUpdateType::Die);
|
|
out.writeU32(id);
|
|
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;
|
|
throw "Server init failed";
|
|
}
|
|
|
|
setup();
|
|
}
|
|
|
|
void Server::setupInternals() {
|
|
|
|
}
|
|
|
|
void Server::setup() {
|
|
G.CCH->enabled = false;
|
|
G.SC->setSize(4, 4, 4);
|
|
auto genStart = std::chrono::high_resolution_clock::now();
|
|
CaveGenerator::GenConf gc;
|
|
CaveGenerator::Generate(*(G.SC), gc);
|
|
auto genEnd = std::chrono::high_resolution_clock::now();
|
|
auto genDelta = std::chrono::duration_cast<std::chrono::milliseconds>(genEnd - genStart);
|
|
getOutputStream() << "Map gen took " << genDelta.count() << "ms" << std::endl;
|
|
G.CCH->enabled = true;
|
|
|
|
//G.SC->save("/tmp/a");
|
|
//G.SC->load("/tmp/a");
|
|
|
|
/*{
|
|
Game *G = this->G;
|
|
std::thread make([G]{CaveGenerator::GenerateCaveSystem(*(G.SC), true, 15);});
|
|
make.detach();
|
|
}*/
|
|
}
|
|
|
|
|
|
void chunk_updater(Game *G, Superchunk *sc, Host &H) {
|
|
while (true) {
|
|
for (int x=0; x < CX; x++)
|
|
for (int y=0; y < CY; y++)
|
|
for (int z=0; z < CZ; z++) {
|
|
Chunk* c = sc->getChunk(x, y, z);
|
|
if (c)
|
|
c->updateServerPrepare();
|
|
}
|
|
for (int x=0; x < CX; x++)
|
|
for (int y=0; y < CY; y++)
|
|
for (int z=0; z < CZ; z++) {
|
|
Chunk* c = sc->getChunk(x, y, z);
|
|
if (c)
|
|
c->updateServer();
|
|
}
|
|
for (int x=0; x < CX; x++)
|
|
for (int y=0; y < CY; y++)
|
|
for (int z=0; z < CZ; z++) {
|
|
Chunk* c = sc->getChunk(x, y, z);
|
|
if (c)
|
|
c->updateServerSwap();
|
|
}
|
|
if (!G->CCH->empty()) {
|
|
OutMessage msg(MessageType::MapUpdate, G->CCH->count());
|
|
// Message subtype = update count, trickery ;)
|
|
G->CCH->flush(msg);
|
|
NetHelper::Broadcast(G, msg, Tfer::Rel, Channels::MapUpdate);
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
}
|
|
}
|
|
|
|
void Server::run() {
|
|
InMessage msg;
|
|
Peer peer;
|
|
std::thread upd(chunk_updater, &G, G.SC.get(), std::ref(H));
|
|
while (true) {
|
|
if (H.recv(msg, peer, 100)) {
|
|
switch (msg.getType()) {
|
|
case MessageType::Connect:
|
|
getOutputStream() << peer.getHost() << " NEWCONN" << std::endl;
|
|
break;
|
|
case MessageType::Disconnect:
|
|
handleDisconnect(peer);
|
|
break;
|
|
|
|
case MessageType::PlayerJoin:
|
|
handlePlayerJoin(msg, peer);
|
|
break;
|
|
case MessageType::PlayerQuit:
|
|
handlePlayerQuit(peer);
|
|
break;
|
|
|
|
case MessageType::Chat:
|
|
handleChat(msg, peer);
|
|
break;
|
|
case MessageType::PlayerUpdate:
|
|
handlePlayerUpdate(msg, peer);
|
|
break;
|
|
case MessageType::Event:
|
|
handleEvent(msg, peer);
|
|
break;
|
|
case MessageType::MapUpdate:
|
|
handlePlayerMapUpdate(msg, peer);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.id);
|
|
msg.writeString(message);
|
|
H.send(p.P, msg, Tfer::Rel);
|
|
p.P.disconnect();
|
|
}
|
|
|
|
Server::~Server() {
|
|
}
|
|
|
|
} |