Package com.bergerkiller.bukkit.common.internal

Source Code of com.bergerkiller.bukkit.common.internal.CommonTabController$PlayerTabInfo

package com.bergerkiller.bukkit.common.internal;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;

import com.bergerkiller.bukkit.common.collections.EntityMap;
import com.bergerkiller.bukkit.common.collections.FilteredCollectionSelf;
import com.bergerkiller.bukkit.common.collections.UniqueList;
import com.bergerkiller.bukkit.common.conversion.Conversion;
import com.bergerkiller.bukkit.common.events.PacketReceiveEvent;
import com.bergerkiller.bukkit.common.events.PacketSendEvent;
import com.bergerkiller.bukkit.common.protocol.CommonPacket;
import com.bergerkiller.bukkit.common.protocol.PacketListener;
import com.bergerkiller.bukkit.common.protocol.PacketType;
import com.bergerkiller.bukkit.common.reflection.FieldAccessor;
import com.bergerkiller.bukkit.common.reflection.SafeField;
import com.bergerkiller.bukkit.common.tab.TabView;
import com.bergerkiller.bukkit.common.utils.CommonUtil;
import com.bergerkiller.bukkit.common.utils.LogicUtil;
import com.bergerkiller.bukkit.common.utils.MathUtil;
import com.bergerkiller.bukkit.common.utils.PacketUtil;
import com.bergerkiller.bukkit.common.utils.StringUtil;

public class CommonTabController implements PacketListener, Listener {
  public static final int MAX_TEXT_LENGTH = 16
  private static final char[] RANDOM_STYLE_CHARS;
  private final FieldAccessor<Integer> maxPlayersField;
  private int serverMaxPlayers;
  private int serverListWidth, serverListHeight, serverListCount;
  private int customListWidth, customListHeight, customListCount;
  private final EntityMap<Player, PlayerTabInfo> players = new EntityMap<Player, PlayerTabInfo>();
  private TabView defaultTab;

