/**
   * Checks whether a character should change movement mode (from being underwater or in a ladder).
   * A higher and lower point of the character is tested for being in water, only if both points are
   * in water does the character count as swimming. <br>
   * <br>
   * Sends the OnEnterLiquidEvent and OnLeaveLiquidEvent events.
   *
   * @param movementComp The movement component of the character.
   * @param state The current state of the character.
   */
  private void checkMode(
      final CharacterMovementComponent movementComp,
      final CharacterStateEvent state,
      final CharacterStateEvent oldState,
      EntityRef entity,
      boolean firstRun) {
    // If we are ghosting or we can't move, the mode cannot be changed.
    if (!state.getMode().respondToEnvironment) {
      return;
    }

    Vector3f worldPos = state.getPosition();
    Vector3f top = new Vector3f(worldPos);
    Vector3f bottom = new Vector3f(worldPos);
    top.y += 0.5f * movementComp.height;
    bottom.y -= 0.5f * movementComp.height;

    final boolean topUnderwater = worldProvider.getBlock(top).isLiquid();
    final boolean bottomUnderwater = worldProvider.getBlock(bottom).isLiquid();

    final boolean newSwimming = !topUnderwater && bottomUnderwater;
    final boolean newDiving = topUnderwater && bottomUnderwater;
    boolean newClimbing = false;

    // TODO: refactor this knot of if-else statements into something easy to read. Some sub-methods
    // and switch statements would be nice.
    if (!newSwimming && !newDiving) { // TODO: generalize to isClimbingAllowed() or similar
      Vector3f[] sides = {
        new Vector3f(worldPos),
        new Vector3f(worldPos),
        new Vector3f(worldPos),
        new Vector3f(worldPos),
        new Vector3f(worldPos)
      };
      float factor = 1.0f;
      sides[0].x += factor * movementComp.radius;
      sides[1].x -= factor * movementComp.radius;
      sides[2].z += factor * movementComp.radius;
      sides[3].z -= factor * movementComp.radius;
      sides[4].y -= movementComp.height;

      float distance = 100f;

      for (Vector3f side : sides) {
        Block block = worldProvider.getBlock(side);
        if (block.isClimbable()) {
          // If any of our sides are near a climbable block, check if we are near to the side
          Vector3i myPos = new Vector3i(worldPos, 0.5f);
          Vector3i climbBlockPos = new Vector3i(side, 0.5f);
          Vector3i dir = new Vector3i(block.getDirection().getVector3i());
          float currentDistance = 10f;

          if (dir.x != 0
              && Math.abs(worldPos.x - (float) climbBlockPos.x + (float) dir.x * .5f)
                  < movementComp.radius + 0.1f) {
            newClimbing = true;
            if (myPos.x < climbBlockPos.x) {
              dir.x = -dir.x;
            }
            currentDistance = Math.abs(climbBlockPos.z - worldPos.z);

          } else if (dir.z != 0
              && Math.abs(worldPos.z - (float) climbBlockPos.z + (float) dir.z * .5f)
                  < movementComp.radius + 0.1f) {
            newClimbing = true;
            if (myPos.z < climbBlockPos.z) {
              dir.z = -dir.z;
            }
            currentDistance = Math.abs(climbBlockPos.z - worldPos.z);
          }

          // if there are multiple climb blocks, choose the nearest one. This can happen when there
          // are two
          // adjacent ledges around a corner.
          if (currentDistance < distance) {
            distance = currentDistance;
            state.setClimbDirection(dir);
          }
        }
      }
    }

    if (newDiving) {
      if (state.getMode() != MovementMode.DIVING) {
        state.setMode(MovementMode.DIVING);
      }
    } else if (newSwimming) {
      if (state.getMode() != MovementMode.SWIMMING) {
        state.setMode(MovementMode.SWIMMING);
      }
      state.getVelocity().y += 0.02;
    } else if (state.getMode() == MovementMode.SWIMMING) {
      if (newClimbing) {
        state.setMode(MovementMode.CLIMBING);
        state.getVelocity().y = 0;
      } else {
        if (state.getVelocity().y > 0) {
          state.getVelocity().y += 4;
        }
        state.setMode(MovementMode.WALKING);
      }
    } else if (newClimbing != (state.getMode() == MovementMode.CLIMBING)) {
      // We need to toggle the climbing mode
      state.getVelocity().y = 0;
      state.setMode((newClimbing) ? MovementMode.CLIMBING : MovementMode.WALKING);
    }
  }