diff --git a/src/itdelatrisu/opsu/MusicController.java b/src/itdelatrisu/opsu/MusicController.java index 370176fa..fd69accb 100644 --- a/src/itdelatrisu/opsu/MusicController.java +++ b/src/itdelatrisu/opsu/MusicController.java @@ -177,7 +177,7 @@ public class MusicController { public static String getTrackName() { if (!trackExists() || lastOsu == null) return null; - return lastOsu.title; + return lastOsu.getTitle(); } /** @@ -186,7 +186,7 @@ public class MusicController { public static String getArtistName() { if (!trackExists() || lastOsu == null) return null; - return lastOsu.artist; + return lastOsu.getArtist(); } /** diff --git a/src/itdelatrisu/opsu/OsuFile.java b/src/itdelatrisu/opsu/OsuFile.java index fd5bc546..e1c1120c 100644 --- a/src/itdelatrisu/opsu/OsuFile.java +++ b/src/itdelatrisu/opsu/OsuFile.java @@ -18,6 +18,8 @@ package itdelatrisu.opsu; +import itdelatrisu.opsu.states.Options; + import java.io.File; import java.util.ArrayList; @@ -108,9 +110,28 @@ public class OsuFile implements Comparable { /** * Returns the associated file object. + * @return the File object */ public File getFile() { return file; } + /** + * Returns the song title. + * If configured, the Unicode string will be returned instead. + * @return the song title + */ + public String getTitle() { + return (Options.useUnicodeMetadata() && !titleUnicode.isEmpty()) ? titleUnicode : title; + } + + /** + * Returns the song artist. + * If configured, the Unicode string will be returned instead. + * @return the song artist + */ + public String getArtist() { + return (Options.useUnicodeMetadata() && !artistUnicode.isEmpty()) ? artistUnicode : artist; + } + /** * Draws the background associated with the OsuFile. * @param width the container width @@ -154,6 +175,6 @@ public class OsuFile implements Comparable { */ @Override public String toString() { - return String.format("%s - %s [%s]", artist, title, version); + return String.format("%s - %s [%s]", getArtist(), getTitle(), version); } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/OsuGroupNode.java b/src/itdelatrisu/opsu/OsuGroupNode.java index 4a283ab7..91cac2d2 100644 --- a/src/itdelatrisu/opsu/OsuGroupNode.java +++ b/src/itdelatrisu/opsu/OsuGroupNode.java @@ -95,9 +95,9 @@ public class OsuGroupNode { float cx = x + (bg.getWidth() * 0.05f) - xOffset; float cy = y + (bg.getHeight() * 0.2f) - 3; - Utils.FONT_MEDIUM.drawString(cx, cy, osu.title, textColor); + Utils.FONT_MEDIUM.drawString(cx, cy, osu.getTitle(), textColor); Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4, - String.format("%s // %s", osu.artist, osu.creator), textColor); + String.format("%s // %s", osu.getArtist(), osu.creator), textColor); if (expanded || osuFiles.size() == 1) Utils.FONT_BOLD.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() + Utils.FONT_DEFAULT.getLineHeight() - 8, osu.version, textColor); @@ -144,7 +144,7 @@ public class OsuGroupNode { @Override public String toString() { if (osuFileIndex == -1) - return String.format("%s - %s", osuFiles.get(0).artist, osuFiles.get(0).title); + return String.format("%s - %s", osuFiles.get(0).getArtist(), osuFiles.get(0).getTitle()); else return osuFiles.get(osuFileIndex).toString(); } diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java index 62bb549c..8d86b6cf 100644 --- a/src/itdelatrisu/opsu/OsuParser.java +++ b/src/itdelatrisu/opsu/OsuParser.java @@ -20,9 +20,11 @@ package itdelatrisu.opsu; 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.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -110,7 +112,7 @@ public class OsuParser { private static OsuFile parseFile(File file, ArrayList osuFiles, boolean parseObjects) { OsuFile osu = new OsuFile(file); - try (BufferedReader in = new BufferedReader(new FileReader(file))) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { // initialize timing point list osu.timingPoints = new ArrayList(); diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index ce2b3182..f159c938 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -21,9 +21,12 @@ package itdelatrisu.opsu; import itdelatrisu.opsu.states.Options; import java.awt.Font; +import java.awt.GraphicsEnvironment; import java.io.File; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; @@ -104,6 +107,11 @@ public class Utils { cursorX = new LinkedList(), cursorY = new LinkedList(); + /** + * Set of all Unicode strings already loaded. + */ + private static HashSet loadedGlyphs = new HashSet(); + // game-related variables private static GameContainer container; private static StateBasedGame game; @@ -148,15 +156,23 @@ public class Utils { // create fonts float fontBase; if (height <= 600) - fontBase = 9f; - else if (height < 800) fontBase = 10f; + else if (height < 800) + fontBase = 11f; else if (height <= 900) - fontBase = 12f; + fontBase = 13f; else - fontBase = 14f; + fontBase = 15f; - Font font = new Font("Lucida Sans Unicode", Font.PLAIN, (int) (fontBase * 4 / 3)); + // TODO: cross-platform multilingual support + String fontName = ""; + String[] fontNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + if (Arrays.asList(fontNames).contains("Arial Unicode MS")) + fontName = "Arial Unicode MS"; + else + fontName = "Lucida Sans Console"; + + Font font = new Font(fontName, Font.PLAIN, (int) (fontBase * 4 / 3)); FONT_DEFAULT = new UnicodeFont(font); FONT_BOLD = new UnicodeFont(font.deriveFont(Font.BOLD)); FONT_XLARGE = new UnicodeFont(font.deriveFont(fontBase * 4)); @@ -485,4 +501,35 @@ public class Utils { } return true; } + + /** + * Adds and loads glyphs for an OsuFile's Unicode title and artist strings. + * @param osu the OsuFile + */ + public static void loadGlyphs(OsuFile osu) { + boolean glyphsAdded = false; + if (!osu.titleUnicode.isEmpty() && !loadedGlyphs.contains(osu.titleUnicode)) { + Utils.FONT_LARGE.addGlyphs(osu.titleUnicode); + Utils.FONT_MEDIUM.addGlyphs(osu.titleUnicode); + Utils.FONT_DEFAULT.addGlyphs(osu.titleUnicode); + loadedGlyphs.add(osu.titleUnicode); + glyphsAdded = true; + } + if (!osu.artistUnicode.isEmpty() && !loadedGlyphs.contains(osu.artistUnicode)) { + Utils.FONT_LARGE.addGlyphs(osu.artistUnicode); + Utils.FONT_MEDIUM.addGlyphs(osu.artistUnicode); + Utils.FONT_DEFAULT.addGlyphs(osu.artistUnicode); + loadedGlyphs.add(osu.artistUnicode); + glyphsAdded = true; + } + if (glyphsAdded && Options.useUnicodeMetadata()) { + try { + Utils.FONT_LARGE.loadGlyphs(); + Utils.FONT_MEDIUM.loadGlyphs(); + Utils.FONT_DEFAULT.loadGlyphs(); + } catch (SlickException e) { + Log.warn("Failed to load glyphs.", e); + } + } + } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index 31c6ae0c..ce6fce9b 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -121,7 +121,7 @@ public class GameRanking extends BasicGameState { // header text g.setColor(Color.white); Utils.FONT_LARGE.drawString(10, 0, - String.format("%s - %s [%s]", osu.artist, osu.title, osu.version)); + String.format("%s - %s [%s]", osu.getArtist(), osu.getTitle(), osu.version)); Utils.FONT_MEDIUM.drawString(10, Utils.FONT_LARGE.getLineHeight() - 6, String.format("Beatmap by %s", osu.creator)); diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/states/Options.java index 1aad4a23..4fafb2e1 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/states/Options.java @@ -128,7 +128,8 @@ public class Options extends BasicGameState { CHECKPOINT, DISABLE_SOUNDS, KEY_LEFT, - KEY_RIGHT; + KEY_RIGHT, + SHOW_UNICODE; }; /** @@ -169,6 +170,7 @@ public class Options extends BasicGameState { // GameOption.FULLSCREEN, GameOption.TARGET_FPS, GameOption.SHOW_FPS, + GameOption.SHOW_UNICODE, GameOption.SCREENSHOT_FORMAT, GameOption.NEW_CURSOR, GameOption.DYNAMIC_BACKGROUND, @@ -363,6 +365,11 @@ public class Options extends BasicGameState { private static boolean disableSound = (System.getProperty("os.name").toLowerCase().indexOf("linux") > -1); + /** + * Whether or not to display non-English metadata. + */ + private static boolean showUnicode = false; + /** * Left and right game keys. */ @@ -607,6 +614,18 @@ public class Options extends BasicGameState { keyEntryLeft = false; keyEntryRight = true; break; + case SHOW_UNICODE: + showUnicode = !showUnicode; + if (showUnicode) { + try { + Utils.FONT_LARGE.loadGlyphs(); + Utils.FONT_MEDIUM.loadGlyphs(); + Utils.FONT_DEFAULT.loadGlyphs(); + } catch (SlickException e) { + Log.warn("Failed to load glyphs.", e); + } + } + break; default: break; } @@ -780,6 +799,12 @@ public class Options extends BasicGameState { "Show an FPS counter in the bottom-right hand corner." ); break; + case SHOW_UNICODE: + drawOption(pos, "Prefer Non-English Metadata", + showUnicode ? "Yes" : "No", + "Where available, song titles will be shown in their native language." + ); + break; case NEW_CURSOR: drawOption(pos, "Enable New Cursor", newCursor ? "Yes" : "No", @@ -1132,6 +1157,12 @@ public class Options extends BasicGameState { */ public static boolean isSoundDisabled() { return disableSound; } + /** + * Returns whether or not to use non-English metadata where available. + * @return true if Unicode preferred + */ + public static boolean useUnicodeMetadata() { return showUnicode; } + /** * Sets the track checkpoint time, if within bounds. * @param time the track position (in ms) @@ -1288,6 +1319,9 @@ public class Options extends BasicGameState { case "FpsCounter": showFPS = Boolean.parseBoolean(value); break; + case "ShowUnicode": + showUnicode = Boolean.parseBoolean(value); + break; case "NewCursor": newCursor = Boolean.parseBoolean(value); break; @@ -1416,6 +1450,8 @@ public class Options extends BasicGameState { writer.newLine(); writer.write(String.format("FpsCounter = %b", showFPS)); writer.newLine(); + writer.write(String.format("ShowUnicode = %b", showUnicode)); + writer.newLine(); writer.write(String.format("ScreenshotFormat = %d", screenshotFormatIndex)); writer.newLine(); writer.write(String.format("NewCursor = %b", newCursor)); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 7619ce36..bdfd53fc 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -240,6 +240,7 @@ public class SongMenu extends BasicGameState { OsuGroupNode node = startNode; for (int i = 0; i < MAX_BUTTONS && node != null; i++) { node.draw(buttonX, buttonY + (i*buttonOffset), (node == focusNode)); + Utils.loadGlyphs(node.osuFiles.get(0)); node = node.next; } @@ -569,7 +570,9 @@ public class SongMenu extends BasicGameState { if (flag || (startNode.index == 0 && startNode.osuFileIndex == -1 && startNode.prev == null)) startNode = node; focusNode = Opsu.groups.getNode(node, pos); - MusicController.play(focusNode.osuFiles.get(focusNode.osuFileIndex), true); + OsuFile osu = focusNode.osuFiles.get(focusNode.osuFileIndex); + MusicController.play(osu, true); + Utils.loadGlyphs(osu); // check startNode bounds while (startNode.index >= Opsu.groups.size() + length - MAX_BUTTONS && startNode.prev != null) diff --git a/src/itdelatrisu/opsu/states/Splash.java b/src/itdelatrisu/opsu/states/Splash.java index 0e24f03d..88bdb266 100644 --- a/src/itdelatrisu/opsu/states/Splash.java +++ b/src/itdelatrisu/opsu/states/Splash.java @@ -110,7 +110,6 @@ public class Splash extends BasicGameState { // load other resources in a new thread final int width = container.getWidth(); final int height = container.getHeight(); - final SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU); new Thread() { @Override public void run() { @@ -122,10 +121,6 @@ public class Splash extends BasicGameState { // parse song directory OsuParser.parseAllFiles(beatmapDir, width, height); - // initialize song list - Opsu.groups.init(); - menu.setFocus(Opsu.groups.getRandomNode(), -1, true); - // load sounds SoundController.init(); @@ -140,8 +135,13 @@ public class Splash extends BasicGameState { logo.setAlpha(alpha + (delta / 400f)); // change states when loading complete - if (finished && alpha >= 1f) + if (finished && alpha >= 1f) { + // initialize song list + Opsu.groups.init(); + ((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(Opsu.groups.getRandomNode(), -1, true); + game.enterState(Opsu.STATE_MAINMENU); + } } @Override