/**
  * Count the number of frames per second. Update the title bar with the count every second.
  *
  * @param millisSkipped The number of milliseconds skipped in the current frame
  */
 public void updateFPS(long millisSkipped) {
   millisecondsSkipped += millisSkipped;
   framesRendered += 1;
   if (Thunderbrand.getTime() - lastFPSTitleUpdate
       > 1000) { // Update the title in one-second increments
     GameInitializer.setWindowTitle(
         "FPS: " + framesRendered + " | Idle time: " + (millisecondsSkipped / 10) + "%");
     framesRendered = 0; // Reset the frames rendered
     millisecondsSkipped = 0; // Reset the milliseconds skipped
     lastFPSTitleUpdate += 1000; // Add one second
   }
 }
  public void run() throws InterruptedException, IOException {
    GameInitializer.initializeDisplay();
    Textures.initializeTextures();
    Crissaegrim.initializePlayer(boardMap);
    Player player = Crissaegrim.getPlayer();
    EntityMovementHelper playerMovementHelper = player.getMovementHelper();

    Crissaegrim.getValmanwayConnection().connectToValmonwayServer();
    if (Crissaegrim.connectionStable) { // Wait to get player id...
      long lastSend = 0;
      while (player.getId() == -1) {
        if (Thunderbrand.getTime() - lastSend
            > Crissaegrim.getValmanwayConnection().getConnectionTimeoutMillis()) {
          lastSend = Thunderbrand.getTime();
          Crissaegrim.addOutgoingDataPacket(
              new RequestPlayerIdPacket(Crissaegrim.getClientVersion()));
        }
      }
    } else { // Couldn't connect + offline mode disallowed; display error and quit
      displayMessageForever(Textures.NO_CONNECTION_MESSAGE, 458, 64, null);
      return;
    }
    if (player.getId() == -2) { // playerId of -2 signifies outdated version
      displayMessageForever(
          Textures.CLIENT_OUTDATED_MESSAGE, 595, 102, "Your version is outdated!");
      return;
    }

    Crissaegrim.addSystemMessage("Welcome to Crissaegrim!");
    setNewDestinationToSpawn();
    requestTravelToDestinationBoard();

    // Set name to last used username if applicable:
    String lastUsername = Crissaegrim.getPreferenceHandler().getLastUsername();
    if (lastUsername != null) {
      Crissaegrim.addOutgoingDataPacket(
          new SendChatMessagePacket(new TextBlock("/setname " + lastUsername, Color.WHITE)));
    } else {
      Crissaegrim.addSystemMessage("Tip: You can use /setname to permanently set your username.");
    }

    // initializeGame();

    lastFPSTitleUpdate = Thunderbrand.getTime();
    GameInitializer.initializeOpenGLFor2D();

    long startTime, endTime, elaspedTime; // Per-loop times to keep FRAMES_PER_SECOND
    while (!Display.isCloseRequested()) {
      startTime = Thunderbrand.getTime();

      if (!Crissaegrim.connectionStable) { // Lost connection to server
        Crissaegrim.getValmanwayConnection().closeConnections();
        displayMessageForever(
            Textures.LOST_CONNECTION_MESSAGE, 423, 64, "Connection lost - Please restart");
        return;
      }

      // Update the board, including all entities and bullets:
      if (!Crissaegrim.currentlyLoading) {
        ClientBoard.verifyChunksExist(Crissaegrim.getCurrentBoard());
        if (Crissaegrim.currentlyLoading) {
          continue;
        }
        player.update();
        setIconForDoodads();
        setIconForLocalDroppedItems();

        // Draw new scene:
        drawScene();

        // Get input and move the player:
        if (Crissaegrim.getChatBox().isTypingMode()) {
          Crissaegrim.getChatBox().getKeyboardInput(true);
        } else {
          getKeyboardAndMouseInput();
        }
        drawMouseHoverStatus();
        playerMovementHelper.moveEntityPre();
        Item itemToUse = playerMovementHelper.getItemToUse();
        if (itemToUse != null && !player.isBusy()) {
          // Cycle through relevant click-to-interact Doodads:
          for (Doodad doodad : Crissaegrim.getCurrentBoard().getDoodads().values()) {
            if (!player.isBusy()
                && RectUtils.coordinateIsInRect(player.getPosition(), doodad.getBounds())) {
              switch (doodad.getDoodadAction()) {
                case DoodadActions.MINE_ROCK:
                  if (!(player.getInventory().getCurrentItem() instanceof ItemPickaxe)) {
                    Crissaegrim.addSystemMessage(
                        "You need to be holding a pickaxe to mine this rock.");
                  } else if (player.getInventory().isFull()) {
                    new DialogBoxRunner().run(new DialogBox("Your inventory is full.", "Ok"));
                  } else {
                    MineableRock mineableRock = (MineableRock) doodad;
                    ItemPickaxe pickaxe = (ItemPickaxe) (player.getInventory().getCurrentItem());
                    if (!mineableRock.isDepleted()) {
                      Crissaegrim.addSystemMessage("You start mining the rock...");
                      player.setBusy(
                          new MiningRockBusy(player.getPosition(), pickaxe.getPickaxeType()));
                      Crissaegrim.addOutgoingDataPacket(
                          new MineRockRequestPacket(
                              mineableRock.getId(),
                              player.getId(),
                              player.getBusy().getId(),
                              player.getCurrentBoardName(),
                              mineableRock.getChanceOfSuccess()
                                  * pickaxe.getChanceOfSuccessMultiplier()));
                    }
                  }
                  break;
                default:
                  break;
              }
              break; // Found the relevant Doodad; ignore the rest
            }
          }
          // Use item if necessary:
          if (itemToUse instanceof ItemSword) {
            // TODO: This should be split up depending upon the weapon and attack type
            // TODO: Bounding rect of sword swing should not be entire entity
            ItemSword sword = (ItemSword) (itemToUse);
            player.setBusy(new SwordSwingBusy(sword.getSwordType()));
            Crissaegrim.addOutgoingDataPacket(
                new AttackPacket(
                    new Attack(
                        player.getId(),
                        player.getCurrentBoardName(),
                        player.getSwordSwingRect(sword),
                        sword.getAttackPower(),
                        1)));
          } else if (itemToUse instanceof ItemPartyPopper) {
            ItemPartyPopper popper = (ItemPartyPopper) (itemToUse);
            Crissaegrim.addOutgoingDataPacket(
                new ParticleSystemPacket(
                    125,
                    playerMovementHelper.getCoordinateClicked(),
                    player.getCurrentBoardName(),
                    popper.getColor()));
            if (popper.removeOneFromStack()) {
              player.getInventory().removeCurrentItem();
            }
          }
        }
        playerMovementHelper.moveEntityPost();

        drawHUD();

        // Transmit data to the server
        Crissaegrim.getValmanwayConnection().sendPlayerStatus();
      } else {
        drawScene();
        drawLoadingMessage();
        drawLoadingProgressBar();
      }

      Display.update();
      endTime = Thunderbrand.getTime();
      elaspedTime = endTime - startTime;
      Thread.sleep(Math.max(0, MILLISECONDS_PER_FRAME - elaspedTime));
      updateFPS(Math.max(0, MILLISECONDS_PER_FRAME - elaspedTime));
    }

    Display.destroy();
    Crissaegrim.getValmanwayConnection().closeConnections();
  }