Follow-up to #99: replay seeking improvements.

- Added on/off option for replay seeking in the "custom" menu.
- Mute sounds while seeking.
- Draw a bar on the left of the screen during replays for seeking (instead of just clicking near the top of the screen).

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-07-02 22:16:14 -05:00
parent 495a7e7f8b
commit 2167698740
4 changed files with 70 additions and 8 deletions

View File

@ -453,7 +453,8 @@ public class Options {
val - TimeUnit.MINUTES.toSeconds(TimeUnit.SECONDS.toMinutes(val))); val - TimeUnit.MINUTES.toSeconds(TimeUnit.SECONDS.toMinutes(val)));
} }
}, },
ENABLE_THEME_SONG ("Enable Theme Song", "MenuMusic", "Whether to play the theme song upon starting opsu!", true); ENABLE_THEME_SONG ("Enable Theme Song", "MenuMusic", "Whether to play the theme song upon starting opsu!", true),
REPLAY_SEEKING ("Replay Seeking", "ReplaySeeking", "Enable a seeking bar on the left side of the screen during replays.", false);
/** Option name. */ /** Option name. */
private String name; private String name;
@ -958,6 +959,12 @@ public class Options {
*/ */
public static boolean isThemeSongEnabled() { return GameOption.ENABLE_THEME_SONG.getBooleanValue(); } public static boolean isThemeSongEnabled() { return GameOption.ENABLE_THEME_SONG.getBooleanValue(); }
/**
* Returns whether or not replay seeking is enabled.
* @return true if enabled
*/
public static boolean isReplaySeekingEnabled() { return GameOption.REPLAY_SEEKING.getBooleanValue(); }
/** /**
* Sets the track checkpoint time, if within bounds. * Sets the track checkpoint time, if within bounds.
* @param time the track position (in ms) * @param time the track position (in ms)

View File

@ -59,6 +59,9 @@ public class SoundController {
/** Sample volume multiplier, from timing points [0, 1]. */ /** Sample volume multiplier, from timing points [0, 1]. */
private static float sampleVolumeMultiplier = 1f; private static float sampleVolumeMultiplier = 1f;
/** Whether all sounds are muted. */
private static boolean isMuted;
/** The name of the current sound file being loaded. */ /** The name of the current sound file being loaded. */
private static String currentFileName; private static String currentFileName;
@ -261,7 +264,7 @@ public class SoundController {
if (clip == null) // clip failed to load properly if (clip == null) // clip failed to load properly
return; return;
if (volume > 0f) { if (volume > 0f && !isMuted) {
try { try {
clip.start(volume, listener); clip.start(volume, listener);
} catch (LineUnavailableException e) { } catch (LineUnavailableException e) {
@ -317,6 +320,12 @@ public class SoundController {
playClip(s.getClip(), Options.getHitSoundVolume() * sampleVolumeMultiplier * Options.getMasterVolume(), null); playClip(s.getClip(), Options.getHitSoundVolume() * sampleVolumeMultiplier * Options.getMasterVolume(), null);
} }
/**
* Mutes or unmutes all sounds (hit sounds and sound effects).
* @param mute true to mute, false to unmute
*/
public static void mute(boolean mute) { isMuted = mute; }
/** /**
* Returns the name of the current file being loaded, or null if none. * Returns the name of the current file being loaded, or null if none.
*/ */

View File

@ -220,6 +220,15 @@ public class Game extends BasicGameState {
/** Whether the game is currently seeking to a replay position. */ /** Whether the game is currently seeking to a replay position. */
private boolean isSeeking; private boolean isSeeking;
/** Music position bar coordinates and dimensions (for replay seeking). */
private float musicBarX, musicBarY, musicBarWidth, musicBarHeight;
/** Music position bar background colors. */
private static final Color
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
MUSICBAR_HOVER = new Color(12, 9, 10, 0.35f),
MUSICBAR_FILL = new Color(255, 255, 255, 0.75f);
// game-related variables // game-related variables
private GameContainer container; private GameContainer container;
private StateBasedGame game; private StateBasedGame game;
@ -245,6 +254,12 @@ public class Game extends BasicGameState {
gOffscreen = offscreen.getGraphics(); gOffscreen = offscreen.getGraphics();
gOffscreen.setBackground(Color.black); gOffscreen.setBackground(Color.black);
// initialize music position bar location
musicBarX = width * 0.01f;
musicBarY = height * 0.05f;
musicBarWidth = Math.max(width * 0.005f, 7);
musicBarHeight = height * 0.9f;
// create the associated GameData object // create the associated GameData object
data = new GameData(width, height); data = new GameData(width, height);
} }
@ -525,6 +540,18 @@ public class Game extends BasicGameState {
if (isReplay || GameMod.AUTO.isActive()) if (isReplay || GameMod.AUTO.isActive())
playbackSpeed.getButton().draw(); playbackSpeed.getButton().draw();
// draw music position bar (for replay seeking)
if (isReplay && Options.isReplaySeekingEnabled()) {
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
g.setColor((musicPositionBarContains(mouseX, mouseY)) ? MUSICBAR_HOVER : MUSICBAR_NORMAL);
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4);
if (!isLeadIn()) {
g.setColor(MUSICBAR_FILL);
float musicBarPosition = Math.min((float) trackPosition / beatmap.endTime, 1f);
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight * musicBarPosition, 4);
}
}
// returning from pause screen // returning from pause screen
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
// darken the screen // darken the screen
@ -613,7 +640,7 @@ public class Game extends BasicGameState {
if (replayIndex >= replay.frames.length) if (replayIndex >= replay.frames.length)
updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed); updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed);
//TODO probably should to disable sounds then reseek to the new position // seeking to a position earlier than original track position
if (isSeeking && replayIndex - 1 >= 1 && replayIndex < replay.frames.length && if (isSeeking && replayIndex - 1 >= 1 && replayIndex < replay.frames.length &&
trackPosition < replay.frames[replayIndex - 1].getTime()) { trackPosition < replay.frames[replayIndex - 1].getTime()) {
replayIndex = 0; replayIndex = 0;
@ -633,7 +660,6 @@ public class Game extends BasicGameState {
timingPointIndex++; timingPointIndex++;
} }
} }
isSeeking = false;
} }
// update and run replay frames // update and run replay frames
@ -648,6 +674,12 @@ public class Game extends BasicGameState {
} }
mouseX = replayX; mouseX = replayX;
mouseY = replayY; mouseY = replayY;
// unmute sounds
if (isSeeking) {
isSeeking = false;
SoundController.mute(false);
}
} }
data.updateDisplays(delta); data.updateDisplays(delta);
@ -923,9 +955,10 @@ public class Game extends BasicGameState {
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
} }
// TODO // replay seeking
else if (!GameMod.AUTO.isActive() && y < 50) { else if (Options.isReplaySeekingEnabled() && !GameMod.AUTO.isActive() && musicPositionBarContains(x, y)) {
float pos = (float) x / container.getWidth() * beatmap.endTime; SoundController.mute(true); // mute sounds while seeking
float pos = (y - musicBarY) / musicBarHeight * beatmap.endTime;
MusicController.setPosition((int) pos); MusicController.setPosition((int) pos);
isSeeking = true; isSeeking = true;
} }
@ -1188,6 +1221,8 @@ public class Game extends BasicGameState {
MusicController.setPosition(0); MusicController.setPosition(0);
MusicController.setPitch(GameMod.getSpeedMultiplier()); MusicController.setPitch(GameMod.getSpeedMultiplier());
MusicController.pause(); MusicController.pause();
SoundController.mute(false);
} }
skipButton.resetHover(); skipButton.resetHover();
@ -1753,4 +1788,14 @@ public class Game extends BasicGameState {
gameObjects[i].updatePosition(); gameObjects[i].updatePosition();
} }
} }
/**
* Returns true if the coordinates are within the music position bar bounds.
* @param cx the x coordinate
* @param cy the y coordinate
*/
private boolean musicPositionBarContains(float cx, float cy) {
return ((cx > musicBarX && cx < musicBarX + musicBarWidth) &&
(cy > musicBarY && cy < musicBarY + musicBarHeight));
}
} }

View File

@ -93,7 +93,8 @@ public class OptionsMenu extends BasicGameState {
GameOption.FIXED_HP, GameOption.FIXED_HP,
GameOption.FIXED_AR, GameOption.FIXED_AR,
GameOption.FIXED_OD, GameOption.FIXED_OD,
GameOption.CHECKPOINT GameOption.CHECKPOINT,
GameOption.REPLAY_SEEKING
}); });
/** Total number of tabs. */ /** Total number of tabs. */