src/Network/Client.cc
author Tero Marttila <terom@fixme.fi>
Thu, 22 Jan 2009 02:47:53 +0200
branchnew_graphics
changeset 419 9cd4e54693b6
parent 417 c503e0c6a740
child 420 278020dcd9b7
permissions -rw-r--r--
fix Engine <-> NetworkClient interaction

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

#include <cassert>

/*
 * NetworkClientConnect
 */
NetworkClientConnect::NetworkClientConnect (Engine &engine, const NetworkEndpoint &connect_to) :
    engine(engine), netsession(NETWORK_MAGIC_ID)
{
    // connect NetworkSession to get server node (this is still blocking)
    server = netsession.connect(connect_to);
    
    // connect slots
    slots.connect(netsession.sig_chan_message(NETCHAN_TERRAIN_ARRAY), this, &NetworkClientConnect::on_terrain_array);
    slots.connect(server->sig_disconnected(), this, &NetworkClientConnect::on_disconnected);
    
    // then we must wait for the terrain data
}

void NetworkClientConnect::on_disconnected (void) {
    Engine::log(ERROR, "client.on_disconnect") << "Disconnected from server";
    engine.stop();
}

void NetworkClientConnect::on_terrain_array (NetworkPacketInput &pkt, NetworkNode *node) {
    // ignore if not from server
    if (node != server)
        return;
    
    // read map width/height
    PixelDimension map_w = pkt.read_uint32();
    PixelDimension map_h = pkt.read_uint32();

    // the terrain byte array
    size_t terrain_size = map_w * map_h;
    TerrainPixel *terrain_buf = new TerrainPixel[map_w * map_h];

    // read uncompressed terrain data
    size_t inflate_size = pkt.read_uncompressed(terrain_buf, terrain_size);
    
    // invalid data?
    if (inflate_size != terrain_size)
        throw Error("Corrupt terrain data");

    // create the terrain object that we then use, and hand over terrain_buf to it
    Terrain *terrain = new Terrain(map_w, map_h, terrain_buf);

    // execute connectDone
    connectDone(terrain);
}

void NetworkClientConnect::connectDone (Terrain *terrain) {
    // pass Terrain to engine to create game
    GameState &gs = engine.onNetworkClientConnected(terrain);

    // create our new NetworkClient object
    client = new NetworkClient(engine, gs, netsession, server);
}
 
/*
 * NetworkClient
 */
NetworkClient::NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode *server) : 
    engine(engine), state(state), netsession(netsession), server(server),
    controller(*this)
{

}
        
void NetworkClient::player_quit (NetworkClientRemotePlayer *player) {
    (void) player;
}

/*
 * NetworkClientController
 */ 
NetworkClientController::NetworkClientController (NetworkClient &client) :
    NetworkObject_ClientController(client.netsession, NETCHAN_CORE, client.server), client(client)
{

}

void NetworkClientController::handle_create (NetworkObjectID obj_id, NetworkMessageID msg_id, NetworkPacketInput &pkt, NetworkNode *node) {
    // XXX: should be server
    (void) node;

    switch (msg_id) {
        case NETMSG_SERVER_HELLO:
            on_server_hello(obj_id, pkt);
            break;

        case NETMSG_PLAYER_INFO:
            on_player_info(obj_id, pkt);
            break;

        case NETMSG_PLAYER_JOIN:
            on_player_join(obj_id, pkt);
            break;

        case NETMSG_PROJECTILE_PLAYER_FIRED:
            on_projectile_player_fired(obj_id, pkt);
            break;

        default:
            Engine::log(WARN, "client.handle_create") << "Unknown object create message: obj_id=" << obj_id << ", msg_id=" << msg_id;
    }
}
        
