Initial replay recording support.
- Added listener and events in Game state to record replay frames. - Send more accurate keys. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
7536056a59
commit
790a66ec1e
|
@ -1238,6 +1238,9 @@ public class GameData {
|
||||||
if (replay != null)
|
if (replay != null)
|
||||||
return replay;
|
return replay;
|
||||||
|
|
||||||
|
if (frames == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
replay = new Replay();
|
replay = new Replay();
|
||||||
replay.mode = OsuFile.MODE_OSU;
|
replay.mode = OsuFile.MODE_OSU;
|
||||||
replay.version = Updater.get().getBuildDate();
|
replay.version = Updater.get().getBuildDate();
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class OsuWriter {
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param file the file to write to
|
* @param file the file to write to
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
*/
|
*/
|
||||||
public OsuWriter(File file) throws FileNotFoundException {
|
public OsuWriter(File file) throws FileNotFoundException {
|
||||||
this(new FileOutputStream(file));
|
this(new FileOutputStream(file));
|
||||||
|
@ -61,7 +61,7 @@ public class OsuWriter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the output stream.
|
* Closes the output stream.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void close() throws IOException { writer.close(); }
|
public void close() throws IOException { writer.close(); }
|
||||||
|
|
||||||
|
|
|
@ -428,7 +428,7 @@ public class OsuDB {
|
||||||
* Sets all OsuFile non-array fields using a given result set.
|
* Sets all OsuFile non-array fields using a given result set.
|
||||||
* @param rs the result set containing the fields
|
* @param rs the result set containing the fields
|
||||||
* @param osu the OsuFile
|
* @param osu the OsuFile
|
||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
private static void setOsuFileFields(ResultSet rs, OsuFile osu) throws SQLException {
|
private static void setOsuFileFields(ResultSet rs, OsuFile osu) throws SQLException {
|
||||||
try {
|
try {
|
||||||
|
@ -476,7 +476,7 @@ public class OsuDB {
|
||||||
* Sets all OsuFile array fields using a given result set.
|
* Sets all OsuFile array fields using a given result set.
|
||||||
* @param rs the result set containing the fields
|
* @param rs the result set containing the fields
|
||||||
* @param osu the OsuFile
|
* @param osu the OsuFile
|
||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
private static void setOsuFileArrayFields(ResultSet rs, OsuFile osu) throws SQLException {
|
private static void setOsuFileArrayFields(ResultSet rs, OsuFile osu) throws SQLException {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -237,9 +237,9 @@ public class Replay {
|
||||||
// life data
|
// life data
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (lifeFrames != null) {
|
if (lifeFrames != null) {
|
||||||
NumberFormat nf = new DecimalFormat("##.##");
|
NumberFormat nf = new DecimalFormat("##.##");
|
||||||
for (int i = 0; i < lifeFrames.length; i++) {
|
for (int i = 0; i < lifeFrames.length; i++) {
|
||||||
LifeFrame frame = lifeFrames[i];
|
LifeFrame frame = lifeFrames[i];
|
||||||
sb.append(String.format("%d|%s,",
|
sb.append(String.format("%d|%s,",
|
||||||
frame.getTime(), nf.format(frame.getPercentage())));
|
frame.getTime(), nf.format(frame.getPercentage())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,16 @@ public class ReplayFrame {
|
||||||
private float x, y;
|
private float x, y;
|
||||||
|
|
||||||
/** Keys pressed (bitmask). */
|
/** Keys pressed (bitmask). */
|
||||||
private int keys;
|
private int keys;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start frame.
|
||||||
|
* @param t the value for the {@code time} and {@code timeDiff} fields
|
||||||
|
*/
|
||||||
|
public static ReplayFrame getStartFrame(int t) {
|
||||||
|
return new ReplayFrame(t, t, 256, -500, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param timeDiff time since the previous action (in ms)
|
* @param timeDiff time since the previous action (in ms)
|
||||||
|
@ -72,6 +80,11 @@ public class ReplayFrame {
|
||||||
*/
|
*/
|
||||||
public int getTimeDiff() { return timeDiff; }
|
public int getTimeDiff() { return timeDiff; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time since the previous action, in milliseconds.
|
||||||
|
*/
|
||||||
|
public void setTimeDiff(int diff) { this.timeDiff = diff; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the scaled cursor x coordinate.
|
* Returns the scaled cursor x coordinate.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -173,11 +173,17 @@ public class Game extends BasicGameState {
|
||||||
/** Whether or not the replay thread should continue running. */
|
/** Whether or not the replay thread should continue running. */
|
||||||
private boolean replayThreadRunning;
|
private boolean replayThreadRunning;
|
||||||
|
|
||||||
|
/** The last replay frame time. */
|
||||||
|
private int lastReplayTime = 0;
|
||||||
|
|
||||||
|
/** The last game keys pressed. */
|
||||||
|
private int lastKeysPressed = ReplayFrame.KEY_NONE;
|
||||||
|
|
||||||
/** The previous game mod state (before the replay). */
|
/** The previous game mod state (before the replay). */
|
||||||
private int previousMods = 0;
|
private int previousMods = 0;
|
||||||
|
|
||||||
/** The list of current replay frames (for recording replays). */
|
/** The list of current replay frames (for recording replays). */
|
||||||
private LinkedList<ReplayFrame> frameList;
|
private LinkedList<ReplayFrame> replayFrames;
|
||||||
|
|
||||||
// game-related variables
|
// game-related variables
|
||||||
private GameContainer container;
|
private GameContainer container;
|
||||||
|
@ -468,14 +474,28 @@ public class Game extends BasicGameState {
|
||||||
if (MusicController.trackEnded() && objectIndex < hitObjects.length)
|
if (MusicController.trackEnded() && objectIndex < hitObjects.length)
|
||||||
hitObjects[objectIndex].update(true, delta, mouseX, mouseY, false);
|
hitObjects[objectIndex].update(true, delta, mouseX, mouseY, false);
|
||||||
|
|
||||||
if (checkpointLoaded) // if checkpoint used, skip ranking screen
|
// if checkpoint used, skip ranking screen
|
||||||
|
if (checkpointLoaded)
|
||||||
game.closeRequested();
|
game.closeRequested();
|
||||||
else { // go to ranking screen
|
|
||||||
|
// go to ranking screen
|
||||||
|
else {
|
||||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
||||||
ScoreData score = data.getScoreData(osu, (frameList == null) ? null :
|
ReplayFrame[] rf = null;
|
||||||
frameList.toArray(new ReplayFrame[frameList.size()]));
|
if (!isReplay && replayFrames != null) {
|
||||||
|
// finalize replay frames with start/skip frames
|
||||||
|
if (!replayFrames.isEmpty())
|
||||||
|
replayFrames.getFirst().setTimeDiff(replaySkipTime * -1);
|
||||||
|
replayFrames.addFirst(ReplayFrame.getStartFrame(replaySkipTime));
|
||||||
|
replayFrames.addFirst(ReplayFrame.getStartFrame(0));
|
||||||
|
rf = replayFrames.toArray(new ReplayFrame[replayFrames.size()]);
|
||||||
|
}
|
||||||
|
ScoreData score = data.getScoreData(osu, rf);
|
||||||
|
|
||||||
|
// add score to database
|
||||||
if (!GameMod.AUTO.isActive() && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && !isReplay)
|
if (!GameMod.AUTO.isActive() && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && !isReplay)
|
||||||
ScoreDB.addScore(score);
|
ScoreDB.addScore(score);
|
||||||
|
|
||||||
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -587,9 +607,9 @@ public class Game extends BasicGameState {
|
||||||
// game keys
|
// game keys
|
||||||
if (!Keyboard.isRepeatEvent() && !isReplay) {
|
if (!Keyboard.isRepeatEvent() && !isReplay) {
|
||||||
if (key == Options.getGameKeyLeft())
|
if (key == Options.getGameKeyLeft())
|
||||||
gameKeyPressed(Input.MOUSE_LEFT_BUTTON, input.getMouseX(), input.getMouseY());
|
gameKeyPressed(ReplayFrame.KEY_K1, input.getMouseX(), input.getMouseY());
|
||||||
else if (key == Options.getGameKeyRight())
|
else if (key == Options.getGameKeyRight())
|
||||||
gameKeyPressed(Input.MOUSE_RIGHT_BUTTON, input.getMouseX(), input.getMouseY());
|
gameKeyPressed(ReplayFrame.KEY_K2, input.getMouseX(), input.getMouseY());
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
@ -715,16 +735,16 @@ public class Game extends BasicGameState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameKeyPressed(button, x, y);
|
gameKeyPressed((button == Input.MOUSE_LEFT_BUTTON) ? ReplayFrame.KEY_M1 : ReplayFrame.KEY_M2, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a game key pressed event.
|
* Handles a game key pressed event.
|
||||||
* @param button the index of the button pressed
|
* @param keys the game keys pressed
|
||||||
* @param x the mouse x coordinate
|
* @param x the mouse x coordinate
|
||||||
* @param y the mouse y coordinate
|
* @param y the mouse y coordinate
|
||||||
*/
|
*/
|
||||||
private void gameKeyPressed(int button, int x, int y) {
|
private void gameKeyPressed(int keys, int x, int y) {
|
||||||
// returning from pause screen
|
// returning from pause screen
|
||||||
if (pauseTime > -1) {
|
if (pauseTime > -1) {
|
||||||
double distance = Math.hypot(pausedMouseX - x, pausedMouseY - y);
|
double distance = Math.hypot(pausedMouseX - x, pausedMouseY - y);
|
||||||
|
@ -755,6 +775,8 @@ public class Game extends BasicGameState {
|
||||||
if (GameMod.AUTO.isActive() || GameMod.RELAX.isActive())
|
if (GameMod.AUTO.isActive() || GameMod.RELAX.isActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
addReplayFrame(x, y, lastKeysPressed | keys);
|
||||||
|
|
||||||
// circles
|
// circles
|
||||||
if (hitObject.isCircle() && hitObjects[objectIndex].mousePressed(x, y))
|
if (hitObject.isCircle() && hitObjects[objectIndex].mousePressed(x, y))
|
||||||
objectIndex++; // circle hit
|
objectIndex++; // circle hit
|
||||||
|
@ -764,6 +786,36 @@ public class Game extends BasicGameState {
|
||||||
hitObjects[objectIndex].mousePressed(x, y);
|
hitObjects[objectIndex].mousePressed(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(int button, int x, int y) {
|
||||||
|
if (Options.isMouseDisabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (button == Input.MOUSE_MIDDLE_BUTTON)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int key = (button == Input.MOUSE_LEFT_BUTTON) ? ReplayFrame.KEY_M1 : ReplayFrame.KEY_M2;
|
||||||
|
if ((lastKeysPressed & key) > 0)
|
||||||
|
addReplayFrame(x, y, ReplayFrame.KEY_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(int key, char c) {
|
||||||
|
if ((key == Options.getGameKeyLeft() && (lastKeysPressed & ReplayFrame.KEY_K1) > 0) ||
|
||||||
|
(key == Options.getGameKeyRight() && (lastKeysPressed & ReplayFrame.KEY_K2) > 0))
|
||||||
|
addReplayFrame(input.getMouseX(), input.getMouseY(), ReplayFrame.KEY_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(int oldx, int oldy, int newx, int newy) {
|
||||||
|
addReplayFrame(newx, newy, lastKeysPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
|
||||||
|
addReplayFrame(newx, newy, lastKeysPressed);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseWheelMoved(int newValue) {
|
public void mouseWheelMoved(int newValue) {
|
||||||
if (Options.isMouseWheelDisabled() || Options.isMouseDisabled())
|
if (Options.isMouseWheelDisabled() || Options.isMouseDisabled())
|
||||||
|
@ -869,22 +921,16 @@ public class Game extends BasicGameState {
|
||||||
while (replayThreadRunning) {
|
while (replayThreadRunning) {
|
||||||
// update frames
|
// update frames
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
int keys = ReplayFrame.KEY_NONE;
|
|
||||||
while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) {
|
while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) {
|
||||||
ReplayFrame frame = replay.frames[replayIndex];
|
ReplayFrame frame = replay.frames[replayIndex];
|
||||||
replayX = frame.getX();
|
replayX = frame.getX();
|
||||||
replayY = frame.getY();
|
replayY = frame.getY();
|
||||||
replayKeyPressed = frame.isKeyPressed();
|
replayKeyPressed = frame.isKeyPressed();
|
||||||
if (replayKeyPressed)
|
if (replayKeyPressed) // send a key press
|
||||||
keys = frame.getKeys();
|
gameKeyPressed(frame.getKeys(), replayX, replayY);
|
||||||
replayIndex++;
|
replayIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a key press
|
|
||||||
if (replayKeyPressed && keys != ReplayFrame.KEY_NONE)
|
|
||||||
gameKeyPressed(((keys & ReplayFrame.KEY_M1) > 0) ?
|
|
||||||
Input.MOUSE_LEFT_BUTTON : Input.MOUSE_RIGHT_BUTTON, replayX, replayY);
|
|
||||||
|
|
||||||
// out of frames
|
// out of frames
|
||||||
if (replayIndex >= replay.frames.length)
|
if (replayIndex >= replay.frames.length)
|
||||||
break;
|
break;
|
||||||
|
@ -898,8 +944,16 @@ public class Game extends BasicGameState {
|
||||||
};
|
};
|
||||||
replayThreadRunning = true;
|
replayThreadRunning = true;
|
||||||
replayThread.start();
|
replayThread.start();
|
||||||
} else
|
}
|
||||||
frameList = new LinkedList<ReplayFrame>();
|
|
||||||
|
// initialize replay-recording structures
|
||||||
|
else {
|
||||||
|
lastReplayTime = 0;
|
||||||
|
lastKeysPressed = ReplayFrame.KEY_NONE;
|
||||||
|
replaySkipTime = -1;
|
||||||
|
replayFrames = new LinkedList<ReplayFrame>();
|
||||||
|
replayFrames.add(new ReplayFrame(0, 0, input.getMouseX(), input.getMouseY(), 0));
|
||||||
|
}
|
||||||
|
|
||||||
leadInTime = osu.audioLeadIn + approachTime;
|
leadInTime = osu.audioLeadIn + approachTime;
|
||||||
restart = Restart.FALSE;
|
restart = Restart.FALSE;
|
||||||
|
@ -944,7 +998,7 @@ public class Game extends BasicGameState {
|
||||||
checkpointLoaded = false;
|
checkpointLoaded = false;
|
||||||
deaths = 0;
|
deaths = 0;
|
||||||
deathTime = -1;
|
deathTime = -1;
|
||||||
frameList = null;
|
replayFrames = null;
|
||||||
|
|
||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
@ -961,7 +1015,7 @@ public class Game extends BasicGameState {
|
||||||
leadInTime = 0;
|
leadInTime = 0;
|
||||||
MusicController.resume();
|
MusicController.resume();
|
||||||
}
|
}
|
||||||
replaySkipTime = -1;
|
replaySkipTime = (isReplay) ? -1 : trackPosition;
|
||||||
if (replayThread != null && replayThread.isAlive())
|
if (replayThread != null && replayThread.isAlive())
|
||||||
replayThread.interrupt();
|
replayThread.interrupt();
|
||||||
MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
|
MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
|
||||||
|
@ -1109,7 +1163,30 @@ public class Game extends BasicGameState {
|
||||||
* @param replay the replay
|
* @param replay the replay
|
||||||
*/
|
*/
|
||||||
public void setReplay(Replay replay) {
|
public void setReplay(Replay replay) {
|
||||||
|
if (replay.frames == null) {
|
||||||
|
ErrorHandler.error("Invalid replay.", null, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.isReplay = true;
|
this.isReplay = true;
|
||||||
this.replay = replay;
|
this.replay = replay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a replay frame to the list.
|
||||||
|
* @param x the cursor x coordinate
|
||||||
|
* @param y the cursor y coordinate
|
||||||
|
* @param keys the keys pressed
|
||||||
|
*/
|
||||||
|
private void addReplayFrame(int x, int y, int keys) {
|
||||||
|
if (isReplay)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int time = MusicController.getPosition();
|
||||||
|
int timeDiff = time - lastReplayTime;
|
||||||
|
lastReplayTime = time;
|
||||||
|
lastKeysPressed = keys;
|
||||||
|
int cx = (int) ((x - OsuHitObject.getXOffset()) / OsuHitObject.getXMultiplier());
|
||||||
|
int cy = (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier());
|
||||||
|
replayFrames.add(new ReplayFrame(timeDiff, time, cx, cy, lastKeysPressed));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user