diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 9f52761a..7e14b995 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1207,8 +1207,13 @@ public void sliderTickResult(int time, int result, float x, float y, HitObject h } fullObjectCount++; } + public void sliderFinalResult(int time, int hitSlider30, float x, float y, + HitObject hitObject, int currentRepeats) { + score += 30; + } /** + * https://osu.ppy.sh/wiki/Score * Returns the score for a hit based on the following score formula: *

* Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25 @@ -1219,19 +1224,26 @@ public void sliderTickResult(int time, int result, float x, float y, HitObject h *

  • Mod: mod multipliers * * @param hitValue the hit value + * @param hitObject * @return the score value */ - private int getScoreForHit(int hitValue) { - return hitValue + (int) (hitValue * (Math.max(combo - 1, 0) * difficultyMultiplier * GameMod.getScoreMultiplier()) / 25); + private int getScoreForHit(int hitValue, HitObject hitObject) { + int comboMulti = Math.max(combo - 1, 0); + if(hitObject.isSlider()){ + comboMulti += 1; + } + return (hitValue + (int)(hitValue * (comboMulti * difficultyMultiplier * GameMod.getScoreMultiplier()) / 25)); } - /** + * https://osu.ppy.sh/wiki/Score#How_to_calculate_the_Difficulty_multiplier * Computes and stores the difficulty multiplier used in the score formula. * @param drainRate the raw HP drain rate value * @param circleSize the raw circle size value * @param overallDifficulty the raw overall difficulty value */ public void calculateDifficultyMultiplier(float drainRate, float circleSize, float overallDifficulty) { + //TODO THE LIES ( difficultyMultiplier ) + //* float sum = drainRate + circleSize + overallDifficulty; // typically 2~27 if (sum <= 5f) difficultyMultiplier = 2; @@ -1243,8 +1255,21 @@ else if (sum <= 24f) difficultyMultiplier = 5; else //if (sum <= 30f) difficultyMultiplier = 6; + //*/ + + /* + 924 3x1/4 beat notes 0.14stars + 924 3x1beat 0.28stars + 912 3x1beat wth 1 extra note 10 sec away 0.29stars + + seems to be based on hitobject density? (Total Objects/Time) + */ + /* + float mult = ((circleSize + overallDifficulty + drainRate) / 6) + 1.5f; + System.out.println("diffuculty Multiplier : "+ mult); + difficultyMultiplier = (int)mult; + */ } - /** * Handles a hit result and performs all associated calculations. * @param time the object start time @@ -1292,7 +1317,7 @@ private int handleHitResult(int time, int result, float x, float y, Color color, hitObject.getAdditionSampleSet(repeat)); // calculate score and increment combo streak - changeScore(getScoreForHit(hitValue)); + changeScore(getScoreForHit(hitValue, hitObject)); incrementComboStreak(); } hitResultCount[result]++; @@ -1357,6 +1382,7 @@ else if (hitResult == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT } } + /** * Returns a ScoreData object encapsulating all game data. * If score data already exists, the existing object will be returned @@ -1387,6 +1413,7 @@ public ScoreData getScoreData(Beatmap beatmap) { scoreData.perfect = (comboMax == fullObjectCount); scoreData.mods = GameMod.getModState(); scoreData.replayString = (replay == null) ? null : replay.getReplayFilename(); + scoreData.playerName = "OpsuPlayer"; //TODO GameDataPlayerName? return scoreData; } @@ -1407,7 +1434,7 @@ public Replay getReplay(ReplayFrame[] frames, Beatmap beatmap) { replay = new Replay(); replay.mode = Beatmap.MODE_OSU; replay.version = Updater.get().getBuildDate(); - replay.beatmapHash = (beatmap == null) ? "" : Utils.getMD5(beatmap.getFile()); + replay.beatmapHash = (beatmap == null) ? "" : beatmap.md5Hash;//Utils.getMD5(beatmap.getFile()); replay.playerName = ""; // TODO replay.replayHash = Long.toString(System.currentTimeMillis()); // TODO replay.hit300 = (short) hitResultCount[HIT_300]; diff --git a/src/itdelatrisu/opsu/MD5InputStreamWrapper.java b/src/itdelatrisu/opsu/MD5InputStreamWrapper.java new file mode 100644 index 00000000..530d50f7 --- /dev/null +++ b/src/itdelatrisu/opsu/MD5InputStreamWrapper.java @@ -0,0 +1,91 @@ +package itdelatrisu.opsu; + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class MD5InputStreamWrapper extends InputStream { + + InputStream in; + private boolean eof; // End Of File + MessageDigest md; + public MD5InputStreamWrapper(InputStream in) throws NoSuchAlgorithmException { + this.in = in; + md = MessageDigest.getInstance("MD5"); + } + + @Override + public int read() throws IOException { + int readed = in.read(); + if(readed>=0) + md.update((byte) readed); + else + eof=true; + return readed; + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int readed = in.read(b, off, len); + if(readed>=0) + md.update(b, off, readed); + else + eof=true; + + return readed; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0 ,b.length); + } + + @Override + public synchronized void reset() throws IOException { + throw new RuntimeException("MD5 stream not resetable"); + } + + @Override + public long skip(long n) throws IOException { + throw new RuntimeException("MD5 stream not skipable"); + } + + public String getMD5() throws IOException { + byte[] buf = null; + if(!eof) + buf = new byte[0x1000]; + while(!eof){ + read(buf); + } + + byte[] md5byte = md.digest(); + StringBuilder result = new StringBuilder(); + for (byte b : md5byte) + result.append(String.format("%02x", b)); + //System.out.println("MD5 stream md5 " + result.toString()); + return result.toString(); + + } + +} diff --git a/src/itdelatrisu/opsu/Options.java b/src/itdelatrisu/opsu/Options.java index 59273f0d..5a39ab1f 100644 --- a/src/itdelatrisu/opsu/Options.java +++ b/src/itdelatrisu/opsu/Options.java @@ -110,6 +110,9 @@ public class Options { /** The replay directory (created when needed). */ private static File replayDir; + /** The replay import directory. */ + private static File replayImportDir; + /** The root skin directory. */ private static File skinRootDir; @@ -1067,7 +1070,21 @@ public static File getOSZDir() { oszDir.mkdir(); return oszDir; } + + /** + * Returns the replay import directory. + * If invalid, this will create and return a "ReplayImport" directory. + * @return the replay import directory + */ + public static File getReplayImportDir() { + if (replayImportDir != null && replayImportDir.isDirectory()) + return replayImportDir; + replayImportDir = new File(DATA_DIR, "ReplayImport/"); + replayImportDir.mkdir(); + return replayImportDir; + } + /** * Returns the screenshot directory. * If invalid, this will return a "Screenshot" directory. diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 7cbe18ed..b6307031 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -77,6 +77,9 @@ public class ScoreData implements Comparable { /** The tooltip string. */ private String tooltip; + + /** The players Name. */ + public String playerName; /** Drawing values. */ private static float baseX, baseY, buttonWidth, buttonHeight, buttonOffset; @@ -164,6 +167,7 @@ public ScoreData(ResultSet rs) throws SQLException { this.perfect = rs.getBoolean(16); this.mods = rs.getInt(17); this.replayString = rs.getString(18); + this.playerName = rs.getString(19); } /** @@ -260,7 +264,7 @@ public void draw(Graphics g, int index, int rank, long prevScore, boolean focus) // hit counts (custom: osu! shows user instead, above score) Utils.FONT_SMALL.drawString( textX, y + textOffset + Utils.FONT_MEDIUM.getLineHeight(), - String.format("300:%d 100:%d 50:%d Miss:%d", hit300, hit100, hit50, miss), + String.format("300:%d 100:%d 50:%d Miss:%d Name:%s", hit300, hit100, hit50, miss, getPlayerName()), Color.white ); @@ -331,6 +335,11 @@ public String toString() { ); } + public String getPlayerName() { + if(playerName == null) + return "Null Name"; + return playerName; + } @Override public int compareTo(ScoreData that) { if (this.score != that.score) diff --git a/src/itdelatrisu/opsu/beatmap/Beatmap.java b/src/itdelatrisu/opsu/beatmap/Beatmap.java index ad8b5476..7216b0de 100644 --- a/src/itdelatrisu/opsu/beatmap/Beatmap.java +++ b/src/itdelatrisu/opsu/beatmap/Beatmap.java @@ -184,7 +184,10 @@ public class Beatmap implements Comparable { /** Slider border color. If null, the skin value is used. */ public Color sliderBorder; - + + /** md5 hash of this file */ + public String md5Hash; + /** * [HitObjects] */ diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java index 369ea19e..315d8f7a 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java @@ -19,16 +19,20 @@ package itdelatrisu.opsu.beatmap; import itdelatrisu.opsu.ErrorHandler; +import itdelatrisu.opsu.MD5InputStreamWrapper; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.db.BeatmapDB; +import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -205,7 +209,15 @@ private static Beatmap parseFile(File file, File dir, ArrayList beatmap Beatmap beatmap = new Beatmap(file); beatmap.timingPoints = new ArrayList(); - try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { + try (InputStream inFileStream = new BufferedInputStream(new FileInputStream(file));){ + MD5InputStreamWrapper md5stream = null; + try { + md5stream = new MD5InputStreamWrapper(inFileStream); + } catch (NoSuchAlgorithmException e1) { + ErrorHandler.error("Failed to get MD5 hash stream.", e1, true); + } + BufferedReader in = new BufferedReader(new InputStreamReader(md5stream!=null?md5stream:inFileStream, "UTF-8")); + String line = in.readLine(); String tokens[] = null; while (line != null) { @@ -578,6 +590,8 @@ else if ((type & HitObject.TYPE_SLIDER) > 0) break; } } + if (md5stream != null) + beatmap.md5Hash = md5stream.getMD5(); } catch (IOException e) { ErrorHandler.error(String.format("Failed to read file '%s'.", file.getAbsolutePath()), e, false); } diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java b/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java index 7c5aab81..80362838 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; @@ -57,6 +58,10 @@ public class BeatmapSetList { /** Set of all beatmap set IDs for the parsed beatmaps. */ private HashSet MSIDdb; + + /** Map of all hash to Beatmap . */ + public HashMap beatmapHashesToFile; + /** Index of current expanded node (-1 if no node is expanded). */ private int expandedIndex; @@ -83,6 +88,7 @@ public class BeatmapSetList { private BeatmapSetList() { parsedNodes = new ArrayList(); MSIDdb = new HashSet(); + beatmapHashesToFile = new HashMap(); reset(); } @@ -117,6 +123,10 @@ public BeatmapSetNode addSongGroup(ArrayList beatmaps) { int msid = beatmaps.get(0).beatmapSetID; if (msid > 0) MSIDdb.add(msid); + for(Beatmap f : beatmaps) { + beatmapHashesToFile.put(f.md5Hash, f); + } + return node; } @@ -501,4 +511,8 @@ public boolean search(String query) { * @return true if id is in the list */ public boolean containsBeatmapSetID(int id) { return MSIDdb.contains(id); } + + public Beatmap getFileFromBeatmapHash(String beatmapHash) { + return beatmapHashesToFile.get(beatmapHash); + } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/db/BeatmapDB.java b/src/itdelatrisu/opsu/db/BeatmapDB.java index 3f85373e..57508975 100644 --- a/src/itdelatrisu/opsu/db/BeatmapDB.java +++ b/src/itdelatrisu/opsu/db/BeatmapDB.java @@ -43,7 +43,7 @@ public class BeatmapDB { * Current database version. * This value should be changed whenever the database format changes. */ - private static final String DATABASE_VERSION = "2014-06-08"; + private static final String DATABASE_VERSION = "2015-06-11"; /** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */ private static final float LOAD_BATCH_MIN_RATIO = 0.2f; @@ -96,7 +96,7 @@ public static void init() { insertStmt = connection.prepareStatement( "INSERT INTO beatmaps VALUES (" + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + - "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ); selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?"); deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?"); @@ -122,7 +122,8 @@ private static void createDatabase() { "bpmMin INTEGER, bpmMax INTEGER, endTime INTEGER, " + "audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " + "mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " + - "bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT" + + "bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT," + + "md5hash TEXT" + "); " + "CREATE TABLE IF NOT EXISTS info (" + "key TEXT NOT NULL UNIQUE, value TEXT" + @@ -340,6 +341,7 @@ private static void setStatementFields(PreparedStatement stmt, Beatmap beatmap) stmt.setString(38, beatmap.timingPointsToString()); stmt.setString(39, beatmap.breaksToString()); stmt.setString(40, beatmap.comboToString()); + stmt.setString(41, beatmap.md5Hash); } catch (SQLException e) { throw e; } catch (Exception e) { @@ -481,6 +483,7 @@ private static void setBeatmapFields(ResultSet rs, Beatmap beatmap) throws SQLEx if (bg != null) beatmap.bg = new File(dir, BeatmapParser.getDBString(bg)); beatmap.sliderBorderFromString(rs.getString(37)); + beatmap.md5Hash = BeatmapParser.getDBString(rs.getString(41)); } catch (SQLException e) { throw e; } catch (Exception e) { diff --git a/src/itdelatrisu/opsu/db/ScoreDB.java b/src/itdelatrisu/opsu/db/ScoreDB.java index 5ac5c300..4feee2da 100644 --- a/src/itdelatrisu/opsu/db/ScoreDB.java +++ b/src/itdelatrisu/opsu/db/ScoreDB.java @@ -45,7 +45,7 @@ public class ScoreDB { * This value should be changed whenever the database format changes. * Add any update queries to the {@link #getUpdateQueries(int)} method. */ - private static final int DATABASE_VERSION = 20140311; + private static final int DATABASE_VERSION = 20150401; /** * Returns a list of SQL queries to apply, in order, to update from @@ -57,6 +57,8 @@ private static List getUpdateQueries(int version) { List list = new LinkedList(); if (version < 20140311) list.add("ALTER TABLE scores ADD COLUMN replay TEXT"); + if (version < 20150401) + list.add("ALTER TABLE scores ADD COLUMN playerName TEXT"); /* add future updates here */ @@ -95,8 +97,13 @@ public static void init() { // prepare sql statements try { + + //TODO timestamp as primary key should prevent importing the same replay multiple times + //but if for some magical reason two different replays has the same time stamp + //it will fail, such as copying replays from another drive? which will reset + //the last modified of the file. insertStmt = connection.prepareStatement( - "INSERT INTO scores VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + "INSERT OR IGNORE INTO scores VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ); selectMapStmt = connection.prepareStatement( "SELECT * FROM scores WHERE " + @@ -114,7 +121,8 @@ public static void init() { "DELETE FROM scores WHERE " + "timestamp = ? AND MID = ? AND MSID = ? AND title = ? AND artist = ? AND " + "creator = ? AND version = ? AND hit300 = ? AND hit100 = ? AND hit50 = ? AND " + - "geki = ? AND katu = ? AND miss = ? AND score = ? AND combo = ? AND perfect = ? AND mods = ?" + "geki = ? AND katu = ? AND miss = ? AND score = ? AND combo = ? AND perfect = ? AND mods = ? AND " + + "replay = ? AND playerName = ?" ); } catch (SQLException e) { ErrorHandler.error("Failed to prepare score statements.", e, true); @@ -137,7 +145,8 @@ private static void createDatabase() { "combo INTEGER, " + "perfect BOOLEAN, " + "mods INTEGER, " + - "replay TEXT" + + "replay TEXT," + + "playerName TEXT"+ ");" + "CREATE TABLE IF NOT EXISTS info (" + "key TEXT NOT NULL UNIQUE, value TEXT" + @@ -283,6 +292,9 @@ private static void setStatementFields(PreparedStatement stmt, ScoreData data) stmt.setInt(15, data.combo); stmt.setBoolean(16, data.perfect); stmt.setInt(17, data.mods); + stmt.setString(18, data.replayString); + stmt.setString(19, data.playerName); + } /** diff --git a/src/itdelatrisu/opsu/io/OsuReader.java b/src/itdelatrisu/opsu/io/OsuReader.java index c36a2b88..0cf84dcd 100644 --- a/src/itdelatrisu/opsu/io/OsuReader.java +++ b/src/itdelatrisu/opsu/io/OsuReader.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.io; +import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; @@ -50,7 +51,7 @@ public OsuReader(File file) throws IOException { * @param source the input stream to read from */ public OsuReader(InputStream source) { - this.reader = new DataInputStream(source); + this.reader = new DataInputStream(new BufferedInputStream(source)); } /** diff --git a/src/itdelatrisu/opsu/io/OsuWriter.java b/src/itdelatrisu/opsu/io/OsuWriter.java index eca07b3a..91b2efce 100644 --- a/src/itdelatrisu/opsu/io/OsuWriter.java +++ b/src/itdelatrisu/opsu/io/OsuWriter.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.io; +import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; @@ -51,7 +52,7 @@ public OsuWriter(File file) throws FileNotFoundException { * @param dest the output stream to write to */ public OsuWriter(OutputStream dest) { - this.writer = new DataOutputStream(dest); + this.writer = new DataOutputStream(new BufferedOutputStream(dest)); } /** diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index d52f32fd..c844dcc0 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -38,6 +38,9 @@ public class Circle implements GameObject { /** The amount of time, in milliseconds, to fade in the circle. */ private static final int FADE_IN_TIME = 375; + /** The diameter of Circle Hitobjects */ + private static float diameter; + /** The associated HitObject. */ private HitObject hitObject; @@ -62,11 +65,12 @@ public class Circle implements GameObject { * @param circleSize the map's circleSize value */ public static void init(GameContainer container, float circleSize) { - int diameter = (int) (104 - (circleSize * 8)); - diameter = (int) (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480) - GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter)); - GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter)); - GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter)); + diameter = (104 - (circleSize * 8)); + diameter = (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480) + int diameterInt = (int)diameter; + GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt)); + GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameterInt, diameterInt)); + GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt)); } /** @@ -119,15 +123,16 @@ public void draw(Graphics g, int trackPosition) { private int hitResult(int time) { int timeDiff = Math.abs(time); + int[] hitResultOffset = game.getHitResultOffsets(); int result = -1; - if (timeDiff < hitResultOffset[GameData.HIT_300]) + if (timeDiff <= hitResultOffset[GameData.HIT_300]) result = GameData.HIT_300; - else if (timeDiff < hitResultOffset[GameData.HIT_100]) + else if (timeDiff <= hitResultOffset[GameData.HIT_100]) result = GameData.HIT_100; - else if (timeDiff < hitResultOffset[GameData.HIT_50]) + else if (timeDiff <= hitResultOffset[GameData.HIT_50]) result = GameData.HIT_50; - else if (timeDiff < hitResultOffset[GameData.HIT_MISS]) + else if (timeDiff <= hitResultOffset[GameData.HIT_MISS]) result = GameData.HIT_MISS; //else not a hit @@ -137,8 +142,7 @@ else if (timeDiff < hitResultOffset[GameData.HIT_MISS]) @Override public boolean mousePressed(int x, int y, int trackPosition) { double distance = Math.hypot(this.x - x, this.y - y); - int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; - if (distance < circleRadius) { + if (distance < diameter/2) { int timeDiff = trackPosition - hitObject.getTime(); int result = hitResult(timeDiff); @@ -158,7 +162,7 @@ public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolea int[] hitResultOffset = game.getHitResultOffsets(); boolean isAutoMod = GameMod.AUTO.isActive(); - if (overlap || trackPosition > time + hitResultOffset[GameData.HIT_50]) { + if (trackPosition > time + hitResultOffset[GameData.HIT_50]) { if (isAutoMod) // "auto" mod: catch any missed notes due to lag data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true); @@ -193,4 +197,9 @@ public void updatePosition() { this.x = hitObject.getScaledX(); this.y = hitObject.getScaledY(); } + + @Override + public void reset() { + + } } diff --git a/src/itdelatrisu/opsu/objects/DummyObject.java b/src/itdelatrisu/opsu/objects/DummyObject.java index 3f928c36..80f920a7 100644 --- a/src/itdelatrisu/opsu/objects/DummyObject.java +++ b/src/itdelatrisu/opsu/objects/DummyObject.java @@ -63,4 +63,9 @@ public void updatePosition() { this.x = hitObject.getScaledX(); this.y = hitObject.getScaledY(); } + + @Override + public void reset() { + + } } diff --git a/src/itdelatrisu/opsu/objects/GameObject.java b/src/itdelatrisu/opsu/objects/GameObject.java index ca1ab8e5..39c66020 100644 --- a/src/itdelatrisu/opsu/objects/GameObject.java +++ b/src/itdelatrisu/opsu/objects/GameObject.java @@ -69,4 +69,11 @@ public interface GameObject { * Updates the position of the hit object. */ public void updatePosition(); + + /** + * Resets the hit object so that it can be reused. + */ + public void reset(); + + } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 414aff0e..ee81fb77 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -49,6 +49,10 @@ public class Slider implements GameObject { /** Rate at which slider ticks are placed. */ private static float sliderTickRate = 1.0f; + + private static float followRadius; + + private static float diameter; /** The amount of time, in milliseconds, to fade in the slider. */ private static final int FADE_IN_TIME = 375; @@ -100,6 +104,8 @@ public class Slider implements GameObject { /** Container dimensions. */ private static int containerWidth, containerHeight; + + /** * Initializes the Slider data type with images and dimensions. @@ -110,10 +116,12 @@ public class Slider implements GameObject { public static void init(GameContainer container, float circleSize, Beatmap beatmap) { containerWidth = container.getWidth(); containerHeight = container.getHeight(); - - int diameter = (int) (104 - (circleSize * 8)); - diameter = (int) (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480) - + + diameter = (104 - (circleSize * 8)); + diameter = (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480) + int diameterInt = (int)diameter; + + followRadius = diameter / 2 * 3f; // slider ball if (GameImage.SLIDER_BALL.hasSkinImages() || (!GameImage.SLIDER_BALL.hasSkinImage() && GameImage.SLIDER_BALL.getImages() != null)) @@ -121,11 +129,11 @@ public static void init(GameContainer container, float circleSize, Beatmap beatm else 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); + sliderBallImages[i] = sliderBallImages[i].getScaledCopy(diameterInt * 118 / 128, diameterInt * 118 / 128); - 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)); + GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameterInt * 259 / 128, diameterInt * 259 / 128)); + GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameterInt, diameterInt)); + GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameterInt / 4, diameterInt / 4)); sliderMultiplier = beatmap.sliderMultiplier; sliderTickRate = beatmap.sliderTickRate; @@ -272,6 +280,55 @@ public void draw(Graphics g, int trackPosition) { * @return the hit result (GameData.HIT_* constants) */ private int hitResult() { + /* + time scoredelta score-hit-initial-tick= unaccounted + (1/4 - 1) 396 - 300 - 30 46 + (1+1/4 - 2) 442 - 300 - 30 - 10 + (2+1/4 - 3) 488 - 300 - 30 - 2*10 896 (408)5x + (3+1/4 - 4) 534 - 300 - 30 - 3*10 + (4+1/4 - 5) 580 - 300 - 30 - 4*10 + (5+1/4 - 6) 626 - 300 - 30 - 5*10 + (6+1/4 - 7) 672 - 300 - 30 - 6*10 + + difficultyMulti = 3 (+36 per combo) + + score = + (t)ticks(10) * nticks + + (h)hitValue + (c)combo (hitValue/25 * difficultyMultiplier*(combo-1)) + (i)initialHit (30) + + (f)finalHit(30) + + + s t h c i f + 626 - 10*5 - 300 - 276(-216 - 30 - 30) (all)(7x) + 240 - 10*5 - 100 - 90 (-60 <- 30>) (no final or initial)(6x) + + 218 - 10*4 - 100 - 78 (-36 - 30) (4 tick no initial)(5x) + 196 - 10*3 - 100 - 66 (-24 - 30 ) (3 tick no initial)(4x) + 112 - 10*2 - 50 - 42 (-12 - 30 ) (2 tick no initial)(3x) + 96 - 10 - 50 - 36 ( -6 - 30 ) (1 tick no initial)(2x) + + 206 - 10*4 - 100 - 66 (-36 - 30 ) (4 tick no initial)(4x) + 184 - 10*3 - 100 - 54 (-24 - 30 ) (3 tick no initial)(3x) + 90 - 10 - 50 - 30 ( - 30 ) (1 tick no initial)(0x) + + 194 - 10*4 - 100 - 54 (-24 - 30 ) (4 tick no initial)(3x) + + 170 - 10*4 - 100 - 30 ( - 30 ) (4 tick no final)(0x) + 160 - 10*3 - 100 - 30 ( - 30 ) (3 tick no final)(0x) + 100 - 10*2 - 50 - 30 ( - 30 ) (2 tick no final)(0x) + + 198 - 10*5 - 100 - 48 (-36 ) (no initial and final)(5x) + 110 - 50 - ( - 30 - 30 ) (final and initial no tick)(0x) + 80 - 50 - ( <- 30> ) (only final or initial)(0x) + + 140 - 10*4 - 100 - 0 (4 ticks only)(0x) + 80 - 10*3 - 50 - 0 (3 tick only)(0x) + 70 - 10*2 - 50 - 0 (2 tick only)(0x) + 60 - 10 - 50 - 0 (1 tick only)(0x) + + + */ float tickRatio = (float) ticksHit / tickIntervals; int result; @@ -308,8 +365,7 @@ public boolean mousePressed(int x, int y, int trackPosition) { return false; double distance = Math.hypot(this.x - x, this.y - y); - int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; - if (distance < circleRadius) { + if (distance < diameter / 2) { int timeDiff = Math.abs(trackPosition - hitObject.getTime()); int[] hitResultOffset = game.getHitResultOffsets(); @@ -365,26 +421,29 @@ else if (GameMod.RELAX.isActive() && trackPosition >= time) } // end of slider - if (overlap || trackPosition > hitObject.getTime() + sliderTimeTotal) { + if (trackPosition > hitObject.getTime() + sliderTimeTotal) { tickIntervals++; // check if cursor pressed and within end circle if (keyPressed || GameMod.RELAX.isActive()) { float[] c = curve.pointAt(getT(trackPosition, false)); double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY); - int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2; - if (distance < followCircleRadius) + if (distance < followRadius) sliderClickedFinal = true; } // final circle hit - if (sliderClickedFinal) + if (sliderClickedFinal){ ticksHit++; + data.sliderFinalResult(hitObject.getTime(), GameData.HIT_SLIDER30, this.x, this.y, hitObject, currentRepeats); + } // "auto" mod: always send a perfect hit result if (isAutoMod) ticksHit = tickIntervals; + //TODO missing the final shouldn't increment the combo + // calculate and send slider result hitResult(); return true; @@ -417,8 +476,7 @@ else if (GameMod.RELAX.isActive() && trackPosition >= time) // holding slider... float[] c = curve.pointAt(getT(trackPosition, false)); double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY); - int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2; - if (((keyPressed || GameMod.RELAX.isActive()) && distance < followCircleRadius) || isAutoMod) { + if (((keyPressed || GameMod.RELAX.isActive()) && distance < followRadius) || isAutoMod) { // mouse pressed and within follow circle followCircleActive = true; data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER); @@ -501,4 +559,16 @@ private float getT(int trackPosition, boolean raw) { return (floor % 2 == 0) ? t - floor : floor + 1 - t; } } + + @Override + public void reset() { + sliderClickedInitial = false; + sliderClickedFinal = false; + followCircleActive = false; + currentRepeats = 0; + tickIndex = 0; + ticksHit = 0; + tickIntervals = 1; + } + } diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index efea9112..9f0c18de 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -45,11 +45,10 @@ public class Spinner implements GameObject { private static float overallDifficulty = 5f; /** The number of rotation velocities to store. */ - // note: currently takes about 200ms to spin up (4 * 50) - private static final int MAX_ROTATION_VELOCITIES = 50; + private int maxStoredDeltaAngles; /** The amount of time, in milliseconds, before another velocity is stored. */ - private static final int DELTA_UPDATE_TIME = 4; + private static final float DELTA_UPDATE_TIME = 1000 / 60f; /** The amount of time, in milliseconds, to fade in the spinner. */ private static final int FADE_IN_TIME = 500; @@ -64,6 +63,8 @@ public class Spinner implements GameObject { TWO_PI = (float) (Math.PI * 2), HALF_PI = (float) (Math.PI / 2); + private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * AUTO_MULTIPLIER; // ~95.3 + /** The associated HitObject. */ private HitObject hitObject; @@ -83,19 +84,25 @@ public class Spinner implements GameObject { private float rotationsNeeded; /** The remaining amount of time that was not used. */ - private int deltaOverflow; + private float deltaOverflow; /** The sum of all the velocities in storedVelocities. */ - private float sumVelocity = 0f; + private float sumDeltaAngle = 0f; /** Array holding the most recent rotation velocities. */ - private float[] storedVelocities = new float[MAX_ROTATION_VELOCITIES]; + private float[] storedDeltaAngle; /** True if the mouse cursor is pressed. */ private boolean isSpinning; /** Current index of the stored velocities in rotations/second. */ - private int velocityIndex = 0; + private int deltaAngleIndex = 0; + + /** The remaining amount of the angle that was not used. */ + private float deltaAngleOverflow = 0; + + /** The RPM that is drawn to the screen. */ + private int drawnRPM = 0; /** * Initializes the Spinner data type with images and dimensions. @@ -117,7 +124,46 @@ public static void init(GameContainer container, float difficulty) { public Spinner(HitObject hitObject, Game game, GameData data) { this.hitObject = hitObject; this.data = data; - +/* + 1 beat = 731.707317073171ms + RPM at frame X with spinner Y beats long + 10 20 30 40 50 60 5sec ~ 48 ~ 800ms + final int minVel = 12; + final int maxVel = 48; + final int minTime = 2000; + final int maxTime = 5000; + maxStoredDeltaAngles = (int) Utils.clamp( + (hitObject.getEndTime() - hitObject.getTime() - minTime) * (maxVel-minVel)/(maxTime-minTime) + minVel + , minVel, maxVel); + storedDeltaAngle = new float[maxStoredDeltaAngles]; + // calculate rotations needed float spinsPerMinute = 100 + (overallDifficulty * 15); rotationsNeeded = spinsPerMinute * (hitObject.getEndTime() - hitObject.getTime()) / 60000f; @@ -144,12 +190,11 @@ public void draw(Graphics g, int trackPosition) { } // rpm - int rpm = Math.abs(Math.round(sumVelocity / storedVelocities.length * 60)); Image rpmImg = GameImage.SPINNER_RPM.getImage(); rpmImg.setAlpha(alpha); rpmImg.drawCentered(width / 2f, height - rpmImg.getHeight() / 2f); if (timeDiff < 0) - data.drawSymbolString(Integer.toString(rpm), (width + rpmImg.getWidth() * 0.95f) / 2f, + data.drawSymbolString(Integer.toString(drawnRPM), (width + rpmImg.getWidth() * 0.95f) / 2f, height - data.getScoreSymbolImage('0').getHeight() * 1.025f, 1f, 1f, true); // spinner meter (subimage) @@ -205,7 +250,10 @@ else if (ratio >= 0.75f) } @Override - public boolean mousePressed(int x, int y, int trackPosition) { return false; } // not used + public boolean mousePressed(int x, int y, int trackPosition) { + lastAngle = (float) Math.atan2(x - (height / 2), y - (width / 2)); + return false; + } @Override public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) { @@ -222,17 +270,18 @@ public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolea // spin automatically // http://osu.ppy.sh/wiki/FAQ#Spinners - float angle; + + deltaOverflow += delta; + + float angleDiff = 0; if (GameMod.AUTO.isActive()) { - lastAngle = 0; - angle = delta * AUTO_MULTIPLIER; + angleDiff = delta * AUTO_MULTIPLIER; isSpinning = true; } else if (GameMod.SPUN_OUT.isActive() || GameMod.AUTOPILOT.isActive()) { - lastAngle = 0; - angle = delta * SPUN_OUT_MULTIPLIER; + angleDiff = delta * SPUN_OUT_MULTIPLIER; isSpinning = true; } else { - angle = (float) Math.atan2(mouseY - (height / 2), mouseX - (width / 2)); + float angle = (float) Math.atan2(mouseY - (height / 2), mouseX - (width / 2)); // set initial angle to current mouse position to skip first click if (!isSpinning && (keyPressed || GameMod.RELAX.isActive())) { @@ -240,35 +289,52 @@ public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolea isSpinning = true; return false; } + angleDiff = angle - lastAngle; + if(Math.abs(angleDiff) > 0.01f){ + lastAngle = angle; + }else{ + angleDiff = 0; + } + } // make angleDiff the smallest angle change possible // (i.e. 1/4 rotation instead of 3/4 rotation) - float angleDiff = angle - lastAngle; if (angleDiff < -Math.PI) angleDiff += TWO_PI; else if (angleDiff > Math.PI) angleDiff -= TWO_PI; - - // spin caused by the cursor - float cursorVelocity = 0; + + //may be a problem at higher frame rate due to float point round off if (isSpinning) - cursorVelocity = Utils.clamp(angleDiff / TWO_PI / delta * 1000, -8f, 8f); - - deltaOverflow += delta; + deltaAngleOverflow += angleDiff; + while (deltaOverflow >= DELTA_UPDATE_TIME) { - sumVelocity -= storedVelocities[velocityIndex]; - sumVelocity += cursorVelocity; - storedVelocities[velocityIndex++] = cursorVelocity; - velocityIndex %= storedVelocities.length; + // spin caused by the cursor + float deltaAngle = 0; + if (isSpinning){ + deltaAngle = deltaAngleOverflow * DELTA_UPDATE_TIME / deltaOverflow; + deltaAngleOverflow -= deltaAngle; + deltaAngle = Utils.clamp(deltaAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF); + } + sumDeltaAngle -= storedDeltaAngle[deltaAngleIndex]; + sumDeltaAngle += deltaAngle; + storedDeltaAngle[deltaAngleIndex++] = deltaAngle; + deltaAngleIndex %= storedDeltaAngle.length; deltaOverflow -= DELTA_UPDATE_TIME; + + float rotationAngle = sumDeltaAngle / maxStoredDeltaAngles; + rotationAngle = Utils.clamp(rotationAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF); + float rotationPerSec = rotationAngle * (1000/DELTA_UPDATE_TIME) / TWO_PI; + + drawnRPM = (int)(Math.abs(rotationPerSec * 60)); + + rotate(rotationAngle); + if (Math.abs(rotationAngle) > 0.00001f) + data.changeHealth(DELTA_UPDATE_TIME * GameData.HP_DRAIN_MULTIPLIER); + } - float rotationAngle = sumVelocity / storedVelocities.length * TWO_PI * delta / 1000; - rotate(rotationAngle); - if (Math.abs(rotationAngle) > 0.00001f) - data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER); - - lastAngle = angle; + //TODO may need to update 1 more time when the spinner ends? return false; } @@ -311,15 +377,38 @@ private void rotate(float angle) { // added one whole rotation... if (Math.floor(newRotations) > rotations) { + //TODO seems to give 1100 points per spin but also an extra 100 for some spinners if (newRotations > rotationsNeeded) { // extra rotations data.changeScore(1000); + SoundController.playSound(SoundEffect.SPINNERBONUS); - } else { + } + data.changeScore(100); + SoundController.playSound(SoundEffect.SPINNERSPIN); + + } + /* + //The extra 100 for some spinners (mostly wrong) + if (Math.floor(newRotations + 0.5f) > rotations + 0.5f) { + if (newRotations + 0.5f > rotationsNeeded) { // extra rotations data.changeScore(100); - SoundController.playSound(SoundEffect.SPINNERSPIN); } } + //*/ rotations = newRotations; } + + @Override + public void reset() { + deltaAngleIndex = 0; + sumDeltaAngle = 0; + for(int i=0; i= replay.frames.length) updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed); + + //TODO probably should to disable sounds then reseek to the new position + if(seeking && replayIndex-1 >= 1 && replayIndex < replay.frames.length && trackPosition < replay.frames[replayIndex-1].getTime()){ + replayIndex = 0; + while(objectIndex>=0){ + gameObjects[objectIndex].reset(); + objectIndex--; + + } + // reset game data + resetGameData(); + + // load the first timingPoint + if (!beatmap.timingPoints.isEmpty()) { + TimingPoint timingPoint = beatmap.timingPoints.get(0); + if (!timingPoint.isInherited()) { + setBeatLength(timingPoint, true); + timingPointIndex++; + } + } + seeking = false; + } // update and run replay frames while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) { @@ -753,7 +781,7 @@ else if (replayFrames != null) { while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) { // check if we've already passed the next object's start time boolean overlap = (objectIndex + 1 < gameObjects.length && - trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_300]); + trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]); // update hit object and check completion status if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition)) @@ -897,6 +925,11 @@ else if (playbackSpeed.getButton().contains(x, y)) { MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); } + if(!GameMod.AUTO.isActive() && y < 50){ + float pos = (float)x / width * beatmap.endTime; + MusicController.setPosition((int)pos); + seeking = true; + } return; } @@ -1044,6 +1077,9 @@ public void enter(GameContainer container, StateBasedGame game) } else if (restart == Restart.REPLAY) retries = 0; + gameObjects = new GameObject[beatmap.objects.length]; + playbackSpeed = PlaybackSpeed.NORMAL; + // reset game data resetGameData(); @@ -1063,7 +1099,7 @@ public void enter(GameContainer container, StateBasedGame game) // is this the last note in the combo? boolean comboEnd = false; - if (i + 1 < beatmap.objects.length && beatmap.objects[i + 1].isNewCombo()) + if (i + 1 >= beatmap.objects.length || beatmap.objects[i + 1].isNewCombo()) comboEnd = true; Color color = combo[hitObject.getComboIndex()]; @@ -1278,7 +1314,6 @@ public void loadBeatmap(Beatmap beatmap) { * Resets all game data and structures. */ public void resetGameData() { - gameObjects = new GameObject[beatmap.objects.length]; data.clear(); objectIndex = 0; breakIndex = 0; @@ -1303,8 +1338,7 @@ public void resetGameData() { autoMouseY = 0; autoMousePressed = false; flashlightRadius = container.getHeight() * 2 / 3; - playbackSpeed = PlaybackSpeed.NORMAL; - + System.gc(); } @@ -1405,11 +1439,19 @@ private void setMapModifiers() { // overallDifficulty (hit result time offsets) hitResultOffset = new int[GameData.HIT_MAX]; + /* + float mult = 0.608f; + hitResultOffset[GameData.HIT_300] = (int) ((128 - (overallDifficulty * 9.6))*mult); + hitResultOffset[GameData.HIT_100] = (int) ((224 - (overallDifficulty * 12.8))*mult); + hitResultOffset[GameData.HIT_50] = (int) ((320 - (overallDifficulty * 16))*mult); + hitResultOffset[GameData.HIT_MISS] = (int) ((1000 - (overallDifficulty * 10))*mult); + /*/ hitResultOffset[GameData.HIT_300] = (int) (78 - (overallDifficulty * 6)); hitResultOffset[GameData.HIT_100] = (int) (138 - (overallDifficulty * 8)); hitResultOffset[GameData.HIT_50] = (int) (198 - (overallDifficulty * 10)); hitResultOffset[GameData.HIT_MISS] = (int) (500 - (overallDifficulty * 10)); data.setHitResultOffset(hitResultOffset); + //*/ // HPDrainRate (health change) data.setDrainRate(HPDrainRate); diff --git a/src/itdelatrisu/opsu/states/Splash.java b/src/itdelatrisu/opsu/states/Splash.java index 7b48729e..8d8ef388 100644 --- a/src/itdelatrisu/opsu/states/Splash.java +++ b/src/itdelatrisu/opsu/states/Splash.java @@ -25,6 +25,7 @@ import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; +import itdelatrisu.opsu.replay.ReplayImporter; import itdelatrisu.opsu.beatmap.BeatmapSetList; import itdelatrisu.opsu.beatmap.BeatmapParser; import itdelatrisu.opsu.ui.UI; @@ -127,6 +128,9 @@ public void run() { // parse song directory BeatmapParser.parseAllFiles(beatmapDir); + + // import replays + ReplayImporter.importAllReplaysFromDir(Options.getReplayImportDir()); // load sounds SoundController.init(); diff --git a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java index 2e737886..7042407b 100644 --- a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java +++ b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java @@ -41,6 +41,7 @@ import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; + /** * A generic tool to work on a supplied stream, pulling out PCM data and buffered it to OpenAL * as required. @@ -224,6 +225,7 @@ public synchronized void play(boolean loop) throws IOException { */ public void setup(float pitch) { this.pitch = pitch; + syncPosition(); } /** @@ -354,7 +356,7 @@ public synchronized boolean setPosition(float position) { } playedPos = streamPos; - syncStartTime = getTime() - playedPos * 1000 / sampleSize / sampleRate; + syncStartTime = (long) (getTime() - (playedPos * 1000 / sampleSize / sampleRate)/pitch); startPlayback(); @@ -403,24 +405,34 @@ public float getPosition() { float thisPosition = getALPosition(); long thisTime = getTime(); float dxPosition = thisPosition - lastUpdatePosition; - long dxTime = thisTime - syncStartTime; + float dxTime = (thisTime - syncStartTime) * pitch; // hard reset if (Math.abs(thisPosition - dxTime / 1000f) > 1 / 2f) { - syncStartTime = thisTime - ((long) (thisPosition * 1000)); - dxTime = thisTime - syncStartTime; + //System.out.println("Time HARD Reset"+" "+thisPosition+" "+(dxTime / 1000f)); + syncPosition(); + dxTime = (thisTime - syncStartTime) * pitch; avgDiff = 0; } if ((int) (dxPosition * 1000) != 0) { // lastPosition != thisPosition float diff = thisPosition * 1000 - (dxTime); + avgDiff = (diff + avgDiff * 9) / 10; - syncStartTime -= (int) (avgDiff/2); - dxTime = thisTime - syncStartTime; + if(Math.abs(avgDiff) >= 1){ + syncStartTime -= (int)(avgDiff); + avgDiff -= (int)(avgDiff); + dxTime = (thisTime - syncStartTime) * pitch; + } lastUpdatePosition = thisPosition; } + return dxTime / 1000f; } + private void syncPosition(){ + syncStartTime = getTime() - (long) ( getALPosition() * 1000 / pitch); + avgDiff = 0; + } /** * Processes a track pause. diff --git a/src/org/newdawn/slick/openal/SoundStore.java b/src/org/newdawn/slick/openal/SoundStore.java index 30655b00..01e135b0 100644 --- a/src/org/newdawn/slick/openal/SoundStore.java +++ b/src/org/newdawn/slick/openal/SoundStore.java @@ -536,6 +536,7 @@ private int getMusicSource() { */ public void setMusicPitch(float pitch) { if (soundWorks) { + stream.setup(pitch); AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch); } }