private void tickChat() {
    if (chat.isOpen()) {
      keys.release();
    }

    if (keys.chat.wasReleased()) {
      chat.open();
    }

    chat.tick();

    String msg = chat.getWaitingMessage();
    if (msg != null) {
      synchronizer.addCommand(new ChatCommand(texts.playerName(player.localTeam) + ": " + msg));
    }
  }
  public void handleAction(int id) {
    if (id == TitleMenu.RETURN_TO_TITLESCREEN) {
      clearMenus();
      TitleMenu menu = new TitleMenu(GAME_WIDTH, GAME_HEIGHT);
      addMenu(menu);

    } else if (id == TitleMenu.START_GAME_ID) {
      clearMenus();
      isMultiplayer = false;
      chat.clear();

      localId = 0;
      localTeam = Team.Team1;
      synchronizer = new TurnSynchronizer(this, null, 0, 1);
      synchronizer.setStarted(true);

      createLevel(TitleMenu.level, TitleMenu.defaultGameMode);
      soundPlayer.stopBackgroundMusic();
    } else if (id == TitleMenu.SELECT_LEVEL_ID) {
      addMenu(new LevelSelect(false, localTeam));
    } else if (id == TitleMenu.SELECT_HOST_LEVEL_ID) {
      addMenu(new LevelSelect(true, localTeam));
    } else if (id == TitleMenu.UPDATE_LEVELS) {
      GuiMenu menu = menuStack.pop();
      if (menu instanceof LevelSelect) {
        addMenu(new LevelSelect(((LevelSelect) menu).bHosting, localTeam));
      } else {
        addMenu(new LevelSelect(false, localTeam));
      }
    } else if (id == TitleMenu.HOST_GAME_ID) {
      addMenu(new HostingWaitMenu());
      isMultiplayer = true;
      isServer = true;
      chat.clear();
      try {
        if (isServer) {
          localId = 0;
          localTeam = Team.Team1;
          serverSocket = new ServerSocket(3000);
          serverSocket.setSoTimeout(1000);

          hostThread =
              new Thread() {

                @Override
                public void run() {
                  boolean fail = true;
                  try {
                    while (!isInterrupted()) {
                      Socket socket = null;
                      try {
                        socket = serverSocket.accept();
                      } catch (SocketTimeoutException e) {

                      }
                      if (socket == null) {
                        System.out.println("Waiting for player to connect");
                        continue;
                      }
                      fail = false;

                      packetLink = new NetworkPacketLink(socket);

                      createServerState = 1;
                      break;
                    }
                  } catch (Exception e) {
                    e.printStackTrace();
                  }
                  if (fail) {
                    try {
                      serverSocket.close();
                    } catch (IOException e) {
                    }
                  }
                };
              };
          hostThread.start();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    } else if (id == TitleMenu.JOIN_GAME_ID) {
      addMenu(new JoinGameMenu());
    } else if (id == TitleMenu.CANCEL_JOIN_ID) {
      popMenu();
      if (hostThread != null) {
        hostThread.interrupt();
        hostThread = null;
      }
    } else if (id == TitleMenu.PERFORM_JOIN_ID) {
      menuStack.clear();
      isMultiplayer = true;
      isServer = false;
      chat.clear();

      try {
        localId = 1;
        localTeam = Team.Team2;
        packetLink = new ClientSidePacketLink(TitleMenu.ip, 3000);
        synchronizer = new TurnSynchronizer(this, packetLink, localId, 2);
        packetLink.setPacketListener(this);
      } catch (Exception e) {
        e.printStackTrace();
        // System.exit(1);
        addMenu(new TitleMenu(GAME_WIDTH, GAME_HEIGHT));
      }
    } else if (id == TitleMenu.HOW_TO_PLAY) {
      addMenu(new HowToPlayMenu());
    } else if (id == TitleMenu.OPTIONS_ID) {
      addMenu(new OptionsMenu());
    } else if (id == TitleMenu.SELECT_DIFFICULTY_ID) {
      addMenu(new DifficultySelect(false));
    } else if (id == TitleMenu.SELECT_DIFFICULTY_HOSTING_ID) {
      addMenu(new DifficultySelect(true));
    } else if (id == TitleMenu.KEY_BINDINGS_ID) {
      addMenu(new KeyBindingsMenu(keys, inputHandler));
    } else if (id == TitleMenu.EXIT_GAME_ID) {
      System.exit(0);
    } else if (id == TitleMenu.RETURN_ID) {
      synchronizer.addCommand(new PauseCommand(false));
      keys.tick();
    } else if (id == TitleMenu.BACK_ID) {
      popMenu();
    }
  }
  private void tick() {
    // Not-In-Focus-Pause
    if (this.isFocusOwner() && level != null) {
      paused2 = false;
    }

    if (!this.isFocusOwner() && level != null) {
      keys.release();
      mouseButtons.releaseAll();
      if (!paused && !paused2) {
        PauseCommand pauseCommand = new PauseCommand(true);
        synchronizer.addCommand(pauseCommand);
        paused2 = true;
      }
    }
    if (isMultiplayer) {
      tickChat();
    }

    if (requestToggleFullscreen || keys.fullscreen.wasPressed()) {
      requestToggleFullscreen = false;
      setFullscreen(!fullscreen);
    }

    if (level != null && level.victoryConditions != null) {
      if (level.victoryConditions.isVictoryConditionAchieved()) {
        addMenu(new WinMenu(GAME_WIDTH, GAME_HEIGHT, level.victoryConditions.playerVictorious()));
        level = null;
        return;
      }
    }

    if (packetLink != null) {
      packetLink.tick();
    }

    mouseButtons.setPosition(getMousePosition());
    if (!menuStack.isEmpty()) {
      menuStack.peek().tick(mouseButtons);
    }
    if (mouseMoved) {
      mouseMoved = false;
      mouseHideTime = 0;
      if (mouseButtons.mouseHidden) {
        mouseButtons.mouseHidden = false;
      }
    }
    if (mouseHideTime < 60) {
      mouseHideTime++;
      if (mouseHideTime == 60) {
        mouseButtons.mouseHidden = true;
      }
    }

    if (level == null) {
      mouseButtons.tick();
    } else if (level != null) {
      if (synchronizer.preTurn()) {
        synchronizer.postTurn();

        for (int index = 0; index < mouseButtons.currentState.length; index++) {
          boolean nextState = mouseButtons.nextState[index];
          if (mouseButtons.isDown(index) != nextState) {
            synchronizer.addCommand(new ChangeMouseButtonCommand(index, nextState));
          }
        }

        synchronizer.addCommand(
            new ChangeMouseCoordinateCommand(
                mouseButtons.getX(), mouseButtons.getY(), mouseButtons.mouseHidden));

        mouseButtons.tick();
        for (MouseButtons sMouseButtons : synchedMouseButtons) {
          sMouseButtons.tick();
        }

        if (!paused) {
          for (int index = 0; index < keys.getAll().size(); index++) {
            Keys.Key key = keys.getAll().get(index);
            boolean nextState = key.nextState;
            if (key.isDown != nextState) {
              synchronizer.addCommand(new ChangeKeyCommand(index, nextState));
            }
          }

          keys.tick();
          for (Keys skeys : synchedKeys) {
            skeys.tick();
          }

          if (keys.pause.wasPressed()) {
            keys.release();
            mouseButtons.releaseAll();
            synchronizer.addCommand(new PauseCommand(true));
          }

          level.tick();
        }

        // every 4 minutes, start new background music :)
        if (System.currentTimeMillis() / 1000 > nextMusicInterval) {
          nextMusicInterval = (System.currentTimeMillis() / 1000) + 4 * 60;
          soundPlayer.startBackgroundMusic();
        }

        if (keys.screenShot.isDown) {
          takeScreenShot();
        }
      }
    }

    if (createServerState == 1) {
      createServerState = 2;

      synchronizer = new TurnSynchronizer(MojamComponent.this, packetLink, localId, 2);

      clearMenus();
      createLevel(TitleMenu.level, TitleMenu.defaultGameMode);

      synchronizer.setStarted(true);
      if (TitleMenu.level.vanilla) {
        packetLink.sendPacket(
            new StartGamePacket(
                TurnSynchronizer.synchedSeed,
                TitleMenu.level.getUniversalPath(),
                DifficultyList.getDifficultyID(TitleMenu.difficulty)));
      } else {
        packetLink.sendPacket(
            new StartGamePacketCustom(
                TurnSynchronizer.synchedSeed,
                level,
                DifficultyList.getDifficultyID(TitleMenu.difficulty)));
      }
      packetLink.setPacketListener(MojamComponent.this);
    }
  }
  @Override
  public void run() {
    long lastTime = System.nanoTime();
    double unprocessed = 0;
    int frames = 0;
    long lastTimer1 = System.currentTimeMillis();

    try {
      init();
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }

    // if (!isMultiplayer) {
    // createLevel();
    // }

    int toTick = 0;

    long lastRenderTime = System.nanoTime();
    int min = 999999999;
    int max = 0;

    while (running) {
      if (!this.hasFocus()) {
        keys.release();
      }

      double nsPerTick = 1000000000.0 / framerate;
      boolean shouldRender = false;
      while (unprocessed >= 1) {
        toTick++;
        unprocessed -= 1;
      }

      int tickCount = toTick;
      if (toTick > 0 && toTick < 3) {
        tickCount = 1;
      }
      if (toTick > 20) {
        toTick = 20;
      }

      for (int i = 0; i < tickCount; i++) {
        toTick--;
        // long before = System.nanoTime();
        tick();
        // long after = System.nanoTime();
        // System.out.println("Tick time took " + (after - before) *
        // 100.0 / nsPerTick + "% of the max time");
        shouldRender = true;
      }
      // shouldRender = true;

      BufferStrategy bs = getBufferStrategy();
      if (bs == null) {
        createBufferStrategy(3);
        continue;
      }

      if (shouldRender) {
        frames++;
        Graphics g = bs.getDrawGraphics();

        Random lastRandom = TurnSynchronizer.synchedRandom;
        TurnSynchronizer.synchedRandom = null;

        render(g);

        TurnSynchronizer.synchedRandom = lastRandom;

        long renderTime = System.nanoTime();
        int timePassed = (int) (renderTime - lastRenderTime);
        if (timePassed < min) {
          min = timePassed;
        }
        if (timePassed > max) {
          max = timePassed;
        }
        lastRenderTime = renderTime;
      }

      long now = System.nanoTime();
      unprocessed += (now - lastTime) / nsPerTick;
      lastTime = now;

      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      if (shouldRender) {
        if (bs != null) {
          bs.show();
        }
      }

      if (System.currentTimeMillis() - lastTimer1 > 1000) {
        lastTimer1 += 1000;
        fps = frames;
        frames = 0;
      }
    }
  }
  public void handleAction(int id) {
    switch (id) {
      case TitleMenu.RETURN_TO_TITLESCREEN:
        clearMenus();
        level = null;
        TitleMenu menu = new TitleMenu(GAME_WIDTH, GAME_HEIGHT);
        addMenu(menu);
        this.nextMusicInterval = 0;
        soundPlayer.stopBackgroundMusic();
        soundPlayer.startTitleMusic();
        break;

      case TitleMenu.START_GAME_ID:
        clearMenus();
        isMultiplayer = false;
        chat.clear();

        localId = 0;
        MojamComponent.localTeam = Team.Team1;
        synchronizer = new TurnSynchronizer(this, null, 0, 1);
        synchronizer.setStarted(true);

        createLevel(TitleMenu.level, TitleMenu.defaultGameMode);
        soundPlayer.stopBackgroundMusic();
        break;

      case TitleMenu.SELECT_LEVEL_ID:
        addMenu(new LevelSelect(false));
        break;

      case TitleMenu.SELECT_HOST_LEVEL_ID:
        addMenu(new LevelSelect(true));
        break;
        /*
         * case TitleMenu.UPDATE_LEVELS:
         * GuiMenu menu = menuStack.pop();
         * if (menu instanceof LevelSelect) { addMenu(new
         * LevelSelect(((LevelSelect) menu).bHosting)); } else { addMenu(new
         * LevelSelect(false)); } }
         */
      case TitleMenu.HOST_GAME_ID:
        addMenu(new HostingWaitMenu());
        isMultiplayer = true;
        isServer = true;
        chat.clear();
        try {
          if (isServer) {
            localId = 0;
            MojamComponent.localTeam = Team.Team1;
            serverSocket = new ServerSocket(Options.getAsInteger(Options.MP_PORT, 3000));
            serverSocket.setSoTimeout(1000);

            hostThread =
                new Thread() {

                  @Override
                  public void run() {
                    boolean fail = true;
                    try {
                      while (!isInterrupted()) {
                        Socket socket = null;
                        try {
                          socket = serverSocket.accept();
                        } catch (SocketTimeoutException e) {
                        }
                        if (socket == null) {
                          System.out.println("Waiting for player to connect");
                          continue;
                        }
                        fail = false;
                        packetLink = new NetworkPacketLink(socket);
                        createServerState = 1;
                        break;
                      }
                    } catch (Exception e) {
                      e.printStackTrace();
                    }
                    if (fail) {
                      try {
                        serverSocket.close();
                      } catch (IOException e) {
                      }
                    }
                  };
                };
            hostThread.start();
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
        break;

      case TitleMenu.JOIN_GAME_ID:
        addMenu(new JoinGameMenu());
        break;

      case TitleMenu.CANCEL_JOIN_ID:
        popMenu();
        if (hostThread != null) {
          hostThread.interrupt();
          hostThread = null;
        }
        break;

      case TitleMenu.PERFORM_JOIN_ID:
        menuStack.clear();
        isMultiplayer = true;
        isServer = false;
        chat.clear();

        String[] data = TitleMenu.ip.trim().split(":");
        String ip = data[0];
        Integer port =
            (data.length > 1)
                ? Integer.parseInt(data[1])
                : Options.getAsInteger(Options.MP_PORT, 3000);

        try {
          localId = 1;
          MojamComponent.localTeam = Team.Team2;
          packetLink = new ClientSidePacketLink(ip, port);
          synchronizer = new TurnSynchronizer(this, packetLink, localId, 2);
          packetLink.setPacketListener(this);
        } catch (Exception e) {
          e.printStackTrace();
          // System.exit(1);
          addMenu(new TitleMenu(GAME_WIDTH, GAME_HEIGHT));
        }
        break;

      case TitleMenu.HOW_TO_PLAY:
        addMenu(new HowToPlayMenu(level != null));
        break;

      case TitleMenu.OPTIONS_ID:
        addMenu(new OptionsMenu(level != null));
        break;

      case TitleMenu.SELECT_DIFFICULTY_ID:
        addMenu(new DifficultySelect(false));
        break;

      case TitleMenu.SELECT_DIFFICULTY_HOSTING_ID:
        addMenu(new DifficultySelect(true));
        break;

      case TitleMenu.KEY_BINDINGS_ID:
        addMenu(new KeyBindingsMenu(keys, inputHandler));
        break;

      case TitleMenu.LEVEL_EDITOR_ID:
        addMenu(new LevelEditorMenu());
        break;

      case TitleMenu.EXIT_GAME_ID:
        System.exit(0);
        break;

      case TitleMenu.RETURN_ID:
        synchronizer.addCommand(new PauseCommand(false));
        keys.tick();
        break;

      case TitleMenu.BACK_ID:
        popMenu();
        break;

      case TitleMenu.CREDITS_ID:
        addMenu(new CreditsScreen(GAME_WIDTH, GAME_HEIGHT));
        break;

      case TitleMenu.CHARACTER_ID:
        addMenu(new CharacterSelectionMenu());
        break;
    }
  }
  private void tick() {

    if (packetLink != null) {
      packetLink.tick();
    }

    mouseButtons.setPosition(getMousePosition());
    if (!menuStack.isEmpty()) {
      menuStack.peek().tick(mouseButtons);
    }
    if (mouseMoved) {
      mouseMoved = false;
      mouseHideTime = 0;
      if (mouseHidden) {
        mouseHidden = false;
      }
    }
    if (mouseHideTime < 60) {
      mouseHideTime++;
      if (mouseHideTime == 60) {
        mouseHidden = true;
      }
    }
    mouseButtons.tick();

    if (level != null) {
      if (synchronizer.preTurn()) {
        synchronizer.postTurn();

        if (!paused) {
          for (int index = 0; index < keys.getAll().size(); index++) {
            Keys.Key key = keys.getAll().get(index);
            boolean nextState = key.nextState;
            if (key.isDown != nextState) {
              synchronizer.addCommand(new ChangeKeyCommand(index, nextState));
            }
          }

          keys.tick();
          for (Keys skeys : synchedKeys) {
            skeys.tick();
          }

          if (keys.pause.wasPressed()) {
            keys.release();
            synchronizer.addCommand(new PauseCommand(true));
          }

          if (keys.fullscreen.wasPressed()) {
            setFullscreen(!fullscreen);
          }

          // if mouse is in use, update player orientation before level tick
          if (!mouseHidden) {

            // update player mouse, in world pixels relative to
            // player
            player.setAimByMouse(
                ((mouseButtons.getX() / SCALE) - (screen.w / 2)),
                (((mouseButtons.getY() / SCALE) + 24) - (screen.h / 2)));
          } else {
            player.setAimByKeyboard();
          }

          level.tick();
        }

        // every 4 minutes, start new background music :)
        if (System.currentTimeMillis() / 1000 > nextMusicInterval) {
          nextMusicInterval = (System.currentTimeMillis() / 1000) + 4 * 60;
          soundPlayer.startBackgroundMusic();
        }

        if (keys.screenShot.isDown) {
          takeScreenShot();
        }
      }
    }

    if (createServerState == 1) {
      createServerState = 2;

      synchronizer = new TurnSynchronizer(MojamComponent.this, packetLink, localId, 2);

      clearMenus();
      createLevel(TitleMenu.level);

      synchronizer.setStarted(true);
      if (TitleMenu.level.vanilla) {
        packetLink.sendPacket(
            new StartGamePacket(
                TurnSynchronizer.synchedSeed,
                TitleMenu.level.getUniversalPath(),
                DifficultyList.getDifficultyID(TitleMenu.difficulty)));
      } else {
        packetLink.sendPacket(
            new StartGamePacketCustom(
                TurnSynchronizer.synchedSeed,
                level,
                DifficultyList.getDifficultyID(TitleMenu.difficulty)));
      }
      packetLink.setPacketListener(MojamComponent.this);
    }
  }
  private void tick() {
    if (level != null) {
      if (level.player1Score >= Level.TARGET_SCORE) {
        addMenu(new WinMenu(GAME_WIDTH, GAME_HEIGHT, 1));
        level = null;
        return;
      }
      if (level.player2Score >= Level.TARGET_SCORE) {
        addMenu(new WinMenu(GAME_WIDTH, GAME_HEIGHT, 2));
        level = null;
        return;
      }
      if (keys.escape.wasPressed()) {
        clearMenus();
        addMenu(new TitleMenu(GAME_WIDTH, GAME_HEIGHT));
        level = null;
        return;
      }
    }
    if (packetLink != null) {
      packetLink.tick();
    }

    mouseButtons.setPosition(getMousePosition());
    if (!menuStack.isEmpty()) {
      menuStack.peek().tick(mouseButtons);
    }
    if (mouseMoved) {
      mouseMoved = false;
      mouseHideTime = 0;
      if (mouseHidden) {
        mouseHidden = false;
      }
    }
    if (mouseHideTime < 60) {
      mouseHideTime++;
      if (mouseHideTime == 60) {
        mouseHidden = true;
      }
    }
    mouseButtons.tick();

    if (level != null) {
      if (synchronizer.preTurn()) {
        synchronizer.postTurn();
        for (int index = 0; index < keys.getAll().size(); index++) {
          Keys.Key key = keys.getAll().get(index);
          boolean nextState = key.nextState;
          if (key.isDown != nextState) {
            synchronizer.addCommand(new ChangeKeyCommand(index, nextState));
          }
        }
        if (keys.save.isDown) {
          level.save();
        }
        keys.tick();
        for (Keys skeys : synchedKeys) {
          skeys.tick();
        }

        // if mouse is in use, update player orientation before level
        // tick
        if (!mouseHidden) {

          // update player mouse, in world pixels relative to player
          player.setAimByMouse(
              ((mouseButtons.getX() / SCALE) - (screen.w / 2)),
              (((mouseButtons.getY() / SCALE) + 24) - (screen.h / 2)));
        } else {
          player.setAimByKeyboard();
        }

        level.tick();
      }
    }

    if (createServerState == 1) {
      createServerState = 2;

      synchronizer = new TurnSynchronizer(MojamComponent.this, packetLink, localId, 2);

      clearMenus();
      createLevel(TitleMenu.level);

      synchronizer.setStarted(true);
      packetLink.sendPacket(new StartGamePacket(TurnSynchronizer.synchedSeed, TitleMenu.level));
      packetLink.setPacketListener(MojamComponent.this);
    }
  }