src/Player.cc
author nireco
Sat, 31 Jan 2009 12:33:08 +0200
changeset 443 5d1119729f58
parent 428 712b943195a6
permissions -rw-r--r--
worm02 two pics to comment

#include "Player.hh"
#include "Weapons.hh"
#include "Engine.hh"

#include <cstdlib>
#include <algorithm>
#include <string>
#include <cassert>

#if GRAPHICS_ENABLED

#include <ClanLib/display.h>
#include "Graphics/Graphics.hh"

/*
 * Static draw-related state
 */
bool Player::skin_loaded = false;
CL_Surface Player::skin_surface;

#endif

// XXX: give these better names and move elsewhere
const int img_num_aim = 5;
const int img_num_step = 4;
const int img_height = 20;
const int img_width = 17;

Player::Player(GameState &state, Vector position, bool visible) : 
    PhysicsObject(state.world, PLAYER_MASS, position, Vector(0, 0), PLAYER, PLAYER_COLLISION_ELASTICITY), 
    state(state), 
    visible(visible), 
    weapons(buildWeaponsList()), 
    selectedWeapon(0), 
    rope(*this),
    health(PLAYER_HEALTH),
    respawn_timer(PLAYER_RESPAWN_DELAY),
    animation_step(0),
    kills(0),
    deaths(0)
{
    // XXX: populate weapons from somewhere else

    // build the player's shape
    // XXX: these dimensions are incorrect...
    std::vector<Vector> shape(4);
    shape[0] = Vector(0,-9);
    shape[1] = Vector(6,0);
    shape[2] = Vector(0,9); 
    shape[3] = Vector(-6,0);

    // Initialize the shape of the player (salmiakki shape)
    setShape(shape);

    // add to GameState players list
    state.addPlayer(this);

    // connect respawn timer
    respawn_slot = respawn_timer.sig_tick().connect(this, &Player::respawn);

    // spawn
    spawn(position);
}

Player::~Player (void) {
    state.removePlayer(this);
}
    
void Player::spawn (Vector position) {
    // dig hole to make room
    world.terrain.removeGround(position, PLAYER_DIG_RADIUS);

    // reset health
    health = PLAYER_HEALTH;

    // XXX: reload weapons

    // resume at our new position
    resume(position);
}

void Player::die (bool start_timer) {

    deaths++; // Increment death counter

    // we don't have a rope anymore
    rope.release();

    // reset/disable our PhysicsObject
    reset();

    if (start_timer) {
        // start respawn timer
        respawn_timer.fire_once();
    }
}

void Player::respawn (TimeMS dt) {
    (void) dt;
    
    // spawn in the middle of the world
    spawn(world.dimensions / 2); 
}

void Player::handleDig (Vector pos, float radius) {
    // calculate new position and velocity
    Vector digPosition = pos + getDirection() * PROJECTILE_START_DISTANCE;
    
    // remove directly
    world.terrain.removeGround(digPosition, radius);
}

void Player::handleFireWeapon (Weapon *weapon, Vector position, Vector velocity) {
    weaponFired(weapon);

    new Projectile(this, position, velocity, weapon);
}
        
void Player::handleChangeWeapon (unsigned int weaponIndex) {
    assert(weaponIndex < weapons.size());

    selectedWeapon = weaponIndex;
}
    
void Player::weaponFired (Weapon *weapon) {
    // apply recoil
    applyForce(-getDirection() * weapon->getRecoil());
    
    // reload weapon
    weapon->reload();
}

void Player::printDebugInfo (void) {
    Engine::log(DEBUG, "layer.debug") << "In air: " << this->inAir;
}
        
void Player::tick (TimeMS dt) {
    // let PhysicsObject execute
    PhysicsObject::tick(dt);

    // tick current weapon reload
    if (getCurrentWeapon())
        getCurrentWeapon()->tickReload(dt);
}
        
void Player::handleRopeState (RopeState state) {
    (void) state;
}

void Player::handleRopeLength (float length) {
    (void) length;
}

/*
 * LocalPlayer
 */
