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 25cbef40..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, Options.isMergingSliders() ? 1 : 0, hitResult.curve.getCurvePoints().length); + 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/OptionGroups.java b/src/itdelatrisu/opsu/OptionGroups.java deleted file mode 100644 index 8b3942a0..00000000 --- a/src/itdelatrisu/opsu/OptionGroups.java +++ /dev/null @@ -1,241 +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 itdelatrisu.opsu.Options.GameOption; - -import yugecin.opsudance.ui.OptionsOverlay.OptionTab; - -public class OptionGroups { - - 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("DANCING HITCIRCLES", new GameOption[] { - GameOption.DANCING_CIRCLES, - GameOption.DANCING_CIRCLES_MULTIPLIER, - }), - 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/Options.java b/src/itdelatrisu/opsu/Options.java deleted file mode 100644 index ec48a37b..00000000 --- a/src/itdelatrisu/opsu/Options.java +++ /dev/null @@ -1,2111 +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, 50, 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), - DANCING_CIRCLES ("Enable", "DancingHitcircles", "Make hitcircles dance to the beat", false), - DANCING_CIRCLES_MULTIPLIER ("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); - } - }, - 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(); } - public static boolean isDancingHitCircles() { return GameOption.DANCING_CIRCLES.getBooleanValue(); } - public static float getDancingHitCirclesMultiplier() { return GameOption.DANCING_CIRCLES_MULTIPLIER.getIntegerValue() / 10f; } - - /** - * 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 9930f1d1..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}. @@ -660,4 +557,20 @@ public class Utils { (float) (Options.height / 2d + Math.sin(ang) * d) }; } + + /** + * 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 8fea0fa6..66f05181 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -21,7 +21,6 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameData; import itdelatrisu.opsu.GameData.HitObjectType; import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.objects.curves.Vec2f; @@ -34,6 +33,8 @@ 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. */ @@ -178,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); } @@ -193,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 eea5d0a4..67a2af65 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; @@ -39,6 +38,9 @@ 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. @@ -214,7 +216,7 @@ 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; Vec2f endPos = curve.pointAt(1); @@ -232,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; } @@ -263,7 +265,7 @@ 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))) { + 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); @@ -310,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; @@ -335,7 +337,7 @@ public class Slider extends GameObject { if (mirror) { g.rotate(x, y, -180f); } - if (!GameMod.HIDDEN.isActive() && Options.isDrawApproach()) { + if (!GameMod.HIDDEN.isActive() && OPTION_DANCE_DRAW_APPROACH.state) { gameObjectRenderer.renderApproachCircle(x, y, color, approachScale); } g.popTransform(); @@ -358,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); @@ -455,17 +457,17 @@ 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 (!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 { @@ -475,8 +477,8 @@ public class Slider extends GameObject { game.addMergedSliderPointsToRender(baseSliderFrom, 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); @@ -587,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, 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 7ab8ccd3..cd21a525 100644 --- a/src/itdelatrisu/opsu/objects/curves/Curve.java +++ b/src/itdelatrisu/opsu/objects/curves/Curve.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.objects.curves; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.render.CurveRenderState; import itdelatrisu.opsu.skins.Skin; @@ -30,6 +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.skinning.SkinService; + +import static yugecin.opsudance.options.Options.*; /** * Representation of a curve. @@ -101,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."); } } @@ -132,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++) @@ -143,10 +144,8 @@ 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, false); renderState.draw(color, borderColor, from, to); diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java index d897f117..c6f06308 100644 --- a/src/itdelatrisu/opsu/render/CurveRenderState.java +++ b/src/itdelatrisu/opsu/render/CurveRenderState.java @@ -18,7 +18,6 @@ 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.curves.Vec2f; @@ -40,6 +39,8 @@ 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 * style curves are drawn. @@ -112,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; } @@ -356,7 +357,7 @@ 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++) { 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 6fa5d414..febefdc9 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -67,12 +67,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. */ @@ -84,6 +89,9 @@ public class Game extends ComplexOpsuState { @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 { @@ -359,7 +367,7 @@ 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); } @@ -394,7 +402,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; @@ -412,15 +420,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(); @@ -447,7 +455,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 @@ -504,7 +512,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); } @@ -542,7 +550,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) { @@ -575,7 +583,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) { @@ -676,15 +684,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()) { @@ -714,7 +722,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()) { @@ -852,7 +860,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) { @@ -920,7 +928,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); @@ -945,7 +953,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)) { @@ -1090,7 +1098,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; @@ -1122,12 +1132,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) { @@ -1170,8 +1182,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.")); } @@ -1180,7 +1194,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); @@ -1202,31 +1216,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; @@ -1276,7 +1288,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); @@ -1285,12 +1297,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); @@ -1364,7 +1376,7 @@ public class Game extends ComplexOpsuState { return true; } - if (Options.isMouseDisabled()) { + if (OPTION_DISABLE_MOUSE_BUTTONS.state) { return true; } @@ -1394,12 +1406,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; } @@ -1424,7 +1438,7 @@ public class Game extends ComplexOpsuState { return true; } - if (Options.isMouseWheelDisabled()) { + if (OPTION_DISABLE_MOUSE_WHEEL.state) { return true; } @@ -1435,7 +1449,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); @@ -1481,7 +1495,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; @@ -1528,7 +1542,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]; @@ -1633,7 +1651,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); } @@ -1641,8 +1659,8 @@ public class Game extends ComplexOpsuState { this.leadInTime += epiImgTime; SoundController.mute(false); - if (Options.isMergingSliders()) { - if (!Options.isShrinkingSliders()) { + if (!OPTION_FALLBACK_SLIDERS.state && OPTION_MERGING_SLIDERS.state) { + if (!OPTION_SHRINKING_SLIDERS.state) { knorkesliders = null; // workaround for issue-130 } if (knorkesliders == null) { @@ -1697,7 +1715,7 @@ public class Game extends ComplexOpsuState { MusicController.setPitch(1f); MusicController.resume(); - if (Options.isEnableSB()) { + if (OPTION_DANCE_ENABLE_SB.state) { storyboardOverlay.onLeave(); } @@ -1737,15 +1755,15 @@ public class Game extends ComplexOpsuState { gameObjectRenderer.initForFrame(); // draw result objects - if (!Options.isHideObjects()) { + if (!OPTION_DANCE_HIDE_OBJECTS.state) { data.drawHitResults(trackPosition); } - if (Options.isMergingSliders() && knorkesliders != null) { + 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); @@ -1770,7 +1788,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; @@ -1835,9 +1853,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); @@ -1887,9 +1905,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); } @@ -1990,22 +2009,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 @@ -2017,8 +2040,11 @@ public class Game extends ComplexOpsuState { 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/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 56516f1f..12a23d85 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -69,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. *

@@ -82,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; @@ -231,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(); } } @@ -594,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); @@ -735,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; @@ -1023,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 c5e3060e..f1a86b49 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; @@ -37,6 +34,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. *

