#include "Player.hh"
#include "Weapons.hh"
#include "Engine.hh"
#include "Graphics.hh"
#include <cstdlib>
#include <ClanLib/display.h>
#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 = 9;
const int img_num_step = 4;
const int img_height = 9;
const int img_width = 10;
Player::Player(GameState &state, Vector position, bool visible) :
PhysicsObject(state.world, PLAYER_MASS, position, Vector(0, 0)), state(state), visible(visible),
weapons(buildWeaponsList()), selectedWeapon(0), changing(false), animation_step(0), rope(*this)
{
// 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);
// XXX: this should be a PhysicsObject constructor arg
collision_elasticity = PLAYER_COLLISION_ELASTICITY;
}
void Player::handleDig (Vector position, float radius) {
// XXX: clean this bit up
float shotspeed = 1; //0*PHYSICS_TICK_MS;
Vector shotVelocity = getDirection() * shotspeed;
new Projectile(state, position, shotVelocity, false, radius, 1);
}
void Player::handleCreateProjectile (Weapon *weapon, Vector position, Vector velocity) {
new Projectile(state, position, velocity, true, weapon->getExplosionRadius());
}
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 LocalPlayer::fireWeapon (Weapon *weapon) {
// update reload timer
weapon->reload();
// calculate new position and velocity
Vector shotPosition = position + getDirection() * PROJECTILE_START_DISTANCE;
Vector shotVelocity = velocity + getDirection() * weapon->getSpeed();
// execute
handleCreateProjectile(weapon, shotPosition, shotVelocity);
}
void LocalPlayer::handleInput (PlayerInput input) {
// 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) && (velocity.x > -PLAYER_MAX_SPEED)) {
setFacing(false);
move_force.x -= PLAYER_MOVE_FORCE;
}
if ((input & INPUT_MOVE_RIGHT) && (velocity.x < PLAYER_MAX_SPEED)) {
setFacing(true);
move_force.x += PLAYER_MOVE_FORCE;
}
// handle aim by creating a aim angle delta
if (input & INPUT_AIM_UP)
aim_delta += CROSSHAIR_ANGLE_SPEED;
if (input & INPUT_AIM_DOWN)
aim_delta -= CROSSHAIR_ANGLE_SPEED;
// 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, 15);
// XXX: currently not network safe
if (input & INPUT_CHANGE) {
selectedWeapon = (selectedWeapon + 1) % weapons.size();
Engine::log(DEBUG, "player.input ") << "changed weapon to " << selectedWeapon;
}
// 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
if (input & INPUT_ROPE)
rope.throwRope();
if (input & INPUT_UNROPE)
rope.release();
if (input & INPUT_ROPE_UP)
rope.changeLength(-ROPE_GROWTH_RATE);
if (input & INPUT_ROPE_DOWN)
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);
}
Weapon* Player::getCurrentWeapon() {
return weapons[selectedWeapon % weapons.size()];
}
void Player::draw (Graphics *g) {
CL_GraphicContext *gc = g->get_gc();
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;
}
// XXX: this logic looks weird
CL_Rectf destination(position.x - 4, position.y - 4, position.x + 5, position.y + 4);
if (!getFacing()) {
destination = CL_Rect(position.x + 5, position.y - 4, position.x - 4, position.y + 4);
}
skin_surface.draw_subpixel(
CL_Rectf(
1 + step_img_idx * img_width,
aim_img_idx * img_height + 1,
1 + (1 + step_img_idx) * img_width,
(aim_img_idx + 1) * img_height + 1
),
destination, gc
);
const uint16_t chlen = 10;
uint16_t x = position.x;
uint16_t y = position.y;
// draw "crosshair"
if (facingRight) {
gc->draw_line(x + std::cos(aim)*chlen/2,
y - std::sin(aim)*chlen/2,
x + std::cos(aim)*chlen,
y - std::sin(aim)*chlen,
CL_Color::black);
} else {
gc->draw_line(x - std::cos(aim)*chlen/2,
y - std::sin(aim)*chlen/2,
x - std::cos(aim)*chlen,
y - std::sin(aim)*chlen,
CL_Color::black);
}
rope.draw(g);
}
void LocalPlayer::draw (Graphics *g, bool displayWeapon) {
// superclass draw
Player::draw(g);
// display weapon name?
if (displayWeapon && getCurrentWeapon()) {
const std::string weaponName = getCurrentWeapon()->getName();
g->getSimpleFont().draw(
position.x - g->getSimpleFont().get_width(weaponName) / 2,
position.y + 10,
weaponName,
g->get_gc()
);
}
}