Merge pull request #68 from DarkTigrus/doubletime

HalfTime and DoubleTime mod
This commit is contained in:
Jeffrey Han 2015-04-03 16:46:52 -04:00
commit 420284af4f
11 changed files with 169 additions and 26 deletions

BIN
res/playback-05x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
res/playback-1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
res/playback-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -104,6 +104,10 @@ 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"),

View File

@ -33,7 +33,7 @@ public enum GameMod {
"Easy", "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."), "Easy", "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."),
NO_FAIL (Category.EASY, 1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f, NO_FAIL (Category.EASY, 1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f,
"NoFail", "You can't fail. No matter what."), "NoFail", "You can't fail. No matter what."),
HALF_TIME (Category.EASY, 2, GameImage.MOD_HALF_TIME, "HT", 256, Input.KEY_E, 0.3f, false, HALF_TIME (Category.EASY, 2, GameImage.MOD_HALF_TIME, "HT", 256, Input.KEY_E, 0.3f,
"HalfTime", "Less zoom."), "HalfTime", "Less zoom."),
HARD_ROCK (Category.HARD, 0, GameImage.MOD_HARD_ROCK, "HR", 16, Input.KEY_A, 1.06f, HARD_ROCK (Category.HARD, 0, GameImage.MOD_HARD_ROCK, "HR", 16, Input.KEY_A, 1.06f,
"HardRock", "Everything just got a bit harder..."), "HardRock", "Everything just got a bit harder..."),
@ -41,7 +41,7 @@ public enum GameMod {
"SuddenDeath", "Miss a note and fail."), "SuddenDeath", "Miss a note and fail."),
// PERFECT (Category.HARD, 1, GameImage.MOD_PERFECT, "PF", 64, Input.KEY_S, 1f, // PERFECT (Category.HARD, 1, GameImage.MOD_PERFECT, "PF", 64, Input.KEY_S, 1f,
// "Perfect", "SS or quit."), // "Perfect", "SS or quit."),
DOUBLE_TIME (Category.HARD, 2, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f, false, DOUBLE_TIME (Category.HARD, 2, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f,
"DoubleTime", "Zoooooooooom."), "DoubleTime", "Zoooooooooom."),
// NIGHTCORE (Category.HARD, 2, GameImage.MOD_NIGHTCORE, "NT", 64, Input.KEY_D, 1.12f, // NIGHTCORE (Category.HARD, 2, GameImage.MOD_NIGHTCORE, "NT", 64, Input.KEY_D, 1.12f,
// "Nightcore", "uguuuuuuuu"), // "Nightcore", "uguuuuuuuu"),
@ -173,6 +173,9 @@ 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;
/** */
private static float speedMultiplier = -1f;
/** /**
* Initializes the game mods. * Initializes the game mods.
* @param width the container width * @param width the container width
@ -198,7 +201,7 @@ public enum GameMod {
mod.active = false; mod.active = false;
} }
scoreMultiplier = -1f; scoreMultiplier = speedMultiplier = -1f;
} }
/** /**
@ -216,6 +219,21 @@ public enum GameMod {
return scoreMultiplier; return scoreMultiplier;
} }
/**
*
*/
public static float getSpeedMultiplier() {
if (speedMultiplier < 0f) {
float multiplier = 1f;
if (DOUBLE_TIME.isActive())
multiplier = 1.5f;
else if (HALF_TIME.isActive())
multiplier = 0.75f;
speedMultiplier = multiplier;
}
return speedMultiplier;
}
/** /**
* Returns the current game mod state (bitwise OR of active mods). * Returns the current game mod state (bitwise OR of active mods).
*/ */
@ -233,6 +251,7 @@ public enum GameMod {
* @param state the state (bitwise OR of active mods) * @param state the state (bitwise OR of active mods)
*/ */
public static void loadModState(int state) { public static void loadModState(int state) {
scoreMultiplier = speedMultiplier = -1f;
for (GameMod mod : GameMod.values()) for (GameMod mod : GameMod.values())
mod.active = ((state & mod.getBit()) > 0); mod.active = ((state & mod.getBit()) > 0);
} }
@ -352,7 +371,7 @@ public enum GameMod {
return; return;
active = !active; active = !active;
scoreMultiplier = -1f; scoreMultiplier = speedMultiplier = -1f;
if (checkInverse) { if (checkInverse) {
if (AUTO.isActive()) { if (AUTO.isActive()) {

View File

@ -118,16 +118,19 @@ public class OsuGroupNode {
return null; return null;
OsuFile osu = osuFiles.get(osuFileIndex); OsuFile osu = osuFiles.get(osuFileIndex);
float speedModifier = GameMod.getSpeedMultiplier();
long endTime = (long) (osu.endTime / speedModifier);
int bpmMin = (int) (osu.bpmMin * speedModifier);
int bpmMax = (int) (osu.bpmMax * speedModifier);
String[] info = new String[5]; String[] info = new String[5];
info[0] = osu.toString(); info[0] = osu.toString();
info[1] = String.format("Mapped by %s", info[1] = String.format("Mapped by %s",
osu.creator); osu.creator);
info[2] = String.format("Length: %d:%02d BPM: %s Objects: %d", info[2] = String.format("Length: %d:%02d BPM: %s Objects: %d",
TimeUnit.MILLISECONDS.toMinutes(osu.endTime), TimeUnit.MILLISECONDS.toMinutes(endTime),
TimeUnit.MILLISECONDS.toSeconds(osu.endTime) - TimeUnit.MILLISECONDS.toSeconds(endTime) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(osu.endTime)), TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(endTime)),
(osu.bpmMax <= 0) ? "--" : (bpmMax <= 0) ? "--" : ((bpmMin == bpmMax) ? bpmMin : String.format("%d-%d", bpmMin, bpmMax)),
((osu.bpmMin == osu.bpmMax) ? osu.bpmMin : String.format("%d-%d", osu.bpmMin, osu.bpmMax)),
(osu.hitObjectCircle + osu.hitObjectSlider + osu.hitObjectSpinner)); (osu.hitObjectCircle + osu.hitObjectSlider + osu.hitObjectSpinner));
info[3] = String.format("Circles: %d Sliders: %d Spinners: %d", info[3] = String.format("Circles: %d Sliders: %d Spinners: %d",
osu.hitObjectCircle, osu.hitObjectSlider, osu.hitObjectSpinner); osu.hitObjectCircle, osu.hitObjectSlider, osu.hitObjectSpinner);

View File

@ -49,6 +49,7 @@ 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;
@ -180,6 +181,9 @@ public class Utils {
// initialize game mods // initialize game mods
GameMod.init(width, height); GameMod.init(width, height);
// initialize playback buttons
PlaybackSpeed.init(width, height);
// initialize hit objects // initialize hit objects
OsuHitObject.init(width, height); OsuHitObject.init(width, height);

View File

@ -297,6 +297,14 @@ public class MusicController {
SoundStore.get().setMusicVolume((isTrackDimmed()) ? volume * dimLevel : volume); 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. * Returns whether or not the current track has ended.
*/ */

View File

@ -0,0 +1,62 @@
package itdelatrisu.opsu.replay;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameMod;
import itdelatrisu.opsu.MenuButton;
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 button of the playback. */
private MenuButton button;
/** The speed modifier of the playback. */
private float modifier;
PlaybackSpeed(GameImage gameImage, float modifier) {
this.gameImage = gameImage;
this.modifier = modifier;
}
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);
playback.button.setHoverFade();
}
}
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;
}
/**
* Returns the button.
* @return the associated button
*/
public MenuButton getButton() { return button; }
/**
* Returns the speed modifier.
* @return the speed
*/
public float getModifier() { return modifier; }
}

View File

@ -50,6 +50,7 @@ import java.io.File;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Stack; import java.util.Stack;
import itdelatrisu.opsu.replay.PlaybackSpeed;
import org.lwjgl.input.Keyboard; import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import org.newdawn.slick.Animation; import org.newdawn.slick.Animation;
@ -131,6 +132,9 @@ 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;
@ -527,6 +531,9 @@ 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())
@ -544,6 +551,8 @@ public class Game extends BasicGameState {
UI.update(delta); UI.update(delta);
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())
playbackButton.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
@ -871,17 +880,25 @@ public class Game extends BasicGameState {
@Override @Override
public void mousePressed(int button, int x, int y) { public void mousePressed(int button, int x, int y) {
if (Options.isMouseDisabled())
return;
// watching replay // watching replay
if (isReplay) { if (isReplay || GameMod.AUTO.isActive()) {
// only allow skip button // allow skip button
if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y)) if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y)) {
skipIntro(); skipIntro();
return;
}
if (button != Input.MOUSE_MIDDLE_BUTTON && playbackButton.contains(x, y)) {
PlaybackSpeed playbackSpeed = PlaybackSpeed.next();
playbackButton = playbackSpeed.getButton();
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
return;
}
return; return;
} }
if (Options.isMouseDisabled())
return;
// mouse wheel: pause the game // mouse wheel: pause the game
if (button == Input.MOUSE_MIDDLE_BUTTON && !Options.isMouseWheelDisabled()) { if (button == Input.MOUSE_MIDDLE_BUTTON && !Options.isMouseWheelDisabled()) {
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
@ -1023,11 +1040,6 @@ public class Game extends BasicGameState {
// reset game data // reset game data
resetGameData(); resetGameData();
// needs to play before setting position to resume without lag later
MusicController.play(false);
MusicController.setPosition(0);
MusicController.pause();
// initialize object maps // initialize object maps
for (int i = 0; i < osu.objects.length; i++) { for (int i = 0; i < osu.objects.length; i++) {
OsuHitObject hitObject = osu.objects[i]; OsuHitObject hitObject = osu.objects[i];
@ -1090,6 +1102,8 @@ 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;
@ -1119,9 +1133,17 @@ public class Game extends BasicGameState {
leadInTime = osu.audioLeadIn + approachTime; leadInTime = osu.audioLeadIn + approachTime;
restart = Restart.FALSE; restart = Restart.FALSE;
// needs to play before setting position to resume without lag later
MusicController.play(false);
MusicController.setPosition(0);
MusicController.setPitch(GameMod.getSpeedMultiplier());
MusicController.pause();
} }
skipButton.resetHover(); skipButton.resetHover();
if (isReplay || GameMod.AUTO.isActive())
playbackButton.resetHover();
} }
@Override @Override
@ -1136,6 +1158,9 @@ public class Game extends BasicGameState {
// replays // replays
if (isReplay) if (isReplay)
GameMod.loadModState(previousMods); GameMod.loadModState(previousMods);
// reset playback speed
MusicController.setPitch(1f);
} }
/** /**
@ -1280,6 +1305,7 @@ public class Game extends BasicGameState {
MusicController.resume(); MusicController.resume();
} }
MusicController.setPosition(firstObjectTime - SKIP_OFFSET); MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
MusicController.setPitch(GameMod.getSpeedMultiplier());
replaySkipTime = (isReplay) ? -1 : trackPosition; replaySkipTime = (isReplay) ? -1 : trackPosition;
if (isReplay) { if (isReplay) {
replayX = (int) skipButton.getX(); replayX = (int) skipButton.getX();
@ -1317,6 +1343,9 @@ 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,11 +337,7 @@ public class SongMenu extends BasicGameState {
// song info text // song info text
if (songInfo == null) { if (songInfo == null) {
songInfo = focusNode.getInfo(); songInfo = getSongInfo();
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;
@ -349,7 +345,8 @@ 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], Color.white); Utils.FONT_BOLD.drawString(marginX, headerTextY, songInfo[2],
(GameMod.DOUBLE_TIME.isActive()) ? Color.red : (GameMod.HALF_TIME.isActive()) ? Color.green : Color.white);
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;
@ -982,6 +979,9 @@ 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,6 +1302,20 @@ 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.
*/ */