#include "Server.hh"
#include "Protocol.hh"
#include "Config.hh"
#include "../Engine.hh"
#include "../Logger.hh"
#include <cassert>
#include <zlib.h>
NetworkServer::NetworkServer (GameState &state, const NetworkEndpoint &listen_addr) :
state(state), netsession(NETWORK_MAGIC_ID), controller(netsession, NETCHAN_CORE) {
// connect slots
slots.connect(netsession.sig_node_connected(), this, &NetworkServer::on_node_connected);
// and then we listen
netsession.listen(listen_addr);
Engine::log(INFO, "server") << "running, listen_addr=" << listen_addr;
}
void NetworkServer::on_node_connected (NetworkNode *node) {
// create the player object (it logs it)
NetworkServerPlayer *player = new NetworkServerPlayer(*this, node);
// add to players
players.push_back(player);
}
void NetworkServer::handle_disconnect (NetworkServerPlayer *player) {
// remove from list
players.remove(player);
}
/*
* NetworkServerObject
*/
NetworkServerObject::NetworkServerObject (NetworkServer &server) :
NetworkObject_Server(server.controller), server(server)
{
}
/*
* NetworkServerPlayer
*/
NetworkServerPlayer::NetworkServerPlayer (NetworkServer &server, NetworkNode *node) :
Player(server.state, Vector(PLAYER_INITIAL_X, PLAYER_INITIAL_Y), true), NetworkServerObject(server), node(node)
{
// log
Engine::log(INFO, "server_player.connected") << this << ": node=" << node;
// messages
slots.connect(node->sig_disconnected(), this, &NetworkServerPlayer::on_disconnected);
slots.connect(this->sig_message(NETMSG_CLIENT_INPUT), this, &NetworkServerPlayer::on_input);
// the initial NETMSG_PLAYER_HELLO
NetworkPacket hello_pkt;
hello_pkt.write_vector(position);
this->send_to(node, NETMSG_SERVER_HELLO, hello_pkt, true);
// send other player objects
for (std::list<NetworkServerPlayer*>::iterator it = server.players.begin(); it != server.players.end(); it++) {
NetworkPacket player_pkt;
NetworkServerPlayer *player = *it;
// player is not in players list yet
assert(player != this);
// write packet
player_pkt.write_vector(player->position);
player->send_to(node, NETMSG_PLAYER_INFO, player_pkt, true);
// XXX: send rope info...
}
// XXX: send projectiles? Or let the client handle the events that the unknown projectiles generate?
// broadcast NETMSG_PLAYER_JOIN to all clients except current
this->send_all_except(NETMSG_PLAYER_JOIN, hello_pkt, node, true);
// send terrain data...
send_terrain_data();
}
void NetworkServerPlayer::handleDig (Vector position, float radius) {
NetworkPacket pkt;
pkt.write_vector(position);
pkt.write_float32(radius);
Engine::log(INFO, "server_player.handle_dig") << "position=" << position << ", radius=" << radius;
// tell everyone... make this reliable...
this->send_all(NETMSG_PLAYER_DIG, pkt, true);
// and carry out the actual dig on the server as well
Player::handleDig(position, radius);
}
void NetworkServerPlayer::handleFireWeapon (Weapon *weapon, Vector position, Vector velocity) {
Engine::log(INFO, "server_player.fire_weapon") << "weapon='" << weapon->getName() << "', position=" << position << ", velocity=" << velocity;
// create new NetworkServerProjectile object
new NetworkServerProjectile(server, this, position, velocity, weapon);
// as handleFireWeapon does
weaponFired(weapon);
}
void NetworkServerPlayer::handleChangeWeapon (unsigned int weaponIndex) {
NetworkPacket pkt;
Engine::log(INFO, "server_player.change_weapon") << "weaponIndex=" << weaponIndex;
// write packet
pkt.write_uint8(weaponIndex);
// XXX: only tell the client itself?
send_all(NETMSG_PLAYER_WEAPON_CHANGE, pkt, true);
// pass through
Player::handleChangeWeapon(weaponIndex);
}
void NetworkServerPlayer::handleRopeState (RopeState state) {
NetworkPacket pkt;
Engine::log(INFO, "server_player.rope_state") << "state=" << rope.getState() << ", position=" << rope.getPosition() << ", velocity=" << rope.getVelocity() << ", length=" << rope.getLength() << ", pivotPlayer=" << rope.getPivotPlayer();
switch (state) {
case ROPE_FLYING:
pkt.write_vector(rope.getPosition());
pkt.write_vector(rope.getVelocity());
pkt.write_float32(rope.getLength());
send_all(NETMSG_PLAYER_ROPE_THROW, pkt, true);
break;
case ROPE_FIXED: {
Player *player_base = rope.getPivotPlayer();
NetworkServerPlayer *player = NULL;
if (player_base != NULL && (player = dynamic_cast<NetworkServerPlayer*>(player_base)) == NULL)
throw Error("NetworkServerPlayer::handleRopeState: rope's pivotPlayer is not a NetworkServerPlayer");
pkt.write_vector(rope.getPosition());
pkt.write_float32(rope.getLength());
controller.write_object(pkt, player); // may be NULL
send_all(NETMSG_PLAYER_ROPE_FIXED, pkt, true);
} break;
case ROPE_FOLDED:
send_all(NETMSG_PLAYER_ROPE_RELEASED, pkt, true);
break;
}
}
void NetworkServerPlayer::handleRopeLength (float length) {
NetworkPacket pkt;
pkt.write_float32(length);
send_all(NETMSG_PLAYER_ROPE_LENGTH, pkt, true);
}
void NetworkServerPlayer::spawn (Vector position) {
NetworkPacket pkt;
// write packet
pkt.write_vector(position);
Engine::log(INFO, "server_player.spawn") << this << ": position=" << position;
// send
send_all(NETMSG_PLAYER_SPAWN, pkt, true);
// super
Player::spawn(position);
}
void NetworkServerPlayer::die (bool start_timer) {
NetworkPacket pkt;
Engine::log(INFO, "server_player.die") << this;
// send
send_all(NETMSG_PLAYER_DIE, pkt, true);
// super
Player::die(start_timer);
}
void NetworkServerPlayer::on_disconnected (void) {
NetworkPacket pkt;
Engine::log(INFO, "server_player.disconnected") << this << ": node=" << node;
// remove from server
server.handle_disconnect(this);
// tell other clients
send_all(NETMSG_PLAYER_QUIT, pkt, true);
// free
// delete this;
}
void NetworkServerPlayer::on_input (NetworkNode *src, NetworkPacketInput &pkt) {
// sanity-check, other players shouldn't move
if (src != node) {
Engine::log(WARN, "server_player.on_input") << "packet from wrong src=" << src << ", node=" << node;
return;
}
// read packet
PlayerInput input = pkt.read_uint16();
TimeMS dt = pkt.read_uint32();
// Engine::log(INFO, "server_player.on_input") << "player=" << obj << ", old_pos=" << position << ", input=" << input;
// apply input
handleInput(input, dt);
// send position update
send_position_update();
}
void NetworkServerPlayer::send_terrain_data (void) {
Terrain &terrain = server.state.world;
// dimensions?
PixelCoordinate map = terrain.getDimensions();
// translate to a byte array
size_t terrain_size = map.x * map.y;
uint8_t terrain_buf[map.x][map.y];
// copy over from terrain vector
for (PixelDimension x = 0; x < map.x; x++) {
for (PixelDimension y = 0; y < map.y; y++) {
terrain_buf[x][y] = (uint8_t) terrain.terrain[x][y];
}
}
// compress the terrain buffer
unsigned long deflate_size = compressBound(terrain_size);
uint8_t deflate_buf[deflate_size];
// and compress
if (compress(deflate_buf, &deflate_size, (const uint8_t *) terrain_buf, terrain_size) != Z_OK)
throw Error("compress failed");
// allocate our packet...
BigNetworkPacket pkt (
NETWORK_SESSION_HEADER_SIZE // NetworkChannel header
+ 3 * sizeof(uint32_t) // our own header
+ deflate_size // compressed terrain buffer
);
// write netsession header
node->write_packet_header(pkt, NETCHAN_TERRAIN_ARRAY);
// write terrain dimensions
pkt.write_uint32(map.x);
pkt.write_uint32(map.y);
// write compressed data
pkt.write(deflate_buf, deflate_size);
// send
node->send_raw(pkt, true);
}
void NetworkServerPlayer::send_position_update (void) {
NetworkPacket pkt;
int flags =
(inAir ? NETWORK_PHYSICS_INAIR : 0) |
(facing == FACING_RIGHT ? NETWORK_PHYSICS_FACE_RIGHT : 0);
pkt.write_vector(position);
pkt.write_vector(velocity);
pkt.write_uint8(flags);
pkt.write_float32(aim);
// Engine::log(INFO, "server_player.send_position_update") << "obj=" << obj << " -> " << position << "+" << velocity << " [" << flags << "]";
send_all(NETMSG_PLAYER_POSITION, pkt, false);
}
/*
* NetworkServerProjectile
*/
NetworkServerProjectile::NetworkServerProjectile (NetworkServer &server, NetworkServerPlayer *player, Vector position,
Vector velocity, Weapon *weapon) :
Projectile(player, position, velocity, weapon, true), NetworkServerObject(server)
{
NetworkPacket pkt;
server.controller.write_object(pkt, player);
pkt.write_vector(position);
pkt.write_vector(velocity);
pkt.write_uint8(weapon->getID());
send_all(NETMSG_PROJECTILE_PLAYER_FIRED, pkt, true);
}
void NetworkServerProjectile::onDestroy (Vector position, bool removeGround) {
NetworkPacket pkt;
Engine::log(INFO, "server_projectile.destroy") << this << "position=" << position << ", removeGround=" << removeGround;
pkt.write_vector(position);
pkt.write_uint8(removeGround ? NETWORK_PROJECTILE_REMOVE_GROUND : 0);
send_all(NETMSG_PROJECTILE_DESTROY, pkt, true);
// XXX: leak obj, not yet implemented: obj.destory();
// pass on to super
Projectile::onDestroy(position, removeGround);
}
void NetworkServerProjectile::onHitPlayer (Player *player_ptr) {
NetworkPacket pkt;
NetworkServerPlayer *player = dynamic_cast<NetworkServerPlayer*>(player_ptr);
if (player == NULL)
throw Error("NetworkServerProjectile::onHitPlayer called with non-NetworkServerPlayer player");
Engine::log(INFO, "server_projectile.hit_player") << this << ": player=" << player;
// write packet
controller.write_object(pkt, player);
// send
send_all(NETMSG_PROJECTILE_HIT_PLAYER, pkt, true);
// super
Projectile::onHitPlayer(player_ptr);
}