void LocalPlayer::fireWeapon (Weapon *weapon) {
    // calculate new position and velocity
    Vector shotPosition = getPosition() + getDirection() * PROJECTILE_START_DISTANCE;
    Vector shotVelocity = getVelocity() + getDirection() * weapon->getSpeed();
    
    // execute
    handleFireWeapon(weapon, shotPosition, shotVelocity);
}
        
void LocalPlayer::changeWeapon (int delta) {
    // need to handle negative deltas
    handleChangeWeapon((weapons.size() + selectedWeapon + delta) % weapons.size());
}

void LocalPlayer::handleInput (PlayerInput input, TimeMS dt) {
    // if we are dead, we can't do anything
    if (!isAlive())
        return;

    // Movement force, vertical is always zero
    Vector move_force = Vector(0, 0); 

    // Crosshair angle change
    float aim_delta = 0; 

    // handle movement left/right by applying a horizontal force, but limit the player's speed
    // also update facing if needed
    if (!((input & INPUT_MOVE_LEFT) && (input & INPUT_MOVE_RIGHT))) {
        // XXX: this should be physics...
        if (input & INPUT_MOVE_LEFT) {
            if (getVelocity().x > -PLAYER_MAX_SPEED)
                move_force.x -= PLAYER_MOVE_FORCE;

            setFacing(FACING_LEFT);
        }

        if (input & INPUT_MOVE_RIGHT) {
            if (getVelocity().x < PLAYER_MAX_SPEED)
                move_force.x += PLAYER_MOVE_FORCE;

            setFacing(FACING_RIGHT);
        }
    }

    // handle aim by creating a aim angle delta
    if (input & INPUT_AIM_UP)
        aim_delta += CROSSHAIR_ANGLE_SPEED*dt;

    if (input & INPUT_AIM_DOWN)
        aim_delta -= CROSSHAIR_ANGLE_SPEED*dt;
    
    // handle jumping by invoking the jump method
    // XXX: the direction should ideally be given using some other method
    if (input & INPUT_JUMP) {
        if (input & INPUT_MOVE_LEFT)
            jump(-1);

        else if (input & INPUT_MOVE_RIGHT)
            jump(1);

        else
            jump(0);
    }
    
    // outsource digging to Player::handleDig, since this modifies the Terrain and Network needs to know
    if (input & INPUT_DIG)
        handleDig(getPosition(), PLAYER_DIG_RADIUS);
    
    // change weapon back/forth
    if (input & INPUT_CHANGE_PREV)
        changeWeapon(-1);

    if (input & INPUT_CHANGE_NEXT)
        changeWeapon(+1);

    // validate shoot events, and then outsource to handleShoot so Network can intercept it
    if ((input & INPUT_SHOOT) && getCurrentWeapon()->canShoot())
        fireWeapon(getCurrentWeapon());
    
    // rope throw+release+changeLength, but supress spurious events
    if (input & INPUT_ROPE)
        rope.throwRope();

    if (input & INPUT_UNROPE && rope.getState() != ROPE_FOLDED)
        rope.release();

    if (input & INPUT_ROPE_UP && rope.getState() == ROPE_FIXED)
        rope.changeLength(-ROPE_GROWTH_RATE);

    if (input & INPUT_ROPE_DOWN && rope.getState() == ROPE_FIXED)
        rope.changeLength(ROPE_GROWTH_RATE);

    // XXX: how should this be written? What does this do? Probably broken under network play
    if (move_force.x != 0) 
        animation_step = (animation_step + 1) % img_num_step;

    // apply aim delta
    if (aim_delta)
        changeAim(aim_delta);

    // apply force
    if (!move_force.zero())
        applyForce(move_force, dt);

    // suicide?
    if (input & INPUT_SUICIDE)
        die();
}

Weapon* Player::getCurrentWeapon() {
    return weapons[selectedWeapon % weapons.size()];
}

Weapon* Player::getWeapon (WeaponID id) {
    if (id < weapons.size())
        return weapons[id];
    else
        return NULL;
}

