diff --git a/README.md b/README.md index b33e4a6c..18de2c1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#opsu!dance +# opsu!dance [example video](https://www.youtube.com/watch?v=tqZqn7nx8N0) Originally started as a fork of [opsu!](https://github.com/itdelatrisu/opsu) with cursordance stuff. I made a cursordancing bot in C# for osu!, and by adding it into this clone, it allows me to do even more stuff with it. This way I can also provide this client to other players so they can play with it too, as I will not give my bot to people because I don't want to endorse cheating in any way. @@ -7,17 +7,17 @@ As of 2017 some major changes were made in this fork which changed the inner wor My goal is to to add cool cursordancing things to this fork, but also make it possible to play the normal way. -###Downloads +### Downloads Click on the releases link (scroll up) to go to the downloadpage with prebuilt jars. -###Building +### Building You can find general (run/build) instructions in the original [opsu! README](README-OPSU.md). Please note that I am only using maven, gradle scripts are not being updated. -###Credits +### Credits opsu! was made by Jeffrey Han ([@itdelatrisu](https://github.com/itdelatrisu)). All game concepts and designs are based on work by osu! developer Dean Herbert. Other opsu! credits can be found [here](CREDITS.md). opsu!dance (everything in the src package yugecin.opsudance) was made by me ([@yugecin](https://github.com/yugecin)). Edits were made in the opsu! sources, too. -###License +### License **This software is licensed under GNU GPL version 3.** You can find the full text of the license [here](LICENSE). diff --git a/src/awlex/ospu/FakeGameObject.java b/src/awlex/ospu/FakeGameObject.java index 7777baf9..bcb5c8a0 100644 --- a/src/awlex/ospu/FakeGameObject.java +++ b/src/awlex/ospu/FakeGameObject.java @@ -4,11 +4,11 @@ package awlex.ospu; * Created by Awlex on 10.10.2016. */ -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.objects.GameObject; import itdelatrisu.opsu.objects.curves.Vec2f; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; +import yugecin.opsudance.options.Options; /** * This class is just a dummy {@link GameObject} to place in the middle of 2 GameObjects. diff --git a/src/awlex/ospu/movers/CombinedSpiralMover.java b/src/awlex/ospu/movers/CombinedSpiralMover.java index de0696d8..d0406e15 100644 --- a/src/awlex/ospu/movers/CombinedSpiralMover.java +++ b/src/awlex/ospu/movers/CombinedSpiralMover.java @@ -1,11 +1,11 @@ package awlex.ospu.movers; import awlex.ospu.FakeGameObject; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.objects.GameObject; import yugecin.opsudance.movers.Mover; import yugecin.opsudance.movers.factories.AutoMoverFactory; +import yugecin.opsudance.options.Options; /** * Created by Alex Wieser on 09.10.2016. diff --git a/src/awlex/ospu/movers/factories/SpiralMoverFactory.java b/src/awlex/ospu/movers/factories/SpiralMoverFactory.java index fa5bc442..b2ce532f 100644 --- a/src/awlex/ospu/movers/factories/SpiralMoverFactory.java +++ b/src/awlex/ospu/movers/factories/SpiralMoverFactory.java @@ -1,6 +1,5 @@ package awlex.ospu.movers.factories; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.objects.GameObject; import awlex.ospu.movers.CentralSpiralMover; @@ -8,6 +7,7 @@ import awlex.ospu.movers.CombinedSpiralMover; import yugecin.opsudance.movers.Mover; import awlex.ospu.movers.SpiralToMover; import yugecin.opsudance.movers.factories.MoverFactory; +import yugecin.opsudance.options.Options; /** * Created by Alex Wieser on 09.10.2016. diff --git a/src/awlex/ospu/spinners/SpiralSpinner.java b/src/awlex/ospu/spinners/SpiralSpinner.java index 41d5935a..aa4fb3a5 100644 --- a/src/awlex/ospu/spinners/SpiralSpinner.java +++ b/src/awlex/ospu/spinners/SpiralSpinner.java @@ -1,7 +1,7 @@ package awlex.ospu.spinners; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; +import yugecin.opsudance.options.Options; import yugecin.opsudance.spinners.Spinner; /** diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 58ab4154..2f5b3b01 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -42,12 +42,25 @@ import org.newdawn.slick.Animation; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.skinning.SkinService; import yugecin.opsudance.utils.SlickUtil; +import static yugecin.opsudance.options.Options.*; + /** * Holds game data and renders all related elements. */ public class GameData { + + @Inject + private Configuration config; + + @Inject + private InstanceContainer instanceContainer; + /** Delta multiplier for steady HP drain. */ public static final float HP_DRAIN_MULTIPLIER = 1 / 200f; @@ -383,7 +396,7 @@ public class GameData { hitResultCount[HIT_100K] = s.katu; hitResultCount[HIT_MISS] = s.miss; this.replay = (s.replayString == null) ? null : - new Replay(new File(Options.getReplayDir(), String.format("%s.osr", s.replayString))); + instanceContainer.injectFields(new Replay(new File(config.replayDir, String.format("%s.osr", s.replayString)))); loadImages(); } @@ -515,7 +528,7 @@ public class GameData { if (digitWidth <= 1f) { return; } - digitWidth = (digitWidth - Options.getSkin().getHitCircleFontOverlap()) * scale; + digitWidth = (digitWidth - SkinService.skin.getHitCircleFontOverlap()) * scale; float cx = x + ((length - 1) * (digitWidth / 2)); for (int i = 0; i < length; i++) { @@ -544,7 +557,7 @@ public class GameData { Image digit = getScoreSymbolImage(c[i]); if (scale != 1.0f) digit = digit.getScaledCopy(scale); - cx -= digit.getWidth() + Options.getSkin().getScoreFontOverlap(); + cx -= digit.getWidth() + SkinService.skin.getScoreFontOverlap(); digit.setAlpha(alpha); digit.draw(cx, y); digit.setAlpha(1f); @@ -557,7 +570,7 @@ public class GameData { digit.setAlpha(alpha); digit.draw(cx, y); digit.setAlpha(1f); - cx += digit.getWidth() - Options.getSkin().getScoreFontOverlap(); + cx += digit.getWidth() - SkinService.skin.getScoreFontOverlap(); } } } @@ -679,7 +692,7 @@ public class GameData { } // hit error bar - if (Options.isHitErrorBarEnabled() && !hitErrorList.isEmpty()) { + if (OPTION_SHOW_HIT_ERROR_BAR.state && !hitErrorList.isEmpty()) { // fade out with last tick float hitErrorAlpha = 1f; Color white = new Color(Color.white); @@ -914,10 +927,8 @@ public class GameData { spinnerOsu.setAlpha(hitResult.alpha); spinnerOsu.drawCentered(width / 2, height / 4); spinnerOsu.setAlpha(1f); - } - - // hit lighting - else if (Options.isHitLightingEnabled() && !hitResult.hideResult && hitResult.result != HIT_MISS && + } else if (OPTION_SHOW_HIT_LIGHTING.state && !hitResult.hideResult && hitResult.result != HIT_MISS && + // hit lighting hitResult.result != HIT_SLIDER30 && hitResult.result != HIT_SLIDER10) { // TODO: add particle system Image lighting = GameImage.LIGHTING.getImage(); @@ -965,14 +976,14 @@ public class GameData { private void drawHitAnimations(HitObjectResult hitResult, int trackPosition) { // fade out slider curve if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.result != HIT_SLIDER_REPEAT_M && hitResult.curve != null) { - if (!Options.isShrinkingSliders()) { + if (!OPTION_SHRINKING_SLIDERS.state) { float progress = AnimationEquation.OUT_CUBIC.calc( (float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME); float alpha = 1f - progress; float oldWhiteAlpha = Colors.WHITE_FADE.a; float oldColorAlpha = hitResult.color.a; Colors.WHITE_FADE.a = hitResult.color.a = alpha; - hitResult.curve.draw(hitResult.color); + hitResult.curve.draw(hitResult.color, (!OPTION_FALLBACK_SLIDERS.state && OPTION_MERGING_SLIDERS.state) ? 1 : 0, hitResult.curve.getCurvePoints().length); Colors.WHITE_FADE.a = oldWhiteAlpha; hitResult.color.a = oldColorAlpha; } @@ -987,7 +998,7 @@ public class GameData { fc.drawCentered(hitResult.x, hitResult.y); } - if (!Options.isDrawSliderEndCircles()) { + if (!OPTION_DRAW_SLIDER_ENDCIRCLES.state) { return; } } @@ -1024,7 +1035,7 @@ public class GameData { } scaledRepeat.rotate(ang); scaledRepeat.drawCentered(hitResult.x, hitResult.y, hitResult.color); - if (!Options.isDrawSliderEndCircles()) { + if (!OPTION_DRAW_SLIDER_ENDCIRCLES.state) { GameImage.HITCIRCLE.getImage().draw(-1000, -1000); // TODO this 'fixes' #114. Why? Get a better solution! GameImage.HITCIRCLE_OVERLAY.getImage().draw(-1000, -1000); return; @@ -1186,7 +1197,7 @@ public class GameData { } // combo burst - if (comboBurstIndex > -1 && Options.isComboBurstEnabled()) { + if (comboBurstIndex > -1 && OPTION_SHOW_COMBO_BURSTS.state) { int leftX = 0; int rightX = width - comboBurstImages[comboBurstIndex].getWidth(); if (comboBurstX < leftX) { @@ -1210,7 +1221,7 @@ public class GameData { comboPopTime = COMBO_POP_TIME; // hit error bar - if (Options.isHitErrorBarEnabled()) { + if (OPTION_SHOW_HIT_ERROR_BAR.state) { int trackPosition = MusicController.getPosition(); Iterator iter = hitErrorList.iterator(); while (iter.hasNext()) { @@ -1237,21 +1248,22 @@ public class GameData { comboMax = combo; // combo bursts (at 30, 60, 100+50x) - if (Options.isComboBurstEnabled() && - (combo == 30 || combo == 60 || (combo >= 100 && combo % 50 == 0))) { - if (Options.getSkin().isComboBurstRandom()) + if (OPTION_SHOW_COMBO_BURSTS.state && (combo == 30 || combo == 60 || (combo >= 100 && combo % 50 == 0))) { + if (SkinService.skin.isComboBurstRandom()) { comboBurstIndex = (int) (Math.random() * comboBurstImages.length); - else { - if (combo == 30) + } else { + if (combo == 30) { comboBurstIndex = 0; - else + } else { comboBurstIndex = (comboBurstIndex + 1) % comboBurstImages.length; + } } comboBurstAlpha = 0.8f; - if ((comboBurstIndex % 2) == 0) + if ((comboBurstIndex % 2) == 0) { comboBurstX = width; - else + } else { comboBurstX = comboBurstImages[0].getWidth() * -1; + } } } @@ -1277,7 +1289,7 @@ public class GameData { */ public void sendSliderRepeatResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) { hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, x, y, color, type, curve, true, true)); - if (!Options.isMirror()) { + if (!OPTION_DANCE_MIRROR.state) { return; } float[] m = Utils.mirrorPoint(x, y); @@ -1294,7 +1306,7 @@ public class GameData { */ public void sendSliderStartResult(int time, float x, float y, Color color, Color mirrorColor, boolean expand) { hitResultList.add(new HitObjectResult(time, HIT_ANIMATION_RESULT, x, y, color, HitObjectType.CIRCLE, null, expand, true)); - if (!Options.isMirror()) { + if (!OPTION_DANCE_MIRROR.state) { return; } float[] m = Utils.mirrorPoint(x, y); @@ -1338,10 +1350,9 @@ public class GameData { score += hitValue; incrementComboStreak(); - if (!Options.isPerfectHitBurstEnabled()) - ; // hide perfect hit results - else + if (OPTION_SHOW_PERFECT_HIT.state) { hitResultList.add(new HitObjectResult(time, result, x, y, null, HitObjectType.SLIDERTICK, null, false, false)); + } } fullObjectCount++; } @@ -1527,7 +1538,7 @@ public class GameData { if (hitResult == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive())) return; // "relax" and "autopilot" mods: hide misses - boolean hideResult = (hitResult == HIT_300 || hitResult == HIT_300G || hitResult == HIT_300K) && !Options.isPerfectHitBurstEnabled(); + boolean hideResult = (hitResult == HIT_300 || hitResult == HIT_300G || hitResult == HIT_300K) && !OPTION_SHOW_PERFECT_HIT.state; hitResultList.add(new HitObjectResult(time, hitResult, x, y, color, hitResultType, curve, expand, hideResult)); } diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index a3600911..4281003f 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -32,8 +32,11 @@ import org.newdawn.slick.util.ResourceLoader; import yugecin.opsudance.core.errorhandling.ErrorHandler; import yugecin.opsudance.core.events.EventBus; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.skinning.SkinService; import yugecin.opsudance.utils.SlickUtil; +import static yugecin.opsudance.options.Options.*; + /** * Game images. */ @@ -546,7 +549,7 @@ public enum GameImage { * and UI scale. */ private static String[] getSuffixes() { - return (Options.loadHDImages() && uiscale >= 1) ? SUFFIXES_HD : SUFFIXES_SD; + return (OPTION_LOAD_HD_IMAGES.state && uiscale >= 1) ? SUFFIXES_HD : SUFFIXES_SD; } @@ -687,11 +690,12 @@ public enum GameImage { * If the default image has already been loaded, this will do nothing. */ public void setDefaultImage() { - if (defaultImage != null || defaultImages != null || Options.getSkin() == null) + if (defaultImage != null || defaultImages != null || SkinService.skin == null) { return; + } // try to load skin images - File skinDir = Options.getSkin().getDirectory(); + File skinDir = SkinService.skin.getDirectory(); if (filenameFormat != null) { if (skinDir != null && ((defaultImages = loadImageArray(skinDir)) != null)) { isSkinned = true; @@ -739,15 +743,17 @@ public enum GameImage { * @return true if a new skin image is loaded, false otherwise */ public boolean setBeatmapSkinImage(File dir) { - if (dir == null) + if (dir == null) { return false; + } // destroy the existing images, if any destroyBeatmapSkinImage(); // beatmap skins disabled - if (Options.isBeatmapSkinIgnored()) + if (OPTION_IGNORE_BEATMAP_SKINS.state) { return false; + } // try to load multiple images if ((skinImages = loadImageArray(dir)) != null) { diff --git a/src/itdelatrisu/opsu/Options.java b/src/itdelatrisu/opsu/Options.java deleted file mode 100644 index 2b88f595..00000000 --- a/src/itdelatrisu/opsu/Options.java +++ /dev/null @@ -1,2102 +0,0 @@ -/* - * opsu! - an open-source osu! client - * Copyright (C) 2014, 2015 Jeffrey Han - * - * opsu! is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * opsu! is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with opsu!. If not, see . - */ - -package itdelatrisu.opsu; - -import awlex.ospu.polymover.factory.PolyMoverFactory; -import itdelatrisu.opsu.audio.MusicController; -import itdelatrisu.opsu.audio.SoundController; -import itdelatrisu.opsu.beatmap.Beatmap; -import itdelatrisu.opsu.beatmap.TimingPoint; -import itdelatrisu.opsu.skins.Skin; -import itdelatrisu.opsu.skins.SkinLoader; -import itdelatrisu.opsu.states.Game; -import itdelatrisu.opsu.ui.Fonts; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.net.URI; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.lwjgl.input.Keyboard; -import org.newdawn.slick.Input; -import org.newdawn.slick.SlickException; -import org.newdawn.slick.openal.SoundStore; -import org.newdawn.slick.util.ClasspathLocation; -import org.newdawn.slick.util.FileSystemLocation; -import org.newdawn.slick.util.Log; -import org.newdawn.slick.util.ResourceLoader; - -import com.sun.jna.platform.win32.Advapi32Util; -import com.sun.jna.platform.win32.Win32Exception; -import com.sun.jna.platform.win32.WinReg; -import yugecin.opsudance.*; -import yugecin.opsudance.core.DisplayContainer; -import yugecin.opsudance.core.errorhandling.ErrorHandler; -import yugecin.opsudance.core.events.EventBus; -import yugecin.opsudance.events.BarNotificationEvent; -import yugecin.opsudance.events.BubbleNotificationEvent; -import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; -import yugecin.opsudance.movers.factories.ExgonMoverFactory; -import yugecin.opsudance.movers.factories.QuadraticBezierMoverFactory; -import yugecin.opsudance.movers.slidermovers.DefaultSliderMoverController; -import yugecin.opsudance.utils.CachedVariable; - -/** - * Handles all user options. - */ -public class Options { - /** Whether to use XDG directories. */ - public static final boolean USE_XDG = checkXDGFlag(); - - /** The config directory. */ - private static final File CONFIG_DIR = getXDGBaseDir("XDG_CONFIG_HOME", ".config"); - - /** The data directory. */ - private static final File DATA_DIR = getXDGBaseDir("XDG_DATA_HOME", ".local/share"); - - /** The cache directory. */ - private static final File CACHE_DIR = getXDGBaseDir("XDG_CACHE_HOME", ".cache"); - - /** File for logging errors. */ - public static final File LOG_FILE = new File(CONFIG_DIR, ".opsu.log"); - - /** File for storing user options. */ - private static final File OPTIONS_FILE = new File(CONFIG_DIR, ".opsu.cfg"); - - /** The default beatmap directory (unless an osu! installation is detected). */ - private static final File BEATMAP_DIR = new File(DATA_DIR, "Songs/"); - - /** The default skin directory (unless an osu! installation is detected). */ - private static final File SKIN_ROOT_DIR = new File(DATA_DIR, "Skins/"); - - /** Cached beatmap database name. */ - public static final File BEATMAP_DB = new File(DATA_DIR, ".opsu.db"); - - /** Score database name. */ - public static final File SCORE_DB = new File(DATA_DIR, ".opsu_scores.db"); - - /** Directory where natives are unpacked. */ - public static final File NATIVE_DIR = new File(CACHE_DIR, "Natives/"); - - /** Directory where temporary files are stored (deleted on exit). */ - public static final File TEMP_DIR = new File(CACHE_DIR, "Temp/"); - - /** Font file name. */ - public static final String FONT_NAME = "DroidSansFallback.ttf"; - - /** Version file name. */ - public static final String VERSION_FILE = "version"; - - /** Repository address. */ - public static final URI REPOSITORY_URI = URI.create("https://github.com/itdelatrisu/opsu"); - - /** Dance repository address. */ - public static final URI DANCE_REPOSITORY_URI = URI.create("https://github.com/yugecin/opsu-dance"); - - /** Issue reporting address. */ - public static final String ISSUES_URL = "https://github.com/yugecin/opsu-dance/issues/new?title=%s&body=%s"; - - /** Address containing the latest version file. */ - public static final String VERSION_REMOTE = "https://raw.githubusercontent.com/yugecin/opsu-dance/master/version"; - - /** The beatmap directory. */ - private static File beatmapDir; - - /** The OSZ archive directory. */ - private static File oszDir; - - /** The screenshot directory (created when needed). */ - private static File screenshotDir; - - /** 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; - - /** Port binding. */ - private static int port = 49250; - - private static boolean noSingleInstance; - - /** The theme song string: {@code filename,title,artist,length(ms)} */ - private static String themeString = "theme.mp3,Rainbows,Kevin MacLeod,219350"; - - /** The theme song timing point string (for computing beats to pulse the logo) . */ - private static String themeTimingPoint = "1080,545.454545454545,4,1,0,100,0,0"; - - /** - * Returns whether the XDG flag in the manifest (if any) is set to "true". - * @return true if XDG directories are enabled, false otherwise - */ - private static boolean checkXDGFlag() { - JarFile jarFile = Utils.getJarFile(); - if (jarFile == null) - return false; - try { - Manifest manifest = jarFile.getManifest(); - if (manifest == null) - return false; - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue("Use-XDG"); - return (value != null && value.equalsIgnoreCase("true")); - } catch (IOException e) { - return false; - } - } - - /** - * Returns the directory based on the XDG base directory specification for - * Unix-like operating systems, only if the "XDG" flag is enabled. - * @param env the environment variable to check (XDG_*_*) - * @param fallback the fallback directory relative to ~home - * @return the XDG base directory, or the working directory if unavailable - */ - private static File getXDGBaseDir(String env, String fallback) { - File workingDir = Utils.isJarRunning() ? - Utils.getRunningDirectory().getParentFile() : Utils.getWorkingDirectory(); - - if (!USE_XDG) - return workingDir; - - String OS = System.getProperty("os.name").toLowerCase(); - if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0) { - String rootPath = System.getenv(env); - if (rootPath == null) { - String home = System.getProperty("user.home"); - if (home == null) - return new File("./"); - rootPath = String.format("%s/%s", home, fallback); - } - File dir = new File(rootPath, "opsu"); - if (!dir.isDirectory() && !dir.mkdir()) - ErrorHandler.error(String.format("Failed to create configuration folder at '%s/opsu'.", rootPath), new Exception("empty")).preventReport().show(); - return dir; - } else - return workingDir; - } - - /** - * Returns the osu! installation directory. - * @return the directory, or null if not found - */ - private static File getOsuInstallationDirectory() { - if (!System.getProperty("os.name").startsWith("Win")) - return null; // only works on Windows - - // registry location - final WinReg.HKEY rootKey = WinReg.HKEY_CLASSES_ROOT; - final String regKey = "osu\\DefaultIcon"; - final String regValue = null; // default value - final String regPathPattern = "\"(.+)\\\\[^\\/]+\\.exe\""; - - String value; - try { - value = Advapi32Util.registryGetStringValue(rootKey, regKey, regValue); - } catch (Win32Exception e) { - return null; // key/value not found - } - Pattern pattern = Pattern.compile(regPathPattern); - Matcher m = pattern.matcher(value); - if (!m.find()) - return null; - File dir = new File(m.group(1)); - return (dir.isDirectory()) ? dir : null; - } - - /** Game options. */ - public enum GameOption { - // internal options (not displayed in-game) - BEATMAP_DIRECTORY ("BeatmapDirectory") { - @Override - public String write() { return getBeatmapDir().getAbsolutePath(); } - - @Override - public void read(String s) { beatmapDir = new File(s); } - }, - OSZ_DIRECTORY ("OSZDirectory") { - @Override - public String write() { return getOSZDir().getAbsolutePath(); } - - @Override - public void read(String s) { oszDir = new File(s); } - }, - SCREENSHOT_DIRECTORY ("ScreenshotDirectory") { - @Override - public String write() { return getScreenshotDir().getAbsolutePath(); } - - @Override - public void read(String s) { screenshotDir = new File(s); } - }, - REPLAY_DIRECTORY ("ReplayDirectory") { - @Override - public String write() { return getReplayDir().getAbsolutePath(); } - - @Override - public void read(String s) { replayDir = new File(s); } - }, - REPLAY_IMPORT_DIRECTORY ("ReplayImportDirectory") { - @Override - public String write() { return getReplayImportDir().getAbsolutePath(); } - - @Override - public void read(String s) { replayImportDir = new File(s); } - }, - SKIN_DIRECTORY ("SkinDirectory") { - @Override - public String write() { return getSkinRootDir().getAbsolutePath(); } - - @Override - public void read(String s) { skinRootDir = new File(s); } - }, - THEME_SONG ("ThemeSong") { - @Override - public String write() { return themeString; } - - @Override - public void read(String s) { - String oldThemeString = themeString; - themeString = s; - Beatmap beatmap = getThemeBeatmap(); - if (beatmap == null) { - themeString = oldThemeString; - Log.warn(String.format("The theme song string [%s] is malformed.", s)); - } else if (!beatmap.audioFilename.isFile()) { - themeString = oldThemeString; - Log.warn(String.format("Cannot find theme song [%s].", beatmap.audioFilename.getAbsolutePath())); - } - } - }, - THEME_SONG_TIMINGPOINT ("ThemeSongTiming") { - @Override - public String write() { return themeTimingPoint; } - - @Override - public void read(String s) { - try { - new TimingPoint(s); - themeTimingPoint = s; - } catch (Exception e) { - Log.warn(String.format("The theme song timing point [%s] is malformed.", s)); - } - } - }, - PORT ("Port") { - @Override - public String write() { return Integer.toString(port); } - - @Override - public void read(String s) { - int i = Integer.parseInt(s); - if (i > 0 && i <= 65535) - port = i; - } - }, - NOSINGLEINSTANCE ("NoSingleInstance") { - @Override - public String write() { return noSingleInstance + ""; } - - @Override - public void read(String s) { - noSingleInstance = !"false".equals(s); - } - }, - - // in-game options - SCREEN_RESOLUTION ("Screen Resolution", "ScreenResolution", "Change the size of the game.") { - @Override - public String getValueString() { - return resolutions[resolutionIdx]; - } - - @Override - public Object[] getListItems() { - return resolutions; - } - - @Override - public void clickListItem(int index) { - resolutionIdx = index; - setDisplayMode(DisplayContainer.instance); - } - - @Override - public void read(String s) { - try { - resolutionIdx = Integer.parseInt(s); - } catch (NumberFormatException ignored) { } - } - - @Override - public String write() { - return resolutionIdx + ""; - } - }, - ALLOW_LARGER_RESOLUTIONS ("Allow large resolutions", "AllowLargeRes", "Allow resolutions larger than the native resolution", false), - FULLSCREEN ("Fullscreen Mode", "Fullscreen", "Restart to apply changes.", false), - SKIN ("Skin", "Skin", "Change how the game looks.") { - @Override - public String getValueString() { return skinName; } - - @Override - public Object[] getListItems() { - return skinDirs; - } - - @Override - public void clickListItem(int index) { - skinName = skinDirs[index]; - reloadSkin(); - } - - @Override - public void read(String s) { skinName = s; } - }, - TARGET_UPS ("target UPS", "targetUPS", "Higher values result in less input lag and smoother cursor trail, but may cause high CPU usage.", 480, 20, 1000) { - @Override - public String getValueString() { - return String.format("%dups", val); - } - - @Override - public void setValue(int value) { - super.setValue(value); - displayContainer.setUPS(value); - } - }, - TARGET_FPS ("FPS limit", "FPSlimit", "Higher values may cause high CPU usage. A value higher than the UPS has no effect.") { - @Override - public String getValueString() { - return String.format("%dfps", getTargetFPS()); - } - - private CachedVariable $_getListItems = new CachedVariable<>(new CachedVariable.Getter() { - @Override - public Object[] get() { - String[] list = new String[targetFPS.length]; - for (int i = 0; i < targetFPS.length; i++) { - list[i] = String.format("%dfps", targetFPS[i]); - } - return list; - } - }); - - @Override - public Object[] getListItems() { - return $_getListItems.get(); - } - - @Override - public void clickListItem(int index) { - targetFPSindex = index; - displayContainer.setFPS(targetFPS[index]); - } - - @Override - public String write() { - return Integer.toString(targetFPS[targetFPSindex]); - } - - @Override - public void read(String s) { - int i = Integer.parseInt(s); - for (int j = 0; j < targetFPS.length; j++) { - if (i == targetFPS[j]) { - targetFPSindex = j; - break; - } - } - } - }, - SHOW_FPS ("Show FPS Counters", "FpsCounter", "Show FPS and UPS counters in the bottom-right hand corner.", true), - USE_FPS_DELTAS ("Use deltas for FPS counters", "FpsCounterDeltas", "Show time between updates instead of updates per second.", false) { - @Override - public boolean showCondition() { - return SHOW_FPS.bool; - } - }, - SHOW_UNICODE ("Prefer Non-English Metadata", "ShowUnicode", "Where available, song titles will be shown in their native language.", false) { - @Override - public void click() { - super.click(); - if (bool) { - try { - Fonts.LARGE.loadGlyphs(); - Fonts.MEDIUM.loadGlyphs(); - Fonts.DEFAULT.loadGlyphs(); - } catch (SlickException e) { - Log.warn("Failed to load glyphs.", e); - } - } - } - }, - SCREENSHOT_FORMAT ("Screenshot Format", "ScreenshotFormat", "Press F12 to take a screenshot.") { - @Override - public String getValueString() { return screenshotFormat[screenshotFormatIndex]; } - - @Override - public Object[] getListItems() { - return screenshotFormat; - } - - @Override - public void clickListItem(int index) { - screenshotFormatIndex = index; - } - - @Override - public String write() { return Integer.toString(screenshotFormatIndex); } - - @Override - public void read(String s) { - int i = Integer.parseInt(s); - if (i >= 0 && i < screenshotFormat.length) - screenshotFormatIndex = i; - } - }, - CURSOR_SIZE ("Size", "CursorSize", "Change the cursor scale.", 100, 10, 200) { - @Override - public String getValueString() { return String.format("%.2fx", val / 100f); } - - @Override - public String write() { return String.format(Locale.US, "%.2f", val / 100f); } - - @Override - public void read(String s) { - int i = (int) (Float.parseFloat(s) * 100f); - if (i >= 50 && i <= 200) - val = i; - } - }, - NEW_CURSOR ("Enable New Cursor", "NewCursor", "Use the new cursor style (may cause higher CPU usage).", true), - DYNAMIC_BACKGROUND ("Enable Dynamic Backgrounds", "DynamicBackground", "The song background will be used as the main menu background.", true), - LOAD_VERBOSE ("Show Detailed Loading Progress", "LoadVerbose", "Display more specific loading information in the splash screen.", false), - COLOR_MAIN_MENU_LOGO ("Use cursor color as main menu logo tint", "ColorMainMenuLogo", "Colorful main menu logo", false), - MASTER_VOLUME ("Master", "VolumeUniversal", "Global volume level.", 35, 0, 100) { - @Override - public void setValue(int value) { - super.setValue(value); - SoundStore.get().setMusicVolume(getMasterVolume() * getMusicVolume()); - } - }, - MUSIC_VOLUME ("Music", "VolumeMusic", "Volume of music.", 80, 0, 100) { - @Override - public void setValue(int value) { - super.setValue(value); - SoundStore.get().setMusicVolume(getMasterVolume() * getMusicVolume()); - } - }, - SAMPLE_VOLUME_OVERRIDE ("Sample override", "BMSampleOverride", "Override beatmap hitsound volume", 100, 0, 100) { - @Override - public String getValueString() { - if (val == 0) { - return "Disabled"; - } - return super.getValueString(); - } - }, - EFFECT_VOLUME ("Effects", "VolumeEffect", "Volume of menu and game sounds.", 70, 0, 100), - HITSOUND_VOLUME ("Hit Sounds", "VolumeHitSound", "Volume of hit sounds.", 30, 0, 100), - MUSIC_OFFSET ("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); } - }, - DISABLE_SOUNDS ("Disable All Sound Effects", "DisableSound", "May resolve Linux sound driver issues. Requires a restart.", - (System.getProperty("os.name").toLowerCase().contains("linux"))), - KEY_LEFT ("Left Game Key", "keyOsuLeft", "Select this option to input a key.") { - @Override - public String getValueString() { return Keyboard.getKeyName(getGameKeyLeft()); } - - @Override - public String write() { return Keyboard.getKeyName(getGameKeyLeft()); } - - @Override - public void read(String s) { setGameKeyLeft(Keyboard.getKeyIndex(s)); } - }, - KEY_RIGHT ("Right Game Key", "keyOsuRight", "Select this option to input a key.") { - @Override - public String getValueString() { return Keyboard.getKeyName(getGameKeyRight()); } - - @Override - public String write() { return Keyboard.getKeyName(getGameKeyRight()); } - - @Override - public void read(String s) { setGameKeyRight(Keyboard.getKeyIndex(s)); } - }, - DISABLE_MOUSE_WHEEL ("Disable mouse wheel in play mode", "MouseDisableWheel", "During play, you can use the mouse wheel to adjust the volume and pause the game. This will disable that functionality.", false), - DISABLE_MOUSE_BUTTONS ("Disable mouse buttons in play mode", "MouseDisableButtons", "This option will disable all mouse buttons. Specifically for people who use their keyboard to click.", false), - DISABLE_CURSOR ("Disable Cursor", "DisableCursor", "Hide the cursor sprite.", false), - BACKGROUND_DIM ("Background Dim", "DimLevel", "Percentage to dim the background image during gameplay.", 50, 0, 100), - DANCE_REMOVE_BG ("Use black background instead of image", "RemoveBG", "Hello darkness my old friend", true), - FORCE_DEFAULT_PLAYFIELD ("Force Default Playfield", "ForceDefaultPlayfield", "Override the song background with the default playfield background.", false), - IGNORE_BEATMAP_SKINS ("Ignore All Beatmap Skins", "IgnoreBeatmapSkins", "Never use skin element overrides provided by beatmaps.", false), - SNAKING_SLIDERS ("Snaking sliders", "SnakingSliders", "Sliders gradually snake out from their starting point.", true), - SHRINKING_SLIDERS ("Shrinking sliders", "ShrinkingSliders", "Sliders shrinks when sliderball passes (aka knorkesliders)", true), - FALLBACK_SLIDERS ("Fallback sliders", "FallbackSliders", "Enable this if sliders won't render", false), - MERGING_SLIDERS ("Merging sliders", "MergingSliders", "Merge sliders (aka knorkesliders)", true) { - @Override - public boolean showCondition() { - return !FALLBACK_SLIDERS.bool; - } - }, - MERGING_SLIDERS_MIRROR_POOL ("Merging sliders mirror pool", "MergingSliderMirrorPool", "Amount of mirrors to calculate for merging sliders (impacts performance)", 2, 1, 5) { - @Override - public String getValueString() { - return String.valueOf(val); - } - - @Override - public boolean showCondition() { - return MERGING_SLIDERS.showCondition() && MERGING_SLIDERS.getBooleanValue(); - } - }, - DRAW_SLIDER_ENDCIRCLES ("Draw endcircles", "DrawSliderEndCircles", "Old slider style", false), - SHOW_HIT_LIGHTING ("Show Hit Lighting", "HitLighting", "Adds an effect behind hit explosions.", true), - SHOW_HIT_ANIMATIONS ("Show Hit Animations", "HitAnimations", "Fade out circles and curves.", true), - SHOW_REVERSEARROW_ANIMATIONS ("Show reverse arrow animations", "ReverseArrowAnimations", "Fade out reverse arrows after passing.", true), - SHOW_COMBO_BURSTS ("Show Combo Bursts", "ComboBurst", "A character image is displayed at combo milestones.", true), - SHOW_PERFECT_HIT ("Show Perfect Hits", "PerfectHit", "Whether to show perfect hit result bursts (300s, slider ticks).", true), - SHOW_FOLLOW_POINTS ("Show Follow Points", "FollowPoints", "Whether to show follow points between hit objects.", true), - SHOW_HIT_ERROR_BAR ("Show Hit Error Bar", "ScoreMeter", "Shows precisely how accurate you were with each hit.", false), - MAP_START_DELAY ("Map start delay", "StartDelay", "Have a fix amount of time to prepare your play/record", 20, 1, 50) { - @Override - public String getValueString() { - return (val * 100) + "ms"; - } - }, - MAP_END_DELAY ("Map end delay", "EndDelay", "Have a fix amount of time at the and of the map for a smooth finish", 50, 1, 150) { - @Override - public String getValueString() { - return (val * 100) + "ms"; - } - }, - EPILEPSY_WARNING ("Epilepsy warning image", "EpiWarn", "Show a little warning for flashing colours in the beginning", 0, 0, 20) { - @Override - public String getValueString() { - if (val == 0) { - return "Disabled"; - } - return (val * 100) + "ms"; - } - }, - LOAD_HD_IMAGES ("Load HD Images", "LoadHDImages", String.format("Loads HD (%s) images when available. Increases memory usage and loading times.", GameImage.HD_SUFFIX), true), - FIXED_CS ("Fixed CS", "FixedCS", "Determines the size of circles and sliders.", 0, 0, 100) { - @Override - public String getValueString() { return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); } - - @Override - public String write() { return String.format(Locale.US, "%.1f", val / 10f); } - - @Override - public void read(String s) { - int i = (int) (Float.parseFloat(s) * 10f); - if (i >= 0 && i <= 100) - val = i; - } - }, - FIXED_HP ("Fixed HP", "FixedHP", "Determines the rate at which health decreases.", 0, 0, 100) { - @Override - public String getValueString() { return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); } - - @Override - public String write() { return String.format(Locale.US, "%.1f", val / 10f); } - - @Override - public void read(String s) { - int i = (int) (Float.parseFloat(s) * 10f); - if (i >= 0 && i <= 100) - val = i; - } - }, - FIXED_AR ("Fixed AR", "FixedAR", "Determines how long hit circles stay on the screen.", 0, 0, 100) { - @Override - public String getValueString() { return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); } - - @Override - public String write() { return String.format(Locale.US, "%.1f", val / 10f); } - - @Override - public void read(String s) { - int i = (int) (Float.parseFloat(s) * 10f); - if (i >= 0 && i <= 100) - val = i; - } - }, - FIXED_OD ("Fixed OD", "FixedOD", "Determines the time window for hit results.", 0, 0, 100) { - @Override - public String getValueString() { return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); } - - @Override - public String write() { return String.format(Locale.US, "%.1f", val / 10f); } - - @Override - public void read(String s) { - int i = (int) (Float.parseFloat(s) * 10f); - if (i >= 0 && i <= 100) - val = i; - } - }, - CHECKPOINT ("Track Checkpoint", "Checkpoint", "Press Ctrl+L while playing to load a checkpoint, and Ctrl+S to set one.", 0, 0, 3599) { - @Override - public String getValueString() { - return (val == 0) ? "Disabled" : String.format("%02d:%02d", - TimeUnit.SECONDS.toMinutes(val), - val - TimeUnit.MINUTES.toSeconds(TimeUnit.SECONDS.toMinutes(val))); - } - }, - ENABLE_THEME_SONG ("Enable Theme Song", "MenuMusic", "Whether to play the theme song upon starting opsu!", true), - REPLAY_SEEKING ("Replay Seeking", "ReplaySeeking", "Enable a seeking bar on the left side of the screen during replays.", false), - DISABLE_UPDATER ("Disable Automatic Updates", "DisableUpdater", "Disable automatic checking for updates upon starting opsu!.", false), - ENABLE_WATCH_SERVICE ("Enable Watch Service", "WatchService", "Watch the beatmap directory for changes. Requires a restart.", false), - DANCE_MOVER ("Algorithm", "Mover", "Algorithm that decides how to move from note to note" ) { - @Override - public Object[] getListItems() { - return Dancer.moverFactories; - } - - @Override - public void clickListItem(int index) { - if (Game.isInGame && Dancer.moverFactories[index] instanceof PolyMoverFactory) { - // TODO remove this when #79 is fixed - EventBus.post(new BarNotificationEvent("This mover is disabled in the storyboard right now")); - return; - } - Dancer.instance.setMoverFactoryIndex(index); - } - - @Override - public String getValueString() { - return Dancer.moverFactories[Dancer.instance.getMoverFactoryIndex()].toString(); - } - - @Override - public String write() { - return String.valueOf(Dancer.instance.getMoverFactoryIndex()); - } - - @Override - public void read(String s) { - int i = Integer.parseInt(s); - Dancer.instance.setMoverFactoryIndex(i); - } - }, - DANCE_EXGON_DELAY ("ExGon delay", "ExGonDelay", "Delay between moves for the ExGon mover", 25, 2, 750) { - @Override - public String getValueString() { - return String.valueOf(val); - } - @Override - public boolean showCondition() { - return Dancer.moverFactories[Dancer.instance.getMoverFactoryIndex()] instanceof ExgonMoverFactory; - } - }, - DANCE_QUAD_BEZ_AGGRESSIVENESS ("Bezier aggressiveness", "QuadBezAgr", "AKA initial D factor", 50, 0, 200) { - @Override - public String getValueString() { - return String.valueOf(val); - } - - @Override - public boolean showCondition() { - return Dancer.moverFactories[Dancer.instance.getMoverFactoryIndex()] instanceof QuadraticBezierMoverFactory; - } - }, - DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR ("Exit aggressiveness", "CubBezSliderExitAgr", "AKA initial D factor for sliderexits", 4, 1, 6) { - @Override - public String getValueString() { - return String.valueOf(val); - } - - @Override - public boolean showCondition() { - return DANCE_QUAD_BEZ_AGGRESSIVENESS.showCondition() - && Dancer.sliderMoverController instanceof DefaultSliderMoverController; - } - }, - DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS ("Use cubic bezier before sliders", "QuadBezCubicSliders", "Slider entry looks better using this", true) { - @Override - public boolean showCondition() { - return DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR.showCondition(); - } - }, - DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR ("Entry aggressiveness", "CubBezSliderEntryAgr", "AKA initial D factor for sliderentries", 4, 1, 6) { - @Override - public String getValueString() { - return String.valueOf(val); - } - - @Override - public boolean showCondition() { - return DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS.showCondition() - && DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS.getBooleanValue(); - } - }, - DANCE_MOVER_DIRECTION ("Direction", "MoverDirection", "The direction the mover goes" ) { - @Override - public String getValueString() { - return Dancer.moverDirection.toString(); - } - - @Override - public Object[] getListItems() { - return MoverDirection.values(); - } - - @Override - public void clickListItem(int index) { - Dancer.moverDirection = MoverDirection.values()[index]; - } - - @Override - public String write() { - return "" + Dancer.moverDirection.nr; - } - - @Override - public void read(String s) { - Dancer.moverDirection = MoverDirection.values()[Integer.parseInt(s)]; - } - }, - DANCE_SLIDER_MOVER_TYPE ("Slider mover", "SliderMover", "How to move in sliders") { - @Override - public String getValueString() { - return Dancer.sliderMoverController.toString(); - } - - @Override - public Object[] getListItems() { - return Dancer.sliderMovers; - } - - @Override - public void clickListItem(int index) { - val = index; - Dancer.sliderMoverController = Dancer.sliderMovers[index]; - } - - @Override - public String write() { - return String.valueOf(val); - } - - @Override - public void read(String s) { - Dancer.sliderMoverController = Dancer.sliderMovers[val = Integer.parseInt(s)]; - } - }, - DANCE_SPINNER ("Algorithm", "Spinner", "Spinner style") { - @Override - public Object[] getListItems() { - return Dancer.spinners; - } - - @Override - public void clickListItem(int index) { - Dancer.instance.setSpinnerIndex(index); - } - - @Override - public String getValueString() { - return Dancer.spinners[Dancer.instance.getSpinnerIndex()].toString(); - } - - @Override - public String write() { - return Dancer.instance.getSpinnerIndex() + ""; - } - - @Override - public void read(String s) { - Dancer.instance.setSpinnerIndex(Integer.parseInt(s)); - } - }, - DANCE_SPINNER_DELAY ("Delay", "SpinnerDelay", "Fiddle with this if spinner goes too fast.", 3, 0, 20) { - @Override - public String getValueString() { - return String.format("%dms", val); - } - }, - DANCE_LAZY_SLIDERS ("Lazy sliders", "LazySliders", "Don't do short sliders", false), - DANCE_ONLY_CIRCLE_STACKS ("Only circle stacks", "CircleStacks", "Only do circle movement on stacks", false), - DANCE_CIRCLE_STREAMS ("Circle streams", "CircleStreams", "Make circles while streaming", false), - DANCE_MIRROR ("Mirror collage", "MirrorCollage", "Hypnotizing stuff. Toggle this ingame by pressing the M key.", false), - DANCE_DRAW_APPROACH ("Draw approach circles", "DrawApproach", "Can get a bit busy when using mirror collage", true), - DANCE_OBJECT_COLOR_OVERRIDE ("Color", "ObjColorOverride", "Override object colors") { - @Override - public String getValueString() { - return Dancer.colorOverride.toString(); - } - - @Override - public Object[] getListItems() { - return ObjectColorOverrides.values(); - } - - @Override - public void clickListItem(int index) { - Dancer.colorOverride = ObjectColorOverrides.values()[index]; - } - - @Override - public String write() { - return "" + Dancer.colorOverride.nr; - } - - @Override - public void read(String s) { - Dancer.colorOverride = ObjectColorOverrides.values()[Integer.parseInt(s)]; - } - }, - DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED ("Mirror color", "ObjColorMirroredOverride", "Override collage object colors") { - @Override - public String getValueString() { - return Dancer.colorMirrorOverride.toString(); - } - - @Override - public Object[] getListItems() { - return ObjectColorOverrides.values(); - } - - @Override - public void clickListItem(int index) { - Dancer.colorMirrorOverride = ObjectColorOverrides.values()[index]; - } - - @Override - public String write() { - return "" + Dancer.colorMirrorOverride.nr; - } - - @Override - public void read(String s) { - Dancer.colorMirrorOverride = ObjectColorOverrides.values()[Integer.parseInt(s)]; - } - }, - DANCE_RGB_OBJECT_INC ("RGB increment", "RGBInc", "Amount of hue to shift, used for rainbow object override", 70, -1800, 1800) { - @Override - public String getValueString() { - return String.format("%.1f°", val / 10f); - } - }, - DANCE_CURSOR_COLOR_OVERRIDE ("Color", "CursorColorOverride", "Override cursor color") { - @Override - public String getValueString() { - return Dancer.cursorColorOverride.toString(); - } - - @Override - public Object[] getListItems() { - return CursorColorOverrides.values(); - } - - @Override - public void clickListItem(int index) { - Dancer.cursorColorOverride = CursorColorOverrides.values()[index]; - } - - @Override - public String write() { - return "" + Dancer.cursorColorOverride.nr; - } - - @Override - public void read(String s) { - Dancer.cursorColorOverride = CursorColorOverrides.values()[Integer.parseInt(s)]; - } - }, - DANCE_CURSOR_MIRROR_COLOR_OVERRIDE ("Mirror color", "CursorMirrorColorOverride", "Override mirror cursor color") { - @Override - public String getValueString() { - return Dancer.cursorColorMirrorOverride.toString(); - } - - @Override - public Object[] getListItems() { - return CursorColorOverrides.values(); - } - - @Override - public void clickListItem(int index) { - Dancer.cursorColorMirrorOverride = CursorColorOverrides.values()[index]; - } - - @Override - public String write() { - return "" + Dancer.cursorColorMirrorOverride.nr; - } - - @Override - public void read(String s) { - Dancer.cursorColorMirrorOverride = CursorColorOverrides.values()[Integer.parseInt(s)]; - } - }, - DANCE_CURSOR_ONLY_COLOR_TRAIL ("Only color cursor trail", "OnlyColorTrail", "Don't color the cursor, only the trail", false), - DANCE_RGB_CURSOR_INC ("RGB cursor increment", "RGBCursorInc", "Amount of hue to shift, used for rainbow cursor override", 100, -2000, 2000) { - @Override - public String getValueString() { - return String.format("%.2f°", val / 1000f); - } - }, - DANCE_CURSOR_TRAIL_OVERRIDE ("Trail length", "CursorTrailOverride", "Override cursor trail length", 20, 20, 600) { - @Override - public String getValueString() { - if (val == 20) { - return "Disabled"; - } - return "" + val; - } - }, - DANCE_HIDE_OBJECTS ("Don't draw objects", "HideObj", "If you only want to see cursors :)", false), - DANCE_CIRLCE_IN_SLOW_SLIDERS ("Do circles in slow sliders", "CircleInSlider", "Circle around sliderball in lazy & slow sliders", false), - DANCE_CIRLCE_IN_LAZY_SLIDERS ("Do circles in lazy sliders", "CircleInLazySlider", "Circle in hitcircle in lazy sliders", false), - DANCE_HIDE_UI ("Hide all UI", "HideUI", ".", true), - DANCE_ENABLE_SB ("Enable storyboard editor", "EnableStoryBoard", "Dance storyboard", false), - PIPPI_ENABLE ("Enable", "Pippi", "Move in circles like dancing pippi (osu! april fools joke 2016)", false), - PIPPI_RADIUS_PERCENT ("Radius", "PippiRad", "Radius of pippi, percentage of circle radius", 100, 0, 100) { - @Override - public String getValueString() { - return val + "%"; - } - @Override - public void setValue(int value) { - super.setValue(value); - Pippi.setRadiusPercent(value); - } - }, - PIPPI_ANGLE_INC_MUL("Normal", "PippiAngIncMul", "How fast pippi's angle increments", 10, -200, 200) { - @Override - public String getValueString() { - return String.format("x%.1f", val / 10f); - } - }, - PIPPI_ANGLE_INC_MUL_SLIDER ("In slider", "PippiAngIncMulSlider", "Same as above, but in sliders", 50, -200, 200) { - @Override - public String getValueString() { - return String.format("x%.1f", val / 10f); - } - }, - PIPPI_SLIDER_FOLLOW_EXPAND ("Followcircle expand", "PippiFollowExpand", "Increase radius in followcircles", false), - PIPPI_PREVENT_WOBBLY_STREAMS ("Prevent wobbly streams", "PippiPreventWobblyStreams", "Force linear mover while doing streams to prevent wobbly pippi", true); - - public static DisplayContainer displayContainer; - - /** Option name. */ - private final String name; - - /** Option name, as displayed in the configuration file. */ - private final String displayName; - - /** Option description. */ - private final String description; - - /** The boolean value for the option (if applicable). */ - protected boolean bool; - - private int defaultVal = 0; - - /** The integer value for the option (if applicable). */ - protected int val; - - /** The upper and lower bounds on the integer value (if applicable). */ - private int max, min; - - /** Option types. */ - public enum OptionType { BOOLEAN, NUMERIC, OTHER }; - - /** Whether or not this is a numeric option. */ - private OptionType type = OptionType.OTHER; - - /** - * If this option should not be shown in the optionsmenu because it does - * not match the search string. - */ - private boolean filtered; - - /** - * Constructor for internal options (not displayed in-game). - * @param displayName the option name, as displayed in the configuration file - */ - GameOption(String displayName) { - this(null, displayName, null); - } - - /** - * Constructor for other option types. - * @param name the option name - * @param displayName the option name, as displayed in the configuration file - * @param description the option description - */ - GameOption(String name, String displayName, String description) { - this.name = name; - this.displayName = displayName; - this.description = description; - } - - /** - * Constructor for boolean options. - * @param name the option name - * @param displayName the option name, as displayed in the configuration file - * @param description the option description - * @param value the default boolean value - */ - GameOption(String name, String displayName, String description, boolean value) { - this(name, displayName, description); - this.bool = value; - this.type = OptionType.BOOLEAN; - } - - /** - * Constructor for numeric options. - * @param name the option name - * @param displayName the option name, as displayed in the configuration file - * @param description the option description - * @param value the default integer value - */ - GameOption(String name, String displayName, String description, int value, int min, int max) { - this(name, displayName, description); - this.val = value; - this.defaultVal = value; - this.min = min; - this.max = max; - this.type = OptionType.NUMERIC; - } - - /** - * should the option be shown - * @return true if the option should be shown - */ - public boolean showCondition() { - return true; - } - /** - * Returns the option name. - * @return the name string - */ - public String getName() { return name; } - - /** - * Returns the option name, as displayed in the configuration file. - * @return the display name string - */ - public String getDisplayName() { return displayName; } - - /** - * Returns the option description. - * @return the description string - */ - public String getDescription() { return description; } - - /** - * Returns the boolean value for the option, if applicable. - * @return the boolean value - */ - public boolean getBooleanValue() { return bool; } - - /** - * Returns the integer value for the option, if applicable. - * @return the integer value - */ - public int getIntegerValue() { return val; } - - /** - * Sets the boolean value for the option. - * @param value the new boolean value - */ - public void setValue(boolean value) { this.bool = value; } - - /** - * Sets the integer value for the option. - * @param value the new integer value - */ - public void setValue(int value) { this.val = value; } - - /** - * Returns the value of the option as a string (via override). - *

- * By default, this returns "{@code val}%" for numeric options, - * "Yes" or "No" based on the {@code bool} field for boolean options, - * and an empty string otherwise. - * @return the value string - */ - public String getValueString() { - if (type == OptionType.NUMERIC) - return String.format("%d%%", val); - else if (type == OptionType.BOOLEAN) - return (bool) ? "Yes" : "No"; - else - return ""; - } - - /** - * Processes a mouse click action (via override). - *

- * By default, this inverts the current {@code bool} field. - */ - public void click() { bool = !bool; } - - /** - * Get a list of values to choose from - * @return list with value string or null if no list should be shown - */ - public Object[] getListItems() { return null; } - - /** - * Fired when an item in the value list has been clicked - * @param index the itemindex which has been clicked - */ - public void clickListItem(int index) { } - - /** - * Returns the string to write to the configuration file (via override). - *

- * By default, this returns "{@code val}" for numeric options, - * "true" or "false" based on the {@code bool} field for boolean options, - * and {@link #getValueString()} otherwise. - * @return the string to write - */ - public String write() { - if (type == OptionType.NUMERIC) - return Integer.toString(val); - else if (type == OptionType.BOOLEAN) - return Boolean.toString(bool); - else - return getValueString(); - } - - /** - * Reads the value of the option from the configuration file (via override). - *

- * By default, this sets {@code val} for numeric options only if the - * value is between the min and max bounds, sets {@code bool} for - * boolean options, and does nothing otherwise. - * @param s the value string read from the configuration file - */ - public void read(String s) { - if (type == OptionType.NUMERIC) { - int i = Integer.parseInt(s); - if (i >= min && i <= max) - val = i; - } else if (type == OptionType.BOOLEAN) - bool = Boolean.parseBoolean(s); - } - - public OptionType getType() { - return type; - } - - public int getMinValue() { - return min; - } - - public int getMaxValue() { - return max; - } - - public int getDefaultVal() { - return defaultVal; - } - - /** - * Update the filtered flag for this option based on the given searchString. - * @param searchString the searched string or null to reset the filtered flag - * @return true if this option does need to be filtered - */ - public boolean filter(String searchString) { - if (searchString == null || searchString.length() == 0) { - filtered = false; - return false; - } - filtered = !name.toLowerCase().contains(searchString) && !description.toLowerCase().contains(searchString); - return filtered; - } - - /** - * Check if this option should be filtered (= not shown) because it does not - * match the search string. - * @return true if the option shouldn't be shown. - */ - public boolean isFiltered() { - return filtered; - } - - } - - /** Map of option display names to GameOptions. */ - private static HashMap optionMap; - - private static String[] resolutions = { - null, - "800x600", - "1024x600", - "1024x768", - "1280x720", - "1280x800", - "1280x960", - "1280x1024", - "1366x768", - "1440x900", - "1600x900", - "1600x1200", - "1680x1050", - "1920x1080", - "1920x1200", - "2560x1440", - "2560x1600", - "3840x2160" - }; - - private static int resolutionIdx; - - public static int width; - public static int height; - - /** The available skin directories. */ - private static String[] skinDirs; - - /** The index in the skinDirs array. */ - private static int skinDirIndex = 0; - - /** The name of the skin. */ - private static String skinName = "Default"; - - /** The current skin. */ - private static Skin skin; - - /** Frame limiters. */ - private static final int[] targetFPS = { 60, 120, 240, 1000 }; - - /** Index in targetFPS[] array. */ - private static int targetFPSindex = 0; - - /** Screenshot file formats. */ - private static String[] screenshotFormat = { "PNG", "JPG", "BMP" }; - - /** Index in screenshotFormat[] array. */ - private static int screenshotFormatIndex = 0; - - /** Left and right game keys. */ - private static int - keyLeft = Keyboard.KEY_NONE, - keyRight = Keyboard.KEY_NONE; - - // This class should not be instantiated. - private Options() {} - - public static String getSkinName() { - return skinName; - } - - public static int getResolutionIdx() { - return resolutionIdx; - } - - public static boolean allowLargeResolutions() { - return GameOption.ALLOW_LARGER_RESOLUTIONS.getBooleanValue(); - } - - /** - * Returns the target frame rate. - * @return the target FPS - */ - public static int getTargetFPS() { return targetFPS[targetFPSindex]; } - - public static int getTargetUPS() { - return GameOption.TARGET_UPS.val; - } - - /** - * Sets the target frame rate to the next available option, and sends a - * bar notification about the action. - */ - public static void setNextFPS(DisplayContainer displayContainer) { - GameOption.displayContainer = displayContainer; // TODO dirty shit - GameOption.TARGET_FPS.clickListItem((targetFPSindex + 1) % targetFPS.length); - EventBus.post(new BarNotificationEvent(String.format("Frame limiter: %s", GameOption.TARGET_FPS.getValueString()))); - } - - /** - * Returns the master volume level. - * @return the volume [0, 1] - */ - public static float getMasterVolume() { return GameOption.MASTER_VOLUME.getIntegerValue() / 100f; } - - /** - * Sets the master volume level (if within valid range). - * @param volume the volume [0, 1] - */ - public static void setMasterVolume(float volume) { - if (volume >= 0f && volume <= 1f) { - GameOption.MASTER_VOLUME.setValue((int) (volume * 100f)); - MusicController.setVolume(getMasterVolume() * getMusicVolume()); - } - } - - /** - * Returns the default music volume. - * @return the volume [0, 1] - */ - public static float getMusicVolume() { return GameOption.MUSIC_VOLUME.getIntegerValue() / 100f; } - - /** - * Returns the default sound effect volume. - * @return the sound volume [0, 1] - */ - public static float getEffectVolume() { return GameOption.EFFECT_VOLUME.getIntegerValue() / 100f; } - - /** - * Returns the default hit sound volume. - * @return the hit sound volume [0, 1] - */ - public static float getHitSoundVolume() { return GameOption.HITSOUND_VOLUME.getIntegerValue() / 100f; } - - /** - * Returns the default hit sound volume. - * @return the hit sound volume [0, 1] - */ - public static float getSampleVolumeOverride() { return GameOption.SAMPLE_VOLUME_OVERRIDE.val / 100f; } - - /** - * Returns the music offset time. - * @return the offset (in milliseconds) - */ - public static int getMusicOffset() { return GameOption.MUSIC_OFFSET.getIntegerValue(); } - - /** - * Returns the screenshot file format. - * @return the file extension ("png", "jpg", "bmp") - */ - public static String getScreenshotFormat() { return screenshotFormat[screenshotFormatIndex].toLowerCase(); } - - /** - * Sets the container size and makes the window borderless if the container - * size is identical to the screen resolution. - *

- * If the configured resolution is larger than the screen size, the smallest - * available resolution will be used. - */ - public static void setDisplayMode(DisplayContainer container) { - int screenWidth = container.nativeDisplayMode.getWidth(); - int screenHeight = container.nativeDisplayMode.getHeight(); - - resolutions[0] = screenWidth + "x" + screenHeight; - if (resolutionIdx < 0 || resolutionIdx > resolutions.length) { - resolutionIdx = 0; - } - if (!resolutions[resolutionIdx].matches("^[0-9]+x[0-9]+$")) { - resolutionIdx = 0; - } - String[] res = resolutions[resolutionIdx].split("x"); - width = Integer.parseInt(res[0]); - height = Integer.parseInt(res[1]); - - // check for larger-than-screen dimensions - if (!GameOption.ALLOW_LARGER_RESOLUTIONS.getBooleanValue() && (screenWidth < width || screenHeight < height)) { - width = 800; - height = 600; - } - - try { - container.setDisplayMode(width, height, isFullscreen()); - } catch (Exception e) { - EventBus.post(new BubbleNotificationEvent("Failed to change resolution", BubbleNotificationEvent.COMMONCOLOR_RED)); - Log.error("Failed to set display mode.", e); - } - - if (!isFullscreen()) { - // set borderless window if dimensions match screen size - boolean borderless = (screenWidth == width && screenHeight == height); - System.setProperty("org.lwjgl.opengl.Window.undecorated", Boolean.toString(borderless)); - } - } - - public static int getExgonDelay() { - return GameOption.DANCE_EXGON_DELAY.getIntegerValue(); - } - public static int getQuadBezAggressiveness() { - return GameOption.DANCE_QUAD_BEZ_AGGRESSIVENESS.getIntegerValue(); - } - public static int getQuadBezSliderAggressiveness() { - return GameOption.DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR.getIntegerValue(); - } - public static boolean isQuadBezCubicEnabled() { - return GameOption.DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS.getBooleanValue(); - } - public static int getQuadBezSliderEntryAggressiveness() { - return GameOption.DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR.getIntegerValue(); - } - public static int getSpinnerDelay() { - return GameOption.DANCE_SPINNER_DELAY.getIntegerValue(); - } - public static boolean isLazySliders() { - return GameOption.DANCE_LAZY_SLIDERS.getBooleanValue(); - } - public static boolean isOnlyCircleStacks() { - return GameOption.DANCE_ONLY_CIRCLE_STACKS.getBooleanValue(); - } - public static boolean isCircleStreams() { - return GameOption.DANCE_CIRCLE_STREAMS.getBooleanValue(); - } - public static boolean isMirror() { - return GameOption.DANCE_MIRROR.getBooleanValue(); - } - public static void setMirror(boolean mirror) { - GameOption.DANCE_MIRROR.setValue(mirror); - } - public static boolean isDrawApproach() { - return GameOption.DANCE_DRAW_APPROACH.getBooleanValue(); - } - public static int getRGBObjInc() { - return GameOption.DANCE_RGB_OBJECT_INC.getIntegerValue(); - } - public static boolean isCursorOnlyColorTrail() { - return GameOption.DANCE_CURSOR_ONLY_COLOR_TRAIL.getBooleanValue(); - } - public static int getRGBCursorInc() { - return GameOption.DANCE_RGB_CURSOR_INC.getIntegerValue(); - } - public static int getCursorTrailOverride() { - return GameOption.DANCE_CURSOR_TRAIL_OVERRIDE.getIntegerValue(); - } - public static boolean isHideObjects() { - return GameOption.DANCE_HIDE_OBJECTS.getBooleanValue(); - } - public static boolean isRemoveBG() { - return GameOption.DANCE_REMOVE_BG.getBooleanValue(); - } - public static boolean isCircleInSlowSliders() { - return GameOption.DANCE_CIRLCE_IN_SLOW_SLIDERS.getBooleanValue(); - } - public static boolean isCircleInLazySliders() { - return GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS.getBooleanValue(); - } - public static boolean isHideUI() { - return GameOption.DANCE_HIDE_UI.getBooleanValue(); - } - public static boolean isEnableSB() { - return GameOption.DANCE_ENABLE_SB.getBooleanValue(); - } - public static boolean isPippiEnabled() { - return GameOption.PIPPI_ENABLE.getBooleanValue(); - } - public static int getPippiAngIncMultiplier() { - return GameOption.PIPPI_ANGLE_INC_MUL.getIntegerValue(); - } - public static int getPippiAngIncMultiplierSlider() { - return GameOption.PIPPI_ANGLE_INC_MUL_SLIDER.getIntegerValue(); - } - public static boolean isPippiFollowcircleExpand() { - return GameOption.PIPPI_SLIDER_FOLLOW_EXPAND.getBooleanValue(); - } - public static boolean isPippiPreventWobblyStreams() { - return GameOption.PIPPI_PREVENT_WOBBLY_STREAMS.getBooleanValue(); - } - - /** - * Returns whether or not fullscreen mode is enabled. - * @return true if enabled - */ - public static boolean isFullscreen() { return GameOption.FULLSCREEN.getBooleanValue(); } - - /** - * Returns whether or not the FPS counter display is enabled. - * @return true if enabled - */ - public static boolean isFPSCounterEnabled() { return GameOption.SHOW_FPS.getBooleanValue(); } - - public static boolean useDeltasForFPSCounter() { return GameOption.USE_FPS_DELTAS.getBooleanValue(); } - - /** - * Returns whether or not hit lighting effects are enabled. - * @return true if enabled - */ - public static boolean isHitLightingEnabled() { return GameOption.SHOW_HIT_LIGHTING.getBooleanValue(); } - - /** - * Returns whether or not hit animation effects are enabled. - * @return true if enabled - */ - public static boolean isHitAnimationEnabled() { return GameOption.SHOW_HIT_ANIMATIONS.getBooleanValue(); } - - /** - * Returns whether or not hit animation effects are enabled. - * @return true if enabled - */ - public static boolean isReverseArrowAnimationEnabled() { return GameOption.SHOW_REVERSEARROW_ANIMATIONS.getBooleanValue(); } - - /** - * Returns whether or not combo burst effects are enabled. - * @return true if enabled - */ - public static boolean isComboBurstEnabled() { return GameOption.SHOW_COMBO_BURSTS.getBooleanValue(); } - - /** - * Returns the port number to bind to. - * @return the port - */ - public static int getPort() { return port; } - - public static boolean noSingleInstance() { return noSingleInstance; } - - /** - * Returns the cursor scale. - * @return the scale [0.5, 2] - */ - public static float getCursorScale() { return GameOption.CURSOR_SIZE.getIntegerValue() / 100f; } - - /** - * Returns whether or not the new cursor type is enabled. - * @return true if enabled - */ - public static boolean isNewCursorEnabled() { return GameOption.NEW_CURSOR.getBooleanValue(); } - - /** - * Returns whether or not the main menu background should be the current track image. - * @return true if enabled - */ - public static boolean isDynamicBackgroundEnabled() { return GameOption.DYNAMIC_BACKGROUND.getBooleanValue(); } - - /** - * Returns whether or not to show perfect hit result bursts. - * @return true if enabled - */ - public static boolean isPerfectHitBurstEnabled() { return GameOption.SHOW_PERFECT_HIT.getBooleanValue(); } - - /** - * Returns whether or not to show follow points. - * @return true if enabled - */ - public static boolean isFollowPointEnabled() { return GameOption.SHOW_FOLLOW_POINTS.getBooleanValue(); } - - /** - * Returns the background dim level. - * @return the alpha level [0, 1] - */ - public static float getBackgroundDim() { return (100 - GameOption.BACKGROUND_DIM.getIntegerValue()) / 100f; } - - /** - * Returns whether or not to override the song background with the default playfield background. - * @return true if forced - */ - public static boolean isDefaultPlayfieldForced() { return GameOption.FORCE_DEFAULT_PLAYFIELD.getBooleanValue(); } - - /** - * Returns whether or not beatmap skins are ignored. - * @return true if ignored - */ - public static boolean isBeatmapSkinIgnored() { return GameOption.IGNORE_BEATMAP_SKINS.getBooleanValue(); } - - /** - * Returns whether or not sliders should snake in or just appear fully at once. - * @return true if sliders should snake in - */ - public static boolean isSliderSnaking() { return GameOption.SNAKING_SLIDERS.getBooleanValue(); } - - public static boolean isFallbackSliders() { return GameOption.FALLBACK_SLIDERS.getBooleanValue(); } - - public static boolean isShrinkingSliders() { return GameOption.SHRINKING_SLIDERS.getBooleanValue(); } - public static boolean isMergingSliders() { return !isFallbackSliders() && GameOption.MERGING_SLIDERS.getBooleanValue(); } - public static int getMergingSlidersMirrorPool() { return GameOption.MERGING_SLIDERS_MIRROR_POOL.getIntegerValue(); } - public static boolean isDrawSliderEndCircles() { return GameOption.DRAW_SLIDER_ENDCIRCLES.getBooleanValue(); } - - /** - * Returns the fixed circle size override, if any. - * @return the CS value (0, 10], 0f if disabled - */ - public static float getFixedCS() { return GameOption.FIXED_CS.getIntegerValue() / 10f; } - - /** - * Returns the fixed HP drain rate override, if any. - * @return the HP value (0, 10], 0f if disabled - */ - public static float getFixedHP() { return GameOption.FIXED_HP.getIntegerValue() / 10f; } - - /** - * Returns the fixed approach rate override, if any. - * @return the AR value (0, 10], 0f if disabled - */ - public static float getFixedAR() { return GameOption.FIXED_AR.getIntegerValue() / 10f; } - - /** - * Returns the fixed overall difficulty override, if any. - * @return the OD value (0, 10], 0f if disabled - */ - public static float getFixedOD() { return GameOption.FIXED_OD.getIntegerValue() / 10f; } - - /** - * Returns whether or not to render loading text in the splash screen. - * @return true if enabled - */ - public static boolean isLoadVerbose() { return GameOption.LOAD_VERBOSE.getBooleanValue(); } - - /** - * Returns whether or not to color the main menu logo. - * @return true if enabled - */ - public static boolean isColorMainMenuLogo() { return GameOption.COLOR_MAIN_MENU_LOGO.getBooleanValue(); } - - /** - * Returns the track checkpoint time. - * @return the checkpoint time (in ms) - */ - public static int getCheckpoint() { return GameOption.CHECKPOINT.getIntegerValue() * 1000; } - - /** - * Returns whether or not all sound effects are disabled. - * @return true if disabled - */ - public static boolean isSoundDisabled() { return GameOption.DISABLE_SOUNDS.getBooleanValue(); } - - /** - * Returns whether or not to use non-English metadata where available. - * @return true if Unicode preferred - */ - public static boolean useUnicodeMetadata() { return GameOption.SHOW_UNICODE.getBooleanValue(); } - - /** - * Returns whether or not to play the theme song. - * @return true if enabled - */ - public static boolean isThemeSongEnabled() { return GameOption.ENABLE_THEME_SONG.getBooleanValue(); } - - /** - * Returns whether or not replay seeking is enabled. - * @return true if enabled - */ - public static boolean isReplaySeekingEnabled() { return GameOption.REPLAY_SEEKING.getBooleanValue(); } - - /** - * Returns whether or not automatic checking for updates is disabled. - * @return true if disabled - */ - public static boolean isUpdaterDisabled() { return GameOption.DISABLE_UPDATER.getBooleanValue(); } - - /** - * Returns whether or not the beatmap watch service is enabled. - * @return true if enabled - */ - public static boolean isWatchServiceEnabled() { return GameOption.ENABLE_WATCH_SERVICE.getBooleanValue(); } - - /** - * Sets the track checkpoint time, if within bounds. - * @param time the track position (in ms) - * @return true if within bounds - */ - public static boolean setCheckpoint(int time) { - if (time >= 0 && time < 3600) { - GameOption.CHECKPOINT.setValue(time); - return true; - } - return false; - } - - /** - * Returns whether or not to show the hit error bar. - * @return true if enabled - */ - public static boolean isHitErrorBarEnabled() { return GameOption.SHOW_HIT_ERROR_BAR.getBooleanValue(); } - - public static int getMapStartDelay() { return GameOption.MAP_START_DELAY.getIntegerValue() * 100; } - public static int getMapEndDelay() { return GameOption.MAP_END_DELAY.getIntegerValue() * 100; } - public static int getEpilepsyWarningLength() { return GameOption.EPILEPSY_WARNING.getIntegerValue() * 100; } - - /** - * Returns whether or not to load HD (@2x) images. - * @return true if HD images are enabled, false if only SD images should be loaded - */ - public static boolean loadHDImages() { return GameOption.LOAD_HD_IMAGES.getBooleanValue(); } - - /** - * Returns whether or not the mouse wheel is disabled during gameplay. - * @return true if disabled - */ - public static boolean isMouseWheelDisabled() { return GameOption.DISABLE_MOUSE_WHEEL.getBooleanValue(); } - - /** - * Returns whether or not the mouse buttons are disabled during gameplay. - * @return true if disabled - */ - public static boolean isMouseDisabled() { return GameOption.DISABLE_MOUSE_BUTTONS.getBooleanValue(); } - - /** - * Toggles the mouse button enabled/disabled state during gameplay and - * sends a bar notification about the action. - */ - public static void toggleMouseDisabled() { - GameOption.DISABLE_MOUSE_BUTTONS.click(); - EventBus.post(new BarNotificationEvent((GameOption.DISABLE_MOUSE_BUTTONS.getBooleanValue()) ? - "Mouse buttons are disabled." : "Mouse buttons are enabled.")); - } - - /** - * Returns whether or not the cursor sprite should be hidden. - * @return true if disabled - */ - public static boolean isCursorDisabled() { return GameOption.DISABLE_CURSOR.getBooleanValue(); } - - /** - * Returns the left game key. - * @return the left key code - */ - public static int getGameKeyLeft() { - if (keyLeft == Keyboard.KEY_NONE) - setGameKeyLeft(Input.KEY_Z); - return keyLeft; - } - - /** - * Returns the right game key. - * @return the right key code - */ - public static int getGameKeyRight() { - if (keyRight == Keyboard.KEY_NONE) - setGameKeyRight(Input.KEY_X); - return keyRight; - } - - /** - * Sets the left game key. - * This will not be set to the same key as the right game key, nor to any - * reserved keys (see {@link #isValidGameKey(int)}). - * @param key the keyboard key - * @return {@code true} if the key was set, {@code false} if it was rejected - */ - public static boolean setGameKeyLeft(int key) { - if ((key == keyRight && key != Keyboard.KEY_NONE) || !isValidGameKey(key)) - return false; - keyLeft = key; - return true; - } - - /** - * Sets the right game key. - * This will not be set to the same key as the left game key, nor to any - * reserved keys (see {@link #isValidGameKey(int)}). - * @param key the keyboard key - * @return {@code true} if the key was set, {@code false} if it was rejected - */ - public static boolean setGameKeyRight(int key) { - if ((key == keyLeft && key != Keyboard.KEY_NONE) || !isValidGameKey(key)) - return false; - keyRight = key; - return true; - } - - /** - * Checks if the given key is a valid game key. - * @param key the keyboard key - * @return {@code true} if valid, {@code false} otherwise - */ - private static boolean isValidGameKey(int key) { - return (key != Keyboard.KEY_ESCAPE && key != Keyboard.KEY_SPACE && - key != Keyboard.KEY_UP && key != Keyboard.KEY_DOWN && - key != Keyboard.KEY_F7 && key != Keyboard.KEY_F10 && key != Keyboard.KEY_F12); - } - - /** - * Returns the beatmap directory. - * If invalid, this will attempt to search for the directory, - * and if nothing found, will create one. - * @return the beatmap directory - */ - public static File getBeatmapDir() { - if (beatmapDir != null && beatmapDir.isDirectory()) - return beatmapDir; - - // use osu! installation directory, if found - File osuDir = getOsuInstallationDirectory(); - if (osuDir != null) { - beatmapDir = new File(osuDir, BEATMAP_DIR.getName()); - if (beatmapDir.isDirectory()) - return beatmapDir; - } - - // use default directory - beatmapDir = BEATMAP_DIR; - if (!beatmapDir.isDirectory() && !beatmapDir.mkdir()) - EventBus.post(new BubbleNotificationEvent(String.format("Failed to create beatmap directory at '%s'.", beatmapDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED)); - return beatmapDir; - } - - /** - * Returns the OSZ archive directory. - * If invalid, this will create and return a "SongPacks" directory. - * @return the OSZ archive directory - */ - public static File getOSZDir() { - if (oszDir != null && oszDir.isDirectory()) - return oszDir; - - oszDir = new File(DATA_DIR, "SongPacks/"); - if (!oszDir.isDirectory() && !oszDir.mkdir()) - EventBus.post(new BubbleNotificationEvent(String.format("Failed to create song packs directory at '%s'.", oszDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED)); - 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/"); - if (!replayImportDir.isDirectory() && !replayImportDir.mkdir()) - EventBus.post(new BubbleNotificationEvent(String.format("Failed to create replay import directory at '%s'.", replayImportDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED)); - return replayImportDir; - } - - /** - * Returns the screenshot directory. - * If invalid, this will return a "Screenshot" directory. - * @return the screenshot directory - */ - public static File getScreenshotDir() { - if (screenshotDir != null && screenshotDir.isDirectory()) - return screenshotDir; - - screenshotDir = new File(DATA_DIR, "Screenshots/"); - return screenshotDir; - } - - /** - * Returns the replay directory. - * If invalid, this will return a "Replay" directory. - * @return the replay directory - */ - public static File getReplayDir() { - if (replayDir != null && replayDir.isDirectory()) - return replayDir; - - replayDir = new File(DATA_DIR, "Replays/"); - return replayDir; - } - - /** - * Returns the current skin directory. - * If invalid, this will create a "Skins" folder in the root directory. - * @return the skin directory - */ - public static File getSkinRootDir() { - if (skinRootDir != null && skinRootDir.isDirectory()) - return skinRootDir; - - // use osu! installation directory, if found - File osuDir = getOsuInstallationDirectory(); - if (osuDir != null) { - skinRootDir = new File(osuDir, SKIN_ROOT_DIR.getName()); - if (skinRootDir.isDirectory()) - return skinRootDir; - } - - // use default directory - skinRootDir = SKIN_ROOT_DIR; - if (!skinRootDir.isDirectory() && !skinRootDir.mkdir()) - EventBus.post(new BubbleNotificationEvent(String.format("Failed to create skins directory at '%s'.", skinRootDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED)); - return skinRootDir; - } - - public static void reloadSkin() { - loadSkin(); - SoundController.init(); - EventBus.post(new ResolutionOrSkinChangedEvent()); - } - - /** - * Loads the skin given by the current skin directory. - * If the directory is invalid, the default skin will be loaded. - */ - public static void loadSkin() { - File skinDir = getSkinDir(); - if (skinDir == null) // invalid skin name - skinName = Skin.DEFAULT_SKIN_NAME; - - // create available skins list - File[] dirs = SkinLoader.getSkinDirectories(getSkinRootDir()); - skinDirs = new String[dirs.length + 1]; - skinDirs[0] = Skin.DEFAULT_SKIN_NAME; - for (int i = 0; i < dirs.length; i++) - skinDirs[i + 1] = dirs[i].getName(); - - // set skin and modify resource locations - ResourceLoader.removeAllResourceLocations(); - if (skinDir == null) - skin = new Skin(null); - else { - // set skin index - for (int i = 1; i < skinDirs.length; i++) { - if (skinDirs[i].equals(skinName)) { - skinDirIndex = i; - break; - } - } - - // load the skin - skin = SkinLoader.loadSkin(skinDir); - ResourceLoader.addResourceLocation(new FileSystemLocation(skinDir)); - } - ResourceLoader.addResourceLocation(new ClasspathLocation()); - ResourceLoader.addResourceLocation(new FileSystemLocation(new File("."))); - ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/"))); - } - - /** - * Returns the current skin. - * @return the skin, or null if no skin is loaded (see {@link #loadSkin()}) - */ - public static Skin getSkin() { return skin; } - - /** - * Returns the current skin directory. - *

- * NOTE: This directory will differ from that of the currently loaded skin - * if {@link #loadSkin()} has not been called after a directory change. - * Use {@link Skin#getDirectory()} to get the directory of the currently - * loaded skin. - * @return the skin directory, or null for the default skin - */ - public static File getSkinDir() { - File root = getSkinRootDir(); - File dir = new File(root, skinName); - return (dir.isDirectory()) ? dir : null; - } - - /** - * Returns a dummy Beatmap containing the theme song. - * @return the theme song beatmap, or {@code null} if the theme string is malformed - */ - public static Beatmap getThemeBeatmap() { - String[] tokens = themeString.split(","); - if (tokens.length != 4) - return null; - - Beatmap beatmap = new Beatmap(null); - beatmap.audioFilename = new File(tokens[0]); - beatmap.title = tokens[1]; - beatmap.artist = tokens[2]; - try { - beatmap.endTime = Integer.parseInt(tokens[3]); - } catch (NumberFormatException e) { - return null; - } - try { - beatmap.timingPoints = new ArrayList<>(1); - beatmap.timingPoints.add(new TimingPoint(themeTimingPoint)); - } catch (Exception e) { - return null; - } - - return beatmap; - } - - /** - * Reads user options from the options file, if it exists. - */ - public static void parseOptions() { - // if no config file, use default settings - if (!OPTIONS_FILE.isFile()) { - saveOptions(); - return; - } - - // create option map - if (optionMap == null) { - optionMap = new HashMap(); - for (GameOption option : GameOption.values()) - optionMap.put(option.getDisplayName(), option); - } - - // read file - try (BufferedReader in = new BufferedReader(new FileReader(OPTIONS_FILE))) { - String line; - while ((line = in.readLine()) != null) { - line = line.trim(); - if (line.length() < 2 || line.charAt(0) == '#') - continue; - int index = line.indexOf('='); - if (index == -1) - continue; - - // read option - String name = line.substring(0, index).trim(); - GameOption option = optionMap.get(name); - if (option != null) { - try { - String value = line.substring(index + 1).trim(); - option.read(value); - } catch (NumberFormatException e) { - Log.warn(String.format("Format error in options file for line: '%s'.", line), e); - } - } - } - } catch (IOException e) { - String err = String.format("Failed to read file '%s'.", OPTIONS_FILE.getAbsolutePath()); - Log.error(err, e); - EventBus.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED)); - } - } - - /** - * (Over)writes user options to a file. - */ - public static void saveOptions() { - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(OPTIONS_FILE), "utf-8"))) { - // header - SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM dd, yyyy"); - String date = dateFormat.format(new Date()); - writer.write("# opsu! configuration"); - writer.newLine(); - writer.write("# last updated on "); - writer.write(date); - writer.newLine(); - writer.newLine(); - - // options - for (GameOption option : GameOption.values()) { - writer.write(option.getDisplayName()); - writer.write(" = "); - writer.write(option.write()); - writer.newLine(); - } - writer.close(); - } catch (IOException e) { - String err = String.format("Failed to write to file '%s'.", OPTIONS_FILE.getAbsolutePath()); - Log.error(err, e); - EventBus.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED)); - } - } -} diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 7dd46fbf..ff2648db 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -18,17 +18,8 @@ package itdelatrisu.opsu; -import itdelatrisu.opsu.audio.MusicController; -import itdelatrisu.opsu.audio.SoundController; -import itdelatrisu.opsu.audio.SoundEffect; -import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.downloads.Download; -import itdelatrisu.opsu.downloads.DownloadNode; -import itdelatrisu.opsu.replay.PlaybackSpeed; -import itdelatrisu.opsu.ui.Fonts; -import itdelatrisu.opsu.ui.UI; -import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; @@ -41,18 +32,13 @@ import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URISyntaxException; import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; import java.util.Arrays; -import java.util.Date; import java.util.Scanner; import java.util.jar.JarFile; -import javax.imageio.ImageIO; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; @@ -61,21 +47,15 @@ import javax.net.ssl.X509TrustManager; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.GL11; +import org.lwjgl.input.Keyboard; import org.newdawn.slick.Animation; import org.newdawn.slick.Color; -import org.newdawn.slick.GameContainer; -import org.newdawn.slick.Input; -import org.newdawn.slick.state.StateBasedGame; import org.newdawn.slick.util.Log; import com.sun.jna.platform.FileUtils; import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.errorhandling.ErrorHandler; -import yugecin.opsudance.core.events.EventBus; -import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Options; /** * Contains miscellaneous utilities. @@ -104,32 +84,6 @@ public class Utils { // TODO clean this up // game settings - displayContainer.setFPS(Options.getTargetFPS()); // TODO move this elsewhere - MusicController.setMusicVolume(Options.getMusicVolume() * Options.getMasterVolume()); - - // load skin - Options.loadSkin(); - - // initialize game images - for (GameImage img : GameImage.values()) { - if (img.isPreload()) - img.setDefaultImage(); - } - - // initialize game mods - GameMod.init(displayContainer.width, displayContainer.height); - - // initialize playback buttons - PlaybackSpeed.init(displayContainer.width, displayContainer.height); - - // initialize hit objects - HitObject.init(displayContainer.width, displayContainer.height); - - // initialize download nodes - DownloadNode.init(displayContainer.width, displayContainer.height); - - // initialize UI components - UI.init(displayContainer); } /** @@ -240,55 +194,6 @@ public class Utils { return true; } - /** - * Takes a screenshot. - * @author http://wiki.lwjgl.org/index.php?title=Taking_Screen_Shots - */ - public static void takeScreenShot() { - // create the screenshot directory - File dir = Options.getScreenshotDir(); - if (!dir.isDirectory() && !dir.mkdir()) { - EventBus.post(new BubbleNotificationEvent(String.format("Failed to create screenshot directory at '%s'.", dir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED)); - return; - } - - // create file name - SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss"); - final String fileName = String.format("screenshot_%s.%s", date.format(new Date()), Options.getScreenshotFormat()); - final File file = new File(dir, fileName); - - SoundController.playSound(SoundEffect.SHUTTER); - - // copy the screen to file - final int width = Display.getWidth(); - final int height = Display.getHeight(); - final int bpp = 3; // assuming a 32-bit display with a byte each for red, green, blue, and alpha - final ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); - GL11.glReadBuffer(GL11.GL_FRONT); - GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); - GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); - new Thread() { - @Override - public void run() { - try { - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - int i = (x + (width * y)) * bpp; - int r = buffer.get(i) & 0xFF; - int g = buffer.get(i + 1) & 0xFF; - int b = buffer.get(i + 2) & 0xFF; - image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); - } - } - ImageIO.write(image, Options.getScreenshotFormat(), file); - EventBus.post(new BubbleNotificationEvent("Created " + fileName, BubbleNotificationEvent.COMMONCOLOR_PURPLE)); - } catch (Exception e) { - ErrorHandler.error("Failed to take a screenshot.", e).show(); - } - } - }.start(); - } /** * Returns a human-readable representation of a given number of bytes. @@ -550,14 +455,6 @@ public class Utils { } } - /** - * Returns the current working directory. - * @return the directory - */ - public static File getWorkingDirectory() { - return Paths.get(".").toAbsolutePath().normalize().toFile(); - } - /** * Parses the integer string argument as a boolean: * {@code 1} is {@code true}, and all other values are {@code false}. @@ -661,4 +558,19 @@ public class Utils { }; } + /** + * Returns the file extension of a file. + * @param file the file name + */ + public static String getFileExtension(String file) { + int i = file.lastIndexOf('.'); + return (i != -1) ? file.substring(i + 1).toLowerCase() : ""; + } + + public static boolean isValidGameKey(int key) { + return (key != Keyboard.KEY_ESCAPE && key != Keyboard.KEY_SPACE && + key != Keyboard.KEY_UP && key != Keyboard.KEY_DOWN && + key != Keyboard.KEY_F7 && key != Keyboard.KEY_F10 && key != Keyboard.KEY_F12); + } + } diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 3082310f..d30cea6f 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -18,9 +18,8 @@ package itdelatrisu.opsu.audio; -import itdelatrisu.opsu.Options; +import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.Beatmap; -import itdelatrisu.opsu.beatmap.BeatmapParser; import itdelatrisu.opsu.beatmap.TimingPoint; import itdelatrisu.opsu.states.Game; @@ -50,6 +49,8 @@ import yugecin.opsudance.core.events.EventBus; import yugecin.opsudance.events.BarNotificationEvent; import yugecin.opsudance.events.BubbleNotificationEvent; +import static yugecin.opsudance.options.Options.*; + /** * Controller for all music. */ @@ -109,7 +110,7 @@ public class MusicController { reset(); System.gc(); - switch (BeatmapParser.getExtension(beatmap.audioFilename.getName())) { + switch (Utils.getFileExtension(beatmap.audioFilename.getName())) { case "ogg": case "mp3": trackLoader = new Thread() { @@ -168,7 +169,7 @@ public class MusicController { */ public static void playAt(final int position, final boolean loop) { if (trackExists()) { - setVolume(Options.getMusicVolume() * Options.getMasterVolume()); + setVolume(OPTION_MUSIC_VOLUME.val / 100f * OPTION_MASTER_VOLUME.val / 100f); trackEnded = false; pauseTime = 0f; resetTimingPoint(); @@ -348,9 +349,9 @@ public class MusicController { */ public static int getPosition() { if (isPlaying()) - return (int) (player.getPosition() * 1000 + Options.getMusicOffset() + Game.currentMapMusicOffset); + return (int) (player.getPosition() * 1000 + OPTION_MUSIC_OFFSET.val + Game.currentMapMusicOffset); else if (isPaused()) - return Math.max((int) (pauseTime * 1000 + Options.getMusicOffset() + Game.currentMapMusicOffset), 0); + return Math.max((int) (pauseTime * 1000 + OPTION_MUSIC_OFFSET.val + Game.currentMapMusicOffset), 0); else return 0; } @@ -443,13 +444,9 @@ public class MusicController { playAt((preview) ? lastBeatmap.previewTime : 0, false); } - /** - * Plays the theme song. - */ - public static void playThemeSong() { - Beatmap beatmap = Options.getThemeBeatmap(); - if (beatmap != null) { - play(beatmap, false, false); + public static void playThemeSong(Beatmap themeBeatmap) { + if (themeBeatmap != null) { + play(themeBeatmap, false, false); themePlaying = true; } } @@ -470,7 +467,7 @@ public class MusicController { * @param multiplier the volume multiplier when the track is dimmed */ public static void toggleTrackDimmed(float multiplier) { - float volume = Options.getMusicVolume() * Options.getMasterVolume(); + float volume = OPTION_MUSIC_VOLUME.val / 100f * OPTION_MASTER_VOLUME.val / 100f; dimLevel = (isTrackDimmed()) ? 1f : multiplier; trackDimmed = !trackDimmed; setVolume(volume); diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index af0d7f0a..b32162a6 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -18,12 +18,10 @@ package itdelatrisu.opsu.audio; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.audio.HitSound.SampleSet; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.downloads.Download; import itdelatrisu.opsu.downloads.Download.DownloadListener; -import itdelatrisu.opsu.ui.UI; import java.io.File; import java.io.IOException; @@ -44,6 +42,10 @@ import yugecin.opsudance.core.errorhandling.ErrorHandler; import yugecin.opsudance.core.events.EventBus; import yugecin.opsudance.events.BarNotificationEvent; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Controller for all (non-music) sound components. @@ -190,7 +192,7 @@ public class SoundController { */ private static String getSoundFileName(String filename) { String wav = String.format("%s.wav", filename), mp3 = String.format("%s.mp3", filename); - File skinDir = Options.getSkin().getDirectory(); + File skinDir = SkinService.skin.getDirectory(); if (skinDir != null) { File skinWAV = new File(skinDir, wav), skinMP3 = new File(skinDir, mp3); if (skinWAV.isFile()) @@ -209,8 +211,9 @@ public class SoundController { * Loads all sound files. */ public static void init() { - if (Options.isSoundDisabled()) + if (OPTION_DISABLE_SOUNDS.state) { return; + } currentFileIndex = 0; @@ -290,7 +293,7 @@ public class SoundController { * @param s the sound effect */ public static void playSound(SoundComponent s) { - playClip(s.getClip(), Options.getEffectVolume() * Options.getMasterVolume(), null); + playClip(s.getClip(), OPTION_EFFECT_VOLUME.val / 100f * OPTION_MASTER_VOLUME.val / 100f, null); } /** @@ -303,16 +306,16 @@ public class SoundController { if (hitSound < 0) return; - if (Options.getSampleVolumeOverride() > 0) { - sampleVolumeMultiplier = Options.getSampleVolumeOverride(); + if (OPTION_SAMPLE_VOLUME_OVERRIDE.val > 0) { + sampleVolumeMultiplier = OPTION_SAMPLE_VOLUME_OVERRIDE.val / 100f; } - float volume = Options.getHitSoundVolume() * sampleVolumeMultiplier * Options.getMasterVolume(); + float volume = OPTION_HITSOUND_VOLUME.val / 100f * sampleVolumeMultiplier * OPTION_MASTER_VOLUME.val / 100f; if (volume == 0f) return; // play all sounds - if (hitSound == HitObject.SOUND_NORMAL || Options.getSkin().isLayeredHitSounds()) { + if (hitSound == HitObject.SOUND_NORMAL || SkinService.skin.isLayeredHitSounds()) { HitSound.setSampleSet(sampleSet); playClip(HitSound.NORMAL.getClip(), volume, null); } @@ -333,7 +336,7 @@ public class SoundController { * @param s the hit sound */ public static void playHitSound(SoundComponent s) { - playClip(s.getClip(), Options.getHitSoundVolume() * sampleVolumeMultiplier * Options.getMasterVolume(), null); + playClip(s.getClip(), OPTION_HITSOUND_VOLUME.val / 100f * sampleVolumeMultiplier * OPTION_MASTER_VOLUME.val / 100f, null); } /** @@ -370,15 +373,16 @@ public class SoundController { * @return true if playing, false otherwise * @throws SlickException if any error occurred */ - public static synchronized boolean playTrack(String url, String name, boolean isMP3, LineListener listener) + public static synchronized boolean playTrack(Configuration config, String url, String name, boolean isMP3, LineListener listener) throws SlickException { // stop previous track stopTrack(); // download new track - File dir = Options.TEMP_DIR; - if (!dir.isDirectory()) + File dir = config.TEMP_DIR; + if (!dir.isDirectory()) { dir.mkdir(); + } String filename = String.format("%s.%s", name, isMP3 ? "mp3" : "wav"); final File downloadFile = new File(dir, filename); boolean complete; @@ -406,7 +410,7 @@ public class SoundController { try { AudioInputStream audioIn = AudioSystem.getAudioInputStream(downloadFile); currentTrack = loadClip(filename, audioIn, isMP3); - playClip(currentTrack, Options.getMusicVolume() * Options.getMasterVolume(), listener); + playClip(currentTrack, OPTION_MUSIC_VOLUME.val / 100f * OPTION_MASTER_VOLUME.val / 100f, listener); return true; } catch (Exception e) { throw new SlickException(String.format("Failed to load clip '%s'.", url)); diff --git a/src/itdelatrisu/opsu/beatmap/Beatmap.java b/src/itdelatrisu/opsu/beatmap/Beatmap.java index 55a34ab5..acb24feb 100644 --- a/src/itdelatrisu/opsu/beatmap/Beatmap.java +++ b/src/itdelatrisu/opsu/beatmap/Beatmap.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu.beatmap; -import itdelatrisu.opsu.Options; - import java.io.File; import java.util.ArrayList; import java.util.Collection; @@ -30,6 +28,8 @@ import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.util.Log; +import static yugecin.opsudance.options.Options.*; + /** * Beatmap structure storing data parsed from OSU files. */ @@ -264,7 +264,7 @@ public class Beatmap implements Comparable { * @return the song title */ public String getTitle() { - return (Options.useUnicodeMetadata() && !titleUnicode.isEmpty()) ? titleUnicode : title; + return (OPTION_SHOW_UNICODE.state && !titleUnicode.isEmpty()) ? titleUnicode : title; } /** @@ -273,25 +273,23 @@ public class Beatmap implements Comparable { * @return the song artist */ public String getArtist() { - return (Options.useUnicodeMetadata() && !artistUnicode.isEmpty()) ? artistUnicode : artist; + return (OPTION_SHOW_UNICODE.state && !artistUnicode.isEmpty()) ? artistUnicode : artist; } /** * Returns the list of combo colors (max 8). - * If the beatmap does not provide colors, the skin colors will be returned instead. - * @return the combo colors + * @return the combo colors, or null if this beatmap does not have combo colors specified. */ public Color[] getComboColors() { - return (combo != null) ? combo : Options.getSkin().getComboColors(); + return combo; } /** * Returns the slider border color. - * If the beatmap does not provide a color, the skin color will be returned instead. - * @return the slider border color + * @return the slider border color, or null if this beatmap does not have a slider border color specified. */ public Color getSliderBorderColor() { - return (sliderBorder != null) ? sliderBorder : Options.getSkin().getSliderBorderColor(); + return sliderBorder; } /** diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java index 529cee40..19e8f54f 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java @@ -18,7 +18,6 @@ package itdelatrisu.opsu.beatmap; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.db.BeatmapDB; import itdelatrisu.opsu.io.MD5InputStreamWrapper; @@ -36,55 +35,69 @@ import org.newdawn.slick.Color; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.errorhandling.ErrorHandler; import yugecin.opsudance.core.events.EventBus; -import yugecin.opsudance.events.BarNotificationEvent; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Parser for beatmaps. */ public class BeatmapParser { + + @Inject + private InstanceContainer instanceContainer; + + @Inject + private Configuration config; + /** The string lookup database. */ private static HashMap stringdb = new HashMap(); /** The expected pattern for beatmap directories, used to find beatmap set IDs. */ - private static final String DIR_MSID_PATTERN = "^\\d+ .*"; + private final String DIR_MSID_PATTERN = "^\\d+ .*"; /** The current file being parsed. */ - private static File currentFile; + private File currentFile; /** The current directory number while parsing. */ - private static int currentDirectoryIndex = -1; + private int currentDirectoryIndex = -1; /** The total number of directories to parse. */ - private static int totalDirectories = -1; + private int totalDirectories = -1; /** Parser statuses. */ public enum Status { NONE, PARSING, CACHE, INSERTING }; /** The current status. */ - private static Status status = Status.NONE; + private Status status = Status.NONE; /** If no Provider supports a MessageDigestSpi implementation for the MD5 algorithm. */ - private static boolean hasNoMD5Algorithm = false; + private boolean hasNoMD5Algorithm = false; - // This class should not be instantiated. - private BeatmapParser() {} + @Inject + public BeatmapParser() { + } /** * Invokes parser for each OSU file in a root directory and * adds the beatmaps to a new BeatmapSetList. * @param root the root directory (search has depth 1) */ - public static void parseAllFiles(File root) { + public void parseAll() { // create a new BeatmapSetList BeatmapSetList.create(); // create a new watch service - if (Options.isWatchServiceEnabled()) - BeatmapWatchService.create(); + if (OPTION_ENABLE_WATCH_SERVICE.state) { + BeatmapWatchService.create(instanceContainer); + } // parse all directories - parseDirectories(root.listFiles()); + parseDirectories(config.beatmapDir.listFiles()); } /** @@ -93,7 +106,7 @@ public class BeatmapParser { * @param dirs the array of directories to parse * @return the last BeatmapSetNode parsed, or null if none */ - public static BeatmapSetNode parseDirectories(File[] dirs) { + public BeatmapSetNode parseDirectories(File[] dirs) { if (dirs == null) return null; @@ -111,7 +124,7 @@ public class BeatmapParser { List parsedBeatmaps = new LinkedList(); // loaded from parser // watch service - BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null; + BeatmapWatchService ws = BeatmapWatchService.get(); // parse directories BeatmapSetNode lastNode = null; @@ -215,7 +228,7 @@ public class BeatmapParser { return lastNode; } - public static void parseOnlyTimingPoints(Beatmap map) { + public void parseOnlyTimingPoints(Beatmap map) { if (map == null || map.getFile() == null || !map.getFile().exists()) { return; } @@ -259,7 +272,7 @@ public class BeatmapParser { } } - private static void parseSectionTimingPoints(Beatmap beatmap, String line) { + private void parseSectionTimingPoints(Beatmap beatmap, String line) { TimingPoint timingPoint = new TimingPoint(line); if(!timingPoint.isInherited()) { int bpm = Math.round(60000 / timingPoint.getBeatLength()); @@ -282,7 +295,7 @@ public class BeatmapParser { * @param parseObjects if true, hit objects will be fully parsed now * @return the new beatmap */ - private static Beatmap parseFile(File file, File dir, ArrayList beatmaps, boolean parseObjects) { + private Beatmap parseFile(File file, File dir, ArrayList beatmaps, boolean parseObjects) { Beatmap beatmap = new Beatmap(file); beatmap.timingPoints = new ArrayList(); @@ -523,7 +536,7 @@ public class BeatmapParser { switch (tokens[0]) { case "0": // background tokens[2] = tokens[2].replaceAll("^\"|\"$", ""); - String ext = BeatmapParser.getExtension(tokens[2]); + String ext = Utils.getFileExtension(tokens[2]); if (ext.equals("jpg") || ext.equals("png")) beatmap.bg = new File(dir, getDBString(tokens[2])); break; @@ -767,6 +780,9 @@ public class BeatmapParser { // combo info Color[] combo = beatmap.getComboColors(); + if (combo == null) { + combo = SkinService.skin.getComboColors(); + } int comboIndex = 0; // color index int comboNumber = 1; // combo number @@ -832,7 +848,7 @@ public class BeatmapParser { * Splits line into two strings: tag, value. * If no ':' character is present, null will be returned. */ - private static String[] tokenize(String line) { + private String[] tokenize(String line) { int index = line.indexOf(':'); if (index == -1) { Log.debug(String.format("Failed to tokenize line: '%s'.", line)); @@ -845,19 +861,10 @@ public class BeatmapParser { return tokens; } - /** - * Returns the file extension of a file. - * @param file the file name - */ - public static String getExtension(String file) { - int i = file.lastIndexOf('.'); - return (i != -1) ? file.substring(i + 1).toLowerCase() : ""; - } - /** * Returns the name of the current file being parsed, or null if none. */ - public static String getCurrentFileName() { + public String getCurrentFileName() { if (status == Status.PARSING) return (currentFile != null) ? currentFile.getName() : null; else @@ -868,7 +875,7 @@ public class BeatmapParser { * Returns the progress of file parsing, or -1 if not parsing. * @return the completion percent [0, 100] or -1 */ - public static int getParserProgress() { + public int getParserProgress() { if (currentDirectoryIndex == -1 || totalDirectories == -1) return -1; @@ -878,7 +885,9 @@ public class BeatmapParser { /** * Returns the current parser status. */ - public static Status getStatus() { return status; } + public Status getStatus() { + return status; + } /** * Returns the String object in the database for the given String. @@ -894,4 +903,5 @@ public class BeatmapParser { } else return DBString; } + } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java b/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java index 83e03df9..334b5b06 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java @@ -18,7 +18,6 @@ package itdelatrisu.opsu.beatmap; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.db.BeatmapDB; @@ -210,7 +209,7 @@ public class BeatmapSetList { BeatmapDB.delete(dir.getName()); // delete the associated directory - BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null; + BeatmapWatchService ws = BeatmapWatchService.get(); if (ws != null) ws.pause(); try { @@ -265,9 +264,10 @@ public class BeatmapSetList { BeatmapDB.delete(file.getParentFile().getName(), file.getName()); // delete the associated file - BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null; - if (ws != null) + BeatmapWatchService ws = BeatmapWatchService.get(); + if (ws != null) { ws.pause(); + } try { Utils.deleteToTrash(file); } catch (IOException e) { diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapSetNode.java b/src/itdelatrisu/opsu/beatmap/BeatmapSetNode.java index 1430162a..affb88d4 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapSetNode.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapSetNode.java @@ -20,12 +20,14 @@ package itdelatrisu.opsu.beatmap; import itdelatrisu.opsu.GameData.Grade; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.ui.Colors; import itdelatrisu.opsu.ui.Fonts; import org.newdawn.slick.Color; import org.newdawn.slick.Image; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Node in an BeatmapSetList representing a beatmap set. @@ -78,14 +80,14 @@ public class BeatmapSetNode { Beatmap beatmap = beatmapSet.get(expanded ? beatmapIndex : 0); bg.setAlpha(0.9f); Color bgColor; - Color textColor = Options.getSkin().getSongSelectInactiveTextColor(); + Color textColor = SkinService.skin.getSongSelectInactiveTextColor(); // get drawing parameters if (expanded) { x -= bg.getWidth() / 10f; if (focus) { bgColor = Color.white; - textColor = Options.getSkin().getSongSelectActiveTextColor(); + textColor = SkinService.skin.getSongSelectActiveTextColor(); } else bgColor = Colors.BLUE_BUTTON; } else if (beatmapSet.isPlayed()) @@ -105,7 +107,7 @@ public class BeatmapSetNode { } // draw text - if (Options.useUnicodeMetadata()) { // load glyphs + if (OPTION_SHOW_UNICODE.state) { Fonts.loadGlyphs(Fonts.MEDIUM, beatmap.titleUnicode); Fonts.loadGlyphs(Fonts.DEFAULT, beatmap.artistUnicode); } diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapWatchService.java b/src/itdelatrisu/opsu/beatmap/BeatmapWatchService.java index 064d6ef0..86313d3d 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapWatchService.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapWatchService.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu.beatmap; -import itdelatrisu.opsu.Options; - import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystems; @@ -42,7 +40,11 @@ import java.util.concurrent.Executors; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; + +import static yugecin.opsudance.options.Options.*; /* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. @@ -88,14 +90,14 @@ public class BeatmapWatchService { * Creates a new watch service instance (overwriting any previous instance), * registers the beatmap directory, and starts processing events. */ - public static void create() { + public static void create(InstanceContainer instanceContainer) { // close the existing watch service destroy(); // create a new watch service try { - ws = new BeatmapWatchService(); - ws.register(Options.getBeatmapDir().toPath()); + ws = instanceContainer.provide(BeatmapWatchService.class); + ws.register(instanceContainer.provide(Configuration.class).beatmapDir.toPath()); } catch (IOException e) { Log.error("Could not create watch service", e); EventBus.post(new BubbleNotificationEvent("Could not create watch service", BubbleNotificationEvent.COMMONCOLOR_RED)); @@ -126,9 +128,14 @@ public class BeatmapWatchService { } /** - * Returns the single instance of this class. + * Returns the single instance of this class, or null if not enabled. */ - public static BeatmapWatchService get() { return ws; } + public static BeatmapWatchService get() { + if (!OPTION_ENABLE_WATCH_SERVICE.state) { + return null; + } + return ws; + } /** Watch service listener interface. */ public interface BeatmapWatchServiceListener { diff --git a/src/itdelatrisu/opsu/beatmap/OszUnpacker.java b/src/itdelatrisu/opsu/beatmap/OszUnpacker.java index 02789cbb..c6f3491f 100644 --- a/src/itdelatrisu/opsu/beatmap/OszUnpacker.java +++ b/src/itdelatrisu/opsu/beatmap/OszUnpacker.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu.beatmap; -import itdelatrisu.opsu.Options; - import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; @@ -29,20 +27,27 @@ import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.exception.ZipException; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; /** * Unpacker for OSZ (ZIP) archives. */ public class OszUnpacker { + + @Inject + private Configuration config; + /** The index of the current file being unpacked. */ - private static int fileIndex = -1; + private int fileIndex = -1; /** The total number of files to unpack. */ - private static File[] files; + private File[] files; - // This class should not be instantiated. - private OszUnpacker() {} + @Inject + public OszUnpacker() { + } /** * Invokes the unpacker for each OSZ archive in a root directory. @@ -51,11 +56,11 @@ public class OszUnpacker { * @return an array containing the new (unpacked) directories, or null * if no OSZs found */ - public static File[] unpackAllFiles(File root, File dest) { + public File[] unpackAll() { List dirs = new ArrayList(); // find all OSZ files - files = root.listFiles(new FilenameFilter() { + files = config.oszDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".osz"); @@ -67,13 +72,13 @@ public class OszUnpacker { } // unpack OSZs - BeatmapWatchService ws = (Options.isWatchServiceEnabled()) ? BeatmapWatchService.get() : null; + BeatmapWatchService ws = BeatmapWatchService.get(); if (ws != null) ws.pause(); for (File file : files) { fileIndex++; String dirName = file.getName().substring(0, file.getName().lastIndexOf('.')); - File songDir = new File(dest, dirName); + File songDir = new File(config.beatmapDir, dirName); if (!songDir.isDirectory()) { songDir.mkdir(); unzip(file, songDir); @@ -94,7 +99,7 @@ public class OszUnpacker { * @param file the ZIP archive * @param dest the destination directory */ - private static void unzip(File file, File dest) { + private void unzip(File file, File dest) { try { ZipFile zipFile = new ZipFile(file); zipFile.extractAll(dest.getAbsolutePath()); @@ -108,7 +113,7 @@ public class OszUnpacker { /** * Returns the name of the current file being unpacked, or null if none. */ - public static String getCurrentFileName() { + public String getCurrentFileName() { if (files == null || fileIndex == -1) return null; @@ -119,10 +124,11 @@ public class OszUnpacker { * Returns the progress of file unpacking, or -1 if not unpacking. * @return the completion percent [0, 100] or -1 */ - public static int getUnpackerProgress() { + public int getUnpackerProgress() { if (files == null || fileIndex == -1) return -1; return (fileIndex + 1) * 100 / files.length; } + } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/db/BeatmapDB.java b/src/itdelatrisu/opsu/db/BeatmapDB.java index e6e0e4d5..20f7037f 100644 --- a/src/itdelatrisu/opsu/db/BeatmapDB.java +++ b/src/itdelatrisu/opsu/db/BeatmapDB.java @@ -18,7 +18,6 @@ package itdelatrisu.opsu.db; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.BeatmapParser; @@ -35,11 +34,13 @@ import java.util.Map; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.options.Configuration; /** * Handles connections and queries with the cached beatmap database. */ public class BeatmapDB { + /** * Current database version. * This value should be changed whenever the database format changes. @@ -91,12 +92,16 @@ public class BeatmapDB { // This class should not be instantiated. private BeatmapDB() {} + private static Configuration config; // TODO + /** * Initializes the database connection. */ - public static void init() { + public static void init(Configuration config) { + BeatmapDB.config = config; + // create a database connection - connection = DBController.createConnection(Options.BEATMAP_DB.getPath()); + connection = DBController.createConnection(config.BEATMAP_DB.getPath()); if (connection == null) return; diff --git a/src/itdelatrisu/opsu/db/DBController.java b/src/itdelatrisu/opsu/db/DBController.java index 61e0d5ca..d6223a9e 100644 --- a/src/itdelatrisu/opsu/db/DBController.java +++ b/src/itdelatrisu/opsu/db/DBController.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.db; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.options.Configuration; import java.sql.Connection; import java.sql.DriverManager; @@ -34,7 +35,7 @@ public class DBController { /** * Initializes all databases. */ - public static void init() { + public static void init(Configuration config) { // load the sqlite-JDBC driver using the current class loader try { Class.forName("org.sqlite.JDBC"); @@ -43,8 +44,8 @@ public class DBController { } // initialize the databases - BeatmapDB.init(); - ScoreDB.init(); + BeatmapDB.init(config); + ScoreDB.init(config); } /** diff --git a/src/itdelatrisu/opsu/db/ScoreDB.java b/src/itdelatrisu/opsu/db/ScoreDB.java index 6fa86910..a195a87d 100644 --- a/src/itdelatrisu/opsu/db/ScoreDB.java +++ b/src/itdelatrisu/opsu/db/ScoreDB.java @@ -18,10 +18,10 @@ package itdelatrisu.opsu.db; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.ScoreData; import itdelatrisu.opsu.beatmap.Beatmap; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.options.Configuration; import java.sql.Connection; import java.sql.PreparedStatement; @@ -83,9 +83,9 @@ public class ScoreDB { /** * Initializes the database connection. */ - public static void init() { + public static void init(Configuration config) { // create a database connection - connection = DBController.createConnection(Options.SCORE_DB.getPath()); + connection = DBController.createConnection(config.SCORE_DB.getPath()); if (connection == null) return; diff --git a/src/itdelatrisu/opsu/downloads/DownloadNode.java b/src/itdelatrisu/opsu/downloads/DownloadNode.java index 4261a252..d632a29b 100644 --- a/src/itdelatrisu/opsu/downloads/DownloadNode.java +++ b/src/itdelatrisu/opsu/downloads/DownloadNode.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.downloads; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.BeatmapSetList; import itdelatrisu.opsu.downloads.Download.DownloadListener; @@ -35,13 +34,21 @@ import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.events.BarNotificationEvent; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; + +import static yugecin.opsudance.options.Options.*; /** * Node containing song data and a Download object. */ public class DownloadNode { + + @Inject + private Configuration config; + /** The associated Download object. */ private Download download; @@ -272,7 +279,7 @@ public class DownloadNode { String url = server.getDownloadURL(beatmapSetID); if (url == null) return; - String path = String.format("%s%c%d", Options.getOSZDir(), File.separatorChar, beatmapSetID); + String path = String.format("%s%c%d", config.oszDir, File.separatorChar, beatmapSetID); String rename = String.format("%d %s - %s.osz", beatmapSetID, artist, title); Download download = new Download(url, path, rename); download.setListener(new DownloadListener() { @@ -287,8 +294,9 @@ public class DownloadNode { } }); this.download = download; - if (Options.useUnicodeMetadata()) // load glyphs + if (OPTION_SHOW_UNICODE.state) { Fonts.loadGlyphs(Fonts.LARGE, getTitle()); + } } /** @@ -318,7 +326,7 @@ public class DownloadNode { * If configured, the Unicode string will be returned instead. */ public String getTitle() { - return (Options.useUnicodeMetadata() && titleUnicode != null && !titleUnicode.isEmpty()) ? titleUnicode : title; + return (OPTION_SHOW_UNICODE.state && titleUnicode != null && !titleUnicode.isEmpty()) ? titleUnicode : title; } /** @@ -326,7 +334,7 @@ public class DownloadNode { * If configured, the Unicode string will be returned instead. */ public String getArtist() { - return (Options.useUnicodeMetadata() && artistUnicode != null && !artistUnicode.isEmpty()) ? artistUnicode : artist; + return (OPTION_SHOW_UNICODE.state && artistUnicode != null && !artistUnicode.isEmpty()) ? artistUnicode : artist; } /** @@ -375,7 +383,7 @@ public class DownloadNode { // text // TODO: if the title/artist line is too long, shorten it (e.g. add "...") instead of just clipping - if (Options.useUnicodeMetadata()) { // load glyphs + if (OPTION_SHOW_UNICODE.state) { Fonts.loadGlyphs(Fonts.BOLD, getTitle()); Fonts.loadGlyphs(Fonts.BOLD, getArtist()); } diff --git a/src/itdelatrisu/opsu/downloads/Updater.java b/src/itdelatrisu/opsu/downloads/Updater.java index abe2c18e..96ff228d 100644 --- a/src/itdelatrisu/opsu/downloads/Updater.java +++ b/src/itdelatrisu/opsu/downloads/Updater.java @@ -18,10 +18,8 @@ package itdelatrisu.opsu.downloads; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.downloads.Download.DownloadListener; -import itdelatrisu.opsu.ui.UI; import java.io.File; import java.io.IOException; @@ -39,23 +37,27 @@ import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; import yugecin.opsudance.core.errorhandling.ErrorHandler; import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.events.BarNotificationEvent; +import yugecin.opsudance.options.Configuration; /** * Handles automatic program updates. */ public class Updater { - /** The single instance of this class. */ - private static Updater updater = new Updater(); + + @Inject + private Configuration config; + + private static Updater updater; + + public static Updater get() { + return updater; + } /** The exit confirmation message. */ public static final String EXIT_CONFIRMATION = "An opsu! update is being downloaded.\nAre you sure you want to quit opsu!?"; - /** - * Returns the single instance of this class. - */ - public static Updater get() { return updater; } - /** Updater status. */ public enum Status { INITIAL (""), @@ -117,11 +119,10 @@ public class Updater { return currentVersion.getMajorVersion() + "." + currentVersion.getMinorVersion() + "." + currentVersion.getIncrementalVersion(); } - /** - * Constructor. - */ - private Updater() { + @Inject + public Updater() { status = Status.INITIAL; + updater = this; } /** @@ -144,7 +145,7 @@ public class Updater { Date date = null; try { Properties props = new Properties(); - props.load(ResourceLoader.getResourceAsStream(Options.VERSION_FILE)); + props.load(ResourceLoader.getResourceAsStream(config.VERSION_FILE)); String build = props.getProperty("build.date"); if (build == null || build.equals("${timestamp}") || build.equals("${maven.build.timestamp}")) date = new Date(); @@ -206,23 +207,23 @@ public class Updater { * @throws IOException if an I/O exception occurs */ public void checkForUpdates() throws IOException { - if (status != Status.INITIAL || Options.USE_XDG) + if (status != Status.INITIAL || config.USE_XDG) return; status = Status.CHECKING; // get current version Properties props = new Properties(); - props.load(ResourceLoader.getResourceAsStream(Options.VERSION_FILE)); + props.load(ResourceLoader.getResourceAsStream(config.VERSION_FILE)); if ((currentVersion = getVersion(props)) == null) return; // get latest version String s = null; try { - s = Utils.readDataFromUrl(new URL(Options.VERSION_REMOTE)); + s = Utils.readDataFromUrl(new URL(config.VERSION_REMOTE)); } catch (UnknownHostException e) { - Log.warn(String.format("Check for updates failed. Please check your internet connection, or your connection to %s.", Options.VERSION_REMOTE)); + Log.warn(String.format("Check for updates failed. Please check your internet connection, or your connection to %s.", config.VERSION_REMOTE)); } if (s == null) { status = Status.CONNECTION_ERROR; diff --git a/src/itdelatrisu/opsu/downloads/servers/BloodcatServer.java b/src/itdelatrisu/opsu/downloads/servers/BloodcatServer.java index d86a4579..641360c6 100644 --- a/src/itdelatrisu/opsu/downloads/servers/BloodcatServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/BloodcatServer.java @@ -34,11 +34,17 @@ import java.util.Date; import org.json.JSONArray; import org.json.JSONObject; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; /** * Download server: http://bloodcat.com/osu/ */ public class BloodcatServer extends DownloadServer { + + @Inject + public InstanceContainer instanceContainer; + /** Server name. */ private static final String SERVER_NAME = "Bloodcat"; @@ -54,8 +60,9 @@ public class BloodcatServer extends DownloadServer { /** Total result count from the last query. */ private int totalResults = -1; - /** Constructor. */ - public BloodcatServer() {} + @Inject + public BloodcatServer() { + } @Override public String getName() { return SERVER_NAME; } @@ -82,12 +89,12 @@ public class BloodcatServer extends DownloadServer { nodes = new DownloadNode[arr.length()]; for (int i = 0; i < nodes.length; i++) { JSONObject item = arr.getJSONObject(i); - nodes[i] = new DownloadNode( + nodes[i] = instanceContainer.injectFields(new DownloadNode( item.getInt("id"), formatDate(item.getString("synced")), //"date" item.getString("title"), item.isNull("titleU") ? null : item.getString("titleU"), //"titleUnicode" item.getString("artist"), item.isNull("artistU") ? null : item.getString("artistU"), //"artistUnicode" item.getString("creator") - ); + )); } // store total result count diff --git a/src/itdelatrisu/opsu/downloads/servers/HexideServer.java b/src/itdelatrisu/opsu/downloads/servers/HexideServer.java index f0c6cb7d..a4661510 100644 --- a/src/itdelatrisu/opsu/downloads/servers/HexideServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/HexideServer.java @@ -30,6 +30,8 @@ import java.net.URLEncoder; import org.json.JSONArray; import org.json.JSONObject; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; /** * Download server: https://osu.hexide.com/ @@ -37,6 +39,10 @@ import yugecin.opsudance.core.errorhandling.ErrorHandler; * This server went offline in 2016. */ public class HexideServer extends DownloadServer { + + @Inject + private InstanceContainer instanceContainer; + /** Server name. */ private static final String SERVER_NAME = "Hexide"; @@ -58,8 +64,9 @@ public class HexideServer extends DownloadServer { /** Total result count from the last query. */ private int totalResults = -1; - /** Constructor. */ - public HexideServer() {} + @Inject + public HexideServer() { + } @Override public String getName() { return SERVER_NAME; } @@ -117,10 +124,10 @@ public class HexideServer extends DownloadServer { artist = creator = "?"; } } - nodes[i] = new DownloadNode( + nodes[i] = instanceContainer.injectFields(new DownloadNode( item.getInt("ranked_id"), item.getString("date"), title, null, artist, null, creator - ); + )); } // store total result count diff --git a/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java b/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java index a2e298a4..907b2fda 100644 --- a/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java @@ -30,11 +30,17 @@ import java.net.URLEncoder; import org.json.JSONArray; import org.json.JSONObject; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; /** * Download server: http://osu.mengsky.net/ */ public class MengSkyServer extends DownloadServer { + + @Inject + private InstanceContainer instanceContainer; + /** Server name. */ private static final String SERVER_NAME = "MengSky"; @@ -50,8 +56,9 @@ public class MengSkyServer extends DownloadServer { /** Total result count from the last query. */ private int totalResults = -1; - /** Constructor. */ - public MengSkyServer() {} + @Inject + public MengSkyServer() { + } @Override public String getName() { return SERVER_NAME; } @@ -86,19 +93,18 @@ public class MengSkyServer extends DownloadServer { // sometimes titleU is artistU instead of the proper title if (titleU.equals(artistU) && !titleU.equals(title)) titleU = title; - nodes[i] = new DownloadNode( + nodes[i] = instanceContainer.injectFields(new DownloadNode( item.getInt("id"), item.getString("syncedDateTime"), title, titleU, artist, artistU, creator - ); + )); } // store total result count int pageTotal = json.getInt("pageTotal"); - int resultCount = nodes.length; - if (page == pageTotal) - resultCount = nodes.length + (pageTotal - 1) * PAGE_LIMIT; - else - resultCount = 1 + (pageTotal - 1) * PAGE_LIMIT; + int resultCount = 1 + (pageTotal - 1) * PAGE_LIMIT; + if (page == pageTotal) { + resultCount += nodes.length - 1; + } this.totalResults = resultCount; } catch (MalformedURLException | UnsupportedEncodingException e) { ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show(); diff --git a/src/itdelatrisu/opsu/downloads/servers/MnetworkServer.java b/src/itdelatrisu/opsu/downloads/servers/MnetworkServer.java index e9c2e3c6..e851a35d 100644 --- a/src/itdelatrisu/opsu/downloads/servers/MnetworkServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/MnetworkServer.java @@ -21,6 +21,8 @@ package itdelatrisu.opsu.downloads.servers; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.downloads.DownloadNode; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -36,6 +38,10 @@ import java.util.regex.Pattern; * Download server: http://osu.uu.gl/ */ public class MnetworkServer extends DownloadServer { + + @Inject + private InstanceContainer instanceContainer; + /** Server name. */ private static final String SERVER_NAME = "Mnetwork"; @@ -51,8 +57,9 @@ public class MnetworkServer extends DownloadServer { /** Beatmap pattern. */ private Pattern BEATMAP_PATTERN = Pattern.compile("^(\\d+) ([^-]+) - (.+)\\.osz$"); - /** Constructor. */ - public MnetworkServer() {} + @Inject + public MnetworkServer() { + } @Override public String getName() { return SERVER_NAME; } @@ -112,7 +119,7 @@ public class MnetworkServer extends DownloadServer { if (!m.matches()) continue; - nodeList.add(new DownloadNode(Integer.parseInt(m.group(1)), date, m.group(3), null, m.group(2), null, "")); + nodeList.add(instanceContainer.injectFields(new DownloadNode(Integer.parseInt(m.group(1)), date, m.group(3), null, m.group(2), null, ""))); } nodes = nodeList.toArray(new DownloadNode[nodeList.size()]); diff --git a/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java b/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java index 435eee2f..98d3fef1 100644 --- a/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java @@ -36,6 +36,8 @@ import java.util.TimeZone; import org.json.JSONArray; import org.json.JSONObject; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; /** * Download server: http://loli.al/ @@ -43,6 +45,10 @@ import yugecin.opsudance.core.errorhandling.ErrorHandler; * This server went offline in August 2015. */ public class OsuMirrorServer extends DownloadServer { + + @Inject + private InstanceContainer instanceContainer; + /** Server name. */ private static final String SERVER_NAME = "osu!Mirror"; @@ -67,8 +73,9 @@ public class OsuMirrorServer extends DownloadServer { /** Lookup table from beatmap set ID -> server download ID. */ private HashMap idTable = new HashMap(); - /** Constructor. */ - public OsuMirrorServer() {} + @Inject + public OsuMirrorServer() { + } @Override public String getName() { return SERVER_NAME; } @@ -106,12 +113,12 @@ public class OsuMirrorServer extends DownloadServer { JSONObject item = arr.getJSONObject(i); int beatmapSetID = item.getInt("OSUSetid"); int serverID = item.getInt("id"); - nodes[i] = new DownloadNode( + nodes[i] = instanceContainer.injectFields(new DownloadNode( beatmapSetID, formatDate(item.getString("ModifyDate")), item.getString("Title"), null, item.getString("Artist"), null, item.getString("Mapper") - ); + )); idTable.put(beatmapSetID, serverID); if (serverID > maxServerID) maxServerID = serverID; diff --git a/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java b/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java index 9e13dc58..2ba683b6 100644 --- a/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java @@ -34,11 +34,17 @@ import java.util.List; import org.json.JSONObject; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; /** * Download server: http://osu.yas-online.net/ */ public class YaSOnlineServer extends DownloadServer { + + @Inject + public InstanceContainer instanceContainer; + /** Server name. */ private static final String SERVER_NAME = "YaS Online"; @@ -66,8 +72,9 @@ public class YaSOnlineServer extends DownloadServer { /** Max server download ID seen (for approximating total pages). */ private int maxServerID = 0; - /** Constructor. */ - public YaSOnlineServer() {} + @Inject + public YaSOnlineServer() { + } @Override public String getName() { return SERVER_NAME; } @@ -176,7 +183,7 @@ public class YaSOnlineServer extends DownloadServer { if (serverID > maxServerID) maxServerID = serverID; - nodeList.add(new DownloadNode(item.getInt("mapid"), date, title, null, artist, null, "")); + nodeList.add(instanceContainer.injectFields(new DownloadNode(item.getInt("mapid"), date, title, null, artist, null, ""))); } nodes = nodeList.toArray(new DownloadNode[nodeList.size()]); diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 56d393b2..66f05181 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -20,9 +20,7 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameData; import itdelatrisu.opsu.GameData.HitObjectType; -import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.objects.curves.Vec2f; @@ -30,16 +28,20 @@ import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.ui.Colors; import org.newdawn.slick.Color; -import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; import yugecin.opsudance.Dancer; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.render.GameObjectRenderer; + +import static yugecin.opsudance.options.Options.*; /** * Data type representing a circle object. */ public class Circle extends GameObject { - /** The diameter of hit circles. */ - public static float diameter; + + @Inject + private GameObjectRenderer gameObjectRenderer; /** The associated HitObject. */ private HitObject hitObject; @@ -62,18 +64,6 @@ public class Circle extends GameObject { private int comboColorIndex; - /** - * Initializes the Circle data type with map modifiers, images, and dimensions. - * @param circleDiameter the circle diameter - */ - public static void init(float circleDiameter) { - diameter = circleDiameter * 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)); - } - /** * Constructor. * @param hitObject the associated HitObject @@ -129,16 +119,10 @@ public class Circle extends GameObject { float oldAlpha = Colors.WHITE_FADE.a; Colors.WHITE_FADE.a = color.a = alpha; - if (timeDiff >= 0 && !GameMod.HIDDEN.isActive() && Options.isDrawApproach()) - GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color); - GameImage.HITCIRCLE.getImage().drawCentered(x, y, color); - boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber(); - if (!overlayAboveNumber) - GameImage.HITCIRCLE_OVERLAY.getImage().drawCentered(x, y, Colors.WHITE_FADE); - data.drawSymbolNumber(hitObject.getComboNumber(), x, y, - GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha); - if (overlayAboveNumber) - GameImage.HITCIRCLE_OVERLAY.getImage().drawCentered(x, y, Colors.WHITE_FADE); + if (timeDiff >= 0) { + gameObjectRenderer.renderApproachCircle(x, y, color, approachScale); + } + gameObjectRenderer.renderHitCircle(x, y, color, hitObject.getComboNumber(), alpha); Colors.WHITE_FADE.a = oldAlpha; @@ -172,7 +156,7 @@ public class Circle extends GameObject { @Override public boolean mousePressed(int x, int y, int trackPosition) { double distance = Math.hypot(this.x - x, this.y - y); - if (distance < diameter / 2) { + if (distance < gameObjectRenderer.getCircleDiameter() / 2) { int timeDiff = trackPosition - hitObject.getTime(); int result = hitResult(timeDiff); @@ -195,7 +179,7 @@ public class Circle extends GameObject { if (trackPosition > time + hitResultOffset[GameData.HIT_50]) { if (isAutoMod) {// "auto" mod: catch any missed notes due to lag data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); - if (Options.isMirror() && GameMod.AUTO.isActive()) { + if (OPTION_DANCE_MIRROR.state && GameMod.AUTO.isActive()) { float[] m = Utils.mirrorPoint(x, y); data.sendHitResult(time, GameData.HIT_300, m[0], m[1], mirrorColor, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false, false); } @@ -210,7 +194,7 @@ public class Circle extends GameObject { else if (isAutoMod) { if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) { data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); - if (Options.isMirror() && GameMod.AUTO.isActive()) { + if (OPTION_DANCE_MIRROR.state && GameMod.AUTO.isActive()) { float[] m = Utils.mirrorPoint(x, y); data.sendHitResult(time, GameData.HIT_300, m[0], m[1], mirrorColor, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false, false); } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index f661c523..a55efb6d 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -22,7 +22,6 @@ import itdelatrisu.opsu.GameData; import itdelatrisu.opsu.GameData.HitObjectType; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.HitObject; @@ -37,11 +36,23 @@ import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; import yugecin.opsudance.Dancer; import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.render.GameObjectRenderer; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Data type representing a slider object. */ public class Slider extends GameObject { + + @Inject + private DisplayContainer displayContainer; + + @Inject + private GameObjectRenderer gameObjectRenderer; + /** Slider ball frames. */ private static Image[] sliderBallImages; @@ -54,9 +65,6 @@ public class Slider extends GameObject { /** Follow circle radius. */ private static float followRadius; - /** The diameter of hit circles. */ - private static float diameter; - /** The associated HitObject. */ private HitObject hitObject; @@ -116,9 +124,6 @@ public class Slider extends GameObject { private static final int FOLLOW_EXPAND_TIME = 150; private static final int FOLLOW_SHRINK_TIME = 100; - /** Container dimensions. */ - private static int containerWidth, containerHeight; - private int repeats; private static Color curveColor = new Color(0, 0, 0, 20); @@ -134,14 +139,9 @@ public class Slider extends GameObject { * @param circleDiameter the circle diameter * @param beatmap the associated beatmap */ - public static void init(DisplayContainer displayContainer, float circleDiameter, Beatmap beatmap) { - containerWidth = displayContainer.width; - containerHeight = displayContainer.height; - - diameter = circleDiameter * HitObject.getXMultiplier(); // convert from Osupixels (640x480) - int diameterInt = (int) diameter; - - followRadius = diameter / 2 * 3f; + public static void init(float circleDiameter, Beatmap beatmap) { + followRadius = circleDiameter / 2 * 3f; + int diameterInt = (int) circleDiameter; // slider ball if (GameImage.SLIDER_BALL.hasBeatmapSkinImages() || @@ -216,11 +216,9 @@ public class Slider extends GameObject { double fadeinScale = (timeDiff - approachTime + fadeInTime) / (double) fadeInTime; float alpha = Utils.clamp(1 - (float) fadeinScale, 0, 1); float decorationsAlpha = Utils.clamp(-2.0f * (float) fadeinScale, 0, 1); - boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber(); + boolean overlayAboveNumber = SkinService.skin.isHitCircleOverlayAboveNumber(); float oldAlpha = Colors.WHITE_FADE.a; Colors.WHITE_FADE.a = color.a = alpha; - Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); - Image hitCircle = GameImage.HITCIRCLE.getImage(); Vec2f endPos = curve.pointAt(1); float oldWhiteFadeAlpha = Colors.WHITE_FADE.a; @@ -236,11 +234,11 @@ public class Slider extends GameObject { color.a = alpha; // end circle (only draw if ball still has to go there) - if (Options.isDrawSliderEndCircles() && isCurveCompletelyDrawn && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) { + if (OPTION_DRAW_SLIDER_ENDCIRCLES.state && isCurveCompletelyDrawn && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) { Color circleColor = new Color(color); Color overlayColor = new Color(Colors.WHITE_FADE); if (currentRepeats == 0) { - if (Options.isSliderSnaking()) { + if (OPTION_SNAKING_SLIDERS.state) { // fade in end circle using decorationsAlpha when snaking sliders are enabled circleColor.a = overlayColor.a = sliderAlpha * decorationsAlpha; } @@ -249,8 +247,8 @@ public class Slider extends GameObject { circleColor.a = overlayColor.a = sliderAlpha * getCircleAlphaAfterRepeat(trackPosition, true); } Vec2f endCircPos = curve.pointAt(1f); - hitCircle.drawCentered(endCircPos.x, endCircPos.y, circleColor); - hitCircleOverlay.drawCentered(endCircPos.x, endCircPos.y, overlayColor); + gameObjectRenderer.renderHitCircleOnly(endCircPos.x, endCircPos.y, circleColor); + gameObjectRenderer.renderHitCircleOverlayOnly(endCircPos.x, endCircPos.y, overlayColor); } g.pushTransform(); @@ -267,10 +265,11 @@ public class Slider extends GameObject { } // start circle, only draw if ball still has to go there - if (!sliderClickedInitial || (Options.isDrawSliderEndCircles() && currentRepeats < repeatCount - (repeatCount % 2 == 1 ? 1 : 0))) { - hitCircle.drawCentered(x, y, firstCircleColor); - if (!overlayAboveNumber || sliderClickedInitial) - hitCircleOverlay.drawCentered(x, y, startCircleOverlayColor); + if (!sliderClickedInitial || (OPTION_DRAW_SLIDER_ENDCIRCLES.state && currentRepeats < repeatCount - (repeatCount % 2 == 1 ? 1 : 0))) { + gameObjectRenderer.renderHitCircleOnly(x, y, firstCircleColor); + if (!overlayAboveNumber || sliderClickedInitial) { + gameObjectRenderer.renderHitCircleOverlayOnly(x, y, startCircleOverlayColor); + } } g.popTransform(); @@ -297,12 +296,11 @@ public class Slider extends GameObject { // draw combo number and overlay if not initially clicked if (!sliderClickedInitial) { - data.drawSymbolNumber(hitObject.getComboNumber(), x, y, - hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha); + gameObjectRenderer.renderComboNumberOnly(x, y, hitObject.getComboNumber(), alpha); if (overlayAboveNumber) { startCircleOverlayColor.a = sliderAlpha; - hitCircleOverlay.drawCentered(x, y, startCircleOverlayColor); + gameObjectRenderer.renderHitCircleOverlayOnly(x, y, startCircleOverlayColor); } } @@ -314,7 +312,7 @@ public class Slider extends GameObject { Image arrow = GameImage.REVERSEARROW.getImage(); arrow = arrow.getScaledCopy((float) (1 + 0.2d * ((trackPosition + sliderTime * tcurRepeat) % 292) / 292)); if (tcurRepeat == 0) { - arrow.setAlpha(Options.isSliderSnaking() ? decorationsAlpha : 1f); + arrow.setAlpha(OPTION_SNAKING_SLIDERS.state ? decorationsAlpha : 1f); } else { if (!sliderClickedInitial) { continue; @@ -339,8 +337,8 @@ public class Slider extends GameObject { if (mirror) { g.rotate(x, y, -180f); } - if (!GameMod.HIDDEN.isActive() && Options.isDrawApproach()) { - GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color); + if (!GameMod.HIDDEN.isActive() && OPTION_DANCE_DRAW_APPROACH.state) { + gameObjectRenderer.renderApproachCircle(x, y, color, approachScale); } g.popTransform(); } else { @@ -362,7 +360,7 @@ public class Slider extends GameObject { Image sliderBallFrame = sliderBallImages[(int) (t * sliderTime * 60 / 1000) % sliderBallImages.length]; float angle = (float) (Math.atan2(c2.y - c.y, c2.x - c.x) * 180 / Math.PI); sliderBallFrame.setRotation(angle); - if (Options.getSkin().isAllowSliderBallTint()) { + if (SkinService.skin.isAllowSliderBallTint()) { sliderBallFrame.drawCentered(c.x, c.y, color); } else { sliderBallFrame.drawCentered(c.x, c.y); @@ -373,13 +371,13 @@ public class Slider extends GameObject { float followCircleScale = 1f + (tickExpandTime / (float) TICK_EXPAND_TIME) * 0.1f; float followAlpha = 1f; if (followCircleActive && followExpandTime < FOLLOW_EXPAND_TIME) { - followExpandTime += DisplayContainer.instance.renderDelta; + followExpandTime += displayContainer.renderDelta; followCircleScale *= 0.5f; float progress = AnimationEquation.OUT_QUAD.calc((float) followExpandTime / FOLLOW_EXPAND_TIME); followCircleScale = followCircleScale + followCircleScale * progress; followAlpha = progress; } else if (!followCircleActive) { - followExpandTime -= DisplayContainer.instance.renderDelta; + followExpandTime -= displayContainer.renderDelta; if (followExpandTime > FOLLOW_SHRINK_TIME) { followExpandTime = FOLLOW_SHRINK_TIME; } @@ -394,7 +392,7 @@ public class Slider extends GameObject { float oldAlphaBlack = Colors.BLACK_ALPHA.a; Colors.BLACK_ALPHA.a = 0.75f; g.setColor(Colors.BLACK_ALPHA); - g.fillRect(0, 0, containerWidth, containerHeight); + g.fillRect(0, 0, displayContainer.width, displayContainer.height); Colors.BLACK_ALPHA.a = oldAlphaBlack; } } @@ -459,27 +457,28 @@ public class Slider extends GameObject { } private boolean drawSliderTrack(int trackPosition, double snakingSliderProgress) { - double curveIntervalTo = Options.isSliderSnaking() ? snakingSliderProgress : 1d; + double curveIntervalTo = OPTION_SNAKING_SLIDERS.state ? snakingSliderProgress : 1d; double curveIntervalFrom = 0d; - if (Options.isShrinkingSliders()) { + if (OPTION_SHRINKING_SLIDERS.state) { double sliderprogress = (trackPosition - getTime() - ((double) sliderTime * (repeats - 1))) / (double) sliderTime; if (sliderprogress > 0) { curveIntervalFrom = sliderprogress; } } int curvelen = curve.getCurvePoints().length; - if (Options.isMergingSliders()) { - if (Options.isShrinkingSliders() && curveIntervalFrom > 0) { - if (repeats % 2 == 0) { - game.spliceSliderCurve(baseSliderFrom + (int) ((1d - curveIntervalFrom) * curvelen) - 1, baseSliderFrom + curvelen); + if (!OPTION_FALLBACK_SLIDERS.state && OPTION_MERGING_SLIDERS.state) { + if (OPTION_SHRINKING_SLIDERS.state && curveIntervalFrom > 0) { + if (hitObject.getRepeatCount() % 2 == 0) { + game.addMergedSliderPointsToRender(baseSliderFrom, baseSliderFrom + (int) ((1d - curveIntervalFrom) * curvelen)); } else { - game.setSlidercurveFrom(baseSliderFrom + (int) (curveIntervalFrom * curvelen) + 1); + game.addMergedSliderPointsToRender(baseSliderFrom + (int) (curveIntervalFrom * curvelen) + 1, baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); } + } else { + game.addMergedSliderPointsToRender(baseSliderFrom, baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); } - game.setSlidercurveTo(baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); } else { - if (Options.isShrinkingSliders() && curveIntervalFrom > 0 && repeats % 2 == 0) { - if (Options.isFallbackSliders()) { + if (OPTION_SHRINKING_SLIDERS.state && curveIntervalFrom > 0 && repeats % 2 == 0) { + if (OPTION_FALLBACK_SLIDERS.state) { curveIntervalTo = 1d - curveIntervalFrom; } else { curve.splice((int) ((1d - curveIntervalFrom) * curvelen), curvelen); @@ -590,7 +589,7 @@ public class Slider extends GameObject { data.sendHitResult(hitObject.getTime() + (int) sliderTimeTotal, result, cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd, currentRepeats + 1, curve, sliderHeldToEnd); - if (Options.isMirror() && GameMod.AUTO.isActive()) { + if (OPTION_DANCE_MIRROR.state && GameMod.AUTO.isActive()) { float[] m = Utils.mirrorPoint(cx, cy); data.sendHitResult(hitObject.getTime() + (int) sliderTimeTotal, result, m[0], m[1], mirrorColor, comboEnd, hitObject, type, sliderHeldToEnd, @@ -606,7 +605,7 @@ public class Slider extends GameObject { return false; double distance = Math.hypot(this.x - x, this.y - y); - if (distance < diameter / 2) { + if (distance < gameObjectRenderer.getCircleDiameter() / 2) { int timeDiff = Math.abs(trackPosition - hitObject.getTime()); int[] hitResultOffset = game.getHitResultOffsets(); @@ -691,9 +690,6 @@ public class Slider extends GameObject { // calculate and send slider result hitResult(); - if (Options.isMergingSliders()) { - game.setSlidercurveFrom(baseSliderFrom + curve.getCurvePoints().length + 1); - } return true; } diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index bb3ed811..a5a0bb46 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -22,7 +22,6 @@ import itdelatrisu.opsu.GameData; import itdelatrisu.opsu.GameData.HitObjectType; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; @@ -32,10 +31,10 @@ import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.ui.Colors; import org.newdawn.slick.Color; -import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.skinning.SkinService; /** * Data type representing a spinner object. @@ -189,7 +188,7 @@ public class Spinner extends GameObject { float alpha = Utils.clamp(1 - (float) timeDiff / fadeInTime, 0f, 1f); // darken screen - if (Options.getSkin().isSpinnerFadePlayfield()) { + if (SkinService.skin.isSpinnerFadePlayfield()) { float oldAlpha = Colors.BLACK_ALPHA.a; if (timeDiff > 0) Colors.BLACK_ALPHA.a *= alpha; diff --git a/src/itdelatrisu/opsu/objects/curves/Curve.java b/src/itdelatrisu/opsu/objects/curves/Curve.java index 18f54f62..cd21a525 100644 --- a/src/itdelatrisu/opsu/objects/curves/Curve.java +++ b/src/itdelatrisu/opsu/objects/curves/Curve.java @@ -19,8 +19,6 @@ package itdelatrisu.opsu.objects.curves; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.render.CurveRenderState; import itdelatrisu.opsu.skins.Skin; @@ -31,7 +29,9 @@ import org.lwjgl.opengl.GLContext; import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.util.Log; -import yugecin.opsudance.objects.curves.FakeCombinedCurve; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Representation of a curve. @@ -43,7 +43,7 @@ public abstract class Curve { protected static float CURVE_POINTS_SEPERATION = 2.5f; /** The curve border color. */ - private static Color borderColor; + protected static Color borderColor; /** Whether mmsliders are supported. */ private static boolean mmsliderSupported = false; @@ -58,7 +58,7 @@ public abstract class Curve { protected float[] sliderX, sliderY; /** Per-curve render-state used for the new style curve renders. */ - private CurveRenderState renderState; + protected CurveRenderState renderState; /** Points along the curve (set by inherited classes). */ public Vec2f[] curve; @@ -103,11 +103,10 @@ public abstract class Curve { ContextCapabilities capabilities = GLContext.getCapabilities(); mmsliderSupported = capabilities.OpenGL30; - if (mmsliderSupported) + if (mmsliderSupported) { CurveRenderState.init(width, height, circleDiameter); - else { - if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER) - Log.warn("New slider style requires OpenGL 3.0."); + } else if (SkinService.skin.getSliderStyle() != Skin.STYLE_PEPPYSLIDER) { + Log.warn("New slider style requires OpenGL 3.0."); } } @@ -134,8 +133,8 @@ public abstract class Curve { if (curve == null) return; - // peppysliders - if (Options.isFallbackSliders() || Options.getSkin().getSliderStyle() == Skin.STYLE_PEPPYSLIDER || !mmsliderSupported) { + if (OPTION_FALLBACK_SLIDERS.state || SkinService.skin.getSliderStyle() == Skin.STYLE_PEPPYSLIDER || !mmsliderSupported) { + // peppysliders Image hitCircle = GameImage.HITCIRCLE.getImage(); Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); for (int i = from; i < to; i++) @@ -145,19 +144,17 @@ public abstract class Curve { for (int i = from; i < to; i++) hitCircle.drawCentered(curve[i].x, curve[i].y, fallbackSliderColor); fallbackSliderColor.a = a; - } - - // mmsliders - else { + } else { + // mmsliders if (renderState == null) - renderState = new CurveRenderState(hitObject, curve, this instanceof FakeCombinedCurve); + renderState = new CurveRenderState(hitObject, curve, false); renderState.draw(color, borderColor, from, to); } } public void splice(int from, int to) { if (renderState == null) - renderState = new CurveRenderState(hitObject, curve, this instanceof FakeCombinedCurve); + renderState = new CurveRenderState(hitObject, curve, false); renderState.splice(from, to); } diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java index 9ca3a33e..c6f06308 100644 --- a/src/itdelatrisu/opsu/render/CurveRenderState.java +++ b/src/itdelatrisu/opsu/render/CurveRenderState.java @@ -18,15 +18,15 @@ package itdelatrisu.opsu.render; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; -import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.curves.Vec2f; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.Iterator; +import java.util.List; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.EXTFramebufferObject; @@ -37,6 +37,9 @@ import org.lwjgl.opengl.GL20; import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.util.Log; +import yugecin.opsudance.render.GameObjectRenderer; + +import static yugecin.opsudance.options.Options.*; /** * Hold the temporary render state that needs to be restored again after the new @@ -69,6 +72,8 @@ public class CurveRenderState { private int spliceFrom; private int spliceTo; + protected List pointsToRender; + private final int mirrors; /** @@ -96,7 +101,7 @@ public class CurveRenderState { */ public static void shutdown() { staticState.shutdown(); - //FrameBufferCache.shutdown(); + FrameBufferCache.shutdown(); } /** @@ -108,7 +113,7 @@ public class CurveRenderState { this.hitObject = hitObject; this.curve = curve; if (isKnorkeSlider) { - this.mirrors = Options.getMergingSlidersMirrorPool(); + this.mirrors = OPTION_MERGING_SLIDERS_MIRROR_POOL.val; } else { this.mirrors = 1; } @@ -138,6 +143,14 @@ public class CurveRenderState { lastPointDrawn = -1; // force redraw } + public void draw(Color color, Color borderColor, List pointsToRender) { + lastPointDrawn = -1; + firstPointDrawn = -1; + this.pointsToRender = pointsToRender; + draw(color, borderColor, 0, curve.length); + this.pointsToRender = null; + } + /** * Draw a curve to the screen that's tinted with `color`. The first time * this is called this caches the image result of the curve and on subsequent @@ -303,7 +316,7 @@ public class CurveRenderState { double diff_x = x - last_x; double diff_y = y - last_y; float dist = Utils.distance(x, y, last_x, last_y); - if (dist < Circle.diameter / 8) { + if (dist < GameObjectRenderer.instance.getCircleDiameter() / 8) { x = (float) (x - diff_x / 2); y = (float) (y - diff_y / 2); } else { @@ -344,11 +357,15 @@ public class CurveRenderState { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); } int max = mirrors; - if (!Options.isMirror()) { + if (!OPTION_DANCE_MIRROR.state) { max = 1; } for (int i = 0; i < max; i++) { - renderCurve(from, to, i); + if (pointsToRender == null) { + renderCurve(from, to, i); + } else { + renderCurve(i); + } } GL11.glFlush(); GL20.glDisableVertexAttribArray(staticState.texCoordLoc); @@ -370,6 +387,16 @@ public class CurveRenderState { } } + private void renderCurve(int mirror) { + Iterator iter = pointsToRender.iterator(); + while (iter.hasNext()) { + for (int i = iter.next() * 2, end = iter.next() * 2 - 1; i < end; ++i) { + final int index = i + curve.length * 2 * mirror; + GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, index * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2); + } + } + } + /** * Fill {@code buff} with the texture coordinates and positions for a cone * that has its center at the coordinates {@code (x1,y1)}. diff --git a/src/itdelatrisu/opsu/replay/Replay.java b/src/itdelatrisu/opsu/replay/Replay.java index a74d7dc6..825d60c0 100644 --- a/src/itdelatrisu/opsu/replay/Replay.java +++ b/src/itdelatrisu/opsu/replay/Replay.java @@ -18,7 +18,6 @@ package itdelatrisu.opsu.replay; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.ScoreData; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.Beatmap; @@ -46,7 +45,9 @@ import org.newdawn.slick.util.Log; import lzma.streams.LzmaOutputStream; import yugecin.opsudance.core.errorhandling.ErrorHandler; import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; /** * Captures osu! replay data. @@ -55,6 +56,10 @@ import yugecin.opsudance.events.BubbleNotificationEvent; * @author smoogipooo (https://github.com/smoogipooo/osu-Replay-API/) */ public class Replay { + + @Inject + public Configuration config; + /** The associated file. */ private File file; @@ -272,16 +277,13 @@ public class Replay { */ public void save() { // create replay directory - File dir = Options.getReplayDir(); - if (!dir.isDirectory()) { - if (!dir.mkdir()) { - EventBus.post(new BubbleNotificationEvent("Failed to create replay directory.", BubbleNotificationEvent.COMMONCOLOR_RED)); - return; - } + if (!config.replayDir.isDirectory() && !config.replayDir.mkdir()) { + EventBus.post(new BubbleNotificationEvent("Failed to create replay directory.", BubbleNotificationEvent.COMMONCOLOR_RED)); + return; } // write file in new thread - final File file = new File(dir, String.format("%s.osr", getReplayFilename())); + final File file = new File(config.replayDir, String.format("%s.osr", getReplayFilename())); new Thread() { @Override public void run() { diff --git a/src/itdelatrisu/opsu/replay/ReplayImporter.java b/src/itdelatrisu/opsu/replay/ReplayImporter.java index 775e59ff..120c564f 100644 --- a/src/itdelatrisu/opsu/replay/ReplayImporter.java +++ b/src/itdelatrisu/opsu/replay/ReplayImporter.java @@ -18,7 +18,6 @@ package itdelatrisu.opsu.replay; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.BeatmapSetList; import itdelatrisu.opsu.db.ScoreDB; @@ -31,32 +30,42 @@ import java.nio.file.StandardCopyOption; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.options.Configuration; /** * Importer for replay files. */ public class ReplayImporter { + + @Inject + private InstanceContainer instanceContainer; + + @Inject + private Configuration config; + /** The subdirectory (within the replay import directory) to move replays that could not be imported. */ - private static final String FAILED_IMPORT_DIR = "failed"; + private final String FAILED_IMPORT_DIR = "failed"; /** The index of the current file being imported. */ - private static int fileIndex = -1; + private int fileIndex = -1; /** The total number of replays to import. */ - private static File[] files; + private File[] files; - // This class should not be instantiated. - private ReplayImporter() {} + @Inject + public ReplayImporter() { + } /** - * Invokes the importer for each OSR file in a directory, adding the replay + * Invokes the importer for each OSR file in the replay import dir, adding the replay * to the score database and moving the file into the replay directory. - * @param dir the directory */ - public static void importAllReplaysFromDir(File dir) { + public void importAll() { // find all OSR files - files = dir.listFiles(new FilenameFilter() { + files = config.replayImportDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".osr"); @@ -68,20 +77,17 @@ public class ReplayImporter { } // get replay directory - File replayDir = Options.getReplayDir(); - if (!replayDir.isDirectory()) { - if (!replayDir.mkdir()) { - String err = String.format("Failed to create replay directory '%s'.", replayDir.getAbsolutePath()); - Log.error(err); - EventBus.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED)); - return; - } + if (!config.replayDir.isDirectory() && !config.replayDir.mkdir()) { + String err = String.format("Failed to create replay directory '%s'.", config.replayDir.getAbsolutePath()); + Log.error(err); + EventBus.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED)); + return; } // import OSRs for (File file : files) { fileIndex++; - Replay r = new Replay(file); + Replay r = instanceContainer.injectFields(new Replay(file)); try { r.loadHeader(); } catch (IOException e) { @@ -97,11 +103,11 @@ public class ReplayImporter { ScoreDB.addScore(r.getScoreData(beatmap)); // move to replay directory - File moveToFile = new File(replayDir, String.format("%s.osr", r.getReplayFilename())); + File moveToFile = new File(config.replayDir, String.format("%s.osr", r.getReplayFilename())); try { Files.move(file.toPath(), moveToFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - Log.warn(String.format("Failed to move replay '%s' to the replay directory '%s'.", file, replayDir), e); + Log.warn(String.format("Failed to move replay '%s' to the replay directory '%s'.", file, config.replayDir), e); } } else { moveToFailedDirectory(file); @@ -119,8 +125,8 @@ public class ReplayImporter { * Moves a replay file into the failed import directory. * @param file the file to move */ - private static void moveToFailedDirectory(File file) { - File dir = new File(Options.getReplayImportDir(), FAILED_IMPORT_DIR); + private void moveToFailedDirectory(File file) { + File dir = new File(config.replayImportDir, FAILED_IMPORT_DIR); dir.mkdir(); File moveToFile = new File(dir, file.getName()); try { @@ -133,7 +139,7 @@ public class ReplayImporter { /** * Returns the name of the current file being imported, or null if none. */ - public static String getCurrentFileName() { + public String getCurrentFileName() { if (files == null || fileIndex == -1) return null; @@ -144,10 +150,11 @@ public class ReplayImporter { * Returns the progress of replay importing, or -1 if not importing. * @return the completion percent [0, 100] or -1 */ - public static int getLoadingProgress() { + public int getLoadingProgress() { if (files == null || fileIndex == -1) return -1; return (fileIndex + 1) * 100 / files.length; } + } diff --git a/src/itdelatrisu/opsu/states/ButtonMenu.java b/src/itdelatrisu/opsu/states/ButtonMenu.java index dd0b22a3..77cfc95d 100644 --- a/src/itdelatrisu/opsu/states/ButtonMenu.java +++ b/src/itdelatrisu/opsu/states/ButtonMenu.java @@ -20,9 +20,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.ScoreData; -import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java index 032e3cf9..4003f7f0 100644 --- a/src/itdelatrisu/opsu/states/DownloadsMenu.java +++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; @@ -60,6 +59,7 @@ import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.core.state.ComplexOpsuState; import yugecin.opsudance.events.BarNotificationEvent; +import yugecin.opsudance.options.Configuration; /** * Downloads menu. @@ -72,6 +72,15 @@ public class DownloadsMenu extends ComplexOpsuState { @Inject private InstanceContainer instanceContainer; + @Inject + private Configuration config; + + @Inject + private OszUnpacker oszUnpacker; + + @Inject + private BeatmapParser beatmapParser; + /** Delay time, in milliseconds, between each search. */ private static final int SEARCH_DELAY = 700; @@ -82,12 +91,7 @@ public class DownloadsMenu extends ComplexOpsuState { private static final int MIN_REQUEST_INTERVAL = 300; /** Available beatmap download servers. */ - private static final DownloadServer[] SERVERS = { - new BloodcatServer(), - new YaSOnlineServer(), - new MnetworkServer(), - new MengSkyServer() - }; + private final DownloadServer[] SERVERS; /** The current list of search results. */ private DownloadNode[] resultList; @@ -276,9 +280,9 @@ public class DownloadsMenu extends ComplexOpsuState { /** Imports all packed beatmaps. */ private void importBeatmaps() { // invoke unpacker and parser - File[] dirs = OszUnpacker.unpackAllFiles(Options.getOSZDir(), Options.getBeatmapDir()); + File[] dirs = oszUnpacker.unpackAll(); if (dirs != null && dirs.length > 0) { - this.importedNode = BeatmapParser.parseDirectories(dirs); + this.importedNode = beatmapParser.parseDirectories(dirs); if (importedNode != null) { // send notification EventBus.post(new BarNotificationEvent((dirs.length == 1) ? "Imported 1 new song." : @@ -290,6 +294,16 @@ public class DownloadsMenu extends ComplexOpsuState { } } + @Inject + public DownloadsMenu(InstanceContainer instanceContainer) { + SERVERS = new DownloadServer[] { + instanceContainer.provide(BloodcatServer.class), + instanceContainer.provide(YaSOnlineServer.class), + instanceContainer.provide(MnetworkServer.class), + instanceContainer.provide(MengSkyServer.class), + }; + } + @Override public void revalidate() { super.revalidate(); @@ -665,6 +679,7 @@ public class DownloadsMenu extends ComplexOpsuState { try { previewID = -1; boolean playing = SoundController.playTrack( + config, url, Integer.toString(node.getID()), true, diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 4c11abfd..86e76295 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -18,12 +18,7 @@ package itdelatrisu.opsu.states; -import itdelatrisu.opsu.GameData; -import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.ScoreData; -import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.*; import itdelatrisu.opsu.audio.HitSound; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; @@ -34,7 +29,6 @@ import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.beatmap.TimingPoint; import itdelatrisu.opsu.db.BeatmapDB; import itdelatrisu.opsu.db.ScoreDB; -import itdelatrisu.opsu.downloads.Updater; import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.DummyObject; import itdelatrisu.opsu.objects.GameObject; @@ -76,11 +70,17 @@ import yugecin.opsudance.core.state.transitions.FadeOutTransitionState; import yugecin.opsudance.events.BarNotificationEvent; import yugecin.opsudance.events.BubbleNotificationEvent; import yugecin.opsudance.objects.curves.FakeCombinedCurve; +import yugecin.opsudance.options.OptionGroups; +import yugecin.opsudance.options.Options; +import yugecin.opsudance.render.GameObjectRenderer; import yugecin.opsudance.sbv2.MoveStoryboard; +import yugecin.opsudance.skinning.SkinService; import yugecin.opsudance.ui.OptionsOverlay; import yugecin.opsudance.ui.StoryboardOverlay; import yugecin.opsudance.utils.GLHelper; +import static yugecin.opsudance.options.Options.*; + /** * "Game" state. */ @@ -89,6 +89,12 @@ public class Game extends ComplexOpsuState { @Inject private InstanceContainer instanceContainer; + @Inject + private GameObjectRenderer gameObjectRenderer; + + @Inject + private BeatmapParser beatmapParser; + public static boolean isInGame; // TODO delete this when #79 is fixed /** Game restart states. */ public enum Restart { @@ -330,7 +336,7 @@ public class Game extends ComplexOpsuState { super(); mirrorCursor = new Cursor(true); this.moveStoryboardOverlay = new MoveStoryboard(displayContainer); - this.optionsOverlay = new OptionsOverlay(displayContainer, OptionsMenu.storyboardOptions); + this.optionsOverlay = new OptionsOverlay(displayContainer, OptionGroups.storyboardOptions); this.storyboardOverlay = new StoryboardOverlay(displayContainer, moveStoryboardOverlay, optionsOverlay, this); storyboardOverlay.show(); moveStoryboardOverlay.show(); @@ -364,7 +370,8 @@ public class Game extends ComplexOpsuState { scoreboardStarStream.setDurationSpread(700, 100); // create the associated GameData object - data = new GameData(displayContainer.width, displayContainer.height); + data = instanceContainer.injectFields(new GameData(displayContainer.width, displayContainer.height)); + gameObjectRenderer.setGameData(data); } @@ -386,17 +393,6 @@ public class Game extends ComplexOpsuState { if (objectIndex > 0) { objectIndex--; } - if (Options.isMergingSliders()) { - int obj = objectIndex; - while (obj < gameObjects.length) { - if (gameObjects[obj] instanceof Slider) { - slidercurveFrom = slidercurveTo = ((Slider) gameObjects[obj]).baseSliderFrom; - break; - } - obj++; - } - spliceSliderCurve(-1, -1); - } Dancer.instance.setObjectIndex(objectIndex); storyboardOverlay.updateIndex(objectIndex); lastReplayTime = beatmap.objects[objectIndex].getTime(); @@ -409,7 +405,7 @@ public class Game extends ComplexOpsuState { int trackPosition = MusicController.getPosition(); if (isLeadIn()) { - trackPosition -= leadInTime - currentMapMusicOffset - Options.getMusicOffset(); + trackPosition -= leadInTime - currentMapMusicOffset - OPTION_MUSIC_OFFSET.val; } if (pauseTime > -1) // returning from pause screen trackPosition = pauseTime; @@ -427,15 +423,15 @@ public class Game extends ComplexOpsuState { } // background - if (!Options.isRemoveBG() && GameMod.AUTO.isActive()) { - float dimLevel = Options.getBackgroundDim(); + if (!OPTION_DANCE_REMOVE_BG.state && GameMod.AUTO.isActive()) { + float dimLevel = (100 - OPTION_BACKGROUND_DIM.val) / 100f; if (trackPosition < firstObjectTime) { if (timeDiff < approachTime) dimLevel += (1f - dimLevel) * ((float) timeDiff / approachTime); else dimLevel = 1f; } - if (Options.isDefaultPlayfieldForced() || !beatmap.drawBackground(width, height, dimLevel, false)) { + if (OPTION_FORCE_DEFAULT_PLAYFIELD.state || !beatmap.drawBackground(width, height, dimLevel, false)) { Image playfield = GameImage.PLAYFIELD.getImage(); playfield.setAlpha(dimLevel); playfield.draw(); @@ -462,7 +458,7 @@ public class Game extends ComplexOpsuState { if (GameMod.FLASHLIGHT.isActive()) { // render hit objects offscreen Graphics.setCurrent(gOffscreen); - int trackPos = (isLeadIn()) ? (leadInTime - Options.getMusicOffset()) * -1 : trackPosition; + int trackPos = (isLeadIn()) ? (leadInTime - OPTION_MUSIC_OFFSET.val) * -1 : trackPosition; drawHitObjects(gOffscreen, trackPos); // restore original graphics context @@ -519,7 +515,7 @@ public class Game extends ComplexOpsuState { Colors.BLACK_ALPHA.a = a; } - if (!Options.isHideUI() || !GameMod.AUTO.isActive()) { + if (!OPTION_DANCE_HIDE_UI.state || !GameMod.AUTO.isActive()) { data.drawGameElements(g, true, objectIndex == 0, 1f); } @@ -557,7 +553,7 @@ public class Game extends ComplexOpsuState { // non-break else { - if (!GameMod.AUTO.isActive() || !Options.isHideUI()) { + if (!GameMod.AUTO.isActive() || !OPTION_DANCE_HIDE_UI.state) { // game elements float gameElementAlpha = 1f; if (gameFinished) { @@ -590,7 +586,7 @@ public class Game extends ComplexOpsuState { } if (isLeadIn()) - trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in + trackPosition = (leadInTime - OPTION_MUSIC_OFFSET.val) * -1; // render approach circles during song lead-in // countdown if (beatmap.countdown > 0) { @@ -691,15 +687,15 @@ public class Game extends ComplexOpsuState { } } - if (!Options.isHideUI() && GameMod.AUTO.isActive()) + if (!OPTION_DANCE_HIDE_UI.state && GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); // draw replay speed button - if (isReplay || (!Options.isHideUI()&& GameMod.AUTO.isActive())) + if (isReplay || (!OPTION_DANCE_HIDE_UI.state&& GameMod.AUTO.isActive())) playbackSpeed.getButton().draw(); // draw music position bar (for replay seeking) - if (isReplay && Options.isReplaySeekingEnabled()) { + if (isReplay && OPTION_REPLAY_SEEKING.state) { g.setColor((musicPositionBarContains(displayContainer.mouseX, displayContainer.mouseY)) ? MUSICBAR_HOVER : MUSICBAR_NORMAL); g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4); if (!isLeadIn()) { @@ -729,7 +725,7 @@ public class Game extends ComplexOpsuState { displayContainer.cursor.draw(replayKeyPressed); } else if (GameMod.AUTO.isActive()) { displayContainer.cursor.draw(autoMousePressed); - if (Options.isMirror() && GameMod.AUTO.isActive()) { + if (OPTION_DANCE_MIRROR.state && GameMod.AUTO.isActive()) { mirrorCursor.draw(autoMousePressed); } } else if (GameMod.AUTOPILOT.isActive()) { @@ -876,7 +872,7 @@ public class Game extends ComplexOpsuState { } // update in-game scoreboard - if (!Options.isHideUI() && previousScores != null && trackPosition > firstObjectTime) { + if (!OPTION_DANCE_HIDE_UI.state && previousScores != null && trackPosition > firstObjectTime) { // show scoreboard if selected, and always in break // hide when game ends if ((scoreboardVisible || breakTime > 0) && !gameFinished) { @@ -945,7 +941,7 @@ public class Game extends ComplexOpsuState { displayContainer.cursor.setCursorPosition(displayContainer.delta, replayX, replayY); } else if (GameMod.AUTO.isActive()) { displayContainer.cursor.setCursorPosition(displayContainer.delta, (int) autoMousePosition.x, (int) autoMousePosition.y); - if (Options.isMirror() && GameMod.AUTO.isActive()) { + if (OPTION_DANCE_MIRROR.state && GameMod.AUTO.isActive()) { double dx = autoMousePosition.x - Options.width / 2d; double dy = autoMousePosition.y - Options.height / 2d; double d = Math.sqrt(dx * dx + dy * dy); @@ -970,7 +966,7 @@ public class Game extends ComplexOpsuState { boolean complete = objectIndex >= gameObjects.length; if (GameMod.AUTO.isActive() && complete) { if (gameObjects.length > 0) { - complete = trackPosition >= gameObjects[gameObjects.length - 1].getEndTime() + Options.getMapEndDelay(); + complete = trackPosition >= gameObjects[gameObjects.length - 1].getEndTime() + OPTION_MAP_END_DELAY.val * 100; } } if (complete || (MusicController.trackEnded() && objectIndex > 0)) { @@ -1115,7 +1111,9 @@ public class Game extends ComplexOpsuState { objectIndex++; // done, so increment object index storyboardOverlay.updateIndex(objectIndex); if (objectIndex >= mirrorTo) { - Options.setMirror(false); + if (OPTION_DANCE_MIRROR.state) { + OPTION_DANCE_MIRROR.toggle(); + } } } else break; @@ -1147,12 +1145,14 @@ public class Game extends ComplexOpsuState { // game keys if (!Keyboard.isRepeatEvent()) { int keys = ReplayFrame.KEY_NONE; - if (key == Options.getGameKeyLeft()) + if (key == OPTION_KEY_LEFT.intval) { keys = ReplayFrame.KEY_K1; - else if (key == Options.getGameKeyRight()) + } else if (key == OPTION_KEY_RIGHT.intval) { keys = ReplayFrame.KEY_K2; - if (keys != ReplayFrame.KEY_NONE) + } + if (keys != ReplayFrame.KEY_NONE) { gameKeyPressed(keys, mouseX, mouseY, trackPosition); + } } switch (key) { @@ -1195,8 +1195,10 @@ public class Game extends ComplexOpsuState { break; } - int position = (pauseTime > -1) ? pauseTime : trackPosition; - if (Options.setCheckpoint(position / 1000)) { + int time = (pauseTime > -1) ? pauseTime : trackPosition; + time /= 1000; + if (0 <= time && time < 3600) { + OPTION_CHECKPOINT.setValue(time); SoundController.playSound(SoundEffect.MENUCLICK); EventBus.post(new BarNotificationEvent("Checkpoint saved.")); } @@ -1205,7 +1207,7 @@ public class Game extends ComplexOpsuState { case Input.KEY_L: // load checkpoint if (displayContainer.input.isKeyDown(Input.KEY_RCONTROL) || displayContainer.input.isKeyDown(Input.KEY_LCONTROL)) { - int checkpoint = Options.getCheckpoint(); + int checkpoint = OPTION_CHECKPOINT.val * 1000; if (checkpoint == 0 || checkpoint > beatmap.endTime) break; // invalid checkpoint loadCheckpoint(checkpoint); @@ -1227,31 +1229,29 @@ public class Game extends ComplexOpsuState { UI.changeVolume(-1); break; case Input.KEY_TAB: - if (!Options.isHideUI()) { + if (!OPTION_DANCE_HIDE_UI.state) { scoreboardVisible = !scoreboardVisible; } break; case Input.KEY_M: - if (Options.isMirror()) { + if (OPTION_DANCE_MIRROR.state) { mirrorTo = objectIndex; - Options.setMirror(false); } else { mirrorCursor.resetLocations((int) autoMousePosition.x, (int) autoMousePosition.y); mirrorFrom = objectIndex; mirrorTo = gameObjects.length; - Options.setMirror(true); } + OPTION_DANCE_MIRROR.toggle(); break; case Input.KEY_P: - if (Options.isMirror()) { + if (OPTION_DANCE_MIRROR.state) { mirrorTo = objectIndex; - Options.setMirror(false); } else { mirrorCursor.resetLocations((int) autoMousePosition.x, (int) autoMousePosition.y); mirrorFrom = objectIndex; mirrorTo = mirrorFrom + 1; - Options.setMirror(true); } + OPTION_DANCE_MIRROR.toggle(); break; case Input.KEY_MINUS: currentMapMusicOffset += 5; @@ -1301,7 +1301,7 @@ public class Game extends ComplexOpsuState { } // replay seeking - else if (Options.isReplaySeekingEnabled() && !GameMod.AUTO.isActive() && musicPositionBarContains(x, y)) { + else if (OPTION_REPLAY_SEEKING.state && !GameMod.AUTO.isActive() && musicPositionBarContains(x, y)) { SoundController.mute(true); // mute sounds while seeking float pos = (y - musicBarY) / musicBarHeight * beatmap.endTime; MusicController.setPosition((int) pos); @@ -1310,12 +1310,12 @@ public class Game extends ComplexOpsuState { return true; } - if (Options.isMouseDisabled()) { + if (OPTION_DISABLE_MOUSE_BUTTONS.state) { return true; } // mouse wheel: pause the game - if (button == Input.MOUSE_MIDDLE_BUTTON && !Options.isMouseWheelDisabled()) { + if (button == Input.MOUSE_MIDDLE_BUTTON && !OPTION_DISABLE_MOUSE_WHEEL.state) { int trackPosition = MusicController.getPosition(); if (pauseTime < 0 && breakTime <= 0 && trackPosition >= beatmap.objects[0].getTime()) { pausedMousePosition = new Vec2f(x, y); @@ -1389,7 +1389,7 @@ public class Game extends ComplexOpsuState { return true; } - if (Options.isMouseDisabled()) { + if (OPTION_DISABLE_MOUSE_BUTTONS.state) { return true; } @@ -1419,12 +1419,14 @@ public class Game extends ComplexOpsuState { } int keys = ReplayFrame.KEY_NONE; - if (key == Options.getGameKeyLeft()) + if (key == OPTION_KEY_LEFT.intval) { keys = ReplayFrame.KEY_K1; - else if (key == Options.getGameKeyRight()) + } else if (key == OPTION_KEY_RIGHT.intval) { keys = ReplayFrame.KEY_K2; - if (keys != ReplayFrame.KEY_NONE) + } + if (keys != ReplayFrame.KEY_NONE) { gameKeyReleased(keys, displayContainer.input.getMouseX(), displayContainer.input.getMouseY(), MusicController.getPosition()); + } return true; } @@ -1449,7 +1451,7 @@ public class Game extends ComplexOpsuState { return true; } - if (Options.isMouseWheelDisabled()) { + if (OPTION_DISABLE_MOUSE_WHEEL.state) { return true; } @@ -1461,7 +1463,7 @@ public class Game extends ComplexOpsuState { @Override public void enter() { overlays.clear(); - if (Options.isEnableSB()) { + if (OPTION_DANCE_ENABLE_SB.state) { overlays.add(optionsOverlay); overlays.add(moveStoryboardOverlay); overlays.add(storyboardOverlay); @@ -1537,7 +1539,7 @@ public class Game extends ComplexOpsuState { } // load epilepsy warning img - epiImgTime = Options.getEpilepsyWarningLength(); + epiImgTime = OPTION_EPILEPSY_WARNING.val * 100; if (epiImgTime > 0) { epiImg = GameImage.EPILEPSY_WARNING.getImage(); float desWidth = displayContainer.width / 2; @@ -1584,7 +1586,11 @@ public class Game extends ComplexOpsuState { } // initialize object maps - CursorColorOverrides.comboColors = ObjectColorOverrides.comboColors = beatmap.getComboColors(); + Color[] comboColors = beatmap.getComboColors(); + if (comboColors == null) { + comboColors = SkinService.skin.getComboColors(); + } + CursorColorOverrides.comboColors = ObjectColorOverrides.comboColors = comboColors; for (int i = 0; i < beatmap.objects.length; i++) { HitObject hitObject = beatmap.objects[i]; @@ -1604,12 +1610,13 @@ public class Game extends ComplexOpsuState { } try { - if (hitObject.isCircle()) - gameObjects[i] = new Circle(hitObject, this, data, hitObject.getComboIndex(), comboEnd); - else if (hitObject.isSlider()) - gameObjects[i] = new Slider(hitObject, this, data, hitObject.getComboIndex(), comboEnd); - else if (hitObject.isSpinner()) + if (hitObject.isCircle()) { + gameObjects[i] = instanceContainer.injectFields(new Circle(hitObject, this, data, hitObject.getComboIndex(), comboEnd)); + } else if (hitObject.isSlider()) { + gameObjects[i] = instanceContainer.injectFields(new Slider(hitObject, this, data, hitObject.getComboIndex(), comboEnd)); + } else if (hitObject.isSpinner()) { gameObjects[i] = new Spinner(hitObject, this, data); + } } catch (Exception e) { String message = String.format("Failed to create %s at index %d:\n%s", hitObject.getTypeName(), i, hitObject.toString()); Log.error(message, e); @@ -1688,7 +1695,7 @@ public class Game extends ComplexOpsuState { MusicController.pause(); if (gameObjects.length > 0) { - int leadIntime = Options.getMapStartDelay() - gameObjects[0].getTime(); + int leadIntime = OPTION_MAP_START_DELAY.val * 100 - gameObjects[0].getTime(); if (leadIntime > 0) { this.leadInTime = Math.max(leadIntime, this.leadInTime); } @@ -1696,7 +1703,10 @@ public class Game extends ComplexOpsuState { this.leadInTime += epiImgTime; SoundController.mute(false); - if (Options.isMergingSliders()) { + if (!OPTION_FALLBACK_SLIDERS.state && OPTION_MERGING_SLIDERS.state) { + if (!OPTION_SHRINKING_SLIDERS.state) { + knorkesliders = null; // workaround for issue-130 + } if (knorkesliders == null) { // let's create knorkesliders List curvepoints = new ArrayList<>(); @@ -1721,9 +1731,6 @@ public class Game extends ComplexOpsuState { } } - slidercurveFrom = 0; - slidercurveTo = 0; - Dancer.instance.setGameObjects(gameObjects); storyboardOverlay.setGameObjects(gameObjects); if (!skippedToCheckpoint) { @@ -1752,7 +1759,7 @@ public class Game extends ComplexOpsuState { MusicController.setPitch(1f); MusicController.resume(); - if (Options.isEnableSB()) { + if (OPTION_DANCE_ENABLE_SB.state) { storyboardOverlay.onLeave(); } @@ -1779,19 +1786,8 @@ public class Game extends ComplexOpsuState { GameMod.loadModState(previousMods); } - private int slidercurveFrom; - private int slidercurveTo; - - public void setSlidercurveFrom(int slidercurveFrom) { - this.slidercurveFrom = Math.max(slidercurveFrom, this.slidercurveFrom); - } - - public void setSlidercurveTo(int slidercurveTo) { - this.slidercurveTo = Math.max(slidercurveTo, this.slidercurveTo); - } - - public void spliceSliderCurve(int from, int to) { - this.knorkesliders.splice(from, to); + public void addMergedSliderPointsToRender(int from, int to) { + knorkesliders.addRange(from, to); } /** @@ -1800,15 +1796,18 @@ public class Game extends ComplexOpsuState { * @param trackPosition the track position */ private void drawHitObjects(Graphics g, int trackPosition) { + gameObjectRenderer.initForFrame(); + // draw result objects - if (!Options.isHideObjects()) { + if (!OPTION_DANCE_HIDE_OBJECTS.state) { data.drawHitResults(trackPosition); } - if (Options.isMergingSliders() && knorkesliders != null) { - knorkesliders.draw(Color.white, this.slidercurveFrom, this.slidercurveTo); + if (!OPTION_FALLBACK_SLIDERS.state && OPTION_MERGING_SLIDERS.state && knorkesliders != null) { + knorkesliders.draw(Color.white); + knorkesliders.initForFrame(); /* - if (Options.isMirror()) { + if (OPTION_DANCE_MIRROR.state) { g.pushTransform(); g.rotate(Options.width / 2f, Options.height / 2f, 180f); knorkesliders.draw(Color.white, this.slidercurveFrom, this.slidercurveTo); @@ -1833,7 +1832,7 @@ public class Game extends ComplexOpsuState { stack.add(index); // draw follow points - if (!Options.isFollowPointEnabled() || loseState || !Options.isHideObjects()) + if (!OPTION_SHOW_FOLLOW_POINTS.state || loseState || !OPTION_DANCE_HIDE_OBJECTS.state) continue; if (beatmap.objects[index].isSpinner()) { lastObjectIndex = -1; @@ -1898,9 +1897,9 @@ public class Game extends ComplexOpsuState { // normal case if (!loseState) { - if (!Options.isHideObjects()) { + if (!OPTION_DANCE_HIDE_OBJECTS.state) { gameObj.draw(g, trackPosition, false); - if (Options.isMirror() && GameMod.AUTO.isActive() && idx < mirrorTo && idx >= mirrorFrom) { + if (OPTION_DANCE_MIRROR.state && GameMod.AUTO.isActive() && idx < mirrorTo && idx >= mirrorFrom) { g.pushTransform(); g.rotate(Options.width / 2f, Options.height / 2f, 180f); gameObj.draw(g, trackPosition, true); @@ -1950,9 +1949,10 @@ public class Game extends ComplexOpsuState { } this.beatmap = beatmap; Display.setTitle(String.format("opsu!dance - %s", beatmap.toString())); - if (beatmap.breaks == null) + if (beatmap.breaks == null) { BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY); - BeatmapParser.parseHitObjects(beatmap); + } + beatmapParser.parseHitObjects(beatmap); HitSound.setDefaultSampleSet(beatmap.sampleSet); } @@ -2053,22 +2053,26 @@ public class Game extends ComplexOpsuState { * Set map modifiers. */ private void setMapModifiers() { + // fixed difficulty overrides + float circleSize = OPTION_FIXED_CS.val / 10f; + float approachRate = OPTION_FIXED_AR.val / 10f; + float overallDifficulty = OPTION_FIXED_OD.val / 10f; + float HPDrainRate = OPTION_FIXED_HP.val / 10f; + // map-based properties, re-initialized each game float multiplier = GameMod.getDifficultyMultiplier(); - float circleSize = Math.min(beatmap.circleSize * multiplier, 10f); - float approachRate = Math.min(beatmap.approachRate * multiplier, 10f); - float overallDifficulty = Math.min(beatmap.overallDifficulty * multiplier, 10f); - float HPDrainRate = Math.min(beatmap.HPDrainRate * multiplier, 10f); - - // fixed difficulty overrides - if (Options.getFixedCS() > 0f) - circleSize = Options.getFixedCS(); - if (Options.getFixedAR() > 0f) - approachRate = Options.getFixedAR(); - if (Options.getFixedOD() > 0f) - overallDifficulty = Options.getFixedOD(); - if (Options.getFixedHP() > 0f) - HPDrainRate = Options.getFixedHP(); + if (circleSize == 0f) { + circleSize = Math.min(beatmap.circleSize * multiplier, 10f); + } + if (approachRate == 0f) { + approachRate = Math.min(beatmap.approachRate * multiplier, 10f); + } + if (overallDifficulty == 0f) { + overallDifficulty = Math.min(beatmap.overallDifficulty * multiplier, 10f); + } + if (HPDrainRate == 0f) { + HPDrainRate = Math.min(beatmap.HPDrainRate * multiplier, 10f); + } // Stack modifier scales with hit object size // StackOffset = HitObjectRadius / 10 @@ -2077,11 +2081,14 @@ public class Game extends ComplexOpsuState { HitObject.setStackOffset(diameter * STACK_OFFSET_MODIFIER); // initialize objects - Circle.init(diameter); - Slider.init(displayContainer, diameter, beatmap); + gameObjectRenderer.initForGame(data, diameter); + Slider.init(gameObjectRenderer.getCircleDiameter(), beatmap); Spinner.init(displayContainer, overallDifficulty); - Curve.init(displayContainer.width, displayContainer.height, diameter, (Options.isBeatmapSkinIgnored()) ? - Options.getSkin().getSliderBorderColor() : beatmap.getSliderBorderColor()); + Color sliderBorderColor = SkinService.skin.getSliderBorderColor(); + if (!OPTION_IGNORE_BEATMAP_SKINS.state && beatmap.getSliderBorderColor() != null) { + sliderBorderColor = beatmap.getSliderBorderColor(); + } + Curve.init(displayContainer.width, displayContainer.height, diameter, sliderBorderColor); // approachRate (hit object approach time) if (approachRate < 5) diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index 95c7e933..0c8bb9bd 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; @@ -35,6 +34,8 @@ import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.core.state.BaseOpsuState; +import static yugecin.opsudance.options.Options.*; + /** * "Game Pause/Fail" state. *

@@ -93,9 +94,9 @@ public class GamePauseMenu extends BaseOpsuState { // game keys if (!Keyboard.isRepeatEvent()) { - if (key == Options.getGameKeyLeft()) { + if (key == OPTION_KEY_LEFT.intval) { mousePressed(Input.MOUSE_LEFT_BUTTON, displayContainer.mouseX, displayContainer.mouseY); - } else if (key == Options.getGameKeyRight()) { + } else if (key == OPTION_KEY_RIGHT.intval) { mousePressed(Input.MOUSE_RIGHT_BUTTON, displayContainer.mouseX, displayContainer.mouseY); } } @@ -166,7 +167,7 @@ public class GamePauseMenu extends BaseOpsuState { return true; } - if (Options.isMouseWheelDisabled()) { + if (OPTION_DISABLE_MOUSE_WHEEL.state) { return true; } diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index f13beeeb..0411ba5d 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; @@ -54,6 +53,8 @@ import yugecin.opsudance.core.state.OpsuState; import yugecin.opsudance.events.BarNotificationEvent; import yugecin.opsudance.events.BubbleNotificationEvent; +import static yugecin.opsudance.options.Options.*; + /** * "Main Menu" state. *

@@ -64,6 +65,9 @@ public class MainMenu extends BaseOpsuState { @Inject private InstanceContainer instanceContainer; + @Inject + private Updater updater; + /** Idle time, in milliseconds, before returning the logo to its original position. */ private static final short LOGO_IDLE_DELAY = 10000; @@ -232,9 +236,9 @@ public class MainMenu extends BaseOpsuState { // draw background Beatmap beatmap = MusicController.getBeatmap(); - if (Options.isDynamicBackgroundEnabled() && + if (OPTION_DYNAMIC_BACKGROUND.state && beatmap != null && beatmap.drawBackground(width, height, bgAlpha.getValue(), true)) - ; + ; else { Image bg = GameImage.MENU_BG.getImage(); bg.setAlpha(bgAlpha.getValue()); @@ -262,7 +266,7 @@ public class MainMenu extends BaseOpsuState { } // draw logo (pulsing) - Color color = Options.isColorMainMenuLogo() ? Cursor.lastCursorColor : Color.white; + Color color = OPTION_COLOR_MAIN_MENU_LOGO.state ? Cursor.lastCursorColor : Color.white; Float position = MusicController.getBeatProgress(); boolean renderPiece = position != null; if (position == null) { @@ -315,12 +319,13 @@ public class MainMenu extends BaseOpsuState { } // draw update button - if (Updater.get().showButton()) { - Updater.Status status = Updater.get().getStatus(); - if (status == Updater.Status.UPDATE_AVAILABLE || status == Updater.Status.UPDATE_DOWNLOADING) + if (updater.showButton()) { + Updater.Status status = updater.getStatus(); + if (status == Updater.Status.UPDATE_AVAILABLE || status == Updater.Status.UPDATE_DOWNLOADING) { updateButton.draw(); - else if (status == Updater.Status.UPDATE_DOWNLOADED) + } else if (status == Updater.Status.UPDATE_DOWNLOADED) { restartButton.draw(); + } } // draw text @@ -328,11 +333,11 @@ public class MainMenu extends BaseOpsuState { g.setFont(Fonts.MEDIUM); float lineHeight = Fonts.MEDIUM.getLineHeight() * 0.925f; g.drawString(String.format("Loaded %d songs and %d beatmaps.", - BeatmapSetList.get().getMapSetCount(), BeatmapSetList.get().getMapCount()), marginX, topMarginY); - if (MusicController.isTrackLoading()) + BeatmapSetList.get().getMapSetCount(), BeatmapSetList.get().getMapCount()), marginX, topMarginY); + if (MusicController.isTrackLoading()) { g.drawString("Track loading...", marginX, topMarginY + lineHeight); - else if (MusicController.trackExists()) { - if (Options.useUnicodeMetadata()) { // load glyphs + } else if (MusicController.trackExists()) { + if (OPTION_SHOW_UNICODE.state) { Fonts.loadGlyphs(Fonts.MEDIUM, beatmap.titleUnicode); Fonts.loadGlyphs(Fonts.MEDIUM, beatmap.artistUnicode); } @@ -365,7 +370,7 @@ public class MainMenu extends BaseOpsuState { repoButton.hoverUpdate(delta, mouseX, mouseY); danceRepoButton.hoverUpdate(delta, mouseX, mouseY); } - if (Updater.get().showButton()) { + if (updater.showButton()) { updateButton.autoHoverUpdate(delta, true); restartButton.autoHoverUpdate(delta, false); } @@ -387,7 +392,7 @@ public class MainMenu extends BaseOpsuState { // fade in background Beatmap beatmap = MusicController.getBeatmap(); - if (!(Options.isDynamicBackgroundEnabled() && beatmap != null && beatmap.isBackgroundLoading())) + if (!(OPTION_DYNAMIC_BACKGROUND.state && beatmap != null && beatmap.isBackgroundLoading())) bgAlpha.update(delta); // check measure progress @@ -445,8 +450,8 @@ public class MainMenu extends BaseOpsuState { UI.updateTooltip(delta, "Next track", false); else if (musicPrevious.contains(mouseX, mouseY)) UI.updateTooltip(delta, "Previous track", false); - else if (Updater.get().showButton()) { - Updater.Status status = Updater.get().getStatus(); + else if (updater.showButton()) { + Updater.Status status = updater.getStatus(); if (((status == Updater.Status.UPDATE_AVAILABLE || status == Updater.Status.UPDATE_DOWNLOADING) && updateButton.contains(mouseX, mouseY)) || (status == Updater.Status.UPDATE_DOWNLOADED && restartButton.contains(mouseX, mouseY))) UI.updateTooltip(delta, status.getDescription(), true); @@ -466,10 +471,10 @@ public class MainMenu extends BaseOpsuState { UI.enter(); if (!enterNotification) { - if (Updater.get().getStatus() == Updater.Status.UPDATE_AVAILABLE) { + if (updater.getStatus() == Updater.Status.UPDATE_AVAILABLE) { EventBus.post(new BarNotificationEvent("An opsu! update is available.")); enterNotification = true; - } else if (Updater.get().justUpdated()) { + } else if (updater.justUpdated()) { EventBus.post(new BarNotificationEvent("opsu! is now up to date!")); enterNotification = true; } @@ -547,10 +552,12 @@ public class MainMenu extends BaseOpsuState { lastMeasureProgress = 0f; if (!previous.isEmpty()) { instanceContainer.provide(SongMenu.class).setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false); - if (Options.isDynamicBackgroundEnabled()) + if (OPTION_DYNAMIC_BACKGROUND.state) { bgAlpha.setTime(0); - } else + } + } else { MusicController.setPosition(0); + } EventBus.post(new BarNotificationEvent("<< Previous")); return true; } @@ -565,7 +572,7 @@ public class MainMenu extends BaseOpsuState { // repository button actions if (repoButton != null && repoButton.contains(x, y)) { try { - Desktop.getDesktop().browse(Options.REPOSITORY_URI); + Desktop.getDesktop().browse(config.REPOSITORY_URI); } catch (UnsupportedOperationException e) { EventBus.post(new BarNotificationEvent("The repository web page could not be opened.")); } catch (IOException e) { @@ -577,7 +584,7 @@ public class MainMenu extends BaseOpsuState { if (danceRepoButton != null && danceRepoButton.contains(x, y)) { try { - Desktop.getDesktop().browse(Options.DANCE_REPOSITORY_URI); + Desktop.getDesktop().browse(config.DANCE_REPOSITORY_URI); } catch (UnsupportedOperationException e) { EventBus.post(new BarNotificationEvent("The repository web page could not be opened.")); } catch (IOException e) { @@ -588,11 +595,11 @@ public class MainMenu extends BaseOpsuState { } // update button actions - if (Updater.get().showButton()) { - Updater.Status status = Updater.get().getStatus(); + if (updater.showButton()) { + Updater.Status status = updater.getStatus(); if (updateButton.contains(x, y) && status == Updater.Status.UPDATE_AVAILABLE) { SoundController.playSound(SoundEffect.MENUHIT); - Updater.get().startDownload(); + updater.startDownload(); updateButton.removeHoverEffects(); updateButton.setHoverAnimationDuration(800); updateButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_QUAD); @@ -600,7 +607,7 @@ public class MainMenu extends BaseOpsuState { return true; } else if (restartButton.contains(x, y) && status == Updater.Status.UPDATE_DOWNLOADED) { SoundController.playSound(SoundEffect.MENUHIT); - Updater.get().prepareUpdate(); + updater.prepareUpdate(); displayContainer.exitRequested = true; return true; } @@ -719,8 +726,9 @@ public class MainMenu extends BaseOpsuState { if (!isTheme && !sameAudio) previous.add(node.index); } - if (Options.isDynamicBackgroundEnabled() && !sameAudio && !MusicController.isThemePlaying()) + if (OPTION_DYNAMIC_BACKGROUND.state && !sameAudio && !MusicController.isThemePlaying()) { bgAlpha.setTime(0); + } } /** diff --git a/src/itdelatrisu/opsu/states/OptionsMenu.java b/src/itdelatrisu/opsu/states/OptionsMenu.java deleted file mode 100644 index a43121cb..00000000 --- a/src/itdelatrisu/opsu/states/OptionsMenu.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * opsu! - an open-source osu! client - * Copyright (C) 2014, 2015 Jeffrey Han - * - * opsu! is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * opsu! is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with opsu!. If not, see . - */ - -package itdelatrisu.opsu.states; - -import itdelatrisu.opsu.Options.GameOption; - -import yugecin.opsudance.ui.OptionsOverlay.OptionTab; - -public class OptionsMenu { - - public static final OptionTab[] normalOptions = new OptionTab[] { - new OptionTab("GENERAL", null), - new OptionTab("GENERAL", new GameOption[]{ - GameOption.DISABLE_UPDATER, - GameOption.ENABLE_WATCH_SERVICE - }), - new OptionTab("LANGUAGE", new GameOption[]{ - GameOption.SHOW_UNICODE, - }), - new OptionTab("GRAPHICS", null), - new OptionTab("RENDERER", new GameOption[] { - GameOption.SCREEN_RESOLUTION, - GameOption.ALLOW_LARGER_RESOLUTIONS, - GameOption.FULLSCREEN, - GameOption.TARGET_UPS, - GameOption.TARGET_FPS, - GameOption.SHOW_FPS, - GameOption.USE_FPS_DELTAS, - GameOption.SCREENSHOT_FORMAT, - }), - new OptionTab("SLIDER OPTIONS", new GameOption[]{ - GameOption.SNAKING_SLIDERS, - GameOption.FALLBACK_SLIDERS, - GameOption.SHRINKING_SLIDERS, - GameOption.MERGING_SLIDERS, - //GameOption.MERGING_SLIDERS_MIRROR_POOL, - GameOption.DRAW_SLIDER_ENDCIRCLES, - }), - new OptionTab("SKIN", null), - new OptionTab("SKIN", new GameOption[]{ - GameOption.SKIN, - GameOption.IGNORE_BEATMAP_SKINS, - GameOption.DYNAMIC_BACKGROUND, - GameOption.LOAD_HD_IMAGES, - GameOption.LOAD_VERBOSE, - GameOption.COLOR_MAIN_MENU_LOGO, - }), - new OptionTab("CURSOR", new GameOption[]{ - GameOption.CURSOR_SIZE, - GameOption.NEW_CURSOR, - GameOption.DISABLE_CURSOR - // TODO use combo colour as tint for slider ball option - }), - new OptionTab("AUDIO", null), - new OptionTab("VOLUME", new GameOption[]{ - GameOption.MASTER_VOLUME, - GameOption.MUSIC_VOLUME, - GameOption.EFFECT_VOLUME, - GameOption.HITSOUND_VOLUME, - GameOption.SAMPLE_VOLUME_OVERRIDE, - }), - new OptionTab("MISC", new GameOption[] { - GameOption.MUSIC_OFFSET, - GameOption.DISABLE_SOUNDS, - GameOption.ENABLE_THEME_SONG - }), - new OptionTab("GAMEPLAY", null), - new OptionTab("GENERAL", new GameOption[] { - GameOption.BACKGROUND_DIM, - GameOption.FORCE_DEFAULT_PLAYFIELD, - GameOption.SHOW_HIT_LIGHTING, - GameOption.SHOW_HIT_ANIMATIONS, - GameOption.SHOW_COMBO_BURSTS, - GameOption.SHOW_PERFECT_HIT, - GameOption.SHOW_FOLLOW_POINTS, - GameOption.SHOW_HIT_ERROR_BAR, - GameOption.MAP_START_DELAY, - GameOption.MAP_END_DELAY, - GameOption.EPILEPSY_WARNING, - }), - new OptionTab("INPUT", null), - new OptionTab("KEY MAPPING", new GameOption[]{ - GameOption.KEY_LEFT, - GameOption.KEY_RIGHT, - }), - new OptionTab("MOUSE", new GameOption[] { - GameOption.DISABLE_MOUSE_WHEEL, - GameOption.DISABLE_MOUSE_BUTTONS, - }), - new OptionTab("CUSTOM", null), - new OptionTab("DIFFICULTY", new GameOption[]{ - GameOption.FIXED_CS, - GameOption.FIXED_HP, - GameOption.FIXED_AR, - GameOption.FIXED_OD, - }), - new OptionTab("MISC", new GameOption[] { - GameOption.CHECKPOINT, - GameOption.REPLAY_SEEKING, - }), - new OptionTab("DANCE", null), - new OptionTab("MOVER", new GameOption[]{ - GameOption.DANCE_MOVER, - GameOption.DANCE_EXGON_DELAY, - GameOption.DANCE_QUAD_BEZ_AGGRESSIVENESS, - GameOption.DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR, - GameOption.DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS, - GameOption.DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR, - GameOption.DANCE_MOVER_DIRECTION, - GameOption.DANCE_SLIDER_MOVER_TYPE, - }), - new OptionTab("SPINNER", new GameOption[]{ - GameOption.DANCE_SPINNER, - GameOption.DANCE_SPINNER_DELAY, - }), - new OptionTab("SLIDER OPTIONS", new GameOption[]{ - GameOption.DANCE_LAZY_SLIDERS, - GameOption.DANCE_CIRLCE_IN_SLOW_SLIDERS, - GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS, - }), - new OptionTab("CIRCLE MOVEMENTS", new GameOption[]{ - GameOption.DANCE_CIRCLE_STREAMS, - GameOption.DANCE_ONLY_CIRCLE_STACKS, - }), - new OptionTab("MIRROR", new GameOption[] { - GameOption.DANCE_MIRROR, - }), - new OptionTab("ADVANCED DISPLAY", null), - new OptionTab("OBJECTS", new GameOption[]{ - GameOption.DANCE_DRAW_APPROACH, - GameOption.DANCE_OBJECT_COLOR_OVERRIDE, - GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED, - GameOption.DANCE_RGB_OBJECT_INC, - GameOption.DANCE_HIDE_OBJECTS, - }), - new OptionTab("CURSOR", new GameOption[]{ - GameOption.DANCE_CURSOR_COLOR_OVERRIDE, - GameOption.DANCE_CURSOR_MIRROR_COLOR_OVERRIDE, - GameOption.DANCE_CURSOR_ONLY_COLOR_TRAIL, - GameOption.DANCE_RGB_CURSOR_INC, - GameOption.DANCE_CURSOR_TRAIL_OVERRIDE, - }), - new OptionTab("MISC", new GameOption[] { - GameOption.DANCE_HIDE_UI, - GameOption.DANCE_REMOVE_BG, - GameOption.DANCE_ENABLE_SB, - }), - new OptionTab ("PIPPI", null), - new OptionTab ("GENERAL", new GameOption[]{ - GameOption.PIPPI_ENABLE, - GameOption.PIPPI_RADIUS_PERCENT, - }), - new OptionTab ("ANGLE MULTIPLIERS", new GameOption[]{ - GameOption.PIPPI_ANGLE_INC_MUL, - GameOption.PIPPI_ANGLE_INC_MUL_SLIDER, - }), - new OptionTab ("MISC", new GameOption[] { - GameOption.PIPPI_SLIDER_FOLLOW_EXPAND, - GameOption.PIPPI_PREVENT_WOBBLY_STREAMS, - }) - }; - - public static final OptionTab[] storyboardOptions = new OptionTab[] { - new OptionTab("Gameplay", new GameOption[] { - GameOption.BACKGROUND_DIM, - GameOption.DANCE_REMOVE_BG, - GameOption.SNAKING_SLIDERS, - GameOption.SHRINKING_SLIDERS, - GameOption.SHOW_HIT_LIGHTING, - GameOption.SHOW_HIT_ANIMATIONS, - GameOption.SHOW_COMBO_BURSTS, - GameOption.SHOW_PERFECT_HIT, - GameOption.SHOW_FOLLOW_POINTS, - }), - new OptionTab("Input", new GameOption[] { - GameOption.CURSOR_SIZE, - GameOption.NEW_CURSOR, - GameOption.DISABLE_CURSOR - }), - new OptionTab("Dance", new GameOption[] { - GameOption.DANCE_MOVER, - GameOption.DANCE_EXGON_DELAY, - GameOption.DANCE_QUAD_BEZ_AGGRESSIVENESS, - GameOption.DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR, - GameOption.DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS, - GameOption.DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR, - GameOption.DANCE_MOVER_DIRECTION, - GameOption.DANCE_SLIDER_MOVER_TYPE, - GameOption.DANCE_SPINNER, - GameOption.DANCE_SPINNER_DELAY, - GameOption.DANCE_LAZY_SLIDERS, - GameOption.DANCE_CIRCLE_STREAMS, - GameOption.DANCE_ONLY_CIRCLE_STACKS, - GameOption.DANCE_CIRLCE_IN_SLOW_SLIDERS, - GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS, - GameOption.DANCE_MIRROR, - }), - new OptionTab("Dance display", new GameOption[] { - GameOption.DANCE_DRAW_APPROACH, - GameOption.DANCE_OBJECT_COLOR_OVERRIDE, - GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED, - GameOption.DANCE_RGB_OBJECT_INC, - GameOption.DANCE_CURSOR_COLOR_OVERRIDE, - GameOption.DANCE_CURSOR_MIRROR_COLOR_OVERRIDE, - GameOption.DANCE_CURSOR_ONLY_COLOR_TRAIL, - GameOption.DANCE_RGB_CURSOR_INC, - GameOption.DANCE_CURSOR_TRAIL_OVERRIDE, - GameOption.DANCE_HIDE_OBJECTS, - GameOption.DANCE_HIDE_UI, - }), - new OptionTab ("Pippi", new GameOption[] { - GameOption.PIPPI_ENABLE, - GameOption.PIPPI_RADIUS_PERCENT, - GameOption.PIPPI_ANGLE_INC_MUL, - GameOption.PIPPI_ANGLE_INC_MUL_SLIDER, - GameOption.PIPPI_SLIDER_FOLLOW_EXPAND, - GameOption.PIPPI_PREVENT_WOBBLY_STREAMS, - }) - }; - -} diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 16696d35..12a23d85 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -18,13 +18,8 @@ package itdelatrisu.opsu.states; -import itdelatrisu.opsu.GameData; +import itdelatrisu.opsu.*; import itdelatrisu.opsu.GameData.Grade; -import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.ScoreData; -import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MultiClip; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; @@ -74,8 +69,12 @@ import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.core.state.ComplexOpsuState; import yugecin.opsudance.events.BarNotificationEvent; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.options.OptionGroups; import yugecin.opsudance.ui.OptionsOverlay; +import static yugecin.opsudance.options.Options.*; + /** * "Song Selection" state. *

@@ -87,6 +86,15 @@ public class SongMenu extends ComplexOpsuState { @Inject private InstanceContainer instanceContainer; + @Inject + private Configuration config; + + @Inject + private OszUnpacker oszUnpacker; + + @Inject + private BeatmapParser beatmapParser; + /** The max number of song buttons to be shown on each screen. */ public static final int MAX_SONG_BUTTONS = 6; @@ -236,18 +244,11 @@ public class SongMenu extends ComplexOpsuState { /** Reloads all beatmaps. */ private void reloadBeatmaps() { - File beatmapDir = Options.getBeatmapDir(); - if (fullReload) { - // clear the beatmap cache BeatmapDB.clearDatabase(); - - // invoke unpacker - OszUnpacker.unpackAllFiles(Options.getOSZDir(), beatmapDir); + oszUnpacker.unpackAll(); } - - // invoke parser - BeatmapParser.parseAllFiles(beatmapDir); + beatmapParser.parseAll(); } } @@ -329,7 +330,7 @@ public class SongMenu extends ComplexOpsuState { public SongMenu(DisplayContainer displayContainer) { super(); - optionsOverlay = new OptionsOverlay(displayContainer, OptionsMenu.normalOptions); + optionsOverlay = new OptionsOverlay(displayContainer, OptionGroups.normalOptions); overlays.add(optionsOverlay); } @@ -599,7 +600,7 @@ public class SongMenu extends ComplexOpsuState { // song info text if (songInfo == null) { songInfo = focusNode.getInfo(); - if (Options.useUnicodeMetadata()) { // load glyphs + if (OPTION_SHOW_UNICODE.state) { Beatmap beatmap = focusNode.getBeatmapSet().get(0); Fonts.loadGlyphs(Fonts.LARGE, beatmap.titleUnicode); Fonts.loadGlyphs(Fonts.LARGE, beatmap.artistUnicode); @@ -740,7 +741,7 @@ public class SongMenu extends ComplexOpsuState { // initialize song list setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true); } else - MusicController.playThemeSong(); + MusicController.playThemeSong(config.themeBeatmap); reloadThread = null; } int mouseX = displayContainer.mouseX; @@ -1028,7 +1029,7 @@ public class SongMenu extends ComplexOpsuState { SoundController.playSound(SoundEffect.MENUHIT); if (button != Input.MOUSE_RIGHT_BUTTON) { // view score - instanceContainer.provide(GameRanking.class).setGameData(new GameData(focusScores[rank], displayContainer.width, displayContainer.height)); + instanceContainer.provide(GameRanking.class).setGameData(instanceContainer.injectFields(new GameData(focusScores[rank], displayContainer.width, displayContainer.height))); displayContainer.switchState(GameRanking.class); } else { // score management diff --git a/src/itdelatrisu/opsu/states/Splash.java b/src/itdelatrisu/opsu/states/Splash.java index b9ac5e99..71a7703c 100644 --- a/src/itdelatrisu/opsu/states/Splash.java +++ b/src/itdelatrisu/opsu/states/Splash.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.beatmap.BeatmapParser; @@ -28,8 +27,6 @@ import itdelatrisu.opsu.beatmap.OszUnpacker; import itdelatrisu.opsu.replay.ReplayImporter; import itdelatrisu.opsu.ui.UI; -import java.io.File; - import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import org.newdawn.slick.Input; @@ -38,6 +35,8 @@ import org.newdawn.slick.util.Log; import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.core.state.BaseOpsuState; +import static yugecin.opsudance.options.Options.*; + /** * "Splash Screen" state. *

@@ -48,6 +47,15 @@ public class Splash extends BaseOpsuState { @Inject private SongMenu songMenu; + @Inject + private ReplayImporter replayImporter; + + @Inject + private OszUnpacker oszUnpacker; + + @Inject + private BeatmapParser beatmapParser; + /** Whether or not loading has completed. */ private boolean finished; @@ -78,18 +86,10 @@ public class Splash extends BaseOpsuState { thread = new Thread() { @Override public void run() { - File beatmapDir = Options.getBeatmapDir(); + oszUnpacker.unpackAll(); + beatmapParser.parseAll(); + replayImporter.importAll(); - // unpack all OSZ archives - OszUnpacker.unpackAllFiles(Options.getOSZDir(), beatmapDir); - - // parse song directory - BeatmapParser.parseAllFiles(beatmapDir); - - // import replays - ReplayImporter.importAllReplaysFromDir(Options.getReplayImportDir()); - - // load sounds SoundController.init(); finished = true; @@ -108,14 +108,14 @@ public class Splash extends BaseOpsuState { // initialize song list if (BeatmapSetList.get().size() == 0) { - MusicController.playThemeSong(); + MusicController.playThemeSong(config.themeBeatmap); displayContainer.switchStateInstantly(MainMenu.class); return; } BeatmapSetList.get().init(); - if (Options.isThemeSongEnabled()) { - MusicController.playThemeSong(); + if (OPTION_ENABLE_THEME_SONG.state) { + MusicController.playThemeSong(config.themeBeatmap); } else { songMenu.setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true); } diff --git a/src/itdelatrisu/opsu/ui/Cursor.java b/src/itdelatrisu/opsu/ui/Cursor.java index 9e0a9182..9c15a2f0 100644 --- a/src/itdelatrisu/opsu/ui/Cursor.java +++ b/src/itdelatrisu/opsu/ui/Cursor.java @@ -19,9 +19,7 @@ package itdelatrisu.opsu.ui; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; -import itdelatrisu.opsu.skins.Skin; import itdelatrisu.opsu.ui.animations.AnimationEquation; import java.awt.Point; @@ -29,6 +27,9 @@ import java.util.LinkedList; import org.newdawn.slick.*; import yugecin.opsudance.Dancer; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Updates and draws the cursor. @@ -66,11 +67,6 @@ public class Cursor { private boolean isMirrored; - private Color filter; - - /** - * Constructor. - */ public Cursor() { this(false); } @@ -80,29 +76,25 @@ public class Cursor { this.isMirrored = isMirrored; } - public Cursor(Color filter) { - this(false); - this.filter = filter; - } - /** * Draws the cursor. * @param mousePressed whether or not the mouse button is pressed */ public void draw(boolean mousePressed) { - if (Options.isCursorDisabled()) + if (OPTION_DISABLE_CURSOR.state) { return; + } // determine correct cursor image Image cursor, cursorMiddle = null, cursorTrail; boolean beatmapSkinned = GameImage.CURSOR.hasBeatmapSkinImage(); boolean hasMiddle; - Skin skin = Options.getSkin(); if (beatmapSkinned) { newStyle = true; // osu! currently treats all beatmap cursors as new-style cursors hasMiddle = GameImage.CURSOR_MIDDLE.hasBeatmapSkinImage(); - } else - newStyle = hasMiddle = Options.isNewCursorEnabled(); + } else { + newStyle = hasMiddle = OPTION_NEW_CURSOR.state; + } if (beatmapSkinned || newStyle) { cursor = GameImage.CURSOR.getImage(); cursorTrail = GameImage.CURSOR_TRAIL.getImage(); @@ -115,7 +107,7 @@ public class Cursor { // scale cursor float cursorScaleAnimated = 1f; - if (skin.isCursorExpanded()) { + if (SkinService.skin.isCursorExpanded()) { if (lastCursorPressState != mousePressed) { lastCursorPressState = mousePressed; lastCursorPressTime = System.currentTimeMillis(); @@ -125,7 +117,7 @@ public class Cursor { Utils.clamp(System.currentTimeMillis() - lastCursorPressTime, 0, CURSOR_SCALE_TIME) / CURSOR_SCALE_TIME); cursorScaleAnimated = 1f + ((mousePressed) ? cursorScaleChange : CURSOR_SCALE_CHANGE - cursorScaleChange); } - float cursorScale = cursorScaleAnimated * Options.getCursorScale(); + float cursorScale = cursorScaleAnimated * OPTION_CURSOR_SIZE.val / 100f; if (cursorScale != 1f) { cursor = cursor.getScaledCopy(cursorScale); cursorTrail = cursorTrail.getScaledCopy(cursorScale); @@ -138,19 +130,15 @@ public class Cursor { lastCursorColor = filter = Dancer.cursorColorOverride.getColor(); } - if (this.filter != null) { - filter = this.filter; - } - // draw a fading trail float alpha = 0f; float t = 2f / trail.size(); int cursorTrailWidth = cursorTrail.getWidth(), cursorTrailHeight = cursorTrail.getHeight(); - float cursorTrailRotation = (skin.isCursorTrailRotated()) ? cursorAngle : 0; + float cursorTrailRotation = (SkinService.skin.isCursorTrailRotated()) ? cursorAngle : 0; cursorTrail.startUse(); for (Point p : trail) { alpha += t; - cursorTrail.setImageColor(filter.r, filter.g, filter.b, alpha * 0.1f); + cursorTrail.setImageColor(filter.r, filter.g, filter.b, alpha); cursorTrail.drawEmbedded( p.x - (cursorTrailWidth / 2f), p.y - (cursorTrailHeight / 2f), cursorTrailWidth, cursorTrailHeight, cursorTrailRotation); @@ -161,11 +149,13 @@ public class Cursor { cursorTrail.endUse(); // draw the other components - if (newStyle && skin.isCursorRotated()) + if (newStyle && SkinService.skin.isCursorRotated()) { cursor.setRotation(cursorAngle); - cursor.drawCentered(lastPosition.x, lastPosition.y, Options.isCursorOnlyColorTrail() ? Color.white : filter); - if (hasMiddle) - cursorMiddle.drawCentered(lastPosition.x, lastPosition.y, Options.isCursorOnlyColorTrail() ? Color.white : filter); + } + cursor.drawCentered(lastPosition.x, lastPosition.y, OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL.state ? Color.white : filter); + if (hasMiddle) { + cursorMiddle.drawCentered(lastPosition.x, lastPosition.y, OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL.state ? Color.white : filter); + } } /** @@ -194,7 +184,7 @@ public class Cursor { removeCount = trail.size() - max; } - int cursortraillength = Options.getCursorTrailOverride(); + int cursortraillength = OPTION_DANCE_CURSOR_TRAIL_OVERRIDE.val; if (cursortraillength > 20) { removeCount = trail.size() - cursortraillength; } diff --git a/src/itdelatrisu/opsu/ui/DropdownMenu.java b/src/itdelatrisu/opsu/ui/DropdownMenu.java index a3df29bd..474e36ae 100644 --- a/src/itdelatrisu/opsu/ui/DropdownMenu.java +++ b/src/itdelatrisu/opsu/ui/DropdownMenu.java @@ -93,8 +93,9 @@ public class DropdownMenu extends Component { * @throws IllegalArgumentException if {@code index} is negative or greater than or equal to size */ public void setSelectedIndex(int index) { - if (index < 0 || index >= items.length) + if (index < 0 || items.length <= index) { throw new IllegalArgumentException(); + } this.selectedItemIndex = index; } @@ -252,7 +253,7 @@ public class DropdownMenu extends Component { return; } this.expanded = (idx == -1) && !expanded; - if (idx >= 0 && selectedItemIndex != idx) { + if (0 <= idx && idx < items.length && selectedItemIndex != idx) { this.selectedItemIndex = idx; itemSelected(idx, items[selectedItemIndex]); } diff --git a/src/itdelatrisu/opsu/ui/Fonts.java b/src/itdelatrisu/opsu/ui/Fonts.java index 2b1cb6d6..a11831d6 100644 --- a/src/itdelatrisu/opsu/ui/Fonts.java +++ b/src/itdelatrisu/opsu/ui/Fonts.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.ui; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import java.awt.Font; import java.awt.FontFormatException; @@ -35,6 +34,7 @@ import org.newdawn.slick.font.effects.ColorEffect; import org.newdawn.slick.font.effects.Effect; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; +import yugecin.opsudance.options.Configuration; /** * Fonts used for drawing. @@ -54,9 +54,9 @@ public class Fonts { * @throws FontFormatException if any font stream data does not contain the required font tables * @throws IOException if a font stream cannot be completely read */ - public static void init() throws SlickException, FontFormatException, IOException { + public static void init(Configuration config) throws SlickException, FontFormatException, IOException { float fontBase = 12f * GameImage.getUIscale(); - Font javaFont = Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(Options.FONT_NAME)); + Font javaFont = Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(config.FONT_NAME)); Font font = javaFont.deriveFont(Font.PLAIN, (int) (fontBase * 4 / 3)); DEFAULT = new UnicodeFont(font); BOLD = new UnicodeFont(font.deriveFont(Font.BOLD)); diff --git a/src/itdelatrisu/opsu/ui/UI.java b/src/itdelatrisu/opsu/ui/UI.java index 586075b1..8acfb1cf 100644 --- a/src/itdelatrisu/opsu/ui/UI.java +++ b/src/itdelatrisu/opsu/ui/UI.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.ui; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.beatmap.BeatmapParser; @@ -28,14 +27,14 @@ import itdelatrisu.opsu.replay.ReplayImporter; import itdelatrisu.opsu.ui.animations.AnimatedValue; import itdelatrisu.opsu.ui.animations.AnimationEquation; -import org.newdawn.slick.Animation; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; import yugecin.opsudance.core.DisplayContainer; -import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; import yugecin.opsudance.ui.BackButton; +import static yugecin.opsudance.options.Options.*; + /** * Draws common UI components. */ @@ -70,9 +69,6 @@ public class UI { */ public static void init(DisplayContainer displayContainer) { UI.displayContainer = displayContainer; - } - - public static void revalidate() { backButton = new BackButton(displayContainer); } @@ -151,7 +147,7 @@ public class UI { img.drawCentered(displayContainer.width - img.getWidth() / 2f + xOffset, displayContainer.height / 2f); float barHeight = img.getHeight() * 0.9f; - float volume = Options.getMasterVolume(); + float volume = OPTION_MASTER_VOLUME.val / 100f; g.setColor(Color.white); g.fillRoundRect( displayContainer.width - (img.getWidth() * 0.368f) + xOffset, @@ -179,7 +175,8 @@ public class UI { */ public static void changeVolume(int units) { final float UNIT_OFFSET = 0.05f; - Options.setMasterVolume(Utils.clamp(Options.getMasterVolume() + (UNIT_OFFSET * units), 0f, 1f)); + float volume = Utils.clamp(OPTION_MASTER_VOLUME.val / 100f + (UNIT_OFFSET * units), 0f, 1f); + OPTION_MASTER_VOLUME.setValue((int) (volume * 100f)); if (volumeDisplay == -1) volumeDisplay = 0; else if (volumeDisplay >= VOLUME_DISPLAY_TIME / 10) @@ -196,6 +193,9 @@ public class UI { int progress; // determine current action + // + /* + TODO if ((file = OszUnpacker.getCurrentFileName()) != null) { text = "Unpacking new beatmaps..."; progress = OszUnpacker.getUnpackerProgress(); @@ -211,12 +211,17 @@ public class UI { progress = SoundController.getLoadingProgress(); } else return; + */ + + if (true) { + return; // TODO + } // draw loading info float marginX = displayContainer.width * 0.02f, marginY = displayContainer.height * 0.02f; float lineY = displayContainer.height - marginY; int lineOffsetY = Fonts.MEDIUM.getLineHeight(); - if (Options.isLoadVerbose()) { + if (OPTION_LOAD_VERBOSE.state) { // verbose: display percentages and file names Fonts.MEDIUM.drawString( marginX, lineY - (lineOffsetY * 2), diff --git a/src/yugecin/opsudance/CursorColorOverrides.java b/src/yugecin/opsudance/CursorColorOverrides.java index 92005bab..f9a5d4dc 100644 --- a/src/yugecin/opsudance/CursorColorOverrides.java +++ b/src/yugecin/opsudance/CursorColorOverrides.java @@ -17,10 +17,11 @@ */ package yugecin.opsudance; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.ui.Cursor; import org.newdawn.slick.Color; +import static yugecin.opsudance.options.Options.*; + public enum CursorColorOverrides { NONE ("Do not override", 0) { @@ -111,12 +112,12 @@ public enum CursorColorOverrides { } private static Color nextRainbowColor() { - hue += Options.getRGBCursorInc() / 1000f; + hue += OPTION_DANCE_RGB_CURSOR_INC.val / 1000f; return new Color(java.awt.Color.getHSBColor(hue / 360f, 1.0f, 1.0f).getRGB()); } private static Color nextMirrorRainbowColor() { - hue += Options.getRGBCursorInc() / 1000f; + hue += OPTION_DANCE_RGB_CURSOR_INC.val / 1000f; return new Color(java.awt.Color.getHSBColor((hue + 180f) / 360f, 1.0f, 1.0f).getRGB()); } diff --git a/src/yugecin/opsudance/Dancer.java b/src/yugecin/opsudance/Dancer.java index 3dbc25f2..beadcd0b 100644 --- a/src/yugecin/opsudance/Dancer.java +++ b/src/yugecin/opsudance/Dancer.java @@ -22,10 +22,8 @@ import awlex.ospu.movers.factories.SpiralMoverFactory; import awlex.ospu.polymover.factory.ArcFactory; import awlex.ospu.polymover.factory.PolyMoverFactory; import awlex.ospu.spinners.SpiralSpinner; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; -import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.DummyObject; import itdelatrisu.opsu.objects.GameObject; import itdelatrisu.opsu.objects.Slider; @@ -37,10 +35,13 @@ import yugecin.opsudance.movers.factories.*; import yugecin.opsudance.movers.slidermovers.DefaultSliderMoverController; import yugecin.opsudance.movers.slidermovers.InheritedSliderMoverController; import yugecin.opsudance.movers.slidermovers.SliderMoverController; +import yugecin.opsudance.render.GameObjectRenderer; import yugecin.opsudance.spinners.*; import java.awt.*; +import static yugecin.opsudance.options.Options.*; + public class Dancer { public static MoverFactory[] moverFactories = new MoverFactory[] { @@ -193,12 +194,12 @@ public class Dancer { } isCurrentLazySlider = false; // detect lazy sliders, should work pretty good - if (c.isSlider() && Options.isLazySliders() && Utils.distance(c.start.x, c.start.y, c.end.x, c.end.y) <= Circle.diameter * 0.8f) { + if (c.isSlider() && OPTION_DANCE_LAZY_SLIDERS.state && Utils.distance(c.start.x, c.start.y, c.end.x, c.end.y) <= GameObjectRenderer.instance.getCircleDiameter() * 0.8f) { Slider s = (Slider) c; Vec2f mid = s.getCurve().pointAt(1f); - if (s.getRepeats() == 1 || Utils.distance(c.start.x, c.start.y, mid.x, mid.y) <= Circle.diameter * 0.8f) { + if (s.getRepeats() == 1 || Utils.distance(c.start.x, c.start.y, mid.x, mid.y) <= GameObjectRenderer.instance.getCircleDiameter() * 0.8f) { mid = s.getCurve().pointAt(0.5f); - if (Utils.distance(c.start.x, c.start.y, mid.x, mid.y) <= Circle.diameter * 0.8f) { + if (Utils.distance(c.start.x, c.start.y, mid.x, mid.y) <= GameObjectRenderer.instance.getCircleDiameter() * 0.8f) { isCurrentLazySlider = true; } } @@ -250,8 +251,8 @@ public class Dancer { } } Pippi.dance(time, c, isCurrentLazySlider); - x = Utils.clamp(x, 10, Options.width - 10); - y = Utils.clamp(y, 10, Options.height - 10); + x = Utils.clamp(x, 10, width - 10); + y = Utils.clamp(y, 10, height - 10); } private void createNewMover() { diff --git a/src/yugecin/opsudance/ObjectColorOverrides.java b/src/yugecin/opsudance/ObjectColorOverrides.java index dbf3e0a4..7749b7ac 100644 --- a/src/yugecin/opsudance/ObjectColorOverrides.java +++ b/src/yugecin/opsudance/ObjectColorOverrides.java @@ -17,9 +17,10 @@ */ package yugecin.opsudance; -import itdelatrisu.opsu.Options; import org.newdawn.slick.Color; +import static yugecin.opsudance.options.Options.*; + public enum ObjectColorOverrides { NONE ("Do not override", 0) { @@ -95,7 +96,7 @@ public enum ObjectColorOverrides { } private static Color nextRainbowColor() { - hue += Options.getRGBObjInc() / 10f; + hue += OPTION_DANCE_RGB_OBJECT_INC.val / 10f; return new Color(java.awt.Color.getHSBColor(hue / 360f, 1.0f, 1.0f).getRGB()); } diff --git a/src/yugecin/opsudance/OpsuDance.java b/src/yugecin/opsudance/OpsuDance.java index b50dd0ec..99cbcb3c 100644 --- a/src/yugecin/opsudance/OpsuDance.java +++ b/src/yugecin/opsudance/OpsuDance.java @@ -17,7 +17,6 @@ */ package yugecin.opsudance; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.BeatmapWatchService; import itdelatrisu.opsu.db.DBController; @@ -27,6 +26,9 @@ import itdelatrisu.opsu.states.Splash; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.options.OptionsService; import java.io.File; import java.io.IOException; @@ -35,18 +37,29 @@ import java.net.ServerSocket; import java.net.UnknownHostException; import static yugecin.opsudance.core.Entrypoint.sout; +import static yugecin.opsudance.options.Options.*; /* * loosely based on itdelatrisu.opsu.Opsu */ public class OpsuDance { - private final DisplayContainer container; + @Inject + private DisplayContainer container; + + @Inject + private OptionsService optionsService; + + @Inject + private Configuration config; + + @Inject + private Updater updater; private ServerSocket singleInstanceSocket; - public OpsuDance(DisplayContainer container) { - this.container = container; + @Inject + public OpsuDance() { } public void start(String[] args) { @@ -54,7 +67,7 @@ public class OpsuDance { sout("initialized"); checkRunningDirectory(); - Options.parseOptions(); + optionsService.loadOptions(); ensureSingleInstance(); sout("prechecks done and options parsed"); @@ -70,12 +83,12 @@ public class OpsuDance { while (rungame()); container.teardownAL(); - Options.saveOptions(); + optionsService.saveOptions(); closeSingleInstanceSocket(); DBController.closeConnections(); DownloadList.get().cancelAllDownloads(); - Utils.deleteDirectory(Options.TEMP_DIR); - if (!Options.isWatchServiceEnabled()) { + Utils.deleteDirectory(config.TEMP_DIR); + if (!OPTION_ENABLE_WATCH_SERVICE.state) { BeatmapWatchService.destroy(); } } @@ -101,7 +114,7 @@ public class OpsuDance { private void initDatabase() { try { - DBController.init(); + DBController.init(config); } catch (UnsatisfiedLinkError e) { errorAndExit("Could not initialize database.", e); } @@ -109,18 +122,19 @@ public class OpsuDance { private void initUpdater(String[] args) { // check if just updated - if (args.length >= 2) - Updater.get().setUpdateInfo(args[0], args[1]); + if (args.length >= 2) { + updater.setUpdateInfo(args[0], args[1]); + } // check for updates - if (Options.isUpdaterDisabled()) { + if (OPTION_DISABLE_UPDATER.state) { return; } new Thread() { @Override public void run() { try { - Updater.get().checkForUpdates(); + updater.checkForUpdates(); } catch (IOException e) { Log.warn("updatecheck failed.", e); } @@ -143,18 +157,18 @@ public class OpsuDance { } private void ensureSingleInstance() { - if (Options.noSingleInstance()) { + if (OPTION_NOSINGLEINSTANCE.state) { return; } try { - singleInstanceSocket = new ServerSocket(Options.getPort(), 1, InetAddress.getLocalHost()); + singleInstanceSocket = new ServerSocket(OPTION_PORT.val, 1, InetAddress.getLocalHost()); } catch (UnknownHostException e) { // shouldn't happen } catch (IOException e) { errorAndExit(String.format( "Could not launch. Either opsu! is already running or a different program uses port %d.\n" + "You can change the port opsu! uses by editing the 'Port' field in the .opsu.cfg configuration file.\n" + - "If that still does not resolve the problem, you can set 'NoSingleInstance' to 'true', but this is not recommended.", Options.getPort()), e); + "If that still does not resolve the problem, you can set 'NoSingleInstance' to 'true', but this is not recommended.", OPTION_PORT.val), e); } } diff --git a/src/yugecin/opsudance/Pippi.java b/src/yugecin/opsudance/Pippi.java index 2130c6a7..3567910c 100644 --- a/src/yugecin/opsudance/Pippi.java +++ b/src/yugecin/opsudance/Pippi.java @@ -17,10 +17,11 @@ */ package yugecin.opsudance; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.GameObject; import itdelatrisu.opsu.objects.Slider; +import yugecin.opsudance.render.GameObjectRenderer; + +import static yugecin.opsudance.options.Options.*; public class Pippi { @@ -37,30 +38,30 @@ public class Pippi { public static void setRadiusPercent(int radiusPercent) { Pippi.radiusPercent = radiusPercent; - pippiminrad = pippirad = (Circle.diameter / 2d - 10d) * radiusPercent / 100d; + pippiminrad = pippirad = (GameObjectRenderer.instance.getCircleDiameter() / 2d - 10d) * radiusPercent / 100d; } public static void reset() { angle = 0; currentdelta = 0; setRadiusPercent(radiusPercent); - pippimaxrad = Circle.diameter - 10d; + pippimaxrad = GameObjectRenderer.instance.getCircleDiameter() - 10d; } public static void dance(int time, GameObject c, boolean isCurrentLazySlider) { - boolean slowSlider = Options.isCircleInSlowSliders() && c.isSlider() && (((((Slider) c).pixelLength < 200 || c.getEndTime() - c.getTime() > 400)) || isCurrentLazySlider); + boolean slowSlider = OPTION_DANCE_CIRLCE_IN_SLOW_SLIDERS.state && c.isSlider() && (((((Slider) c).pixelLength < 200 || c.getEndTime() - c.getTime() > 400)) || isCurrentLazySlider); if (!slowSlider) { - slowSlider = Options.isCircleInLazySliders() && isCurrentLazySlider; + slowSlider = OPTION_DANCE_CIRLCE_IN_LAZY_SLIDERS.state && isCurrentLazySlider; } - if ((!Options.isPippiEnabled() || c.isSpinner()) && !slowSlider) { + if ((!OPTION_PIPPI_ENABLE.state || c.isSpinner()) && !slowSlider) { return; } if (currentdelta >= targetdelta && c != previous) { currentdelta = 0; if (c.isSlider() && c.getTime() < time) { - angle += Options.getPippiAngIncMultiplierSlider() / 1800d * Math.PI; + angle += OPTION_PIPPI_ANGLE_INC_MUL_SLIDER.val / 1800d * Math.PI; if (!slowSlider) { - if (Options.isPippiFollowcircleExpand()) { + if (OPTION_PIPPI_SLIDER_FOLLOW_EXPAND.state) { if (c.getEndTime() - time < 40 && pippirad > pippimaxrad) { pippirad -= 5d; } else if (time - c.getTime() > 10 && c.getEndTime() - c.getTime() > 600 && pippirad < pippimaxrad) { @@ -69,10 +70,10 @@ public class Pippi { } } } else if (!c.isSpinner()) { - if (Options.isPippiFollowcircleExpand() && pippirad != pippiminrad) { + if (OPTION_PIPPI_SLIDER_FOLLOW_EXPAND.state && pippirad != pippiminrad) { pippirad = pippiminrad; } - angle += Options.getPippiAngIncMultiplier() / 1800d * Math.PI; + angle += OPTION_PIPPI_ANGLE_INC_MUL.val / 1800d * Math.PI; } // don't inc on long movements if (c.getTime() - time > 400) { @@ -91,7 +92,7 @@ public class Pippi { } public static boolean shouldPreventWobblyStream(double distance) { - return Options.isPippiEnabled() && distance < Circle.diameter * 0.93f && Options.isPippiPreventWobblyStreams(); + return OPTION_PIPPI_ENABLE.state && distance < GameObjectRenderer.instance.getCircleDiameter() * 0.93f && OPTION_PIPPI_PREVENT_WOBBLY_STREAMS.state; } } diff --git a/src/yugecin/opsudance/PreStartupInitializer.java b/src/yugecin/opsudance/PreStartupInitializer.java index b7b671bf..678d0c3d 100644 --- a/src/yugecin/opsudance/PreStartupInitializer.java +++ b/src/yugecin/opsudance/PreStartupInitializer.java @@ -18,10 +18,11 @@ package yugecin.opsudance; import itdelatrisu.opsu.NativeLoader; -import itdelatrisu.opsu.Options; import org.newdawn.slick.util.FileSystemLocation; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.options.Configuration; import java.io.File; import java.io.IOException; @@ -29,7 +30,11 @@ import java.lang.reflect.Field; public class PreStartupInitializer { - public PreStartupInitializer() { + private final Configuration config; + + @Inject + public PreStartupInitializer(Configuration config) { + this.config = config; loadNatives(); setResourcePath(); } @@ -52,7 +57,7 @@ public class PreStartupInitializer { } private File loadNativesUsingOptionsPath() { - File nativeDir = Options.NATIVE_DIR; + File nativeDir = config.NATIVE_DIR; try { new NativeLoader(nativeDir).loadNatives(); } catch (IOException e) { diff --git a/src/yugecin/opsudance/core/DisplayContainer.java b/src/yugecin/opsudance/core/DisplayContainer.java index 1ac2cff7..a185910b 100644 --- a/src/yugecin/opsudance/core/DisplayContainer.java +++ b/src/yugecin/opsudance/core/DisplayContainer.java @@ -17,15 +17,15 @@ */ package yugecin.opsudance.core; -import itdelatrisu.opsu.GameData; -import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.*; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.beatmap.Beatmap; +import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.downloads.DownloadList; +import itdelatrisu.opsu.downloads.DownloadNode; import itdelatrisu.opsu.downloads.Updater; import itdelatrisu.opsu.render.CurveRenderState; +import itdelatrisu.opsu.replay.PlaybackSpeed; import itdelatrisu.opsu.ui.Cursor; import itdelatrisu.opsu.ui.Fonts; import itdelatrisu.opsu.ui.UI; @@ -42,6 +42,7 @@ import org.newdawn.slick.util.Log; import yugecin.opsudance.core.events.EventBus; import yugecin.opsudance.core.errorhandling.ErrorDumpable; import yugecin.opsudance.core.events.EventListener; +import yugecin.opsudance.core.inject.Inject; import yugecin.opsudance.core.inject.InstanceContainer; import yugecin.opsudance.core.state.OpsuState; import yugecin.opsudance.core.state.specialstates.BarNotificationState; @@ -50,19 +51,25 @@ import yugecin.opsudance.core.state.specialstates.FpsRenderState; import yugecin.opsudance.core.state.transitions.*; import yugecin.opsudance.events.BubbleNotificationEvent; import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.skinning.SkinService; import yugecin.opsudance.utils.GLHelper; import java.io.StringWriter; import static yugecin.opsudance.core.Entrypoint.sout; +import static yugecin.opsudance.options.Options.*; /** * based on org.newdawn.slick.AppGameContainer */ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListener { - @Deprecated - public static DisplayContainer instance; // TODO d remove this + @Inject + private SkinService skinService; + + @Inject + private Configuration config; private static SGL GL = Renderer.get(); @@ -116,11 +123,11 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen public final Cursor cursor; public boolean drawCursor; + @Inject public DisplayContainer(InstanceContainer instanceContainer) { this.instanceContainer = instanceContainer; this.cursor = new Cursor(); drawCursor = true; - instance = this; outTransitionListener = new TransitionFinishedListener() { @Override @@ -145,8 +152,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen @Override public void onEvent(ResolutionOrSkinChangedEvent event) { destroyImages(); - Utils.init(DisplayContainer.this); // TODO this shouldn't be here - UI.revalidate(); // TODO this shouldn't be here + reinit(); } }); @@ -155,8 +161,29 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen lastFrame = getTime(); delta = 1; renderDelta = 1; + } - Options.GameOption.displayContainer = this; + private void reinit() { + // this used to be in Utils.init + // TODO find a better place for this? + setFPS(targetFPS[targetFPSIndex]); + MusicController.setMusicVolume(OPTION_MUSIC_VOLUME.val / 100f * OPTION_MASTER_VOLUME.val / 100f); + + skinService.loadSkin(); + + // initialize game images + for (GameImage img : GameImage.values()) { + if (img.isPreload()) { + img.setDefaultImage(); + } + } + + // TODO clean this up + GameMod.init(width, height); + PlaybackSpeed.init(width, height); + HitObject.init(width, height); + DownloadNode.init(width, height); + UI.init(this); } public void setUPS(int ups) { @@ -170,8 +197,8 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen } public void init(Class startingState) { - setUPS(Options.getTargetUPS()); - setFPS(Options.getTargetFPS()); + setUPS(OPTION_TARGET_UPS.val); + setFPS(targetFPS[targetFPSIndex]); state = instanceContainer.provide(startingState); state.enter(); @@ -245,7 +272,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen width = height = -1; Input.disableControllers(); Display.setTitle("opsu!dance"); - Options.setDisplayMode(this); + updateDisplayMode(OPTION_SCREEN_RESOLUTION.getValueString()); Display.create(); GLHelper.setIcons(new String[] { "icon16.png", "icon32.png" }); initGL(); @@ -303,6 +330,38 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen return true; } + public void updateDisplayMode(String resolutionString) { + int screenWidth = nativeDisplayMode.getWidth(); + int screenHeight = nativeDisplayMode.getHeight(); + + int width = screenWidth; + int height = screenHeight; + if (resolutionString.matches("^[0-9]+x[0-9]+$")) { + String[] res = resolutionString.split("x"); + width = Integer.parseInt(res[0]); + height = Integer.parseInt(res[1]); + } + + // check for larger-than-screen dimensions + if (!OPTION_ALLOW_LARGER_RESOLUTIONS.state && (screenWidth < width || screenHeight < height)) { + width = 800; + height = 600; + } + + try { + setDisplayMode(width, height, OPTION_FULLSCREEN.state); + } catch (Exception e) { + EventBus.post(new BubbleNotificationEvent("Failed to change resolution", BubbleNotificationEvent.COMMONCOLOR_RED)); + Log.error("Failed to set display mode.", e); + } + + if (OPTION_FULLSCREEN.state) { + // set borderless window if dimensions match screen size + boolean borderless = (screenWidth == width && screenHeight == height); + System.setProperty("org.lwjgl.opengl.Window.undecorated", Boolean.toString(borderless)); + } + } + public void setDisplayMode(int width, int height, boolean fullscreen) throws Exception { if (this.width == width && this.height == height) { Display.setFullscreen(fullscreen); @@ -353,9 +412,9 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen sout("GL ready"); GameImage.init(width, height); - Fonts.init(); + Fonts.init(config); - EventBus.post(new ResolutionOrSkinChangedEvent()); + EventBus.post(new ResolutionOrSkinChangedEvent(null, width, height)); } public void resetCursor() { diff --git a/src/yugecin/opsudance/core/errorhandling/ErrorHandler.java b/src/yugecin/opsudance/core/errorhandling/ErrorHandler.java index 99321ba4..4369913b 100644 --- a/src/yugecin/opsudance/core/errorhandling/ErrorHandler.java +++ b/src/yugecin/opsudance/core/errorhandling/ErrorHandler.java @@ -17,10 +17,10 @@ */ package yugecin.opsudance.core.errorhandling; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import org.newdawn.slick.util.Log; import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.options.Configuration; import yugecin.opsudance.utils.MiscUtils; import javax.swing.*; @@ -42,6 +42,7 @@ public class ErrorHandler { private static ErrorHandler instance; + private final Configuration config; private final DisplayContainer displayContainer; private String customMessage; @@ -54,8 +55,9 @@ public class ErrorHandler { private boolean ignoreAndContinue; private boolean allowTerminate; - public ErrorHandler(DisplayContainer displayContainer) { + public ErrorHandler(DisplayContainer displayContainer, Configuration config) { this.displayContainer = displayContainer; + this.config = config; instance = this; } @@ -164,7 +166,7 @@ public class ErrorHandler { @Override public void actionPerformed(ActionEvent event) { try { - Desktop.getDesktop().open(Options.LOG_FILE); + Desktop.getDesktop().open(config.LOG_FILE); } catch (IOException e) { Log.warn("Could not open log file", e); JOptionPane.showMessageDialog(null, "whoops could not open log file", "errorception", JOptionPane.ERROR_MESSAGE); @@ -230,7 +232,7 @@ public class ErrorHandler { } catch (UnsupportedEncodingException e) { Log.warn("URLEncoder failed to encode the auto-filled issue report URL.", e); } - return URI.create(String.format(Options.ISSUES_URL, issueTitle, issueBody)); + return URI.create(String.format(config.ISSUES_URL, issueTitle, issueBody)); } private String truncateGithubIssueBody(String body) { diff --git a/src/yugecin/opsudance/core/inject/Injector.java b/src/yugecin/opsudance/core/inject/Injector.java index 7ee7a054..9ddee01d 100644 --- a/src/yugecin/opsudance/core/inject/Injector.java +++ b/src/yugecin/opsudance/core/inject/Injector.java @@ -101,6 +101,11 @@ public abstract class Injector implements InstanceContainer, Binder { return object; } + @Override + public T injectFields(T obj) { + return injectFields(obj, obj.getClass()); + } + public final Binder bind(Class type) { lastType = type; return this; diff --git a/src/yugecin/opsudance/core/inject/InstanceContainer.java b/src/yugecin/opsudance/core/inject/InstanceContainer.java index 7cc0065d..77ba1be7 100644 --- a/src/yugecin/opsudance/core/inject/InstanceContainer.java +++ b/src/yugecin/opsudance/core/inject/InstanceContainer.java @@ -20,5 +20,6 @@ package yugecin.opsudance.core.inject; public interface InstanceContainer { T provide(Class type); + T injectFields(T instance); } diff --git a/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java b/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java index 9c9d4479..3129ded8 100644 --- a/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java +++ b/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java @@ -17,6 +17,10 @@ */ package yugecin.opsudance.core.inject; +import itdelatrisu.opsu.beatmap.BeatmapParser; +import itdelatrisu.opsu.beatmap.OszUnpacker; +import itdelatrisu.opsu.downloads.Updater; +import itdelatrisu.opsu.replay.ReplayImporter; import itdelatrisu.opsu.states.*; import yugecin.opsudance.PreStartupInitializer; import yugecin.opsudance.core.DisplayContainer; @@ -27,10 +31,23 @@ import yugecin.opsudance.core.state.transitions.EmptyTransitionState; import yugecin.opsudance.core.state.transitions.FadeInTransitionState; import yugecin.opsudance.core.state.transitions.FadeOutTransitionState; import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.options.OptionsService; +import yugecin.opsudance.render.GameObjectRenderer; +import yugecin.opsudance.skinning.SkinService; public class OpsuDanceInjector extends Injector { protected void configure() { + bind(Configuration.class).asEagerSingleton(); + + bind(OptionsService.class).asLazySingleton(); + bind(ReplayImporter.class).asLazySingleton(); + bind(OszUnpacker.class).asLazySingleton(); + bind(BeatmapParser.class).asLazySingleton(); + bind(Updater.class).asLazySingleton(); + bind(SkinService.class).asEagerSingleton(); + bind(PreStartupInitializer.class).asEagerSingleton(); bind(DisplayContainer.class).asEagerSingleton(); @@ -44,6 +61,8 @@ public class OpsuDanceInjector extends Injector { bind(FadeInTransitionState.class).asEagerSingleton(); bind(FadeOutTransitionState.class).asEagerSingleton(); + bind(GameObjectRenderer.class).asEagerSingleton(); + bind(Splash.class).asEagerSingleton(); bind(MainMenu.class).asEagerSingleton(); bind(ButtonMenu.class).asEagerSingleton(); diff --git a/src/yugecin/opsudance/core/state/BaseOpsuState.java b/src/yugecin/opsudance/core/state/BaseOpsuState.java index b8838d27..0ff6c63d 100644 --- a/src/yugecin/opsudance/core/state/BaseOpsuState.java +++ b/src/yugecin/opsudance/core/state/BaseOpsuState.java @@ -17,8 +17,6 @@ */ package yugecin.opsudance.core.state; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.ui.UI; import org.newdawn.slick.Graphics; @@ -27,15 +25,26 @@ import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.events.EventBus; import yugecin.opsudance.core.events.EventListener; import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.events.BarNotificationEvent; import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; +import yugecin.opsudance.options.Configuration; +import yugecin.opsudance.skinning.SkinService; import java.io.StringWriter; +import static yugecin.opsudance.options.Options.*; + public abstract class BaseOpsuState implements OpsuState, EventListener { @Inject protected DisplayContainer displayContainer; + @Inject + protected Configuration config; + + @Inject + protected SkinService skinService; + /** * state is dirty when resolution or skin changed but hasn't rendered yet */ @@ -97,20 +106,21 @@ public abstract class BaseOpsuState implements OpsuState, EventListener { private final static Color GREEN = new Color(171, 218, 25); @@ -54,17 +55,17 @@ public class FpsRenderState implements EventListener nextTime) { - nextTime = time + Options.getExgonDelay(); - if (time > getEnd().getEndTime() - Options.getExgonDelay()) { + nextTime = time + OPTION_DANCE_EXGON_DELAY.val; + if (time > getEnd().getEndTime() - OPTION_DANCE_EXGON_DELAY.val) { pos[0] = endX; pos[1] = endY; } else { diff --git a/src/yugecin/opsudance/movers/QuadraticBezierMover.java b/src/yugecin/opsudance/movers/QuadraticBezierMover.java index 35a598cd..c17b1861 100644 --- a/src/yugecin/opsudance/movers/QuadraticBezierMover.java +++ b/src/yugecin/opsudance/movers/QuadraticBezierMover.java @@ -17,12 +17,13 @@ */ package yugecin.opsudance.movers; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.objects.GameObject; import java.awt.*; +import static yugecin.opsudance.options.Options.*; + public class QuadraticBezierMover extends Mover { public static Point p; @@ -34,7 +35,7 @@ public class QuadraticBezierMover extends Mover { } public static void setPrevspeed(double distance, int timedelta) { - prevspeed = distance * Options.getQuadBezAggressiveness() * Options.getQuadBezSliderAggressiveness() / timedelta; + prevspeed = distance * OPTION_DANCE_QUAD_BEZ_AGGRESSIVENESS.val * OPTION_DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR.val / timedelta; } public static double getPrevspeed() { @@ -53,7 +54,7 @@ public class QuadraticBezierMover extends Mover { double dist = Utils.distance(startX, startY, endX, endY); p.x = (int) (startX + Math.cos(startAngle) * prevspeed); p.y = (int) (startY + Math.sin(startAngle) * prevspeed); - prevspeed = (dist / totalTime) * Options.getQuadBezAggressiveness(); + prevspeed = (dist / totalTime) * OPTION_DANCE_QUAD_BEZ_AGGRESSIVENESS.val; } @Override diff --git a/src/yugecin/opsudance/movers/factories/AutoMoverFactory.java b/src/yugecin/opsudance/movers/factories/AutoMoverFactory.java index 2a904833..ecff7fec 100644 --- a/src/yugecin/opsudance/movers/factories/AutoMoverFactory.java +++ b/src/yugecin/opsudance/movers/factories/AutoMoverFactory.java @@ -17,13 +17,15 @@ */ package yugecin.opsudance.movers.factories; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; -import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.GameObject; import yugecin.opsudance.Pippi; import yugecin.opsudance.movers.*; +import yugecin.opsudance.options.Options; +import yugecin.opsudance.render.GameObjectRenderer; + +import static yugecin.opsudance.options.Options.*; public class AutoMoverFactory implements MoverFactory { @@ -42,8 +44,8 @@ public class AutoMoverFactory implements MoverFactory { } // stacked: circles if not too quick - int circle_stream = Options.isCircleStreams() ? 58: 85; - if (distance < Circle.diameter && ((dt > circle_stream && !Options.isOnlyCircleStacks()) || distance < HitObject.getStackOffset() * 5.2f)) { // TODO get the correct multiplier for stackoffsets + int circle_stream = OPTION_DANCE_CIRCLE_STREAMS.state ? 58: 85; + if (distance < GameObjectRenderer.instance.getCircleDiameter() && ((dt > circle_stream && !OPTION_DANCE_ONLY_CIRCLE_STACKS.state) || distance < HitObject.getStackOffset() * 5.2f)) { // TODO get the correct multiplier for stackoffsets return new CircleMover(start, end, dir); } @@ -93,16 +95,14 @@ public class AutoMoverFactory implements MoverFactory { } @SuppressWarnings("SimplifiableIfStatement") - protected boolean inbounds(Mover m ) - { + protected boolean inbounds(Mover m ) { this.m = m; if (!checkBounds(m.getPointAt((int) (starttime + (endtime - starttime) * 0.3)))) return false; if (!checkBounds(m.getPointAt((int) (starttime + (endtime - starttime) * 0.7)))) return false; return checkBounds(m.getPointAt((int) (starttime + (endtime - starttime) * 0.5))); } - private boolean checkBounds( double[] pos ) - { + private boolean checkBounds( double[] pos ) { return 0 < pos[0] && pos[0] < Options.width - Options.width / 8 && 0 < pos[1] && pos[1] < Options.height - Options.height / 8; } diff --git a/src/yugecin/opsudance/movers/factories/QuadraticBezierMoverFactory.java b/src/yugecin/opsudance/movers/factories/QuadraticBezierMoverFactory.java index 83036494..d37260ed 100644 --- a/src/yugecin/opsudance/movers/factories/QuadraticBezierMoverFactory.java +++ b/src/yugecin/opsudance/movers/factories/QuadraticBezierMoverFactory.java @@ -17,17 +17,18 @@ */ package yugecin.opsudance.movers.factories; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.objects.GameObject; import yugecin.opsudance.movers.CubicBezierMover; import yugecin.opsudance.movers.Mover; import yugecin.opsudance.movers.QuadraticBezierMover; +import static yugecin.opsudance.options.Options.*; + public class QuadraticBezierMoverFactory implements MoverFactory { @Override public Mover create(GameObject start, GameObject end, int dir) { - if (Options.isQuadBezCubicEnabled() && end.isSlider()) { + if (OPTION_DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS.state && end.isSlider()) { return new CubicBezierMover(start, end, dir); } return new QuadraticBezierMover(start, end, dir); diff --git a/src/yugecin/opsudance/objects/curves/FakeCombinedCurve.java b/src/yugecin/opsudance/objects/curves/FakeCombinedCurve.java index 788c3886..db0d4f65 100644 --- a/src/yugecin/opsudance/objects/curves/FakeCombinedCurve.java +++ b/src/yugecin/opsudance/objects/curves/FakeCombinedCurve.java @@ -20,12 +20,36 @@ package yugecin.opsudance.objects.curves; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.objects.curves.Curve; import itdelatrisu.opsu.objects.curves.Vec2f; +import itdelatrisu.opsu.render.CurveRenderState; +import org.newdawn.slick.Color; + +import java.util.ArrayList; +import java.util.List; public class FakeCombinedCurve extends Curve { + private List pointsToRender; + public FakeCombinedCurve(Vec2f[] points) { super(new HitObject(0, 0, 0), false); this.curve = points; + pointsToRender = new ArrayList<>(); + } + + public void initForFrame() { + pointsToRender.clear(); + } + + public void addRange(int from, int to) { + pointsToRender.add(from); + pointsToRender.add(to); + } + + @Override + public void draw(Color color) { + if (renderState == null) + renderState = new CurveRenderState(hitObject, curve, true); + renderState.draw(color, borderColor, pointsToRender); } @Override diff --git a/src/yugecin/opsudance/options/Configuration.java b/src/yugecin/opsudance/options/Configuration.java new file mode 100644 index 00000000..393725ad --- /dev/null +++ b/src/yugecin/opsudance/options/Configuration.java @@ -0,0 +1,304 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +import com.sun.jna.platform.win32.Advapi32Util; +import com.sun.jna.platform.win32.Win32Exception; +import com.sun.jna.platform.win32.WinReg; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.audio.SoundController; +import itdelatrisu.opsu.audio.SoundEffect; +import itdelatrisu.opsu.beatmap.Beatmap; +import itdelatrisu.opsu.beatmap.TimingPoint; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.GL11; +import yugecin.opsudance.core.errorhandling.ErrorHandler; +import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.events.BubbleNotificationEvent; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static yugecin.opsudance.options.Options.*; + +public class Configuration { + + public final boolean USE_XDG; + public final File CONFIG_DIR; + public final File DATA_DIR; + public final File CACHE_DIR; + public final File BEATMAP_DIR; + public final File SKIN_ROOT_DIR; + public final File BEATMAP_DB; + public final File SCORE_DB; + public final File NATIVE_DIR; + public final File TEMP_DIR; + + public final File LOG_FILE; + public final File OPTIONS_FILE; + + public final String FONT_NAME; + public final String VERSION_FILE; + public final URI REPOSITORY_URI; + public final URI DANCE_REPOSITORY_URI; + public final String ISSUES_URL; + public final String VERSION_REMOTE; + + public final File osuInstallationDirectory; + + public final Beatmap themeBeatmap; + + public File beatmapDir; + public File oszDir; + public File screenshotDir; + public File replayDir; + public File replayImportDir; + public File skinRootDir; + + @Inject + public Configuration() { + USE_XDG = areXDGDirectoriesEnabled(); + + CONFIG_DIR = getXDGBaseDir("XDG_CONFIG_HOME", ".config"); + DATA_DIR = getXDGBaseDir("XDG_DATA_HOME", ".local/share"); + CACHE_DIR = getXDGBaseDir("XDG_CACHE_HOME", ".cache"); + + BEATMAP_DIR = new File(DATA_DIR, "Songs/"); + SKIN_ROOT_DIR = new File(DATA_DIR, "Skins/"); + BEATMAP_DB = new File(DATA_DIR, ".opsu.db"); + SCORE_DB = new File(DATA_DIR, ".opsu_scores.db"); + NATIVE_DIR = new File(CACHE_DIR, "Natives/"); + TEMP_DIR = new File(CACHE_DIR, "Temp/"); + + LOG_FILE = new File(CONFIG_DIR, ".opsu.log"); + OPTIONS_FILE = new File(CONFIG_DIR, ".opsu.cfg"); + + FONT_NAME = "DroidSansFallback.ttf"; + VERSION_FILE = "version"; + REPOSITORY_URI = URI.create("https://github.com/itdelatrisu/opsu"); + DANCE_REPOSITORY_URI = URI.create("https://github.com/yugecin/opsu-dance"); + ISSUES_URL = "https://github.com/yugecin/opsu-dance/issues/new?title=%s&body=%s"; + VERSION_REMOTE = "https://raw.githubusercontent.com/yugecin/opsu-dance/master/version"; + + osuInstallationDirectory = loadOsuInstallationDirectory(); + + themeBeatmap = createThemeBeatmap(); + } + + private Beatmap createThemeBeatmap() { + try { + String[] tokens = {"theme.mp3", "Rainbows", "Kevin MacLeod", "219350"}; + Beatmap beatmap = new Beatmap(null); + beatmap.audioFilename = new File(tokens[0]); + beatmap.title = tokens[1]; + beatmap.artist = tokens[2]; + beatmap.endTime = Integer.parseInt(tokens[3]); + beatmap.timingPoints = new ArrayList<>(1); + beatmap.timingPoints.add(new TimingPoint("1080,545.454545454545,4,1,0,100,0,0")); + return beatmap; + } catch (Exception e) { + return null; + } + + } + + private File loadOsuInstallationDirectory() { + if (!System.getProperty("os.name").startsWith("Win")) { + return null; + } + + final WinReg.HKEY rootKey = WinReg.HKEY_CLASSES_ROOT; + final String regKey = "osu\\DefaultIcon"; + final String regValue = null; // default value + final String regPathPattern = "\"(.+)\\\\[^\\/]+\\.exe\""; + + String value; + try { + value = Advapi32Util.registryGetStringValue(rootKey, regKey, regValue); + } catch (Win32Exception ignored) { + return null; + } + Pattern pattern = Pattern.compile(regPathPattern); + Matcher m = pattern.matcher(value); + if (!m.find()) { + return null; + } + File dir = new File(m.group(1)); + if (dir.isDirectory()) { + return dir; + } + return null; + } + + public void loadDirectories() { + replayImportDir = loadDirectory(replayImportDir, new File(DATA_DIR, "ReplayImport"), "replay import"); + oszDir = loadDirectory(oszDir, new File(DATA_DIR, "SongPacks"), "song packs"); + screenshotDir = loadDirectory(screenshotDir, new File(DATA_DIR, "Screenshots"), "screenshots"); + replayDir = loadDirectory(replayDir, new File(DATA_DIR, "Replays"), "replays"); + beatmapDir = loadOsuDirectory(beatmapDir, BEATMAP_DIR, "beatmap"); + skinRootDir = loadOsuDirectory(skinRootDir, SKIN_ROOT_DIR, "skin root"); + } + + private File loadDirectory(File dir, File defaultDir, String kind) { + if (dir.exists() && dir.isDirectory()) { + return dir; + } + if (!defaultDir.isDirectory() && !defaultDir.mkdir()) { + String msg = String.format("Failed to create %s directory at '%s'.", kind, defaultDir.getAbsolutePath()); + EventBus.post(new BubbleNotificationEvent(msg, BubbleNotificationEvent.COMMONCOLOR_RED)); + } + return defaultDir; + } + + private File loadOsuDirectory(File dir, File defaultDir, String kind) { + if (dir != null && dir.isDirectory()) { + return dir; + } + + if (osuInstallationDirectory != null) { + dir = new File(osuInstallationDirectory, defaultDir.getName()); + if (dir.isDirectory()) { + return dir; + } + } + + return loadDirectory(dir, defaultDir, kind); + } + + private boolean areXDGDirectoriesEnabled() { + JarFile jarFile = Utils.getJarFile(); + if (jarFile == null) { + return false; + } + try { + Manifest manifest = jarFile.getManifest(); + if (manifest == null) { + return false; + } + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue("Use-XDG"); + return (value != null && value.equalsIgnoreCase("true")); + } catch (IOException e) { + return false; + } + } + + /** + * Returns the directory based on the XDG base directory specification for + * Unix-like operating systems, only if the "XDG" flag is enabled. + * @param env the environment variable to check (XDG_*_*) + * @param fallback the fallback directory relative to ~home + * @return the XDG base directory, or the working directory if unavailable + */ + private File getXDGBaseDir(String env, String fallback) { + File workingdir; + if (Utils.isJarRunning()) { + workingdir = Utils.getRunningDirectory().getParentFile(); + } else { + workingdir = Paths.get(".").toAbsolutePath().normalize().toFile(); + } + + if (!USE_XDG) { + return workingdir; + } + + String OS = System.getProperty("os.name").toLowerCase(); + if (OS.indexOf("nix") == -1 && OS.indexOf("nux") == -1 && OS.indexOf("aix") == -1){ + return workingdir; + } + + String rootPath = System.getenv(env); + if (rootPath == null) { + String home = System.getProperty("user.home"); + if (home == null) { + return new File("./"); + } + rootPath = String.format("%s/%s", home, fallback); + } + File dir = new File(rootPath, "opsu"); + if (!dir.isDirectory() && !dir.mkdir()) { + ErrorHandler.error(String.format("Failed to create configuration folder at '%s/opsu'.", rootPath), new Exception("empty")).preventReport().show(); + } + return dir; + } + + /** + * @author http://wiki.lwjgl.org/index.php?title=Taking_Screen_Shots + */ + public void takeScreenShot() { + // TODO: get a decent place for this + // create the screenshot directory + if (!screenshotDir.isDirectory() && !screenshotDir.mkdir()) { + EventBus.post(new BubbleNotificationEvent(String.format("Failed to create screenshot directory at '%s'.", screenshotDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED)); + return; + } + + // create file name + SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss"); + final String fileName = String.format("screenshot_%s.%s", date.format(new Date()), OPTION_SCREENSHOT_FORMAT.getValueString().toLowerCase()); + final File file = new File(screenshotDir, fileName); + + SoundController.playSound(SoundEffect.SHUTTER); + + // copy the screen to file + final int width = Display.getWidth(); + final int height = Display.getHeight(); + final int bpp = 3; // assuming a 32-bit display with a byte each for red, green, blue, and alpha + final ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); + GL11.glReadBuffer(GL11.GL_FRONT); + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + new Thread() { + @Override + public void run() { + try { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int i = (x + (width * y)) * bpp; + int r = buffer.get(i) & 0xFF; + int g = buffer.get(i + 1) & 0xFF; + int b = buffer.get(i + 2) & 0xFF; + image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); + } + } + ImageIO.write(image, OPTION_SCREENSHOT_FORMAT.getValueString().toLowerCase(), file); + EventBus.post(new BubbleNotificationEvent("Created " + fileName, BubbleNotificationEvent.COMMONCOLOR_PURPLE)); + } catch (Exception e) { + ErrorHandler.error("Failed to take a screenshot.", e).show(); + } + } + }.start(); + } + +} diff --git a/src/yugecin/opsudance/options/GenericOption.java b/src/yugecin/opsudance/options/GenericOption.java new file mode 100644 index 00000000..7b585e79 --- /dev/null +++ b/src/yugecin/opsudance/options/GenericOption.java @@ -0,0 +1,40 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +public abstract class GenericOption extends Option { + + public int intval; + public String strval; + public boolean boolval; + + public GenericOption(String name, String configurationName, String description, int intval, String strval, boolean boolval) { + super(name, configurationName, description); + this.intval = intval; + this.strval = strval; + this.boolval = boolval; + } + + @Override + public abstract String getValueString(); + @Override + public abstract void read(String s); + @Override + public abstract String write(); + +} diff --git a/src/yugecin/opsudance/options/ListOption.java b/src/yugecin/opsudance/options/ListOption.java new file mode 100644 index 00000000..5c0c5808 --- /dev/null +++ b/src/yugecin/opsudance/options/ListOption.java @@ -0,0 +1,33 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +public abstract class ListOption extends Option { + + public ListOption(String name, String configurationName, String description) { + super(name, configurationName, description); + } + + @Override + public abstract String write(); + @Override + public abstract void read(String s); + public abstract Object[] getListItems(); + public abstract void clickListItem(int index); + +} diff --git a/src/yugecin/opsudance/options/NumericOption.java b/src/yugecin/opsudance/options/NumericOption.java new file mode 100644 index 00000000..84466f11 --- /dev/null +++ b/src/yugecin/opsudance/options/NumericOption.java @@ -0,0 +1,60 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +import itdelatrisu.opsu.Utils; +import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.events.BubbleNotificationEvent; + +public class NumericOption extends Option { + + public final int min; + public final int max; + public int val; + + public NumericOption(String name, String configurationName, String description, int val, int min, int max) { + super(name, configurationName, description); + this.min = min; + this.max = max; + this.val = val; + } + + public void setValue(int val) { + this.val = val; + } + + @Override + public String getValueString() { + return String.format("%d%%", val); + } + + @Override + public String write() { + return Integer.toString(val); + } + + @Override + public void read(String s) { + try { + val = Utils.clamp(Integer.parseInt(s), min, max); + } catch (Exception ignored) { + EventBus.post(new BubbleNotificationEvent("Failed to parse " + configurationName + " option", BubbleNotificationEvent.COMMONCOLOR_RED)); + } + } + +} diff --git a/src/yugecin/opsudance/options/Option.java b/src/yugecin/opsudance/options/Option.java new file mode 100644 index 00000000..cb379138 --- /dev/null +++ b/src/yugecin/opsudance/options/Option.java @@ -0,0 +1,102 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.core.inject.InstanceContainer; + +public class Option { + + // keep a reference to the instancecontainer so that not every option instance needs to be injected + protected static InstanceContainer instanceContainer; + // caching some commonly used classes + protected static Configuration config; + protected static DisplayContainer displayContainer; + public static void setInstanceContainer(InstanceContainer instanceContainer) { + Option.instanceContainer = instanceContainer; + Option.config = instanceContainer.provide(Configuration.class); + Option.displayContainer = instanceContainer.provide(DisplayContainer.class); + } + + public final String name; + public final String configurationName; + public final String description; + + /** + * If this option should not be shown in the optionsmenu because it does + * not match the search string. + */ + private boolean filtered; + + /** + * Constructor for internal options (not displayed in-game). + */ + public Option(String configurationName) { + this(null, configurationName, null); + } + + public Option(String name, String configurationName, String description) { + this.name = name; + this.configurationName = configurationName; + this.description = description; + OptionsService.registerOption(this); + } + + /** + * should the option be shown + * @return true if the option should be shown + */ + public boolean showCondition() { + return true; + } + + public String getValueString() { + return ""; + } + + public String write() { + return getValueString(); + } + + public void read(String s) { + } + + /** + * Update the filtered flag for this option based on the given searchString. + * @param searchString the searched string or null to reset the filtered flag + * @return true if this option does need to be filtered + */ + public boolean filter(String searchString) { + if (searchString == null || searchString.length() == 0) { + filtered = false; + return false; + } + filtered = !name.toLowerCase().contains(searchString) && !description.toLowerCase().contains(searchString); + return filtered; + } + + /** + * Check if this option should be filtered (= not shown) because it does not + * match the search string. + * @return true if the option shouldn't be shown. + */ + public boolean isFiltered() { + return filtered; + } + +} diff --git a/src/yugecin/opsudance/options/OptionGroups.java b/src/yugecin/opsudance/options/OptionGroups.java new file mode 100644 index 00000000..089865e9 --- /dev/null +++ b/src/yugecin/opsudance/options/OptionGroups.java @@ -0,0 +1,239 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014, 2015 Jeffrey Han + * + * opsu! is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu! is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!. If not, see . + */ + +package yugecin.opsudance.options; + +import static yugecin.opsudance.options.Options.*; + +public class OptionGroups { + + public static final OptionTab[] normalOptions = new OptionTab[] { + new OptionTab("GENERAL", null), + new OptionTab("GENERAL", new Option[]{ + OPTION_DISABLE_UPDATER, + OPTION_ENABLE_WATCH_SERVICE + }), + new OptionTab("LANGUAGE", new Option[]{ + OPTION_SHOW_UNICODE, + }), + new OptionTab("GRAPHICS", null), + new OptionTab("RENDERER", new Option[] { + OPTION_SCREEN_RESOLUTION, + OPTION_ALLOW_LARGER_RESOLUTIONS, + OPTION_FULLSCREEN, + OPTION_TARGET_UPS, + OPTION_TARGET_FPS, + OPTION_SHOW_FPS, + OPTION_USE_FPS_DELTAS, + OPTION_SCREENSHOT_FORMAT, + }), + new OptionTab("SLIDER OPTIONS", new Option[]{ + OPTION_SNAKING_SLIDERS, + OPTION_FALLBACK_SLIDERS, + OPTION_SHRINKING_SLIDERS, + OPTION_MERGING_SLIDERS, + //OPTION_MERGING_SLIDERS_MIRROR_POOL, + OPTION_DRAW_SLIDER_ENDCIRCLES, + }), + new OptionTab("DANCING HITCIRCLES", new Option[] { + OPTION_DANCING_CIRCLES, + OPTION_DANCING_CIRCLES_MULTIPLIER, + }), + new OptionTab("SKIN", null), + new OptionTab("SKIN", new Option[]{ + OPTION_SKIN, + OPTION_IGNORE_BEATMAP_SKINS, + OPTION_DYNAMIC_BACKGROUND, + OPTION_LOAD_HD_IMAGES, + OPTION_LOAD_VERBOSE, + OPTION_COLOR_MAIN_MENU_LOGO, + }), + new OptionTab("CURSOR", new Option[]{ + OPTION_CURSOR_SIZE, + OPTION_NEW_CURSOR, + OPTION_DISABLE_CURSOR + // TODO use combo colour as tint for slider ball option + }), + new OptionTab("AUDIO", null), + new OptionTab("VOLUME", new Option[]{ + OPTION_MASTER_VOLUME, + OPTION_MUSIC_VOLUME, + OPTION_EFFECT_VOLUME, + OPTION_HITSOUND_VOLUME, + OPTION_SAMPLE_VOLUME_OVERRIDE, + }), + new OptionTab("MISC", new Option[] { + OPTION_MUSIC_OFFSET, + OPTION_DISABLE_SOUNDS, + OPTION_ENABLE_THEME_SONG + }), + new OptionTab("GAMEPLAY", null), + new OptionTab("GENERAL", new Option[] { + OPTION_BACKGROUND_DIM, + OPTION_FORCE_DEFAULT_PLAYFIELD, + OPTION_SHOW_HIT_LIGHTING, + OPTION_SHOW_HIT_ANIMATIONS, + OPTION_SHOW_COMBO_BURSTS, + OPTION_SHOW_PERFECT_HIT, + OPTION_SHOW_FOLLOW_POINTS, + OPTION_SHOW_HIT_ERROR_BAR, + OPTION_MAP_START_DELAY, + OPTION_MAP_END_DELAY, + OPTION_EPILEPSY_WARNING, + }), + new OptionTab("INPUT", null), + new OptionTab("KEY MAPPING", new Option[]{ + OPTION_KEY_LEFT, + OPTION_KEY_RIGHT, + }), + new OptionTab("MOUSE", new Option[] { + OPTION_DISABLE_MOUSE_WHEEL, + OPTION_DISABLE_MOUSE_BUTTONS, + }), + new OptionTab("CUSTOM", null), + new OptionTab("DIFFICULTY", new Option[]{ + OPTION_FIXED_CS, + OPTION_FIXED_HP, + OPTION_FIXED_AR, + OPTION_FIXED_OD, + }), + new OptionTab("MISC", new Option[] { + OPTION_CHECKPOINT, + OPTION_REPLAY_SEEKING, + }), + new OptionTab("DANCE", null), + new OptionTab("MOVER", new Option[]{ + OPTION_DANCE_MOVER, + OPTION_DANCE_EXGON_DELAY, + OPTION_DANCE_QUAD_BEZ_AGGRESSIVENESS, + OPTION_DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR, + OPTION_DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS, + OPTION_DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR, + OPTION_DANCE_MOVER_DIRECTION, + OPTION_DANCE_SLIDER_MOVER_TYPE, + }), + new OptionTab("SPINNER", new Option[]{ + OPTION_DANCE_SPINNER, + OPTION_DANCE_SPINNER_DELAY, + }), + new OptionTab("SLIDER OPTIONS", new Option[]{ + OPTION_DANCE_LAZY_SLIDERS, + OPTION_DANCE_CIRLCE_IN_SLOW_SLIDERS, + OPTION_DANCE_CIRLCE_IN_LAZY_SLIDERS, + }), + new OptionTab("CIRCLE MOVEMENTS", new Option[]{ + OPTION_DANCE_CIRCLE_STREAMS, + OPTION_DANCE_ONLY_CIRCLE_STACKS, + }), + new OptionTab("MIRROR", new Option[] { + OPTION_DANCE_MIRROR, + }), + new OptionTab("ADVANCED DISPLAY", null), + new OptionTab("OBJECTS", new Option[]{ + OPTION_DANCE_DRAW_APPROACH, + OPTION_DANCE_OBJECT_COLOR_OVERRIDE, + OPTION_DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED, + OPTION_DANCE_RGB_OBJECT_INC, + OPTION_DANCE_HIDE_OBJECTS, + }), + new OptionTab("CURSOR", new Option[]{ + OPTION_DANCE_CURSOR_COLOR_OVERRIDE, + OPTION_DANCE_CURSOR_MIRROR_COLOR_OVERRIDE, + OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL, + OPTION_DANCE_RGB_CURSOR_INC, + OPTION_DANCE_CURSOR_TRAIL_OVERRIDE, + }), + new OptionTab("MISC", new Option[] { + OPTION_DANCE_HIDE_UI, + OPTION_DANCE_REMOVE_BG, + OPTION_DANCE_ENABLE_SB, + }), + new OptionTab ("PIPPI", null), + new OptionTab ("GENERAL", new Option[]{ + OPTION_PIPPI_ENABLE, + OPTION_PIPPI_RADIUS_PERCENT, + }), + new OptionTab ("ANGLE MULTIPLIERS", new Option[]{ + OPTION_PIPPI_ANGLE_INC_MUL, + OPTION_PIPPI_ANGLE_INC_MUL_SLIDER, + }), + new OptionTab ("MISC", new Option[] { + OPTION_PIPPI_SLIDER_FOLLOW_EXPAND, + OPTION_PIPPI_PREVENT_WOBBLY_STREAMS, + }) + }; + + public static final OptionTab[] storyboardOptions = new OptionTab[] { + new OptionTab("Gameplay", new Option[] { + OPTION_BACKGROUND_DIM, + OPTION_DANCE_REMOVE_BG, + OPTION_SNAKING_SLIDERS, + OPTION_SHRINKING_SLIDERS, + OPTION_SHOW_HIT_LIGHTING, + OPTION_SHOW_HIT_ANIMATIONS, + OPTION_SHOW_COMBO_BURSTS, + OPTION_SHOW_PERFECT_HIT, + OPTION_SHOW_FOLLOW_POINTS, + }), + new OptionTab("Input", new Option[] { + OPTION_CURSOR_SIZE, + OPTION_NEW_CURSOR, + OPTION_DISABLE_CURSOR + }), + new OptionTab("Dance", new Option[] { + OPTION_DANCE_MOVER, + OPTION_DANCE_EXGON_DELAY, + OPTION_DANCE_QUAD_BEZ_AGGRESSIVENESS, + OPTION_DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR, + OPTION_DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS, + OPTION_DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR, + OPTION_DANCE_MOVER_DIRECTION, + OPTION_DANCE_SLIDER_MOVER_TYPE, + OPTION_DANCE_SPINNER, + OPTION_DANCE_SPINNER_DELAY, + OPTION_DANCE_LAZY_SLIDERS, + OPTION_DANCE_CIRCLE_STREAMS, + OPTION_DANCE_ONLY_CIRCLE_STACKS, + OPTION_DANCE_CIRLCE_IN_SLOW_SLIDERS, + OPTION_DANCE_CIRLCE_IN_LAZY_SLIDERS, + OPTION_DANCE_MIRROR, + }), + new OptionTab("Dance display", new Option[] { + OPTION_DANCE_DRAW_APPROACH, + OPTION_DANCE_OBJECT_COLOR_OVERRIDE, + OPTION_DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED, + OPTION_DANCE_RGB_OBJECT_INC, + OPTION_DANCE_CURSOR_COLOR_OVERRIDE, + OPTION_DANCE_CURSOR_MIRROR_COLOR_OVERRIDE, + OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL, + OPTION_DANCE_RGB_CURSOR_INC, + OPTION_DANCE_CURSOR_TRAIL_OVERRIDE, + OPTION_DANCE_HIDE_OBJECTS, + OPTION_DANCE_HIDE_UI, + }), + new OptionTab ("Pippi", new Option[] { + OPTION_PIPPI_ENABLE, + OPTION_PIPPI_RADIUS_PERCENT, + OPTION_PIPPI_ANGLE_INC_MUL, + OPTION_PIPPI_ANGLE_INC_MUL_SLIDER, + OPTION_PIPPI_SLIDER_FOLLOW_EXPAND, + OPTION_PIPPI_PREVENT_WOBBLY_STREAMS, + }) + }; + +} diff --git a/src/yugecin/opsudance/options/OptionTab.java b/src/yugecin/opsudance/options/OptionTab.java new file mode 100644 index 00000000..7db8dcd4 --- /dev/null +++ b/src/yugecin/opsudance/options/OptionTab.java @@ -0,0 +1,31 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +public class OptionTab { + + public final String name; + public final Option[] options; + public boolean filtered; + + public OptionTab(String name, Option[] options) { + this.name = name; + this.options = options; + } + +} diff --git a/src/yugecin/opsudance/options/Options.java b/src/yugecin/opsudance/options/Options.java new file mode 100644 index 00000000..32a66003 --- /dev/null +++ b/src/yugecin/opsudance/options/Options.java @@ -0,0 +1,978 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +import awlex.ospu.polymover.factory.PolyMoverFactory; +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.states.Game; +import itdelatrisu.opsu.ui.Fonts; +import org.lwjgl.input.Keyboard; +import org.newdawn.slick.Input; +import org.newdawn.slick.SlickException; +import org.newdawn.slick.openal.SoundStore; +import org.newdawn.slick.util.Log; +import yugecin.opsudance.*; +import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.events.EventListener; +import yugecin.opsudance.events.BarNotificationEvent; +import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; +import yugecin.opsudance.movers.factories.ExgonMoverFactory; +import yugecin.opsudance.movers.factories.QuadraticBezierMoverFactory; +import yugecin.opsudance.movers.slidermovers.DefaultSliderMoverController; +import yugecin.opsudance.skinning.SkinService; +import yugecin.opsudance.utils.CachedVariable; +import yugecin.opsudance.utils.CachedVariable.Getter; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +/** + * @author itdelatrisu (https://github.com/itdelatrisu) most functions are copied from itdelatrisu.opsu.Options.java + */ +public class Options { + + // TODO remove this? + public static int width; + public static int height; + + static { + EventBus.subscribe(ResolutionOrSkinChangedEvent.class, new EventListener() { + @Override + public void onEvent(ResolutionOrSkinChangedEvent event) { + if (event.width > 0) { + width = event.width; + height = event.height; + } + } + }); + } + + // internal options (not displayed in-game) + public static final Option OPTION_BEATMAP_DIRECTORY = new Option("BeatmapDirectory") { + @Override + public String write() { + return config.BEATMAP_DIR.getAbsolutePath(); + } + + @Override + public void read(String s) { + config.beatmapDir = new File(s); + } + }; + + public static final Option OPTION_OSZ_DIRECTORY = new Option("OSZDirectory") { + @Override + public String write() { + return config.oszDir.getAbsolutePath(); + } + + @Override + public void read(String s) { + config.oszDir = new File(s); + } + }; + + public static final Option OPTION_SCREENSHOT_DIRECTORY = new Option("ScreenshotDirectory") { + @Override + public String write() { + return config.screenshotDir.getAbsolutePath(); + } + + @Override + public void read(String s) { + config.screenshotDir = new File(s); + } + }; + + public static final Option OPTION_REPLAY_DIRECTORY = new Option("ReplayDirectory") { + @Override + public String write() { + return config.replayDir.getAbsolutePath(); + } + + @Override + public void read(String s) { + config.replayDir = new File(s); + } + }; + + public static final Option OPTION_REPLAY_IMPORT_DIRECTORY = new Option("ReplayImportDirectory") { + @Override + public String write() { + return config.replayImportDir.getAbsolutePath(); + } + + @Override + public void read(String s) { + config.replayImportDir = new File(s); + } + }; + + public static final Option OPTION_SKIN_DIRECTORY = new Option("SkinDirectory") { + @Override + public String write() { + return config.skinRootDir.getAbsolutePath(); + } + + @Override + public void read(String s) { + config.skinRootDir = new File(s); + } + }; + + public static final NumericOption OPTION_PORT = new NumericOption("-", "Port", "-", 49250, 1024, 65535) { + @Override + public void read (String s){ + super.read(s); + } + }; + + public static final ToggleOption OPTION_NOSINGLEINSTANCE = new ToggleOption("-", "NoSingleInstance", "-", false); + + // in-game options + public static final Option OPTION_SCREEN_RESOLUTION = new ListOption("Screen Resolution", "ScreenResolution", "Change the size of the game.") { + private final String[] resolutions = { + null, + "800x600", + "1024x600", + "1024x768", + "1280x720", + "1280x800", + "1280x960", + "1280x1024", + "1366x768", + "1440x900", + "1600x900", + "1600x1200", + "1680x1050", + "1920x1080", + "1920x1200", + "2560x1440", + "2560x1600", + "3840x2160" + }; + + private int idx; + + @Override + public String getValueString () { + return resolutions[idx]; // do not change (see DisplayContainer#setup) + } + + @Override + public Object[] getListItems () { + return resolutions; + } + + @Override + public void clickListItem(int index){ + idx = index; + displayContainer.updateDisplayMode(resolutions[idx]); + } + + @Override + public void read (String s){ + resolutions[0] = displayContainer.nativeDisplayMode.getWidth() + "x" + displayContainer.nativeDisplayMode.getHeight(); + try { + idx = Integer.parseInt(s); + } catch (Exception ignored) { + } + idx = Utils.clamp(idx, 0, resolutions.length); + } + + @Override + public String write () { + return String.valueOf(idx); + } + }; + + public static final ToggleOption OPTION_ALLOW_LARGER_RESOLUTIONS = new ToggleOption("Allow large resolutions", "AllowLargeRes", "Allow resolutions larger than the native resolution", false); + public static final ToggleOption OPTION_FULLSCREEN = new ToggleOption("Fullscreen Mode", "Fullscreen", "Restart to apply changes.", false); + public static final ListOption OPTION_SKIN = new ListOption("Skin", "Skin", "Change how the game looks.") { + private CachedVariable skinService = new CachedVariable<>(new Getter() { + @Override + public SkinService get() { + return instanceContainer.provide(SkinService.class); + } + }); + + @Override + public String getValueString () { + return skinService.get().usedSkinName; + } + + @Override + public Object[] getListItems () { + return skinService.get().availableSkinDirectories; + } + + @Override + public void clickListItem(int index){ + skinService.get().usedSkinName = skinService.get().availableSkinDirectories[index]; + skinService.get().reloadSkin(); + } + + @Override + public void read (String s){ + skinService.get().usedSkinName = s; + } + + @Override + public String write() { + return skinService.get().usedSkinName; + } + }; + + public static final NumericOption OPTION_TARGET_UPS = new NumericOption("target UPS", "targetUPS", "Higher values result in less input lag and smoother cursor trail, but may cause high CPU usage.", 480, 20, 1000) { + @Override + public String getValueString () { + return String.format("%dups", val); + } + + @Override + public void setValue ( int value){ + super.setValue(value); + displayContainer.setUPS(value); + } + }; + + public static final int[] targetFPS = {60, 120, 240, 1000}; + public static int targetFPSIndex = 0; + + public static final ListOption OPTION_TARGET_FPS = new ListOption("FPS limit", "FPSlimit", "Higher values may cause high CPU usage. A value higher than the UPS has no effect.") { + private CachedVariable $_getListItems = new CachedVariable<>(new Getter() { + @Override + public String[] get() { + String[] list = new String[targetFPS.length]; + for (int i = 0; i < targetFPS.length; i++) { + list[i] = String.format("%dfps", targetFPS[i]); + } + return list; + } + }); + + @Override + public String getValueString () { + return $_getListItems.get()[targetFPSIndex]; + } + + @Override + public Object[] getListItems () { + return $_getListItems.get(); + } + + @Override + public void clickListItem(int index){ + targetFPSIndex = index; + displayContainer.setFPS(targetFPS[targetFPSIndex]); + } + + @Override + public String write () { + return Integer.toString(targetFPS[targetFPSIndex]); + } + + @Override + public void read (String s){ + int i = Integer.parseInt(s); + for (int j = 0; j < targetFPS.length; j++) { + if (i == targetFPS[j]) { + targetFPSIndex = j; + break; + } + } + } + }; + + public static final ToggleOption OPTION_SHOW_FPS = new ToggleOption("Show FPS Counters", "FpsCounter", "Show FPS and UPS counters in the bottom-right hand corner.", true); + public static final ToggleOption OPTION_USE_FPS_DELTAS = new ToggleOption("Use deltas for FPS counters", "FpsCounterDeltas", "Show time between updates instead of updates per second.", false) { + @Override + public boolean showCondition () { + return OPTION_SHOW_FPS.state; + } + }; + + public static final ToggleOption OPTION_SHOW_UNICODE = new ToggleOption("Prefer Non-English Metadata", "ShowUnicode", "Where available, song titles will be shown in their native language.", false) { + @Override + public void toggle () { + super.toggle(); + if (!state) { + return; + } + try { + Fonts.LARGE.loadGlyphs(); + Fonts.MEDIUM.loadGlyphs(); + Fonts.DEFAULT.loadGlyphs(); + } catch (SlickException e) { + Log.warn("Failed to load glyphs.", e); + } + } + }; + + public static final ListOption OPTION_SCREENSHOT_FORMAT = new ListOption("Screenshot Format", "ScreenshotFormat", "Press F12 to take a screenshot.") { + private String[] formats = { "PNG", "JPG", "BMP" }; + private int index = 0; + + @Override + public String getValueString () { + return formats[index]; + } + + @Override + public Object[] getListItems () { + return formats; + } + + @Override + public void clickListItem(int index){ + this.index = index; + } + + @Override + public String write () { + return Integer.toString(index); + } + + @Override + public void read (String s){ + int i = Integer.parseInt(s); + if (0 <= i && i < formats.length) { + index = i; + } + } + }; + + public static final NumericOption OPTION_CURSOR_SIZE = new NumericOption("Size", "CursorSize", "Change the cursor scale.", 100, 50, 200) { + @Override + public String getValueString () { + return String.format("%.2fx", val / 100f); + } + + @Override + public String write () { + return String.format("%.2f", val / 100f); + } + + @Override + public void read (String s){ + int i = (int) (Float.parseFloat(s.replace(',', '.')) * 100f); + if (i >= 50 && i <= 200) + val = i; + } + }; + + public static final ToggleOption OPTION_NEW_CURSOR = new ToggleOption("Enable New Cursor", "NewCursor", "Use the new cursor style (may cause higher CPU usage).", true); + public static final ToggleOption OPTION_DYNAMIC_BACKGROUND = new ToggleOption("Enable Dynamic Backgrounds", "DynamicBackground", "The song background will be used as the main menu background.", true); + public static final ToggleOption OPTION_LOAD_VERBOSE = new ToggleOption("Show Detailed Loading Progress", "LoadVerbose", "Display more specific loading information in the splash screen.", false); + public static final ToggleOption OPTION_COLOR_MAIN_MENU_LOGO = new ToggleOption("Use cursor color as main menu logo tint", "ColorMainMenuLogo", "Colorful main menu logo", false); + public static final NumericOption OPTION_MASTER_VOLUME = new NumericOption("Master", "VolumeUniversal", "Global volume level.", 35, 0, 100) { + @Override + public void setValue(int value){ + super.setValue(value); + // changing mastervolume, so music volume should change too + OPTION_MUSIC_VOLUME.setValue(OPTION_MUSIC_VOLUME.val); + } + }; + + public static final NumericOption OPTION_MUSIC_VOLUME = new NumericOption("Music", "VolumeMusic", "Volume of music.", 80, 0, 100) { + @Override + public void setValue(int value){ + super.setValue(value); + SoundStore.get().setMusicVolume(OPTION_MASTER_VOLUME.val * OPTION_MUSIC_VOLUME.val / 10000f); + } + }; + + public static final NumericOption OPTION_SAMPLE_VOLUME_OVERRIDE = new NumericOption("Sample override", "BMSampleOverride", "Override beatmap hitsound volume", 100, 0, 100) { + @Override + public String getValueString () { + if (val == 0) { + return "Disabled"; + } + return super.getValueString(); + } + }; + + 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) { + @Override + public String getValueString () { + return String.format("%dms", val); + } + }; + + public static final ToggleOption OPTION_DISABLE_SOUNDS = new ToggleOption("Disable All Sound Effects", "DisableSound", "May resolve Linux sound driver issues. Requires a restart.", (System.getProperty("os.name").toLowerCase().contains("linux"))); + public static final GenericOption OPTION_KEY_LEFT = new GenericOption("Left Game Key", "keyOsuLeft", "Select this option to input a key.", Input.KEY_Z, null, false) { + @Override + public String getValueString () { + return Keyboard.getKeyName(intval); + } + + @Override + public String write () { + return Keyboard.getKeyName(intval); + } + + @Override + public void read(String s){ + intval = Keyboard.getKeyIndex(s); + if (intval == Keyboard.KEY_NONE) { + intval = Input.KEY_Y; + } + } + }; + + public static final GenericOption OPTION_KEY_RIGHT = new GenericOption("Right Game Key", "keyOsuRight", "Select this option to input a key.", Input.KEY_X, null, false) { + @Override + public String getValueString () { + return Keyboard.getKeyName(intval); + } + + @Override + public String write () { + return Keyboard.getKeyName(intval); + } + + @Override + public void read(String s){ + intval = Keyboard.getKeyIndex(s); + if (intval == Keyboard.KEY_NONE) { + intval = Input.KEY_X; + } + } + }; + + public static final NumericOption OPTION_BACKGROUND_DIM = new NumericOption("Background Dim", "DimLevel", "Percentage to dim the background image during gameplay.", 50, 0, 100); + public static final ToggleOption OPTION_DISABLE_MOUSE_WHEEL = new ToggleOption("Disable mouse wheel in play mode", "MouseDisableWheel", "During play, you can use the mouse wheel to adjust the volume and pause the game. This will disable that functionality.", false); + public static final ToggleOption OPTION_DISABLE_MOUSE_BUTTONS = new ToggleOption("Disable mouse buttons in play mode", "MouseDisableButtons", "This option will disable all mouse buttons. Specifically for people who use their keyboard to click.", false) { + @Override + public void toggle() { + EventBus.post(new BarNotificationEvent(state ? "Mouse buttons are disabled." : "Mouse buttons are enabled.")); + } + }; + public static final ToggleOption OPTION_DISABLE_CURSOR = new ToggleOption("Disable Cursor", "DisableCursor", "Hide the cursor sprite.", false); + public static final ToggleOption OPTION_DANCE_REMOVE_BG = new ToggleOption("Use black background instead of image", "RemoveBG", "Hello darkness my old friend", true); + public static final ToggleOption OPTION_FORCE_DEFAULT_PLAYFIELD = new ToggleOption("Force Default Playfield", "ForceDefaultPlayfield", "Override the song background with the default playfield background.", false); + public static final ToggleOption OPTION_IGNORE_BEATMAP_SKINS = new ToggleOption("Ignore All Beatmap Skins", "IgnoreBeatmapSkins", "Never use skin element overrides provided by beatmaps.", false); + public static final ToggleOption OPTION_SNAKING_SLIDERS = new ToggleOption("Snaking sliders", "SnakingSliders", "Sliders gradually snake out from their starting point.", true); + public static final ToggleOption OPTION_SHRINKING_SLIDERS = new ToggleOption("Shrinking sliders", "ShrinkingSliders", "Sliders shrinks when sliderball passes (aka knorkesliders)", true); + public static final ToggleOption OPTION_FALLBACK_SLIDERS = new ToggleOption("Fallback sliders", "FallbackSliders", "Enable this if sliders won't render", false); + public static final ToggleOption OPTION_MERGING_SLIDERS = new ToggleOption("Merging sliders", "MergingSliders", "Merge sliders (aka knorkesliders)", true) { + @Override + public boolean showCondition () { + return !OPTION_FALLBACK_SLIDERS.state; + } + }; + + public static final NumericOption OPTION_MERGING_SLIDERS_MIRROR_POOL = new NumericOption("Merging sliders mirror pool", "MergingSliderMirrorPool", "Amount of mirrors to calculate for merging sliders (impacts performance)", 2, 1, 5) { + @Override + public String getValueString () { + return String.valueOf(val); + } + + @Override + public boolean showCondition () { + return OPTION_MERGING_SLIDERS.showCondition() && OPTION_MERGING_SLIDERS.state; + } + }; + + public static final ToggleOption OPTION_DRAW_SLIDER_ENDCIRCLES = new ToggleOption("Draw endcircles", "DrawSliderEndCircles", "Old slider style", false); + public static final ToggleOption OPTION_DANCING_CIRCLES = new ToggleOption("Enable", "DancingHitcircles", "Make hitcircles dance to the beat", false); + public static final NumericOption OPTION_DANCING_CIRCLES_MULTIPLIER = new NumericOption("Multiplier", "DancingHitcirclesMP", "Multiplier to expand the hitcircles when dancing to the beat", 50, 1, 200) { + @Override + public String getValueString () { + return String.format("%.1f%%", val / 10f); + } + }; + + public static final ToggleOption OPTION_SHOW_HIT_LIGHTING = new ToggleOption("Show Hit Lighting", "HitLighting", "Adds an effect behind hit explosions.", true); + public static final ToggleOption OPTION_SHOW_HIT_ANIMATIONS = new ToggleOption("Show Hit Animations", "HitAnimations", "Fade out circles and curves.", true); + public static final ToggleOption OPTION_SHOW_REVERSEARROW_ANIMATIONS = new ToggleOption("Show reverse arrow animations", "ReverseArrowAnimations", "Fade out reverse arrows after passing.", true); + public static final ToggleOption OPTION_SHOW_COMBO_BURSTS = new ToggleOption("Show Combo Bursts", "ComboBurst", "A character image is displayed at combo milestones.", true); + public static final ToggleOption OPTION_SHOW_PERFECT_HIT = new ToggleOption("Show Perfect Hits", "PerfectHit", "Whether to show perfect hit result bursts (300s, slider ticks).", true); + public static final ToggleOption OPTION_SHOW_FOLLOW_POINTS = new ToggleOption("Show Follow Points", "FollowPoints", "Whether to show follow points between hit objects.", true); + public static final ToggleOption OPTION_SHOW_HIT_ERROR_BAR = new ToggleOption("Show Hit Error Bar", "ScoreMeter", "Shows precisely how accurate you were with each hit.", false); + public static final NumericOption OPTION_MAP_START_DELAY = new NumericOption("Map start delay", "StartDelay", "Have a fix amount of time to prepare your play/record", 20, 1, 50) { + @Override + public String getValueString () { + return (val * 100) + "ms"; + } + }; + + public static final NumericOption OPTION_MAP_END_DELAY = new NumericOption("Map end delay", "EndDelay", "Have a fix amount of time at the and of the map for a smooth finish", 50, 1, 150) { + @Override + public String getValueString () { + return (val * 100) + "ms"; + } + }; + + public static final NumericOption OPTION_EPILEPSY_WARNING = new NumericOption("Epilepsy warning image", "EpiWarn", "Show a little warning for flashing colours in the beginning", 0, 0, 20) { + @Override + public String getValueString () { + if (val == 0) { + return "Disabled"; + } + return (val * 100) + "ms"; + } + }; + + public static final ToggleOption OPTION_LOAD_HD_IMAGES = new ToggleOption("Load HD Images", "LoadHDImages", String.format("Loads HD (%s) images when available. Increases memory usage and loading times.", GameImage.HD_SUFFIX), true); + public static final NumericOption OPTION_FIXED_CS = new NumericOption("Fixed CS", "FixedCS", "Determines the size of circles and sliders.", 0, 0, 100) { + @Override + public String getValueString () { + return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); + } + + @Override + public String write () { + return String.format("%.1f", val / 10f); + } + + @Override + public void read (String s){ + int i = (int) (Float.parseFloat(s.replace(',', '.')) * 10f); + if (i >= 0 && i <= 100) + val = i; + } + }; + + public static final NumericOption OPTION_FIXED_HP = new NumericOption("Fixed HP", "FixedHP", "Determines the rate at which health decreases.", 0, 0, 100) { + @Override + public String getValueString () { + return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); + } + + @Override + public String write () { + return String.format("%.1f", val / 10f); + } + + @Override + public void read (String s){ + int i = (int) (Float.parseFloat(s.replace(',', '.')) * 10f); + if (i >= 0 && i <= 100) + val = i; + } + }; + + public static final NumericOption OPTION_FIXED_AR = new NumericOption("Fixed AR", "FixedAR", "Determines how long hit circles stay on the screen.", 0, 0, 100) { + @Override + public String getValueString () { + return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); + } + + @Override + public String write () { + return String.format("%.1f", val / 10f); + } + + @Override + public void read (String s){ + int i = (int) (Float.parseFloat(s.replace(',', '.')) * 10f); + if (i >= 0 && i <= 100) + val = i; + } + }; + + public static final NumericOption OPTION_FIXED_OD = new NumericOption("Fixed OD", "FixedOD", "Determines the time window for hit results.", 0, 0, 100) { + @Override + public String getValueString () { + return (val == 0) ? "Disabled" : String.format("%.1f", val / 10f); + } + + @Override + public String write () { + return String.format("%.1f", val / 10f); + } + + @Override + public void read (String s){ + int i = (int) (Float.parseFloat(s.replace(',', '.')) * 10f); + if (i >= 0 && i <= 100) + val = i; + } + }; + + public static final NumericOption OPTION_CHECKPOINT = new NumericOption("Track Checkpoint", "Checkpoint", "Press Ctrl+L while playing to load a checkpoint, and Ctrl+S to set one.", 0, 0, 3599) { + @Override + public String getValueString () { + return (val == 0) ? "Disabled" : String.format("%02d:%02d", + TimeUnit.SECONDS.toMinutes(val), + val - TimeUnit.MINUTES.toSeconds(TimeUnit.SECONDS.toMinutes(val))); + } + }; + + public static final ToggleOption OPTION_ENABLE_THEME_SONG = new ToggleOption("Enable Theme Song", "MenuMusic", "Whether to play the theme song upon starting opsu!", true); + public static final ToggleOption OPTION_REPLAY_SEEKING = new ToggleOption("Replay Seeking", "ReplaySeeking", "Enable a seeking bar on the left side of the screen during replays.", false); + public static final ToggleOption OPTION_DISABLE_UPDATER = new ToggleOption("Disable Automatic Updates", "DisableUpdater", "Disable automatic checking for updates upon starting opsu!.", false); + public static final ToggleOption OPTION_ENABLE_WATCH_SERVICE = new ToggleOption("Enable Watch Service", "WatchService", "Watch the beatmap directory for changes. Requires a restart.", false); + public static final ListOption OPTION_DANCE_MOVER = new ListOption("Algorithm", "Mover", "Algorithm that decides how to move from note to note" ) { + @Override + public Object[] getListItems () { + return Dancer.moverFactories; + } + + @Override + public void clickListItem(int index){ + if (Game.isInGame && Dancer.moverFactories[index] instanceof PolyMoverFactory) { + // TODO remove this when #79 is fixed + EventBus.post(new BarNotificationEvent("This mover is disabled in the storyboard right now")); + return; + } + Dancer.instance.setMoverFactoryIndex(index); + } + + @Override + public String getValueString () { + return Dancer.moverFactories[Dancer.instance.getMoverFactoryIndex()].toString(); + } + + @Override + public String write () { + return String.valueOf(Dancer.instance.getMoverFactoryIndex()); + } + + @Override + public void read (String s){ + int i = Integer.parseInt(s); + Dancer.instance.setMoverFactoryIndex(i); + } + }; + + public static final NumericOption OPTION_DANCE_EXGON_DELAY = new NumericOption("ExGon delay", "ExGonDelay", "Delay between moves for the ExGon mover", 25, 2, 750) { + @Override + public String getValueString () { + return String.valueOf(val); + } + @Override + public boolean showCondition () { + return Dancer.moverFactories[Dancer.instance.getMoverFactoryIndex()] instanceof ExgonMoverFactory; + } + }; + + public static final NumericOption OPTION_DANCE_QUAD_BEZ_AGGRESSIVENESS = new NumericOption("Bezier aggressiveness", "QuadBezAgr", "AKA initial D factor", 50, 0, 200) { + @Override + public String getValueString () { + return String.valueOf(val); + } + + @Override + public boolean showCondition () { + return Dancer.moverFactories[Dancer.instance.getMoverFactoryIndex()] instanceof QuadraticBezierMoverFactory; + } + }; + + public static final NumericOption OPTION_DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR = new NumericOption("Exit aggressiveness", "CubBezSliderExitAgr", "AKA initial D factor for sliderexits", 4, 1, 6) { + @Override + public String getValueString () { + return String.valueOf(val); + } + + @Override + public boolean showCondition () { + return OPTION_DANCE_QUAD_BEZ_AGGRESSIVENESS.showCondition() + && Dancer.sliderMoverController instanceof DefaultSliderMoverController; + } + }; + + public static final ToggleOption OPTION_DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS = new ToggleOption("Use cubic bezier before sliders", "QuadBezCubicSliders", "Slider entry looks better using this", true) { + @Override + public boolean showCondition () { + return OPTION_DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR.showCondition(); + } + }; + + public static final NumericOption OPTION_DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR = new NumericOption("Entry aggressiveness", "CubBezSliderEntryAgr", "AKA initial D factor for sliderentries", 4, 1, 6) { + @Override + public String getValueString () { + return String.valueOf(val); + } + + @Override + public boolean showCondition () { + return OPTION_DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS.showCondition() + && OPTION_DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS.state; + } + }; + + public static final ListOption OPTION_DANCE_MOVER_DIRECTION = new ListOption("Direction", "MoverDirection", "The direction the mover goes" ) { + @Override + public String getValueString () { + return Dancer.moverDirection.toString(); + } + + @Override + public Object[] getListItems () { + return MoverDirection.values(); + } + + @Override + public void clickListItem(int index){ + Dancer.moverDirection = MoverDirection.values()[index]; + } + + @Override + public String write () { + return "" + Dancer.moverDirection.nr; + } + + @Override + public void read (String s){ + Dancer.moverDirection = MoverDirection.values()[Integer.parseInt(s)]; + } + }; + + public static final ListOption OPTION_DANCE_SLIDER_MOVER_TYPE = new ListOption("Slider mover", "SliderMover", "How to move in sliders") { + private int val; + + @Override + public String getValueString () { + return Dancer.sliderMoverController.toString(); + } + + @Override + public Object[] getListItems () { + return Dancer.sliderMovers; + } + + @Override + public void clickListItem(int index){ + val = index; + Dancer.sliderMoverController = Dancer.sliderMovers[index]; + } + + @Override + public String write () { + return String.valueOf(val); + } + + @Override + public void read (String s){ + Dancer.sliderMoverController = Dancer.sliderMovers[val = Integer.parseInt(s)]; + } + }; + + public static final ListOption OPTION_DANCE_SPINNER = new ListOption("Algorithm", "Spinner", "Spinner style") { + @Override + public Object[] getListItems () { + return Dancer.spinners; + } + + @Override + public void clickListItem(int index){ + Dancer.instance.setSpinnerIndex(index); + } + + @Override + public String getValueString () { + return Dancer.spinners[Dancer.instance.getSpinnerIndex()].toString(); + } + + @Override + public String write () { + return Dancer.instance.getSpinnerIndex() + ""; + } + + @Override + public void read (String s){ + Dancer.instance.setSpinnerIndex(Integer.parseInt(s)); + } + }; + + public static final NumericOption OPTION_DANCE_SPINNER_DELAY = new NumericOption("Delay", "SpinnerDelay", "Fiddle with this if spinner goes too fast.", 3, 0, 20) { + @Override + public String getValueString () { + return String.format("%dms", val); + } + }; + + public static final ToggleOption OPTION_DANCE_LAZY_SLIDERS = new ToggleOption("Lazy sliders", "LazySliders", "Don't do short sliders", false); + public static final ToggleOption OPTION_DANCE_ONLY_CIRCLE_STACKS = new ToggleOption("Only circle stacks", "CircleStacks", "Only do circle movement on stacks", false); + public static final ToggleOption OPTION_DANCE_CIRCLE_STREAMS = new ToggleOption("Circle streams", "CircleStreams", "Make circles while streaming", false); + public static final ToggleOption OPTION_DANCE_MIRROR = new ToggleOption("Mirror collage", "MirrorCollage", "Hypnotizing stuff. Toggle this ingame by pressing the M key.", false); + public static final ToggleOption OPTION_DANCE_DRAW_APPROACH = new ToggleOption("Draw approach circles", "DrawApproach", "Can get a bit busy when using mirror collage", true); + public static final ListOption OPTION_DANCE_OBJECT_COLOR_OVERRIDE = new ListOption("Color", "ObjColorOverride", "Override object colors") { + @Override + public String getValueString () { + return Dancer.colorOverride.toString(); + } + + @Override + public Object[] getListItems () { + return ObjectColorOverrides.values(); + } + + @Override + public void clickListItem(int index){ + Dancer.colorOverride = ObjectColorOverrides.values()[index]; + } + + @Override + public String write () { + return "" + Dancer.colorOverride.nr; + } + + @Override + public void read (String s){ + Dancer.colorOverride = ObjectColorOverrides.values()[Integer.parseInt(s)]; + } + }; + + public static final ListOption OPTION_DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED = new ListOption("Mirror color", "ObjColorMirroredOverride", "Override collage object colors") { + @Override + public String getValueString () { + return Dancer.colorMirrorOverride.toString(); + } + + @Override + public Object[] getListItems () { + return ObjectColorOverrides.values(); + } + + @Override + public void clickListItem(int index){ + Dancer.colorMirrorOverride = ObjectColorOverrides.values()[index]; + } + + @Override + public String write () { + return "" + Dancer.colorMirrorOverride.nr; + } + + @Override + public void read (String s){ + Dancer.colorMirrorOverride = ObjectColorOverrides.values()[Integer.parseInt(s)]; + } + }; + + public static final NumericOption OPTION_DANCE_RGB_OBJECT_INC = new NumericOption("RGB increment", "RGBInc", "Amount of hue to shift, used for rainbow object override", 70, -1800, 1800) { + @Override + public String getValueString () { + return String.format("%.1f°", val / 10f); + } + }; + + public static final ListOption OPTION_DANCE_CURSOR_COLOR_OVERRIDE = new ListOption("Color", "CursorColorOverride", "Override cursor color") { + @Override + public String getValueString () { + return Dancer.cursorColorOverride.toString(); + } + + @Override + public Object[] getListItems () { + return CursorColorOverrides.values(); + } + + @Override + public void clickListItem(int index){ + Dancer.cursorColorOverride = CursorColorOverrides.values()[index]; + } + + @Override + public String write () { + return "" + Dancer.cursorColorOverride.nr; + } + + @Override + public void read (String s){ + Dancer.cursorColorOverride = CursorColorOverrides.values()[Integer.parseInt(s)]; + } + }; + + public static final ListOption OPTION_DANCE_CURSOR_MIRROR_COLOR_OVERRIDE = new ListOption("Mirror color", "CursorMirrorColorOverride", "Override mirror cursor color") { + @Override + public String getValueString () { + return Dancer.cursorColorMirrorOverride.toString(); + } + + @Override + public Object[] getListItems () { + return CursorColorOverrides.values(); + } + + @Override + public void clickListItem(int index){ + Dancer.cursorColorMirrorOverride = CursorColorOverrides.values()[index]; + } + + @Override + public String write () { + return "" + Dancer.cursorColorMirrorOverride.nr; + } + + @Override + public void read (String s){ + Dancer.cursorColorMirrorOverride = CursorColorOverrides.values()[Integer.parseInt(s)]; + } + }; + + public static final ToggleOption OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL = new ToggleOption("Only color cursor trail", "OnlyColorTrail", "Don't color the cursor, only the trail", false); + public static final NumericOption OPTION_DANCE_RGB_CURSOR_INC = new NumericOption("RGB cursor increment", "RGBCursorInc", "Amount of hue to shift, used for rainbow cursor override", 100, -2000, 2000) { + @Override + public String getValueString () { + return String.format("%.2f°", val / 1000f); + } + }; + + public static final NumericOption OPTION_DANCE_CURSOR_TRAIL_OVERRIDE = new NumericOption("Trail length", "CursorTrailOverride", "Override cursor trail length", 20, 20, 600) { + @Override + public String getValueString () { + if (val == 20) { + return "Disabled"; + } + return "" + val; + } + }; + + public static final ToggleOption OPTION_DANCE_HIDE_OBJECTS = new ToggleOption("Don't draw objects", "HideObj", "If you only want to see cursors :)", false); + public static final ToggleOption OPTION_DANCE_CIRLCE_IN_SLOW_SLIDERS = new ToggleOption("Do circles in slow sliders", "CircleInSlider", "Circle around sliderball in lazy & slow sliders", false); + public static final ToggleOption OPTION_DANCE_CIRLCE_IN_LAZY_SLIDERS = new ToggleOption("Do circles in lazy sliders", "CircleInLazySlider", "Circle in hitcircle in lazy sliders", false); + public static final ToggleOption OPTION_DANCE_HIDE_UI = new ToggleOption("Hide all UI", "HideUI", ".", true); + public static final ToggleOption OPTION_DANCE_ENABLE_SB = new ToggleOption("Enable storyboard editor", "EnableStoryBoard", "Dance storyboard", false); + public static final ToggleOption OPTION_PIPPI_ENABLE = new ToggleOption("Enable", "Pippi", "Move in circles like dancing pippi (osu! april fools joke 2016)", false); + public static final NumericOption OPTION_PIPPI_RADIUS_PERCENT = new NumericOption("Radius", "PippiRad", "Radius of pippi, percentage of circle radius", 100, 0, 100) { + @Override + public String getValueString () { + return val + "%"; + } + @Override + public void setValue ( int value){ + super.setValue(value); + Pippi.setRadiusPercent(value); + } + }; + + public static final NumericOption OPTION_PIPPI_ANGLE_INC_MUL = new NumericOption("Normal", "PippiAngIncMul", "How fast pippi's angle increments", 10, -200, 200) { + @Override + public String getValueString () { + return String.format("x%.1f", val / 10f); + } + }; + + public static final NumericOption OPTION_PIPPI_ANGLE_INC_MUL_SLIDER = new NumericOption("In slider", "PippiAngIncMulSlider", "Same as above, but in sliders", 50, -200, 200) { + @Override + public String getValueString () { + return String.format("x%.1f", val / 10f); + } + }; + + public static final ToggleOption OPTION_PIPPI_SLIDER_FOLLOW_EXPAND = new ToggleOption("Followcircle expand", "PippiFollowExpand", "Increase radius in followcircles", false); + public static final ToggleOption OPTION_PIPPI_PREVENT_WOBBLY_STREAMS = new ToggleOption("Prevent wobbly streams", "PippiPreventWobblyStreams", "Force linear mover while doing streams to prevent wobbly pippi", true); + +} diff --git a/src/yugecin/opsudance/options/OptionsService.java b/src/yugecin/opsudance/options/OptionsService.java new file mode 100644 index 00000000..01c05d9e --- /dev/null +++ b/src/yugecin/opsudance/options/OptionsService.java @@ -0,0 +1,118 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +import org.newdawn.slick.util.Log; +import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.core.inject.InstanceContainer; +import yugecin.opsudance.events.BubbleNotificationEvent; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; + +/** + * @author itdelatrisu (https://github.com/itdelatrisu) most functions are copied from itdelatrisu.opsu.Options.java + */ +public class OptionsService { + + @Inject + private Configuration config; + + public static final HashMap optionMap = new HashMap<>(); + + @Inject + public OptionsService(InstanceContainer instanceContainer) { + Option.setInstanceContainer(instanceContainer); + } + + public static void registerOption(Option option) { + optionMap.put(option.configurationName, option); + } + + public void loadOptions() { + // if no config file, use default settings + if (!config.OPTIONS_FILE.isFile()) { + saveOptions(); + return; + } + + // read file + try (BufferedReader in = new BufferedReader(new FileReader(config.OPTIONS_FILE))) { + String line; + while ((line = in.readLine()) != null) { + line = line.trim(); + if (line.length() < 2 || line.charAt(0) == '#') { + continue; + } + int index = line.indexOf('='); + if (index == -1) { + continue; + } + + // read option + String name = line.substring(0, index).trim(); + Option option = optionMap.get(name); + if (option != null) { + try { + String value = line.substring(index + 1).trim(); + option.read(value); + } catch (Exception e) { + Log.warn(String.format("Format error in options file for line: '%s'.", line), e); + } + } + } + } catch (IOException e) { + String err = String.format("Failed to read option file '%s'.", config.OPTIONS_FILE.getAbsolutePath()); + Log.error(err, e); + EventBus.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED)); + } + config.loadDirectories(); + } + + public void saveOptions() { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(config.OPTIONS_FILE), "utf-8"))) { + // header + SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM dd, yyyy"); + String date = dateFormat.format(new Date()); + writer.write("# opsu! configuration"); + writer.newLine(); + writer.write("# last updated on "); + writer.write(date); + writer.newLine(); + writer.newLine(); + + // options + for (Option option : optionMap.values()) { + writer.write(option.configurationName); + writer.write(" = "); + writer.write(option.write()); + writer.newLine(); + } + writer.close(); + } catch (IOException e) { + String err = String.format("Failed to write to file '%s'.", config.OPTIONS_FILE.getAbsolutePath()); + Log.error(err, e); + EventBus.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED)); + } + } + +} diff --git a/src/yugecin/opsudance/options/ToggleOption.java b/src/yugecin/opsudance/options/ToggleOption.java new file mode 100644 index 00000000..41da7d85 --- /dev/null +++ b/src/yugecin/opsudance/options/ToggleOption.java @@ -0,0 +1,43 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.options; + +public class ToggleOption extends Option { + + public boolean state; + + public ToggleOption(String name, String configurationName, String description, boolean state) { + super(name, configurationName, description); + this.state = state; + } + + public void toggle() { + this.state = !this.state; + } + + @Override + public String write() { + return Boolean.toString(state); + } + + @Override + public void read(String s) { + state = Boolean.parseBoolean(s); + } + +} diff --git a/src/yugecin/opsudance/render/GameObjectRenderer.java b/src/yugecin/opsudance/render/GameObjectRenderer.java new file mode 100644 index 00000000..25b6b211 --- /dev/null +++ b/src/yugecin/opsudance/render/GameObjectRenderer.java @@ -0,0 +1,121 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.render; + +import itdelatrisu.opsu.GameData; +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; +import itdelatrisu.opsu.audio.MusicController; +import itdelatrisu.opsu.beatmap.HitObject; +import itdelatrisu.opsu.ui.Colors; +import itdelatrisu.opsu.ui.animations.AnimationEquation; +import org.newdawn.slick.Color; +import org.newdawn.slick.Image; +import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; + +public class GameObjectRenderer { + + @Inject + private DisplayContainer displayContainer; + + private GameData gameData; + + private float circleDiameter; + private int circleDiameterInt; + + private Image hitcircle; + private Image hitcircleOverlay; + private Image approachCircle; + + @Deprecated + public static GameObjectRenderer instance; + + public GameObjectRenderer() { + instance = this; // TODO get rid of this + } + + public void initForGame(GameData gameData, float circleDiameter) { + this.gameData = gameData; + this.circleDiameter = circleDiameter * HitObject.getXMultiplier(); // convert from Osupixels (640x480) + this.circleDiameterInt = (int) this.circleDiameter; + GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(circleDiameterInt, circleDiameterInt)); + GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(circleDiameterInt, circleDiameterInt)); + GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(circleDiameterInt, circleDiameterInt)); + hitcircle = GameImage.HITCIRCLE.getImage(); + hitcircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); + approachCircle = GameImage.APPROACHCIRCLE.getImage(); + } + + public float getCircleDiameter() { + return circleDiameter; + } + + public void setGameData(GameData gameData) { + this.gameData = gameData; + } + + public void initForFrame() { + if (!OPTION_DANCING_CIRCLES.state) { + return; + } + Float position = MusicController.getBeatProgress(); + if (position == null) { + position = 0f; + } + int size = circleDiameterInt + (int) (circleDiameter * OPTION_DANCING_CIRCLES_MULTIPLIER.val / 1000f * AnimationEquation.IN_OUT_QUAD.calc(position)); + hitcircle = GameImage.HITCIRCLE.getImage().getScaledCopy(size, size); + hitcircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(size, size); + } + + public void renderHitCircle(float x, float y, Color color, int comboNumber, float comboNumberAlpha) { + renderHitCircleOnly(x, y, color); + boolean overlayAboveNumber = SkinService.skin.isHitCircleOverlayAboveNumber(); + if (!overlayAboveNumber) { + renderHitCircleOverlayOnly(x, y, Colors.WHITE_FADE); + } + renderComboNumberOnly(x, y, comboNumber, comboNumberAlpha); + if (overlayAboveNumber) { + renderHitCircleOverlayOnly(x, y, Colors.WHITE_FADE); + } + } + + public void renderHitCircleOnly(float x, float y, Color color) { + hitcircle.drawCentered(x, y, color); + } + + public void renderHitCircleOverlayOnly(float x, float y, Color color) { + hitcircleOverlay.drawCentered(x, y, color); + } + + public void renderComboNumberOnly(float x, float y, int number, float alpha) { + if (number > 0) { + gameData.drawSymbolNumber(number, x, y, GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / gameData.getDefaultSymbolImage(0).getHeight(), alpha); + } + } + + public void renderApproachCircle(float x, float y, Color color, float approachScale) { + if (!GameMod.HIDDEN.isActive() && OPTION_DANCE_DRAW_APPROACH.state) { + approachCircle.getScaledCopy(approachScale).drawCentered(x, y, color); + } + } + +} diff --git a/src/yugecin/opsudance/skinning/SkinService.java b/src/yugecin/opsudance/skinning/SkinService.java new file mode 100644 index 00000000..7b7fe64b --- /dev/null +++ b/src/yugecin/opsudance/skinning/SkinService.java @@ -0,0 +1,102 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.skinning; + +import itdelatrisu.opsu.audio.SoundController; +import itdelatrisu.opsu.skins.Skin; +import itdelatrisu.opsu.skins.SkinLoader; +import org.newdawn.slick.util.ClasspathLocation; +import org.newdawn.slick.util.FileSystemLocation; +import org.newdawn.slick.util.ResourceLoader; +import yugecin.opsudance.core.events.EventBus; +import yugecin.opsudance.core.inject.Inject; +import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; +import yugecin.opsudance.options.Configuration; + +import java.io.File; + +/** + * @author itdelatrisu (https://github.com/itdelatrisu) most functions are copied from itdelatrisu.opsu.Options.java + */ +public class SkinService { + + @Inject + private Configuration config; + + public String[] availableSkinDirectories; + public String usedSkinName = "Default"; + public static Skin skin; + + @Inject + public SkinService() { + } + + public void reloadSkin() { + loadSkin(); + SoundController.init(); + EventBus.post(new ResolutionOrSkinChangedEvent(usedSkinName, -1, -1)); + } + + /** + * Loads the skin given by the current skin directory. + * If the directory is invalid, the default skin will be loaded. + */ + public void loadSkin() { + File skinDir = getCurrentSkinDirectory(); + if (skinDir == null) { + // invalid skin name + usedSkinName = Skin.DEFAULT_SKIN_NAME; + } + + // create available skins list + File[] dirs = SkinLoader.getSkinDirectories(config.skinRootDir); + availableSkinDirectories = new String[dirs.length + 1]; + availableSkinDirectories[0] = Skin.DEFAULT_SKIN_NAME; + for (int i = 0; i < dirs.length; i++) { + availableSkinDirectories[i + 1] = dirs[i].getName(); + } + + // set skin and modify resource locations + ResourceLoader.removeAllResourceLocations(); + if (skinDir == null) { + skin = new Skin(null); + } else { + // load the skin + skin = SkinLoader.loadSkin(skinDir); + ResourceLoader.addResourceLocation(new FileSystemLocation(skinDir)); + } + ResourceLoader.addResourceLocation(new ClasspathLocation()); + ResourceLoader.addResourceLocation(new FileSystemLocation(new File("."))); + ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/"))); + } + + /** + * Returns the current skin directory. + *

+ * NOTE: This directory will differ from that of the currently loaded skin + * if {@link #loadSkin()} has not been called after a directory change. + * Use {@link Skin#getDirectory()} to get the directory of the currently + * loaded skin. + * @return the skin directory, or null for the default skin + */ + public File getCurrentSkinDirectory() { + File dir = new File(config.skinRootDir, usedSkinName); + return (dir.isDirectory()) ? dir : null; + } + +} diff --git a/src/yugecin/opsudance/spinners/ApproachCircleSpinner.java b/src/yugecin/opsudance/spinners/ApproachCircleSpinner.java index b32eba3c..b210bc03 100644 --- a/src/yugecin/opsudance/spinners/ApproachCircleSpinner.java +++ b/src/yugecin/opsudance/spinners/ApproachCircleSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class ApproachCircleSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/BeamSpinner.java b/src/yugecin/opsudance/spinners/BeamSpinner.java index a26fadfd..36c02558 100644 --- a/src/yugecin/opsudance/spinners/BeamSpinner.java +++ b/src/yugecin/opsudance/spinners/BeamSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class BeamSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/CircleSpinner.java b/src/yugecin/opsudance/spinners/CircleSpinner.java index 33f9085c..1f654318 100644 --- a/src/yugecin/opsudance/spinners/CircleSpinner.java +++ b/src/yugecin/opsudance/spinners/CircleSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class CircleSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/CubeSpinner.java b/src/yugecin/opsudance/spinners/CubeSpinner.java index b9a727f1..15e7780e 100644 --- a/src/yugecin/opsudance/spinners/CubeSpinner.java +++ b/src/yugecin/opsudance/spinners/CubeSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class CubeSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/DonutSpinner.java b/src/yugecin/opsudance/spinners/DonutSpinner.java index 149d28f4..18241079 100644 --- a/src/yugecin/opsudance/spinners/DonutSpinner.java +++ b/src/yugecin/opsudance/spinners/DonutSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class DonutSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/FivePointStarApproachSpinner.java b/src/yugecin/opsudance/spinners/FivePointStarApproachSpinner.java index aee97dee..5d223104 100644 --- a/src/yugecin/opsudance/spinners/FivePointStarApproachSpinner.java +++ b/src/yugecin/opsudance/spinners/FivePointStarApproachSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class FivePointStarApproachSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/FivePointStarSpinner.java b/src/yugecin/opsudance/spinners/FivePointStarSpinner.java index 4d1d9e41..a905b583 100644 --- a/src/yugecin/opsudance/spinners/FivePointStarSpinner.java +++ b/src/yugecin/opsudance/spinners/FivePointStarSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class FivePointStarSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/HalfCircleSpinner.java b/src/yugecin/opsudance/spinners/HalfCircleSpinner.java index 3dcdba4e..fb6a7d46 100644 --- a/src/yugecin/opsudance/spinners/HalfCircleSpinner.java +++ b/src/yugecin/opsudance/spinners/HalfCircleSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class HalfCircleSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/IlluminatiSpinner.java b/src/yugecin/opsudance/spinners/IlluminatiSpinner.java index c81c9a96..97d1b459 100644 --- a/src/yugecin/opsudance/spinners/IlluminatiSpinner.java +++ b/src/yugecin/opsudance/spinners/IlluminatiSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class IlluminatiSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/LessThanThreeSpinner.java b/src/yugecin/opsudance/spinners/LessThanThreeSpinner.java index 01bab098..b5ce0264 100644 --- a/src/yugecin/opsudance/spinners/LessThanThreeSpinner.java +++ b/src/yugecin/opsudance/spinners/LessThanThreeSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class LessThanThreeSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/RektCircleSpinner.java b/src/yugecin/opsudance/spinners/RektCircleSpinner.java index 2ce9e00f..161b4cbd 100644 --- a/src/yugecin/opsudance/spinners/RektCircleSpinner.java +++ b/src/yugecin/opsudance/spinners/RektCircleSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class RektCircleSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/RektSpinner.java b/src/yugecin/opsudance/spinners/RektSpinner.java index d531aad0..26bc5ddd 100644 --- a/src/yugecin/opsudance/spinners/RektSpinner.java +++ b/src/yugecin/opsudance/spinners/RektSpinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import yugecin.opsudance.options.Options; public class RektSpinner extends Spinner { diff --git a/src/yugecin/opsudance/spinners/Spinner.java b/src/yugecin/opsudance/spinners/Spinner.java index 43903798..201143f8 100644 --- a/src/yugecin/opsudance/spinners/Spinner.java +++ b/src/yugecin/opsudance/spinners/Spinner.java @@ -17,7 +17,7 @@ */ package yugecin.opsudance.spinners; -import itdelatrisu.opsu.Options; +import static yugecin.opsudance.options.Options.*; public abstract class Spinner { @@ -42,7 +42,7 @@ public abstract class Spinner { } public boolean waitForDelay() { - if (delay >= Options.getSpinnerDelay()) { + if (delay >= OPTION_DANCE_SPINNER_DELAY.val) { delay = 0; return true; } diff --git a/src/yugecin/opsudance/ui/OptionsOverlay.java b/src/yugecin/opsudance/ui/OptionsOverlay.java index 63d3b87d..7bb3bde2 100644 --- a/src/yugecin/opsudance/ui/OptionsOverlay.java +++ b/src/yugecin/opsudance/ui/OptionsOverlay.java @@ -18,9 +18,6 @@ package yugecin.opsudance.ui; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.Options.GameOption; -import itdelatrisu.opsu.Options.GameOption.OptionType; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; @@ -30,11 +27,14 @@ import org.newdawn.slick.*; import org.newdawn.slick.gui.TextField; import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.state.OverlayOpsuState; +import yugecin.opsudance.options.*; import yugecin.opsudance.utils.FontUtil; import java.util.HashMap; import java.util.LinkedList; +import static yugecin.opsudance.options.Options.*; + public class OptionsOverlay extends OverlayOpsuState { private final DisplayContainer displayContainer; @@ -84,14 +84,14 @@ public class OptionsOverlay extends OverlayOpsuState { private OptionTab[] sections; - private GameOption hoverOption; - private GameOption selectedOption; + private Option hoverOption; + private Option selectedOption; private int sliderOptionStartX; private int sliderOptionLength; private boolean isAdjustingSlider; - private final HashMap> dropdownMenus; + private final HashMap> dropdownMenus; private final LinkedList> visibleDropdownMenus; private int dropdownMenuPaddingY; private DropdownMenu openDropdownMenu; @@ -195,15 +195,16 @@ public class OptionsOverlay extends OverlayOpsuState { if (section.options == null) { continue; } - for (final GameOption option : section.options) { - Object[] items = option.getListItems(); - if (items == null) { + for (final Option option : section.options) { + if (!(option instanceof ListOption)) { continue; } + final ListOption listOption = (ListOption) option; + Object[] items = listOption.getListItems(); DropdownMenu menu = new DropdownMenu(displayContainer, items, 0, 0, 0) { @Override public void itemSelected(int index, Object item) { - option.clickListItem(index); + listOption.clickListItem(index); openDropdownMenu = null; } }; @@ -224,7 +225,7 @@ public class OptionsOverlay extends OverlayOpsuState { menu.setHighlightColor(COL_COMBOBOX_HOVER); menu.setTextColor(COL_WHITE); dropdownMenuPaddingY = (optionHeight - menu.getHeight()) / 2; - dropdownMenus.put(option, menu); + dropdownMenus.put(listOption, menu); } } @@ -301,8 +302,8 @@ public class OptionsOverlay extends OverlayOpsuState { private void renderTooltip(Graphics g) { if (hoverOption != null) { - String tip = hoverOption.getDescription(); - if (hoverOption.getType() == OptionType.NUMERIC) { + String tip = hoverOption.description; + if (hoverOption instanceof NumericOption) { tip = "(" + hoverOption.getValueString() + ") " + tip; } UI.updateTooltip(displayContainer.renderDelta, tip, true); @@ -336,7 +337,7 @@ public class OptionsOverlay extends OverlayOpsuState { } int lineHeight = (int) (Fonts.LARGE.getLineHeight() * 0.9f); for (int optionIndex = 0; optionIndex < section.options.length; optionIndex++) { - GameOption option = section.options[optionIndex]; + Option option = section.options[optionIndex]; if (!option.showCondition() || option.isFiltered()) { continue; } @@ -368,7 +369,7 @@ public class OptionsOverlay extends OverlayOpsuState { if (sections[sectionIndex].options == null) { continue; } - for (GameOption option : sections[sectionIndex].options) { + for (Option option : sections[sectionIndex].options) { if (option.showCondition() && !option.isFiltered()) { maxScrollOffset += optionHeight; } @@ -384,24 +385,22 @@ public class OptionsOverlay extends OverlayOpsuState { scrollHandler.setMinMax(0, maxScrollOffset); } - private void renderOption(Graphics g, GameOption option, int y) { - OptionType type = option.getType(); - Object[] listItems = option.getListItems(); - if (listItems != null) { - renderListOption(g, option, y); - } else if (type == OptionType.BOOLEAN) { - renderCheckOption(option, y); - } else if (type == OptionType.NUMERIC) { - renderSliderOption(g, option, y); - } else { - renderGenericOption(option, y); + private void renderOption(Graphics g, Option option, int y) { + if (option instanceof ListOption) { + renderListOption(g, (ListOption) option, y); + } else if (option instanceof ToggleOption) { + renderCheckOption((ToggleOption) option, y); + } else if (option instanceof NumericOption) { + renderSliderOption(g, (NumericOption) option, y); + } else if (option instanceof GenericOption) { + renderGenericOption((GenericOption) option, y); } } - private void renderListOption(Graphics g, GameOption option, int y) { + private void renderListOption(Graphics g, ListOption option, int y) { // draw option name - int nameWith = Fonts.MEDIUM.getWidth(option.getName()); - Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.getName(), COL_WHITE); + int nameWith = Fonts.MEDIUM.getWidth(option.name); + Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.name, COL_WHITE); nameWith += 15; int comboboxStartX = optionStartX + nameWith; int comboboxWidth = optionWidth - nameWith; @@ -424,19 +423,19 @@ public class OptionsOverlay extends OverlayOpsuState { dropdown.render(g); } - private void renderCheckOption(GameOption option, int y) { - if (option.getBooleanValue()) { + private void renderCheckOption(ToggleOption option, int y) { + if (option.state) { checkOnImg.draw(optionStartX, y + controlImagePadding, COL_PINK); } else { checkOffImg.draw(optionStartX, y + controlImagePadding, COL_PINK); } - Fonts.MEDIUM.drawString(optionStartX + 30, y + optionTextOffsetY, option.getName(), COL_WHITE); + Fonts.MEDIUM.drawString(optionStartX + 30, y + optionTextOffsetY, option.name, COL_WHITE); } - private void renderSliderOption(Graphics g, GameOption option, int y) { + private void renderSliderOption(Graphics g, NumericOption option, int y) { final int padding = 10; - int nameLen = Fonts.MEDIUM.getWidth(option.getName()); - Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.getName(), COL_WHITE); + int nameLen = Fonts.MEDIUM.getWidth(option.name); + Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.name, COL_WHITE); int sliderLen = optionWidth - nameLen - padding; if (sliderLen <= 1) { return; @@ -453,7 +452,7 @@ public class OptionsOverlay extends OverlayOpsuState { } } - float sliderValue = (float) (option.getIntegerValue() - option.getMinValue()) / (option.getMaxValue() - option.getMinValue()); + float sliderValue = (float) (option.val - option.min) / (option.max - option.min); float sliderBallPos = sliderStartX + (int) ((sliderLen - controlImageSize) * sliderValue); g.setLineWidth(3f); @@ -471,10 +470,10 @@ public class OptionsOverlay extends OverlayOpsuState { } } - private void renderGenericOption(GameOption option, int y) { + private void renderGenericOption(GenericOption option, int y) { String value = option.getValueString(); int valueLen = Fonts.MEDIUM.getWidth(value); - Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.getName(), COL_WHITE); + Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.name, COL_WHITE); Fonts.MEDIUM.drawString(optionStartX + optionWidth - valueLen, y + optionTextOffsetY, value, COL_BLUE); } @@ -556,9 +555,9 @@ public class OptionsOverlay extends OverlayOpsuState { updateIndicatorAlpha(); UI.getBackButton().hoverUpdate(delta, mouseX, mouseY); if (isAdjustingSlider) { - int sliderValue = hoverOption.getIntegerValue(); + int sliderValue = ((NumericOption) hoverOption).val; updateSliderOption(); - if (hoverOption.getIntegerValue() - sliderValue != 0 && sliderSoundDelay <= 0) { + if (((NumericOption) hoverOption).val - sliderValue != 0 && sliderSoundDelay <= 0) { sliderSoundDelay = 90; SoundController.playSound(SoundEffect.MENUHIT); } @@ -634,7 +633,7 @@ public class OptionsOverlay extends OverlayOpsuState { mousePressY = y; selectedOption = hoverOption; - if (hoverOption != null && hoverOption.getType() == OptionType.NUMERIC) { + if (hoverOption != null && hoverOption instanceof NumericOption) { isAdjustingSlider = sliderOptionStartX <= x && x < sliderOptionStartX + sliderOptionLength; if (isAdjustingSlider) { updateSliderOption(); @@ -678,16 +677,16 @@ public class OptionsOverlay extends OverlayOpsuState { } if (hoverOption != null) { - if (hoverOption.getType() == OptionType.BOOLEAN) { - hoverOption.click(); + if (hoverOption instanceof ToggleOption) { + ((ToggleOption) hoverOption).toggle(); if (listener != null) { listener.onSaveOption(hoverOption); } SoundController.playSound(SoundEffect.MENUHIT); return true; - } else if (hoverOption == GameOption.KEY_LEFT) { + } else if (hoverOption == OPTION_KEY_LEFT) { keyEntryLeft = true; - } else if (hoverOption == GameOption.KEY_RIGHT) { + } else if (hoverOption == OPTION_KEY_RIGHT) { keyEntryLeft = true; } } @@ -724,13 +723,17 @@ public class OptionsOverlay extends OverlayOpsuState { @Override public boolean onKeyPressed(int key, char c) { if (keyEntryRight) { - Options.setGameKeyRight(key); + if (Utils.isValidGameKey(key)) { + OPTION_KEY_RIGHT.intval = key; + } keyEntryRight = false; return true; } if (keyEntryLeft) { - Options.setGameKeyLeft(key); + if (Utils.isValidGameKey(key)) { + OPTION_KEY_LEFT.intval = key; + } keyEntryLeft = false; return true; } @@ -767,10 +770,9 @@ public class OptionsOverlay extends OverlayOpsuState { } private void updateSliderOption() { - int min = hoverOption.getMinValue(); - int max = hoverOption.getMaxValue(); - int value = min + Math.round((float) (max - min) * (displayContainer.mouseX - sliderOptionStartX) / (sliderOptionLength)); - hoverOption.setValue(Utils.clamp(value, min, max)); + NumericOption o = (NumericOption) hoverOption; + int value = o.min + Math.round((float) (o.max - o.min) * (displayContainer.mouseX - sliderOptionStartX) / (sliderOptionLength)); + o.setValue(Utils.clamp(value, o.min, o.max)); } private void updateHoverOption(int mouseX, int mouseY) { @@ -797,7 +799,7 @@ public class OptionsOverlay extends OverlayOpsuState { continue; } for (int optionIndex = 0; optionIndex < section.options.length; optionIndex++) { - GameOption option = section.options[optionIndex]; + Option option = section.options[optionIndex]; if (option.isFiltered() || !option.showCondition()) { continue; } @@ -824,7 +826,7 @@ public class OptionsOverlay extends OverlayOpsuState { if (section.options == null) { continue; } - for (GameOption opt : section.options) { + for (Option opt : section.options) { opt.filter(null); } } @@ -844,7 +846,7 @@ public class OptionsOverlay extends OverlayOpsuState { continue; } section.filtered = true; - for (GameOption option : section.options) { + for (Option option : section.options) { if (lastBigSectionMatches || sectionMatches) { section.filtered = false; option.filter(null); @@ -859,24 +861,9 @@ public class OptionsOverlay extends OverlayOpsuState { updateHoverOption(prevMouseX, prevMouseY); } - public static class OptionTab { - - public final String name; - public final GameOption[] options; - private boolean filtered; - - public OptionTab(String name, GameOption[] options) { - this.name = name; - this.options = options; - } - - } - public interface Listener { - void onLeaveOptionsMenu(); - void onSaveOption(GameOption option); - + void onSaveOption(Option option); } } diff --git a/src/yugecin/opsudance/ui/StoryboardOverlay.java b/src/yugecin/opsudance/ui/StoryboardOverlay.java index cc69aab6..14fb1dd6 100644 --- a/src/yugecin/opsudance/ui/StoryboardOverlay.java +++ b/src/yugecin/opsudance/ui/StoryboardOverlay.java @@ -17,12 +17,11 @@ */ package yugecin.opsudance.ui; -import itdelatrisu.opsu.Options; -import itdelatrisu.opsu.Options.GameOption; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.objects.GameObject; import itdelatrisu.opsu.states.Game; -import itdelatrisu.opsu.states.OptionsMenu; +import yugecin.opsudance.options.Option; +import yugecin.opsudance.options.OptionGroups; import itdelatrisu.opsu.ui.Fonts; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; @@ -30,15 +29,17 @@ import org.newdawn.slick.Input; import yugecin.opsudance.ObjectColorOverrides; import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.state.OverlayOpsuState; +import yugecin.opsudance.options.OptionTab; import yugecin.opsudance.sbv2.MoveStoryboard; -import yugecin.opsudance.ui.OptionsOverlay.OptionTab; import java.util.*; +import static yugecin.opsudance.options.Options.*; + @SuppressWarnings("unchecked") public class StoryboardOverlay extends OverlayOpsuState implements OptionsOverlay.Listener { - private final static List optionList = new ArrayList<>(); + private final static List