Skip to content

Commit

Permalink
Added performance graphs (life frames).
Browse files Browse the repository at this point in the history
Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
  • Loading branch information
itdelatrisu committed Feb 4, 2017
1 parent 5da33d8 commit 510944f
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 33 deletions.
Binary file added res/ranking-graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 44 additions & 3 deletions src/itdelatrisu/opsu/GameData.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import itdelatrisu.opsu.downloads.Updater;
import itdelatrisu.opsu.objects.curves.Curve;
import itdelatrisu.opsu.options.Options;
import itdelatrisu.opsu.replay.LifeFrame;
import itdelatrisu.opsu.replay.Replay;
import itdelatrisu.opsu.replay.ReplayFrame;
import itdelatrisu.opsu.ui.Colors;
Expand Down Expand Up @@ -786,6 +787,7 @@ public void drawRankingElements(Graphics g, Beatmap beatmap, int time) {
// animation timings
int animationTime = 400, offsetTime = 150, gradeAnimationTime = 1000, whiteAnimationTime = 2200;
int rankStart = 50, comboStart = 1800, perfectStart = 2700, gradeStart = 2800, whiteStart = 3800;
int graphEnd = whiteStart + 100;

// ranking panel
GameImage.RANKING_PANEL.getImage().draw(0, (int) (102 * uiScale));
Expand Down Expand Up @@ -883,6 +885,44 @@ public void drawRankingElements(Graphics g, Beatmap beatmap, int time) {
);
}

// graph
float graphX = 416 * uiScale;
float graphY = 688 * uiScale;
Image graphImg = GameImage.RANKING_GRAPH.getImage();
graphImg.drawCentered(graphX, graphY);
if (replay != null && replay.lifeFrames != null && replay.lifeFrames.length > 0) {
float margin = 8 * uiScale;
float cx = graphX - graphImg.getWidth() / 2f + margin;
float cy = graphY - graphImg.getHeight() / 2f + margin;
float graphWidth = graphImg.getWidth() - margin * 2f;
float graphHeight = graphImg.getHeight() - margin * 2f;
g.setClip((int) cx, (int) cy, (int) (graphWidth * ((float) time / graphEnd)), (int) graphHeight);
float lastXt = cx;
float lastYt = cy + graphHeight * (1f - replay.lifeFrames[0].getHealth());
g.setLineWidth(2 * uiScale);
if (replay.lifeFrames.length == 1) {
g.setColor(replay.lifeFrames[0].getHealth() >= 0.5f ? Colors.GREEN : Color.red);
g.drawLine(lastXt, lastYt, lastXt + graphWidth, lastYt);
} else {
int minTime = replay.lifeFrames[0].getTime();
int maxTime = replay.lifeFrames[replay.lifeFrames.length - 1].getTime();
int totalTime = maxTime - minTime;
Color lastColor = null;
for (int i = 1; i < replay.lifeFrames.length; i++) {
float xt = cx + graphWidth * ((float) (replay.lifeFrames[i].getTime() - minTime) / totalTime);
float yt = cy + graphHeight * (1f - replay.lifeFrames[i].getHealth());
Color color = replay.lifeFrames[i].getHealth() >= 0.5f ? Colors.GREEN : Color.red;
if (color != lastColor)
g.setColor(color);
g.drawLine(lastXt, lastYt, xt, yt);
lastXt = xt;
lastYt = yt;
lastColor = color;
}
}
g.clearClip();
}