void Player::onCollision (Vector collisionPoint, PhysicsObject *other) {
    (void) collisionPoint;

    if (other == NULL)
        return;

    // Currently we handle only player-player collision here.
    // Player-projectile collision is handled in projectile's onCollision.
    // XXX: not completely network-safe
    if (other->getType() == PLAYER) {
        //Vector normal = this->getPosition() - other->getPosition();
        //this->bounce(normal);

        // Move the player back to its previous position so that players won't
        // stuff inside each others when having a collision walk, e.g.
        //this->setPosition(this->getPreviousPosition());
    }
}

Vector Player::getPivotForce () {
    Vector direction = this->pivot->getPosition() - this->getPosition();
    float dirLength = direction.length();
    if (dirLength >= this->rope.getLength())
        return direction / dirLength * ROPE_FORCE;
    else
        return Vector(0,0);
}
 
void Player::takeDamage (Projectile *source) {
    health -= source->getDamage();

    if (health <= 0) {
        if (this != source->getOwner()) { 
            source->addKillToOwner();
        }
        die();
    }

    Engine::log(DEBUG, "player.take_damage") << this << ": damage=" << source->getDamage() << ", health=" << health;
    Engine::log(DEBUG, "player.take_damage") << this << ": kills=" << kills << ", deaths=" << deaths;
}

float Player::getHealthPercent () const {
    return std::max(0.0f, this->health * 100 / (float) PLAYER_HEALTH);
}

void Player::addKill () {
    kills++;
}

#if GRAPHICS_ENABLED
void Player::draw (graphics::Display &display, PixelCoordinate camera) {
    CL_GraphicContext *gc = display.get_gc();

    if (!isAlive())
        return;

    // draw rope behind player
    rope.draw(display, camera);
    
    // animation indexes
    int aim_img_idx = (int)((1 - (getAim() + KG_PI / 2) / KG_PI) * img_num_aim);
    int step_img_idx = animation_step % img_num_step;

    // load skin image if not yet loaded
    if (!skin_loaded) {
        skin_surface = CL_Surface(PLAYER_SKIN_PATH);
        skin_loaded = true;
    }

    // screen position
    PixelCoordinate position = getCoordinate();
    
    // calulate where to draw the worm
    CL_Rectf destination(
        position.x + (facing == FACING_RIGHT ? -1 : 1) * img_width / 2  - camera.x, 
        position.y - img_height / 2                                     - camera.y, 
        position.x + (facing == FACING_RIGHT ? 1 : -1) * img_width / 2  - camera.x, 
        position.y + img_height / 2                                     - camera.y
    );
    
    // draw the correct animation frame from the skin
//    skin_surface.set_color(CL_Color(255, 0, 255));
    skin_surface.draw_subpixel(
        CL_Rectf(
            step_img_idx * img_width, 
            aim_img_idx * img_height, 
            (1 + step_img_idx) * img_width, 
            (aim_img_idx + 1) * img_height
        ), destination, gc
    );
    
    // draw a proper crosshair-box
    PixelCoordinate crosshair = getCoordinate() + world.terrain.getPixelCoordinate(getDirection() * PLAYER_CROSSHAIR_DISTANCE) - camera;
    
    gc->draw_rect(
        CL_Rectf(
            crosshair.x - PLAYER_CROSSHAIR_WIDTH / 2,
            crosshair.y - PLAYER_CROSSHAIR_WIDTH / 2,
            crosshair.x + PLAYER_CROSSHAIR_WIDTH / 2,
            crosshair.y + PLAYER_CROSSHAIR_WIDTH / 2
        ), CL_Color::red
    );    
}

void LocalPlayer::draw (graphics::Display &display, bool displayWeapon, PixelCoordinate camera) {
    // superclass draw
    Player::draw(display, camera);

    // display weapon name?
    if (isAlive() && displayWeapon && getCurrentWeapon()) {
        const std::string weaponName = getCurrentWeapon()->getName();
        
        // position
        PixelCoordinate pc = getCoordinate() - camera;

        // get font
        CL_Font &font = graphics::graphics->fonts.getSimpleFont();
        
        // XXX: fix magic constants once we know how big the worm is
        font.draw(
            pc.x - font.get_width(weaponName) / 2,
            pc.y - 20,
            weaponName,
            display.get_gc()
        );
    }
}
#endif