Implemented skinnable theme songs.
Plays a theme song when opsu! starts (can be disabled in options). The default song is "welcome to osu!" by nekodex, uploaded by CyberKitsune. To change the song, place a new "theme.osu" in the skins folder and edit the file name, metadata, and song length (at minimum). Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
9d3a12ad9c
commit
ce0eccd32b
|
@ -27,3 +27,7 @@ The following projects were referenced in creating opsu!:
|
||||||
* "Wojtkosu" - Wojtek Kowaluk (https://osu.ppy.sh/forum/t/97260)
|
* "Wojtkosu" - Wojtek Kowaluk (https://osu.ppy.sh/forum/t/97260)
|
||||||
* "osu-parser" - nojhamster (https://github.com/nojhamster/osu-parser)
|
* "osu-parser" - nojhamster (https://github.com/nojhamster/osu-parser)
|
||||||
* "osu! web" - pictuga (https://github.com/pictuga/osu-web)
|
* "osu! web" - pictuga (https://github.com/pictuga/osu-web)
|
||||||
|
|
||||||
|
Theme Song
|
||||||
|
----------
|
||||||
|
The theme song is "welcome to osu!" by nekodex (https://soundcloud.com/nekodex).
|
||||||
|
|
BIN
res/theme.ogg
Normal file
BIN
res/theme.ogg
Normal file
Binary file not shown.
12
res/theme.osu
Normal file
12
res/theme.osu
Normal file
|
@ -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:
|
|
@ -22,6 +22,7 @@ import itdelatrisu.opsu.states.Options;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
|
||||||
import javazoom.jl.converter.Converter;
|
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.Audio;
|
||||||
import org.newdawn.slick.openal.SoundStore;
|
import org.newdawn.slick.openal.SoundStore;
|
||||||
import org.newdawn.slick.util.Log;
|
import org.newdawn.slick.util.Log;
|
||||||
|
import org.newdawn.slick.util.ResourceLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for all music.
|
* Controller for all music.
|
||||||
|
@ -59,6 +61,11 @@ public class MusicController {
|
||||||
*/
|
*/
|
||||||
private static Thread trackLoader;
|
private static Thread trackLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the theme song is currently playing.
|
||||||
|
*/
|
||||||
|
private static boolean themePlaying = false;
|
||||||
|
|
||||||
// This class should not be instantiated.
|
// This class should not be instantiated.
|
||||||
private MusicController() {}
|
private MusicController() {}
|
||||||
|
|
||||||
|
@ -70,6 +77,8 @@ public class MusicController {
|
||||||
boolean play = (lastOsu == null || !osu.audioFilename.equals(lastOsu.audioFilename));
|
boolean play = (lastOsu == null || !osu.audioFilename.equals(lastOsu.audioFilename));
|
||||||
lastOsu = osu;
|
lastOsu = osu;
|
||||||
if (play) {
|
if (play) {
|
||||||
|
themePlaying = false;
|
||||||
|
|
||||||
// TODO: properly interrupt instead of using deprecated Thread.stop();
|
// TODO: properly interrupt instead of using deprecated Thread.stop();
|
||||||
// interrupt the conversion/track loading
|
// interrupt the conversion/track loading
|
||||||
if (isTrackLoading())
|
if (isTrackLoading())
|
||||||
|
@ -248,6 +257,30 @@ public class MusicController {
|
||||||
return (trackExists() && player.setPosition(position / 1000f));
|
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
|
* Stops and releases all sources, clears each of the specified Audio
|
||||||
* buffers, destroys the OpenAL context, and resets SoundStore for future use.
|
* buffers, destroys the OpenAL context, and resets SoundStore for future use.
|
||||||
|
|
|
@ -109,7 +109,7 @@ public class OsuParser {
|
||||||
* @param parseObjects if true, hit objects will be fully parsed now
|
* @param parseObjects if true, hit objects will be fully parsed now
|
||||||
* @return the new OsuFile object
|
* @return the new OsuFile object
|
||||||
*/
|
*/
|
||||||
private static OsuFile parseFile(File file, ArrayList<OsuFile> osuFiles, boolean parseObjects) {
|
public static OsuFile parseFile(File file, ArrayList<OsuFile> osuFiles, boolean parseObjects) {
|
||||||
OsuFile osu = new OsuFile(file);
|
OsuFile osu = new OsuFile(file);
|
||||||
|
|
||||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
|
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
|
||||||
|
@ -464,7 +464,9 @@ public class OsuParser {
|
||||||
parseHitObjects(osu);
|
parseHitObjects(osu);
|
||||||
|
|
||||||
// add OsuFile to song group
|
// add OsuFile to song group
|
||||||
osuFiles.add(osu);
|
if (osuFiles != null)
|
||||||
|
osuFiles.add(osu);
|
||||||
|
|
||||||
return osu;
|
return osu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -293,11 +293,13 @@ public class MainMenu extends BasicGameState {
|
||||||
else if (!MusicController.isTrackLoading())
|
else if (!MusicController.isTrackLoading())
|
||||||
MusicController.resume();
|
MusicController.resume();
|
||||||
} else if (musicNext.contains(x, y)) {
|
} else if (musicNext.contains(x, y)) {
|
||||||
|
boolean isTheme = MusicController.isThemePlaying();
|
||||||
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
||||||
OsuGroupNode node = menu.setFocus(Opsu.groups.getRandomNode(), -1, true);
|
OsuGroupNode node = menu.setFocus(Opsu.groups.getRandomNode(), -1, true);
|
||||||
if (node != null)
|
if (node != null && !isTheme)
|
||||||
previous.add(node.index);
|
previous.add(node.index);
|
||||||
if (Options.isDynamicBackgroundEnabled())
|
if (Options.isDynamicBackgroundEnabled() &&
|
||||||
|
MusicController.getOsuFile() != null && !MusicController.isThemePlaying())
|
||||||
bgAlpha = 0f;
|
bgAlpha = 0f;
|
||||||
} else if (musicPrevious.contains(x, y)) {
|
} else if (musicPrevious.contains(x, y)) {
|
||||||
if (!previous.isEmpty()) {
|
if (!previous.isEmpty()) {
|
||||||
|
|
|
@ -83,6 +83,11 @@ public class Options extends BasicGameState {
|
||||||
*/
|
*/
|
||||||
public static final String FONT_NAME = "kochi-gothic.ttf";
|
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.
|
* The beatmap directory.
|
||||||
*/
|
*/
|
||||||
|
@ -134,7 +139,8 @@ public class Options extends BasicGameState {
|
||||||
DISABLE_SOUNDS,
|
DISABLE_SOUNDS,
|
||||||
KEY_LEFT,
|
KEY_LEFT,
|
||||||
KEY_RIGHT,
|
KEY_RIGHT,
|
||||||
SHOW_UNICODE;
|
SHOW_UNICODE,
|
||||||
|
ENABLE_THEME_SONG;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,7 +196,8 @@ public class Options extends BasicGameState {
|
||||||
GameOption.EFFECT_VOLUME,
|
GameOption.EFFECT_VOLUME,
|
||||||
GameOption.HITSOUND_VOLUME,
|
GameOption.HITSOUND_VOLUME,
|
||||||
GameOption.MUSIC_OFFSET,
|
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;
|
private static boolean ignoreBeatmapSkins = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to play the theme song.
|
||||||
|
*/
|
||||||
|
private static boolean themeSongEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fixed difficulty overrides.
|
* Fixed difficulty overrides.
|
||||||
*/
|
*/
|
||||||
|
@ -631,6 +643,9 @@ public class Options extends BasicGameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ENABLE_THEME_SONG:
|
||||||
|
themeSongEnabled = !themeSongEnabled;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
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."
|
"Press CTRL+L while playing to load a checkpoint, and CTRL+S to set one."
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case ENABLE_THEME_SONG:
|
||||||
|
drawOption(pos, "Enable Theme Song",
|
||||||
|
themeSongEnabled ? "Yes" : "No",
|
||||||
|
"Whether to play the theme song upon starting opsu!"
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1168,6 +1189,12 @@ public class Options extends BasicGameState {
|
||||||
*/
|
*/
|
||||||
public static boolean useUnicodeMetadata() { return showUnicode; }
|
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.
|
* Sets the track checkpoint time, if within bounds.
|
||||||
* @param time the track position (in ms)
|
* @param time the track position (in ms)
|
||||||
|
@ -1410,6 +1437,9 @@ public class Options extends BasicGameState {
|
||||||
case "Checkpoint":
|
case "Checkpoint":
|
||||||
setCheckpoint(Integer.parseInt(value));
|
setCheckpoint(Integer.parseInt(value));
|
||||||
break;
|
break;
|
||||||
|
case "MenuMusic":
|
||||||
|
themeSongEnabled = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -1501,6 +1531,8 @@ public class Options extends BasicGameState {
|
||||||
writer.newLine();
|
writer.newLine();
|
||||||
writer.write(String.format("Checkpoint = %d", checkpoint));
|
writer.write(String.format("Checkpoint = %d", checkpoint));
|
||||||
writer.newLine();
|
writer.newLine();
|
||||||
|
writer.write(String.format("MenuMusic = %b", themeSongEnabled));
|
||||||
|
writer.newLine();
|
||||||
writer.close();
|
writer.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.error(String.format("Failed to write to file '%s'.", OPTIONS_FILE.getAbsolutePath()), e);
|
Log.error(String.format("Failed to write to file '%s'.", OPTIONS_FILE.getAbsolutePath()), e);
|
||||||
|
|
|
@ -501,6 +501,10 @@ public class SongMenu extends BasicGameState {
|
||||||
public void enter(GameContainer container, StateBasedGame game)
|
public void enter(GameContainer container, StateBasedGame game)
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
Display.setTitle(game.getTitle());
|
Display.setTitle(game.getTitle());
|
||||||
|
|
||||||
|
// stop playing the theme song
|
||||||
|
if (MusicController.isThemePlaying() && focusNode != null)
|
||||||
|
MusicController.play(focusNode.osuFiles.get(focusNode.osuFileIndex), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.states;
|
package itdelatrisu.opsu.states;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.MusicController;
|
||||||
import itdelatrisu.opsu.Opsu;
|
import itdelatrisu.opsu.Opsu;
|
||||||
import itdelatrisu.opsu.OsuParser;
|
import itdelatrisu.opsu.OsuParser;
|
||||||
import itdelatrisu.opsu.OszUnpacker;
|
import itdelatrisu.opsu.OszUnpacker;
|
||||||
|
@ -140,6 +141,10 @@ public class Splash extends BasicGameState {
|
||||||
Opsu.groups.init();
|
Opsu.groups.init();
|
||||||
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(Opsu.groups.getRandomNode(), -1, true);
|
((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);
|
game.enterState(Opsu.STATE_MAINMENU);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user