package funkin.ui.options; import funkin.ui.Page.PageName; import funkin.ui.transition.LoadingState; import funkin.ui.TextMenuList; import funkin.ui.TextMenuList.TextMenuItem; import flixel.math.FlxPoint; import funkin.ui.TextMenuList; import funkin.ui.TextMenuList.TextMenuItem; import flixel.FlxSprite; import flixel.FlxObject; import flixel.FlxSubState; import flixel.group.FlxGroup; import flixel.util.FlxSignal; import funkin.audio.FunkinSound; import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatState; import funkin.graphics.shaders.HSVShader; import funkin.input.Controls; #if FEATURE_NEWGROUNDS import funkin.api.newgrounds.NewgroundsClient; #end #if mobile import funkin.util.TouchUtil; import funkin.mobile.ui.FunkinBackButton; import funkin.mobile.input.ControlsHandler; import funkin.mobile.ui.options.ControlsSchemeMenu; #end #if FEATURE_MOBILE_IAP import funkin.mobile.util.InAppPurchasesUtil; #end import flixel.util.FlxColor; /** * The main options menu * It mainly is controlled via the "optionsCodex" object, * which handles paging and going to the different submenus */ class OptionsState extends MusicBeatState { /** * Instance of the OptionsState */ public static var instance:OptionsState; var optionsCodex:Codex; public var drumsBG:FunkinSound; public static var rememberedSelectedIndex:Int = 0; override function create():Void { instance = this; persistentUpdate = true; drumsBG = FunkinSound.load(Paths.music('offsetsLoop/drumsLoop'), 0, true, false, false, false); var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBG')); var hsv = new HSVShader(-0.6, 0.9, 3.6); menuBG.shader = hsv; menuBG.setGraphicSize(Std.int(FlxG.width * 1.1)); menuBG.updateHitbox(); menuBG.screenCenter(); menuBG.scrollFactor.set(0, 0); add(menuBG); optionsCodex = new Codex(Options); add(optionsCodex); var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu()); var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData)); var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu()); var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu()); #if FEATURE_LAG_ADJUSTMENT var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu()); #end if (options.hasMultipleOptions()) { options.onExit.add(exitToMainMenu); controls.onExit.add(exitControls); preferences.onExit.add(optionsCodex.switchPage.bind(Options)); #if FEATURE_LAG_ADJUSTMENT offsets.onExit.add(exitOffsets); #end saveData.onExit.add(optionsCodex.switchPage.bind(Options)); } else { // No need to show Options page #if mobile preferences.onExit.add(exitToMainMenu); optionsCodex.setPage(Preferences); #else controls.onExit.add(exitToMainMenu); optionsCodex.setPage(Controls); #end } super.create(); #if mobile addHitbox(); hitbox.visible = false; #end } function exitOffsets():Void { if (drumsBG.volume > 0) { drumsBG.fadeOut(0.5, 0); } FlxG.sound.music.fadeOut(0.5, 0, function(tw) { FunkinSound.playMusic('freakyMenu', { startingVolume: 0, overrideExisting: true, restartTrack: true, persist: true }); FlxG.sound.music.fadeIn(0.5, 1); }); optionsCodex.switchPage(Options); } function exitControls():Void { // Apply any changes to the controls. PlayerSettings.reset(); PlayerSettings.init(); optionsCodex.switchPage(Options); } function exitToMainMenu() { optionsCodex.currentPage.enabled = false; // TODO: Animate this transition? FlxG.keys.enabled = false; FlxG.switchState(() -> new MainMenuState()); } } /** * Our default Page when we enter the OptionsState, a bit of the root */ class OptionsMenu extends Page { var items:TextMenuList; #if FEATURE_TOUCH_CONTROLS var backButton:FunkinBackButton; var goingBack:Bool = false; #end /** * Camera focus point */ var camFocusPoint:FlxObject; final CAMERA_MARGIN:Int = 150; public function new(saveDataMenu:SaveDataMenu) { super(); add(items = new TextMenuList()); createItem("PREFERENCES", function() codex.switchPage(Preferences)); #if mobile if (ControlsHandler.hasExternalInputDevice) #end createItem("CONTROLS", function() codex.switchPage(Controls)); // createItem("CONTROL SCHEMES", function() { // FlxG.state.openSubState(new ControlsSchemeMenu()); // }); #if FEATURE_LAG_ADJUSTMENT createItem("LAG ADJUSTMENT", function() { FlxG.sound.music.fadeOut(0.5, 0, function(tw) { FunkinSound.playMusic('offsetsLoop', { startingVolume: 0, overrideExisting: true, restartTrack: true, loop: true }); OptionsState.instance.drumsBG.play(true); FlxG.sound.music.fadeIn(1, 1); }); codex.switchPage(Offsets); }); #end #if FEATURE_MOBILE_IAP createItem("RESTORE PURCHASES", function() { InAppPurchasesUtil.restorePurchases(); }); #end #if android createItem("OPEN DATA FOLDER", function() { funkin.external.android.DataFolderUtil.openDataFolder(); }); #end #if FEATURE_NEWGROUNDS if (NewgroundsClient.instance.isLoggedIn()) { createItem("LOGOUT OF NG", function() { NewgroundsClient.instance.logout(function() { // Reset the options menu when logout succeeds. // This means the login option will be displayed. FlxG.resetState(); }, function() { FlxG.log.warn("Newgrounds logout failed!"); }); }); } else { createItem("LOGIN TO NG", function() { NewgroundsClient.instance.login(function() { // Reset the options menu when login succeeds. // This means the logout option will be displayed. // NOTE: If the user presses login and opens the browser, // then navigates the UI FlxG.resetState(); }, function() { FlxG.log.warn("Newgrounds login failed!"); }); }); } #end // no need to show an entire new menu for just one option if (saveDataMenu.hasMultipleOptions()) { createItem("SAVE DATA OPTIONS", function() { codex.switchPage(SaveData); }); } else { createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt); } #if NO_FEATURE_TOUCH_CONTROLS createItem("EXIT", exit); #else backButton = new FunkinBackButton(FlxG.width - 230, FlxG.height - 200, exit, 1.0); backButton.onConfirmStart.add(function() { items.busy = true; goingBack = true; backButton.active = true; }); add(backButton); #end // Create an object for the camera to track. camFocusPoint = new FlxObject(0, 0, 140, 70); add(camFocusPoint); // Follow the camera focus as we scroll. FlxG.camera.follow(camFocusPoint, null, 0.085); FlxG.camera.deadzone.set(0, CAMERA_MARGIN / 2, FlxG.camera.width, FlxG.camera.height - CAMERA_MARGIN + 40); FlxG.camera.minScrollY = -CAMERA_MARGIN / 2; // Move the camera when the menu is scrolled. items.onChange.add(onMenuChange); onMenuChange(items.members[0]); items.selectItem(OptionsState.rememberedSelectedIndex); #if FEATURE_TOUCH_CONTROLS FlxG.touches.swipeThreshold.y = 100; #end } function onMenuChange(selected:TextMenuItem):Void { camFocusPoint.y = selected.y; } function createItem(name:String, callback:Void->Void, fireInstantly = false):TextMenuItem { var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback); item.fireInstantly = fireInstantly; item.screenCenter(X); return item; } override function update(elapsed:Float):Void { #if FEATURE_TOUCH_CONTROLS backButton.active = (!goingBack) ? !items.busy : true; #end super.update(elapsed); } override function set_enabled(value:Bool):Bool { items.enabled = value; return super.set_enabled(value); } /** * True if this page has multiple options, excluding the exit option. * If false, there's no reason to ever show this page. */ public function hasMultipleOptions():Bool { return items.length > 2; } } enum abstract OptionsMenuPageName(String) to PageName { var Options = "options"; var Controls = "controls"; var Colors = "colors"; var Mods = "mods"; var Preferences = "preferences"; var Offsets = "offsets"; var SaveData = "saveData"; }