src/Network/Server.cc
author Tero Marttila <terom@fixme.fi>
Tue, 27 Jan 2009 00:16:17 +0200
changeset 436 49f94c4bdf0d
parent 435 7bad24ab668e
permissions -rw-r--r--
move initial projectile sync message to NetworkServerProjectile::send_info

#include "Server.hh"
#include "Protocol.hh"
#include "../Config.hh"
#include "../Engine.hh"
#include "../Logger.hh"

// XXX: move
#include "Message.hh"

#include <cassert>

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, "net.server") << "Listening on interface: " << listen_addr;
}

void NetworkServer::on_node_connected (NetworkNode *node) {
    Engine::log(INFO, "net.server") << "Client connected, sending terrain data: " << node->getRemoteAddress();

    // send the terrain data
    send_terrain_data(*node);

    // create the player object (it logs it)
    NetworkServerPlayer *player = new NetworkServerPlayer(*this, *node);

    // add to players
    players.push_back(player);
}
 
void NetworkServer::send_terrain_data (NetworkNode &node) {
    Terrain &terrain = state.world.terrain;
    
    // dimensions?
    PixelCoordinate map = terrain.getDimensions();

    // translate to a byte array
    size_t terrain_size = map.x * map.y;
    
    // get terrain buffer
    const uint8_t *terrain_buf = terrain.getTerrainBuffer();

    // allocate our packet...
    BigNetworkPacket pkt (
        // NetworkChannel header
        NETWORK_SESSION_HEADER_SIZE     

        // our own header
        + 2 * sizeof(uint32_t)          

        // compressed terrain buffer
        + NetworkPacketOutput::write_compressed_size(terrain_size)
    );
    
    // write netsession header
    netsession.write_packet_header(pkt, NETCHAN_TERRAIN_ARRAY);

    // write terrain dimensions
    pkt.write_uint32(map.x);
    pkt.write_uint32(map.y);

    // write compressed terrain data
    pkt.write_compressed(terrain_buf, terrain_size);

    // send
    node.send_raw(pkt, true);
}

       
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) 
{
    // 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
    NetworkMessage hello_msg(*this, NETMSG_SERVER_HELLO);
    hello_msg.write_vector(getPosition());
    node.send(hello_msg);

    // sync other players
    for (std::list<NetworkServerPlayer*>::iterator it = server.players.begin(); it != server.players.end(); it++) {
        NetworkServerPlayer *player = *it;

        // player is not in players list yet
        assert(player != this);
        
        // send info
        player->send_info(node);
    }

    // sync projectiles
    for (std::list<Projectile*>::iterator it = server.state.projectiles.begin(); it != server.state.projectiles.end(); it++) {
        NetworkServerProjectile *proj = dynamic_cast<NetworkServerProjectile*>(*it);

        // all projectiles should be under the control of the server
        assert(proj);
        
        // send info
        proj->send_info(node);
    }

    // broadcast NETMSG_PLAYER_JOIN to all clients except current
    NetworkMessage join_msg(*this, NETMSG_PLAYER_JOIN);
    join_msg.write_vector(getPosition());
    server.netsession.all_nodes_except(node).send(join_msg);

    Engine::log(INFO, "net.server") << "Player joined: " << this << " from " << node.getRemoteAddress();
}
        
void NetworkServerPlayer::handleDig (Vector position, float radius) {
    NetworkMessage msg(*this, NETMSG_PLAYER_DIG);

    Engine::log(DEBUG, "server_player.handle_dig") << "position=" << position << ", radius=" << radius;

    // write packet
    msg.write_vector(position);
    msg.write_float32(radius);
    
    // broadcast packet
    server.netsession.all_nodes().send(msg);

    // 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(DEBUG, "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) {
    NetworkMessage msg(*this, NETMSG_PLAYER_WEAPON_CHANGE);

    Engine::log(DEBUG, "server_player.change_weapon") << "weaponIndex=" << weaponIndex;
    
    // write packet
    msg.write_uint8(weaponIndex);
    
    // XXX: only tell the client itself?
    server.netsession.all_nodes().send(msg);
    
    // pass through
    Player::handleChangeWeapon(weaponIndex);
}
        
void NetworkServerPlayer::handleRopeState (RopeState state) {
    Engine::log(DEBUG, "server_player.rope_state") << "state=" << rope.getState() << ", position=" << rope.getPosition() << ", velocity=" << rope.getVelocity() << ", length=" << rope.getLength() << ", pivotPlayer=" << rope.getPivotPlayer();
    
    // the NetworkGroup to send to
    NetworkGroup all = server.netsession.all_nodes();
    
    // send new rope state
    send_rope_state(all, state);
}
        
void NetworkServerPlayer::handleRopeLength (float length) {
    NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_LENGTH);
    
    // write packet
    msg.write_float32(length);
        
    // send packet
    server.netsession.all_nodes().send(msg);
}

void NetworkServerPlayer::spawn (Vector position) {
    NetworkMessage msg(*this, NETMSG_PLAYER_SPAWN);
    
    // write packet
    msg.write_vector(position);

    Engine::log(DEBUG, "server_player.spawn") << this << ": position=" << position;
    
    // send packet
    server.netsession.all_nodes().send(msg);
    
    // super
    Player::spawn(position);
}
        
