From 93b336fdf6ee5687b85a371d5225f1e55b66265e Mon Sep 17 00:00:00 2001 From: fd Date: Sun, 15 Mar 2015 23:05:27 -0400 Subject: [PATCH] Fixes Flickering during replay Calculates Md5 for beatmap files. Reverse Slider ball #50 --- src/itdelatrisu/opsu/GameData.java | 4 +- src/itdelatrisu/opsu/Utils.java | 37 ++++++ src/itdelatrisu/opsu/objects/HitObject.java | 4 +- src/itdelatrisu/opsu/objects/Slider.java | 31 ++--- src/itdelatrisu/opsu/states/Game.java | 132 +++---------------- src/itdelatrisu/opsu/states/GameRanking.java | 2 +- 6 files changed, 78 insertions(+), 132 deletions(-) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index c0674719..465e1eda 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1235,7 +1235,7 @@ public class GameData { * @param frames the replay frames * @return the Replay object, or null if none exists and frames is null */ - public Replay getReplay(ReplayFrame[] frames) { + public Replay getReplay(ReplayFrame[] frames, OsuFile file) { if (replay != null && frames == null) return replay; @@ -1245,7 +1245,7 @@ public class GameData { replay = new Replay(); replay.mode = OsuFile.MODE_OSU; replay.version = Updater.get().getBuildDate(); - replay.beatmapHash = ""; // TODO + replay.beatmapHash = Utils.getMD5(file.getFile()); replay.playerName = ""; // TODO replay.replayHash = Long.toString(System.currentTimeMillis()); // TODO replay.hit300 = (short) hitResultCount[HIT_300]; diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 326a13b6..f6c1c899 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -25,8 +25,10 @@ import itdelatrisu.opsu.downloads.DownloadNode; import java.awt.Font; import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -34,6 +36,8 @@ import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -537,4 +541,37 @@ public class Utils { return s.useDelimiter("\\A").hasNext() ? s.next() : ""; } } + + + /** + * Returns the md5 hash of a file in hex form. + * @param file + * @return the md5 hash + */ + public static String getMD5(File file){ + try { + InputStream in = new BufferedInputStream(new FileInputStream(file)); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buf = new byte[4096]; + + while(true) { + int len = in.read(buf); + if (len < 0) + break; + md.update(buf, 0, len); + } + in.close(); + + byte[] md5byte = md.digest(); + StringBuilder result = new StringBuilder(); + for (byte b : md5byte) { + result.append(String.format("%02x", b)); + } + return result.toString(); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + Log.error(e); + } + return null; + } } diff --git a/src/itdelatrisu/opsu/objects/HitObject.java b/src/itdelatrisu/opsu/objects/HitObject.java index c55797fc..8f2f626a 100644 --- a/src/itdelatrisu/opsu/objects/HitObject.java +++ b/src/itdelatrisu/opsu/objects/HitObject.java @@ -38,7 +38,7 @@ public interface HitObject { * @param mouseX the x coordinate of the mouse * @param mouseY the y coordinate of the mouse * @param keyPressed whether or not a game key is currently pressed - * @param trackPosition TODO + * @param trackPosition the track Position * @return true if object ended */ public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition); @@ -47,7 +47,7 @@ public interface HitObject { * Processes a mouse click. * @param x the x coordinate of the mouse * @param y the y coordinate of the mouse - * @param trackPosition TODO + * @param trackPosition the track Position * @return true if a hit result was processed */ public boolean mousePressed(int x, int y, int trackPosition); diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index a3077dd2..2827b2df 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -24,13 +24,11 @@ import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.Utils; -import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.objects.curves.CircumscribedCircle; import itdelatrisu.opsu.objects.curves.Curve; import itdelatrisu.opsu.objects.curves.LinearBezier; import itdelatrisu.opsu.states.Game; -import org.newdawn.slick.Animation; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; @@ -41,8 +39,8 @@ import org.newdawn.slick.Image; */ public class Slider implements HitObject { /** Slider ball animation. */ - private static Animation sliderBall; - + private static Image[] sliderBallImages; + /** Slider movement speed multiplier. */ private static float sliderMultiplier = 1.0f; @@ -105,7 +103,6 @@ public class Slider implements HitObject { diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480) // slider ball - Image[] sliderBallImages; if (GameImage.SLIDER_BALL.hasSkinImages() || (!GameImage.SLIDER_BALL.hasSkinImage() && GameImage.SLIDER_BALL.getImages() != null)) sliderBallImages = GameImage.SLIDER_BALL.getImages(); @@ -113,8 +110,7 @@ public class Slider implements HitObject { sliderBallImages = new Image[]{ GameImage.SLIDER_BALL.getImage() }; for (int i = 0; i < sliderBallImages.length; i++) sliderBallImages[i] = sliderBallImages[i].getScaledCopy(diameter * 118 / 128, diameter * 118 / 128); - sliderBall = new Animation(sliderBallImages, 30); - + GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameter * 259 / 128, diameter * 259 / 128)); GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameter, diameter)); GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameter / 4, diameter / 4)); @@ -196,7 +192,9 @@ public class Slider implements HitObject { if (hitObject.getRepeatCount() - 1 > tcurRepeat) { Image arrow = GameImage.REVERSEARROW.getImage(); if (tcurRepeat != currentRepeats) { - float t = getT(trackPosition, true); + if (sliderTime == 0) + continue; + float t = Math.max(getT(trackPosition, true), 0); arrow.setAlpha((float) (t - Math.floor(t))); } else arrow.setAlpha(1f); @@ -211,23 +209,26 @@ public class Slider implements HitObject { } } } + if (timeDiff >= 0) { // approach circle color.a = 1 - scale; GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color); } else { + //since update might not have run before drawing during replay, + //the slider time may not have been calculated. + //Which will cause NAN numbers and cause flicker. + if (sliderTime == 0) + return; float[] c = curve.pointAt(getT(trackPosition, false)); float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); - // slider ball - // TODO: deprecated method - // TODO 2: update the animation based on the distance traveled? - sliderBall.updateNoDraw(); - Image sliderBallFrame = sliderBall.getCurrentFrame(); + float t = getT(trackPosition, false); + //float dis = hitObject.getPixelLength()*OsuHitObject.getXMultiplier() * (t -(int)t); + //Image sliderBallFrame = sliderBallImages[ (int)(dis/ (diameter*Math.PI) *30)%sliderBallImages.length]; + Image sliderBallFrame = sliderBallImages[(int) (t * sliderTime * 60 / 1000) % sliderBallImages.length]; float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI); - if (currentRepeats % 2 == 1) - angle += 180; sliderBallFrame.setRotation(angle); sliderBallFrame.drawCentered(c[0], c[1]); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index f295eaea..92d38117 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -177,12 +177,6 @@ public class Game extends BasicGameState { /** The replay skip time, or -1 if none. */ private int replaySkipTime = -1; - /** The thread updating the replay frames. */ - private Thread replayThread; - - /** Whether or not the replay thread should continue running. */ - private boolean replayThreadRunning; - /** The last replay frame time. */ private int lastReplayTime = 0; @@ -201,8 +195,6 @@ public class Game extends BasicGameState { private Input input; private int state; - private PrintWriter replayLog; - public Game(int state) { this.state = state; } @@ -429,7 +421,6 @@ public class Game extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { UI.update(delta); - int mouseX, mouseY; if (isLeadIn()) { // stop updating during song lead-in leadInTime -= delta; @@ -443,23 +434,21 @@ public class Game extends BasicGameState { mouseY = input.getMouseY(); frameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition); } else { - //if(trackPosition<5000) - // MusicController.setPosition(85000); while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) { ReplayFrame frame = replay.frames[replayIndex]; runFrame(frame); replayX = frame.getScaledX(); replayY = frame.getScaledY(); + replayKeyPressed = frame.isKeyPressed(); + lastKeysPressed = frame.getKeys(); replayIndex++; } - System.out.println("MCP "+MusicController.getPosition()); mouseX = replayX; mouseY = replayY; if (replayIndex >= replay.frames.length){ updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed); } } - } public void updateGame(int mouseX, int mouseY, int delta, int trackPosition, int keysPressed){ //if (!isReplay) @@ -525,7 +514,7 @@ public class Game extends BasicGameState { 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()])); + Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()]), osu); if (r != null) r.save(); } @@ -620,7 +609,7 @@ public class Game extends BasicGameState { } // update objects (loop in unlikely event of any skipped indexes) - boolean keyPressed = keysPressed != ReplayFrame.KEY_NONE;//(isReplay) ? replayKeyPressed : Utils.isGameKeyPressed(); + boolean keyPressed = keysPressed != ReplayFrame.KEY_NONE; while (objectIndex < hitObjects.length && trackPosition > osu.objects[objectIndex].getTime()) { // check if we've already passed the next object's start time boolean overlap = (objectIndex + 1 < hitObjects.length && @@ -711,7 +700,6 @@ public class Game extends BasicGameState { if (checkpoint == 0 || checkpoint > osu.endTime) break; // invalid checkpoint try { - killReplayThread(); restart = Restart.MANUAL; enter(container, game); checkpointLoaded = true; @@ -778,8 +766,10 @@ public class Game extends BasicGameState { return; } - lastKeysPressed |= (button == Input.MOUSE_LEFT_BUTTON) ? ReplayFrame.KEY_M1 : ReplayFrame.KEY_M2; - frameAndRun(x, y, lastKeysPressed, MusicController.getPosition()); + if(!isReplay && (button == Input.MOUSE_LEFT_BUTTON || button == Input.MOUSE_RIGHT_BUTTON)) { + lastKeysPressed |= (button == Input.MOUSE_LEFT_BUTTON) ? ReplayFrame.KEY_M1 : ReplayFrame.KEY_M2; + frameAndRun(x, y, lastKeysPressed, MusicController.getPosition()); + } } /** @@ -787,7 +777,7 @@ public class Game extends BasicGameState { * @param keys the game keys pressed * @param x the mouse x coordinate * @param y the mouse y coordinate - * @param trackPosition TODO + * @param trackPosition the track Position */ private void gameKeyPressed(int keys, int x, int y, int trackPosition) { // returning from pause screen @@ -837,12 +827,8 @@ public class Game extends BasicGameState { if (button == Input.MOUSE_MIDDLE_BUTTON) return; - if (!isReplay) { - if (button == Input.MOUSE_LEFT_BUTTON) - lastKeysPressed &= ~ReplayFrame.KEY_M1; - else if (button == Input.MOUSE_RIGHT_BUTTON) - lastKeysPressed &= ~ReplayFrame.KEY_M2; - + if (!isReplay && button == Input.MOUSE_LEFT_BUTTON || button == Input.MOUSE_RIGHT_BUTTON) { + lastKeysPressed &= ~((button == Input.MOUSE_LEFT_BUTTON) ? ReplayFrame.KEY_M1 : ReplayFrame.KEY_M2); frameAndRun(x, y, lastKeysPressed, MusicController.getPosition()); } } @@ -850,20 +836,15 @@ public class Game extends BasicGameState { @Override public void keyReleased(int key, char c) { if (!isReplay && (key == Options.getGameKeyLeft() || key == Options.getGameKeyRight())) { - if (key == Options.getGameKeyLeft()) - lastKeysPressed &= ~ReplayFrame.KEY_K1; - else if (key == Options.getGameKeyRight()) - lastKeysPressed &= ~ReplayFrame.KEY_K2; - int mouseX = scaleX(unscaleX(input.getMouseX())); - int mouseY = scaleY(unscaleY(input.getMouseY())); - int trackPosition = MusicController.getPosition(); - frameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition); + lastKeysPressed &= ~((key == Options.getGameKeyLeft()) ? ReplayFrame.KEY_K1 : ReplayFrame.KEY_K2); + int mouseX = input.getMouseX(); + int mouseY = input.getMouseY(); + frameAndRun(mouseX, mouseY, lastKeysPressed, MusicController.getPosition()); } } @Override public void mouseMoved(int oldx, int oldy, int newx, int newy) { - } @Override @@ -883,13 +864,6 @@ public class Game extends BasicGameState { throws SlickException { UI.enter(); - try { - replayLog = new PrintWriter("replayLog.txt"); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (osu == null || osu.objects == null) throw new RuntimeException("Running game with no OsuFile loaded."); @@ -956,7 +930,6 @@ public class Game extends BasicGameState { GameMod.loadModState(replay.mods); // load initial data - lastReplayTime = 0; replayX = container.getWidth() / 2; replayY = container.getHeight() / 2; replayKeys = ReplayFrame.KEY_NONE; @@ -976,50 +949,6 @@ public class Game extends BasicGameState { break; } - - // run frame updates in another thread - /* - killReplayThread(); - replayThread = new Thread() { - @Override - public void run() { - while (replayThreadRunning) { - // update frames - int trackPosition = MusicController.getPosition(); - while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) { - ReplayFrame frame = replay.frames[replayIndex]; - replayX = frame.getScaledX(); - replayY = frame.getScaledY(); - replayKeyPressed = frame.isKeyPressed(); - int keys = frame.getKeys(); - if (replayKeyPressed && keys != replayKeys) { // send a key press - System.out.println("Replay Pos:"+trackPosition+" "+replay.frames[replayIndex].getTime()); - - gameKeyPressed(frame.getKeys(), replayX, replayY, replay.frames[replayIndex].getTime()); - } - replayKeys = keys; - replayIndex++; - } - - // out of frames - if (replayIndex >= replay.frames.length) - break; - - // sleep execution - try { - //Thread.sleep(1);//, 256000); - int diff = replay.frames[replayIndex].getTime() - trackPosition - 1; - if (diff < 1) - Thread.sleep(0, 256000); - else - Thread.sleep(diff); - } catch (InterruptedException e) {} - } - } - }; - replayThreadRunning = true; - replayThread.start(); - */ } // initialize replay-recording structures @@ -1043,12 +972,10 @@ public class Game extends BasicGameState { throws SlickException { // container.setMouseGrabbed(false); - replayLog.close(); // replays if (isReplay) { GameMod.loadModState(previousMods); UI.hideCursor(); - killReplayThread(); } } @@ -1107,10 +1034,9 @@ public class Game extends BasicGameState { } MusicController.setPosition(firstObjectTime - SKIP_OFFSET); replaySkipTime = (isReplay) ? -1 : trackPosition; - if (replayThread != null && replayThread.isAlive()) { + if (isReplay) { replayX = (int) skipButton.getX(); replayY = (int) skipButton.getY(); - replayThread.interrupt(); } SoundController.playSound(SoundEffect.MENUHIT); return true; @@ -1240,16 +1166,6 @@ public class Game extends BasicGameState { */ public float getTimingPointMultiplier() { return beatLength / beatLengthBase; } - /** - * Kills the running replay updating thread, if any. - */ - private void killReplayThread() { - if (replayThread != null && replayThread.isAlive()) { - replayThreadRunning = false; - replayThread.interrupt(); - } - replayThread = null; - } /** * Sets a replay to view, or resets the replay if null. @@ -1281,10 +1197,9 @@ public class Game extends BasicGameState { int replayX = frame.getScaledX(); int replayY = frame.getScaledY(); int deltaKeys = (keys & ~prevRunKeys ); - replayLog.println("run frame:"+" "+replayX+" "+replayY+" "+(frame.getTimeDiff())+" "+keys+" "+frame.getTime()); if (deltaKeys > 0) { // send a key press gameKeyPressed(deltaKeys, replayX, replayY, frame.getTime()); - //} else if(keys != prevRunKeys){ + } else if(keys != prevRunKeys){ } else { updateGame(replayX, replayY, frame.getTimeDiff(), frame.getTime(), keys); } @@ -1296,28 +1211,21 @@ public class Game extends BasicGameState { * @param x the cursor x coordinate * @param y the cursor y coordinate * @param keys the keys pressed - * @param trackPosition TODO + * @param trackPosition the track Position */ private ReplayFrame addReplayFrame(int x, int y, int keys, int time) { int timeDiff = time - lastReplayTime; lastReplayTime = time; - //lastKeysPressed = keys; int cx = unscaleX(x); int cy = unscaleY(y); ReplayFrame tFrame = new ReplayFrame(timeDiff, time, cx, cy, keys); replayFrames.add(tFrame); return tFrame; } - public int unscaleX(int x){ + private int unscaleX(int x){ return (int) ((x - OsuHitObject.getXOffset()) / OsuHitObject.getXMultiplier()); } - public int unscaleY(int y){ + private int unscaleY(int y){ return (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier()); } - public int scaleX(int x){ - return (int) (x * OsuHitObject.getXMultiplier() + OsuHitObject.getXOffset()); - } - public int scaleY(int y){ - return (int) (y * OsuHitObject.getYMultiplier() + OsuHitObject.getYOffset()); - } } diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index 9f4f1de4..7b229b43 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -170,7 +170,7 @@ public class GameRanking extends BasicGameState { Game gameState = (Game) game.getState(Opsu.STATE_GAME); boolean returnToGame = false; if (replayButton.contains(x, y)) { - Replay r = data.getReplay(null); + Replay r = data.getReplay(null, null); if (r != null) { try { r.load();