diff --git a/res/playback-05x.png b/res/playback-05x.png new file mode 100644 index 00000000..358d7e0c Binary files /dev/null and b/res/playback-05x.png differ diff --git a/res/playback-1x.png b/res/playback-1x.png new file mode 100644 index 00000000..a5949368 Binary files /dev/null and b/res/playback-1x.png differ diff --git a/res/playback-2x.png b/res/playback-2x.png new file mode 100644 index 00000000..ba43cdfb Binary files /dev/null and b/res/playback-2x.png differ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 12f9a814..50b36914 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -104,6 +104,10 @@ 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"), diff --git a/src/itdelatrisu/opsu/MenuButton.java b/src/itdelatrisu/opsu/MenuButton.java index de04a0e3..8631a90b 100644 --- a/src/itdelatrisu/opsu/MenuButton.java +++ b/src/itdelatrisu/opsu/MenuButton.java @@ -279,6 +279,11 @@ public class MenuButton { */ public void removeHoverEffects() { hoverEffect = 0; } + /** + * Sets the image of the button. + */ + public void setImage(Image image) { img = image; } + /** * Sets the "expand" hover effect. */ diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 0050785d..8fc58c99 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -279,13 +279,13 @@ public class MusicController { * Plays the current track. * @param loop whether or not to loop the track */ - public static void play(float pitch, boolean loop) { + public static void play(boolean loop) { if (trackExists()) { trackEnded = false; if (loop) player.loop(); else - player.play(pitch, 1f); + player.play(); } } @@ -297,6 +297,14 @@ public class MusicController { SoundStore.get().setMusicVolume((isTrackDimmed()) ? volume * dimLevel : volume); } + /** + * Sets the music pitch. + * @param pitch [0, ..] + */ + public static void setPitch(float pitch) { + SoundStore.get().setMusicPitch(pitch); + } + /** * Returns whether or not the current track has ended. */ diff --git a/src/itdelatrisu/opsu/replay/PlaybackSpeed.java b/src/itdelatrisu/opsu/replay/PlaybackSpeed.java new file mode 100644 index 00000000..7d69efbd --- /dev/null +++ b/src/itdelatrisu/opsu/replay/PlaybackSpeed.java @@ -0,0 +1,50 @@ +package itdelatrisu.opsu.replay; + +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; +import org.newdawn.slick.Image; + +public enum PlaybackSpeed { + NORMAL(GameImage.REPLAY_1XPLAYBACK, 1f), + DOUBLE(GameImage.REPLAY_2XPLAYBACK, 2f), + HALF(GameImage.REPLAY_05XPLAYBACK, 0.5f); + + /** The file name of the button image. */ + private GameImage gameImage; + + /** The speed modifier of the playback. */ + private float modifier; + + PlaybackSpeed(GameImage gameImage, float modifier) { + this.gameImage = gameImage; + this.modifier = modifier; + } + + private static int index = 1; + + public static PlaybackSpeed next() { + PlaybackSpeed next = values()[index++ % values().length]; + if((GameMod.DOUBLE_TIME.isActive() && next == PlaybackSpeed.DOUBLE) || + (GameMod.HALF_TIME.isActive() && next == PlaybackSpeed.HALF)) + next = next(); + + return next; + } + + public static void reset() { + index = 1; + } + + /** + * Returns the image. + * @return the associated image + */ + public Image getImage() { return gameImage.getImage(); } + + /** + * Returns the speed modifier. + * @return the speed + */ + public float getModifier() { return modifier; } +} + diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 28204fb3..c8348a8a 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -50,6 +50,7 @@ import java.io.File; import java.util.LinkedList; import java.util.Stack; +import itdelatrisu.opsu.replay.PlaybackSpeed; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.Display; import org.newdawn.slick.Animation; @@ -131,6 +132,9 @@ 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; @@ -527,6 +531,9 @@ 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()) @@ -544,6 +551,8 @@ public class Game extends BasicGameState { UI.update(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); skipButton.hoverUpdate(delta, mouseX, mouseY); + if (isReplay || GameMod.AUTO.isActive()) + playbackButton.hoverUpdate(delta, mouseX, mouseY); int trackPosition = MusicController.getPosition(); // returning from pause screen: must click previous mouse position @@ -871,17 +880,25 @@ public class Game extends BasicGameState { @Override public void mousePressed(int button, int x, int y) { - if (Options.isMouseDisabled()) - return; - // watching replay - if (isReplay) { - // only allow skip button - if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y)) + if (isReplay || GameMod.AUTO.isActive()) { + // allow skip button + if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y)) { skipIntro(); + return; + } + if (button != Input.MOUSE_MIDDLE_BUTTON && playbackButton.contains(x, y)) { + PlaybackSpeed playbackSpeed = PlaybackSpeed.next(); + playbackButton.setImage(playbackSpeed.getImage()); + MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); + return; + } return; } + if (Options.isMouseDisabled()) + return; + // mouse wheel: pause the game if (button == Input.MOUSE_MIDDLE_BUTTON && !Options.isMouseWheelDisabled()) { int trackPosition = MusicController.getPosition(); @@ -1085,6 +1102,8 @@ 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; @@ -1116,12 +1135,15 @@ public class Game extends BasicGameState { restart = Restart.FALSE; // needs to play before setting position to resume without lag later - MusicController.play(GameMod.getSpeedMultiplier(), false); + MusicController.play(false); MusicController.setPosition(0); + MusicController.setPitch(GameMod.getSpeedMultiplier()); MusicController.pause(); } skipButton.resetHover(); + if (isReplay || GameMod.AUTO.isActive()) + playbackButton.resetHover(); } @Override @@ -1280,6 +1302,7 @@ public class Game extends BasicGameState { MusicController.resume(); } MusicController.setPosition(firstObjectTime - SKIP_OFFSET); + MusicController.setPitch(GameMod.getSpeedMultiplier()); replaySkipTime = (isReplay) ? -1 : trackPosition; if (isReplay) { replayX = (int) skipButton.getX(); @@ -1317,6 +1340,12 @@ public class Game extends BasicGameState { } skipButton.setHoverExpand(1.1f, MenuButton.Expand.UP_LEFT); + if (isReplay || GameMod.AUTO.isActive()) { + Image playback = GameImage.REPLAY_1XPLAYBACK.getImage(); + playbackButton = new MenuButton(playback, width * 0.98f - (playback.getWidth() / 2f), height * 0.25f); + playbackButton.setHoverExpand(1.1f, MenuButton.Expand.CENTER); + } + // load other images... ((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages(); data.loadImages();