void NetworkClientController::on_server_hello (NetworkObjectID obj_id, NetworkPacketInput &pkt) {
    // read the packet
    Vector position = pkt.read_vector();
    
    Engine::log(INFO, "client.on_server_hello") << this << ": pos=" << position;

    // create the LocalPlayer object
    NetworkClientLocalPlayer *player = new NetworkClientLocalPlayer(client, obj_id, position);

    // pass it on to engine
    client.engine.onNetworkClientPlayer(player);
}
        
void NetworkClientController::on_player_info (NetworkObjectID obj_id, NetworkPacketInput &pkt) {
    // read the packet
    Vector position = pkt.read_vector();
    
    Engine::log(INFO, "client.on_player_info") << this << ": pos=" << position;

    // create the LocalPlayer object
    new NetworkClientRemotePlayer(client, obj_id, position);
}
        
void NetworkClientController::on_player_join (NetworkObjectID obj_id, NetworkPacketInput &pkt) {
    // read the packet
    Vector position = pkt.read_vector();
    
    Engine::log(INFO, "client.on_player_join") << this << ": pos=" << position;
    
    // create the RemotePlayer object
    new NetworkClientRemotePlayer(client, obj_id, position);
}
        
void NetworkClientController::on_projectile_player_fired (NetworkObjectID obj_id, NetworkPacketInput &pkt) {
    // read the packet
    NetworkObject *player_obj = client.controller.read_object(pkt);
    Vector position = pkt.read_vector();
    Vector velocity = pkt.read_vector();
    WeaponID weapon_id = pkt.read_uint8();
    
    NetworkClientPlayerBase *player;

    // ignore for invalid players
    if ((player = dynamic_cast<NetworkClientPlayerBase*>(player_obj)) == NULL) {
        Engine::log(ERROR, "client.on_projectile_player_fired") << this << ": Unknown player object";
        return;
    }

    Weapon *weapon;

    // try and get the weapon
    if ((weapon = player->getWeapon(weapon_id)) == NULL) {
        Engine::log(ERROR, "client.on_projectile_player_fired") << this << ": Unknown weapon id: player=" << player << ", weapon_id=" << weapon_id;
    }

    Engine::log(INFO, "client.on_projectile_create") << this << ": player=" << player << ", pos=" << position << ", velocity=" << velocity << ", weapon=" << weapon;

    // create the NetworkClientPorjectile object
    new NetworkClientProjectile(client, obj_id, player, position, velocity, weapon);
}

/*
 * NetworkClientObject
 */
NetworkClientObject::NetworkClientObject (NetworkClient &client, NetworkObjectID obj_id) :
    NetworkObject_Client(client.controller, obj_id), client(client)
{

}

/*
 * NetworkClientPlayerBase
 */
NetworkClientPlayerBase::NetworkClientPlayerBase (NetworkClient &client, NetworkObjectID obj_id, Vector position) :
    Player(client.state, position, true),
    NetworkClientObject(client, obj_id)
{
    slots.connect(sig_message(NETMSG_PLAYER_POSITION),          this,   &NetworkClientPlayerBase::on_position           );
    slots.connect(sig_message(NETMSG_PLAYER_DIG),               this,   &NetworkClientPlayerBase::on_dig                );
    slots.connect(sig_message(NETMSG_PLAYER_WEAPON_CHANGE),     this,   &NetworkClientPlayerBase::on_weapon_change      );
    slots.connect(sig_message(NETMSG_PLAYER_ROPE_THROW),        this,   &NetworkClientPlayerBase::on_rope_throw         );
    slots.connect(sig_message(NETMSG_PLAYER_ROPE_FIXED),        this,   &NetworkClientPlayerBase::on_rope_fixed         );
    slots.connect(sig_message(NETMSG_PLAYER_ROPE_RELEASED),     this,   &NetworkClientPlayerBase::on_rope_released      );
    slots.connect(sig_message(NETMSG_PLAYER_ROPE_LENGTH),       this,   &NetworkClientPlayerBase::on_rope_length        );
    slots.connect(sig_message(NETMSG_PLAYER_SPAWN),             this,   &NetworkClientPlayerBase::on_spawn              );
    slots.connect(sig_message(NETMSG_PLAYER_DIE),               this,   &NetworkClientPlayerBase::on_die                );
}

