+ physic.set_velocity_x(dir == LEFT ? -speed : speed);
+ } else {
+ assert(false);
+ }
+}
+
+void
+Zeekling::onBumpVertical() {
+ if (state == FLYING) {
+ physic.set_velocity_y(0);
+ } else
+ if (state == DIVING) {
+ state = CLIMBING;
+ physic.set_velocity_y(-speed);
+ sprite->set_action(dir == LEFT ? "left" : "right");
+ } else
+ if (state == CLIMBING) {
+ state = FLYING;
+ physic.set_velocity_y(0);
+ }
+}
+
+void
+Zeekling::collision_solid(const CollisionHit& hit)
+{
+ if(hit.top || hit.bottom) {
+ onBumpVertical();
+ } else if(hit.left || hit.right) {
+ onBumpHorizontal();
+ }
+}
+
+/**
+ * linear prediction of player and badguy positions to decide if we should enter the DIVING state
+ */
+bool
+Zeekling::should_we_dive() {
+
+ const MovingObject* player = this->get_nearest_player();
+ if (player && last_player && (player == last_player)) {
+
+ // get positions, calculate movement
+ const Vector player_pos = player->get_pos();
+ const Vector player_mov = (player_pos - last_player_pos);
+ const Vector self_pos = this->get_pos();
+ const Vector self_mov = (self_pos - last_self_pos);
+
+ // new vertical speed to test with
+ float vy = 2*fabsf(self_mov.x);
+
+ // do not dive if we are not above the player
+ float height = player_pos.y - self_pos.y;
+ if (height <= 0) return false;
+
+ // do not dive if we are too far above the player
+ if (height > 512) return false;
+
+ // do not dive if we would not descend faster than the player
+ float relSpeed = vy - player_mov.y;
+ if (relSpeed <= 0) return false;
+
+ // guess number of frames to descend to same height as player
+ float estFrames = height / relSpeed;
+
+ // guess where the player would be at this time
+ float estPx = (player_pos.x + (estFrames * player_mov.x));
+
+ // guess where we would be at this time
+ float estBx = (self_pos.x + (estFrames * self_mov.x));
+
+ // near misses are OK, too
+ if (fabsf(estPx - estBx) < 8) return true;