Major track length-related updates.
- Parse and store the end time for every beatmap (i.e. end time of last hit object). - Added a 'length' sorting tab. - Added 'length' search condition. - Removed 'getTrackLength()' and 'getTrackLengthString()' methods, as they are no longer needed. - Added a loader spritesheet animation to render during MP3 conversions (in place of track length rendering upon completion). Other changes: - Added a yellow progress circle during lead-in time. - Fixed sorting tab positioning. - Slightly increased button animation speed in "Main Menu Exit" state. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
4ecd50f488
commit
b0c0b44ef1
BIN
res/loader.png
Normal file
BIN
res/loader.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -478,11 +478,10 @@ public class GameScore {
|
|||
* scorebar, score, score percentage, map progress circle,
|
||||
* mod icons, combo count, combo burst, and grade.
|
||||
* @param g the graphics context
|
||||
* @param mapLength the length of the beatmap (in ms)
|
||||
* @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade
|
||||
* @param firstObject true if the first hit object's start time has not yet passed
|
||||
*/
|
||||
public void drawGameElements(Graphics g, int mapLength, boolean breakPeriod, boolean firstObject) {
|
||||
public void drawGameElements(Graphics g, boolean breakPeriod, boolean firstObject) {
|
||||
// score
|
||||
drawSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay),
|
||||
width - 2, 0, 1.0f, true);
|
||||
|
@ -500,11 +499,19 @@ public class GameScore {
|
|||
float circleDiameter = symbolHeight * 0.75f;
|
||||
g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter);
|
||||
|
||||
int firstObjectTime = MusicController.getOsuFile().objects[0].time;
|
||||
OsuFile osu = MusicController.getOsuFile();
|
||||
int firstObjectTime = osu.objects[0].time;
|
||||
int trackPosition = MusicController.getPosition();
|
||||
if (trackPosition > firstObjectTime) {
|
||||
// map progress (white)
|
||||
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter,
|
||||
-90, -90 + (int) (360f * (trackPosition - firstObjectTime) / mapLength)
|
||||
-90, -90 + (int) (360f * (trackPosition - firstObjectTime) / (osu.endTime - firstObjectTime))
|
||||
);
|
||||
} else {
|
||||
// lead-in time (yellow)
|
||||
g.setColor(Utils.COLOR_YELLOW_ALPHA);
|
||||
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter,
|
||||
-90 + (int) (360f * trackPosition / firstObjectTime), -90
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import itdelatrisu.opsu.states.Options;
|
|||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javazoom.jl.converter.Converter;
|
||||
|
||||
|
@ -249,48 +248,6 @@ public class MusicController {
|
|||
return (trackExists() && player.setPosition(position / 1000f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of the track, in milliseconds.
|
||||
* Returns 0 if no file is loaded or a track is currently being loaded.
|
||||
* @author bdk (http://slick.ninjacave.com/forum/viewtopic.php?t=2699)
|
||||
*/
|
||||
public static int getTrackLength() {
|
||||
if (!trackExists() || isTrackLoading())
|
||||
return 0;
|
||||
|
||||
float duration = 0f;
|
||||
try {
|
||||
// get Music object's (private) Audio object reference
|
||||
Field sound = player.getClass().getDeclaredField("sound");
|
||||
sound.setAccessible(true);
|
||||
Audio audio = (Audio) (sound.get(player));
|
||||
|
||||
// access Audio object's (private)'length' field
|
||||
Field length = audio.getClass().getDeclaredField("length");
|
||||
length.setAccessible(true);
|
||||
duration = (float) (length.get(audio));
|
||||
} catch (Exception e) {
|
||||
Log.debug("Could not get track length.");
|
||||
return 0;
|
||||
}
|
||||
return (int) (duration * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of the track as a formatted string (M:SS).
|
||||
* Returns "--" if a track is currently being loaded.
|
||||
*/
|
||||
public static String getTrackLengthString() {
|
||||
if (isTrackLoading())
|
||||
return "...";
|
||||
|
||||
int duration = getTrackLength();
|
||||
return String.format("%d:%02d",
|
||||
TimeUnit.MILLISECONDS.toMinutes(duration),
|
||||
TimeUnit.MILLISECONDS.toSeconds(duration) -
|
||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and releases all sources, clears each of the specified Audio
|
||||
* buffers, destroys the OpenAL context, and resets SoundStore for future use.
|
||||
|
|
|
@ -96,6 +96,7 @@ public class OsuFile implements Comparable<OsuFile> {
|
|||
public int hitObjectCircle = 0; // number of circles
|
||||
public int hitObjectSlider = 0; // number of sliders
|
||||
public int hitObjectSpinner = 0; // number of spinners
|
||||
public int endTime = -1; // last object end time (in ms)
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
|
|
@ -38,13 +38,14 @@ public class OsuGroupList {
|
|||
SORT_ARTIST = 1,
|
||||
SORT_CREATOR = 2,
|
||||
SORT_BPM = 3,
|
||||
SORT_MAX = 4; // not a sort
|
||||
SORT_LENGTH = 4,
|
||||
SORT_MAX = 5; // not a sort
|
||||
|
||||
/**
|
||||
* Sorting order names (indexed by SORT_* constants).
|
||||
*/
|
||||
public static final String[] SORT_NAMES = {
|
||||
"Title", "Artist", "Creator", "BPM"
|
||||
"Title", "Artist", "Creator", "BPM", "Length"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -240,6 +241,9 @@ public class OsuGroupList {
|
|||
case SORT_BPM:
|
||||
Collections.sort(nodes, new OsuGroupNode.BPMOrder());
|
||||
break;
|
||||
case SORT_LENGTH:
|
||||
Collections.sort(nodes, new OsuGroupNode.LengthOrder());
|
||||
break;
|
||||
}
|
||||
expandedIndex = -1;
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package itdelatrisu.opsu;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
|
@ -100,6 +101,26 @@ public class OsuGroupNode implements Comparable<OsuGroupNode> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two OsuGroupNode objects by length.
|
||||
* Uses the longest beatmap in each set for comparison.
|
||||
*/
|
||||
public static class LengthOrder implements Comparator<OsuGroupNode> {
|
||||
@Override
|
||||
public int compare(OsuGroupNode v, OsuGroupNode w) {
|
||||
int vMax = 0, wMax = 0;
|
||||
for (OsuFile osu : v.osuFiles) {
|
||||
if (osu.endTime > vMax)
|
||||
vMax = osu.endTime;
|
||||
}
|
||||
for (OsuFile osu : w.osuFiles) {
|
||||
if (osu.endTime > wMax)
|
||||
wMax = osu.endTime;
|
||||
}
|
||||
return Integer.compare(vMax, wMax);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a button background image.
|
||||
*/
|
||||
|
@ -162,8 +183,10 @@ public class OsuGroupNode implements Comparable<OsuGroupNode> {
|
|||
info[0] = osu.toString();
|
||||
info[1] = String.format("Mapped by %s",
|
||||
osu.creator);
|
||||
info[2] = String.format("Length: %s BPM: %s Objects: %d",
|
||||
MusicController.getTrackLengthString(),
|
||||
info[2] = String.format("Length: %d:%02d BPM: %s Objects: %d",
|
||||
TimeUnit.MILLISECONDS.toMinutes(osu.endTime),
|
||||
TimeUnit.MILLISECONDS.toSeconds(osu.endTime) -
|
||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(osu.endTime)),
|
||||
(osu.bpmMax <= 0) ? "--" :
|
||||
((osu.bpmMin == osu.bpmMax) ? osu.bpmMin : String.format("%d-%d", osu.bpmMin, osu.bpmMax)),
|
||||
(osu.hitObjectCircle + osu.hitObjectSlider + osu.hitObjectSpinner));
|
||||
|
@ -232,7 +255,7 @@ public class OsuGroupNode implements Comparable<OsuGroupNode> {
|
|||
case "od": osuValue = osu.overallDifficulty; break;
|
||||
case "hp": osuValue = osu.HPDrainRate; break;
|
||||
case "bpm": osuValue = osu.bpmMax; break;
|
||||
// case "length": /* not implemented */ break;
|
||||
case "length": osuValue = osu.endTime / 1000; break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ public class OsuParser {
|
|||
osu.timingPoints = new ArrayList<OsuTimingPoint>();
|
||||
|
||||
String line = in.readLine();
|
||||
String tokens[];
|
||||
String tokens[] = null;
|
||||
while (line != null) {
|
||||
line = line.trim();
|
||||
if (!isValidLine(line)) {
|
||||
|
@ -386,6 +386,7 @@ public class OsuParser {
|
|||
osu.combo = colors.toArray(new Color[colors.size()]);
|
||||
break;
|
||||
case "[HitObjects]":
|
||||
int type = -1;
|
||||
while ((line = in.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (!isValidLine(line))
|
||||
|
@ -394,7 +395,7 @@ public class OsuParser {
|
|||
break;
|
||||
/* Only type counts parsed at this time. */
|
||||
tokens = line.split(",");
|
||||
int type = Integer.parseInt(tokens[3]);
|
||||
type = Integer.parseInt(tokens[3]);
|
||||
if ((type & OsuHitObject.TYPE_CIRCLE) > 0)
|
||||
osu.hitObjectCircle++;
|
||||
else if ((type & OsuHitObject.TYPE_SLIDER) > 0)
|
||||
|
@ -402,6 +403,16 @@ public class OsuParser {
|
|||
else //if ((type & OsuHitObject.TYPE_SPINNER) > 0)
|
||||
osu.hitObjectSpinner++;
|
||||
}
|
||||
|
||||
// map length = last object end time (TODO: end on slider?)
|
||||
if ((type & OsuHitObject.TYPE_SPINNER) > 0) {
|
||||
// some 'endTime' fields contain a ':' character (?)
|
||||
int index = tokens[5].indexOf(':');
|
||||
if (index != -1)
|
||||
tokens[5] = tokens[5].substring(0, index);
|
||||
osu.endTime = Integer.parseInt(tokens[5]);
|
||||
} else
|
||||
osu.endTime = Integer.parseInt(tokens[2]);
|
||||
break;
|
||||
default:
|
||||
line = in.readLine();
|
||||
|
|
|
@ -59,7 +59,8 @@ public class Utils {
|
|||
COLOR_GREEN_OBJECT = new Color(26, 207, 26),
|
||||
COLOR_BLUE_OBJECT = new Color(46, 136, 248),
|
||||
COLOR_RED_OBJECT = new Color(243, 48, 77),
|
||||
COLOR_ORANGE_OBJECT = new Color(255, 200, 32);
|
||||
COLOR_ORANGE_OBJECT = new Color(255, 200, 32),
|
||||
COLOR_YELLOW_ALPHA = new Color(255, 255, 0, 0.4f);
|
||||
|
||||
/**
|
||||
* The default map colors, used when a map does not provide custom colors.
|
||||
|
|
|
@ -114,11 +114,6 @@ public class Game extends BasicGameState {
|
|||
*/
|
||||
private int[] hitResultOffset;
|
||||
|
||||
/**
|
||||
* Time, in milliseconds, between the first and last hit object.
|
||||
*/
|
||||
private int mapLength;
|
||||
|
||||
/**
|
||||
* Current break index in breaks ArrayList.
|
||||
*/
|
||||
|
@ -274,7 +269,7 @@ public class Game extends BasicGameState {
|
|||
g.fillRect(0, height * 0.875f, width, height * 0.125f);
|
||||
}
|
||||
|
||||
score.drawGameElements(g, mapLength, true, objectIndex == 0);
|
||||
score.drawGameElements(g, true, objectIndex == 0);
|
||||
|
||||
if (breakLength >= 8000 &&
|
||||
trackPosition - breakTime > 2000 &&
|
||||
|
@ -317,7 +312,7 @@ public class Game extends BasicGameState {
|
|||
}
|
||||
|
||||
// game elements
|
||||
score.drawGameElements(g, mapLength, false, objectIndex == 0);
|
||||
score.drawGameElements(g, false, objectIndex == 0);
|
||||
|
||||
// skip beginning
|
||||
if (objectIndex == 0 &&
|
||||
|
@ -624,7 +619,7 @@ public class Game extends BasicGameState {
|
|||
// load checkpoint
|
||||
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
|
||||
int checkpoint = Options.getCheckpoint();
|
||||
if (checkpoint == 0 || checkpoint > MusicController.getTrackLength())
|
||||
if (checkpoint == 0 || checkpoint > osu.endTime)
|
||||
break; // invalid checkpoint
|
||||
try {
|
||||
restart = RESTART_MANUAL;
|
||||
|
@ -728,15 +723,6 @@ public class Game extends BasicGameState {
|
|||
if (restart == RESTART_NEW) {
|
||||
loadImages();
|
||||
setMapModifiers();
|
||||
|
||||
// calculate map length (TODO: end on slider?)
|
||||
OsuHitObject lastObject = osu.objects[osu.objects.length - 1];
|
||||
int endTime;
|
||||
if ((lastObject.type & OsuHitObject.TYPE_SPINNER) > 0)
|
||||
endTime = lastObject.endTime;
|
||||
else
|
||||
endTime = lastObject.time;
|
||||
mapLength = endTime - osu.objects[0].time;
|
||||
}
|
||||
|
||||
// initialize object maps
|
||||
|
|
|
@ -190,7 +190,7 @@ public class MainMenu extends BasicGameState {
|
|||
g.setColor(Color.white);
|
||||
if (!MusicController.isTrackLoading())
|
||||
g.fillRoundRect(width - 168, 54,
|
||||
148f * MusicController.getPosition() / MusicController.getTrackLength(), 5, 4);
|
||||
148f * MusicController.getPosition() / osu.endTime, 5, 4);
|
||||
|
||||
// draw text
|
||||
g.setFont(Utils.FONT_MEDIUM);
|
||||
|
|
|
@ -120,9 +120,9 @@ public class MainMenuExit extends BasicGameState {
|
|||
float yesX = yesButton.getX(), noX = noButton.getX();
|
||||
float center = container.getWidth() / 2f;
|
||||
if (yesX < center)
|
||||
yesButton.setX(Math.min(yesX + (delta / 6f), center));
|
||||
yesButton.setX(Math.min(yesX + (delta / 5f), center));
|
||||
if (noX > center)
|
||||
noButton.setX(Math.max(noX - (delta / 6f), center));
|
||||
noButton.setX(Math.max(noX - (delta / 5f), center));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,12 +29,14 @@ import itdelatrisu.opsu.SoundController;
|
|||
import itdelatrisu.opsu.Utils;
|
||||
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.newdawn.slick.Animation;
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.GameContainer;
|
||||
import org.newdawn.slick.Graphics;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.Input;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.SpriteSheet;
|
||||
import org.newdawn.slick.gui.TextField;
|
||||
import org.newdawn.slick.state.BasicGameState;
|
||||
import org.newdawn.slick.state.StateBasedGame;
|
||||
|
@ -128,6 +130,11 @@ public class SongMenu extends BasicGameState {
|
|||
*/
|
||||
private Image musicNote;
|
||||
|
||||
/**
|
||||
* Loader animation.
|
||||
*/
|
||||
private Animation loader;
|
||||
|
||||
// game-related variables
|
||||
private GameContainer container;
|
||||
private StateBasedGame game;
|
||||
|
@ -164,7 +171,7 @@ public class SongMenu extends BasicGameState {
|
|||
Image tab = Utils.getTabImage();
|
||||
float tabX = buttonX + (tab.getWidth() / 2f);
|
||||
float tabY = (height * 0.15f) - (tab.getHeight() / 2f) - 2f;
|
||||
float tabOffset = (width - buttonX) / sortTabs.length;
|
||||
float tabOffset = (width - buttonX - tab.getWidth()) / (sortTabs.length - 1);
|
||||
for (int i = 0; i < sortTabs.length; i++)
|
||||
sortTabs[i] = new GUIMenuButton(tab, tabX + (i * tabOffset), tabY);
|
||||
|
||||
|
@ -178,7 +185,7 @@ public class SongMenu extends BasicGameState {
|
|||
|
||||
search = new TextField(
|
||||
container, Utils.FONT_DEFAULT,
|
||||
(int) tabX + searchIcon.getWidth(), (int) ((height * 0.15f) - (tab.getHeight() * 5 / 2f)),
|
||||
(int) tabX + searchIcon.getWidth(), (int) ((height * 0.15f) - (tab.getHeight() * 2.5f)),
|
||||
(int) (buttonWidth / 2), Utils.FONT_DEFAULT.getHeight()
|
||||
);
|
||||
search.setBackgroundColor(Color.transparent);
|
||||
|
@ -191,8 +198,16 @@ public class SongMenu extends BasicGameState {
|
|||
Image optionsIcon = new Image("options.png").getScaledCopy(iconScale);
|
||||
optionsButton = new GUIMenuButton(optionsIcon, search.getX() - (optionsIcon.getWidth() * 1.5f), search.getY());
|
||||
|
||||
// music note
|
||||
int musicNoteDim = (int) (Utils.FONT_LARGE.getHeight() * 0.75f + Utils.FONT_DEFAULT.getHeight());
|
||||
musicNote = new Image("music-note.png").getScaledCopy(musicNoteDim, musicNoteDim);
|
||||
|
||||
// loader
|
||||
SpriteSheet spr = new SpriteSheet(
|
||||
new Image("loader.png").getScaledCopy(musicNoteDim / 48f),
|
||||
musicNoteDim, musicNoteDim
|
||||
);
|
||||
loader = new Animation(spr, 50);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -218,16 +233,19 @@ public class SongMenu extends BasicGameState {
|
|||
|
||||
// header
|
||||
if (focusNode != null) {
|
||||
musicNote.draw();
|
||||
int musicNoteWidth = musicNote.getWidth();
|
||||
int musicNoteHeight = musicNote.getHeight();
|
||||
if (MusicController.isTrackLoading())
|
||||
loader.draw();
|
||||
else
|
||||
musicNote.draw();
|
||||
int iconWidth = musicNote.getWidth();
|
||||
int iconHeight = musicNote.getHeight();
|
||||
|
||||
String[] info = focusNode.getInfo();
|
||||
g.setColor(Color.white);
|
||||
Utils.FONT_LARGE.drawString(musicNoteWidth + 5, -3, info[0]);
|
||||
Utils.FONT_LARGE.drawString(iconWidth + 5, -3, info[0]);
|
||||
Utils.FONT_DEFAULT.drawString(
|
||||
musicNoteWidth + 5, -3 + Utils.FONT_LARGE.getHeight() * 0.75f, info[1]);
|
||||
int headerY = musicNoteHeight - 3;
|
||||
iconWidth + 5, -3 + Utils.FONT_LARGE.getHeight() * 0.75f, info[1]);
|
||||
int headerY = iconHeight - 3;
|
||||
Utils.FONT_BOLD.drawString(5, headerY, info[2]);
|
||||
headerY += Utils.FONT_BOLD.getLineHeight() - 6;
|
||||
Utils.FONT_DEFAULT.drawString(5, headerY, info[3]);
|
||||
|
|
Loading…
Reference in New Issue
Block a user