diff --git a/res/history.png b/res/history.png new file mode 100644 index 00000000..cb091c99 Binary files /dev/null and b/res/history.png differ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 065566dc..2ffc139e 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -465,6 +465,12 @@ public enum GameImage { return img.getScaledCopy((h * 0.15f) / img.getHeight()); } }, + HISTORY ("history", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.0278f) / img.getHeight()); + } + }, REPOSITORY ("repo", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 4fbdfa93..992543fa 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -59,6 +59,9 @@ public class ScoreData implements Comparable { /** Game mod bitmask. */ public int mods; + /** Time since the score was achieved. */ + private String timeSince; + /** The grade. */ private Grade grade; @@ -191,6 +194,26 @@ public class ScoreData implements Comparable { return grade; } + /** + * Returns the time since achieving the score, or null if over 24 hours. + * This value will not change after the first call. + * @return a string: {number}{s|m|h} + */ + public String getTimeSince() { + if (timeSince == null) { + long seconds = (System.currentTimeMillis() / 1000L) - timestamp; + if (seconds < 60) + timeSince = String.format("%ds", seconds); + else if (seconds < 3600) + timeSince = String.format("%dm", seconds / 60L); + else if (seconds < 86400) + timeSince = String.format("%dh", seconds / 3600L); + else + timeSince = ""; + } + return (timeSince.isEmpty()) ? null : timeSince; + } + /** * Draws the score data as a rectangular button. * @param g the graphics context @@ -201,8 +224,10 @@ public class ScoreData implements Comparable { */ public void draw(Graphics g, int index, int rank, long prevScore, boolean focus) { Image img = getGrade().getMenuImage(); + float textX = baseX + buttonWidth * 0.24f; + float edgeX = baseX + buttonWidth * 0.98f; float y = baseY + index * (buttonOffset); - float textX = baseX + buttonWidth * 0.24f, edgeX = baseX + buttonWidth * 0.98f; + float midY = y + buttonHeight / 2f; float marginY = Utils.FONT_DEFAULT.getLineHeight() * 0.01f; // rectangle outline @@ -219,7 +244,7 @@ public class ScoreData implements Comparable { } // grade image - img.drawCentered(baseX + buttonWidth * 0.15f, y + buttonHeight / 2f); + img.drawCentered(baseX + buttonWidth * 0.15f, midY); // score float textOffset = (buttonHeight - Utils.FONT_MEDIUM.getLineHeight() - Utils.FONT_SMALL.getLineHeight()) / 2f; @@ -269,6 +294,17 @@ public class ScoreData implements Comparable { y + marginY + Utils.FONT_DEFAULT.getLineHeight() * 2, diff, Color.white ); + + // time since + if (getTimeSince() != null) { + Image clock = GameImage.HISTORY.getImage(); + clock.drawCentered(baseX + buttonWidth * 1.02f + clock.getWidth() / 2f, midY); + Utils.FONT_DEFAULT.drawString( + baseX + buttonWidth * 1.03f + clock.getWidth(), + midY - Utils.FONT_DEFAULT.getLineHeight() / 2f, + getTimeSince(), Color.white + ); + } } @Override diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 4da0eb7d..d4cb3b99 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -280,7 +280,7 @@ public class SongMenu extends BasicGameState { for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { // draw the node float offset = (i == hoverIndex) ? hoverOffset : 0f; - ScoreData[] scores = getScoreDataForNode(node); + ScoreData[] scores = getScoreDataForNode(node, false); node.draw( buttonX - offset, buttonY + (i*buttonOffset), (scores == null) ? Grade.NULL : scores[0].getGrade(), @@ -797,7 +797,7 @@ public class SongMenu extends BasicGameState { // reload scores if (focusNode != null) { scoreMap = ScoreDB.getMapSetScores(focusNode.osuFiles.get(focusNode.osuFileIndex)); - focusScores = getScoreDataForNode(focusNode); + focusScores = getScoreDataForNode(focusNode, true); } resetGame = false; @@ -892,7 +892,7 @@ public class SongMenu extends BasicGameState { // load scores scoreMap = ScoreDB.getMapSetScores(osu); - focusScores = getScoreDataForNode(focusNode); + focusScores = getScoreDataForNode(focusNode, true); startScore = 0; // check startNode bounds @@ -936,9 +936,10 @@ public class SongMenu extends BasicGameState { * Returns all the score data for an OsuGroupNode from scoreMap. * If no score data is available for the node, return null. * @param node the OsuGroupNode + * @param setTimeSince whether or not to set the "time since" field for the scores * @return the ScoreData array */ - private ScoreData[] getScoreDataForNode(OsuGroupNode node) { + private ScoreData[] getScoreDataForNode(OsuGroupNode node, boolean setTimeSince) { if (scoreMap == null || scoreMap.isEmpty() || node.osuFileIndex == -1) // node not expanded return null; @@ -950,9 +951,13 @@ public class SongMenu extends BasicGameState { ScoreData s = scores[0]; if (osu.beatmapID == s.MID && osu.beatmapSetID == s.MSID && osu.title.equals(s.title) && osu.artist.equals(s.artist) && - osu.creator.equals(s.creator)) + osu.creator.equals(s.creator)) { + if (setTimeSince) { + for (int i = 0; i < scores.length; i++) + scores[i].getTimeSince(); + } return scores; - else + } else return null; // incorrect map }