Merge branch 'master' of https://github.com/itdelatrisu/opsu into 'upstream'
# Conflicts: # README.md # src/itdelatrisu/opsu/Opsu.java # src/itdelatrisu/opsu/Utils.java # src/itdelatrisu/opsu/render/CurveRenderState.java # src/itdelatrisu/opsu/states/Game.java
This commit is contained in:
commit
e878f0fb0d
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,6 +4,7 @@
|
||||||
/Skins/
|
/Skins/
|
||||||
/SongPacks/
|
/SongPacks/
|
||||||
/Songs/
|
/Songs/
|
||||||
|
/Temp/
|
||||||
/.opsu.log
|
/.opsu.log
|
||||||
/.opsu.cfg
|
/.opsu.cfg
|
||||||
/.opsu.db*
|
/.opsu.db*
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package itdelatrisu.opsu;
|
package itdelatrisu.opsu;
|
||||||
|
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
|
import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
||||||
|
@ -61,6 +62,7 @@ public class Container extends AppGameContainer {
|
||||||
public void start() throws SlickException {
|
public void start() throws SlickException {
|
||||||
try {
|
try {
|
||||||
setup();
|
setup();
|
||||||
|
ErrorHandler.setGlString();
|
||||||
getDelta();
|
getDelta();
|
||||||
while (running())
|
while (running())
|
||||||
gameLoop();
|
gameLoop();
|
||||||
|
@ -131,6 +133,9 @@ public class Container extends AppGameContainer {
|
||||||
// prevent loading tracks from re-initializing OpenAL
|
// prevent loading tracks from re-initializing OpenAL
|
||||||
MusicController.reset();
|
MusicController.reset();
|
||||||
|
|
||||||
|
// stop any playing track
|
||||||
|
SoundController.stopTrack();
|
||||||
|
|
||||||
// reset BeatmapSetList data
|
// reset BeatmapSetList data
|
||||||
if (BeatmapSetList.get() != null)
|
if (BeatmapSetList.get() != null)
|
||||||
BeatmapSetList.get().reset();
|
BeatmapSetList.get().reset();
|
||||||
|
@ -142,6 +147,9 @@ public class Container extends AppGameContainer {
|
||||||
if (!Options.isWatchServiceEnabled())
|
if (!Options.isWatchServiceEnabled())
|
||||||
BeatmapWatchService.destroy();
|
BeatmapWatchService.destroy();
|
||||||
BeatmapWatchService.removeListeners();
|
BeatmapWatchService.removeListeners();
|
||||||
|
|
||||||
|
// delete temporary directory
|
||||||
|
Utils.deleteDirectory(Options.TEMP_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -33,6 +33,7 @@ import javax.swing.JScrollPane;
|
||||||
import javax.swing.JTextArea;
|
import javax.swing.JTextArea;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
import org.newdawn.slick.util.Log;
|
import org.newdawn.slick.util.Log;
|
||||||
import org.newdawn.slick.util.ResourceLoader;
|
import org.newdawn.slick.util.ResourceLoader;
|
||||||
|
|
||||||
|
@ -73,9 +74,23 @@ public class ErrorHandler {
|
||||||
message = { desc, scroll },
|
message = { desc, scroll },
|
||||||
messageReport = { descReport, scroll };
|
messageReport = { descReport, scroll };
|
||||||
|
|
||||||
|
/** OpenGL string (if any). */
|
||||||
|
private static String glString = null;
|
||||||
|
|
||||||
// This class should not be instantiated.
|
// This class should not be instantiated.
|
||||||
private ErrorHandler() {}
|
private ErrorHandler() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the OpenGL version string.
|
||||||
|
*/
|
||||||
|
public static void setGlString() {
|
||||||
|
try {
|
||||||
|
String glVersion = GL11.glGetString(GL11.GL_VERSION);
|
||||||
|
String glVendor = GL11.glGetString(GL11.GL_VENDOR);
|
||||||
|
glString = String.format("%s (%s)", glVersion, glVendor);
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays an error popup and logs the given error.
|
* Displays an error popup and logs the given error.
|
||||||
* @param error a description of the error
|
* @param error a description of the error
|
||||||
|
@ -197,6 +212,11 @@ public class ErrorHandler {
|
||||||
sb.append("**JRE:** ");
|
sb.append("**JRE:** ");
|
||||||
sb.append(System.getProperty("java.version"));
|
sb.append(System.getProperty("java.version"));
|
||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
|
if (glString != null) {
|
||||||
|
sb.append("**OpenGL Version:** ");
|
||||||
|
sb.append(glString);
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
sb.append("**Error:** `");
|
sb.append("**Error:** `");
|
||||||
sb.append(error);
|
sb.append(error);
|
||||||
|
|
|
@ -391,7 +391,7 @@ public class GameData {
|
||||||
if (hitResultList != null) {
|
if (hitResultList != null) {
|
||||||
for (HitObjectResult hitResult : hitResultList) {
|
for (HitObjectResult hitResult : hitResultList) {
|
||||||
if (hitResult.curve != null)
|
if (hitResult.curve != null)
|
||||||
hitResult.curve.discardCache();
|
hitResult.curve.discardGeometry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hitResultList = new LinkedBlockingDeque<HitObjectResult>();
|
hitResultList = new LinkedBlockingDeque<HitObjectResult>();
|
||||||
|
@ -964,7 +964,7 @@ public class GameData {
|
||||||
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME);
|
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME);
|
||||||
} else {
|
} else {
|
||||||
if (hitResult.curve != null)
|
if (hitResult.curve != null)
|
||||||
hitResult.curve.discardCache();
|
hitResult.curve.discardGeometry();
|
||||||
iter.remove();
|
iter.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1436,32 +1436,43 @@ public class GameData {
|
||||||
* (i.e. this will not overwrite existing data).
|
* (i.e. this will not overwrite existing data).
|
||||||
* @param beatmap the beatmap
|
* @param beatmap the beatmap
|
||||||
* @return the ScoreData object
|
* @return the ScoreData object
|
||||||
|
* @see #getCurrentScoreData(Beatmap, boolean)
|
||||||
*/
|
*/
|
||||||
public ScoreData getScoreData(Beatmap beatmap) {
|
public ScoreData getScoreData(Beatmap beatmap) {
|
||||||
if (scoreData != null)
|
if (scoreData == null)
|
||||||
|
scoreData = getCurrentScoreData(beatmap, false);
|
||||||
return scoreData;
|
return scoreData;
|
||||||
|
}
|
||||||
|
|
||||||
scoreData = new ScoreData();
|
/**
|
||||||
scoreData.timestamp = System.currentTimeMillis() / 1000L;
|
* Returns a ScoreData object encapsulating all current game data.
|
||||||
scoreData.MID = beatmap.beatmapID;
|
* @param beatmap the beatmap
|
||||||
scoreData.MSID = beatmap.beatmapSetID;
|
* @param slidingScore if true, use the display score (might not be actual score)
|
||||||
scoreData.title = beatmap.title;
|
* @return the ScoreData object
|
||||||
scoreData.artist = beatmap.artist;
|
* @see #getScoreData(Beatmap)
|
||||||
scoreData.creator = beatmap.creator;
|
*/
|
||||||
scoreData.version = beatmap.version;
|
public ScoreData getCurrentScoreData(Beatmap beatmap, boolean slidingScore) {
|
||||||
scoreData.hit300 = hitResultCount[HIT_300];
|
ScoreData sd = new ScoreData();
|
||||||
scoreData.hit100 = hitResultCount[HIT_100];
|
sd.timestamp = System.currentTimeMillis() / 1000L;
|
||||||
scoreData.hit50 = hitResultCount[HIT_50];
|
sd.MID = beatmap.beatmapID;
|
||||||
scoreData.geki = hitResultCount[HIT_300G];
|
sd.MSID = beatmap.beatmapSetID;
|
||||||
scoreData.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K];
|
sd.title = beatmap.title;
|
||||||
scoreData.miss = hitResultCount[HIT_MISS];
|
sd.artist = beatmap.artist;
|
||||||
scoreData.score = score;
|
sd.creator = beatmap.creator;
|
||||||
scoreData.combo = comboMax;
|
sd.version = beatmap.version;
|
||||||
scoreData.perfect = (comboMax == fullObjectCount);
|
sd.hit300 = hitResultCount[HIT_300];
|
||||||
scoreData.mods = GameMod.getModState();
|
sd.hit100 = hitResultCount[HIT_100];
|
||||||
scoreData.replayString = (replay == null) ? null : replay.getReplayFilename();
|
sd.hit50 = hitResultCount[HIT_50];
|
||||||
scoreData.playerName = null; // TODO
|
sd.geki = hitResultCount[HIT_300G];
|
||||||
return scoreData;
|
sd.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K];
|
||||||
|
sd.miss = hitResultCount[HIT_MISS];
|
||||||
|
sd.score = slidingScore ? scoreDisplay : score;
|
||||||
|
sd.combo = comboMax;
|
||||||
|
sd.perfect = (comboMax == fullObjectCount);
|
||||||
|
sd.mods = GameMod.getModState();
|
||||||
|
sd.replayString = (replay == null) ? null : replay.getReplayFilename();
|
||||||
|
sd.playerName = null; // TODO
|
||||||
|
return sd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -107,10 +107,13 @@ public class Opsu extends StateBasedGame {
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set default exception handler
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
ErrorHandler.error("** Uncaught Exception! **", e, true);
|
ErrorHandler.error("** Uncaught Exception! **", e, true);
|
||||||
|
System.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,16 +127,21 @@ public class Opsu extends StateBasedGame {
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ErrorHandler.error(String.format(
|
errorAndExit(
|
||||||
|
null,
|
||||||
|
String.format(
|
||||||
"opsu! could not be launched for one of these reasons:\n" +
|
"opsu! could not be launched for one of these reasons:\n" +
|
||||||
"- An instance of opsu! is already running.\n" +
|
"- An instance of opsu! is already running.\n" +
|
||||||
"- Another program is bound to port %d. " +
|
"- Another program is bound to port %d. " +
|
||||||
"You can change the port opsu! uses by editing the \"Port\" field in the configuration file.",
|
"You can change the port opsu! uses by editing the \"Port\" field in the configuration file.",
|
||||||
Options.getPort()), null, false);
|
Options.getPort()
|
||||||
System.exit(1);
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load natives
|
||||||
File nativeDir;
|
File nativeDir;
|
||||||
if (!Utils.isJarRunning() && (
|
if (!Utils.isJarRunning() && (
|
||||||
(nativeDir = new File("./target/natives/")).isDirectory() ||
|
(nativeDir = new File("./target/natives/")).isDirectory() ||
|
||||||
|
@ -166,7 +174,7 @@ public class Opsu extends StateBasedGame {
|
||||||
try {
|
try {
|
||||||
DBController.init();
|
DBController.init();
|
||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (UnsatisfiedLinkError e) {
|
||||||
errorAndExit(e, "The databases could not be initialized.");
|
errorAndExit(e, "The databases could not be initialized.", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if just updated
|
// check if just updated
|
||||||
|
@ -213,7 +221,7 @@ public class Opsu extends StateBasedGame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SlickException e) {
|
} catch (SlickException e) {
|
||||||
errorAndExit(e, "An error occurred while creating the game container.");
|
errorAndExit(e, "An error occurred while creating the game container.", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +240,7 @@ public class Opsu extends StateBasedGame {
|
||||||
} else {
|
} else {
|
||||||
if (id == STATE_GAME) {
|
if (id == STATE_GAME) {
|
||||||
MusicController.pause();
|
MusicController.pause();
|
||||||
|
MusicController.setPitch(1.0f);
|
||||||
MusicController.resume();
|
MusicController.resume();
|
||||||
} else
|
} else
|
||||||
songMenu.resetTrackOnLoad();
|
songMenu.resetTrackOnLoad();
|
||||||
|
@ -278,15 +287,16 @@ public class Opsu extends StateBasedGame {
|
||||||
* Throws an error and exits the application with the given message.
|
* Throws an error and exits the application with the given message.
|
||||||
* @param e the exception that caused the crash
|
* @param e the exception that caused the crash
|
||||||
* @param message the message to display
|
* @param message the message to display
|
||||||
|
* @param report whether to ask to report the error
|
||||||
*/
|
*/
|
||||||
private static void errorAndExit(Throwable e, String message) {
|
private static void errorAndExit(Throwable e, String message, boolean report) {
|
||||||
// JARs will not run properly inside directories containing '!'
|
// JARs will not run properly inside directories containing '!'
|
||||||
// http://bugs.java.com/view_bug.do?bug_id=4523159
|
// http://bugs.java.com/view_bug.do?bug_id=4523159
|
||||||
if (Utils.isJarRunning() && Utils.getRunningDirectory() != null &&
|
if (Utils.isJarRunning() && Utils.getRunningDirectory() != null &&
|
||||||
Utils.getRunningDirectory().getAbsolutePath().indexOf('!') != -1)
|
Utils.getRunningDirectory().getAbsolutePath().indexOf('!') != -1)
|
||||||
ErrorHandler.error("JARs cannot be run from some paths containing '!'. Please move or rename the file and try again.", null, false);
|
ErrorHandler.error("JARs cannot be run from some paths containing '!'. Please move or rename the file and try again.", null, false);
|
||||||
else
|
else
|
||||||
ErrorHandler.error(message, e, true);
|
ErrorHandler.error(message, e, report);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,9 @@ public class Options {
|
||||||
/** Directory where natives are unpacked. */
|
/** Directory where natives are unpacked. */
|
||||||
public static final File NATIVE_DIR = new File(CACHE_DIR, "Natives/");
|
public static final File NATIVE_DIR = new File(CACHE_DIR, "Natives/");
|
||||||
|
|
||||||
|
/** Directory where temporary files are stored (deleted on exit). */
|
||||||
|
public static final File TEMP_DIR = new File(CACHE_DIR, "Temp/");
|
||||||
|
|
||||||
/** Font file name. */
|
/** Font file name. */
|
||||||
public static final String FONT_NAME = "DroidSansFallback.ttf";
|
public static final String FONT_NAME = "DroidSansFallback.ttf";
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.sql.SQLException;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.newdawn.slick.Color;
|
import org.newdawn.slick.Color;
|
||||||
import org.newdawn.slick.Graphics;
|
import org.newdawn.slick.Graphics;
|
||||||
|
@ -328,6 +329,69 @@ public class ScoreData implements Comparable<ScoreData> {
|
||||||
c.a = oldAlpha;
|
c.a = oldAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the score in-game (smaller and with less information).
|
||||||
|
* @param g the current graphics context
|
||||||
|
* @param vPos the base y position of the scoreboard
|
||||||
|
* @param rank the current rank of this score
|
||||||
|
* @param position the animated position offset
|
||||||
|
* @param data an instance of GameData to draw rank number
|
||||||
|
* @param alpha the transparency of the score
|
||||||
|
* @param isActive if this score is the one currently played
|
||||||
|
*/
|
||||||
|
public void drawSmall(Graphics g, int vPos, int rank, float position, GameData data, float alpha, boolean isActive) {
|
||||||
|
int rectWidth = (int) (145 * GameImage.getUIscale()); //135
|
||||||
|
int rectHeight = data.getScoreSymbolImage('0').getHeight();
|
||||||
|
int vertDistance = rectHeight + 10;
|
||||||
|
int yPos = (int) (vPos + position * vertDistance - rectHeight / 2);
|
||||||
|
int xPaddingLeft = Math.max(4, (int) (rectWidth * 0.04f));
|
||||||
|
int xPaddingRight = Math.max(2, (int) (rectWidth * 0.02f));
|
||||||
|
int yPadding = Math.max(2, (int) (rectHeight * 0.02f));
|
||||||
|
String scoreString = String.format(Locale.US, "%,d", score);
|
||||||
|
String comboString = String.format("%dx", combo);
|
||||||
|
String rankString = String.format("%d", rank);
|
||||||
|
|
||||||
|
Color white = Colors.WHITE_ALPHA, blue = Colors.BLUE_SCOREBOARD, black = Colors.BLACK_ALPHA;
|
||||||
|
float oldAlphaWhite = white.a, oldAlphaBlue = blue.a, oldAlphaBlack = black.a;
|
||||||
|
|
||||||
|
// rectangle background
|
||||||
|
Color rectColor = isActive ? white : blue;
|
||||||
|
rectColor.a = alpha * 0.2f;
|
||||||
|
g.setColor(rectColor);
|
||||||
|
g.fillRect(0, yPos, rectWidth, rectHeight);
|
||||||
|
black.a = alpha * 0.2f;
|
||||||
|
g.setColor(black);
|
||||||
|
float oldLineWidth = g.getLineWidth();
|
||||||
|
g.setLineWidth(1f);
|
||||||
|
g.drawRect(0, yPos, rectWidth, rectHeight);
|
||||||
|
g.setLineWidth(oldLineWidth);
|
||||||
|
|
||||||
|
// rank
|
||||||
|
data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, alpha * 0.2f, true);
|
||||||
|
|
||||||
|
white.a = blue.a = alpha * 0.75f;
|
||||||
|
|
||||||
|
// player name
|
||||||
|
if (playerName != null)
|
||||||
|
Fonts.MEDIUMBOLD.drawString(xPaddingLeft, yPos + yPadding, playerName, white);
|
||||||
|
|
||||||
|
// score
|
||||||
|
Fonts.DEFAULT.drawString(
|
||||||
|
xPaddingLeft, yPos + rectHeight - Fonts.DEFAULT.getLineHeight() - yPadding, scoreString, white
|
||||||
|
);
|
||||||
|
|
||||||
|
// combo
|
||||||
|
Fonts.DEFAULT.drawString(
|
||||||
|
rectWidth - Fonts.DEFAULT.getWidth(comboString) - xPaddingRight,
|
||||||
|
yPos + rectHeight - Fonts.DEFAULT.getLineHeight() - yPadding,
|
||||||
|
comboString, blue
|
||||||
|
);
|
||||||
|
|
||||||
|
white.a = oldAlphaWhite;
|
||||||
|
blue.a = oldAlphaBlue;
|
||||||
|
black.a = oldAlphaBlack;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tooltip string for this score.
|
* Returns the tooltip string for this score.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,7 +23,6 @@ import itdelatrisu.opsu.audio.SoundEffect;
|
||||||
import itdelatrisu.opsu.beatmap.HitObject;
|
import itdelatrisu.opsu.beatmap.HitObject;
|
||||||
import itdelatrisu.opsu.downloads.Download;
|
import itdelatrisu.opsu.downloads.Download;
|
||||||
import itdelatrisu.opsu.downloads.DownloadNode;
|
import itdelatrisu.opsu.downloads.DownloadNode;
|
||||||
import itdelatrisu.opsu.objects.Circle;
|
|
||||||
import itdelatrisu.opsu.replay.PlaybackSpeed;
|
import itdelatrisu.opsu.replay.PlaybackSpeed;
|
||||||
import itdelatrisu.opsu.ui.Fonts;
|
import itdelatrisu.opsu.ui.Fonts;
|
||||||
import itdelatrisu.opsu.ui.UI;
|
import itdelatrisu.opsu.ui.UI;
|
||||||
|
@ -44,6 +43,7 @@ import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -51,6 +51,10 @@ import java.util.Scanner;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
@ -349,7 +353,7 @@ public class Utils {
|
||||||
* deletes the directory itself.
|
* deletes the directory itself.
|
||||||
* @param dir the directory to delete
|
* @param dir the directory to delete
|
||||||
*/
|
*/
|
||||||
private static void deleteDirectory(File dir) {
|
public static void deleteDirectory(File dir) {
|
||||||
if (dir == null || !dir.isDirectory())
|
if (dir == null || !dir.isDirectory())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -570,6 +574,30 @@ public class Utils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches validation of SSL certificates on or off by installing a default
|
||||||
|
* all-trusting {@link TrustManager}.
|
||||||
|
* @param enabled whether to validate SSL certificates
|
||||||
|
* @author neu242 (http://stackoverflow.com/a/876785)
|
||||||
|
*/
|
||||||
|
public static void setSSLCertValidation(boolean enabled) {
|
||||||
|
// create a trust manager that does not validate certificate chains
|
||||||
|
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||||
|
new X509TrustManager() {
|
||||||
|
@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
|
||||||
|
@Override public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||||
|
@Override public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// install the all-trusting trust manager
|
||||||
|
try {
|
||||||
|
SSLContext sc = SSLContext.getInstance("SSL");
|
||||||
|
sc.init(null, enabled ? null : trustAllCerts, null);
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
|
||||||
public static int getQuadrant(double x, double y) {
|
public static int getQuadrant(double x, double y) {
|
||||||
if (x < Options.width / 2d) {
|
if (x < Options.width / 2d) {
|
||||||
return y < Options.height / 2d ? 2 : 3;
|
return y < Options.height / 2d ? 2 : 3;
|
||||||
|
|
|
@ -22,6 +22,9 @@ import itdelatrisu.opsu.ErrorHandler;
|
||||||
import itdelatrisu.opsu.Options;
|
import itdelatrisu.opsu.Options;
|
||||||
import itdelatrisu.opsu.audio.HitSound.SampleSet;
|
import itdelatrisu.opsu.audio.HitSound.SampleSet;
|
||||||
import itdelatrisu.opsu.beatmap.HitObject;
|
import itdelatrisu.opsu.beatmap.HitObject;
|
||||||
|
import itdelatrisu.opsu.downloads.Download;
|
||||||
|
import itdelatrisu.opsu.downloads.Download.DownloadListener;
|
||||||
|
import itdelatrisu.opsu.ui.UI;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -355,24 +358,58 @@ public class SoundController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays a track from a URL.
|
* Plays a track from a remote URL.
|
||||||
* If a track is currently playing, it will be stopped.
|
* If a track is currently playing, it will be stopped.
|
||||||
* @param url the resource URL
|
* @param url the remote URL
|
||||||
|
* @param name the track file name
|
||||||
* @param isMP3 true if MP3, false if WAV
|
* @param isMP3 true if MP3, false if WAV
|
||||||
* @param listener the line listener
|
* @param listener the line listener
|
||||||
* @return the MultiClip being played
|
* @return true if playing, false otherwise
|
||||||
* @throws SlickException if any error occurred
|
* @throws SlickException if any error occurred
|
||||||
*/
|
*/
|
||||||
public static synchronized MultiClip playTrack(URL url, boolean isMP3, LineListener listener) throws SlickException {
|
public static synchronized boolean playTrack(String url, String name, boolean isMP3, LineListener listener)
|
||||||
|
throws SlickException {
|
||||||
|
// stop previous track
|
||||||
stopTrack();
|
stopTrack();
|
||||||
try {
|
|
||||||
AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);
|
// download new track
|
||||||
currentTrack = loadClip(url.getFile(), audioIn, isMP3);
|
File dir = Options.TEMP_DIR;
|
||||||
playClip(currentTrack, Options.getMusicVolume() * Options.getMasterVolume(), listener);
|
if (!dir.isDirectory())
|
||||||
return currentTrack;
|
dir.mkdir();
|
||||||
} catch (Exception e) {
|
String filename = String.format("%s.%s", name, isMP3 ? "mp3" : "wav");
|
||||||
throw new SlickException(String.format("Failed to load clip '%s'.", url.getFile(), e));
|
final File downloadFile = new File(dir, filename);
|
||||||
|
boolean complete;
|
||||||
|
if (downloadFile.isFile()) {
|
||||||
|
complete = true; // file already downloaded
|
||||||
|
} else {
|
||||||
|
Download download = new Download(url, downloadFile.getAbsolutePath());
|
||||||
|
download.setListener(new DownloadListener() {
|
||||||
|
@Override
|
||||||
|
public void completed() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error() {
|
||||||
|
UI.sendBarNotification("Failed to download track preview.");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
download.start().join();
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
complete = (download.getStatus() == Download.Status.COMPLETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// play the track
|
||||||
|
if (complete) {
|
||||||
|
try {
|
||||||
|
AudioInputStream audioIn = AudioSystem.getAudioInputStream(downloadFile);
|
||||||
|
currentTrack = loadClip(filename, audioIn, isMP3);
|
||||||
|
playClip(currentTrack, Options.getMusicVolume() * Options.getMasterVolume(), listener);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SlickException(String.format("Failed to load clip '%s'.", url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -304,6 +304,16 @@ public class ScoreDB {
|
||||||
* @return all scores for the beatmap, or null if any error occurred
|
* @return all scores for the beatmap, or null if any error occurred
|
||||||
*/
|
*/
|
||||||
public static ScoreData[] getMapScores(Beatmap beatmap) {
|
public static ScoreData[] getMapScores(Beatmap beatmap) {
|
||||||
|
return getMapScoresExcluding(beatmap, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the game scores for a beatmap while excluding a score.
|
||||||
|
* @param beatmap the beatmap
|
||||||
|
* @param exclude the filename (replay string) of the score to exclude
|
||||||
|
* @return all scores for the beatmap except for exclude, or null if any error occurred
|
||||||
|
*/
|
||||||
|
public static ScoreData[] getMapScoresExcluding(Beatmap beatmap, String exclude) {
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -317,8 +327,12 @@ public class ScoreDB {
|
||||||
ResultSet rs = selectMapStmt.executeQuery();
|
ResultSet rs = selectMapStmt.executeQuery();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
ScoreData s = new ScoreData(rs);
|
ScoreData s = new ScoreData(rs);
|
||||||
|
if (s.replayString != null && s.replayString.equals(exclude)) {
|
||||||
|
// don't return this score
|
||||||
|
} else {
|
||||||
list.add(s);
|
list.add(s);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
ErrorHandler.error("Failed to read scores from database.", e, true);
|
ErrorHandler.error("Failed to read scores from database.", e, true);
|
||||||
|
|
|
@ -167,12 +167,13 @@ public class Download {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the download from the "waiting" status.
|
* Starts the download from the "waiting" status.
|
||||||
|
* @return the started download thread, or {@code null} if none started
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public Thread start() {
|
||||||
if (status != Status.WAITING)
|
if (status != Status.WAITING)
|
||||||
return;
|
return null;
|
||||||
|
|
||||||
new Thread() {
|
Thread t = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// open connection
|
// open connection
|
||||||
|
@ -274,7 +275,9 @@ public class Download {
|
||||||
listener.error();
|
listener.error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
};
|
||||||
|
t.start();
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,8 +27,9 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download server: http://osu.mengsky.net/
|
* Download server: http://osu.mengsky.net/
|
||||||
|
@ -38,13 +39,10 @@ public class MengSkyServer extends DownloadServer {
|
||||||
private static final String SERVER_NAME = "MengSky";
|
private static final String SERVER_NAME = "MengSky";
|
||||||
|
|
||||||
/** Formatted download URL: {@code beatmapSetID} */
|
/** Formatted download URL: {@code beatmapSetID} */
|
||||||
private static final String DOWNLOAD_URL = "http://osu.mengsky.net/d.php?id=%d";
|
private static final String DOWNLOAD_URL = "http://osu.mengsky.net/api/download/%d";
|
||||||
|
|
||||||
/** Formatted search URL: {@code query} */
|
/** Formatted search URL: {@code query,page,unranked,approved,qualified} */
|
||||||
private static final String SEARCH_URL = "http://osu.mengsky.net/index.php?search_keywords=%s";
|
private static final String SEARCH_URL = "http://osu.mengsky.net/api/beatmapinfo?query=%s&page=%d&ranked=1&unrank=%d&approved=%d&qualified=%d";
|
||||||
|
|
||||||
/** Formatted home URL: {@code page} */
|
|
||||||
private static final String HOME_URL = "http://osu.mengsky.net/index.php?next=1&page=%d";
|
|
||||||
|
|
||||||
/** Maximum beatmaps displayed per page. */
|
/** Maximum beatmaps displayed per page. */
|
||||||
private static final int PAGE_LIMIT = 20;
|
private static final int PAGE_LIMIT = 20;
|
||||||
|
@ -67,127 +65,41 @@ public class MengSkyServer extends DownloadServer {
|
||||||
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
||||||
DownloadNode[] nodes = null;
|
DownloadNode[] nodes = null;
|
||||||
try {
|
try {
|
||||||
// read HTML
|
// read JSON
|
||||||
String search;
|
int rankedOnlyFlag = rankedOnly ? 0 : 1;
|
||||||
boolean isSearch;
|
String search = String.format(
|
||||||
if (query.isEmpty()) {
|
SEARCH_URL, URLEncoder.encode(query, "UTF-8"), page,
|
||||||
isSearch = false;
|
rankedOnlyFlag, rankedOnlyFlag, rankedOnlyFlag
|
||||||
search = String.format(HOME_URL, page - 1);
|
);
|
||||||
} else {
|
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
||||||
isSearch = true;
|
|
||||||
search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"));
|
// parse result list
|
||||||
|
JSONArray arr = json.getJSONArray("data");
|
||||||
|
nodes = new DownloadNode[arr.length()];
|
||||||
|
for (int i = 0; i < nodes.length; i++) {
|
||||||
|
JSONObject item = arr.getJSONObject(i);
|
||||||
|
String
|
||||||
|
title = item.getString("title"), titleU = item.getString("titleU"),
|
||||||
|
artist = item.getString("artist"), artistU = item.getString("artistU"),
|
||||||
|
creator = item.getString("creator");
|
||||||
|
// bug with v1.x API (as of 10-13-16):
|
||||||
|
// sometimes titleU is artistU instead of the proper title
|
||||||
|
if (titleU.equals(artistU) && !titleU.equals(title))
|
||||||
|
titleU = title;
|
||||||
|
nodes[i] = new DownloadNode(
|
||||||
|
item.getInt("id"), item.getString("syncedDateTime"),
|
||||||
|
title, titleU, artist, artistU, creator
|
||||||
|
);
|
||||||
}
|
}
|
||||||
String html = Utils.readDataFromUrl(new URL(search));
|
|
||||||
if (html == null) {
|
|
||||||
this.totalResults = -1;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse results
|
|
||||||
// NOTE: Maybe an HTML parser would be better for this...
|
|
||||||
// FORMAT:
|
|
||||||
// <div class="beatmap" style="{{...}}">
|
|
||||||
// <div class="preview" style="background-image:url(http://b.ppy.sh/thumb/{{id}}l.jpg)"></div>
|
|
||||||
// <div class="name"> <a href="">{{artist}} - {{title}}</a> </div>
|
|
||||||
// <div class="douban_details">
|
|
||||||
// <span>Creator:</span> {{creator}}<br>
|
|
||||||
// <span>MaxBpm:</span> {{bpm}}<br>
|
|
||||||
// <span>Title:</span> {{titleUnicode}}<br>
|
|
||||||
// <span>Artist:</span> {{artistUnicode}}<br>
|
|
||||||
// <span>Status:</span> <font color={{"#00CD00" || "#EE0000"}}>{{"Ranked?" || "Unranked"}}</font><br>
|
|
||||||
// </div>
|
|
||||||
// <div class="details"> <a href=""></a> <br>
|
|
||||||
// <span>Fork:</span> bloodcat<br>
|
|
||||||
// <span>UpdateTime:</span> {{yyyy}}/{{mm}}/{{dd}} {{hh}}:{{mm}}:{{ss}}<br>
|
|
||||||
// <span>Mode:</span> <img id="{{'s' || 'c' || ...}}" src="/img/{{'s' || 'c' || ...}}.png"> {{...}}
|
|
||||||
// </div>
|
|
||||||
// <div class="download">
|
|
||||||
// <a href="https://osu.ppy.sh/s/{{id}}" class=" btn" target="_blank">Osu.ppy</a>
|
|
||||||
// </div>
|
|
||||||
// <div class="download">
|
|
||||||
// <a href="http://osu.mengsky.net/d.php?id={{id}}" class=" btn" target="_blank">DownLoad</a>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
List<DownloadNode> nodeList = new ArrayList<DownloadNode>();
|
|
||||||
final String
|
|
||||||
START_TAG = "<div class=\"beatmap\"", NAME_TAG = "<div class=\"name\"> <a href=\"\">",
|
|
||||||
CREATOR_TAG = "<span>Creator:</span> ", TITLE_TAG = "<span>Title:</span> ", ARTIST_TAG = "<span>Artist:</span> ",
|
|
||||||
TIMESTAMP_TAG = "<span>UpdateTime:</span> ", DOWNLOAD_TAG = "<div class=\"download\">",
|
|
||||||
BR_TAG = "<br>", HREF_TAG = "<a href=\"", HREF_TAG_END = "</a>";
|
|
||||||
int index = -1;
|
|
||||||
int nextIndex = html.indexOf(START_TAG, index + 1);
|
|
||||||
int divCount = 0;
|
|
||||||
while ((index = nextIndex) != -1) {
|
|
||||||
nextIndex = html.indexOf(START_TAG, index + 1);
|
|
||||||
int n = (nextIndex == -1) ? html.length() : nextIndex;
|
|
||||||
divCount++;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
// find beatmap
|
|
||||||
i = html.indexOf(NAME_TAG, index + START_TAG.length());
|
|
||||||
if (i == -1 || i > n) continue;
|
|
||||||
j = html.indexOf(HREF_TAG_END, i + 1);
|
|
||||||
if (j == -1 || j > n) continue;
|
|
||||||
String beatmap = html.substring(i + NAME_TAG.length(), j);
|
|
||||||
String[] beatmapTokens = beatmap.split(" - ", 2);
|
|
||||||
if (beatmapTokens.length < 2)
|
|
||||||
continue;
|
|
||||||
String artist = beatmapTokens[0];
|
|
||||||
String title = beatmapTokens[1];
|
|
||||||
|
|
||||||
// find other beatmap details
|
|
||||||
i = html.indexOf(CREATOR_TAG, j + HREF_TAG_END.length());
|
|
||||||
if (i == -1 || i > n) continue;
|
|
||||||
j = html.indexOf(BR_TAG, i + CREATOR_TAG.length());
|
|
||||||
if (j == -1 || j > n) continue;
|
|
||||||
String creator = html.substring(i + CREATOR_TAG.length(), j);
|
|
||||||
i = html.indexOf(TITLE_TAG, j + BR_TAG.length());
|
|
||||||
if (i == -1 || i > n) continue;
|
|
||||||
j = html.indexOf(BR_TAG, i + TITLE_TAG.length());
|
|
||||||
if (j == -1 || j > n) continue;
|
|
||||||
String titleUnicode = html.substring(i + TITLE_TAG.length(), j);
|
|
||||||
i = html.indexOf(ARTIST_TAG, j + BR_TAG.length());
|
|
||||||
if (i == -1 || i > n) continue;
|
|
||||||
j = html.indexOf(BR_TAG, i + ARTIST_TAG.length());
|
|
||||||
if (j == -1 || j > n) continue;
|
|
||||||
String artistUnicode = html.substring(i + ARTIST_TAG.length(), j);
|
|
||||||
i = html.indexOf(TIMESTAMP_TAG, j + BR_TAG.length());
|
|
||||||
if (i == -1 || i >= n) continue;
|
|
||||||
j = html.indexOf(BR_TAG, i + TIMESTAMP_TAG.length());
|
|
||||||
if (j == -1 || j > n) continue;
|
|
||||||
String date = html.substring(i + TIMESTAMP_TAG.length(), j);
|
|
||||||
|
|
||||||
// find beatmap ID
|
|
||||||
i = html.indexOf(DOWNLOAD_TAG, j + BR_TAG.length());
|
|
||||||
if (i == -1 || i >= n) continue;
|
|
||||||
i = html.indexOf(HREF_TAG, i + DOWNLOAD_TAG.length());
|
|
||||||
if (i == -1 || i > n) continue;
|
|
||||||
j = html.indexOf('"', i + HREF_TAG.length());
|
|
||||||
if (j == -1 || j > n) continue;
|
|
||||||
String downloadURL = html.substring(i + HREF_TAG.length(), j);
|
|
||||||
String[] downloadTokens = downloadURL.split("(?=\\d*$)", 2);
|
|
||||||
if (downloadTokens[1].isEmpty()) continue;
|
|
||||||
int id;
|
|
||||||
try {
|
|
||||||
id = Integer.parseInt(downloadTokens[1]);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeList.add(new DownloadNode(id, date, title, titleUnicode, artist, artistUnicode, creator));
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes = nodeList.toArray(new DownloadNode[nodeList.size()]);
|
|
||||||
|
|
||||||
// store total result count
|
// store total result count
|
||||||
if (isSearch)
|
int pageTotal = json.getInt("pageTotal");
|
||||||
this.totalResults = nodes.length;
|
int resultCount = nodes.length;
|
||||||
else {
|
if (page == pageTotal)
|
||||||
int resultCount = nodes.length + (page - 1) * PAGE_LIMIT;
|
resultCount = nodes.length + (pageTotal - 1) * PAGE_LIMIT;
|
||||||
if (divCount == PAGE_LIMIT)
|
else
|
||||||
resultCount++;
|
resultCount = 1 + (pageTotal - 1) * PAGE_LIMIT;
|
||||||
this.totalResults = resultCount;
|
this.totalResults = resultCount;
|
||||||
}
|
|
||||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||||
}
|
}
|
||||||
|
@ -195,7 +107,7 @@ public class MengSkyServer extends DownloadServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int minQueryLength() { return 2; }
|
public int minQueryLength() { return 1; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int totalResults() { return totalResults; }
|
public int totalResults() { return totalResults; }
|
||||||
|
|
|
@ -93,6 +93,8 @@ public class YaSOnlineServer extends DownloadServer {
|
||||||
*/
|
*/
|
||||||
private String getDownloadURLFromMapData(int beatmapSetID) throws IOException {
|
private String getDownloadURLFromMapData(int beatmapSetID) throws IOException {
|
||||||
try {
|
try {
|
||||||
|
Utils.setSSLCertValidation(false);
|
||||||
|
|
||||||
// read JSON
|
// read JSON
|
||||||
String search = String.format(DOWNLOAD_URL, beatmapSetID);
|
String search = String.format(DOWNLOAD_URL, beatmapSetID);
|
||||||
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
||||||
|
@ -114,6 +116,8 @@ public class YaSOnlineServer extends DownloadServer {
|
||||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||||
ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true);
|
ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true);
|
||||||
return null;
|
return null;
|
||||||
|
} finally {
|
||||||
|
Utils.setSSLCertValidation(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +125,8 @@ public class YaSOnlineServer extends DownloadServer {
|
||||||
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
||||||
DownloadNode[] nodes = null;
|
DownloadNode[] nodes = null;
|
||||||
try {
|
try {
|
||||||
|
Utils.setSSLCertValidation(false);
|
||||||
|
|
||||||
// read JSON
|
// read JSON
|
||||||
String search;
|
String search;
|
||||||
boolean isSearch;
|
boolean isSearch;
|
||||||
|
@ -181,6 +187,8 @@ public class YaSOnlineServer extends DownloadServer {
|
||||||
this.totalResults = maxServerID;
|
this.totalResults = maxServerID;
|
||||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||||
|
} finally {
|
||||||
|
Utils.setSSLCertValidation(true);
|
||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,12 +101,12 @@ public abstract class Curve {
|
||||||
Curve.borderColor = borderColor;
|
Curve.borderColor = borderColor;
|
||||||
|
|
||||||
ContextCapabilities capabilities = GLContext.getCapabilities();
|
ContextCapabilities capabilities = GLContext.getCapabilities();
|
||||||
mmsliderSupported = capabilities.GL_EXT_framebuffer_object;
|
mmsliderSupported = capabilities.OpenGL20;
|
||||||
if (mmsliderSupported)
|
if (mmsliderSupported)
|
||||||
CurveRenderState.init(width, height, circleDiameter);
|
CurveRenderState.init(width, height, circleDiameter);
|
||||||
else {
|
else {
|
||||||
if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER)
|
if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER)
|
||||||
Log.warn("New slider style requires FBO support.");
|
Log.warn("New slider style requires OpenGL 2.0.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,8 +190,8 @@ public abstract class Curve {
|
||||||
/**
|
/**
|
||||||
* Discards the slider cache (only used for mmsliders).
|
* Discards the slider cache (only used for mmsliders).
|
||||||
*/
|
*/
|
||||||
public void discardCache() {
|
public void discardGeometry() {
|
||||||
if (renderState != null)
|
if (renderState != null)
|
||||||
renderState.discardCache();
|
renderState.discardGeometry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,6 @@ import itdelatrisu.opsu.ui.UI;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import javax.sound.sampled.LineEvent;
|
import javax.sound.sampled.LineEvent;
|
||||||
import javax.sound.sampled.LineListener;
|
import javax.sound.sampled.LineListener;
|
||||||
|
@ -663,15 +661,18 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
SoundController.stopTrack();
|
SoundController.stopTrack();
|
||||||
} else {
|
} else {
|
||||||
// play preview
|
// play preview
|
||||||
try {
|
final String url = serverMenu.getSelectedItem().getPreviewURL(node.getID());
|
||||||
final URL url = new URL(serverMenu.getSelectedItem().getPreviewURL(node.getID()));
|
|
||||||
MusicController.pause();
|
MusicController.pause();
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
previewID = -1;
|
previewID = -1;
|
||||||
SoundController.playTrack(url, true, new LineListener() {
|
boolean playing = SoundController.playTrack(
|
||||||
|
url,
|
||||||
|
Integer.toString(node.getID()),
|
||||||
|
true,
|
||||||
|
new LineListener() {
|
||||||
@Override
|
@Override
|
||||||
public void update(LineEvent event) {
|
public void update(LineEvent event) {
|
||||||
if (event.getType() == LineEvent.Type.STOP) {
|
if (event.getType() == LineEvent.Type.STOP) {
|
||||||
|
@ -681,7 +682,9 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
if (playing)
|
||||||
previewID = node.getID();
|
previewID = node.getID();
|
||||||
} catch (SlickException e) {
|
} catch (SlickException e) {
|
||||||
UI.sendBarNotification("Failed to load track preview. See log for details.");
|
UI.sendBarNotification("Failed to load track preview. See log for details.");
|
||||||
|
@ -689,10 +692,6 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
UI.sendBarNotification("Could not load track preview (bad URL).");
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,12 @@ public class Game extends BasicGameState {
|
||||||
/** Maximum rotation, in degrees, over fade out upon death. */
|
/** Maximum rotation, in degrees, over fade out upon death. */
|
||||||
private static final float MAX_ROTATION = 90f;
|
private static final float MAX_ROTATION = 90f;
|
||||||
|
|
||||||
|
/** The duration of the score changing animation. */
|
||||||
|
private static final float SCOREBOARD_ANIMATION_TIME = 500f;
|
||||||
|
|
||||||
|
/** The time the scoreboard takes to fade in. */
|
||||||
|
private static final float SCOREBOARD_FADE_IN_TIME = 300f;
|
||||||
|
|
||||||
/** Minimum time before start of song, in milliseconds, to process skip-related actions. */
|
/** Minimum time before start of song, in milliseconds, to process skip-related actions. */
|
||||||
private static final int SKIP_OFFSET = 2000;
|
private static final int SKIP_OFFSET = 2000;
|
||||||
|
|
||||||
|
@ -265,6 +271,21 @@ public class Game extends BasicGameState {
|
||||||
private float epiImgY;
|
private float epiImgY;
|
||||||
private int epiImgTime;
|
private int epiImgTime;
|
||||||
|
|
||||||
|
/** The previous scores. */
|
||||||
|
private ScoreData[] previousScores;
|
||||||
|
|
||||||
|
/** The current rank in the scores. */
|
||||||
|
private int currentRank;
|
||||||
|
|
||||||
|
/** The time the rank was last updated. */
|
||||||
|
private int lastRankUpdateTime;
|
||||||
|
|
||||||
|
/** Whether the scoreboard is visible. */
|
||||||
|
private boolean scoreboardVisible;
|
||||||
|
|
||||||
|
/** The current alpha of the scoreboard. */
|
||||||
|
private float currentScoreboardAlpha;
|
||||||
|
|
||||||
/** Music position bar background colors. */
|
/** Music position bar background colors. */
|
||||||
private static final Color
|
private static final Color
|
||||||
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
|
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
|
||||||
|
@ -603,6 +624,46 @@ public class Game extends BasicGameState {
|
||||||
drawHitObjects(g, trackPosition);
|
drawHitObjects(g, trackPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in-game scoreboard
|
||||||
|
if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive()) {
|
||||||
|
ScoreData currentScore = data.getCurrentScoreData(beatmap, true);
|
||||||
|
while (currentRank > 0 && previousScores[currentRank - 1].score < currentScore.score) {
|
||||||
|
currentRank--;
|
||||||
|
lastRankUpdateTime = trackPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
float animation = AnimationEquation.IN_OUT_QUAD.calc(
|
||||||
|
Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f)
|
||||||
|
);
|
||||||
|
int scoreboardPosition = 2 * container.getHeight() / 3;
|
||||||
|
|
||||||
|
if (currentRank < 4) {
|
||||||
|
// draw the (new) top 5 ranks
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int index = i + (i >= currentRank ? 1 : 0);
|
||||||
|
if (i < previousScores.length) {
|
||||||
|
float position = index + (i == currentRank ? animation - 3f : -2f);
|
||||||
|
previousScores[i].drawSmall(g, scoreboardPosition, index + 1, position, data, currentScoreboardAlpha, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, currentScoreboardAlpha, true);
|
||||||
|
} else {
|
||||||
|
// draw the top 2 and next 2 ranks
|
||||||
|
previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, currentScoreboardAlpha, false);
|
||||||
|
previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, currentScoreboardAlpha, false);
|
||||||
|
previousScores[currentRank - 2].drawSmall(
|
||||||
|
g, scoreboardPosition, currentRank - 1, animation - 1f, data, currentScoreboardAlpha * animation, false
|
||||||
|
);
|
||||||
|
previousScores[currentRank - 1].drawSmall(g, scoreboardPosition, currentRank, animation, data, currentScoreboardAlpha, false);
|
||||||
|
currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, currentScoreboardAlpha, true);
|
||||||
|
if (animation < 1.0f && currentRank < previousScores.length) {
|
||||||
|
previousScores[currentRank].drawSmall(
|
||||||
|
g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, currentScoreboardAlpha * (1f - animation), false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!Dancer.hideui && GameMod.AUTO.isActive())
|
if (!Dancer.hideui && GameMod.AUTO.isActive())
|
||||||
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
||||||
|
|
||||||
|
@ -677,6 +738,7 @@ public class Game extends BasicGameState {
|
||||||
if (isReplay || GameMod.AUTO.isActive())
|
if (isReplay || GameMod.AUTO.isActive())
|
||||||
playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
|
playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
|
int firstObjectTime = beatmap.objects[0].getTime();
|
||||||
|
|
||||||
// returning from pause screen: must click previous mouse position
|
// returning from pause screen: must click previous mouse position
|
||||||
if (pauseTime > -1) {
|
if (pauseTime > -1) {
|
||||||
|
@ -782,6 +844,20 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update in-game scoreboard
|
||||||
|
if (previousScores != null && trackPosition > firstObjectTime) {
|
||||||
|
// show scoreboard if selected, and always in break
|
||||||
|
if (scoreboardVisible || breakTime > 0) {
|
||||||
|
currentScoreboardAlpha += 1f / SCOREBOARD_FADE_IN_TIME * delta;
|
||||||
|
if (currentScoreboardAlpha > 1f)
|
||||||
|
currentScoreboardAlpha = 1f;
|
||||||
|
} else {
|
||||||
|
currentScoreboardAlpha -= 1f / SCOREBOARD_FADE_IN_TIME * delta;
|
||||||
|
if (currentScoreboardAlpha < 0f)
|
||||||
|
currentScoreboardAlpha = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data.updateDisplays(delta);
|
data.updateDisplays(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1057,6 +1133,9 @@ public class Game extends BasicGameState {
|
||||||
case Input.KEY_F12:
|
case Input.KEY_F12:
|
||||||
Utils.takeScreenShot();
|
Utils.takeScreenShot();
|
||||||
break;
|
break;
|
||||||
|
case Input.KEY_TAB:
|
||||||
|
scoreboardVisible = !scoreboardVisible;
|
||||||
|
break;
|
||||||
case Input.KEY_M:
|
case Input.KEY_M:
|
||||||
if (Dancer.mirror) {
|
if (Dancer.mirror) {
|
||||||
mirrorTo = objectIndex;
|
mirrorTo = objectIndex;
|
||||||
|
@ -1407,6 +1486,14 @@ public class Game extends BasicGameState {
|
||||||
leadInTime = beatmap.audioLeadIn + approachTime;
|
leadInTime = beatmap.audioLeadIn + approachTime;
|
||||||
restart = Restart.FALSE;
|
restart = Restart.FALSE;
|
||||||
|
|
||||||
|
// fetch previous scores
|
||||||
|
previousScores = ScoreDB.getMapScoresExcluding(beatmap, replay == null ? null : replay.getReplayFilename());
|
||||||
|
lastRankUpdateTime = -1000;
|
||||||
|
if (previousScores != null)
|
||||||
|
currentRank = previousScores.length;
|
||||||
|
scoreboardVisible = true;
|
||||||
|
currentScoreboardAlpha = 0f;
|
||||||
|
|
||||||
// needs to play before setting position to resume without lag later
|
// needs to play before setting position to resume without lag later
|
||||||
MusicController.play(false);
|
MusicController.play(false);
|
||||||
MusicController.setPosition(0);
|
MusicController.setPosition(0);
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class Colors {
|
||||||
DARK_GRAY = new Color(0.3f, 0.3f, 0.3f, 1f),
|
DARK_GRAY = new Color(0.3f, 0.3f, 0.3f, 1f),
|
||||||
RED_HIGHLIGHT = new Color(246, 154, 161),
|
RED_HIGHLIGHT = new Color(246, 154, 161),
|
||||||
BLUE_HIGHLIGHT = new Color(173, 216, 230),
|
BLUE_HIGHLIGHT = new Color(173, 216, 230),
|
||||||
|
BLUE_SCOREBOARD = new Color(133, 208, 212),
|
||||||
BLACK_BG_NORMAL = new Color(0, 0, 0, 0.25f),
|
BLACK_BG_NORMAL = new Color(0, 0, 0, 0.25f),
|
||||||
BLACK_BG_HOVER = new Color(0, 0, 0, 0.5f),
|
BLACK_BG_HOVER = new Color(0, 0, 0, 0.5f),
|
||||||
BLACK_BG_FOCUS = new Color(0, 0, 0, 0.75f);
|
BLACK_BG_FOCUS = new Color(0, 0, 0, 0.75f);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user