diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index e9a3cbc6..2676686e 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -264,6 +264,10 @@ public class Slider implements GameObject { if (sliderTime == 0) return; + // Don't draw follow ball if already done + if (trackPosition > hitObject.getTime() + sliderTimeTotal) + return; + Vec2f c = curve.pointAt(getT(trackPosition, false)); Vec2f c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 505f5232..41cf84ea 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -56,6 +56,7 @@ import itdelatrisu.opsu.ui.animations.AnimationEquation; import java.io.File; import java.util.LinkedList; import java.util.Stack; +import java.util.IdentityHashMap; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.Display; @@ -90,6 +91,12 @@ public class Game extends BasicGameState { LOSE } + /** Music fade-out time, in milliseconds. */ + private static final int FADEOUT_TIME = 2000; + + /** Maximal rotation over fade out, in degrees */ + private static final float MAX_ROTATION = 90.0f; + /** Minimum time before start of song, in milliseconds, to process skip-related actions. */ private static final int SKIP_OFFSET = 2000; @@ -176,6 +183,12 @@ public class Game extends BasicGameState { /** Track position at death, used if "Easy" mod is enabled. */ private int deathTime = -1; + /** System time position at death. */ + private long failTime; + private int failTrackTime; + + private IdentityHashMap rotations; + /** Number of retries. */ private int retries = 0; @@ -650,6 +663,7 @@ public class Game extends BasicGameState { deathTime = -1; } + // normal game update if (!isReplay) addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition); @@ -793,7 +807,7 @@ public class Game extends BasicGameState { } if (MusicController.isPlaying() || isLeadIn()) pauseTime = trackPosition; - game.enterState(Opsu.STATE_GAMEPAUSEMENU); + game.enterState(Opsu.STATE_GAMEPAUSEMENU, new EmptyTransition(), new FadeInTransition(Color.black)); } // drain health @@ -809,25 +823,38 @@ public class Game extends BasicGameState { } } + // game over, force a restart if (!isReplay) { - restart = Restart.LOSE; - game.enterState(Opsu.STATE_GAMEPAUSEMENU); + if (restart != Restart.LOSE) { + restart = Restart.LOSE; + failTime = System.currentTimeMillis(); + failTrackTime = MusicController.getPosition(); + MusicController.fadeOut(FADEOUT_TIME); + MusicController.pitchFadeOut(FADEOUT_TIME); + rotations = new IdentityHashMap(); + SoundController.playSound(SoundEffect.FAIL); + //fade to pause menu + game.enterState(Opsu.STATE_GAMEPAUSEMENU, new FadeOutTransition(Color.black, FADEOUT_TIME), new FadeInTransition(Color.black)); + } } } - // update objects (loop in unlikely event of any skipped indexes) - boolean keyPressed = keys != ReplayFrame.KEY_NONE; - while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) { - // check if we've already passed the next object's start time - boolean overlap = (objectIndex + 1 < gameObjects.length && - trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]); + //don't process hit results when already lost + if (restart != Restart.LOSE) { + // update objects (loop in unlikely event of any skipped indexes) + boolean keyPressed = keys != ReplayFrame.KEY_NONE; + while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) { + // check if we've already passed the next object's start time + boolean overlap = (objectIndex + 1 < gameObjects.length && + trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]); - // update hit object and check completion status - if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition)) - objectIndex++; // done, so increment object index - else - break; + // update hit object and check completion status + if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition)) + objectIndex++; // done, so increment object index + else + break; + } } } @@ -1276,6 +1303,10 @@ public class Game extends BasicGameState { trackPosition < beatmap.objects[objectIndex].getTime() && !beatmap.objects[objectIndex - 1].isSpinner()) lastObjectIndex = objectIndex - 1; + if (restart == Restart.LOSE) { + trackPosition = failTrackTime + (int)(System.currentTimeMillis() - failTime); + } + // draw hit objects in reverse order, or else overlapping objects are unreadable Stack stack = new Stack(); for (int index = objectIndex; index < gameObjects.length && beatmap.objects[index].getTime() < trackPosition + approachTime; index++) { @@ -1340,8 +1371,38 @@ public class Game extends BasicGameState { lastObjectIndex = index; } - while (!stack.isEmpty()) - gameObjects[stack.pop()].draw(g, trackPosition); + while (!stack.isEmpty()){ + if (restart == Restart.LOSE){ + int ix = stack.pop(); + GameObject obj = gameObjects[ix]; + // the time the object began falling + int objTime = Math.max( beatmap.objects[ix].getTime() - approachTime, failTrackTime ); + float dt = (trackPosition - objTime)/(float)(FADEOUT_TIME); + + // would the object already be visible? + if (dt > 0) { + float rotSpeed; + //generate rotation speeds for each objects + if (rotations.containsKey(obj)){ + rotSpeed = rotations.get(obj); + } else { + rotSpeed = (float)(2.0f*(Math.random()-0.5f)*MAX_ROTATION); + rotations.put(obj, rotSpeed); + } + + g.pushTransform(); + + g.translate(0, dt*dt * container.getHeight()); + Vec2f rotationCenter = obj.getPointAt(beatmap.objects[ix].getTime()); + g.rotate(rotationCenter.x, rotationCenter.y, rotSpeed * dt); + gameObjects[ix].draw(g, trackPosition); + + g.popTransform(); + } + } else { + gameObjects[stack.pop()].draw(g, trackPosition); + } + } // draw OsuHitObjectResult objects data.drawHitResults(trackPosition); diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index 09c4fe72..b56b81ea 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -47,16 +47,6 @@ import org.newdawn.slick.state.transition.FadeOutTransition; * or return to the song menu from this state. */ public class GamePauseMenu extends BasicGameState { - /** Music fade-out time, in milliseconds. */ - private static final int FADEOUT_TIME = 2000; - - /** Additional delay time to block state changes during music fade-out, in milliseconds. - This prevents music playback issues when the track hasn't completely finished fading out. */ - private static final int FADEOUT_EXTRA_DELAY = 100; - - /** Track position when the pause menu was loaded (for FADEOUT_TIME). */ - private long pauseStartTime; - /** "Continue", "Retry", and "Back" buttons. */ private MenuButton continueButton, retryButton, backButton; @@ -131,12 +121,9 @@ public class GamePauseMenu extends BasicGameState { // if music faded out (i.e. health is zero), don't process any state changes before FADEOUT_TIME boolean loseState = (gameState.getRestart() == Game.Restart.LOSE); - boolean loseBlockDelay = (loseState && System.currentTimeMillis() - pauseStartTime < FADEOUT_TIME + FADEOUT_EXTRA_DELAY); switch (key) { case Input.KEY_ESCAPE: - if (loseBlockDelay) - break; // 'esc' will normally unpause, but will return to song menu if health is zero if (loseState) { SoundController.playSound(SoundEffect.MENUBACK); @@ -152,8 +139,6 @@ public class GamePauseMenu extends BasicGameState { } break; case Input.KEY_R: - if (loseBlockDelay) - break; // restart if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) { gameState.setRestart(Game.Restart.MANUAL); @@ -179,9 +164,6 @@ public class GamePauseMenu extends BasicGameState { boolean loseState = (gameState.getRestart() == Game.Restart.LOSE); - // if music faded out (i.e. health is zero), don't process any actions before FADEOUT_TIME - if (loseState && System.currentTimeMillis() - pauseStartTime < FADEOUT_TIME + FADEOUT_EXTRA_DELAY) - return; if (continueButton.contains(x, y) && !loseState) { SoundController.playSound(SoundEffect.MENUBACK); @@ -200,6 +182,7 @@ public class GamePauseMenu extends BasicGameState { MusicController.resume(); if (UI.getCursor().isBeatmapSkinned()) UI.getCursor().reset(); + MusicController.setPitch(1.0f); game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); } } @@ -216,24 +199,12 @@ public class GamePauseMenu extends BasicGameState { public void enter(GameContainer container, StateBasedGame game) throws SlickException { UI.enter(); - pauseStartTime = System.currentTimeMillis(); - if (gameState.getRestart() == Game.Restart.LOSE) { - MusicController.fadeOut(FADEOUT_TIME); - MusicController.pitchFadeOut(FADEOUT_TIME); - SoundController.playSound(SoundEffect.FAIL); - } else - MusicController.pause(); + MusicController.pause(); continueButton.resetHover(); retryButton.resetHover(); backButton.resetHover(); } - @Override - public void leave(GameContainer container, StateBasedGame game) - throws SlickException { - // reset pitch fade out - MusicController.pitchFadeOut(0); - } /** * Loads all game pause/fail menu images.