  static {
    LinkedHashSet<Character> chars = new LinkedHashSet<Character>();
    // Add all chars available that do not conflict with rendering
    // ===========================================================================
    LogicUtil.addArray(chars, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
    LogicUtil.addArray(chars, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
    LogicUtil.addArray(chars, 'p', 'q', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
    LogicUtil.addArray(chars, '+', '-', '=', '|', '(', ')', '{', '}', '[', ']');
    // ===========================================================================

    // Also add all upper-case versions
    for (Character c : chars.toArray(new Character[0])) {
      chars.add(Character.toUpperCase(c));
    }
    // To char array and check length
    RANDOM_STYLE_CHARS = Conversion.toCharArr.convert(chars);
    if (RANDOM_STYLE_CHARS.length < 60) {
      CommonPlugin.LOGGER.log(Level.WARNING, "Not enough unique characters to use: " + RANDOM_STYLE_CHARS.length);
    }
  }

  protected CommonTabController() {
    // Read server max players
    maxPlayersField = new SafeField<Integer>(CommonNMS.getPlayerList(), "maxPlayers");
    serverMaxPlayers = 0;
    // Default (startup) dimensions
    customListCount = serverListCount = 0;
    customListWidth = serverListWidth = 1;
    customListHeight = serverListHeight = 1;
    // Set the default tab initial
    defaultTab = TabView.DEFAULT;
  }

  private boolean hasChangedMaxPlayers() {
    return customListCount > serverListCount;
  }

  /**
   * Gets the width of the (total) Tab view that is being used right now
   *
   * @return tab width
   */
  public int getWidth() {
    return customListWidth;
  }

  /**
   * Gets the height of the (total) Tab view that is being used right now
   *
   * @return tab height
   */
  public int getHeight() {
    return customListHeight;
  }

  /**
   * Gets the width of the default player list.
   * This is the width that would be used if no tabs of greater size are created.
   *
   * @return default width
   */
  public int getDefaultWidth() {
    return serverListWidth;
  }

  /**
   * Gets the height of the default player list.
   * This is the height that would be used if no tabs of greater size are created.
   *
   * @return default height
   */
  public int getDefaultHeight() {
    return serverListHeight;
  }

  /**
   * Sets the default player list dimensions based on the player maximum.
   * Bukkit.getMaxPlayers() fails when BKCommonLib enabled, and thus this
   * needs to be called the next tick after enabling.
   */
  public void setDefaultSize() {
    // Calculate the default tab view width and height
    serverMaxPlayers = Bukkit.getMaxPlayers();
    int slotCount = Math.min(serverMaxPlayers, 3 * TabView.MAX_HEIGHT);
    // Calculate the current dimensions of the Player List
    serverListWidth = MathUtil.ceil((double) slotCount / (double) TabView.MAX_HEIGHT);
    serverListHeight = MathUtil.floor((double) slotCount / (double) serverListWidth);
    serverListHeight = Math.max(serverListHeight, getMinHeight(serverListWidth));
    requestNewSize(serverListWidth, serverListHeight);
  }

  /**
   * Prepares the internal logic for the addition of a new tab dimension.
   *
   * @param width of the new tab
   * @param height of the new tab
   */
  public void requestNewSize(int width, int height) {
    if (width > customListWidth || height > customListHeight) {
      customListWidth = Math.max(customListWidth, width);
      customListHeight = Math.max(customListHeight, height);
      customListHeight = Math.max(customListHeight, getMinHeight(customListWidth));
      // Calculate the player list player count needed
      customListCount = customListWidth * customListHeight;
    }
  }

  /**
   * Forces all tabs to resend 'dirty' slot information to all their viewers.
   */
  public void refreshAllTabs() {
    for (PlayerTabInfo info : players.values()) {
      info.refresh();
    }
  }

  /**
   * Maps a tab to a player, telling it to start rendering this new Tab.
   *
   * @param player to map to
   * @param tab to map to the player
   */
  public void showTab(Player player, TabView tab) {
    getInfo(player).setCurrentTab(tab);
  }

  /**
   * Maps a tab to all current and future players, showing it's contents to everyone
   *
   * @param tab to show to all players
   */
  public void showTabToAll(TabView tab) {
    defaultTab = tab;
    for (PlayerTabInfo info : players.values()) {
      info.setCurrentTab(tab);
    }
  }

  /**
   * Gets the Tab View that a player is currently viewing
   *
   * @param player to get the Tab View for
   * @return current player Tab View
   */
  public TabView getCurrentTab(Player player) {
    return getInfo(player).getCurrentTab();
  }

  /**
   * Reloads all text and ping values for a tab.
   * This can be used when a large amount of changes occur.
   *
   * @param tab to reload
   */
  public void reloadAll(TabView tab) {
    for (PlayerTabInfo info : getViewers(tab)) {
      info.reloadAll();
    }
  }

  /**
   * Updates the ping value of a slot in a tab
   *
   * @param tab to update
   * @param x - coordinate of the slot
   * @param y - coordinate of the slot
   * @param ping value to set to
   */
  public void setPing(TabView tab, int x, int y, int ping) {
    for (PlayerTabInfo info : getViewers(tab)) {
      info.setPing(x, y, ping);
    }
  }

  /**
   * Updates the text value of a slot in a tab
   *
   * @param tab to update
   * @param x - coordinate of the slot
   * @param y - coordinate of the slot
   * @param text value to set to
   */
  public void setText(TabView tab, int x, int y, String text) {
    for (PlayerTabInfo info : getViewers(tab)) {
      info.setText(x, y, text);
    }
  }

  /**
   * Updates the ping and text values of a slot in a tab
   *
   * @param tab to update
   * @param x - coordinate of the slot
   * @param y - coordinate of the slot
   * @param text value to set to
   * @param ping value to set to
   */
  public void setSlot(TabView tab, int x, int y, String text, int ping) {
    for (PlayerTabInfo info : getViewers(tab)) {
      info.set(x, y, text, ping);
    }
  }

  @EventHandler(priority = EventPriority.LOWEST)
  public void onPlayerLoginFirst(PlayerLoginEvent event) {
    if (event.getResult() == Result.ALLOWED) {
      // Register a PlayerTabInfo instance
      getInfo(event.getPlayer());   
      // Ensure the listeners are registered in the right order
      CommonUtil.queueListenerLast(this, PlayerLoginEvent.class);
      CommonUtil.queueListenerFirst(this, PlayerJoinEvent.class);
    }
  }

  @EventHandler(priority = EventPriority.MONITOR)
  public void onPlayerLogin(PlayerLoginEvent event) {
    if (event.getResult() == Result.ALLOWED) {
      // Temporarily set the max player count to the one as specified here
      if (hasChangedMaxPlayers()) {
        maxPlayersField.set(CommonNMS.getPlayerList(), customListCount);
      }
    }
  }

  @EventHandler(priority = EventPriority.LOWEST)
  public void onPlayerJoin(PlayerJoinEvent event) {
    if (hasChangedMaxPlayers()) {
      // Restore server max players (required, otherwise new people can join a full server all of a sudden!)
      maxPlayersField.set(CommonNMS.getPlayerList(), serverMaxPlayers);
    }
    // Send all the elements of the current tab for this player
    getInfo(event.getPlayer()).refresh();
  }

  @Override
  public void onPacketReceive(PacketReceiveEvent event) {
  }

  @Override
  public void onPacketSend(PacketSendEvent event) {
    if (event.getType() == PacketType.OUT_PLAYER_INFO && !event.isCancelled()) {
      CommonPacket packet = event.getPacket();
      String name = packet.read(PacketType.OUT_PLAYER_INFO.playerName);
      int ping = packet.read(PacketType.OUT_PLAYER_INFO.ping);
      boolean register = packet.read(PacketType.OUT_PLAYER_INFO.online);
      event.setCancelled(!getInfo(event.getPlayer()).handlePlayerInfoPacket(name, ping, register));
    }
  }

  private Collection<PlayerTabInfo> getViewers(final TabView currentTab) {
    return new FilteredCollectionSelf<PlayerTabInfo>(players.values()) {
      @Override
      public boolean isFiltered(PlayerTabInfo element) {
        return element.getCurrentTab() != currentTab;
      }
    };
  }

  private PlayerTabInfo getInfo(Player player) {
    PlayerTabInfo info = players.get(player);
    if (info == null) {
      info = new PlayerTabInfo(player, customListWidth, customListHeight, defaultTab);
      players.put(player, info);
    }
    return info;
  }

  private static int getMinHeight(int width) {
    if (width == 1) {
      return 1;
    } else if (width == 2) {
      return 11;
    } else if (width == 3) {
      return 14;
    } else {
      return 1;
    }
  }

  /**
   * Stores the information as known to a single Player
   */
  private static class PlayerTabInfo {
    private final Player player;
    private final Map<String, Integer> defaultView = new LinkedHashMap<String, Integer>();
    private final UniqueList<String> names = new UniqueList<String>();
    private final String[] text;
    private final int[] ping;
    private final int width, height, count;
    private TabView currentTab;
    private int dirtyStartIndex;

    public PlayerTabInfo(Player player, int width, int height, TabView currentTab) {
      this.player = player;
      this.width = width;
      this.height = height;
      this.count = width * height;
      this.text = new String[this.count];
      this.ping = new int[this.count];
      this.setCurrentTab(currentTab);
      // No need to refresh if it is the default tab initially
      if (currentTab == TabView.DEFAULT) {
        this.dirtyStartIndex = this.count;
      }
    }

    public TabView getCurrentTab() {
      return this.currentTab;
    }

    public void setCurrentTab(TabView currentTab) {
      // No need to do anything if it's no valid tab change
      if (this.currentTab == currentTab) {
        return;
      }
      // If the current tab was DEFAULT, update the names
      if (this.currentTab == TabView.DEFAULT) {
        this.names.clear();
        this.names.addAll(this.defaultView.keySet());
      }
      this.currentTab = currentTab;
      // Only need to clear (and not send or add names) if EMPTY
      if (currentTab == TabView.EMPTY) {
        Arrays.fill(this.text, TabView.TEXT_DEFAULT);
        Arrays.fill(this.ping, TabView.PING_DEFAULT);
        return;
      }
      // Write all information from the tab to this class
      reloadAll();
    }

    /**
     * Forces all values to be re-set and re-sent to the player
     */
    public void reloadAll() {
      int tabWidth = currentTab.getWidth();
      int tabHeight = currentTab.getHeight();
      int x, y, index;
      String tabText;
      int tabPing;
      for (x = 0; x < this.width; x++) {
        for (y = 0; y < this.height; y++) {
          index = getIndex(x, y);
          // Read from the tab if in bounds
          if (x < tabWidth && y < tabHeight) {
            tabText = currentTab.getText(x, y);
            tabPing = currentTab.getPing(x, y);
          } else {
            tabText = TabView.TEXT_DEFAULT;
            tabPing = TabView.PING_DEFAULT;
          }
          this.text[index] = tabText;
          this.ping[index] = tabPing;
        }
      }
      // Refresh from the first element
      this.dirtyStartIndex = 0;
    }

    public void setText(int x, int y, String text) {
      int index = getIndex(x, y);
      set(index, text, ping[index]);
    }

    public void set(int x, int y, String text, int ping) {
      set(getIndex(x, y), text, ping);
    }

    private void set(int index, String text, int ping) {
      if (this.text[index].equals(text)) {
        // Only set ping
        setPing(index, ping);
        return;
      }
      this.ping[index] = ping;
      this.text[index] = text;
      // Resend all text past the index
      if (index < this.dirtyStartIndex) {
        this.dirtyStartIndex = index;
      }
    }

    public void setPing(int x, int y, int ping) {
      setPing(getIndex(x, y), ping);
    }

    private void setPing(int index, int ping) {
      if (this.ping[index] == ping) {
        return;
      }
      this.ping[index] = ping;
      // Can't update ping if out of bounds (and thus not shown!)
      if (index >= names.size()) {
        return;
      }
      // We can (safely) instantly send here
      // If text changes too, oh well, too bad!
      this.showSlot(names.get(index), ping);
    }

    public boolean handlePlayerInfoPacket(String name, int ping, boolean register) {
      // Update the default view
      if (register) {
        defaultView.put(name, ping);
      } else {
        defaultView.remove(name);
      }

      // If the default tab is shown, allow it to go through
      return this.currentTab == TabView.DEFAULT;
    }

    public void refresh() {
      if (this.dirtyStartIndex < this.count) {
        this.refresh(this.dirtyStartIndex);
        this.dirtyStartIndex = this.count;
      }
    }

    private void refresh(int startIndex) {
      // Hide the old contents
      if (startIndex == 0) {
        // Send removal messages for all names and clear the names
        for (String name : names) {
          hideSlot(name);
        }
        names.clear();
      } else {
        // Send removal messages and remove names past the index
        while ((names.size() - 1) >= startIndex) {
          hideSlot(names.remove(startIndex));
        }
      }
      // Show the new contents
      if (currentTab == TabView.DEFAULT) {
        Iterator<Entry<String, Integer>> iter = LogicUtil.skipIterator(this.defaultView.entrySet().iterator(), startIndex);
        while (iter.hasNext()) {
          Entry<String, Integer> entry = iter.next();
          showSlot(entry.getKey(), entry.getValue());
        }
      } else if (currentTab != TabView.EMPTY) {
        // Find out the end-index to stop showing information at
        // No-slot is better than an empty slot, it reduces network usage
        int endIndex = this.count - 1;
        while (endIndex >= 0 && text[endIndex].equals(TabView.TEXT_DEFAULT)) {
          endIndex--;
        }
        // Show the slots from start index to end index
        for (int i = startIndex; i <= endIndex; i++) {
          showSlot(getName(text[i]), ping[i]);
        }
      }
    }

    private String getName(String text) {
      if (text.length() > MAX_TEXT_LENGTH) {
        text = text.substring(0, MAX_TEXT_LENGTH);
      }
      if (names.add(text)) {
        return text;
      }

      // Get rid of 2 chars before the maximum length, since we need to append style chars...
      int textLength = Math.min(text.length(), MAX_TEXT_LENGTH - 2);
      StringBuilder uniqueNameBuilder = new StringBuilder(textLength + 2);

      // Initial name + chat style char
      // Increment text length since we don't want to trim off the chat style char
      uniqueNameBuilder.append(text);
      uniqueNameBuilder.setLength(textLength);
      uniqueNameBuilder.append(StringUtil.CHAT_STYLE_CHAR);
      textLength++;

      // Let's start building!
      String uniqueName;
      for (char styleChar : RANDOM_STYLE_CHARS) {
        uniqueNameBuilder.setLength(textLength);
        uniqueNameBuilder.append(styleChar);
        uniqueName = uniqueNameBuilder.toString();
        if (names.add(uniqueName)) {
          return uniqueName;
        }
      }
      throw new RuntimeException("Ran out of names to generate... :(");
    }

    private int getIndex(int x, int y) {
      return x + this.width * y;
    }
 
    private void hideSlot(String text) {
      PacketUtil.sendPacket(player, PacketType.OUT_PLAYER_INFO.newInstance(text, false, 0), false);
    }

    private void showSlot(String text, int ping) {
      PacketUtil.sendPacket(player, PacketType.OUT_PLAYER_INFO.newInstance(text, true, ping), false);
    }
  }
}
TOP

Related Classes of com.bergerkiller.bukkit.common.internal.CommonTabController$PlayerTabInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.