void NetworkClientPlayerBase::spawn (Vector position) {
    (void) position;

    throw Error("NetworkClientPlayerBase::spawn called");
}

void NetworkClientPlayerBase::respawn (TimeMS dt) {
    (void) dt;

    throw Error("NetworkClientPlayerBase::respawn called");
}

void NetworkClientPlayerBase::die (bool start_timer) {
    (void) start_timer;
    
    // ignore :>
}

void NetworkClientPlayerBase::on_position (NetworkPacketInput &pkt) {
    Vector position = pkt.read_vector();
    Vector velocity = pkt.read_vector();
    int flags = pkt.read_uint8();
    float aim = pkt.read_float32();

//    Engine::log(INFO, "client_player.on_position") << "obj=" << obj << ", position=" << position << ", velocity=" << velocity << ", aim=" << aim << ", [" << flags << "]";
    
    // just update... 
    updatePhysics(position, velocity, 
            flags & NETWORK_PHYSICS_INAIR, 
            flags & NETWORK_PHYSICS_FACE_RIGHT ? FACING_RIGHT : FACING_LEFT, 
            aim
    );
}

void NetworkClientPlayerBase::on_dig (NetworkPacketInput &pkt) {
    Vector position = pkt.read_vector();
    float radius = pkt.read_float32();

    Engine::log(INFO, "client_player.on_dig") << this << ": position=" << position << ", radius=" << radius;
    
    // just update... 
    handleDig(position, radius);
}
        
void NetworkClientPlayerBase::on_weapon_change (NetworkPacketInput &pkt) {
    uint8_t weapon_index = pkt.read_uint8();

    Engine::log(INFO, "client_player.on_weapon_change") << this << ": weapon_index=" << weapon_index;

    handleChangeWeapon(weapon_index);
}

void NetworkClientPlayerBase::on_rope_throw (NetworkPacketInput &pkt) {
    Vector position = pkt.read_vector();
    Vector velocity = pkt.read_vector();
    float length = pkt.read_float32();

    Engine::log(INFO, "client_player.on_rope_throw") << this << ": position=" << position << ", velocity=" << velocity << ", length=" << length;

    rope.updateState(ROPE_FLYING, position, velocity, length, NULL);
}

void NetworkClientPlayerBase::on_rope_fixed (NetworkPacketInput &pkt) {
    Vector position = pkt.read_vector();
    float length = pkt.read_float32();
    NetworkObject *player_obj = controller.read_object(pkt);

    NetworkClientPlayerBase *player = NULL;

    if (player_obj != NULL && (player = dynamic_cast<NetworkClientPlayerBase*>(player_obj)) == NULL) {
        Engine::log(ERROR, "client.on_rope_fixed") << this << ": Unknown player object";
        return;
    }
    
    Engine::log(INFO, "client_player.on_rope_fixed") << this << ": position=" << position << ", length=" << length 
        << ", player=" << player;

    rope.updateState(ROPE_FIXED, position, Vector(0, 0), length, player);
}

void NetworkClientPlayerBase::on_rope_released (NetworkPacketInput &pkt) {
    (void) pkt;

    Engine::log(INFO, "client_player.on_rope_released") << this;
    
    // use rope.getPosition() instead of e.g. Vector(0, 0) because it will collide there...
    rope.updateState(ROPE_FOLDED, rope.getPosition(), Vector(0, 0), 0, NULL);
}

void NetworkClientPlayerBase::on_rope_length (NetworkPacketInput &pkt) {
    float length = pkt.read_float32();
    
    Engine::log(INFO, "client_player.on_rope_length") << this << ": length=" << length;

    rope.updateLength(length);
}
        
