diff --git a/src/itdelatrisu/opsu/Options.java b/src/itdelatrisu/opsu/Options.java index c1487003..057430f8 100644 --- a/src/itdelatrisu/opsu/Options.java +++ b/src/itdelatrisu/opsu/Options.java @@ -135,6 +135,12 @@ public class Options { /** Port binding. */ private static int port = 49250; + /** The theme song string: {@code filename,title,artist,length(ms)} */ + private static String themeString = "theme.ogg,On the Bach,Jingle Punks,66000"; + + /** The theme song timing point string (for computing beats to pulse the logo) . */ + private static String themeTimingPoint = "-44,631.578947368421,4,1,0,100,1,0"; + /** * Returns whether the XDG flag in the manifest (if any) is set to "true". * @return true if XDG directories are enabled, false otherwise @@ -211,12 +217,6 @@ public class Options { return (dir.isDirectory()) ? dir : null; } - /** - * The theme song string: - * {@code filename,title,artist,length(ms)} - */ - private static String themeString = "theme.ogg,On the Bach,Jingle Punks,66000"; - /** Game options. */ public enum GameOption { // internal options (not displayed in-game) @@ -1342,15 +1342,14 @@ public class Options { beatmap.audioFilename = new File(tokens[0]); beatmap.title = tokens[1]; beatmap.artist = tokens[2]; - // add a timingpoint so the logo can be pulsed in the mainmenu - beatmap.timingPoints = new ArrayList<>(1); - beatmap.timingPoints.add(new TimingPoint("-44,631.578947368421,4,1,0,100,1,0")); try { beatmap.endTime = Integer.parseInt(tokens[3]); } catch (NumberFormatException e) { ErrorHandler.error("Theme song length is not a valid integer", e, false); return null; } + beatmap.timingPoints = new ArrayList<>(1); + beatmap.timingPoints.add(new TimingPoint(themeTimingPoint)); return beatmap; } diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 56bc9de3..f74cc83b 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -186,18 +186,16 @@ public class MusicController { /** * Gets the progress of the current beat. - * @return null if music is paused or no timingpoints are available, or progress as a value in - * [0, 1], where 0 means a beat just happened and 1 means the next beat is coming right now. + * @return a beat progress value [0,1) where 0 marks the current beat and + * 1 marks the next beat, or {@code null} if no beat information + * is available (e.g. music paused, no timing points) */ public static Float getBeatProgress() { - if (!isPlaying() || getBeatmap() == null) { - return null; - } Beatmap map = getBeatmap(); - if (map.timingPoints == null) { + if (!isPlaying() || map == null || map.timingPoints == null) { return null; } - int trackposition = getPosition(); + int trackPosition = getPosition(); TimingPoint p = null; double beatlen = 0d; int time = 0; @@ -215,7 +213,7 @@ public class MusicController { return null; } double beatLength = beatlen * 100d; - return (float) ((((trackposition - time) * 100) % beatLength) / beatLength); + return (float) ((((trackPosition - time) * 100) % beatLength) / beatLength); } /** diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java index 37bbb60e..28fe2f1c 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java @@ -212,45 +212,6 @@ public class BeatmapParser { return lastNode; } - /** - * Parses the timingpoints for a beatmap. - * @param map the map to parse timingpoints for - */ - public static void parseOnlyTimingPoints(Beatmap map) { - if (map == null || map.getFile() == null || !map.getFile().exists()) { - return; - } - if (map.timingPoints == null) { - map.timingPoints = new ArrayList(); - } - try ( - InputStream bis = new BufferedInputStream(new FileInputStream(map.getFile())); - BufferedReader in = new BufferedReader(new InputStreamReader(bis, "UTF-8")); - ) { - String line; - boolean found = false; - while((line = in.readLine()) != null) { - line = line.trim(); - if(!isValidLine(line)) { - continue; - } - if ("[TimingPoints]".equals(line)) { - found = true; - continue; - } - if (found) { - if (line.startsWith("[")) { - break; - } - parseSectionTimingPoints(map, line); - } - } - map.timingPoints.trimToSize(); - } catch (IOException e) { - ErrorHandler.error(String.format("Failed to read file '%s'.", map.getFile().getAbsolutePath()), e, false); - } - } - /** * Parses a beatmap. * @param file the file to parse @@ -532,7 +493,7 @@ public class BeatmapParser { break; try { - parseSectionTimingPoints(beatmap, line); + parseTimingPoint(beatmap, line); } catch (Exception e) { Log.warn(String.format("Failed to read timing point '%s' for file '%s'.", line, file.getAbsolutePath()), e); @@ -650,23 +611,68 @@ public class BeatmapParser { } /** - * Parses a timing point in the timingpoints section of a beatmap file - * @param beatmap the beatmap to add the timingpoint to - * @param line the line with timingpoint to parse + * Parses a timing point and adds it to the beatmap. + * @param beatmap the beatmap + * @param line the line containing the unparsed timing point */ - private static void parseSectionTimingPoints(Beatmap beatmap, String line) { + private static void parseTimingPoint(Beatmap beatmap, String line) { + // parse timing point TimingPoint timingPoint = new TimingPoint(line); - if(!timingPoint.isInherited()) { + beatmap.timingPoints.add(timingPoint); + + // calculate BPM + if (!timingPoint.isInherited()) { int bpm = Math.round(60000 / timingPoint.getBeatLength()); - if( beatmap.bpmMin == 0 ) { + if (beatmap.bpmMin == 0) { beatmap.bpmMin = beatmap.bpmMax = bpm; - } else if( bpm < beatmap.bpmMin ) { + } else if (bpm < beatmap.bpmMin) { beatmap.bpmMin = bpm; - } else if( bpm > beatmap.bpmMax ) { + } else if (bpm > beatmap.bpmMax) { beatmap.bpmMax = bpm; } } - beatmap.timingPoints.add(timingPoint); + } + + /** + * Parses all timing points in a beatmap. + * @param beatmap the beatmap to parse + */ + public static void parseTimingPoints(Beatmap beatmap) { + if (beatmap.timingPoints != null) // already parsed + return; + + beatmap.timingPoints = new ArrayList(); + + try (BufferedReader in = new BufferedReader(new FileReader(beatmap.getFile()))) { + String line = in.readLine(); + while (line != null) { + line = line.trim(); + if (!line.equals("[TimingPoints]")) + line = in.readLine(); + else + break; + } + if (line == null) // no timing points + return; + + while ((line = in.readLine()) != null) { + line = line.trim(); + if (!isValidLine(line)) + continue; + if (line.charAt(0) == '[') + break; + + try { + parseTimingPoint(beatmap, line); + } catch (Exception e) { + Log.warn(String.format("Failed to read timing point '%s' for file '%s'.", + line, beatmap.getFile().getAbsolutePath()), e); + } + } + beatmap.timingPoints.trimToSize(); + } catch (IOException e) { + ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false); + } } /** diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index cd0ea0c0..0f938212 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -257,17 +257,15 @@ public class MainMenu extends BasicGameState { exitButton.draw(); } - // logo + // draw logo (pulsing) Float position = MusicController.getBeatProgress(); - if (position == null) { + if (position == null) // default to 60bpm position = System.currentTimeMillis() % 1000 / 1000f; - } float scale = 1f + position * 0.05f; logo.draw(Color.white, scale); - Image ghostLogo = GameImage.MENU_LOGO.getImage().getScaledCopy(logo.getCurrentScale() / scale * 1.05f); - float scaleposmodx = ghostLogo.getWidth() / 2; - float scaleposmody = ghostLogo.getHeight() / 2; - ghostLogo.draw(logo.getX() - scaleposmodx, logo.getY() - scaleposmody, Colors.GHOST_LOGO); + float ghostScale = logo.getLastScale() / scale * 1.05f; + Image ghostLogo = GameImage.MENU_LOGO.getImage().getScaledCopy(ghostScale); + ghostLogo.drawCentered(logo.getX(), logo.getY(), Colors.GHOST_LOGO); // draw music buttons if (MusicController.isPlaying()) diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index c4b56e3d..fedbb82a 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -262,12 +262,9 @@ public class SongMenu extends BasicGameState { /** Footer pulsing logo button. */ private MenuButton footerLogoButton; - /** Size of the footer pulsing logo. */ + /** Size of the pulsing logo in the footer. */ private float footerLogoSize; - /** Whether the cursor hovers over the footer logo. */ - private boolean bottomLogoHovered; - /** Time, in milliseconds, for fading the search bar. */ private int searchTransitionTimer = SEARCH_TRANSITION_TIME; @@ -344,7 +341,7 @@ public class SongMenu extends BasicGameState { Fonts.SMALL.getLineHeight(); footerY = height - GameImage.SELECTION_MODS.getImage().getHeight(); - // logo coordinates + // footer logo coordinates float footerHeight = height - footerY; footerLogoSize = footerHeight * 3.25f; Image logo = GameImage.MENU_LOGO.getImage(); @@ -516,24 +513,22 @@ public class SongMenu extends BasicGameState { g.drawLine(0, footerY, width, footerY); g.resetLineWidth(); - // opsu logo in bottom bar + // footer logo (pulsing) Float position = MusicController.getBeatProgress(); - if (position == null) { + if (position == null) // default to 60bpm position = System.currentTimeMillis() % 1000 / 1000f; - } - if (bottomLogoHovered) { + if (footerLogoButton.contains(mouseX, mouseY, 0.25f)) { + // hovering over logo: stop pulsing footerLogoButton.draw(); } else { float expand = position * 0.15f; footerLogoButton.draw(Color.white, 1f - expand); Image ghostLogo = GameImage.MENU_LOGO.getImage(); ghostLogo = ghostLogo.getScaledCopy((1f + expand) * footerLogoSize / ghostLogo.getWidth()); - float scaleposmodx = ghostLogo.getWidth() / 2; - float scaleposmody = ghostLogo.getHeight() / 2; - float a = Colors.GHOST_LOGO.a; + float oldGhostAlpha = Colors.GHOST_LOGO.a; Colors.GHOST_LOGO.a *= (1f - position); - ghostLogo.draw(footerLogoButton.getX() - scaleposmodx, footerLogoButton.getY() - scaleposmody, Colors.GHOST_LOGO); - Colors.GHOST_LOGO.a = a; + ghostLogo.drawCentered(footerLogoButton.getX(), footerLogoButton.getY(), Colors.GHOST_LOGO); + Colors.GHOST_LOGO.a = oldGhostAlpha; } // header @@ -697,6 +692,7 @@ public class SongMenu extends BasicGameState { selectRandomButton.hoverUpdate(delta, mouseX, mouseY); selectMapOptionsButton.hoverUpdate(delta, mouseX, mouseY); selectOptionsButton.hoverUpdate(delta, mouseX, mouseY); + footerLogoButton.hoverUpdate(delta, mouseX, mouseY, 0.25f); // beatmap menu timer if (beatmapMenuTimer > -1) { @@ -791,18 +787,7 @@ public class SongMenu extends BasicGameState { } updateDrawnSongPosition(); - // mouse hover (logo) - if (footerLogoButton.contains(mouseX, mouseY, 0.25f)) { - footerLogoButton.hoverUpdate(delta, true); - bottomLogoHovered = true; - // reset beatmap node hover - hoverIndex = null; - return; - } - footerLogoButton.hoverUpdate(delta, false); - bottomLogoHovered = false; - - // mouse hover (beatmap nodes) + // mouse hover BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY); if (node != null) { if (node == hoverIndex) @@ -845,7 +830,7 @@ public class SongMenu extends BasicGameState { if (isScrollingToFocusNode) return; - if (bottomLogoHovered) { + if (footerLogoButton.contains(x, y, 0.25f)) { startGame(); return; } @@ -1445,8 +1430,8 @@ public class SongMenu extends BasicGameState { focusNode = BeatmapSetList.get().getNode(node, beatmapIndex); Beatmap beatmap = focusNode.getSelectedBeatmap(); if (beatmap.timingPoints == null) { - // parse the timingpoints so we can pulse the main menu logo and bottom right logo in songmenu - BeatmapParser.parseOnlyTimingPoints(beatmap); + // parse timing points so we can pulse the logo + BeatmapParser.parseTimingPoints(beatmap); } MusicController.play(beatmap, false, preview); diff --git a/src/itdelatrisu/opsu/ui/MenuButton.java b/src/itdelatrisu/opsu/ui/MenuButton.java index c6675b46..0b89b442 100644 --- a/src/itdelatrisu/opsu/ui/MenuButton.java +++ b/src/itdelatrisu/opsu/ui/MenuButton.java @@ -98,8 +98,8 @@ public class MenuButton { /** The default max rotation angle of the button. */ private static final float DEFAULT_ANGLE_MAX = 30f; - /** The current scale of the drawn button */ - private float currentScale = 1f; + /** The last scale at which the button was drawn. */ + private float lastScale = 1f; /** * Creates a new button from an Image. @@ -170,9 +170,9 @@ public class MenuButton { public float getY() { return y; } /** - * Returns the current scale. + * Returns the last scale at which the button was drawn. */ - public float getCurrentScale() { return currentScale; } + public float getLastScale() { return lastScale; } /** * Sets text to draw in the middle of the button. @@ -208,9 +208,9 @@ public class MenuButton { public void draw(Color filter) { draw(filter, 1f); } /** - * Draw the button with a color filter. + * Draw the button with a color filter at the given scale. * @param filter the color to filter with when drawing - * @param scaleOverride the scale to use when drawing, works only for normal images + * @param scaleOverride the scale to use when drawing (only works for normal images) */ @SuppressWarnings("deprecation") public void draw(Color filter, float scaleOverride) { @@ -223,14 +223,13 @@ public class MenuButton { // normal images if (imgL == null) { - float scalePosModX = 0; - float scalePosModY = 0; + float xScaleOffset = 0f, yScaleOffset = 0f; if (scaleOverride != 1f) { image = image.getScaledCopy(scaleOverride); - scalePosModX = image.getWidth() / 2 - xRadius; - scalePosModY = image.getHeight() / 2 - yRadius; + xScaleOffset = image.getWidth() / 2f - xRadius; + yScaleOffset = image.getHeight() / 2f - yRadius; } - currentScale = scaleOverride; + lastScale = scaleOverride; if (hoverEffect == 0) image.draw(x - xRadius, y - yRadius, filter); else { @@ -240,16 +239,16 @@ public class MenuButton { if (scale.getValue() != 1f) { image = image.getScaledCopy(scale.getValue()); image.setAlpha(oldAlpha); - scalePosModX = image.getWidth() / 2 - xRadius; - scalePosModY = image.getHeight() / 2 - yRadius; - currentScale *= scale.getValue(); + xScaleOffset = image.getWidth() / 2f - xRadius; + yScaleOffset = image.getHeight() / 2f - yRadius; + lastScale *= scale.getValue(); } } if ((hoverEffect & EFFECT_FADE) > 0) image.setAlpha(alpha.getValue()); if ((hoverEffect & EFFECT_ROTATE) > 0) image.setRotation(angle.getValue()); - image.draw(x - xRadius - scalePosModX, y - yRadius - scalePosModY, filter); + image.draw(x - xRadius - xScaleOffset, y - yRadius - yScaleOffset, filter); if (image == this.img) { image.setAlpha(oldAlpha); image.setRotation(oldAngle);