package com.bergerkiller.bukkit.common.tab;
import org.bukkit.entity.Player;
import com.bergerkiller.bukkit.common.FromToCounter;
import com.bergerkiller.bukkit.common.internal.CommonPlugin;
import com.bergerkiller.bukkit.common.internal.CommonTabController;
import com.bergerkiller.bukkit.common.utils.CommonUtil;
/**
* Represents a live view of a single Player List a player sees when pressing tab.
* Changes in this view can be sent to multiple players or all players on the server.
* Many methods are available to ease the setting of text and ping values in this view.<br><br>
*
* To obtain a TabView instance, call the {@link #createTab(int, int)} method with the desired
* dimensions of the tab specified. It is only possible to create new tabs this way
* when enabling your plugin. To create new Tab View instances at runtime, call {@link #clone()}
* or {@link #cloneResize(int, int)}. Pay close attention to the Java Docs of these methods!
*/
public abstract class TabView {
/**
* The maximum allowed width of a Tab View (amount of columns)
*/
public static final int MAX_WIDTH = 3;
/**
* The maximum allowed height of a Tab View (amount of rows)
*/
public static final int MAX_HEIGHT = 20;
/**
* A ping value constant that shows a single green line.
*/
public static final int PING_1 = 1000;
/**
* A ping value constant that shows two green lines
*/
public static final int PING_2 = 600;
/**
* A ping value constant that shows three green lines
*/
public static final int PING_3 = 300;
/**
* A ping value constant that shows four green lines
*/
public static final int PING_4 = 150;
/**
* A ping value constant that shows five green lines
*/
public static final int PING_5 = 0;
/**
* A ping value constant that shows 'X - no connection'
*/
public static final int PING_NONE = -1;
/**
* A ping value constant that shows all green lines
*/
public static final int PING_FULL = PING_5;
/**
* A ping value constant that represents the default ping value, which is equal to {@link #PING_1}
*/
public static final int PING_DEFAULT = PING_1;
/**
* A text value constant that represents the default 'no text', which shows empty (no) text
*/
public static final String TEXT_DEFAULT = "";
/**
* A TabView constant that denotes the default view, that is, the player names and their ping
*/
public static final TabView DEFAULT = new TabViewDefault();
/**
* A TabView constant that denotes an empty view, where all available slots are cleared
*/
public static final TabView EMPTY = new TabViewEmpty();
/**
* Gets the width of this Tab View
*
* @return Tab View width
*/
public abstract int getWidth();
/**
* Gets the height of this Tab View
*
* @return Tab View height
*/
public abstract int getHeight();
/**
* Gets the total amount of slots of this Tab View.
* This is equal to [width x height]
*
* @return total slot count
*/
public int getSlotCount() {
return getWidth() * getHeight();
}
/**
* Sets the displayed ping of a slot
*
* @param x - coordinate of the slot
* @param y - coordinate of the slot
* @param ping to display
*/
public abstract void setPing(int x, int y, int ping);
/**
* Sets the displayed text of a slot
*
* @param x - coordinate of the slot
* @param y - coordinate of the slot
* @param text to display
*/
public abstract void setText(int x, int y, String text);
/**
* Sets the displayed ping and text of a slot
*
* @param x - coordinate of the slot
* @param y - coordinate of the slot
* @param text to display
* @param ping to display
*/
public abstract void set(int x, int y, String text, int ping);
/**
* Sets all the text and ping values of a single column. Points outside of the text or ping array
* bounds are ignored. If the text or ping arrays are null, the values are ignored.
* The text is set from top to bottom
*
* @param column - x-coordinate of the column
* @param text values, null or empty to ignore
* @param ping values, null or empty to ignore
*/
public void setColumn(int column, String[] text, int[] ping) {
setArea(column, 0, column, getHeight() - 1, text, ping);
}
/**
* Sets all the text and ping values of a single row. Points outside of the text or ping array
* bounds are ignored. If the text or ping arrays are null, the values are ignored.
* The text is set from top to bottom<br><br>
*
* To set the values in reverse, simply use y2 for y1.
*
* @param column - x-coordinate of the column
* @param y1 - y-coordinate of point 1 (start) of the column (inclusive)
* @param y2 - y-coordinate of point 2 (end) of the column (inclusive)
* @param text values, null or empty to ignore
* @param ping values, null or empty to ignore
*/
public void setColumn(int column, int y1, int y2, String[] text, int[] ping) {
setArea(column, y1, column, y2, text, ping);
}
/**
* Sets all the text and ping values of a single row. Points outside of the text or ping array
* bounds are ignored. If the text or ping arrays are null, the values are ignored.
* The text is set from left to right
*
* @param row - y-coordinate of the row
* @param text values, null or empty to ignore
* @param ping values, null or empty to ignore
*/
public void setRow(int row, String[] text, int[] ping) {
setArea(0, row, getWidth() - 1, row, text, ping);
}
/**
* Sets all the text and ping values of a single row. Points outside of the text or ping array
* bounds are ignored. If the text or ping arrays are null, the values are ignored.
* The text is set from left to right<br><br>
*
* To set the values in reverse, simply use x2 for x1.
*
* @param row - y-coordinate of the row
* @param x1 - x-coordinate of point 1 (start) of the row (inclusive)
* @param x2 - x-coordinate of point 2 (end) of the row (inclusive)
* @param text values, null or empty to ignore
* @param ping values, null or empty to ignore
*/
public void setRow(int row, int x1, int x2, String[] text, int[] ping) {
setArea(x1, row, x2, row, text, ping);
}
/**
* Sets all the text and ping values of an area. Points outside of the text or ping array
* bounds are ignored. If the text or ping arrays are null, the values are ignored.
* The text is set from left to right<br><br>
*
* To set the values in reverse, simply use x2 for x1 and y2 for y1.<br><br>
*
* @param x1 - x-coordinate of point 1 (inclusive)
* @param y1 - y-coordinate of point 1 (inclusive)
* @param x2 - x-coordinate of point 2 (inclusive)
* @param y2 - y-coordinate of point 2 (inclusive)
* @param text values, null or empty to ignore
* @param ping values, null or empty to ignore
*/
public void setArea(int x1, int y1, int x2, int y2, String[] text, int[] ping) {
boundsCheck(x1, y1);
boundsCheck(x2, y2);
final boolean hasText = text != null && text.length > 0;
final boolean hasPing = ping != null && ping.length > 0;
if (!hasText && !hasPing) {
return;
}
// Initialize all counters
FromToCounter counterX = new FromToCounter(x1, x2);
FromToCounter counterY = new FromToCounter(y1, y2);
FromToCounter textCounter = new FromToCounter();
FromToCounter pingCounter = new FromToCounter();
if (hasText) {
textCounter.reset(0, text.length - 1);
}
if (hasPing) {
pingCounter.reset(0, ping.length - 1);
}
// Start processing the area
while (counterX.hasNext()) {
counterX.next();
counterY.reset();
while (counterY.hasNext()) {
counterY.next();
if (textCounter.hasNext()) {
if (pingCounter.hasNext()) {
// Text and ping are available
set(counterX.get(), counterY.get(), text[textCounter.next()], ping[pingCounter.next()]);
} else {
// Only text is available
setText(counterX.get(), counterY.get(), text[textCounter.next()]);
}
} else if (pingCounter.hasNext()) {
// Only ping is available
setPing(counterX.get(), counterY.get(), ping[pingCounter.next()]);
}
}
}
}
/**
* Sets all text and ping values in the area to the text and ping values specified.
* Ordering of xy1 and xy2 is not important.
*
* @param x1 - x-coordinate of point 1 (inclusive)
* @param y1 - y-coordinate of point 1 (inclusive)
* @param x2 - x-coordinate of point 2 (inclusive)
* @param y2 - y-coordinate of point 2 (inclusive)
* @param text to use
* @param ping to use
*/
public void fillArea(int x1, int y1, int x2, int y2, String text, int ping) {
boundsCheck(x1, y1);
boundsCheck(x2, y2);
// Initialize all counters
FromToCounter counterX = new FromToCounter(x1, x2);
FromToCounter counterY = new FromToCounter(y1, y2);
// Start processing the area
while (counterX.hasNext()) {
counterX.next();
counterY.reset();
while (counterY.hasNext()) {
counterY.next();
set(counterX.get(), counterY.get(), text, ping);
}
}
}
/**
* Fills an entire row with the text and ping specified.
*
* @param row to fill
* @param x1 - x-coordinate of point 1 of the row (inclusive)
* @param x2 - x-coordinate of point 2 of the row (inclusive)
* @param text to use
* @param ping to use
*/
public void fillRow(int row, int x1, int x2, String text, int ping) {
fillArea(x1, row, x2, row, text, ping);
}
/**
* Fills an entire row with the text and ping specified.
*
* @param row to fill
* @param text to use
* @param ping to use
*/
public void fillRow(int row, String text, int ping) {
fillRow(row, 0, getWidth() - 1, text, ping);
}
/**
* Fills an entire column with the text and ping specified.
*
* @param column to fill
* @param y1 - y-coordinate of point 1 of the row (inclusive)
* @param y2 - y-coordinate of point 2 of the row (inclusive)
* @param text to use
* @param ping to use
*/
public void fillColumn(int column, int y1, int y2, String text, int ping) {
fillArea(column, y1, column, y2, text, ping);
}
/**
* Fills an entire column with the text and ping specified.
*
* @param column to fill
* @param text to use
* @param ping to use
*/
public void fillColumn(int column, String text, int ping) {
fillColumn(column, 0, getHeight() - 1, text, ping);
}
/**
* Sets all slots of this Tab View to the text and ping values
*
* @param text value to set to
* @param ping value to set to
*/
public void fillAll(String text, int ping) {
int x, y;
for (x = 0; x < getWidth(); x++) {
for (y = 0; y < getHeight(); y++) {
set(x, y, text, ping);
}
}
}
/**
* Sets all the contents of this Tab View to the text and ping arrays.
* Elements outside of these arrays or if the arrays are null are not set.
*
* @param text array, null or empty to ignore
* @param ping array, null or empty to ignore
*/
public void setAll(String[] text, int[] ping) {
setArea(0, 0, getWidth() - 1, getHeight() - 1, text, ping);
}
/**
* Sets all the contents of this Tab View to contain the contents of the Tab View.
* If the Tab View is larger than this one, only a portion that can fit in this Tab View
* is transferred over.
*
* @param view to set to
*/
public void setAll(TabView view) {
int width = Math.min(this.getWidth(), view.getWidth());
int height = Math.min(this.getHeight(), view.getHeight());
int x, y;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
this.set(x, y, view.getText(x, y), view.getPing(x, y));
}
}
}
/**
* Sets all the text values of an area. Points outside of the text array bounds are ignored.
* If the text array is null, the values are ignored. The text is set from left to right.<br><br>
*
* To set the values in reverse, simply use x2 for x1 and y2 for y1.<br><br>
*
* This is an overload for
* {@link #setArea(int, int, int, int, String[], int[]) setArea(x1, y1, x2, y2, text, ping)}
* to allow variable arguments instead of fixed arrays.
*
* @param x1 - x-coordinate of point 1 (inclusive)
* @param y1 - y-coordinate of point 2 (inclusive)
* @param x2 - x-coordinate of point 1 (inclusive)
* @param y2 - y-coordinate of point 2 (inclusive)
* @param text to set to, null or empty to ignore (nothing happens then)
*/
public void setAreaText(int x1, int y1, int x2, int y2, String... text) {
setArea(x1, y1, x2, y2, text, null);
}
/**
* Sets all the text and ping values of a single row.
* Points outside of the text array bounds are ignored.
* The text is set from left to right.<br><br>
*
* To set the values in reverse, simply use x2 for x1.<br><br>
*
* This is an overload for
* {@link #setRow(int, int, int, String[], int[]) setRow(row, x1, x2, text, ping)}
* to allow variable arguments instead of fixed arrays.
*
* @param row - y-coordinate of the row
* @param x1 - x-coordinate of point 1 (start) of the row (inclusive)
* @param x2 - x-coordinate of point 2 (end) of the row (inclusive)
* @param text
*/
public void setRowText(int row, int x1, int x2, String... text) {
this.setRow(row, x1, x2, text, null);
}
/**
* Sets all the text and ping values of a single row.
* Points outside of the text array bounds are ignored.
* The text is set from left to right.<br><br>
*
* This is an overload for
* {@link #setRow(int, String[], int[]) setRow(row, text, ping)}
* to allow variable arguments instead of fixed arrays.
*
* @param row to set
* @param text to set to
*/
public void setRowText(int row, String... text) {
this.setRow(row, text, null);
}
/**
* Sets all the text and ping values of a single row.
* Points outside of the text array bounds are ignored.
* The text is set from top to bottom.<br><br>
*
* To set the values in reverse, simply use y2 for y1.<br><br>
*
* This is an overload for
* {@link #setColumn(int, int, int, String[], int[]) setColumn(column, y1, y2, text, ping)}
* to allow variable arguments instead of fixed arrays.
*
* @param column - x-coordinate of the column
* @param y1 - y-coordinate of point 1 (start) of the column (inclusive)
* @param y2 - y-coordinate of point 2 (end) of the column (inclusive)
* @param text
*/
public void setColumnText(int column, int y1, int y2, String... text) {
this.setColumn(column, y1, y2, text, null);
}
/**
* Sets all the text values of a single column. Points outside of the text or ping array
* bounds are ignored. The text is set from top to bottom.<br><br>
*
* This is an overload for
* {@link #setColumn(int, String[], int[]) setColumn(column, text, ping)}
* to allow variable arguments instead of fixed arrays.
*
* @param column - x-coordinate of the column
* @param text
*/
public void setColumnText(int column, String... text) {
setColumn(column, text, null);
}
/**
* Sets all the text contents of this Tab View to the text array.
* Elements outside of this array or if the array is null are not set.<br><br>
*
* This is an overload of {@link #setAll(String[], int[]) setAll(text, ping)} to allow variable arguments instead of fixed arrays.
*
* @param text to set to, null or empty to ignore (nothing happens then)
*/
public void setAllText(String... text) {
setAll(text, null);
}
/**
* Gets the text shown in the slot at the x and y coordinates specified
*
* @param x - coordinate of the slot
* @param y - coordinate of the slot
* @return text set for that slot
*/
public abstract String getText(int x, int y);
/**
* Gets the ping shown in the slot at the x and y coordinates specified
*
* @param x - coordinate of the slot
* @param y - coordinate of the slot
* @return ping set for that slot
*/
public abstract int getPing(int x, int y);
/**
* Gets whether the current Tab View is displayed to a player
*
* @param player to check
* @return True if this Tab View is displayed to the Player, False if not
*/
public boolean isDisplayedTo(Player player) {
return getController().getCurrentTab(player) == this;
}
/**
* Displays this Tab View to the player specified.
* The player is added to the viewers, previous viewers are not removed.
* If you wish to hide this Tab View again, use displayTo on the {@link #DEFAULT} or {@link #EMPTY} constants.
*
* @param player
*/
public void displayTo(Player player) {
getController().showTab(player, this);
}
/**
* Displays this Tab View to all online players and
* all players that will join in the future.
*/
public void displayToAll() {
getController().showTabToAll(this);
}
@Override
public TabView clone() {
final int width = getWidth();
final int height = getHeight();
TabView clonedView = new TabViewBasic(width, height);
// Transfer information
int x, y;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
clonedView.set(x, y, this.getText(x, y), this.getPing(x, y));
}
}
// Done
return clonedView;
}
/**
* Creates a new clone of this TabView that is of a smaller size than this one.
*
* @param newWidth for the tab, smaller than the width of this tab
* @param newHeight for the tab, smaller than the height of this tab
* @return cloned tab of the new size
*/
public TabView cloneResize(int newWidth, int newHeight) {
if (newWidth > getWidth() || newHeight > getWidth()) {
throw new IllegalArgumentException("Size {" + newWidth + ", " + newHeight +
"} is not a sub-size of {" + getWidth() + ", " + getHeight() + "}");
}
TabView clonedView = new TabViewBasic(newWidth, newHeight);
// Transfer information
int x, y;
for (x = 0; x < newWidth; x++) {
for (y = 0; y < newHeight; y++) {
clonedView.set(x, y, this.getText(x, y), this.getPing(x, y));
}
}
// Done
return clonedView;
}
/**
* Creates a new Tab View that is a sub-area of this Tab View.
* Any changes made to this returned Tab View operate on this original Tab View.
*
* @param x - coordinate of the top-left start position in this Tab View
* @param y - coordinate of the top-left start position in this Tab View
* @param width of the sub-region in this Tab View
* @param height of the sub-region in this Tab View
* @return a Tab View instance that is a sub-view of this one
* @throws IndexOutOfBoundsException if the area specified is not a sub-area of this one
*/
public TabView subView(int x, int y, int width, int height) {
boundsCheck(x, y);
if (x + width > this.getWidth()) {
throw new IndexOutOfBoundsException("Sub-area is too wide (x{" + x + "} + width{" + width + "} > " + getWidth() + ")");
}
if (y + height > this.getHeight()) {
throw new IndexOutOfBoundsException("Sub-area is too high (x{" + x + "} + width{" + width + "} > " + getWidth() + ")");
}
return new TabViewSubView(this, x, y, width, height);
}
/**
* Checks whether a coordinate is part of this Tab View.
*
* @param x - coordinate
* @param y - coordinate
*/
protected void boundsCheck(int x, int y) {
if (x < 0 || x > this.getWidth() || y < 0 || y > this.getHeight()) {
throw new IndexOutOfBoundsException("The coordinate [" + x + ", " + y + "] is out of bounds!");
}
}
/**
* Gets the index (for a 1-dimensional array) from an x and y coordinate.
* Also performs a bounds check.
*
* @param x - coordinate
* @param y - coordinate
* @return 1D-index
*/
protected int getIndex(int x, int y) {
boundsCheck(x, y);
return x + this.getWidth() * y;
}
/**
* Creates a new TabView. This method can only be called
* while enabling your plugin. Do not call this method while
* players are joining or logging in - it does NOT work!<br><br>
*
* If you need tab instances created at runtime, use clones from one
* tab created when enabling your plugin. You can use smaller sizes than the
* original tab, but you can not use larger sizes. You are restricted
* to defining the maximum tab size you expect to need when enabling.
*
* @param width for the new tab
* @param height for the new tab
* @return new Tab
*/
public static TabView createTab(int width, int height) {
if (CommonUtil.isServerStarted()) {
throw new IllegalStateException("Can not create new tabs while the server is running, create your tabs while enabling and optionally clone those");
}
if (width < 1 || height < 1 || width > MAX_WIDTH || height > MAX_HEIGHT) {
throw new IllegalArgumentException("Dimension {" + width + ", " + height + "} is outside of the allowed bounds!");
}
getController().requestNewSize(width, height);
return new TabViewBasic(width, height);
}
/**
* Obtains the Tab Controller used to communicate changes to the server
*
* @return tab controller
*/
protected static CommonTabController getController() {
return CommonPlugin.getInstance().getTabController();
}
}