From b0c0b44ef13a3960db239af1731fc20712506b24 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 17 Jul 2014 21:16:15 -0400 Subject: [PATCH] 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 --- res/loader.png | Bin 0 -> 25576 bytes src/itdelatrisu/opsu/GameScore.java | 15 ++++-- src/itdelatrisu/opsu/MusicController.java | 43 ------------------ src/itdelatrisu/opsu/OsuFile.java | 1 + src/itdelatrisu/opsu/OsuGroupList.java | 8 +++- src/itdelatrisu/opsu/OsuGroupNode.java | 29 ++++++++++-- src/itdelatrisu/opsu/OsuParser.java | 15 +++++- src/itdelatrisu/opsu/Utils.java | 3 +- src/itdelatrisu/opsu/states/Game.java | 20 ++------ src/itdelatrisu/opsu/states/MainMenu.java | 2 +- src/itdelatrisu/opsu/states/MainMenuExit.java | 4 +- src/itdelatrisu/opsu/states/SongMenu.java | 34 ++++++++++---- 12 files changed, 91 insertions(+), 83 deletions(-) create mode 100644 res/loader.png diff --git a/res/loader.png b/res/loader.png new file mode 100644 index 0000000000000000000000000000000000000000..11264170a622fb6485ba3430fddc2bdcd312e341 GIT binary patch literal 25576 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 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 ); } diff --git a/src/itdelatrisu/opsu/MusicController.java b/src/itdelatrisu/opsu/MusicController.java index 83370519..370176fa 100644 --- a/src/itdelatrisu/opsu/MusicController.java +++ b/src/itdelatrisu/opsu/MusicController.java @@ -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. diff --git a/src/itdelatrisu/opsu/OsuFile.java b/src/itdelatrisu/opsu/OsuFile.java index 44167467..fd5bc546 100644 --- a/src/itdelatrisu/opsu/OsuFile.java +++ b/src/itdelatrisu/opsu/OsuFile.java @@ -96,6 +96,7 @@ public class OsuFile implements Comparable { 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. diff --git a/src/itdelatrisu/opsu/OsuGroupList.java b/src/itdelatrisu/opsu/OsuGroupList.java index f3a840be..4d4a282b 100644 --- a/src/itdelatrisu/opsu/OsuGroupList.java +++ b/src/itdelatrisu/opsu/OsuGroupList.java @@ -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; diff --git a/src/itdelatrisu/opsu/OsuGroupNode.java b/src/itdelatrisu/opsu/OsuGroupNode.java index bf12f5c5..10df7c0a 100644 --- a/src/itdelatrisu/opsu/OsuGroupNode.java +++ b/src/itdelatrisu/opsu/OsuGroupNode.java @@ -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 { } } + /** + * Compares two OsuGroupNode objects by length. + * Uses the longest beatmap in each set for comparison. + */ + public static class LengthOrder implements Comparator { + @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 { 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 { 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; } diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java index c041dfdb..bd04266b 100644 --- a/src/itdelatrisu/opsu/OsuParser.java +++ b/src/itdelatrisu/opsu/OsuParser.java @@ -131,7 +131,7 @@ public class OsuParser { osu.timingPoints = new ArrayList(); 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(); diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index a5744378..a5713b4d 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -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. diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 3c910954..e3dad7eb 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -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 diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index d187e9b6..1c7de45f 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -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); diff --git a/src/itdelatrisu/opsu/states/MainMenuExit.java b/src/itdelatrisu/opsu/states/MainMenuExit.java index 858505d6..b77275b0 100644 --- a/src/itdelatrisu/opsu/states/MainMenuExit.java +++ b/src/itdelatrisu/opsu/states/MainMenuExit.java @@ -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 diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index bea220a6..8dc0e60a 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -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]);