From d8425197a771fc4e7b2ef6b9fea26150adf592b9 Mon Sep 17 00:00:00 2001 From: Pavel Kolchev Date: Thu, 2 Apr 2015 19:38:45 +0300 Subject: [PATCH] Implement playback speed in replays. --- res/playback-05x.png | Bin 0 -> 2201 bytes res/playback-1x.png | Bin 0 -> 2070 bytes res/playback-2x.png | Bin 0 -> 2195 bytes src/itdelatrisu/opsu/GameImage.java | 4 ++ src/itdelatrisu/opsu/MenuButton.java | 5 ++ .../opsu/audio/MusicController.java | 12 ++++- .../opsu/replay/PlaybackSpeed.java | 50 ++++++++++++++++++ src/itdelatrisu/opsu/states/Game.java | 43 ++++++++++++--- 8 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 res/playback-05x.png create mode 100644 res/playback-1x.png create mode 100644 res/playback-2x.png create mode 100644 src/itdelatrisu/opsu/replay/PlaybackSpeed.java diff --git a/res/playback-05x.png b/res/playback-05x.png new file mode 100644 index 0000000000000000000000000000000000000000..358d7e0c752ba578021fac37081a2cd1895f55d3 GIT binary patch literal 2201 zcmV;K2xj+*P)z;o9_%1Uj!b)AI6C>Gh(2cDC`c z@~t^|cfk&{TJ-a`%_!Z{bkj2;1a46k(Mrqza!yfr4iEr*f3{gsQONCk3lh{-pOhP9 zEj&0nk;$C9LX#4|Co<{R=UYUiy@PoJo13ccyyhWq+ghJCNBckMOL>ReGV`iiGV-ch zvQG?Yrwm8+T0`iClC`E&%eZJp5ipVz_T#*jaZP#2aZ%DWKcQ~}%Z|PF`5#obq%Qrv zwep&z4vU|FN3gA2zoA4yX{irKoP6))SnI(xrk1^nO$X*g7!A@j7AS?spMKUL99nM9 z2tTiL`#vspqQP-!g*hYqJ6+F293+|U(H3o37}LCWv1wCky8$aA8qh{JNOCgsCZ|N6 z$$u@b^W&AK)9c2@43!)E`zuLI&Krwj93`)qUX1xqn7bxGa0?~4--5wM9tw+@fWIlq ziS8&~ZaThvRP<=LtOWqDIa(b*+j>e!N?XFszsIhdl`$iHs;Lt#%Kos^%Eu@JJEKB~ zM#q{$Fe0)Dg39!FfC9vi3}uV=I|L~8Z$oh+ zkY&6v_jTi*WocSyI-?erR7z)B!)OdAG9K&1Xz?2>7e=m5LCnvy-t7FadAJ==k2JTR z5D{{T<0D^5_GVi0*(J#;0yPgG!=yix!PO<1BN7)Bw_fE;zM&TCoflD@475*nWf;1BUtKF;mV&CFvqeY5gz=-J zXFin5M5v!KZNNx->YJ5yPrty$?moa;nR{`1{1E0u@iAuy8>s+MF#rIOF41W#wzq$U zhCBK4OPVvEtcskM`GoeJb(d@kt5tp5IK&9L2e|o-G^cTqe86l{uIMr6=zRyrjz_pjU@sBjfh#9nb}}GzTv3b5>co@nMh9g% z3aaes3{lWelE;R>za&i?AFIahiJs0Z5YBcUDdgWdu7V{7AHAzDbW2=Ky7-D<>_;KiRVM;KkNjo4Jd6trQXH^+Q8S z-=BU~$<|M$o61S(`3=6SJF21=7A*=t?J(f1;KI6Sy+En8TI2OY<Erq^Pu!ooey?iQSdl4S|S)fMIFB_I_*QyO0Q2BpL7Cu_k7sy@V!Yf7c>$Vg2vJ z1`+X?1u*rorZ=`YN&%QYXWqfO`uQQDD(Fb3=Bboq{j^~W+O@x02sDA*c&+X{u7Y|+ z#*GN_6)ksD6sZ(VU!0}il^)9-Ik;8Y^6gMN^gm(_ItcmL?t%mkVwnYS@SiAw5B7Hp z9wacpBq&lqLjeE}e480$u))DV3T!9=K$Ox(0sy@uiYNda?y0C%A4*~XS3LIWSG(K2 zKB;Ri_w(jG?S3Nf&CsXY$8dn|e}j6n?Jl7F`Q_7Zp9Xtu_H|H2Jzrzho;9KffFRdU bzPtZ_v2E>8ouz-)00000NkvXXu0mjf>PRnb literal 0 HcmV?d00001 diff --git a/res/playback-1x.png b/res/playback-1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a5949368a70f48382929c956803e6d540403178f GIT binary patch literal 2070 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 literal 0 HcmV?d00001 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();