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/
|
||||
/SongPacks/
|
||||
/Songs/
|
||||
/Temp/
|
||||
/.opsu.log
|
||||
/.opsu.cfg
|
||||
/.opsu.db*
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package itdelatrisu.opsu;
|
||||
|
||||
import itdelatrisu.opsu.audio.MusicController;
|
||||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
||||
|
@ -61,6 +62,7 @@ public class Container extends AppGameContainer {
|
|||
public void start() throws SlickException {
|
||||
try {
|
||||
setup();
|
||||
ErrorHandler.setGlString();
|
||||
getDelta();
|
||||
while (running())
|
||||
gameLoop();
|
||||
|
@ -131,6 +133,9 @@ public class Container extends AppGameContainer {
|
|||
// prevent loading tracks from re-initializing OpenAL
|
||||
MusicController.reset();
|
||||
|
||||
// stop any playing track
|
||||
SoundController.stopTrack();
|
||||
|
||||
// reset BeatmapSetList data
|
||||
if (BeatmapSetList.get() != null)
|
||||
BeatmapSetList.get().reset();
|
||||
|
@ -142,6 +147,9 @@ public class Container extends AppGameContainer {
|
|||
if (!Options.isWatchServiceEnabled())
|
||||
BeatmapWatchService.destroy();
|
||||
BeatmapWatchService.removeListeners();
|
||||
|
||||
// delete temporary directory
|
||||
Utils.deleteDirectory(Options.TEMP_DIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -33,6 +33,7 @@ import javax.swing.JScrollPane;
|
|||
import javax.swing.JTextArea;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.newdawn.slick.util.Log;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
|
||||
|
@ -73,9 +74,23 @@ public class ErrorHandler {
|
|||
message = { desc, scroll },
|
||||
messageReport = { descReport, scroll };
|
||||
|
||||
/** OpenGL string (if any). */
|
||||
private static String glString = null;
|
||||
|
||||
// This class should not be instantiated.
|
||||
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.
|
||||
* @param error a description of the error
|
||||
|
@ -197,6 +212,11 @@ public class ErrorHandler {
|
|||
sb.append("**JRE:** ");
|
||||
sb.append(System.getProperty("java.version"));
|
||||
sb.append('\n');
|
||||
if (glString != null) {
|
||||
sb.append("**OpenGL Version:** ");
|
||||
sb.append(glString);
|
||||
sb.append('\n');
|
||||
}
|
||||
if (error != null) {
|
||||
sb.append("**Error:** `");
|
||||
sb.append(error);
|
||||
|
|
|
@ -391,7 +391,7 @@ public class GameData {
|
|||
if (hitResultList != null) {
|
||||
for (HitObjectResult hitResult : hitResultList) {
|
||||
if (hitResult.curve != null)
|
||||
hitResult.curve.discardCache();
|
||||
hitResult.curve.discardGeometry();
|
||||
}
|
||||
}
|
||||
hitResultList = new LinkedBlockingDeque<HitObjectResult>();
|
||||
|
@ -964,7 +964,7 @@ public class GameData {
|
|||
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME);
|
||||
} else {
|
||||
if (hitResult.curve != null)
|
||||
hitResult.curve.discardCache();
|
||||
hitResult.curve.discardGeometry();
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
@ -1436,34 +1436,45 @@ public class GameData {
|
|||
* (i.e. this will not overwrite existing data).
|
||||
* @param beatmap the beatmap
|
||||
* @return the ScoreData object
|
||||
* @see #getCurrentScoreData(Beatmap, boolean)
|
||||
*/
|
||||
public ScoreData getScoreData(Beatmap beatmap) {
|
||||
if (scoreData != null)
|
||||
return scoreData;
|
||||
|
||||
scoreData = new ScoreData();
|
||||
scoreData.timestamp = System.currentTimeMillis() / 1000L;
|
||||
scoreData.MID = beatmap.beatmapID;
|
||||
scoreData.MSID = beatmap.beatmapSetID;
|
||||
scoreData.title = beatmap.title;
|
||||
scoreData.artist = beatmap.artist;
|
||||
scoreData.creator = beatmap.creator;
|
||||
scoreData.version = beatmap.version;
|
||||
scoreData.hit300 = hitResultCount[HIT_300];
|
||||
scoreData.hit100 = hitResultCount[HIT_100];
|
||||
scoreData.hit50 = hitResultCount[HIT_50];
|
||||
scoreData.geki = hitResultCount[HIT_300G];
|
||||
scoreData.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K];
|
||||
scoreData.miss = hitResultCount[HIT_MISS];
|
||||
scoreData.score = score;
|
||||
scoreData.combo = comboMax;
|
||||
scoreData.perfect = (comboMax == fullObjectCount);
|
||||
scoreData.mods = GameMod.getModState();
|
||||
scoreData.replayString = (replay == null) ? null : replay.getReplayFilename();
|
||||
scoreData.playerName = null; // TODO
|
||||
if (scoreData == null)
|
||||
scoreData = getCurrentScoreData(beatmap, false);
|
||||
return scoreData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ScoreData object encapsulating all current game data.
|
||||
* @param beatmap the beatmap
|
||||
* @param slidingScore if true, use the display score (might not be actual score)
|
||||
* @return the ScoreData object
|
||||
* @see #getScoreData(Beatmap)
|
||||
*/
|
||||
public ScoreData getCurrentScoreData(Beatmap beatmap, boolean slidingScore) {
|
||||
ScoreData sd = new ScoreData();
|
||||
sd.timestamp = System.currentTimeMillis() / 1000L;
|
||||
sd.MID = beatmap.beatmapID;
|
||||
sd.MSID = beatmap.beatmapSetID;
|
||||
sd.title = beatmap.title;
|
||||
sd.artist = beatmap.artist;
|
||||
sd.creator = beatmap.creator;
|
||||
sd.version = beatmap.version;
|
||||
sd.hit300 = hitResultCount[HIT_300];
|
||||
sd.hit100 = hitResultCount[HIT_100];
|
||||
sd.hit50 = hitResultCount[HIT_50];
|
||||
sd.geki = hitResultCount[HIT_300G];
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Replay object encapsulating all game data.
|
||||
* If a replay already exists and frames is null, the existing object will be returned.
|
||||
|
|
|
@ -107,10 +107,13 @@ public class Opsu extends StateBasedGame {
|
|||
} catch (FileNotFoundException e) {
|
||||
Log.error(e);
|
||||
}
|
||||
|
||||
// set default exception handler
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
ErrorHandler.error("** Uncaught Exception! **", e, true);
|
||||
System.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -124,16 +127,21 @@ public class Opsu extends StateBasedGame {
|
|||
} catch (UnknownHostException e) {
|
||||
// shouldn't happen
|
||||
} catch (IOException e) {
|
||||
ErrorHandler.error(String.format(
|
||||
"opsu! could not be launched for one of these reasons:\n" +
|
||||
"- An instance of opsu! is already running.\n" +
|
||||
"- Another program is bound to port %d. " +
|
||||
"You can change the port opsu! uses by editing the \"Port\" field in the configuration file.",
|
||||
Options.getPort()), null, false);
|
||||
System.exit(1);
|
||||
errorAndExit(
|
||||
null,
|
||||
String.format(
|
||||
"opsu! could not be launched for one of these reasons:\n" +
|
||||
"- An instance of opsu! is already running.\n" +
|
||||
"- Another program is bound to port %d. " +
|
||||
"You can change the port opsu! uses by editing the \"Port\" field in the configuration file.",
|
||||
Options.getPort()
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// load natives
|
||||
File nativeDir;
|
||||
if (!Utils.isJarRunning() && (
|
||||
(nativeDir = new File("./target/natives/")).isDirectory() ||
|
||||
|
@ -166,7 +174,7 @@ public class Opsu extends StateBasedGame {
|
|||
try {
|
||||
DBController.init();
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
errorAndExit(e, "The databases could not be initialized.");
|
||||
errorAndExit(e, "The databases could not be initialized.", true);
|
||||
}
|
||||
|
||||
// check if just updated
|
||||
|
@ -213,7 +221,7 @@ public class Opsu extends StateBasedGame {
|
|||
}
|
||||
}
|
||||
} 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 {
|
||||
if (id == STATE_GAME) {
|
||||
MusicController.pause();
|
||||
MusicController.setPitch(1.0f);
|
||||
MusicController.resume();
|
||||
} else
|
||||
songMenu.resetTrackOnLoad();
|
||||
|
@ -278,15 +287,16 @@ public class Opsu extends StateBasedGame {
|
|||
* Throws an error and exits the application with the given message.
|
||||
* @param e the exception that caused the crash
|
||||
* @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 '!'
|
||||
// http://bugs.java.com/view_bug.do?bug_id=4523159
|
||||
if (Utils.isJarRunning() && Utils.getRunningDirectory() != null &&
|
||||
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);
|
||||
else
|
||||
ErrorHandler.error(message, e, true);
|
||||
ErrorHandler.error(message, e, report);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,9 @@ public class Options {
|
|||
/** Directory where natives are unpacked. */
|
||||
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. */
|
||||
public static final String FONT_NAME = "DroidSansFallback.ttf";
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.sql.SQLException;
|
|||
import java.text.NumberFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Graphics;
|
||||
|
@ -328,6 +329,69 @@ public class ScoreData implements Comparable<ScoreData> {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -23,7 +23,6 @@ import itdelatrisu.opsu.audio.SoundEffect;
|
|||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.downloads.Download;
|
||||
import itdelatrisu.opsu.downloads.DownloadNode;
|
||||
import itdelatrisu.opsu.objects.Circle;
|
||||
import itdelatrisu.opsu.replay.PlaybackSpeed;
|
||||
import itdelatrisu.opsu.ui.Fonts;
|
||||
import itdelatrisu.opsu.ui.UI;
|
||||
|
@ -44,6 +43,7 @@ import java.net.URL;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
@ -51,6 +51,10 @@ import java.util.Scanner;
|
|||
import java.util.jar.JarFile;
|
||||
|
||||
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.JSONException;
|
||||
|
@ -349,7 +353,7 @@ public class Utils {
|
|||
* deletes the directory itself.
|
||||
* @param dir the directory to delete
|
||||
*/
|
||||
private static void deleteDirectory(File dir) {
|
||||
public static void deleteDirectory(File dir) {
|
||||
if (dir == null || !dir.isDirectory())
|
||||
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) {
|
||||
if (x < Options.width / 2d) {
|
||||
return y < Options.height / 2d ? 2 : 3;
|
||||
|
|
|
@ -22,6 +22,9 @@ import itdelatrisu.opsu.ErrorHandler;
|
|||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.audio.HitSound.SampleSet;
|
||||
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.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.
|
||||
* @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 listener the line listener
|
||||
* @return the MultiClip being played
|
||||
* @return true if playing, false otherwise
|
||||
* @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();
|
||||
try {
|
||||
AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);
|
||||
currentTrack = loadClip(url.getFile(), audioIn, isMP3);
|
||||
playClip(currentTrack, Options.getMusicVolume() * Options.getMasterVolume(), listener);
|
||||
return currentTrack;
|
||||
} catch (Exception e) {
|
||||
throw new SlickException(String.format("Failed to load clip '%s'.", url.getFile(), e));
|
||||
|
||||
// download new track
|
||||
File dir = Options.TEMP_DIR;
|
||||
if (!dir.isDirectory())
|
||||
dir.mkdir();
|
||||
String filename = String.format("%s.%s", name, isMP3 ? "mp3" : "wav");
|
||||
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
|
||||
*/
|
||||
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)
|
||||
return null;
|
||||
|
||||
|
@ -317,7 +327,11 @@ public class ScoreDB {
|
|||
ResultSet rs = selectMapStmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
ScoreData s = new ScoreData(rs);
|
||||
list.add(s);
|
||||
if (s.replayString != null && s.replayString.equals(exclude)) {
|
||||
// don't return this score
|
||||
} else {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
rs.close();
|
||||
} catch (SQLException e) {
|
||||
|
|
|
@ -167,12 +167,13 @@ public class Download {
|
|||
|
||||
/**
|
||||
* 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)
|
||||
return;
|
||||
return null;
|
||||
|
||||
new Thread() {
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// open connection
|
||||
|
@ -274,7 +275,9 @@ public class Download {
|
|||
listener.error();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
};
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,8 +27,9 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
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/
|
||||
|
@ -38,13 +39,10 @@ public class MengSkyServer extends DownloadServer {
|
|||
private static final String SERVER_NAME = "MengSky";
|
||||
|
||||
/** 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} */
|
||||
private static final String SEARCH_URL = "http://osu.mengsky.net/index.php?search_keywords=%s";
|
||||
|
||||
/** Formatted home URL: {@code page} */
|
||||
private static final String HOME_URL = "http://osu.mengsky.net/index.php?next=1&page=%d";
|
||||
/** Formatted search URL: {@code query,page,unranked,approved,qualified} */
|
||||
private static final String SEARCH_URL = "http://osu.mengsky.net/api/beatmapinfo?query=%s&page=%d&ranked=1&unrank=%d&approved=%d&qualified=%d";
|
||||
|
||||
/** Maximum beatmaps displayed per page. */
|
||||
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 {
|
||||
DownloadNode[] nodes = null;
|
||||
try {
|
||||
// read HTML
|
||||
String search;
|
||||
boolean isSearch;
|
||||
if (query.isEmpty()) {
|
||||
isSearch = false;
|
||||
search = String.format(HOME_URL, page - 1);
|
||||
} else {
|
||||
isSearch = true;
|
||||
search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"));
|
||||
// read JSON
|
||||
int rankedOnlyFlag = rankedOnly ? 0 : 1;
|
||||
String search = String.format(
|
||||
SEARCH_URL, URLEncoder.encode(query, "UTF-8"), page,
|
||||
rankedOnlyFlag, rankedOnlyFlag, rankedOnlyFlag
|
||||
);
|
||||
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
||||
|
||||
// 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
|
||||
if (isSearch)
|
||||
this.totalResults = nodes.length;
|
||||
else {
|
||||
int resultCount = nodes.length + (page - 1) * PAGE_LIMIT;
|
||||
if (divCount == PAGE_LIMIT)
|
||||
resultCount++;
|
||||
this.totalResults = resultCount;
|
||||
}
|
||||
int pageTotal = json.getInt("pageTotal");
|
||||
int resultCount = nodes.length;
|
||||
if (page == pageTotal)
|
||||
resultCount = nodes.length + (pageTotal - 1) * PAGE_LIMIT;
|
||||
else
|
||||
resultCount = 1 + (pageTotal - 1) * PAGE_LIMIT;
|
||||
this.totalResults = resultCount;
|
||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||
}
|
||||
|
@ -195,7 +107,7 @@ public class MengSkyServer extends DownloadServer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int minQueryLength() { return 2; }
|
||||
public int minQueryLength() { return 1; }
|
||||
|
||||
@Override
|
||||
public int totalResults() { return totalResults; }
|
||||
|
|
|
@ -93,6 +93,8 @@ public class YaSOnlineServer extends DownloadServer {
|
|||
*/
|
||||
private String getDownloadURLFromMapData(int beatmapSetID) throws IOException {
|
||||
try {
|
||||
Utils.setSSLCertValidation(false);
|
||||
|
||||
// read JSON
|
||||
String search = String.format(DOWNLOAD_URL, beatmapSetID);
|
||||
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
||||
|
@ -114,6 +116,8 @@ public class YaSOnlineServer extends DownloadServer {
|
|||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true);
|
||||
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 {
|
||||
DownloadNode[] nodes = null;
|
||||
try {
|
||||
Utils.setSSLCertValidation(false);
|
||||
|
||||
// read JSON
|
||||
String search;
|
||||
boolean isSearch;
|
||||
|
@ -181,6 +187,8 @@ public class YaSOnlineServer extends DownloadServer {
|
|||
this.totalResults = maxServerID;
|
||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||
} finally {
|
||||
Utils.setSSLCertValidation(true);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
|
|
@ -101,12 +101,12 @@ public abstract class Curve {
|
|||
Curve.borderColor = borderColor;
|
||||
|
||||
ContextCapabilities capabilities = GLContext.getCapabilities();
|
||||
mmsliderSupported = capabilities.GL_EXT_framebuffer_object;
|
||||
mmsliderSupported = capabilities.OpenGL20;
|
||||
if (mmsliderSupported)
|
||||
CurveRenderState.init(width, height, circleDiameter);
|
||||
else {
|
||||
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).
|
||||
*/
|
||||
public void discardCache() {
|
||||
public void discardGeometry() {
|
||||
if (renderState != null)
|
||||
renderState.discardCache();
|
||||
renderState.discardGeometry();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,6 @@ import itdelatrisu.opsu.ui.UI;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.sound.sampled.LineEvent;
|
||||
import javax.sound.sampled.LineListener;
|
||||
|
@ -663,15 +661,18 @@ public class DownloadsMenu extends BasicGameState {
|
|||
SoundController.stopTrack();
|
||||
} else {
|
||||
// play preview
|
||||
try {
|
||||
final URL url = new URL(serverMenu.getSelectedItem().getPreviewURL(node.getID()));
|
||||
MusicController.pause();
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
previewID = -1;
|
||||
SoundController.playTrack(url, true, new LineListener() {
|
||||
final String url = serverMenu.getSelectedItem().getPreviewURL(node.getID());
|
||||
MusicController.pause();
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
previewID = -1;
|
||||
boolean playing = SoundController.playTrack(
|
||||
url,
|
||||
Integer.toString(node.getID()),
|
||||
true,
|
||||
new LineListener() {
|
||||
@Override
|
||||
public void update(LineEvent event) {
|
||||
if (event.getType() == LineEvent.Type.STOP) {
|
||||
|
@ -681,18 +682,16 @@ public class DownloadsMenu extends BasicGameState {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
if (playing)
|
||||
previewID = node.getID();
|
||||
} catch (SlickException e) {
|
||||
UI.sendBarNotification("Failed to load track preview. See log for details.");
|
||||
Log.error(e);
|
||||
}
|
||||
} catch (SlickException e) {
|
||||
UI.sendBarNotification("Failed to load track preview. See log for details.");
|
||||
Log.error(e);
|
||||
}
|
||||
}.start();
|
||||
} catch (MalformedURLException e) {
|
||||
UI.sendBarNotification("Could not load track preview (bad URL).");
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -100,6 +100,12 @@ public class Game extends BasicGameState {
|
|||
/** Maximum rotation, in degrees, over fade out upon death. */
|
||||
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. */
|
||||
private static final int SKIP_OFFSET = 2000;
|
||||
|
||||
|
@ -265,6 +271,21 @@ public class Game extends BasicGameState {
|
|||
private float epiImgY;
|
||||
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. */
|
||||
private static final Color
|
||||
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
|
||||
|
@ -603,6 +624,46 @@ public class Game extends BasicGameState {
|
|||
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())
|
||||
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
||||
|
||||
|
@ -677,6 +738,7 @@ public class Game extends BasicGameState {
|
|||
if (isReplay || GameMod.AUTO.isActive())
|
||||
playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
|
||||
int trackPosition = MusicController.getPosition();
|
||||
int firstObjectTime = beatmap.objects[0].getTime();
|
||||
|
||||
// returning from pause screen: must click previous mouse position
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1057,6 +1133,9 @@ public class Game extends BasicGameState {
|
|||
case Input.KEY_F12:
|
||||
Utils.takeScreenShot();
|
||||
break;
|
||||
case Input.KEY_TAB:
|
||||
scoreboardVisible = !scoreboardVisible;
|
||||
break;
|
||||
case Input.KEY_M:
|
||||
if (Dancer.mirror) {
|
||||
mirrorTo = objectIndex;
|
||||
|
@ -1407,6 +1486,14 @@ public class Game extends BasicGameState {
|
|||
leadInTime = beatmap.audioLeadIn + approachTime;
|
||||
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
|
||||
MusicController.play(false);
|
||||
MusicController.setPosition(0);
|
||||
|
|
|
@ -42,6 +42,7 @@ public class Colors {
|
|||
DARK_GRAY = new Color(0.3f, 0.3f, 0.3f, 1f),
|
||||
RED_HIGHLIGHT = new Color(246, 154, 161),
|
||||
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_HOVER = new Color(0, 0, 0, 0.5f),
|
||||
BLACK_BG_FOCUS = new Color(0, 0, 0, 0.75f);
|
||||
|
|
Loading…
Reference in New Issue
Block a user