diff --git a/res/playback-2x.png b/res/playback-double.png similarity index 100% rename from res/playback-2x.png rename to res/playback-double.png diff --git a/res/playback-05x.png b/res/playback-half.png similarity index 100% rename from res/playback-05x.png rename to res/playback-half.png diff --git a/res/playback-1x.png b/res/playback-normal.png similarity index 100% rename from res/playback-1x.png rename to res/playback-normal.png diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 50b36914..5f7657a1 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -104,10 +104,6 @@ public enum GameImage { } }, - REPLAY_05XPLAYBACK ("playback-05x", "png"), - REPLAY_1XPLAYBACK ("playback-1x", "png"), - REPLAY_2XPLAYBACK ("playback-2x", "png"), - // Circle HITCIRCLE ("hitcircle", "png"), HITCIRCLE_OVERLAY ("hitcircleoverlay", "png"), @@ -232,6 +228,11 @@ public enum GameImage { SELECTION_OTHER_OPTIONS ("selection-selectoptions", "png", false, false), SELECTION_OTHER_OPTIONS_OVERLAY ("selection-selectoptions-over", "png", false, false), + // Replay Speed Buttons + REPLAY_PLAYBACK_NORMAL ("playback-normal", "png", false, false), + REPLAY_PLAYBACK_DOUBLE ("playback-double", "png", false, false), + REPLAY_PLAYBACK_HALF ("playback-half", "png", false, false), + // Non-Game Components VOLUME ("volume-bg", "png", false, false) { @Override diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java index d5e09a5f..5440bb2e 100644 --- a/src/itdelatrisu/opsu/GameMod.java +++ b/src/itdelatrisu/opsu/GameMod.java @@ -173,7 +173,7 @@ public enum GameMod { /** The last calculated score multiplier, or -1f if it must be recalculated. */ private static float scoreMultiplier = -1f; - /** */ + /** The last calculated track speed multiplier, or -1f if it must be recalculated. */ private static float speedMultiplier = -1f; /** @@ -220,7 +220,7 @@ public enum GameMod { } /** - * + * Returns the current track speed multiplier from all active mods. */ public static float getSpeedMultiplier() { if (speedMultiplier < 0f) { diff --git a/src/itdelatrisu/opsu/OsuGroupNode.java b/src/itdelatrisu/opsu/OsuGroupNode.java index b51eab5b..07058c64 100644 --- a/src/itdelatrisu/opsu/OsuGroupNode.java +++ b/src/itdelatrisu/opsu/OsuGroupNode.java @@ -118,10 +118,10 @@ public class OsuGroupNode { return null; OsuFile osu = osuFiles.get(osuFileIndex); - float speedModifier = GameMod.getSpeedMultiplier(); + float speedModifier = GameMod.getSpeedMultiplier(); long endTime = (long) (osu.endTime / speedModifier); - int bpmMin = (int) (osu.bpmMin * speedModifier); - int bpmMax = (int) (osu.bpmMax * speedModifier); + int bpmMin = (int) (osu.bpmMin * speedModifier); + int bpmMax = (int) (osu.bpmMax * speedModifier); String[] info = new String[5]; info[0] = osu.toString(); info[1] = String.format("Mapped by %s", diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 33f1f42a..6c13aec9 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -22,6 +22,7 @@ import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.downloads.Download; import itdelatrisu.opsu.downloads.DownloadNode; +import itdelatrisu.opsu.replay.PlaybackSpeed; import java.awt.Font; import java.awt.image.BufferedImage; @@ -49,7 +50,6 @@ import java.util.Scanner; import javax.imageio.ImageIO; -import itdelatrisu.opsu.replay.PlaybackSpeed; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; @@ -91,7 +91,9 @@ public class Utils { COLOR_LIGHT_GREEN = new Color(128,255,128), COLOR_LIGHT_BLUE = new Color(128,128,255), COLOR_GREEN_SEARCH = new Color(173, 255, 47), - COLOR_DARK_GRAY = new Color(0.3f, 0.3f, 0.3f, 1f); + COLOR_DARK_GRAY = new Color(0.3f, 0.3f, 0.3f, 1f), + COLOR_RED_HIGHLIGHT = new Color(246, 154, 161), + COLOR_BLUE_HIGHLIGHT = new Color(173, 216, 230); /** The default map colors, used when a map does not provide custom colors. */ public static final Color[] DEFAULT_COMBO = { diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 8fc58c99..c8de37dd 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -298,8 +298,8 @@ public class MusicController { } /** - * Sets the music pitch. - * @param pitch [0, ..] + * Sets the music pitch (and speed). + * @param pitch */ public static void setPitch(float pitch) { SoundStore.get().setMusicPitch(pitch); diff --git a/src/itdelatrisu/opsu/replay/LifeFrame.java b/src/itdelatrisu/opsu/replay/LifeFrame.java index f53e8745..045075f9 100644 --- a/src/itdelatrisu/opsu/replay/LifeFrame.java +++ b/src/itdelatrisu/opsu/replay/LifeFrame.java @@ -16,13 +16,13 @@ * along with opsu!. If not, see . */ +package itdelatrisu.opsu.replay; + /** * Captures a single life frame. * * @author smoogipooo (https://github.com/smoogipooo/osu-Replay-API/) */ -package itdelatrisu.opsu.replay; - public class LifeFrame { /** Time. */ private int time; diff --git a/src/itdelatrisu/opsu/replay/PlaybackSpeed.java b/src/itdelatrisu/opsu/replay/PlaybackSpeed.java index e8248a9e..707fb26a 100644 --- a/src/itdelatrisu/opsu/replay/PlaybackSpeed.java +++ b/src/itdelatrisu/opsu/replay/PlaybackSpeed.java @@ -1,31 +1,57 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014, 2015 Jeffrey Han + * + * opsu! is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu! is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!. If not, see . + */ + package itdelatrisu.opsu.replay; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.MenuButton; + import org.newdawn.slick.Image; +/** + * Playback speeds for replays. + * + * @author DarkTigrus (https://github.com/DarkTigrus) + */ public enum PlaybackSpeed { - NORMAL(GameImage.REPLAY_1XPLAYBACK, 1f), - DOUBLE(GameImage.REPLAY_2XPLAYBACK, 2f), - HALF(GameImage.REPLAY_05XPLAYBACK, 0.5f); + NORMAL (GameImage.REPLAY_PLAYBACK_NORMAL, 1f), + DOUBLE (GameImage.REPLAY_PLAYBACK_DOUBLE, 2f), + HALF (GameImage.REPLAY_PLAYBACK_HALF, 0.5f); - /** The file name of the button image. */ + /** The button image. */ private GameImage gameImage; - /** The button of the playback. */ + /** The button. */ private MenuButton button; - /** The speed modifier of the playback. */ + /** The playback speed modifier. */ private float modifier; - PlaybackSpeed(GameImage gameImage, float modifier) { - this.gameImage = gameImage; - this.modifier = modifier; - } + /** Enum values. */ + private static PlaybackSpeed[] values = PlaybackSpeed.values(); + /** + * Initializes the playback buttons. + * @param width the container width + * @param height the container height + */ public static void init(int width, int height) { - // create buttons for (PlaybackSpeed playback : PlaybackSpeed.values()) { Image img = playback.gameImage.getImage(); playback.button = new MenuButton(img, width * 0.98f - (img.getWidth() / 2f), height * 0.25f); @@ -33,18 +59,14 @@ public enum PlaybackSpeed { } } - private static int index = 1; - - public static PlaybackSpeed next() { - PlaybackSpeed next = values()[index++ % values().length]; - if((GameMod.DOUBLE_TIME.isActive() && next == PlaybackSpeed.DOUBLE)) - next = next(); - - return next; - } - - public static void reset() { - index = 1; + /** + * Constructor. + * @param gameImage the button image + * @param modifier the speed modifier + */ + PlaybackSpeed(GameImage gameImage, float modifier) { + this.gameImage = gameImage; + this.modifier = modifier; } /** @@ -58,5 +80,14 @@ public enum PlaybackSpeed { * @return the speed */ public float getModifier() { return modifier; } -} + /** + * Returns the next playback speed. + */ + public PlaybackSpeed next() { + PlaybackSpeed next = values[(this.ordinal() + 1) % values.length]; + if ((GameMod.DOUBLE_TIME.isActive() && next == PlaybackSpeed.DOUBLE)) + next = next.next(); + return next; + } +} diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 57156c58..9d513763 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -132,9 +132,6 @@ public class Game extends BasicGameState { /** Skip button (displayed at song start, when necessary). */ private MenuButton skipButton; - /** Playback button (displayed in replays). */ - private MenuButton playbackButton; - /** Current timing point index in timingPoints ArrayList. */ private int timingPointIndex; @@ -215,6 +212,9 @@ public class Game extends BasicGameState { /** Whether or not the cursor should be pressed using the "auto" mod. */ private boolean autoMousePressed; + /** Playback speed (used in replays and "auto" mod). */ + private PlaybackSpeed playbackSpeed; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -465,40 +465,41 @@ public class Game extends BasicGameState { trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in // countdown - if (osu.countdown > 0) { // TODO: implement half/double rate settings + if (osu.countdown > 0) { + float speedModifier = GameMod.getSpeedMultiplier() * playbackSpeed.getModifier(); timeDiff = firstObjectTime - trackPosition; - if (timeDiff >= 500 && timeDiff < 3000) { - if (timeDiff >= 1500) { + if (timeDiff >= 500 * speedModifier && timeDiff < 3000 * speedModifier) { + if (timeDiff >= 1500 * speedModifier) { GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2); if (!countdownReadySound) { SoundController.playSound(SoundEffect.READY); countdownReadySound = true; } } - if (timeDiff < 2000) { + if (timeDiff < 2000 * speedModifier) { GameImage.COUNTDOWN_3.getImage().draw(0, 0); if (!countdown3Sound) { SoundController.playSound(SoundEffect.COUNT3); countdown3Sound = true; } } - if (timeDiff < 1500) { + if (timeDiff < 1500 * speedModifier) { GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0); if (!countdown2Sound) { SoundController.playSound(SoundEffect.COUNT2); countdown2Sound = true; } } - if (timeDiff < 1000) { + if (timeDiff < 1000 * speedModifier) { GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2); if (!countdown1Sound) { SoundController.playSound(SoundEffect.COUNT1); countdown1Sound = true; } } - } else if (timeDiff >= -500 && timeDiff < 500) { + } else if (timeDiff >= -500 * speedModifier && timeDiff < 500 * speedModifier) { Image go = GameImage.COUNTDOWN_GO.getImage(); - go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1); + go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / speedModifier / -500f) : 1); go.drawCentered(width / 2, height / 2); if (!countdownGoSound) { SoundController.playSound(SoundEffect.GO); @@ -515,6 +516,10 @@ public class Game extends BasicGameState { if (GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); + // draw replay speed button + if (isReplay || GameMod.AUTO.isActive()) + playbackSpeed.getButton().draw(); + // returning from pause screen if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { // darken the screen @@ -531,9 +536,6 @@ public class Game extends BasicGameState { cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY); } - if (isReplay || GameMod.AUTO.isActive()) - playbackButton.draw(); - if (isReplay) UI.draw(g, replayX, replayY, replayKeyPressed); else if (GameMod.AUTO.isActive()) @@ -552,7 +554,7 @@ public class Game extends BasicGameState { int mouseX = input.getMouseX(), mouseY = input.getMouseY(); skipButton.hoverUpdate(delta, mouseX, mouseY); if (isReplay || GameMod.AUTO.isActive()) - playbackButton.hoverUpdate(delta, mouseX, mouseY); + playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY); int trackPosition = MusicController.getPosition(); // returning from pause screen: must click previous mouse position @@ -850,6 +852,7 @@ public class Game extends BasicGameState { // skip to checkpoint MusicController.setPosition(checkpoint); + MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); while (objectIndex < hitObjects.length && osu.objects[objectIndex++].getTime() <= checkpoint) ; @@ -882,17 +885,19 @@ public class Game extends BasicGameState { public void mousePressed(int button, int x, int y) { // watching replay if (isReplay || GameMod.AUTO.isActive()) { - // allow skip button - if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y)) { + if (button == Input.MOUSE_MIDDLE_BUTTON) + return; + + // skip button + if (skipButton.contains(x, y)) skipIntro(); - return; - } - if (button != Input.MOUSE_MIDDLE_BUTTON && playbackButton.contains(x, y)) { - PlaybackSpeed playbackSpeed = PlaybackSpeed.next(); - playbackButton = playbackSpeed.getButton(); + + // playback speed button + else if (playbackSpeed.getButton().contains(x, y)) { + playbackSpeed = playbackSpeed.next(); MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); - return; } + return; } @@ -1102,8 +1107,6 @@ public class Game extends BasicGameState { previousMods = GameMod.getModState(); GameMod.loadModState(replay.mods); - PlaybackSpeed.reset(); - // load initial data replayX = container.getWidth() / 2; replayY = container.getHeight() / 2; @@ -1143,7 +1146,8 @@ public class Game extends BasicGameState { skipButton.resetHover(); if (isReplay || GameMod.AUTO.isActive()) - playbackButton.resetHover(); + playbackSpeed.getButton().resetHover(); + MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); } @Override @@ -1288,6 +1292,7 @@ public class Game extends BasicGameState { autoMouseY = 0; autoMousePressed = false; flashlightRadius = container.getHeight() * 2 / 3; + playbackSpeed = PlaybackSpeed.NORMAL; System.gc(); } @@ -1305,7 +1310,7 @@ public class Game extends BasicGameState { MusicController.resume(); } MusicController.setPosition(firstObjectTime - SKIP_OFFSET); - MusicController.setPitch(GameMod.getSpeedMultiplier()); + MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); replaySkipTime = (isReplay) ? -1 : trackPosition; if (isReplay) { replayX = (int) skipButton.getX(); @@ -1343,9 +1348,6 @@ public class Game extends BasicGameState { } skipButton.setHoverExpand(1.1f, MenuButton.Expand.UP_LEFT); - if (isReplay || GameMod.AUTO.isActive()) - playbackButton = PlaybackSpeed.NORMAL.getButton(); - // load other images... ((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages(); data.loadImages(); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index ed2cace2..b9151f78 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -337,7 +337,11 @@ public class SongMenu extends BasicGameState { // song info text if (songInfo == null) { - songInfo = getSongInfo(); + songInfo = focusNode.getInfo(); + if (Options.useUnicodeMetadata()) { // load glyphs + OsuFile osu = focusNode.osuFiles.get(0); + Utils.loadGlyphs(Utils.FONT_LARGE, osu.titleUnicode, osu.artistUnicode); + } } marginX += 5; float headerTextY = marginY; @@ -345,8 +349,10 @@ public class SongMenu extends BasicGameState { headerTextY += Utils.FONT_LARGE.getLineHeight() - 8; Utils.FONT_DEFAULT.drawString(marginX + iconWidth * 1.05f, headerTextY, songInfo[1], Color.white); headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 2; - Utils.FONT_BOLD.drawString(marginX, headerTextY, songInfo[2], - (GameMod.DOUBLE_TIME.isActive()) ? Color.red : (GameMod.HALF_TIME.isActive()) ? Color.green : Color.white); + float speedModifier = GameMod.getSpeedMultiplier(); + Color color2 = (speedModifier == 1f) ? Color.white : + (speedModifier > 1f) ? Utils.COLOR_RED_HIGHLIGHT : Utils.COLOR_BLUE_HIGHLIGHT; + Utils.FONT_BOLD.drawString(marginX, headerTextY, songInfo[2], color2); headerTextY += Utils.FONT_BOLD.getLineHeight() - 4; Utils.FONT_DEFAULT.drawString(marginX, headerTextY, songInfo[3], Color.white); headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 4; @@ -934,6 +940,7 @@ public class SongMenu extends BasicGameState { startScore = 0; beatmapMenuTimer = -1; searchTransitionTimer = SEARCH_TRANSITION_TIME; + songInfo = null; // reset song stack randomStack = new Stack(); @@ -979,9 +986,6 @@ public class SongMenu extends BasicGameState { resetGame = false; } - // load song info - songInfo = getSongInfo(); - // state-based action if (stateAction != null) { switch (stateAction) { @@ -1302,20 +1306,6 @@ public class SongMenu extends BasicGameState { return null; // incorrect map } - /** - * Returns an array of strings containing song information. - * @return the String array - */ - private String[] getSongInfo () { - songInfo = focusNode.getInfo(); - if (Options.useUnicodeMetadata()) { // load glyphs - OsuFile osu = focusNode.osuFiles.get(0); - Utils.loadGlyphs(Utils.FONT_LARGE, osu.titleUnicode, osu.artistUnicode); - } - return songInfo; - } - - /** * Starts the game. */