Updates and fixes from fluddokt/opsu@76778f8 and fluddokt/opsu@cf321e9.
- Rotate slider ball image along the curve. - Show download speed and time remaining when hovering over the download info button. - Maintain aspect ratio of beatmap background image during gameplay. - Clear beatmap background image cache after reaching OsuFile.MAX_CACHE_SIZE. - Multiply hit circle size by OsuHitObject.getXMultiplier(). - Scale MENU_BUTTON_BG based on SongMenu.MAX_SONG_BUTTONS. - Fixed download search TextField retaining focus even after leaving the downloads menu. - Include Unicode title/artist strings in searches. - Parse x,y beatmap coordinates as floats (instead of integers), and only read Kiai time if present. - Added 1600x1200 resolution. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
8d00a0f81e
commit
f56c02864b
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu;
|
package itdelatrisu.opsu;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.states.SongMenu;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -381,7 +383,7 @@ public enum GameImage {
|
||||||
MENU_BUTTON_BG ("menu-button-background", "png", false, false) {
|
MENU_BUTTON_BG ("menu-button-background", "png", false, false) {
|
||||||
@Override
|
@Override
|
||||||
protected Image process_sub(Image img, int w, int h) {
|
protected Image process_sub(Image img, int w, int h) {
|
||||||
return img.getScaledCopy(w / 2, h / 6);
|
return img.getScaledCopy(w / 2, h / SongMenu.MAX_SONG_BUTTONS);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MENU_TAB ("selection-tab", "png", false, false) {
|
MENU_TAB ("selection-tab", "png", false, false) {
|
||||||
|
|
|
@ -393,6 +393,7 @@ public class Options {
|
||||||
RES_1440_900 (1440, 900),
|
RES_1440_900 (1440, 900),
|
||||||
RES_1600_900 (1600, 900),
|
RES_1600_900 (1600, 900),
|
||||||
RES_1680_1050 (1680, 1050),
|
RES_1680_1050 (1680, 1050),
|
||||||
|
RES_1600_1200 (1600, 1200),
|
||||||
RES_1920_1080 (1920, 1080),
|
RES_1920_1080 (1920, 1080),
|
||||||
RES_1920_1200 (1920, 1200),
|
RES_1920_1200 (1920, 1200),
|
||||||
RES_2560_1440 (2560, 1440),
|
RES_2560_1440 (2560, 1440),
|
||||||
|
|
|
@ -34,6 +34,9 @@ public class OsuFile implements Comparable<OsuFile> {
|
||||||
/** Map of all loaded background images. */
|
/** Map of all loaded background images. */
|
||||||
private static HashMap<OsuFile, Image> bgImageMap = new HashMap<OsuFile, Image>();
|
private static HashMap<OsuFile, Image> bgImageMap = new HashMap<OsuFile, Image>();
|
||||||
|
|
||||||
|
/** Maximum number of cached images before all get erased. */
|
||||||
|
private static final int MAX_CACHE_SIZE = 10;
|
||||||
|
|
||||||
/** The OSU File object associated with this OsuFile. */
|
/** The OSU File object associated with this OsuFile. */
|
||||||
private File file;
|
private File file;
|
||||||
|
|
||||||
|
@ -250,19 +253,34 @@ public class OsuFile implements Comparable<OsuFile> {
|
||||||
* @param width the container width
|
* @param width the container width
|
||||||
* @param height the container height
|
* @param height the container height
|
||||||
* @param alpha the alpha value
|
* @param alpha the alpha value
|
||||||
|
* @param stretch if true, stretch to screen dimensions; otherwise, maintain aspect ratio
|
||||||
* @return true if successful, false if any errors were produced
|
* @return true if successful, false if any errors were produced
|
||||||
*/
|
*/
|
||||||
public boolean drawBG(int width, int height, float alpha) {
|
public boolean drawBG(int width, int height, float alpha, boolean stretch) {
|
||||||
if (bg == null)
|
if (bg == null)
|
||||||
return false;
|
return false;
|
||||||
try {
|
try {
|
||||||
Image bgImage = bgImageMap.get(this);
|
Image bgImage = bgImageMap.get(this);
|
||||||
if (bgImage == null) {
|
if (bgImage == null) {
|
||||||
bgImage = new Image(bg).getScaledCopy(width, height);
|
bgImage = new Image(bg);
|
||||||
|
if (bgImageMap.size() > MAX_CACHE_SIZE)
|
||||||
|
clearImageCache();
|
||||||
bgImageMap.put(this, bgImage);
|
bgImageMap.put(this, bgImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fit image to screen
|
||||||
|
int swidth = width;
|
||||||
|
int sheight = height;
|
||||||
|
if (!stretch) {
|
||||||
|
if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y
|
||||||
|
sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth());
|
||||||
|
else
|
||||||
|
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
|
||||||
|
}
|
||||||
|
bgImage = bgImage.getScaledCopy(swidth, sheight);
|
||||||
|
|
||||||
bgImage.setAlpha(alpha);
|
bgImage.setAlpha(alpha);
|
||||||
bgImage.draw();
|
bgImage.drawCentered(width / 2, height / 2);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.warn(String.format("Failed to get background image '%s'.", bg), e);
|
Log.warn(String.format("Failed to get background image '%s'.", bg), e);
|
||||||
bg = null; // don't try to load the file again until a restart
|
bg = null; // don't try to load the file again until a restart
|
||||||
|
|
|
@ -150,7 +150,9 @@ public class OsuGroupNode {
|
||||||
|
|
||||||
// search: title, artist, creator, source, version, tags (first OsuFile)
|
// search: title, artist, creator, source, version, tags (first OsuFile)
|
||||||
if (osu.title.toLowerCase().contains(query) ||
|
if (osu.title.toLowerCase().contains(query) ||
|
||||||
|
osu.titleUnicode.toLowerCase().contains(query) ||
|
||||||
osu.artist.toLowerCase().contains(query) ||
|
osu.artist.toLowerCase().contains(query) ||
|
||||||
|
osu.artistUnicode.toLowerCase().contains(query) ||
|
||||||
osu.creator.toLowerCase().contains(query) ||
|
osu.creator.toLowerCase().contains(query) ||
|
||||||
osu.source.toLowerCase().contains(query) ||
|
osu.source.toLowerCase().contains(query) ||
|
||||||
osu.version.toLowerCase().contains(query) ||
|
osu.version.toLowerCase().contains(query) ||
|
||||||
|
|
|
@ -148,8 +148,8 @@ public class OsuHitObject {
|
||||||
String tokens[] = line.split(",");
|
String tokens[] = line.split(",");
|
||||||
|
|
||||||
// common fields
|
// common fields
|
||||||
this.x = Integer.parseInt(tokens[0]) * xMultiplier + xOffset;
|
this.x = Float.parseFloat(tokens[0]) * xMultiplier + xOffset;
|
||||||
this.y = Integer.parseInt(tokens[1]) * yMultiplier + yOffset;
|
this.y = Float.parseFloat(tokens[1]) * yMultiplier + yOffset;
|
||||||
this.time = Integer.parseInt(tokens[2]);
|
this.time = Integer.parseInt(tokens[2]);
|
||||||
this.type = Integer.parseInt(tokens[3]);
|
this.type = Integer.parseInt(tokens[3]);
|
||||||
this.hitSound = Byte.parseByte(tokens[4]);
|
this.hitSound = Byte.parseByte(tokens[4]);
|
||||||
|
|
|
@ -65,7 +65,8 @@ public class OsuTimingPoint {
|
||||||
this.sampleTypeCustom = Byte.parseByte(tokens[4]);
|
this.sampleTypeCustom = Byte.parseByte(tokens[4]);
|
||||||
this.sampleVolume = Integer.parseInt(tokens[5]);
|
this.sampleVolume = Integer.parseInt(tokens[5]);
|
||||||
// this.inherited = (Integer.parseInt(tokens[6]) == 1);
|
// this.inherited = (Integer.parseInt(tokens[6]) == 1);
|
||||||
this.kiai = (Integer.parseInt(tokens[7]) == 1);
|
if (tokens.length > 7)
|
||||||
|
this.kiai = (Integer.parseInt(tokens[7]) == 1);
|
||||||
} catch (ArrayIndexOutOfBoundsException e) {
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||||||
Log.debug(String.format("Error parsing timing point: '%s'", line));
|
Log.debug(String.format("Error parsing timing point: '%s'", line));
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,9 @@ public class Download {
|
||||||
/** Read timeout, in ms. */
|
/** Read timeout, in ms. */
|
||||||
public static final int READ_TIMEOUT = 10000;
|
public static final int READ_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
/** Time between download speed and ETA updates, in ms. */
|
||||||
|
private static final int UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
/** Download statuses. */
|
/** Download statuses. */
|
||||||
public enum Status {
|
public enum Status {
|
||||||
WAITING ("Waiting"),
|
WAITING ("Waiting"),
|
||||||
|
@ -90,6 +93,18 @@ public class Download {
|
||||||
/** The download status. */
|
/** The download status. */
|
||||||
private Status status = Status.WAITING;
|
private Status status = Status.WAITING;
|
||||||
|
|
||||||
|
/** Time when lastReadSoFar was updated. */
|
||||||
|
private long lastReadSoFarTime = -1;
|
||||||
|
|
||||||
|
/** Last readSoFar amount. */
|
||||||
|
private long lastReadSoFar = -1;
|
||||||
|
|
||||||
|
/** Last calculated download speed string. */
|
||||||
|
private String lastDownloadSpeed;
|
||||||
|
|
||||||
|
/** Last calculated ETA string. */
|
||||||
|
private String lastTimeRemaining;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param remoteURL the download URL
|
* @param remoteURL the download URL
|
||||||
|
@ -234,6 +249,60 @@ public class Download {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last calculated download speed, or null if not downloading.
|
||||||
|
*/
|
||||||
|
public String getDownloadSpeed() {
|
||||||
|
updateReadSoFar();
|
||||||
|
return lastDownloadSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last calculated ETA, or null if not downloading.
|
||||||
|
*/
|
||||||
|
public String getTimeRemaining() {
|
||||||
|
updateReadSoFar();
|
||||||
|
return lastTimeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last readSoFar and related fields.
|
||||||
|
*/
|
||||||
|
private void updateReadSoFar() {
|
||||||
|
// only update while downloading
|
||||||
|
if (status != Status.DOWNLOADING) {
|
||||||
|
this.lastDownloadSpeed = null;
|
||||||
|
this.lastTimeRemaining = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update download speed and ETA
|
||||||
|
if (System.currentTimeMillis() > lastReadSoFarTime + UPDATE_INTERVAL) {
|
||||||
|
long readSoFar = readSoFar();
|
||||||
|
long readSoFarTime = System.currentTimeMillis();
|
||||||
|
long dlspeed = (readSoFar - lastReadSoFar) * 1000 / (readSoFarTime - lastReadSoFarTime);
|
||||||
|
if (dlspeed > 0) {
|
||||||
|
this.lastDownloadSpeed = String.format("%s/s", Utils.bytesToString(dlspeed));
|
||||||
|
long t = (contentLength - readSoFar) / dlspeed;
|
||||||
|
if (t >= 3600)
|
||||||
|
this.lastTimeRemaining = String.format("%dh%dm%ds", t / 3600, (t / 60) % 60, t % 60);
|
||||||
|
else
|
||||||
|
this.lastTimeRemaining = String.format("%dm%ds", t / 60, t % 60);
|
||||||
|
} else {
|
||||||
|
this.lastDownloadSpeed = String.format("%s/s", Utils.bytesToString(0));
|
||||||
|
this.lastTimeRemaining = "?";
|
||||||
|
}
|
||||||
|
this.lastReadSoFarTime = readSoFarTime;
|
||||||
|
this.lastReadSoFar = readSoFar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first call
|
||||||
|
else if (lastReadSoFarTime <= 0) {
|
||||||
|
this.lastReadSoFar = readSoFar();
|
||||||
|
this.lastReadSoFarTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the download, if running.
|
* Cancels the download, if running.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -348,9 +348,13 @@ public class DownloadNode {
|
||||||
info = status.getName();
|
info = status.getName();
|
||||||
else if (status == Download.Status.WAITING)
|
else if (status == Download.Status.WAITING)
|
||||||
info = String.format("%s...", status.getName());
|
info = String.format("%s...", status.getName());
|
||||||
else
|
else {
|
||||||
info = String.format("%s: %.1f%% (%s/%s)", status.getName(), progress,
|
if (hover)
|
||||||
Utils.bytesToString(download.readSoFar()), Utils.bytesToString(download.contentLength()));
|
info = String.format("%s: %s left (%s)", status.getName(), download.getTimeRemaining(), download.getDownloadSpeed());
|
||||||
|
else
|
||||||
|
info = String.format("%s: %.1f%% (%s/%s)", status.getName(), progress,
|
||||||
|
Utils.bytesToString(download.readSoFar()), Utils.bytesToString(download.contentLength()));
|
||||||
|
}
|
||||||
Utils.FONT_BOLD.drawString(textX, y + marginY, getTitle(), Color.white);
|
Utils.FONT_BOLD.drawString(textX, y + marginY, getTitle(), Color.white);
|
||||||
Utils.FONT_DEFAULT.drawString(textX, y + marginY + Utils.FONT_BOLD.getLineHeight(), info, Color.white);
|
Utils.FONT_DEFAULT.drawString(textX, y + marginY + Utils.FONT_BOLD.getLineHeight(), info, Color.white);
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class Circle implements HitObject {
|
||||||
*/
|
*/
|
||||||
public static void init(GameContainer container, float circleSize) {
|
public static void init(GameContainer container, float circleSize) {
|
||||||
int diameter = (int) (96 - (circleSize * 8));
|
int diameter = (int) (96 - (circleSize * 8));
|
||||||
diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480)
|
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
||||||
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
|
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
|
||||||
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
|
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
|
||||||
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter));
|
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter));
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class Slider implements HitObject {
|
||||||
*/
|
*/
|
||||||
public static void init(GameContainer container, float circleSize, OsuFile osu) {
|
public static void init(GameContainer container, float circleSize, OsuFile osu) {
|
||||||
int diameter = (int) (96 - (circleSize * 8));
|
int diameter = (int) (96 - (circleSize * 8));
|
||||||
diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480)
|
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
||||||
|
|
||||||
// slider ball
|
// slider ball
|
||||||
Image[] sliderBallImages;
|
Image[] sliderBallImages;
|
||||||
|
@ -211,9 +211,13 @@ public class Slider implements HitObject {
|
||||||
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
|
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
|
||||||
} else {
|
} else {
|
||||||
float[] c = curve.pointAt(getT(trackPosition, false));
|
float[] c = curve.pointAt(getT(trackPosition, false));
|
||||||
|
float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f);
|
||||||
|
|
||||||
// slider ball
|
// slider ball
|
||||||
Utils.drawCentered(sliderBall, c[0], c[1]);
|
Image sliderBallFrame = sliderBall.getCurrentFrame();
|
||||||
|
float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI);
|
||||||
|
sliderBallFrame.setRotation(angle);
|
||||||
|
sliderBallFrame.drawCentered(c[0], c[1]);
|
||||||
|
|
||||||
// follow circle
|
// follow circle
|
||||||
if (followCircleActive)
|
if (followCircleActive)
|
||||||
|
|
|
@ -660,6 +660,12 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
pageDir = Page.RESET;
|
pageDir = Page.RESET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leave(GameContainer container, StateBasedGame game)
|
||||||
|
throws SlickException {
|
||||||
|
search.setFocus(false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the search timer, but respects the minimum request interval.
|
* Resets the search timer, but respects the minimum request interval.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -173,7 +173,7 @@ public class Game extends BasicGameState {
|
||||||
// background
|
// background
|
||||||
g.setBackground(Color.black);
|
g.setBackground(Color.black);
|
||||||
float dimLevel = Options.getBackgroundDim();
|
float dimLevel = Options.getBackgroundDim();
|
||||||
if (Options.isDefaultPlayfieldForced() || !osu.drawBG(width, height, dimLevel)) {
|
if (Options.isDefaultPlayfieldForced() || !osu.drawBG(width, height, dimLevel, false)) {
|
||||||
Image playfield = GameImage.PLAYFIELD.getImage();
|
Image playfield = GameImage.PLAYFIELD.getImage();
|
||||||
playfield.setAlpha(dimLevel);
|
playfield.setAlpha(dimLevel);
|
||||||
playfield.draw();
|
playfield.draw();
|
||||||
|
|
|
@ -95,7 +95,7 @@ public class GameRanking extends BasicGameState {
|
||||||
OsuFile osu = MusicController.getOsuFile();
|
OsuFile osu = MusicController.getOsuFile();
|
||||||
|
|
||||||
// background
|
// background
|
||||||
if (!osu.drawBG(width, height, 0.7f))
|
if (!osu.drawBG(width, height, 0.7f, true))
|
||||||
g.setBackground(Utils.COLOR_BLACK_ALPHA);
|
g.setBackground(Utils.COLOR_BLACK_ALPHA);
|
||||||
|
|
||||||
// ranking screen elements
|
// ranking screen elements
|
||||||
|
|
|
@ -168,7 +168,7 @@ public class MainMenu extends BasicGameState {
|
||||||
// draw background
|
// draw background
|
||||||
OsuFile osu = MusicController.getOsuFile();
|
OsuFile osu = MusicController.getOsuFile();
|
||||||
if (Options.isDynamicBackgroundEnabled() &&
|
if (Options.isDynamicBackgroundEnabled() &&
|
||||||
osu != null && osu.drawBG(width, height, bgAlpha))
|
osu != null && osu.drawBG(width, height, bgAlpha, true))
|
||||||
;
|
;
|
||||||
else {
|
else {
|
||||||
Image bg = GameImage.MENU_BG.getImage();
|
Image bg = GameImage.MENU_BG.getImage();
|
||||||
|
|
|
@ -68,7 +68,7 @@ import org.newdawn.slick.state.transition.FadeOutTransition;
|
||||||
*/
|
*/
|
||||||
public class SongMenu extends BasicGameState {
|
public class SongMenu extends BasicGameState {
|
||||||
/** The max number of song buttons to be shown on each screen. */
|
/** The max number of song buttons to be shown on each screen. */
|
||||||
private static final int MAX_SONG_BUTTONS = 6;
|
public static final int MAX_SONG_BUTTONS = 6;
|
||||||
|
|
||||||
/** The max number of score buttons to be shown at a time. */
|
/** The max number of score buttons to be shown at a time. */
|
||||||
public static final int MAX_SCORE_BUTTONS = 7;
|
public static final int MAX_SCORE_BUTTONS = 7;
|
||||||
|
@ -239,7 +239,7 @@ public class SongMenu extends BasicGameState {
|
||||||
|
|
||||||
// background
|
// background
|
||||||
if (focusNode != null)
|
if (focusNode != null)
|
||||||
focusNode.osuFiles.get(focusNode.osuFileIndex).drawBG(width, height, 1.0f);
|
focusNode.osuFiles.get(focusNode.osuFileIndex).drawBG(width, height, 1.0f, true);
|
||||||
|
|
||||||
// header setup
|
// header setup
|
||||||
float lowerBound = height * 0.15f;
|
float lowerBound = height * 0.15f;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user