void NetworkClientPlayerBase::on_spawn (NetworkPacketInput &pkt) {
    // read packet
    Vector position = pkt.read_vector();

    Engine::log(INFO, "client_player.on_spawn") << this << ": position=" << position;
    
    // super
    Player::spawn(position);
}
        
void NetworkClientPlayerBase::on_die (NetworkPacketInput &pkt) {
    (void) pkt;

    Engine::log(INFO, "client_player.on_die") << this;

    // super, but don't start the respawn_timer
    Player::die(false);
}

/*
 * NetworkClientLocalPlayer
 */
NetworkClientLocalPlayer::NetworkClientLocalPlayer (NetworkClient &client, NetworkObjectID obj_id, Vector position) :
    Player(client.state, position, true), NetworkClientPlayerBase(client, obj_id, position) 
{
    // set ourselves as the local player
    state.setLocalPlayer(this);
}
        
void NetworkClientLocalPlayer::handleInput (PlayerInput input, TimeMS dt) {
    NetworkPacket pkt;

    // write packet
    pkt.write_uint16(input);
    pkt.write_uint32(dt);
    
    // send
    send(NETMSG_CLIENT_INPUT, pkt, false);
    
    // do not handle locally
}
        
/*
 * NetworkClientRemotePlayer
 */
NetworkClientRemotePlayer::NetworkClientRemotePlayer (NetworkClient &client, NetworkObjectID obj_id, Vector position) :
    Player(client.state, position, true), NetworkClientPlayerBase(client, obj_id, position) {
    
    // receive messages
    slots.connect(sig_message(NETMSG_PLAYER_QUIT), this, &NetworkClientRemotePlayer::on_quit);
}

void NetworkClientRemotePlayer::on_quit (NetworkPacketInput &pkt) {
    // pkt is empty
    (void) pkt;

    Engine::log(INFO, "client_player.on_quit") << this;

    client.player_quit(this);

    // delete
    // XXX: leak because deleting the slot while it's being called breaks ClanLib
    //  delete this;
}

/*
 * NetworkClientProjectile
 */
NetworkClientProjectile::NetworkClientProjectile (NetworkClient &client, NetworkObjectID obj_id, Player *player,
        Vector position, Vector velocity, Weapon *weapon) :
    NetworkClientObject(client, obj_id), Projectile(player, position, velocity, weapon)
{
    // hook up signals
    slots.connect(sig_message(NETMSG_PROJECTILE_DESTROY), this, &NetworkClientProjectile::on_destroy);
    slots.connect(sig_message(NETMSG_PROJECTILE_HIT_PLAYER), this, &NetworkClientProjectile::on_hit_player);
    
    // tell Player
    player->weaponFired(weapon);
}

void NetworkClientProjectile::onDestroy (Vector position, bool removeGround) {
    (void) position;
    (void) removeGround;

    // ignore :>
}
        
void NetworkClientProjectile::onHitPlayer (Player *player) {
    (void) player;

    // ignore :>
}

void NetworkClientProjectile::on_destroy (NetworkPacketInput &pkt) {
    Vector position = pkt.read_vector();
    uint8_t flags = pkt.read_uint8();

    Engine::log(INFO, "client_projectile.on_destroy") << this << ": position=" << position << ", flags=" << flags;
    
    // pass on to super
    Projectile::onDestroy(position, flags & NETWORK_PROJECTILE_REMOVE_GROUND);
}

void NetworkClientProjectile::on_hit_player (NetworkPacketInput &pkt) {
    // read packet
    NetworkObject *player_obj = controller.read_object(pkt);

    NetworkClientPlayerBase *player;

    // ignore for invalid players
    if ((player = dynamic_cast<NetworkClientPlayerBase*>(player_obj)) == NULL) {
        Engine::log(ERROR, "client.on_hit_player") << this << ": Unknown player object";
        return;
    }
    
    Engine::log(INFO, "client_projectile.hit_player") << this << ": player=" << player;
    
    // pass on to super
    Projectile::onHitPlayer(player);
}