terom@218: terom@236: #include "Player.hh" terom@236: #include "Weapons.hh" saiam@199: #include "Engine.hh" terom@412: terom@209: #include terom@313: #include nireco@216: #include terom@218: #include nireco@215: terom@417: #if GRAPHICS_ENABLED terom@423: terom@423: #include terom@423: #include "Graphics/Graphics.hh" terom@423: terom@417: /* terom@417: * Static draw-related state terom@417: */ terom@220: bool Player::skin_loaded = false; terom@220: CL_Surface Player::skin_surface; nireco@215: terom@417: #endif terom@417: terom@220: // XXX: give these better names and move elsewhere nireco@342: const int img_num_aim = 5; nireco@219: const int img_num_step = 4; nireco@248: const int img_height = 20; nireco@248: const int img_width = 17; nireco@216: ekku@198: Player::Player(GameState &state, Vector position, bool visible) : terom@282: PhysicsObject(state.world, PLAYER_MASS, position, Vector(0, 0), PLAYER, PLAYER_COLLISION_ELASTICITY), terom@282: state(state), terom@282: visible(visible), terom@282: weapons(buildWeaponsList()), terom@282: selectedWeapon(0), terom@282: rope(*this), terom@300: health(PLAYER_HEALTH), terom@300: respawn_timer(PLAYER_RESPAWN_DELAY), saiam@307: animation_step(0), saiam@307: kills(0), saiam@307: deaths(0) ekku@222: { terom@236: // XXX: populate weapons from somewhere else terom@236: terom@220: // build the player's shape terom@220: // XXX: these dimensions are incorrect... ekku@198: std::vector shape(4); ekku@198: shape[0] = Vector(0,-9); ekku@198: shape[1] = Vector(6,0); ekku@198: shape[2] = Vector(0,9); ekku@198: shape[3] = Vector(-6,0); terom@220: ekku@198: // Initialize the shape of the player (salmiakki shape) ekku@198: setShape(shape); terom@220: terom@274: // add to GameState players list terom@274: state.addPlayer(this); terom@300: terom@300: // connect respawn timer terom@300: respawn_slot = respawn_timer.sig_tick().connect(this, &Player::respawn); terom@300: terom@300: // spawn terom@300: spawn(position); terom@274: } terom@274: terom@274: Player::~Player (void) { terom@274: state.removePlayer(this); ekku@198: } terom@300: terom@300: void Player::spawn (Vector position) { terom@428: // dig hole to make room terom@408: world.terrain.removeGround(position, PLAYER_DIG_RADIUS); terom@300: terom@330: // reset health terom@330: health = PLAYER_HEALTH; terom@330: terom@330: // XXX: reload weapons terom@330: terom@428: // resume at our new position terom@428: resume(position); terom@300: } terom@300: terom@302: void Player::die (bool start_timer) { saiam@307: saiam@307: deaths++; // Increment death counter saiam@307: terom@302: // we don't have a rope anymore terom@302: rope.release(); terom@302: terom@428: // reset/disable our PhysicsObject terom@428: reset(); terom@302: terom@302: if (start_timer) { terom@302: // start respawn timer terom@302: respawn_timer.fire_once(); terom@302: } terom@300: } terom@300: terom@300: void Player::respawn (TimeMS dt) { terom@300: (void) dt; terom@409: terom@409: // spawn in the middle of the world terom@409: spawn(world.dimensions / 2); terom@300: } terom@236: saiam@272: void Player::handleDig (Vector pos, float radius) { nireco@250: // calculate new position and velocity saiam@272: Vector digPosition = pos + getDirection() * PROJECTILE_START_DISTANCE; terom@271: terom@271: // remove directly terom@408: world.terrain.removeGround(digPosition, radius); terom@209: } terom@209: terom@276: void Player::handleFireWeapon (Weapon *weapon, Vector position, Vector velocity) { terom@276: weaponFired(weapon); terom@276: terom@276: new Projectile(this, position, velocity, weapon); saiam@199: } terom@237: terom@237: void Player::handleChangeWeapon (unsigned int weaponIndex) { terom@237: assert(weaponIndex < weapons.size()); terom@237: terom@237: selectedWeapon = weaponIndex; terom@237: } terom@276: terom@276: void Player::weaponFired (Weapon *weapon) { terom@276: // apply recoil terom@276: applyForce(-getDirection() * weapon->getRecoil()); terom@276: terom@276: // reload weapon terom@276: weapon->reload(); terom@276: } saiam@199: terom@221: void Player::printDebugInfo (void) { terom@221: Engine::log(DEBUG, "layer.debug") << "In air: " << this->inAir; terom@221: } terom@221: terom@221: void Player::tick (TimeMS dt) { terom@221: // let PhysicsObject execute terom@221: PhysicsObject::tick(dt); terom@300: terom@221: // tick current weapon reload terom@236: if (getCurrentWeapon()) terom@236: getCurrentWeapon()->tickReload(dt); terom@221: } terom@241: terom@241: void Player::handleRopeState (RopeState state) { terom@282: (void) state; terom@241: } terom@241: terom@241: void Player::handleRopeLength (float length) { terom@282: (void) length; terom@241: } saiam@199: terom@300: /* terom@300: * LocalPlayer terom@300: */ terom@236: void LocalPlayer::fireWeapon (Weapon *weapon) { terom@235: // calculate new position and velocity terom@428: Vector shotPosition = getPosition() + getDirection() * PROJECTILE_START_DISTANCE; terom@428: Vector shotVelocity = getVelocity() + getDirection() * weapon->getSpeed(); saiam@258: terom@223: // execute terom@276: handleFireWeapon(weapon, shotPosition, shotVelocity); terom@223: } terom@237: terom@237: void LocalPlayer::changeWeapon (int delta) { terom@237: // need to handle negative deltas terom@237: handleChangeWeapon((weapons.size() + selectedWeapon + delta) % weapons.size()); terom@237: } terom@223: saiam@275: void LocalPlayer::handleInput (PlayerInput input, TimeMS dt) { terom@299: // if we are dead, we can't do anything terom@299: if (!isAlive()) terom@299: return; terom@299: terom@221: // Movement force, vertical is always zero terom@221: Vector move_force = Vector(0, 0); saiam@199: terom@221: // Crosshair angle change terom@221: float aim_delta = 0; saiam@199: terom@221: // handle movement left/right by applying a horizontal force, but limit the player's speed terom@235: // also update facing if needed ekku@262: if (!((input & INPUT_MOVE_LEFT) && (input & INPUT_MOVE_RIGHT))) { terom@428: // XXX: this should be physics... ekku@262: if (input & INPUT_MOVE_LEFT) { terom@428: if (getVelocity().x > -PLAYER_MAX_SPEED) ekku@262: move_force.x -= PLAYER_MOVE_FORCE; ekku@262: terom@264: setFacing(FACING_LEFT); ekku@262: } terom@221: ekku@262: if (input & INPUT_MOVE_RIGHT) { terom@428: if (getVelocity().x < PLAYER_MAX_SPEED) ekku@262: move_force.x += PLAYER_MOVE_FORCE; ekku@262: terom@264: setFacing(FACING_RIGHT); ekku@262: } terom@221: } terom@221: terom@221: // handle aim by creating a aim angle delta terom@221: if (input & INPUT_AIM_UP) saiam@293: aim_delta += CROSSHAIR_ANGLE_SPEED*dt; terom@221: terom@221: if (input & INPUT_AIM_DOWN) saiam@293: aim_delta -= CROSSHAIR_ANGLE_SPEED*dt; terom@221: terom@221: // handle jumping by invoking the jump method terom@221: // XXX: the direction should ideally be given using some other method terom@221: if (input & INPUT_JUMP) { terom@221: if (input & INPUT_MOVE_LEFT) saiam@199: jump(-1); terom@221: terom@221: else if (input & INPUT_MOVE_RIGHT) saiam@199: jump(1); terom@221: saiam@199: else saiam@199: jump(0); saiam@199: } terom@221: terom@221: // outsource digging to Player::handleDig, since this modifies the Terrain and Network needs to know terom@235: if (input & INPUT_DIG) terom@428: handleDig(getPosition(), PLAYER_DIG_RADIUS); terom@221: terom@237: // change weapon back/forth terom@237: if (input & INPUT_CHANGE_PREV) terom@237: changeWeapon(-1); terom@237: terom@237: if (input & INPUT_CHANGE_NEXT) terom@237: changeWeapon(+1); nireco@213: terom@221: // validate shoot events, and then outsource to handleShoot so Network can intercept it terom@236: if ((input & INPUT_SHOOT) && getCurrentWeapon()->canShoot()) terom@236: fireWeapon(getCurrentWeapon()); terom@221: terom@241: // rope throw+release+changeLength, but supress spurious events ekku@225: if (input & INPUT_ROPE) terom@235: rope.throwRope(); ekku@225: terom@241: if (input & INPUT_UNROPE && rope.getState() != ROPE_FOLDED) ekku@229: rope.release(); ekku@229: terom@241: if (input & INPUT_ROPE_UP && rope.getState() == ROPE_FIXED) terom@235: rope.changeLength(-ROPE_GROWTH_RATE); ekku@231: terom@241: if (input & INPUT_ROPE_DOWN && rope.getState() == ROPE_FIXED) terom@235: rope.changeLength(ROPE_GROWTH_RATE); ekku@231: terom@235: // XXX: how should this be written? What does this do? Probably broken under network play terom@221: if (move_force.x != 0) terom@221: animation_step = (animation_step + 1) % img_num_step; saiam@199: terom@221: // apply aim delta terom@221: if (aim_delta) terom@221: changeAim(aim_delta); nireco@219: terom@221: // apply force terom@221: if (!move_force.zero()) saiam@275: applyForce(move_force, dt); terom@300: terom@300: // suicide? terom@300: if (input & INPUT_SUICIDE) terom@300: die(); saiam@199: } saiam@199: terom@236: Weapon* Player::getCurrentWeapon() { terom@235: return weapons[selectedWeapon % weapons.size()]; nireco@212: } nireco@215: terom@276: Weapon* Player::getWeapon (WeaponID id) { terom@276: if (id < weapons.size()) terom@276: return weapons[id]; terom@276: else terom@276: return NULL; terom@276: } terom@276: ekku@295: void Player::onCollision (Vector collisionPoint, PhysicsObject *other) { terom@296: (void) collisionPoint; ekku@295: ekku@295: if (other == NULL) ekku@295: return; ekku@295: ekku@295: // Currently we handle only player-player collision here. ekku@295: // Player-projectile collision is handled in projectile's onCollision. terom@298: // XXX: not completely network-safe ekku@295: if (other->getType() == PLAYER) { ekku@335: //Vector normal = this->getPosition() - other->getPosition(); ekku@335: //this->bounce(normal); terom@298: ekku@295: // Move the player back to its previous position so that players won't ekku@295: // stuff inside each others when having a collision walk, e.g. ekku@335: //this->setPosition(this->getPreviousPosition()); ekku@295: } ekku@295: } ekku@295: ekku@322: Vector Player::getPivotForce () { ekku@322: Vector direction = this->pivot->getPosition() - this->getPosition(); ekku@322: float dirLength = direction.length(); ekku@322: if (dirLength >= this->rope.getLength()) ekku@322: return direction / dirLength * ROPE_FORCE; ekku@322: else ekku@322: return Vector(0,0); ekku@322: } ekku@322: saiam@308: void Player::takeDamage (Projectile *source) { saiam@308: health -= source->getDamage(); terom@296: saiam@308: if (health <= 0) { saiam@309: if (this != source->getOwner()) { saiam@309: source->addKillToOwner(); saiam@309: } terom@300: die(); saiam@308: } terom@296: saiam@308: Engine::log(DEBUG, "player.take_damage") << this << ": damage=" << source->getDamage() << ", health=" << health; saiam@308: Engine::log(DEBUG, "player.take_damage") << this << ": kills=" << kills << ", deaths=" << deaths; saiam@308: } saiam@308: terom@313: float Player::getHealthPercent () const { terom@313: return std::max(0.0f, this->health * 100 / (float) PLAYER_HEALTH); nireco@312: } nireco@312: saiam@308: void Player::addKill () { saiam@308: kills++; nireco@289: } nireco@289: terom@417: #if GRAPHICS_ENABLED terom@412: void Player::draw (graphics::Display &display, PixelCoordinate camera) { terom@412: CL_GraphicContext *gc = display.get_gc(); terom@299: terom@299: if (!isAlive()) terom@299: return; terom@313: terom@313: // draw rope behind player terom@412: rope.draw(display, camera); terom@255: terom@255: // animation indexes terom@255: int aim_img_idx = (int)((1 - (getAim() + KG_PI / 2) / KG_PI) * img_num_aim); terom@255: int step_img_idx = animation_step % img_num_step; nireco@217: terom@220: // load skin image if not yet loaded terom@220: if (!skin_loaded) { terom@220: skin_surface = CL_Surface(PLAYER_SKIN_PATH); terom@220: skin_loaded = true; terom@220: } terom@428: terom@428: // screen position terom@428: PixelCoordinate position = getCoordinate(); terom@221: terom@255: // calulate where to draw the worm terom@255: CL_Rectf destination( terom@264: position.x + (facing == FACING_RIGHT ? -1 : 1) * img_width / 2 - camera.x, terom@264: position.y - img_height / 2 - camera.y, terom@264: position.x + (facing == FACING_RIGHT ? 1 : -1) * img_width / 2 - camera.x, terom@264: position.y + img_height / 2 - camera.y terom@221: ); nireco@217: terom@255: // draw the correct animation frame from the skin nireco@342: // skin_surface.set_color(CL_Color(255, 0, 255)); terom@255: skin_surface.draw_subpixel( terom@255: CL_Rectf( terom@255: step_img_idx * img_width, terom@255: aim_img_idx * img_height, terom@255: (1 + step_img_idx) * img_width, terom@255: (aim_img_idx + 1) * img_height terom@255: ), destination, gc terom@255: ); terom@218: terom@261: // draw a proper crosshair-box terom@408: PixelCoordinate crosshair = getCoordinate() + world.terrain.getPixelCoordinate(getDirection() * PLAYER_CROSSHAIR_DISTANCE) - camera; terom@255: terom@261: gc->draw_rect( terom@261: CL_Rectf( terom@261: crosshair.x - PLAYER_CROSSHAIR_WIDTH / 2, terom@261: crosshair.y - PLAYER_CROSSHAIR_WIDTH / 2, terom@261: crosshair.x + PLAYER_CROSSHAIR_WIDTH / 2, terom@261: crosshair.y + PLAYER_CROSSHAIR_WIDTH / 2 terom@261: ), CL_Color::red terom@313: ); nireco@215: } nireco@215: terom@412: void LocalPlayer::draw (graphics::Display &display, bool displayWeapon, PixelCoordinate camera) { terom@233: // superclass draw terom@412: Player::draw(display, camera); nireco@219: terom@233: // display weapon name? terom@299: if (isAlive() && displayWeapon && getCurrentWeapon()) { terom@236: const std::string weaponName = getCurrentWeapon()->getName(); terom@412: terom@412: // position terom@412: PixelCoordinate pc = getCoordinate() - camera; terom@236: terom@412: // get font terom@412: CL_Font &font = graphics::graphics->fonts.getSimpleFont(); terom@255: terom@255: // XXX: fix magic constants once we know how big the worm is terom@412: font.draw( terom@412: pc.x - font.get_width(weaponName) / 2, terom@412: pc.y - 20, terom@412: weaponName, terom@412: display.get_gc() terom@233: ); terom@233: } terom@233: } terom@417: #endif terom@235: