diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index ec94eefa..197869e5 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -552,10 +552,11 @@ public class GameData { * @param x the starting x coordinate * @param y the y coordinate * @param scale the scale to apply + * @param alpha the alpha level * @param fixedsize the width to use for all symbols * @param rightAlign align right (true) or left (false) */ - public void drawFixedSizeSymbolString(String str, float x, float y, float scale, float fixedsize, boolean rightAlign) { + public void drawFixedSizeSymbolString(String str, float x, float y, float scale, float alpha, float fixedsize, boolean rightAlign) { char[] c = str.toCharArray(); float cx = x; if (rightAlign) { @@ -564,14 +565,18 @@ public class GameData { if (scale != 1.0f) digit = digit.getScaledCopy(scale); cx -= fixedsize; + digit.setAlpha(alpha); digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y); + digit.setAlpha(1f); } } else { for (int i = 0; i < c.length; i++) { Image digit = getScoreSymbolImage(c[i]); if (scale != 1.0f) digit = digit.getScaledCopy(scale); + digit.setAlpha(alpha); digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y); + digit.setAlpha(1f); cx += fixedsize; } } @@ -584,9 +589,10 @@ public class GameData { * @param g the graphics context * @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade * @param firstObject true if the first hit object's start time has not yet passed + * @param alpha the alpha level at which to render all elements (except the hit error bar) */ @SuppressWarnings("deprecation") - public void drawGameElements(Graphics g, boolean breakPeriod, boolean firstObject) { + public void drawGameElements(Graphics g, boolean breakPeriod, boolean firstObject, float alpha) { boolean relaxAutoPilot = (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive()); int margin = (int) (width * 0.008f); float uiScale = GameImage.getUIscale(); @@ -594,14 +600,14 @@ public class GameData { // score if (!relaxAutoPilot) drawFixedSizeSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay), - width - margin, 0, 1.0f, getScoreSymbolImage('0').getWidth() - 2, true); + width - margin, 0, 1f, alpha, getScoreSymbolImage('0').getWidth() - 2, true); // score percentage int symbolHeight = getScoreSymbolImage('0').getHeight(); if (!relaxAutoPilot) drawSymbolString( String.format((scorePercentDisplay < 10f) ? "0%.2f%%" : "%.2f%%", scorePercentDisplay), - width - margin, symbolHeight, 0.60f, 1f, true); + width - margin, symbolHeight, 0.60f, alpha, true); // map progress circle Beatmap beatmap = MusicController.getBeatmap(); @@ -615,23 +621,27 @@ public class GameData { getScoreSymbolImage('%').getWidth() ) * 0.60f - circleDiameter); if (!relaxAutoPilot) { + float oldWhiteAlpha = Colors.WHITE_ALPHA.a; + Colors.WHITE_ALPHA.a = alpha; g.setAntiAlias(true); g.setLineWidth(2f); - g.setColor(Color.white); + g.setColor(Colors.WHITE_ALPHA); g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter); if (trackPosition > firstObjectTime) { // map progress (white) - g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter, - -90, -90 + (int) (360f * (trackPosition - firstObjectTime) / (beatmap.endTime - firstObjectTime)) - ); + float progress = Math.min((float) (trackPosition - firstObjectTime) / (beatmap.endTime - firstObjectTime), 1f); + g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter, -90, -90 + (int) (360f * progress)); } else { // lead-in time (yellow) + float progress = (float) trackPosition / firstObjectTime; + float oldYellowAlpha = Colors.YELLOW_ALPHA.a; + Colors.YELLOW_ALPHA.a *= alpha; g.setColor(Colors.YELLOW_ALPHA); - g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter, - -90 + (int) (360f * trackPosition / firstObjectTime), -90 - ); + g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter, -90 + (int) (360f * progress), -90); + Colors.YELLOW_ALPHA.a = oldYellowAlpha; } g.setAntiAlias(false); + Colors.WHITE_ALPHA.a = oldWhiteAlpha; } // mod icons @@ -641,10 +651,12 @@ public class GameData { int modCount = 0; for (GameMod mod : GameMod.VALUES_REVERSED) { if (mod.isActive()) { + mod.getImage().setAlpha(alpha); mod.getImage().draw( modX - (modCount * (modWidth / 2f)), symbolHeight + circleDiameter + 10 ); + mod.getImage().setAlpha(1f); modCount++; } } @@ -692,8 +704,8 @@ public class GameData { float tickWidth = 2 * uiScale; for (HitErrorInfo info : hitErrorList) { int time = info.time; - float alpha = 1 - ((float) (trackPosition - time) / HIT_ERROR_FADE_TIME); - white.a = alpha * hitErrorAlpha; + float tickAlpha = 1 - ((float) (trackPosition - time) / HIT_ERROR_FADE_TIME); + white.a = tickAlpha * hitErrorAlpha; g.setColor(white); g.fillRect((hitErrorX + info.timeDiff - 1) * uiScale, tickY, tickWidth, tickHeight); } @@ -716,9 +728,12 @@ public class GameData { float colourX = 4 * uiScale, colourY = 15 * uiScale; Image colourCropped = colour.getSubImage(0, 0, (int) (645 * uiScale * healthRatio), colour.getHeight()); - scorebar.setAlpha(1f); + scorebar.setAlpha(alpha); scorebar.draw(0, 0); + scorebar.setAlpha(1f); + colourCropped.setAlpha(alpha); colourCropped.draw(colourX, colourY); + colourCropped.setAlpha(1f); Image ki = null; if (health >= 50f) @@ -729,7 +744,9 @@ public class GameData { ki = GameImage.SCOREBAR_KI_DANGER2.getImage(); if (comboPopTime < COMBO_POP_TIME) ki = ki.getScaledCopy(1f + (0.45f * (1f - (float) comboPopTime / COMBO_POP_TIME))); + ki.setAlpha(alpha); ki.drawCentered(colourX + colourCropped.getWidth(), colourY); + ki.setAlpha(1f); // combo burst if (comboBurstIndex != -1 && comboBurstAlpha > 0f) { @@ -745,8 +762,8 @@ public class GameData { float comboPopFront = 1 + comboPop * 0.08f; String comboString = String.format("%dx", combo); if (comboPopTime != COMBO_POP_TIME) - drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopBack), comboPopBack, 0.5f, false); - drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopFront), comboPopFront, 1f, false); + drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopBack), comboPopBack, 0.5f * alpha, false); + drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopFront), comboPopFront, alpha, false); } } else if (!relaxAutoPilot) { // grade @@ -754,9 +771,9 @@ public class GameData { if (grade != Grade.NULL) { Image gradeImage = grade.getSmallImage(); float gradeScale = symbolHeight * 0.75f / gradeImage.getHeight(); - gradeImage.getScaledCopy(gradeScale).draw( - circleX - gradeImage.getWidth(), symbolHeight - ); + gradeImage = gradeImage.getScaledCopy(gradeScale); + gradeImage.setAlpha(alpha); + gradeImage.draw(circleX - gradeImage.getWidth(), symbolHeight); } } } @@ -781,7 +798,7 @@ public class GameData { drawFixedSizeSymbolString( (score < 100000000) ? String.format("%08d", score) : Long.toString(score), 210 * uiScale, (rankingHeight + 50) * uiScale, - scoreTextScale, getScoreSymbolImage('0').getWidth() * scoreTextScale - 2, false + scoreTextScale, 1f, getScoreSymbolImage('0').getWidth() * scoreTextScale - 2, false ); // result counts diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index f98a1233..8fcd0df2 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -52,6 +52,7 @@ import itdelatrisu.opsu.ui.Fonts; import itdelatrisu.opsu.ui.MenuButton; import itdelatrisu.opsu.ui.StarStream; import itdelatrisu.opsu.ui.UI; +import itdelatrisu.opsu.ui.animations.AnimatedValue; import itdelatrisu.opsu.ui.animations.AnimationEquation; import java.io.File; @@ -99,6 +100,9 @@ public class Game extends BasicGameState { /** Screen fade-out time, in milliseconds, when health hits zero. */ private static final int LOSE_FADEOUT_TIME = 500; + /** Game element fade-out time, in milliseconds, when the game ends. */ + private static final int FINISHED_FADEOUT_TIME = 400; + /** Maximum rotation, in degrees, over fade out upon death. */ private static final float MAX_ROTATION = 90f; @@ -281,6 +285,12 @@ public class Game extends BasicGameState { /** The star stream shown when passing another score. */ private StarStream scoreboardStarStream; + /** Whether the game is finished (last hit object passed). */ + private boolean gameFinished = false; + + /** Timer after game has finished, before changing states. */ + private AnimatedValue gameFinishedTimer = new AnimatedValue(2500, 0, 1, AnimationEquation.LINEAR); + /** Music position bar background colors. */ private static final Color MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f), @@ -373,7 +383,9 @@ public class Game extends BasicGameState { autoMousePressed = false; if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) { Vec2f autoPoint = null; - if (isLeadIn()) { + if (gameFinished) { + // game finished, do nothing + } else if (isLeadIn()) { // lead-in float progress = Math.max((float) (leadInTime - beatmap.audioLeadIn) / approachTime, 0f); autoMousePosition.y = height / (2f - progress); @@ -488,7 +500,7 @@ public class Game extends BasicGameState { Colors.BLACK_ALPHA.a = a; } - data.drawGameElements(g, true, objectIndex == 0); + data.drawGameElements(g, true, objectIndex == 0, 1f); if (breakLength >= 8000 && trackPosition - breakTime > 2000 && @@ -526,7 +538,13 @@ public class Game extends BasicGameState { // non-break else { // game elements - data.drawGameElements(g, false, objectIndex == 0); + float gameElementAlpha = 1f; + if (gameFinished) { + // game finished: fade everything out + float t = 1f - Math.min(gameFinishedTimer.getTime() / (float) FINISHED_FADEOUT_TIME, 1f); + gameElementAlpha = AnimationEquation.OUT_CUBIC.calc(t); + } + data.drawGameElements(g, false, objectIndex == 0, gameElementAlpha); // skip beginning if (objectIndex == 0 && @@ -756,11 +774,11 @@ public class Game extends BasicGameState { } // normal game update - if (!isReplay) + if (!isReplay && !gameFinished) addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition); // watching replay - else { + else if (!gameFinished) { // out of frames, use previous data if (replayIndex >= replay.frames.length) updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed); @@ -810,7 +828,8 @@ public class Game extends BasicGameState { // update in-game scoreboard if (previousScores != null && trackPosition > firstObjectTime) { // show scoreboard if selected, and always in break - if (scoreboardVisible || breakTime > 0) { + // hide when game ends + if ((scoreboardVisible || breakTime > 0) && !gameFinished) { currentScoreboardAlpha += 1f / SCOREBOARD_FADE_IN_TIME * delta; if (currentScoreboardAlpha > 1f) currentScoreboardAlpha = 1f; @@ -822,6 +841,14 @@ public class Game extends BasicGameState { } data.updateDisplays(delta); + + // game finished: change state after timer expires + if (gameFinished && !gameFinishedTimer.update(delta)) { + if (checkpointLoaded) // if checkpoint used, skip ranking screen + game.closeRequested(); + else // go to ranking screen + game.enterState(Opsu.STATE_GAMERANKING, new EasedFadeOutTransition(), new FadeInTransition()); + } } /** @@ -839,12 +866,8 @@ public class Game extends BasicGameState { if (MusicController.trackEnded() && objectIndex < gameObjects.length) gameObjects[objectIndex].update(true, delta, mouseX, mouseY, false, trackPosition); - // if checkpoint used, skip ranking screen - if (checkpointLoaded) - game.closeRequested(); - - // go to ranking screen - else { + // save score and replay + if (!checkpointLoaded) { boolean unranked = (GameMod.AUTO.isActive() || GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive()); ((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data); if (isReplay) @@ -865,9 +888,12 @@ public class Game extends BasicGameState { // add score to database if (!unranked && !isReplay) ScoreDB.addScore(score); - - game.enterState(Opsu.STATE_GAMERANKING, new EasedFadeOutTransition(), new FadeInTransition()); } + + // start timer + gameFinished = true; + gameFinishedTimer.setTime(0); + return; } @@ -971,6 +997,9 @@ public class Game extends BasicGameState { @Override public void keyPressed(int key, char c) { + if (gameFinished) + return; + int trackPosition = MusicController.getPosition(); int mouseX = input.getMouseX(); int mouseY = input.getMouseY(); @@ -1094,6 +1123,9 @@ public class Game extends BasicGameState { @Override public void mousePressed(int button, int x, int y) { + if (gameFinished) + return; + // watching replay if (isReplay || GameMod.AUTO.isActive()) { if (button == Input.MOUSE_MIDDLE_BUTTON) @@ -1186,6 +1218,9 @@ public class Game extends BasicGameState { @Override public void mouseReleased(int button, int x, int y) { + if (gameFinished) + return; + if (Options.isMouseDisabled()) return; @@ -1203,6 +1238,9 @@ public class Game extends BasicGameState { @Override public void keyReleased(int key, char c) { + if (gameFinished) + return; + int keys = ReplayFrame.KEY_NONE; if (key == Options.getGameKeyLeft()) keys = ReplayFrame.KEY_K1; @@ -1582,6 +1620,8 @@ public class Game extends BasicGameState { autoMousePressed = false; flashlightRadius = container.getHeight() * 2 / 3; scoreboardStarStream.clear(); + gameFinished = false; + gameFinishedTimer.setTime(0); System.gc(); }