diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 1dd3de06..a69020d1 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1396,10 +1396,9 @@ public class GameData { * @return the ScoreData object */ public ScoreData getScoreData(Beatmap beatmap) { - if (scoreData != null) - return scoreData; + if (scoreData == null) + scoreData = new ScoreData(); - scoreData = new ScoreData(); scoreData.timestamp = System.currentTimeMillis() / 1000L; scoreData.MID = beatmap.beatmapID; scoreData.MSID = beatmap.beatmapSetID; @@ -1419,6 +1418,7 @@ public class GameData { scoreData.mods = GameMod.getModState(); scoreData.replayString = (replay == null) ? null : replay.getReplayFilename(); scoreData.playerName = null; // TODO + return scoreData; } diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index 82e9f553..4a7a3abd 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -230,6 +230,7 @@ public class Opsu extends StateBasedGame { } else { if (id == STATE_GAME) { MusicController.pause(); + MusicController.setPitch(1.0f); MusicController.resume(); } else songMenu.resetTrackOnLoad(); diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index bb14c88e..71971160 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -30,6 +30,7 @@ import java.sql.SQLException; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; @@ -328,6 +329,42 @@ public class ScoreData implements Comparable { c.a = oldAlpha; } + /** + * Draws the score ingame (smaller and with less information). + * @param g the current graphics context + * @param vPos the base y position of the scoreboard + * @param rank the current rank of this score + * @param position the animated position offset + * @param data an instance of GameData to draw rank number + * @param alpha the transparancy of the score + * @param isActive if this score is the one currently played + */ + public void drawSmall(Graphics g, int vPos, int rank, float position, GameData data, float alpha, boolean isActive) { + + int rectHeight = data.getScoreSymbolImage('0').getHeight(); + int vertDistance = rectHeight + 10; + int yPos = (int)(vPos + position * vertDistance - rectHeight/2); + String scoreString = String.format(Locale.US, "%,d", score); + String comboString = String.format("%dx", combo); + String rankString = String.format("%d", rank); + int rectWidth = (int) (170 * GameImage.getUIscale()); + + Color rectColor = isActive ? Colors.YELLOW_ALPHA : Colors.BLACK_ALPHA; + rectColor.a = 0.5f * alpha; + + g.setColor(rectColor); + g.fillRect(0, yPos, rectWidth, rectHeight); + data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, 0.25f*alpha, true); + if (playerName != null) { + Colors.WHITE_ALPHA.a = 0.5f * alpha; + Fonts.MEDIUM.drawString(0, yPos, playerName, Colors.WHITE_ALPHA); + } + Colors.WHITE_ALPHA.a = alpha; + Fonts.MEDIUMBOLD.drawString(0, yPos + rectHeight - Fonts.MEDIUMBOLD.getLineHeight(), scoreString, Colors.WHITE_ALPHA); + Fonts.MEDIUMBOLD.drawString(rectWidth - Fonts.MEDIUMBOLD.getWidth(comboString), yPos + rectHeight - Fonts.MEDIUMBOLD.getLineHeight(), comboString, Colors.WHITE_ALPHA); + + } + /** * Returns the tooltip string for this score. */ diff --git a/src/itdelatrisu/opsu/db/ScoreDB.java b/src/itdelatrisu/opsu/db/ScoreDB.java index 34891987..e6f27aef 100644 --- a/src/itdelatrisu/opsu/db/ScoreDB.java +++ b/src/itdelatrisu/opsu/db/ScoreDB.java @@ -304,6 +304,16 @@ public class ScoreDB { * @return all scores for the beatmap, or null if any error occurred */ public static ScoreData[] getMapScores(Beatmap beatmap) { + return getMapScores(beatmap, null); + } + + /** + * Retrieves the game scores for a beatmap while excluding a score. + * @param beatmap the beatmap + * @param exclude the filename of the score to exclude + * @return all scores for the beatmap except for exclude, or null if any error occurred + */ + public static ScoreData[] getMapScores(Beatmap beatmap, String exclude) { if (connection == null) return null; @@ -317,7 +327,11 @@ public class ScoreDB { ResultSet rs = selectMapStmt.executeQuery(); while (rs.next()) { ScoreData s = new ScoreData(rs); - list.add(s); + if (s.replayString != null && s.replayString.equals(exclude)) { + // dont return this score + } else { + list.add(s); + } } rs.close(); } catch (SQLException e) { @@ -326,7 +340,6 @@ public class ScoreDB { } return getSortedArray(list); } - /** * Retrieves the game scores for a beatmap set. * @param beatmap the beatmap diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 96daf0b7..2ce8a3a8 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -101,6 +101,12 @@ public class Game extends BasicGameState { /** Maximum rotation, in degrees, over fade out upon death. */ private static final float MAX_ROTATION = 90f; + /** The duration of the score changing animation */ + private static final float SCOREBOARD_ANIMATION_TIME = 500f; + + /** The time the scoreboard takes to fade in */ + private static final float SCOREBOARD_FADE_IN_TIME = 500f; + /** Minimum time before start of song, in milliseconds, to process skip-related actions. */ private static final int SKIP_OFFSET = 2000; @@ -256,6 +262,21 @@ public class Game extends BasicGameState { /** Music position bar coordinates and dimensions (for replay seeking). */ private float musicBarX, musicBarY, musicBarWidth, musicBarHeight; + /** The previous scores. */ + private ScoreData[] previousScores; + + /** The current rank in the scores. */ + private int currentRank; + + /** The time the rank was last updated. */ + private int lastRankUpdateTime; + + /** Is the scoreboard visible? */ + private boolean scoreboardVisible; + + /** The current aplha of the scoreboard. */ + private float currentScoreboardAlpha; + /** Music position bar background colors. */ private static final Color MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f), @@ -482,6 +503,7 @@ public class Game extends BasicGameState { arrow.draw(width * 0.75f, height * 0.75f); } } + } // non-break @@ -563,6 +585,37 @@ public class Game extends BasicGameState { drawHitObjects(g, trackPosition); } + if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive()) { + ScoreData currentScore = data.getScoreData(beatmap); + while (currentRank > 0 && previousScores[currentRank-1].score < currentScore.score) { + currentRank--; + lastRankUpdateTime = trackPosition; + } + + float animation = AnimationEquation.IN_OUT_QUAD.calc(Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f)); + int scoreboardPosition = 2 * container.getHeight() / 3; + + if (currentRank < 4) { + //draw the (new) top 5 ranks + for (int i = 0; i < 4; i++) { + int ii = i + (i>=currentRank ? 1 : 0); + if (i < previousScores.length) + previousScores[i].drawSmall(g, scoreboardPosition, ii + 1, ii + (i==currentRank ? animation-3f : -2f), data, currentScoreboardAlpha, false); + } + currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, currentScoreboardAlpha, true); + } else { + //draw the top 2 and next 2 ranks + previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, currentScoreboardAlpha, false); + previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, currentScoreboardAlpha, false); + previousScores[currentRank-2].drawSmall(g, scoreboardPosition, currentRank - 1, animation - 1f, data, currentScoreboardAlpha*animation, false); + previousScores[currentRank-1].drawSmall(g, scoreboardPosition, currentRank, animation, data, currentScoreboardAlpha, false); + currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, currentScoreboardAlpha, true); + if (animation < 1.0f && currentRank < previousScores.length) { + previousScores[currentRank].drawSmall(g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, currentScoreboardAlpha*(1f - animation), false); + } + } + } + if (GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); @@ -617,6 +670,21 @@ public class Game extends BasicGameState { if (isReplay || GameMod.AUTO.isActive()) playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY); int trackPosition = MusicController.getPosition(); + int firstObjectTime = beatmap.objects[0].getTime(); + + + if (previousScores != null && trackPosition > firstObjectTime) { + // show scoreboard when in break + if (scoreboardVisible || breakTime > 0) { + currentScoreboardAlpha += 1f/SCOREBOARD_FADE_IN_TIME * delta; + if (currentScoreboardAlpha > 1f) + currentScoreboardAlpha = 1f; + } else { + currentScoreboardAlpha -= 1f/SCOREBOARD_FADE_IN_TIME * delta; + if (currentScoreboardAlpha < 0f) + currentScoreboardAlpha = 0f; + } + } // returning from pause screen: must click previous mouse position if (pauseTime > -1) { @@ -986,6 +1054,9 @@ public class Game extends BasicGameState { case Input.KEY_F12: Utils.takeScreenShot(); break; + case Input.KEY_TAB: + scoreboardVisible = ! scoreboardVisible; + break; } } @@ -1138,6 +1209,13 @@ public class Game extends BasicGameState { if (beatmap == null || beatmap.objects == null) throw new RuntimeException("Running game with no beatmap loaded."); + // fetch previous results + previousScores = ScoreDB.getMapScores(beatmap, replay == null ? null : replay.getReplayFilename()); + lastRankUpdateTime = -1000; + if (previousScores != null) + currentRank = previousScores.length; + scoreboardVisible = true; + currentScoreboardAlpha = 0f; // free all previously cached hitobject to framebuffer mappings if some still exist FrameBufferCache.getInstance().freeMap();