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.
*/