diff --git a/CREDITS.md b/CREDITS.md index a1d4e3b6..b3a18900 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -26,4 +26,8 @@ The following projects were referenced in creating opsu!: * "Wojtkosu" - Wojtek Kowaluk (https://osu.ppy.sh/forum/t/97260) * "osu-parser" - nojhamster (https://github.com/nojhamster/osu-parser) -* "osu! web" - pictuga (https://github.com/pictuga/osu-web) \ No newline at end of file +* "osu! web" - pictuga (https://github.com/pictuga/osu-web) + +Theme Song +---------- +The theme song is "welcome to osu!" by nekodex (https://soundcloud.com/nekodex). diff --git a/res/theme.ogg b/res/theme.ogg new file mode 100644 index 00000000..de133703 Binary files /dev/null and b/res/theme.ogg differ diff --git a/res/theme.osu b/res/theme.osu new file mode 100644 index 00000000..9ab29ae2 --- /dev/null +++ b/res/theme.osu @@ -0,0 +1,12 @@ +[General] +// theme song file name (mp3/ogg supported) +AudioFilename: theme.ogg + +[Metadata] +// theme song title and artist +Title:welcome to osu! +Artist:nekodex + +[HitObjects] +// length of theme song, in ms (third field) +1,1,48000,1,0,0:0:0:0: diff --git a/src/itdelatrisu/opsu/MusicController.java b/src/itdelatrisu/opsu/MusicController.java index 46a624da..9d191c6d 100644 --- a/src/itdelatrisu/opsu/MusicController.java +++ b/src/itdelatrisu/opsu/MusicController.java @@ -22,6 +22,7 @@ import itdelatrisu.opsu.states.Options; import java.io.File; import java.lang.reflect.Field; +import java.net.URL; import java.nio.IntBuffer; import javazoom.jl.converter.Converter; @@ -34,6 +35,7 @@ import org.newdawn.slick.SlickException; import org.newdawn.slick.openal.Audio; import org.newdawn.slick.openal.SoundStore; import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; /** * Controller for all music. @@ -59,6 +61,11 @@ public class MusicController { */ private static Thread trackLoader; + /** + * Whether the theme song is currently playing. + */ + private static boolean themePlaying = false; + // This class should not be instantiated. private MusicController() {} @@ -70,6 +77,8 @@ public class MusicController { boolean play = (lastOsu == null || !osu.audioFilename.equals(lastOsu.audioFilename)); lastOsu = osu; if (play) { + themePlaying = false; + // TODO: properly interrupt instead of using deprecated Thread.stop(); // interrupt the conversion/track loading if (isTrackLoading()) @@ -248,6 +257,30 @@ public class MusicController { return (trackExists() && player.setPosition(position / 1000f)); } + /** + * Plays the theme song. + */ + public static void playThemeSong() { + try { + URL themeURL = ResourceLoader.getResource(Options.OSU_THEME_NAME); + File themeFile = new File(themeURL.toURI()); + OsuFile osu = OsuParser.parseFile(themeFile, null, false); + URL audioURL = ResourceLoader.getResource(osu.audioFilename.getName()); + osu.audioFilename = new File(audioURL.toURI()); + play(osu, true); + themePlaying = true; + } catch (Exception e) { + Log.error("Failed to load theme song.", e); + } + } + + /** + * Returns whether or not the current track, if any, is the theme song. + */ + public static boolean isThemePlaying() { + return (themePlaying && trackExists()); + } + /** * Stops and releases all sources, clears each of the specified Audio * buffers, destroys the OpenAL context, and resets SoundStore for future use. diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java index 8d86b6cf..9a1dc794 100644 --- a/src/itdelatrisu/opsu/OsuParser.java +++ b/src/itdelatrisu/opsu/OsuParser.java @@ -109,7 +109,7 @@ public class OsuParser { * @param parseObjects if true, hit objects will be fully parsed now * @return the new OsuFile object */ - private static OsuFile parseFile(File file, ArrayList osuFiles, boolean parseObjects) { + public static OsuFile parseFile(File file, ArrayList osuFiles, boolean parseObjects) { OsuFile osu = new OsuFile(file); try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { @@ -464,7 +464,9 @@ public class OsuParser { parseHitObjects(osu); // add OsuFile to song group - osuFiles.add(osu); + if (osuFiles != null) + osuFiles.add(osu); + return osu; } diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index ade93a3f..302b7fbd 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -293,11 +293,13 @@ public class MainMenu extends BasicGameState { else if (!MusicController.isTrackLoading()) MusicController.resume(); } else if (musicNext.contains(x, y)) { + boolean isTheme = MusicController.isThemePlaying(); SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU); OsuGroupNode node = menu.setFocus(Opsu.groups.getRandomNode(), -1, true); - if (node != null) + if (node != null && !isTheme) previous.add(node.index); - if (Options.isDynamicBackgroundEnabled()) + if (Options.isDynamicBackgroundEnabled() && + MusicController.getOsuFile() != null && !MusicController.isThemePlaying()) bgAlpha = 0f; } else if (musicPrevious.contains(x, y)) { if (!previous.isEmpty()) { diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/states/Options.java index 8203a16f..23a06391 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/states/Options.java @@ -83,6 +83,11 @@ public class Options extends BasicGameState { */ public static final String FONT_NAME = "kochi-gothic.ttf"; + /** + * The theme song OsuFile file name. + */ + public static final String OSU_THEME_NAME = "theme.osu"; + /** * The beatmap directory. */ @@ -134,7 +139,8 @@ public class Options extends BasicGameState { DISABLE_SOUNDS, KEY_LEFT, KEY_RIGHT, - SHOW_UNICODE; + SHOW_UNICODE, + ENABLE_THEME_SONG; }; /** @@ -190,7 +196,8 @@ public class Options extends BasicGameState { GameOption.EFFECT_VOLUME, GameOption.HITSOUND_VOLUME, GameOption.MUSIC_OFFSET, - GameOption.DISABLE_SOUNDS + GameOption.DISABLE_SOUNDS, + GameOption.ENABLE_THEME_SONG }; /** @@ -344,6 +351,11 @@ public class Options extends BasicGameState { */ private static boolean ignoreBeatmapSkins = false; + /** + * Whether or not to play the theme song. + */ + private static boolean themeSongEnabled = true; + /** * Fixed difficulty overrides. */ @@ -631,6 +643,9 @@ public class Options extends BasicGameState { } } break; + case ENABLE_THEME_SONG: + themeSongEnabled = !themeSongEnabled; + break; default: break; } @@ -938,6 +953,12 @@ public class Options extends BasicGameState { "Press CTRL+L while playing to load a checkpoint, and CTRL+S to set one." ); break; + case ENABLE_THEME_SONG: + drawOption(pos, "Enable Theme Song", + themeSongEnabled ? "Yes" : "No", + "Whether to play the theme song upon starting opsu!" + ); + break; default: break; } @@ -1168,6 +1189,12 @@ public class Options extends BasicGameState { */ public static boolean useUnicodeMetadata() { return showUnicode; } + /** + * Returns whether or not to play the theme song. + * @return true if enabled + */ + public static boolean isThemSongEnabled() { return themeSongEnabled; } + /** * Sets the track checkpoint time, if within bounds. * @param time the track position (in ms) @@ -1410,6 +1437,9 @@ public class Options extends BasicGameState { case "Checkpoint": setCheckpoint(Integer.parseInt(value)); break; + case "MenuMusic": + themeSongEnabled = Boolean.parseBoolean(value); + break; } } } catch (IOException e) { @@ -1501,6 +1531,8 @@ public class Options extends BasicGameState { writer.newLine(); writer.write(String.format("Checkpoint = %d", checkpoint)); writer.newLine(); + writer.write(String.format("MenuMusic = %b", themeSongEnabled)); + writer.newLine(); writer.close(); } catch (IOException e) { Log.error(String.format("Failed to write to file '%s'.", OPTIONS_FILE.getAbsolutePath()), e); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 566cd27c..8cf5c84a 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -501,6 +501,10 @@ public class SongMenu extends BasicGameState { public void enter(GameContainer container, StateBasedGame game) throws SlickException { Display.setTitle(game.getTitle()); + + // stop playing the theme song + if (MusicController.isThemePlaying() && focusNode != null) + MusicController.play(focusNode.osuFiles.get(focusNode.osuFileIndex), true); } @Override diff --git a/src/itdelatrisu/opsu/states/Splash.java b/src/itdelatrisu/opsu/states/Splash.java index 88bdb266..b66ec1ff 100644 --- a/src/itdelatrisu/opsu/states/Splash.java +++ b/src/itdelatrisu/opsu/states/Splash.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.states; +import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.OsuParser; import itdelatrisu.opsu.OszUnpacker; @@ -140,6 +141,10 @@ public class Splash extends BasicGameState { Opsu.groups.init(); ((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(Opsu.groups.getRandomNode(), -1, true); + // play the theme song + if (Options.isThemSongEnabled()) + MusicController.playThemeSong(); + game.enterState(Opsu.STATE_MAINMENU); } }