// full combo
if (time >= perfectStart) {
float t = Math.min((float) (time - perfectStart) / animationTime, 1f);
Expand All @@ -892,7 +932,7 @@ public void drawRankingElements(Graphics g, Beatmap beatmap, int time) {
if (comboMax == fullObjectCount) {
Image img = GameImage.RANKING_PERFECT.getImage().getScaledCopy(scale);
img.setAlpha(alpha);
img.drawCentered(416 * uiScale, 688 * uiScale);
img.drawCentered(graphX, graphY);
}
}

Expand Down Expand Up @@ -1625,10 +1665,11 @@ public ScoreData getCurrentScoreData(Beatmap beatmap, boolean slidingScore) {
* Returns a Replay object encapsulating all game data.
* If a replay already exists and frames is null, the existing object will be returned.
* @param frames the replay frames
* @param lifeFrames the life frames
* @param beatmap the associated beatmap
* @return the Replay object, or null if none exists and frames is null
*/
public Replay getReplay(ReplayFrame[] frames, Beatmap beatmap) {
public Replay getReplay(ReplayFrame[] frames, LifeFrame[] lifeFrames, Beatmap beatmap) {
if (replay != null && frames == null)
return replay;

Expand All @@ -1651,7 +1692,7 @@ public Replay getReplay(ReplayFrame[] frames, Beatmap beatmap) {
replay.combo = (short) comboMax;
replay.perfect = (comboMax == fullObjectCount);
replay.mods = GameMod.getModState();
replay.lifeFrames = null; // TODO
replay.lifeFrames = lifeFrames;
replay.timestamp = new Date();
replay.frames = frames;
replay.seed = 0; // TODO
Expand Down
1 change: 1 addition & 0 deletions src/itdelatrisu/opsu/GameImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ protected Image process_sub(Image img, int w, int h) {
RANKING_TITLE ("ranking-title", "png"),
RANKING_MAXCOMBO ("ranking-maxcombo", "png"),
RANKING_ACCURACY ("ranking-accuracy", "png"),
RANKING_GRAPH ("ranking-graph", "png", false, false),
DEFAULT_0 ("default-0", "png"),
DEFAULT_1 ("default-1", "png"),
DEFAULT_2 ("default-2", "png"),
Expand Down
25 changes: 14 additions & 11 deletions src/itdelatrisu/opsu/replay/LifeFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,37 @@
* @author smoogipooo (https://github.com/smoogipooo/osu-Replay-API/)
*/
public class LifeFrame {
/** Time. */
/** The sample interval, in milliseconds, when saving replays. */
public static final int SAMPLE_INTERVAL = 2000;

/** Time, in milliseconds. */
private final int time;

/** Percentage. */
private final float percentage;
/** Health. */
private final float health;

/**
* Constructor.
* @param time the time
* @param percentage the percentage
* @param time the time (in ms)
* @param health the health [0,1]
*/
public LifeFrame(int time, float percentage) {
public LifeFrame(int time, float health) {
this.time = time;
this.percentage = percentage;
this.health = health;
}

/**
* Returns the frame time.
* Returns the frame time, in milliseconds.
*/
public int getTime() { return time; }

/**
* Returns the frame percentage.
* Returns the health.
*/
public float getPercentage() { return percentage; }
public float getHealth() { return health; }

@Override
public String toString() {
return String.format("(%d, %.2f)", time, percentage);
return String.format("(%d, %.2f)", time, health);
}
}
10 changes: 7 additions & 3 deletions src/itdelatrisu/opsu/replay/Replay.java
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,16 @@ public void run() {

// life data
StringBuilder sb = new StringBuilder();
if (lifeFrames != null) {
if (lifeFrames != null && lifeFrames.length > 0) {
NumberFormat nf = new DecimalFormat("##.##");
int lastFrameTime = 0;
for (int i = 0; i < lifeFrames.length; i++) {
LifeFrame frame = lifeFrames[i];
sb.append(String.format("%d|%s,",
frame.getTime(), nf.format(frame.getPercentage())));
if (i > 0 && frame.getTime() - lastFrameTime < LifeFrame.SAMPLE_INTERVAL)
continue;

sb.append(String.format("%d|%s,", frame.getTime(), nf.format(frame.getHealth())));
lastFrameTime = frame.getTime();
}
}
writer.write(sb.toString());
Expand Down
33 changes: 30 additions & 3 deletions src/itdelatrisu/opsu/states/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import itdelatrisu.opsu.objects.curves.Vec2f;
import itdelatrisu.opsu.options.Options;
import itdelatrisu.opsu.render.FrameBufferCache;
import itdelatrisu.opsu.replay.LifeFrame;
import itdelatrisu.opsu.replay.PlaybackSpeed;
import itdelatrisu.opsu.replay.Replay;
import itdelatrisu.opsu.replay.ReplayFrame;
Expand Down Expand Up @@ -252,6 +253,9 @@ public enum Restart {
/** The list of current replay frames (for recording replays). */
private LinkedList<ReplayFrame> replayFrames;

/** The list of current life frames (for recording replays). */
private LinkedList<LifeFrame> lifeFrames;

/** The offscreen image rendered to. */
private Image offscreen;

Expand Down Expand Up @@ -815,8 +819,10 @@ else if (!container.hasFocus()) {
}

// normal game update
if (!isReplay && !gameFinished)
if (!isReplay && !gameFinished) {
addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
addLifeFrame(trackPosition);
}

// watching replay
else if (!gameFinished) {
Expand Down Expand Up @@ -933,7 +939,11 @@ else if (replayFrames != null) {
replayFrames.getFirst().setTimeDiff(replaySkipTime * -1);
replayFrames.addFirst(ReplayFrame.getStartFrame(replaySkipTime));
replayFrames.addFirst(ReplayFrame.getStartFrame(0));
Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()]), beatmap);
Replay r = data.getReplay(
replayFrames.toArray(new ReplayFrame[replayFrames.size()]),
lifeFrames.toArray(new LifeFrame[lifeFrames.size()]),
beatmap
);
if (r != null && !unranked)
r.save();
}
Expand Down Expand Up @@ -1540,6 +1550,7 @@ else if (hitObject.isSpinner())
replaySkipTime = -1;
replayFrames = new LinkedList<ReplayFrame>();
replayFrames.add(new ReplayFrame(0, 0, input.getMouseX(), input.getMouseY(), 0));
lifeFrames = new LinkedList<LifeFrame>();
}

leadInTime = beatmap.audioLeadIn + approachTime;
Expand Down Expand Up @@ -1779,6 +1790,7 @@ public void resetGameData() {
deaths = 0;
deathTime = -1;
replayFrames = null;
lifeFrames = null;
lastReplayTime = 0;
autoMousePosition = new Vec2f();
autoMousePressed = false;
Expand Down Expand Up @@ -2041,7 +2053,7 @@ public void setReplay(Replay replay) {
* @param keys the keys pressed
* @param time the time of the replay Frame
*/
public synchronized void addReplayFrameAndRun(int x, int y, int keys, int time){
private synchronized void addReplayFrameAndRun(int x, int y, int keys, int time){
// "auto" and "autopilot" mods: use automatic cursor coordinates
if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
x = (int) autoMousePosition.x;
Expand Down Expand Up @@ -2112,6 +2124,21 @@ private ReplayFrame addReplayFrame(int x, int y, int keys, int time) {
return frame;
}

/**
* Adds a life frame to the list, if possible.
* @param time the time of the life frame
*/
private void addLifeFrame(int time) {
if (lifeFrames == null)
return;

// don't record life frames before first object
if (time < beatmap.objects[0].getTime() - approachTime)
return;

lifeFrames.add(new LifeFrame(time, data.getHealthPercent() / 100f));
}

/**
* Returns the point at the t value between a start and end point.
* @param startX the starting x coordinate
Expand Down
40 changes: 27 additions & 13 deletions src/itdelatrisu/opsu/states/GameRanking.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.io.IOException;

import org.lwjgl.opengl.Display;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
Expand Down Expand Up @@ -68,6 +69,9 @@ public class GameRanking extends BasicGameState {
/** Animation progress. */
private AnimatedValue animationProgress = new AnimatedValue(6000, 0f, 1f, AnimationEquation.LINEAR);

/** The loaded replay, or null if it couldn't be loaded. */
private Replay replay = null;

// game-related variables
private StateBasedGame game;
private final int state;
Expand Down Expand Up @@ -189,19 +193,10 @@ public void mousePressed(int button, int x, int y) {
boolean returnToGame = false;
boolean replayButtonPressed = replayButton.contains(x, y);
if (replayButtonPressed && !(data.isGameplay() && GameMod.AUTO.isActive())) {
Replay r = data.getReplay(null, null);
if (r != null) {
try {
r.load();
gameState.setReplay(r);
gameState.setRestart((data.isGameplay()) ? Game.Restart.REPLAY : Game.Restart.NEW);
returnToGame = true;
} catch (FileNotFoundException e) {
UI.getNotificationManager().sendBarNotification("Replay file not found.");
} catch (IOException e) {
Log.error("Failed to load replay data.", e);
UI.getNotificationManager().sendBarNotification("Failed to load replay data. See log for details.");
}
if (replay != null) {
gameState.setReplay(replay);
gameState.setRestart((data.isGameplay()) ? Game.Restart.REPLAY : Game.Restart.NEW);
returnToGame = true;
} else
UI.getNotificationManager().sendBarNotification("Replay file not found.");
}
Expand Down Expand Up @@ -249,6 +244,7 @@ public void enter(GameContainer container, StateBasedGame game)
}
}
replayButton.resetHover();
loadReplay();
}

@Override
Expand All @@ -273,6 +269,24 @@ private void returnToSongMenu() {
game.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
}

/** Loads the replay data. */
private void loadReplay() {
this.replay = null;
Replay r = data.getReplay(null, null, null);
if (r != null) {
try {
r.load();
this.replay = r;
} catch (FileNotFoundException e) {
// file not found
} catch (IOException e) {
Log.error("Failed to load replay data.", e);
UI.getNotificationManager().sendNotification("Failed to load replay data.\nSee log for details.", Color.red);
}
}
// else file not found
}

/**
* Sets the associated GameData object.
* @param data the GameData
Expand Down

0 comments on commit 510944f

Please sign in to comment.