void NetworkServerPlayer::die (bool start_timer) {
    NetworkMessage msg(*this, NETMSG_PLAYER_DIE);

    Engine::log(DEBUG, "server_player.die") << this;
    
    // send packet
    server.netsession.all_nodes().send(msg);

    // super
    Player::die(start_timer);
}

void NetworkServerPlayer::send_info (NetworkNode &node) {
    // we send a NETMSG_PLAYER_INFO message for the player object
    NetworkMessage player_msg(*this, NETMSG_PLAYER_INFO);
   
    // write packet
    player_msg.write_vector(getPosition());
    
    // send message to client
    node.send(player_msg);
    
    // then send rope state, unless folded
    if (rope.getState() != ROPE_FOLDED)
        send_rope_state(node, rope.getState());
}

void NetworkServerPlayer::send_rope_state (NetworkTarget &target, RopeState state) {
    switch (state) {
    case ROPE_FLYING: {
        NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_THROW);

        msg.write_vector(rope.getPosition());
        msg.write_vector(rope.getVelocity());
        msg.write_float32(rope.getLength());
        
        target.send(msg);
       
        } break;

    case ROPE_FIXED: {
        NetworkMessage msg(*this, NETMSG_PLAYER_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");
        
        msg.write_vector(rope.getPosition());
        msg.write_float32(rope.getLength());
        msg.write_object(player);    // may be NULL
        
        target.send(msg);

        } break;
    
    case ROPE_FOLDED: {
        NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_RELEASED);

        target.send(msg);

        } break;
    }
}

void NetworkServerPlayer::on_disconnected (void) {
    NetworkMessage msg(*this, NETMSG_PLAYER_QUIT);
    
    Engine::log(INFO, "net.server") << "Player disconnected: " << this;
    
    // remove from server
    server.handle_disconnect(this);
    
    // tell other clients
    server.netsession.all_nodes().send(msg);

    // XXX: 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_position_update (void) {
    NetworkMessage msg(*this, NETMSG_PLAYER_POSITION);
    
    int flags = 
        (inAir ? NETWORK_PHYSICS_INAIR : 0) | 
        (facing == FACING_RIGHT ? NETWORK_PHYSICS_FACE_RIGHT : 0);

    msg.write_vector(getPosition());
    msg.write_vector(getVelocity());
    msg.write_uint8(flags);
    msg.write_float32(aim);

//    Engine::log(INFO, "server_player.send_position_update") << "obj=" << obj << " -> " << position << "+" << velocity << " [" << flags << "]";

    server.netsession.all_nodes().send(msg);
}

/* 
 * NetworkServerProjectile
 */
NetworkServerProjectile::NetworkServerProjectile (NetworkServer &server, NetworkServerPlayer *player, Vector position,
        Vector velocity, Weapon *weapon) :
    Projectile(player, position, velocity, weapon, true), NetworkServerObject(server)
{
    NetworkMessage msg(*this, NETMSG_PROJECTILE_PLAYER_FIRED);

    // write out packet
    msg.write_object(player);
    msg.write_vector(getPosition());
    msg.write_vector(getVelocity());
    msg.write_uint8(weapon->getID());
    
    // send to given target
    server.netsession.all_nodes().send(msg);
}

void NetworkServerProjectile::onDestroy (Vector position, bool removeGround) {
    NetworkMessage msg(*this, NETMSG_PROJECTILE_DESTROY);

    Engine::log(DEBUG, "server_projectile.destroy") << this << "position=" << position << ", removeGround=" << removeGround;
    
    // write packet
    msg.write_vector(position);
    msg.write_uint8(removeGround ? NETWORK_PROJECTILE_REMOVE_GROUND : 0);
    
    // broadcast packet
    server.netsession.all_nodes().send(msg);

    // XXX: leak obj, not yet implemented:  obj.destory();
    
    // pass on to super
    Projectile::onDestroy(position, removeGround);
}

void NetworkServerProjectile::onHitPlayer (Player *player_ptr) {
    NetworkMessage msg(*this, NETMSG_PROJECTILE_HIT_PLAYER);
    NetworkServerPlayer *player = dynamic_cast<NetworkServerPlayer*>(player_ptr);

    if (player == NULL) 
        throw Error("NetworkServerProjectile::onHitPlayer called with non-NetworkServerPlayer player");
    
    Engine::log(DEBUG, "server_projectile.hit_player") << this << ": player=" << player;
    
    // write packet
    msg.write_object(player);

    // send packet
    server.netsession.all_nodes().send(msg);

    // super
    Projectile::onHitPlayer(player_ptr);
}

void NetworkServerProjectile::send_info (NetworkNode &node) {
    // get our NetworkServerPlayer via getOwner()
    const NetworkServerPlayer *player = dynamic_cast<const NetworkServerPlayer*>(getOwner());

    // as should players
    assert(player);
    
    // send info
    NetworkMessage msg(*this, NETMSG_PROJECTILE_INFO);

    // write out packet
    msg.write_object(player);
    msg.write_vector(getPosition());
    msg.write_vector(getVelocity());
    msg.write_uint8(getWeapon()->getID());
    
    // send to given target
    node.send(msg);

}