Added osu!Mirror download server.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han
2015-05-07 23:58:04 -04:00
parent 50d55d8d99
commit cab207e275
6 changed files with 268 additions and 69 deletions

View File

@@ -51,6 +51,8 @@ import java.util.Scanner;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.json.JSONException;
import org.json.JSONObject;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
@@ -554,6 +556,25 @@ public class Utils {
} }
} }
/**
* Returns a JSON object from a URL.
* @param url the remote URL
* @return the JSON object
* @author Roland Illig (http://stackoverflow.com/a/4308662)
*/
public static JSONObject readJsonFromUrl(URL url) throws IOException {
String s = Utils.readDataFromUrl(url);
JSONObject json = null;
if (s != null) {
try {
json = new JSONObject(s);
} catch (JSONException e) {
ErrorHandler.error("Failed to create JSON object.", e, true);
}
}
return json;
}
/** /**
* Converts an input stream to a string. * Converts an input stream to a string.
* @param is the input stream * @param is the input stream

View File

@@ -26,6 +26,7 @@ import itdelatrisu.opsu.UI;
import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.Download.DownloadListener; import itdelatrisu.opsu.downloads.Download.DownloadListener;
import itdelatrisu.opsu.downloads.Download.Status; import itdelatrisu.opsu.downloads.Download.Status;
import itdelatrisu.opsu.downloads.servers.DownloadServer;
import java.io.File; import java.io.File;
@@ -239,22 +240,26 @@ public class DownloadNode {
* @see #getDownload() * @see #getDownload()
*/ */
public void createDownload(DownloadServer server) { public void createDownload(DownloadServer server) {
if (download == null) { if (download != null)
String path = String.format("%s%c%d", Options.getOSZDir(), File.separatorChar, beatmapSetID); return;
String rename = String.format("%d %s - %s.osz", beatmapSetID, artist, title);
this.download = new Download(server.getURL(beatmapSetID), path, rename);
download.setListener(new DownloadListener() {
@Override
public void completed() {
UI.sendBarNotification(String.format("Download complete: %s", getTitle()));
}
@Override String url = server.getDownloadURL(beatmapSetID);
public void error() { if (url == null)
UI.sendBarNotification("Download failed due to a connection error."); return;
} String path = String.format("%s%c%d", Options.getOSZDir(), File.separatorChar, beatmapSetID);
}); String rename = String.format("%d %s - %s.osz", beatmapSetID, artist, title);
} this.download = new Download(url, path, rename);
download.setListener(new DownloadListener() {
@Override
public void completed() {
UI.sendBarNotification(String.format("Download complete: %s", getTitle()));
}
@Override
public void error() {
UI.sendBarNotification("Download failed due to a connection error.");
}
});
} }
/** /**

View File

@@ -16,10 +16,11 @@
* along with opsu!. If not, see <http://www.gnu.org/licenses/>. * along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/ */
package itdelatrisu.opsu.downloads; package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler; import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -28,13 +29,15 @@ import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
/** /**
* Download server: http://bloodcat.com/osu/ * Download server: http://bloodcat.com/osu/
*/ */
public class BloodcatServer extends DownloadServer { public class BloodcatServer extends DownloadServer {
/** Server name. */
private static final String SERVER_NAME = "Bloodcat";
/** Formatted download URL: {@code beatmapSetID} */ /** Formatted download URL: {@code beatmapSetID} */
private static final String DOWNLOAD_URL = "http://bloodcat.com/osu/s/%d"; private static final String DOWNLOAD_URL = "http://bloodcat.com/osu/s/%d";
@@ -48,7 +51,10 @@ public class BloodcatServer extends DownloadServer {
public BloodcatServer() {} public BloodcatServer() {}
@Override @Override
public String getURL(int beatmapSetID) { public String getName() { return SERVER_NAME; }
@Override
public String getDownloadURL(int beatmapSetID) {
return String.format(DOWNLOAD_URL, beatmapSetID); return String.format(DOWNLOAD_URL, beatmapSetID);
} }
@@ -58,7 +64,7 @@ public class BloodcatServer extends DownloadServer {
try { try {
// read JSON // read JSON
String search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"), rankedOnly ? "0" : "", page); String search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"), rankedOnly ? "0" : "", page);
JSONObject json = readJsonFromUrl(new URL(search)); JSONObject json = Utils.readJsonFromUrl(new URL(search));
if (json == null) { if (json == null) {
this.totalResults = -1; this.totalResults = -1;
return null; return null;
@@ -86,24 +92,8 @@ public class BloodcatServer extends DownloadServer {
} }
@Override @Override
public int totalResults() { return totalResults; } public int minQueryLength() { return 0; }
/** @Override
* Returns a JSON object from a URL. public int totalResults() { return totalResults; }
* @param url the remote URL
* @return the JSON object
* @author Roland Illig (http://stackoverflow.com/a/4308662)
*/
private static JSONObject readJsonFromUrl(URL url) throws IOException {
String s = Utils.readDataFromUrl(url);
JSONObject json = null;
if (s != null) {
try {
json = new JSONObject(s);
} catch (JSONException e) {
ErrorHandler.error("Failed to create JSON object.", e, true);
}
}
return json;
}
} }

View File

@@ -16,7 +16,9 @@
* along with opsu!. If not, see <http://www.gnu.org/licenses/>. * along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/ */
package itdelatrisu.opsu.downloads; package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.downloads.DownloadNode;
import java.io.IOException; import java.io.IOException;
@@ -27,12 +29,18 @@ public abstract class DownloadServer {
/** Track preview URL. */ /** Track preview URL. */
private static final String PREVIEW_URL = "http://b.ppy.sh/preview/%d.mp3"; private static final String PREVIEW_URL = "http://b.ppy.sh/preview/%d.mp3";
/**
* Returns the name of the download server.
* @return the server name
*/
public abstract String getName();
/** /**
* Returns a web address to download the given beatmap. * Returns a web address to download the given beatmap.
* @param beatmapSetID the beatmap set ID * @param beatmapSetID the beatmap set ID
* @return the URL string * @return the URL string, or null if the address could not be determined
*/ */
public abstract String getURL(int beatmapSetID); public abstract String getDownloadURL(int beatmapSetID);
/** /**
* Returns a list of results for a given search query, or null if the * Returns a list of results for a given search query, or null if the
@@ -45,6 +53,12 @@ public abstract class DownloadServer {
*/ */
public abstract DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException; public abstract DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException;
/**
* Returns the minimum allowable length of a search query.
* @return the minimum length, or 0 if none
*/
public abstract int minQueryLength();
/** /**
* Returns the total number of results for the last search query. * Returns the total number of results for the last search query.
* This will differ from the the size of the array returned by * This will differ from the the size of the array returned by

View File

@@ -0,0 +1,129 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Download server: http://loli.al/
*/
public class OsuMirrorServer extends DownloadServer {
/** Server name. */
private static final String SERVER_NAME = "osu!Mirror";
/** Formatted download URL: {@code beatmapSetID} */
private static final String DOWNLOAD_URL = "http://loli.al/d/%d/";
/** Formatted search URL: {@code page,query} */
private static final String SEARCH_URL = "http://loli.al/mirror/search/%d.json?keyword=%s";
/** Formatted home URL: {@code page} */
private static final String HOME_URL = "http://loli.al/mirror/home/%d.json";
/** Minimum allowable length of a search query. */
private static final int MIN_QUERY_LENGTH = 3;
/** Total result count from the last query. */
private int totalResults = -1;
/** Max server download ID seen (for approximating total pages). */
private int maxServerID = 0;
/** Lookup table from beatmap set ID -> server download ID. */
private HashMap<Integer, Integer> idTable = new HashMap<Integer, Integer>();
/** Constructor. */
public OsuMirrorServer() {}
@Override
public String getName() { return SERVER_NAME; }
@Override
public String getDownloadURL(int beatmapSetID) {
return (idTable.containsKey(beatmapSetID)) ? String.format(DOWNLOAD_URL, idTable.get(beatmapSetID)) : null;
}
@Override
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
// NOTE: ignores 'rankedOnly' flag.
DownloadNode[] nodes = null;
try {
// read JSON
String search;
boolean isSearch;
if (query.isEmpty()) {
isSearch = false;
search = String.format(HOME_URL, page);
} else {
isSearch = true;
search = String.format(SEARCH_URL, page, URLEncoder.encode(query, "UTF-8"));
}
JSONObject json = Utils.readJsonFromUrl(new URL(search));
if (json == null || json.getInt("code") != 0) {
this.totalResults = -1;
return null;
}
// parse result list
JSONArray arr = json.getJSONArray("maplist");
nodes = new DownloadNode[arr.length()];
for (int i = 0; i < nodes.length; i++) {
JSONObject item = arr.getJSONObject(i);
int beatmapSetID = item.getInt("OSUSetid");
int serverID = item.getInt("id");
nodes[i] = new DownloadNode(
beatmapSetID, item.getString("ModifyDate"),
item.getString("Title"), null,
item.getString("Artist"), null,
item.getString("Mapper")
);
idTable.put(beatmapSetID, serverID);
if (serverID > maxServerID)
maxServerID = serverID;
}
// store total result count
if (isSearch)
this.totalResults = json.getInt("totalRows");
else
this.totalResults = maxServerID;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
}
return nodes;
}
@Override
public int minQueryLength() { return MIN_QUERY_LENGTH; }
@Override
public int totalResults() { return totalResults; }
}

View File

@@ -31,11 +31,12 @@ import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.downloads.BloodcatServer;
import itdelatrisu.opsu.downloads.Download; import itdelatrisu.opsu.downloads.Download;
import itdelatrisu.opsu.downloads.DownloadList; import itdelatrisu.opsu.downloads.DownloadList;
import itdelatrisu.opsu.downloads.DownloadNode; import itdelatrisu.opsu.downloads.DownloadNode;
import itdelatrisu.opsu.downloads.DownloadServer; import itdelatrisu.opsu.downloads.servers.BloodcatServer;
import itdelatrisu.opsu.downloads.servers.DownloadServer;
import itdelatrisu.opsu.downloads.servers.OsuMirrorServer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -71,8 +72,11 @@ public class DownloadsMenu extends BasicGameState {
/** Minimum time, in milliseconds, that must elapse between queries. */ /** Minimum time, in milliseconds, that must elapse between queries. */
private static final int MIN_REQUEST_INTERVAL = 300; private static final int MIN_REQUEST_INTERVAL = 300;
/** The beatmap download server. */ /** Available beatmap download servers. */
private DownloadServer server = new BloodcatServer(); private static final DownloadServer[] SERVERS = { new BloodcatServer(), new OsuMirrorServer() };
/** The beatmap download server index. */
private int serverIndex = 0;
/** The current list of search results. */ /** The current list of search results. */
private DownloadNode[] resultList; private DownloadNode[] resultList;
@@ -138,7 +142,7 @@ public class DownloadsMenu extends BasicGameState {
private MenuButton prevPage, nextPage; private MenuButton prevPage, nextPage;
/** Buttons. */ /** Buttons. */
private MenuButton clearButton, importButton, resetButton, rankedButton; private MenuButton clearButton, importButton, resetButton, rankedButton, serverButton;
/** Beatmap importing thread. */ /** Beatmap importing thread. */
private Thread importThread; private Thread importThread;
@@ -170,11 +174,11 @@ public class DownloadsMenu extends BasicGameState {
int height = container.getHeight(); int height = container.getHeight();
float baseX = width * 0.024f; float baseX = width * 0.024f;
float searchY = (height * 0.05f) + Utils.FONT_LARGE.getLineHeight(); float searchY = (height * 0.05f) + Utils.FONT_LARGE.getLineHeight();
float searchWidth = width * 0.35f; float searchWidth = width * 0.3f;
// search // search
searchTimer = SEARCH_DELAY; searchTimer = SEARCH_DELAY;
searchResultString = "Type to search!"; searchResultString = "Loading data from server...";
search = new TextField( search = new TextField(
container, Utils.FONT_DEFAULT, (int) baseX, (int) searchY, container, Utils.FONT_DEFAULT, (int) baseX, (int) searchY,
(int) searchWidth, Utils.FONT_MEDIUM.getLineHeight() (int) searchWidth, Utils.FONT_MEDIUM.getLineHeight()
@@ -200,8 +204,10 @@ public class DownloadsMenu extends BasicGameState {
// buttons // buttons
float buttonMarginX = width * 0.004f; float buttonMarginX = width * 0.004f;
float buttonHeight = height * 0.038f; float buttonHeight = height * 0.038f;
float topButtonWidth = width * 0.14f; float resetWidth = width * 0.085f;
float lowerButtonWidth = width * 0.12f; float rankedWidth = width * 0.15f;
float serverWidth = width * 0.12f;
float lowerWidth = width * 0.12f;
float topButtonY = searchY + Utils.FONT_MEDIUM.getLineHeight() / 2f; float topButtonY = searchY + Utils.FONT_MEDIUM.getLineHeight() / 2f;
float lowerButtonY = height * 0.995f - searchY - buttonHeight / 2f; float lowerButtonY = height * 0.995f - searchY - buttonHeight / 2f;
Image button = GameImage.MENU_BUTTON_MID.getImage(); Image button = GameImage.MENU_BUTTON_MID.getImage();
@@ -209,25 +215,33 @@ public class DownloadsMenu extends BasicGameState {
Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage(); Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage();
buttonL = buttonL.getScaledCopy(buttonHeight / buttonL.getHeight()); buttonL = buttonL.getScaledCopy(buttonHeight / buttonL.getHeight());
buttonR = buttonR.getScaledCopy(buttonHeight / buttonR.getHeight()); buttonR = buttonR.getScaledCopy(buttonHeight / buttonR.getHeight());
Image topButton = button.getScaledCopy((int) topButtonWidth - buttonL.getWidth() - buttonR.getWidth(), (int) buttonHeight); int lrButtonWidth = buttonL.getWidth() + buttonR.getWidth();
Image lowerButton = button.getScaledCopy((int) lowerButtonWidth - buttonL.getWidth() - buttonR.getWidth(), (int) buttonHeight); Image resetButtonImage = button.getScaledCopy((int) resetWidth - lrButtonWidth, (int) buttonHeight);
float fullTopButtonWidth = topButton.getWidth() + buttonL.getWidth() + buttonR.getWidth(); Image rankedButtonImage = button.getScaledCopy((int) rankedWidth - lrButtonWidth, (int) buttonHeight);
float fullLowerButtonWidth = lowerButton.getWidth() + buttonL.getWidth() + buttonR.getWidth(); Image serverButtonImage = button.getScaledCopy((int) serverWidth - lrButtonWidth, (int) buttonHeight);
clearButton = new MenuButton(lowerButton, buttonL, buttonR, Image lowerButtonImage = button.getScaledCopy((int) lowerWidth - lrButtonWidth, (int) buttonHeight);
width * 0.75f + buttonMarginX + fullLowerButtonWidth / 2f, lowerButtonY); float resetButtonWidth = resetButtonImage.getWidth() + lrButtonWidth;
importButton = new MenuButton(lowerButton, buttonL, buttonR, float rankedButtonWidth = rankedButtonImage.getWidth() + lrButtonWidth;
width - buttonMarginX - fullLowerButtonWidth / 2f, lowerButtonY); float serverButtonWidth = serverButtonImage.getWidth() + lrButtonWidth;
resetButton = new MenuButton(topButton, buttonL, buttonR, float lowerButtonWidth = lowerButtonImage.getWidth() + lrButtonWidth;
baseX + searchWidth + buttonMarginX + fullTopButtonWidth / 2f, topButtonY); clearButton = new MenuButton(lowerButtonImage, buttonL, buttonR,
rankedButton = new MenuButton(topButton, buttonL, buttonR, width * 0.75f + buttonMarginX + lowerButtonWidth / 2f, lowerButtonY);
baseX + searchWidth + buttonMarginX * 2f + fullTopButtonWidth * 3 / 2f, topButtonY); importButton = new MenuButton(lowerButtonImage, buttonL, buttonR,
width - buttonMarginX - lowerButtonWidth / 2f, lowerButtonY);
resetButton = new MenuButton(resetButtonImage, buttonL, buttonR,
baseX + searchWidth + buttonMarginX + resetButtonWidth / 2f, topButtonY);
rankedButton = new MenuButton(rankedButtonImage, buttonL, buttonR,
baseX + searchWidth + buttonMarginX * 2f + resetButtonWidth + rankedButtonWidth / 2f, topButtonY);
serverButton = new MenuButton(serverButtonImage, buttonL, buttonR,
baseX + searchWidth + buttonMarginX * 3f + resetButtonWidth + rankedButtonWidth + serverButtonWidth / 2f, topButtonY);
clearButton.setText("Clear", Utils.FONT_MEDIUM, Color.white); clearButton.setText("Clear", Utils.FONT_MEDIUM, Color.white);
importButton.setText("Import All", Utils.FONT_MEDIUM, Color.white); importButton.setText("Import All", Utils.FONT_MEDIUM, Color.white);
resetButton.setText("Reset Search", Utils.FONT_MEDIUM, Color.white); resetButton.setText("Reset", Utils.FONT_MEDIUM, Color.white);
clearButton.setHoverFade(); clearButton.setHoverFade();
importButton.setHoverFade(); importButton.setHoverFade();
resetButton.setHoverFade(); resetButton.setHoverFade();
rankedButton.setHoverFade(); rankedButton.setHoverFade();
serverButton.setHoverFade();
} }
@Override @Override
@@ -317,6 +331,8 @@ public class DownloadsMenu extends BasicGameState {
resetButton.draw(Color.red); resetButton.draw(Color.red);
rankedButton.setText((rankedOnly) ? "Show Unranked" : "Hide Unranked", Utils.FONT_MEDIUM, Color.white); rankedButton.setText((rankedOnly) ? "Show Unranked" : "Hide Unranked", Utils.FONT_MEDIUM, Color.white);
rankedButton.draw(Color.magenta); rankedButton.draw(Color.magenta);
serverButton.setText(SERVERS[serverIndex].getName(), Utils.FONT_MEDIUM, Color.white);
serverButton.draw(Color.blue);
// importing beatmaps // importing beatmaps
if (importThread != null) { if (importThread != null) {
@@ -348,6 +364,7 @@ public class DownloadsMenu extends BasicGameState {
importButton.hoverUpdate(delta, mouseX, mouseY); importButton.hoverUpdate(delta, mouseX, mouseY);
resetButton.hoverUpdate(delta, mouseX, mouseY); resetButton.hoverUpdate(delta, mouseX, mouseY);
rankedButton.hoverUpdate(delta, mouseX, mouseY); rankedButton.hoverUpdate(delta, mouseX, mouseY);
serverButton.hoverUpdate(delta, mouseX, mouseY);
// focus timer // focus timer
if (focusResult != -1 && focusTimer < FOCUS_DELAY) if (focusResult != -1 && focusTimer < FOCUS_DELAY)
@@ -361,7 +378,9 @@ public class DownloadsMenu extends BasicGameState {
searchTimerReset = false; searchTimerReset = false;
final String query = search.getText().trim().toLowerCase(); final String query = search.getText().trim().toLowerCase();
if (lastQuery == null || !query.equals(lastQuery)) { final DownloadServer server = SERVERS[serverIndex];
if ((lastQuery == null || !query.equals(lastQuery)) &&
(query.length() == 0 || query.length() >= server.minQueryLength())) {
lastQuery = query; lastQuery = query;
lastQueryDir = pageDir; lastQueryDir = pageDir;
@@ -409,7 +428,7 @@ public class DownloadsMenu extends BasicGameState {
else { else {
if (query.isEmpty()) if (query.isEmpty())
searchResultString = "Type to search!"; searchResultString = "Type to search!";
else if (totalResults == 0) else if (totalResults == 0 || resultList.length == 0)
searchResultString = "No results found."; searchResultString = "No results found.";
else else
searchResultString = String.format("%d result%s found!", searchResultString = String.format("%d result%s found!",
@@ -481,7 +500,7 @@ public class DownloadsMenu extends BasicGameState {
} else { } else {
// play preview // play preview
try { try {
final URL url = new URL(server.getPreviewURL(node.getID())); final URL url = new URL(SERVERS[serverIndex].getPreviewURL(node.getID()));
MusicController.pause(); MusicController.pause();
new Thread() { new Thread() {
@Override @Override
@@ -525,9 +544,13 @@ public class DownloadsMenu extends BasicGameState {
} else { } else {
// start download // start download
if (!DownloadList.get().contains(node.getID())) { if (!DownloadList.get().contains(node.getID())) {
DownloadList.get().addNode(node); node.createDownload(SERVERS[serverIndex]);
node.createDownload(server); if (node.getDownload() == null)
node.getDownload().start(); UI.sendBarNotification("The download could not be started.");
else {
DownloadList.get().addNode(node);
node.getDownload().start();
}
} }
} }
} else { } else {
@@ -624,6 +647,22 @@ public class DownloadsMenu extends BasicGameState {
resetSearchTimer(); resetSearchTimer();
return; return;
} }
if (serverButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
resultList = null;
startResult = 0;
focusResult = -1;
totalResults = 0;
page = 0;
pageResultTotal = 1;
pageDir = Page.RESET;
searchResultString = "Loading data from server...";
serverIndex = (serverIndex + 1) % SERVERS.length;
lastQuery = null;
pageDir = Page.RESET;
resetSearchTimer();
return;
}
// downloads // downloads
if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) { if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) {
@@ -755,6 +794,7 @@ public class DownloadsMenu extends BasicGameState {
importButton.resetHover(); importButton.resetHover();
resetButton.resetHover(); resetButton.resetHover();
rankedButton.resetHover(); rankedButton.resetHover();
serverButton.resetHover();
focusResult = -1; focusResult = -1;
startResult = 0; startResult = 0;
startDownloadIndex = 0; startDownloadIndex = 0;