Merge pull request #150 from mad-s/master

Add falling game objects on fail
This commit is contained in:
Jeffrey Han 2015-11-19 16:29:30 -05:00
commit b55b6c3079
3 changed files with 83 additions and 47 deletions

View File

@ -264,6 +264,10 @@ public class Slider implements GameObject {
if (sliderTime == 0) if (sliderTime == 0)
return; return;
// Don't draw follow ball if already done
if (trackPosition > hitObject.getTime() + sliderTimeTotal)
return;
Vec2f c = curve.pointAt(getT(trackPosition, false)); Vec2f c = curve.pointAt(getT(trackPosition, false));
Vec2f c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); Vec2f c2 = curve.pointAt(getT(trackPosition, false) + 0.01f);

View File

@ -56,6 +56,7 @@ import itdelatrisu.opsu.ui.animations.AnimationEquation;
import java.io.File; import java.io.File;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Stack; import java.util.Stack;
import java.util.IdentityHashMap;
import org.lwjgl.input.Keyboard; import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
@ -90,6 +91,12 @@ public class Game extends BasicGameState {
LOSE 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. */ /** Minimum time before start of song, in milliseconds, to process skip-related actions. */
private static final int SKIP_OFFSET = 2000; 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. */ /** Track position at death, used if "Easy" mod is enabled. */
private int deathTime = -1; private int deathTime = -1;
/** System time position at death. */
private long failTime;
private int failTrackTime;
private IdentityHashMap<GameObject, Float> rotations;
/** Number of retries. */ /** Number of retries. */
private int retries = 0; private int retries = 0;
@ -650,6 +663,7 @@ public class Game extends BasicGameState {
deathTime = -1; deathTime = -1;
} }
// normal game update // normal game update
if (!isReplay) if (!isReplay)
addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition); addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
@ -793,7 +807,7 @@ public class Game extends BasicGameState {
} }
if (MusicController.isPlaying() || isLeadIn()) if (MusicController.isPlaying() || isLeadIn())
pauseTime = trackPosition; pauseTime = trackPosition;
game.enterState(Opsu.STATE_GAMEPAUSEMENU); game.enterState(Opsu.STATE_GAMEPAUSEMENU, new EmptyTransition(), new FadeInTransition(Color.black));
} }
// drain health // drain health
@ -809,25 +823,38 @@ public class Game extends BasicGameState {
} }
} }
// game over, force a restart // game over, force a restart
if (!isReplay) { if (!isReplay) {
restart = Restart.LOSE; if (restart != Restart.LOSE) {
game.enterState(Opsu.STATE_GAMEPAUSEMENU); restart = Restart.LOSE;
failTime = System.currentTimeMillis();
failTrackTime = MusicController.getPosition();
MusicController.fadeOut(FADEOUT_TIME);
MusicController.pitchFadeOut(FADEOUT_TIME);
rotations = new IdentityHashMap<GameObject, Float>();
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) //don't process hit results when already lost
boolean keyPressed = keys != ReplayFrame.KEY_NONE; if (restart != Restart.LOSE) {
while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) { // update objects (loop in unlikely event of any skipped indexes)
// check if we've already passed the next object's start time boolean keyPressed = keys != ReplayFrame.KEY_NONE;
boolean overlap = (objectIndex + 1 < gameObjects.length && while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) {
trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]); // 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 // update hit object and check completion status
if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition)) if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition))
objectIndex++; // done, so increment object index objectIndex++; // done, so increment object index
else else
break; break;
}
} }
} }
@ -1276,6 +1303,10 @@ public class Game extends BasicGameState {
trackPosition < beatmap.objects[objectIndex].getTime() && !beatmap.objects[objectIndex - 1].isSpinner()) trackPosition < beatmap.objects[objectIndex].getTime() && !beatmap.objects[objectIndex - 1].isSpinner())
lastObjectIndex = objectIndex - 1; 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 // draw hit objects in reverse order, or else overlapping objects are unreadable
Stack<Integer> stack = new Stack<Integer>(); Stack<Integer> stack = new Stack<Integer>();
for (int index = objectIndex; index < gameObjects.length && beatmap.objects[index].getTime() < trackPosition + approachTime; index++) { 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; lastObjectIndex = index;
} }
while (!stack.isEmpty()) while (!stack.isEmpty()){
gameObjects[stack.pop()].draw(g, trackPosition); 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 // draw OsuHitObjectResult objects
data.drawHitResults(trackPosition); data.drawHitResults(trackPosition);

View File

@ -47,16 +47,6 @@ import org.newdawn.slick.state.transition.FadeOutTransition;
* or return to the song menu from this state. * or return to the song menu from this state.
*/ */
public class GamePauseMenu extends BasicGameState { 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. */ /** "Continue", "Retry", and "Back" buttons. */
private MenuButton continueButton, retryButton, backButton; 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 // 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 loseState = (gameState.getRestart() == Game.Restart.LOSE);
boolean loseBlockDelay = (loseState && System.currentTimeMillis() - pauseStartTime < FADEOUT_TIME + FADEOUT_EXTRA_DELAY);
switch (key) { switch (key) {
case Input.KEY_ESCAPE: case Input.KEY_ESCAPE:
if (loseBlockDelay)
break;
// 'esc' will normally unpause, but will return to song menu if health is zero // 'esc' will normally unpause, but will return to song menu if health is zero
if (loseState) { if (loseState) {
SoundController.playSound(SoundEffect.MENUBACK); SoundController.playSound(SoundEffect.MENUBACK);
@ -152,8 +139,6 @@ public class GamePauseMenu extends BasicGameState {
} }
break; break;
case Input.KEY_R: case Input.KEY_R:
if (loseBlockDelay)
break;
// restart // restart
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) { if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
gameState.setRestart(Game.Restart.MANUAL); gameState.setRestart(Game.Restart.MANUAL);
@ -179,9 +164,6 @@ public class GamePauseMenu extends BasicGameState {
boolean loseState = (gameState.getRestart() == Game.Restart.LOSE); 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) { if (continueButton.contains(x, y) && !loseState) {
SoundController.playSound(SoundEffect.MENUBACK); SoundController.playSound(SoundEffect.MENUBACK);
@ -200,6 +182,7 @@ public class GamePauseMenu extends BasicGameState {
MusicController.resume(); MusicController.resume();
if (UI.getCursor().isBeatmapSkinned()) if (UI.getCursor().isBeatmapSkinned())
UI.getCursor().reset(); UI.getCursor().reset();
MusicController.setPitch(1.0f);
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); 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) public void enter(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
UI.enter(); UI.enter();
pauseStartTime = System.currentTimeMillis(); MusicController.pause();
if (gameState.getRestart() == Game.Restart.LOSE) {
MusicController.fadeOut(FADEOUT_TIME);
MusicController.pitchFadeOut(FADEOUT_TIME);
SoundController.playSound(SoundEffect.FAIL);
} else
MusicController.pause();
continueButton.resetHover(); continueButton.resetHover();
retryButton.resetHover(); retryButton.resetHover();
backButton.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. * Loads all game pause/fail menu images.