@@ -47,6 +46,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; @@ -74,18 +82,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; @@ -104,14 +104,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 f5ad23bf..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,9 +67,6 @@ public class Cursor { private boolean isMirrored; - /** - * Constructor. - */ public Cursor() { this(false); } @@ -83,19 +81,20 @@ public class 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(); @@ -108,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(); @@ -118,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); @@ -135,7 +134,7 @@ public class Cursor { 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; @@ -150,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); + } } /** @@ -183,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/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 9015ebbd..beadcd0b 100644 --- a/src/yugecin/opsudance/Dancer.java +++ b/src/yugecin/opsudance/Dancer.java @@ -22,7 +22,6 @@ 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.DummyObject; @@ -41,6 +40,8 @@ import yugecin.opsudance.spinners.*; import java.awt.*; +import static yugecin.opsudance.options.Options.*; + public class Dancer { public static MoverFactory[] moverFactories = new MoverFactory[] { @@ -193,7 +194,7 @@ 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) <= GameObjectRenderer.instance.getCircleDiameter() * 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) <= GameObjectRenderer.instance.getCircleDiameter() * 0.8f) { @@ -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 6455d9c8..3567910c 100644 --- a/src/yugecin/opsudance/Pippi.java +++ b/src/yugecin/opsudance/Pippi.java @@ -17,11 +17,12 @@ */ package yugecin.opsudance; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.objects.GameObject; import itdelatrisu.opsu.objects.Slider; import yugecin.opsudance.render.GameObjectRenderer; +import static yugecin.opsudance.options.Options.*; + public class Pippi { private static double angle = 0; @@ -48,19 +49,19 @@ public class Pippi { } 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 < GameObjectRenderer.instance.getCircleDiameter() * 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..bf225498 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,11 +51,14 @@ 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 @@ -64,6 +68,12 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen @Deprecated public static DisplayContainer instance; // TODO d remove this + @Inject + private SkinService skinService; + + @Inject + private Configuration config; + private static SGL GL = Renderer.get(); private final InstanceContainer instanceContainer; @@ -116,6 +126,7 @@ 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(); @@ -145,8 +156,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 +165,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 +201,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 +276,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 +334,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 +416,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/OpsuDanceInjector.java b/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java index 341b0810..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,11 +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(); 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 40c09600..ecff7fec 100644 --- a/src/yugecin/opsudance/movers/factories/AutoMoverFactory.java +++ b/src/yugecin/opsudance/movers/factories/AutoMoverFactory.java @@ -17,14 +17,16 @@ */ package yugecin.opsudance.movers.factories; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; 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 { private int starttime; @@ -42,8 +44,8 @@ public class AutoMoverFactory implements MoverFactory { } // stacked: circles if not too quick - int circle_stream = Options.isCircleStreams() ? 58: 85; - if (distance < GameObjectRenderer.instance.getCircleDiameter() && ((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/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 index dd3a1e7b..25b6b211 100644 --- a/src/yugecin/opsudance/render/GameObjectRenderer.java +++ b/src/yugecin/opsudance/render/GameObjectRenderer.java @@ -20,7 +20,6 @@ package yugecin.opsudance.render; import itdelatrisu.opsu.GameData; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.Options; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.ui.Colors; @@ -29,6 +28,9 @@ 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 { @@ -72,21 +74,21 @@ public class GameObjectRenderer { } public void initForFrame() { - if (!Options.isDancingHitCircles()) { + if (!OPTION_DANCING_CIRCLES.state) { return; } Float position = MusicController.getBeatProgress(); if (position == null) { position = 0f; } - int size = circleDiameterInt + (int) (circleDiameter * Options.getDancingHitCirclesMultiplier() / 100f * AnimationEquation.IN_OUT_QUAD.calc(position)); + 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 = Options.getSkin().isHitCircleOverlayAboveNumber(); + boolean overlayAboveNumber = SkinService.skin.isHitCircleOverlayAboveNumber(); if (!overlayAboveNumber) { renderHitCircleOverlayOnly(x, y, Colors.WHITE_FADE); } @@ -111,7 +113,7 @@ public class GameObjectRenderer { } public void renderApproachCircle(float x, float y, Color color, float approachScale) { - if (!GameMod.HIDDEN.isActive() && Options.isDrawApproach()) { + 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 fe9a581d..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.OptionGroups; +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