Follow-up to #68.

Fixes:
- Set the modified speed again after unpausing and loading from checkpoints.
- Changed countdown delays based on current speed.
- Changed color of highlighted song info text to that in osu!.
- Made playback images unskinnable.

Code changes:
- Changed playback field in Game class to the PlaybackSpeed object instead of just the button.
- Changed PlaybackSpeed.next() to a non-static method.
- Added/edited Javadocs.
- Changed image names.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-04-03 18:08:35 -04:00
parent 420284af4f
commit 2efb18e225
12 changed files with 115 additions and 89 deletions

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -104,10 +104,6 @@ public enum GameImage {
} }
}, },
REPLAY_05XPLAYBACK ("playback-05x", "png"),
REPLAY_1XPLAYBACK ("playback-1x", "png"),
REPLAY_2XPLAYBACK ("playback-2x", "png"),
// Circle // Circle
HITCIRCLE ("hitcircle", "png"), HITCIRCLE ("hitcircle", "png"),
HITCIRCLE_OVERLAY ("hitcircleoverlay", "png"), HITCIRCLE_OVERLAY ("hitcircleoverlay", "png"),
@ -232,6 +228,11 @@ public enum GameImage {
SELECTION_OTHER_OPTIONS ("selection-selectoptions", "png", false, false), SELECTION_OTHER_OPTIONS ("selection-selectoptions", "png", false, false),
SELECTION_OTHER_OPTIONS_OVERLAY ("selection-selectoptions-over", "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 // Non-Game Components
VOLUME ("volume-bg", "png", false, false) { VOLUME ("volume-bg", "png", false, false) {
@Override @Override

View File

@ -173,7 +173,7 @@ public enum GameMod {
/** The last calculated score multiplier, or -1f if it must be recalculated. */ /** The last calculated score multiplier, or -1f if it must be recalculated. */
private static float scoreMultiplier = -1f; private static float scoreMultiplier = -1f;
/** */ /** The last calculated track speed multiplier, or -1f if it must be recalculated. */
private static float speedMultiplier = -1f; 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() { public static float getSpeedMultiplier() {
if (speedMultiplier < 0f) { if (speedMultiplier < 0f) {

View File

@ -22,6 +22,7 @@ import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.downloads.Download; import itdelatrisu.opsu.downloads.Download;
import itdelatrisu.opsu.downloads.DownloadNode; import itdelatrisu.opsu.downloads.DownloadNode;
import itdelatrisu.opsu.replay.PlaybackSpeed;
import java.awt.Font; import java.awt.Font;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -49,7 +50,6 @@ import java.util.Scanner;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import itdelatrisu.opsu.replay.PlaybackSpeed;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
@ -91,7 +91,9 @@ public class Utils {
COLOR_LIGHT_GREEN = new Color(128,255,128), COLOR_LIGHT_GREEN = new Color(128,255,128),
COLOR_LIGHT_BLUE = new Color(128,128,255), COLOR_LIGHT_BLUE = new Color(128,128,255),
COLOR_GREEN_SEARCH = new Color(173, 255, 47), 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. */ /** The default map colors, used when a map does not provide custom colors. */
public static final Color[] DEFAULT_COMBO = { public static final Color[] DEFAULT_COMBO = {

View File

@ -298,8 +298,8 @@ public class MusicController {
} }
/** /**
* Sets the music pitch. * Sets the music pitch (and speed).
* @param pitch [0, ..] * @param pitch
*/ */
public static void setPitch(float pitch) { public static void setPitch(float pitch) {
SoundStore.get().setMusicPitch(pitch); SoundStore.get().setMusicPitch(pitch);

View File

@ -16,13 +16,13 @@
* along with opsu!. If not, see <http://www.gnu.org/licenses/>. * along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/ */
package itdelatrisu.opsu.replay;
/** /**
* Captures a single life frame. * Captures a single life frame.
* *
* @author smoogipooo (https://github.com/smoogipooo/osu-Replay-API/) * @author smoogipooo (https://github.com/smoogipooo/osu-Replay-API/)
*/ */
package itdelatrisu.opsu.replay;
public class LifeFrame { public class LifeFrame {
/** Time. */ /** Time. */
private int time; private int time;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu.replay; package itdelatrisu.opsu.replay;
import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameMod;
import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.MenuButton;
import org.newdawn.slick.Image; import org.newdawn.slick.Image;
/**
* Playback speeds for replays.
*
* @author DarkTigrus (https://github.com/DarkTigrus)
*/
public enum PlaybackSpeed { public enum PlaybackSpeed {
NORMAL(GameImage.REPLAY_1XPLAYBACK, 1f), NORMAL (GameImage.REPLAY_PLAYBACK_NORMAL, 1f),
DOUBLE(GameImage.REPLAY_2XPLAYBACK, 2f), DOUBLE (GameImage.REPLAY_PLAYBACK_DOUBLE, 2f),
HALF(GameImage.REPLAY_05XPLAYBACK, 0.5f); HALF (GameImage.REPLAY_PLAYBACK_HALF, 0.5f);
/** The file name of the button image. */ /** The button image. */
private GameImage gameImage; private GameImage gameImage;
/** The button of the playback. */ /** The button. */
private MenuButton button; private MenuButton button;
/** The speed modifier of the playback. */ /** The playback speed modifier. */
private float modifier; private float modifier;
PlaybackSpeed(GameImage gameImage, float modifier) { /** Enum values. */
this.gameImage = gameImage; private static PlaybackSpeed[] values = PlaybackSpeed.values();
this.modifier = modifier;
}
/**
* Initializes the playback buttons.
* @param width the container width
* @param height the container height
*/
public static void init(int width, int height) { public static void init(int width, int height) {
// create buttons
for (PlaybackSpeed playback : PlaybackSpeed.values()) { for (PlaybackSpeed playback : PlaybackSpeed.values()) {
Image img = playback.gameImage.getImage(); Image img = playback.gameImage.getImage();
playback.button = new MenuButton(img, width * 0.98f - (img.getWidth() / 2f), height * 0.25f); 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; /**
* Constructor.
public static PlaybackSpeed next() { * @param gameImage the button image
PlaybackSpeed next = values()[index++ % values().length]; * @param modifier the speed modifier
if((GameMod.DOUBLE_TIME.isActive() && next == PlaybackSpeed.DOUBLE)) */
next = next(); PlaybackSpeed(GameImage gameImage, float modifier) {
this.gameImage = gameImage;
return next; this.modifier = modifier;
}
public static void reset() {
index = 1;
} }
/** /**
@ -58,5 +80,14 @@ public enum PlaybackSpeed {
* @return the speed * @return the speed
*/ */
public float getModifier() { return modifier; } 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;
}
}

View File

@ -132,9 +132,6 @@ public class Game extends BasicGameState {
/** Skip button (displayed at song start, when necessary). */ /** Skip button (displayed at song start, when necessary). */
private MenuButton skipButton; private MenuButton skipButton;
/** Playback button (displayed in replays). */
private MenuButton playbackButton;
/** Current timing point index in timingPoints ArrayList. */ /** Current timing point index in timingPoints ArrayList. */
private int timingPointIndex; private int timingPointIndex;
@ -215,6 +212,9 @@ public class Game extends BasicGameState {
/** Whether or not the cursor should be pressed using the "auto" mod. */ /** Whether or not the cursor should be pressed using the "auto" mod. */
private boolean autoMousePressed; private boolean autoMousePressed;
/** Playback speed (used in replays and "auto" mod). */
private PlaybackSpeed playbackSpeed;
// game-related variables // game-related variables
private GameContainer container; private GameContainer container;
private StateBasedGame game; private StateBasedGame game;
@ -465,40 +465,41 @@ public class Game extends BasicGameState {
trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in
// countdown // countdown
if (osu.countdown > 0) { // TODO: implement half/double rate settings if (osu.countdown > 0) {
float speedModifier = GameMod.getSpeedMultiplier() * playbackSpeed.getModifier();
timeDiff = firstObjectTime - trackPosition; timeDiff = firstObjectTime - trackPosition;
if (timeDiff >= 500 && timeDiff < 3000) { if (timeDiff >= 500 * speedModifier && timeDiff < 3000 * speedModifier) {
if (timeDiff >= 1500) { if (timeDiff >= 1500 * speedModifier) {
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2); GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
if (!countdownReadySound) { if (!countdownReadySound) {
SoundController.playSound(SoundEffect.READY); SoundController.playSound(SoundEffect.READY);
countdownReadySound = true; countdownReadySound = true;
} }
} }
if (timeDiff < 2000) { if (timeDiff < 2000 * speedModifier) {
GameImage.COUNTDOWN_3.getImage().draw(0, 0); GameImage.COUNTDOWN_3.getImage().draw(0, 0);
if (!countdown3Sound) { if (!countdown3Sound) {
SoundController.playSound(SoundEffect.COUNT3); SoundController.playSound(SoundEffect.COUNT3);
countdown3Sound = true; countdown3Sound = true;
} }
} }
if (timeDiff < 1500) { if (timeDiff < 1500 * speedModifier) {
GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0); GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0);
if (!countdown2Sound) { if (!countdown2Sound) {
SoundController.playSound(SoundEffect.COUNT2); SoundController.playSound(SoundEffect.COUNT2);
countdown2Sound = true; countdown2Sound = true;
} }
} }
if (timeDiff < 1000) { if (timeDiff < 1000 * speedModifier) {
GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2); GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2);
if (!countdown1Sound) { if (!countdown1Sound) {
SoundController.playSound(SoundEffect.COUNT1); SoundController.playSound(SoundEffect.COUNT1);
countdown1Sound = true; countdown1Sound = true;
} }
} }
} else if (timeDiff >= -500 && timeDiff < 500) { } else if (timeDiff >= -500 * speedModifier && timeDiff < 500 * speedModifier) {
Image go = GameImage.COUNTDOWN_GO.getImage(); 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); go.drawCentered(width / 2, height / 2);
if (!countdownGoSound) { if (!countdownGoSound) {
SoundController.playSound(SoundEffect.GO); SoundController.playSound(SoundEffect.GO);
@ -515,6 +516,10 @@ public class Game extends BasicGameState {
if (GameMod.AUTO.isActive()) if (GameMod.AUTO.isActive())
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); 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 // returning from pause screen
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
// darken the screen // darken the screen
@ -531,9 +536,6 @@ public class Game extends BasicGameState {
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY); cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
} }
if (isReplay || GameMod.AUTO.isActive())
playbackButton.draw();
if (isReplay) if (isReplay)
UI.draw(g, replayX, replayY, replayKeyPressed); UI.draw(g, replayX, replayY, replayKeyPressed);
else if (GameMod.AUTO.isActive()) else if (GameMod.AUTO.isActive())
@ -552,7 +554,7 @@ public class Game extends BasicGameState {
int mouseX = input.getMouseX(), mouseY = input.getMouseY(); int mouseX = input.getMouseX(), mouseY = input.getMouseY();
skipButton.hoverUpdate(delta, mouseX, mouseY); skipButton.hoverUpdate(delta, mouseX, mouseY);
if (isReplay || GameMod.AUTO.isActive()) if (isReplay || GameMod.AUTO.isActive())
playbackButton.hoverUpdate(delta, mouseX, mouseY); playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
// returning from pause screen: must click previous mouse position // returning from pause screen: must click previous mouse position
@ -850,6 +852,7 @@ public class Game extends BasicGameState {
// skip to checkpoint // skip to checkpoint
MusicController.setPosition(checkpoint); MusicController.setPosition(checkpoint);
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
while (objectIndex < hitObjects.length && while (objectIndex < hitObjects.length &&
osu.objects[objectIndex++].getTime() <= checkpoint) osu.objects[objectIndex++].getTime() <= checkpoint)
; ;
@ -882,17 +885,19 @@ public class Game extends BasicGameState {
public void mousePressed(int button, int x, int y) { public void mousePressed(int button, int x, int y) {
// watching replay // watching replay
if (isReplay || GameMod.AUTO.isActive()) { if (isReplay || GameMod.AUTO.isActive()) {
// allow skip button if (button == Input.MOUSE_MIDDLE_BUTTON)
if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y)) { return;
// skip button
if (skipButton.contains(x, y))
skipIntro(); skipIntro();
return;
} // playback speed button
if (button != Input.MOUSE_MIDDLE_BUTTON && playbackButton.contains(x, y)) { else if (playbackSpeed.getButton().contains(x, y)) {
PlaybackSpeed playbackSpeed = PlaybackSpeed.next(); playbackSpeed = playbackSpeed.next();
playbackButton = playbackSpeed.getButton();
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
return;
} }
return; return;
} }
@ -1102,8 +1107,6 @@ public class Game extends BasicGameState {
previousMods = GameMod.getModState(); previousMods = GameMod.getModState();
GameMod.loadModState(replay.mods); GameMod.loadModState(replay.mods);
PlaybackSpeed.reset();
// load initial data // load initial data
replayX = container.getWidth() / 2; replayX = container.getWidth() / 2;
replayY = container.getHeight() / 2; replayY = container.getHeight() / 2;
@ -1143,7 +1146,8 @@ public class Game extends BasicGameState {
skipButton.resetHover(); skipButton.resetHover();
if (isReplay || GameMod.AUTO.isActive()) if (isReplay || GameMod.AUTO.isActive())
playbackButton.resetHover(); playbackSpeed.getButton().resetHover();
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
} }
@Override @Override
@ -1288,6 +1292,7 @@ public class Game extends BasicGameState {
autoMouseY = 0; autoMouseY = 0;
autoMousePressed = false; autoMousePressed = false;
flashlightRadius = container.getHeight() * 2 / 3; flashlightRadius = container.getHeight() * 2 / 3;
playbackSpeed = PlaybackSpeed.NORMAL;
System.gc(); System.gc();
} }
@ -1305,7 +1310,7 @@ public class Game extends BasicGameState {
MusicController.resume(); MusicController.resume();
} }
MusicController.setPosition(firstObjectTime - SKIP_OFFSET); MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
MusicController.setPitch(GameMod.getSpeedMultiplier()); MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
replaySkipTime = (isReplay) ? -1 : trackPosition; replaySkipTime = (isReplay) ? -1 : trackPosition;
if (isReplay) { if (isReplay) {
replayX = (int) skipButton.getX(); replayX = (int) skipButton.getX();
@ -1343,9 +1348,6 @@ public class Game extends BasicGameState {
} }
skipButton.setHoverExpand(1.1f, MenuButton.Expand.UP_LEFT); skipButton.setHoverExpand(1.1f, MenuButton.Expand.UP_LEFT);
if (isReplay || GameMod.AUTO.isActive())
playbackButton = PlaybackSpeed.NORMAL.getButton();
// load other images... // load other images...
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages(); ((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
data.loadImages(); data.loadImages();

View File

@ -337,7 +337,11 @@ public class SongMenu extends BasicGameState {
// song info text // song info text
if (songInfo == null) { 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; marginX += 5;
float headerTextY = marginY; float headerTextY = marginY;
@ -345,8 +349,10 @@ public class SongMenu extends BasicGameState {
headerTextY += Utils.FONT_LARGE.getLineHeight() - 8; headerTextY += Utils.FONT_LARGE.getLineHeight() - 8;
Utils.FONT_DEFAULT.drawString(marginX + iconWidth * 1.05f, headerTextY, songInfo[1], Color.white); Utils.FONT_DEFAULT.drawString(marginX + iconWidth * 1.05f, headerTextY, songInfo[1], Color.white);
headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 2; headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 2;
Utils.FONT_BOLD.drawString(marginX, headerTextY, songInfo[2], float speedModifier = GameMod.getSpeedMultiplier();
(GameMod.DOUBLE_TIME.isActive()) ? Color.red : (GameMod.HALF_TIME.isActive()) ? Color.green : Color.white); 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; headerTextY += Utils.FONT_BOLD.getLineHeight() - 4;
Utils.FONT_DEFAULT.drawString(marginX, headerTextY, songInfo[3], Color.white); Utils.FONT_DEFAULT.drawString(marginX, headerTextY, songInfo[3], Color.white);
headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 4; headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 4;
@ -934,6 +940,7 @@ public class SongMenu extends BasicGameState {
startScore = 0; startScore = 0;
beatmapMenuTimer = -1; beatmapMenuTimer = -1;
searchTransitionTimer = SEARCH_TRANSITION_TIME; searchTransitionTimer = SEARCH_TRANSITION_TIME;
songInfo = null;
// reset song stack // reset song stack
randomStack = new Stack<SongNode>(); randomStack = new Stack<SongNode>();
@ -979,9 +986,6 @@ public class SongMenu extends BasicGameState {
resetGame = false; resetGame = false;
} }
// load song info
songInfo = getSongInfo();
// state-based action // state-based action
if (stateAction != null) { if (stateAction != null) {
switch (stateAction) { switch (stateAction) {
@ -1302,20 +1306,6 @@ public class SongMenu extends BasicGameState {
return null; // incorrect map 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. * Starts the game.
*/ */