diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 4ebb7781..8d957ed8 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -22,6 +22,7 @@ import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.TimingPoint; import itdelatrisu.opsu.states.Game; +import yugecin.opsudance.options.Options; import java.io.File; import java.io.IOException; @@ -358,10 +359,14 @@ public class MusicController { * If no track is loaded, 0 will be returned. */ public static int getPosition() { + int offset = OPTION_MUSIC_OFFSET.val; + if (lastBeatmap != null) + offset += lastBeatmap.localMusicOffset; + if (isPlaying()) - return (int) (player.getPosition() * 1000 + OPTION_MUSIC_OFFSET.val + Game.currentMapMusicOffset); + return (int) (player.getPosition() * 1000 + offset); else if (isPaused()) - return Math.max((int) (pauseTime * 1000 + OPTION_MUSIC_OFFSET.val + Game.currentMapMusicOffset), 0); + return Math.max((int) (pauseTime * 1000 + offset), 0); else return 0; } diff --git a/src/itdelatrisu/opsu/beatmap/Beatmap.java b/src/itdelatrisu/opsu/beatmap/Beatmap.java index acb24feb..dac22ad1 100644 --- a/src/itdelatrisu/opsu/beatmap/Beatmap.java +++ b/src/itdelatrisu/opsu/beatmap/Beatmap.java @@ -89,6 +89,9 @@ public class Beatmap implements Comparable { /** The last time this beatmap was played (timestamp). */ public long lastPlayed = 0; + /** The local music offset. */ + public int localMusicOffset = 0; + /** * [General] */ diff --git a/src/itdelatrisu/opsu/db/BeatmapDB.java b/src/itdelatrisu/opsu/db/BeatmapDB.java index 745271db..e0d4ca8e 100644 --- a/src/itdelatrisu/opsu/db/BeatmapDB.java +++ b/src/itdelatrisu/opsu/db/BeatmapDB.java @@ -20,6 +20,7 @@ package itdelatrisu.opsu.db; import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.BeatmapParser; +import yugecin.opsudance.core.errorhandling.ErrorHandler; import java.io.File; import java.sql.Connection; @@ -47,7 +48,7 @@ public class BeatmapDB { * 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 = 20161222; + private static final int DATABASE_VERSION = 20161225; /** * Returns a list of SQL queries to apply, in order, to update from @@ -64,6 +65,10 @@ public class BeatmapDB { list.add("ALTER TABLE beatmaps ADD COLUMN lastPlayed INTEGER"); list.add("UPDATE beatmaps SET dateAdded = 0, favorite = 0, playCount = 0, lastPlayed = 0"); } + if (version < 20161225) { + list.add("ALTER TABLE beatmaps ADD COLUMN localOffset INTEGER"); + list.add("UPDATE beatmaps SET localOffset = 0"); + } /* add future updates here */ @@ -85,7 +90,7 @@ public class BeatmapDB { /** Query statements. */ private static PreparedStatement insertStmt, selectStmt, deleteMapStmt, deleteGroupStmt, - setStarsStmt, updatePlayStatsStmt, setFavoriteStmt, updateSizeStmt; + setStarsStmt, updatePlayStatsStmt, setFavoriteStmt, setLocalOffsetStmt, updateSizeStmt; /** Current size of beatmap cache table. */ private static int cacheSize = -1; @@ -121,7 +126,7 @@ public class BeatmapDB { "INSERT INTO beatmaps VALUES (" + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + - "?, ?, ?, ?, ?, ?" + + "?, ?, ?, ?, ?, ?, ?" + ")" ); selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?"); @@ -130,6 +135,7 @@ public class BeatmapDB { setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?"); updatePlayStatsStmt = connection.prepareStatement("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?"); setFavoriteStmt = connection.prepareStatement("UPDATE beatmaps SET favorite = ? WHERE dir = ? AND file = ?"); + setLocalOffsetStmt = connection.prepareStatement("UPDATE beatmaps SET localOffset = ? WHERE dir = ? AND file = ?"); } catch (SQLException e) { explode("Failed to prepare beatmap statements.", e, DEFAULT_OPTIONS); } @@ -153,7 +159,7 @@ public class BeatmapDB { "mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " + "bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT, " + "md5hash TEXT, stars REAL, " + - "dateAdded INTEGER, favorite BOOLEAN, playCount INTEGER, lastPlayed INTEGER" + + "dateAdded INTEGER, favorite BOOLEAN, playCount INTEGER, lastPlayed INTEGER, localOffset INTEGER" + "); " + "CREATE TABLE IF NOT EXISTS info (" + "key TEXT NOT NULL UNIQUE, value TEXT" + @@ -402,6 +408,7 @@ public class BeatmapDB { stmt.setBoolean(44, beatmap.favorite); stmt.setInt(45, beatmap.playCount); stmt.setLong(46, beatmap.lastPlayed); + stmt.setInt(47, beatmap.localMusicOffset); } catch (SQLException e) { throw e; } catch (Exception e) { @@ -549,6 +556,7 @@ public class BeatmapDB { beatmap.favorite = rs.getBoolean(44); beatmap.playCount = rs.getInt(45); beatmap.lastPlayed = rs.getLong(46); + beatmap.localMusicOffset = rs.getInt(47); } catch (SQLException e) { throw e; } catch (Exception e) { @@ -694,6 +702,24 @@ public class BeatmapDB { } } + /** + * Updates the local music offset for a beatmap in the database. + * @param beatmap the beatmap + */ + public static void updateLocalOffset(Beatmap beatmap) { + if (connection == null) + return; + try { + setLocalOffsetStmt.setInt(1, beatmap.localMusicOffset); + setLocalOffsetStmt.setString(2, beatmap.getFile().getParentFile().getName()); + setLocalOffsetStmt.setString(3, beatmap.getFile().getName()); + setLocalOffsetStmt.executeUpdate(); + } catch (SQLException e) { + ErrorHandler.explode(String.format("Failed to update local music offset for beatmap '%s' in database.", + beatmap.toString()), e, ErrorHandler.DEFAULT_OPTIONS); + } + } + /** * Closes the connection to the database. */ diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 7ebe27d8..78375d13 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -265,8 +265,6 @@ public class Game extends ComplexOpsuState { /** Music position bar coordinates and dimensions (for replay seeking). */ private float musicBarX, musicBarY, musicBarWidth, musicBarHeight; - public static int currentMapMusicOffset; - private int mirrorFrom; private int mirrorTo; @@ -386,7 +384,7 @@ public class Game extends ComplexOpsuState { public void render(Graphics g) { int trackPosition = MusicController.getPosition(); if (isLeadIn()) { - trackPosition -= leadInTime - currentMapMusicOffset - OPTION_MUSIC_OFFSET.val; + trackPosition -= leadInTime - OPTION_MUSIC_OFFSET.val - beatmap.localMusicOffset; } if (pauseTime > -1) // returning from pause screen trackPosition = pauseTime; @@ -439,7 +437,7 @@ public class Game extends ComplexOpsuState { if (GameMod.FLASHLIGHT.isActive()) { // render hit objects offscreen Graphics.setCurrent(gOffscreen); - int trackPos = (isLeadIn()) ? (leadInTime - OPTION_MUSIC_OFFSET.val) * -1 : trackPosition; + int trackPos = (isLeadIn()) ? (leadInTime - OPTION_MUSIC_OFFSET.val - beatmap.localMusicOffset) * -1 : trackPosition; drawHitObjects(gOffscreen, trackPos); // restore original graphics context @@ -566,8 +564,8 @@ public class Game extends ComplexOpsuState { Colors.WHITE_FADE.a = oldAlpha; } - if (isLeadIn()) - trackPosition = (leadInTime - OPTION_MUSIC_OFFSET.val) * -1; // render approach circles during song lead-in + if (isLeadIn()) // render approach circles during song lead-in + trackPosition = (leadInTime - OPTION_MUSIC_OFFSET.val - beatmap.localMusicOffset) * -1; // countdown if (beatmap.countdown > 0) { @@ -1231,14 +1229,13 @@ public class Game extends ComplexOpsuState { } OPTION_DANCE_MIRROR.toggle(); break; + case KEY_SUBTRACT: case KEY_MINUS: - currentMapMusicOffset += 5; - barNotifs.send("Current map offset: " + currentMapMusicOffset); + adjustLocalMusicOffset(-5); break; } - if (key == KEY_ADD || c == '+') { - currentMapMusicOffset -= 5; - barNotifs.send("Current map offset: " + currentMapMusicOffset); + if (key == KEY_EQUALS || key == KEY_ADD || c == '+') { + adjustLocalMusicOffset(5); } return true; @@ -1650,6 +1647,10 @@ public class Game extends ComplexOpsuState { scoreboardVisible = true; currentScoreboardAlpha = 0f; + // using local offset? + if (beatmap.localMusicOffset != 0) + barNotifs.send(String.format("Using local beatmap offset (%dms)", beatmap.localMusicOffset)); + // needs to play before setting position to resume without lag later MusicController.play(false); MusicController.setPosition(0); @@ -1749,7 +1750,19 @@ public class Game extends ComplexOpsuState { if (isReplay) GameMod.loadModState(previousMods); } - + + /** + * Adjusts the beatmap's local music offset. + * @param sign the sign (multiplier) + */ + public void adjustLocalMusicOffset(int amount) { + int newOffset = beatmap.localMusicOffset + amount; + barNotifs.send(String.format("Local beatmap offset set to %dms", newOffset)); + if (beatmap.localMusicOffset != newOffset) { + beatmap.localMusicOffset = newOffset; + BeatmapDB.updateLocalOffset(beatmap); + } + } public void addMergedSliderPointsToRender(int from, int to) { knorkesliders.addRange(from, to); } @@ -1908,9 +1921,6 @@ public class Game extends ComplexOpsuState { * @param beatmap the beatmap to load */ public void loadBeatmap(Beatmap beatmap) { - if (this.beatmap == null || this.beatmap.beatmapID != beatmap.beatmapID) { - currentMapMusicOffset = 0; - } this.beatmap = beatmap; Display.setTitle(String.format("opsu!dance - %s", beatmap.toString())); if (beatmap.breaks == null) { diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index ad4d1907..a320f3ca 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -117,6 +117,15 @@ public class GamePauseMenu extends BaseOpsuState { return true; } + if (key == KEY_SUBTRACT || key == KEY_MINUS) { + gameState.adjustLocalMusicOffset(-5); + return true; + } + if (key == KEY_EQUALS || key == KEY_ADD || c == '+') { + gameState.adjustLocalMusicOffset(5); + return true; + } + return false; } diff --git a/src/yugecin/opsudance/options/Options.java b/src/yugecin/opsudance/options/Options.java index 8c1a4587..9a6763f7 100644 --- a/src/yugecin/opsudance/options/Options.java +++ b/src/yugecin/opsudance/options/Options.java @@ -423,7 +423,7 @@ public class Options { public static final NumericOption OPTION_EFFECT_VOLUME = new NumericOption("Effects", "VolumeEffect", "Volume of menu and game sounds.", 70, 0, 100); public static final NumericOption OPTION_HITSOUND_VOLUME = new NumericOption("Hit Sounds", "VolumeHitSound", "Volume of hit sounds.", 30, 0, 100); - public static final NumericOption OPTION_MUSIC_OFFSET = new NumericOption("Music Offset", "Offset", "Adjust this value if hit objects are out of sync.", -75, -500, 500) { + public static final NumericOption OPTION_MUSIC_OFFSET = new NumericOption("Global Music Offset", "Offset", "Adjust this value if hit objects are out of sync.", -75, -500, 500) { @Override public String getValueString () { return String.format("%dms", val);