#include "Player.hh"
#include "Weapons.hh"
#include "Engine.hh"
#include "Graphics/Graphics.hh"
#include <cstdlib>
#include <ClanLib/display.h>
#include <algorithm>
#include <string>
#include <cassert>
// player static state
bool Player::skin_loaded = false;
CL_Surface Player::skin_surface;
// 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
world.terrain.removeGround(position, PLAYER_DIG_RADIUS);
// update position
setPosition(position);
// reset health
health = PLAYER_HEALTH;
// XXX: reload weapons
// enable
enable();
}
void Player::die (bool start_timer) {
deaths++; // Increment death counter
// we don't have a rope anymore
rope.release();
// disable our PhysicsObject
disable();
// XXX: PhysicsObject::reset
this->velocity = Vector(0, 0);
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 = position + getDirection() * PROJECTILE_START_DISTANCE;
Vector shotVelocity = velocity + 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))) {
if (input & INPUT_MOVE_LEFT) {
if (velocity.x > -PLAYER_MAX_SPEED)
move_force.x -= PLAYER_MOVE_FORCE;
setFacing(FACING_LEFT);
}
if (input & INPUT_MOVE_RIGHT) {
if (velocity.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(position, 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++;
}
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;
}
// 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()
);
}
}