Replace swipeThreshold updates with the one in FlxTouchManager Temporarily remove swipeThreshold sets for testing. Proper SwipeUtil refactoring. Completely replaced all flick and movement checks with the one in Flixel. Rewrote most of the documentation to be more accurate. Invert flick checks as it already comes inverted from FlxFlick. Define fixes for SwipeUtil Fixed null safety issues on compiling.
447 lines
13 KiB
Haxe
447 lines
13 KiB
Haxe
package funkin.ui.mainmenu;
|
|
|
|
import flixel.addons.transition.FlxTransitionableState;
|
|
import funkin.ui.debug.DebugMenuSubState;
|
|
import flixel.FlxObject;
|
|
import flixel.FlxSubState;
|
|
import flixel.FlxSprite;
|
|
import flixel.effects.FlxFlicker;
|
|
import flixel.util.typeLimit.NextState;
|
|
import flixel.util.FlxColor;
|
|
import flixel.tweens.FlxEase;
|
|
import funkin.graphics.FunkinCamera;
|
|
import funkin.audio.FunkinSound;
|
|
import funkin.util.SwipeUtil;
|
|
import flixel.tweens.FlxTween;
|
|
import funkin.ui.MusicBeatState;
|
|
import flixel.util.FlxTimer;
|
|
import funkin.ui.AtlasMenuList.AtlasMenuItem;
|
|
import funkin.ui.freeplay.FreeplayState;
|
|
import funkin.ui.MenuList.MenuTypedList;
|
|
import funkin.ui.MenuList.MenuListItem;
|
|
import funkin.ui.title.TitleState;
|
|
import funkin.ui.story.StoryMenuState;
|
|
import funkin.ui.Prompt;
|
|
import funkin.util.WindowUtil;
|
|
import funkin.util.TouchUtil;
|
|
#if FEATURE_DISCORD_RPC
|
|
import funkin.api.discord.DiscordClient;
|
|
#end
|
|
#if FEATURE_NEWGROUNDS
|
|
import io.newgrounds.NG;
|
|
#end
|
|
|
|
class MainMenuState extends MusicBeatState
|
|
{
|
|
var menuItems:MenuTypedList<AtlasMenuItem>;
|
|
|
|
var magenta:FlxSprite;
|
|
var camFollow:FlxObject;
|
|
|
|
var overrideMusic:Bool = false;
|
|
|
|
static var rememberedSelectedIndex:Int = 0;
|
|
|
|
public function new(?_overrideMusic:Bool = false)
|
|
{
|
|
super();
|
|
overrideMusic = _overrideMusic;
|
|
}
|
|
|
|
override function create():Void
|
|
{
|
|
#if FEATURE_DISCORD_RPC
|
|
DiscordClient.instance.setPresence({state: "In the Menus", details: null});
|
|
#end
|
|
|
|
FlxG.cameras.reset(new FunkinCamera('mainMenu'));
|
|
|
|
transIn = FlxTransitionableState.defaultTransIn;
|
|
transOut = FlxTransitionableState.defaultTransOut;
|
|
|
|
if (!overrideMusic) playMenuMusic();
|
|
|
|
// We want the state to always be able to begin with being able to accept inputs and show the anims of the menu items.
|
|
persistentUpdate = true;
|
|
persistentDraw = true;
|
|
|
|
var bg:FlxSprite = new FlxSprite(Paths.image('menuBG'));
|
|
bg.scrollFactor.x = 0;
|
|
bg.scrollFactor.y = 0.17;
|
|
bg.setGraphicSize(Std.int(FlxG.width * 1.2));
|
|
bg.updateHitbox();
|
|
bg.screenCenter();
|
|
add(bg);
|
|
|
|
camFollow = new FlxObject(0, 0, 1, 1);
|
|
add(camFollow);
|
|
|
|
magenta = new FlxSprite(Paths.image('menuBGMagenta'));
|
|
magenta.scrollFactor.x = bg.scrollFactor.x;
|
|
magenta.scrollFactor.y = bg.scrollFactor.y;
|
|
magenta.setGraphicSize(Std.int(bg.width));
|
|
magenta.updateHitbox();
|
|
magenta.x = bg.x;
|
|
magenta.y = bg.y;
|
|
magenta.visible = false;
|
|
|
|
// TODO: Why doesn't this line compile I'm going fucking feral
|
|
|
|
if (Preferences.flashingLights) add(magenta);
|
|
|
|
menuItems = new MenuTypedList<AtlasMenuItem>();
|
|
add(menuItems);
|
|
menuItems.onChange.add(onMenuItemChange);
|
|
menuItems.onAcceptPress.add(function(_) {
|
|
FlxFlicker.flicker(magenta, 1.1, 0.15, false, true);
|
|
});
|
|
|
|
menuItems.enabled = true; // can move on intro
|
|
createMenuItem('storymode', 'mainmenu/storymode', function() startExitState(() -> new StoryMenuState()));
|
|
createMenuItem('freeplay', 'mainmenu/freeplay', function() {
|
|
persistentDraw = true;
|
|
persistentUpdate = false;
|
|
// Freeplay has its own custom transition
|
|
FlxTransitionableState.skipNextTransIn = true;
|
|
FlxTransitionableState.skipNextTransOut = true;
|
|
|
|
#if FEATURE_DEBUG_FUNCTIONS
|
|
// Debug function: Hold SHIFT when selecting Freeplay to swap character without the char select menu
|
|
var targetCharacter:Null<String> = (FlxG.keys.pressed.SHIFT) ? (FreeplayState.rememberedCharacterId == "pico" ? "bf" : "pico") : null;
|
|
#else
|
|
var targetCharacter:Null<String> = null;
|
|
#end
|
|
|
|
openSubState(new FreeplayState(
|
|
{
|
|
character: targetCharacter
|
|
}));
|
|
});
|
|
|
|
#if FEATURE_OPEN_URL
|
|
// In order to prevent popup blockers from triggering,
|
|
// we need to open the link as an immediate result of a keypress event,
|
|
// so we can't wait for the flicker animation to complete.
|
|
var hasPopupBlocker = #if web true #else false #end;
|
|
createMenuItem('merch', 'mainmenu/merch', selectMerch, hasPopupBlocker);
|
|
#end
|
|
|
|
createMenuItem('options', 'mainmenu/options', function() {
|
|
startExitState(() -> new funkin.ui.options.OptionsState());
|
|
});
|
|
|
|
createMenuItem('credits', 'mainmenu/credits', function() {
|
|
startExitState(() -> new funkin.ui.credits.CreditsState());
|
|
});
|
|
|
|
// Reset position of menu items.
|
|
var spacing = 160;
|
|
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
|
for (i in 0...menuItems.length)
|
|
{
|
|
var menuItem = menuItems.members[i];
|
|
menuItem.x = FlxG.width / 2;
|
|
menuItem.y = top + spacing * i;
|
|
menuItem.scrollFactor.x = 0.0;
|
|
// This one affects how much the menu items move when you scroll between them.
|
|
menuItem.scrollFactor.y = 0.4;
|
|
}
|
|
|
|
menuItems.selectItem(rememberedSelectedIndex);
|
|
|
|
resetCamStuff();
|
|
|
|
// reset camera when debug menu is closed
|
|
subStateClosed.add(_ -> resetCamStuff(false));
|
|
|
|
subStateOpened.add(sub -> {
|
|
if (Type.getClass(sub) == FreeplayState)
|
|
{
|
|
new FlxTimer().start(0.5, _ -> {
|
|
magenta.visible = false;
|
|
});
|
|
}
|
|
});
|
|
|
|
// FlxG.camera.setScrollBounds(bg.x, bg.x + bg.width, bg.y, bg.y + bg.height * 1.2);
|
|
|
|
#if mobile
|
|
addBackButton(FlxG.width * 0.03, FlxG.height * 0.79, FlxColor.BLACK, goBack);
|
|
#end
|
|
|
|
super.create();
|
|
|
|
// This has to come AFTER!
|
|
this.leftWatermarkText.text = Constants.VERSION;
|
|
|
|
#if FEATURE_NEWGROUNDS
|
|
if (NG.core?.loggedIn)
|
|
{
|
|
this.leftWatermarkText.text += ' | Newgrounds: Logged in as ${NG.core?.user?.name}';
|
|
}
|
|
#end
|
|
|
|
// Leave this here do not change it -Zack
|
|
#if TOUCH_CONTROLS
|
|
camFollow.setPosition(640, 360);
|
|
FlxG.camera.snapToTarget();
|
|
// FlxG.touches.swipeThreshold.set(100, menuItems.members[0].height);
|
|
#end
|
|
|
|
funkin.mobile.ui.TouchPointer.TouchPointerGrp.load();
|
|
}
|
|
|
|
function playMenuMusic():Void
|
|
{
|
|
FunkinSound.playMusic('freakyMenu',
|
|
{
|
|
overrideExisting: true,
|
|
restartTrack: false,
|
|
// Continue playing this music between states, until a different music track gets played.
|
|
persist: true
|
|
});
|
|
}
|
|
|
|
function resetCamStuff(?snap:Bool = true):Void
|
|
{
|
|
FlxG.camera.follow(camFollow, null, 0.06);
|
|
|
|
if (snap) FlxG.camera.snapToTarget();
|
|
}
|
|
|
|
function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void
|
|
{
|
|
var item = new AtlasMenuItem(name, Paths.getSparrowAtlas(atlas), callback);
|
|
item.fireInstantly = fireInstantly;
|
|
item.ID = menuItems.length;
|
|
|
|
item.scrollFactor.set();
|
|
|
|
// Set the offset of the item so the sprite is centered on the origin.
|
|
item.centered = true;
|
|
item.changeAnim('idle');
|
|
|
|
menuItems.addItem(name, item);
|
|
}
|
|
|
|
override function closeSubState():Void
|
|
{
|
|
magenta.visible = false;
|
|
// FlxG.touches.swipeThreshold.set(100, menuItems.members[0].height);
|
|
|
|
super.closeSubState();
|
|
}
|
|
|
|
override function openSubState(targetSubState:FlxSubState):Void
|
|
{
|
|
super.openSubState(targetSubState);
|
|
// Leave this here do not change it -Zack
|
|
#if TOUCH_CONTROLS
|
|
if (camFollow != null)
|
|
{
|
|
camFollow.setPosition(640, 360);
|
|
FlxG.camera.snapToTarget();
|
|
}
|
|
#end
|
|
}
|
|
|
|
override function finishTransIn():Void
|
|
{
|
|
super.finishTransIn();
|
|
}
|
|
|
|
function onMenuItemChange(selected:MenuListItem)
|
|
{
|
|
camFollow.setPosition(selected.getGraphicMidpoint().x, selected.getGraphicMidpoint().y);
|
|
}
|
|
|
|
#if FEATURE_OPEN_URL
|
|
function selectDonate()
|
|
{
|
|
WindowUtil.openURL(Constants.URL_ITCH);
|
|
}
|
|
|
|
function selectMerch()
|
|
{
|
|
NG.core?.calls.loader.loadReferral(false)
|
|
.addComponentParameter("referral_name", "merch_link")
|
|
.addResponseHandler(response -> {
|
|
if (response.success) WindowUtil.openURL(response.result.data.url)
|
|
else
|
|
WindowUtil.openURL(Constants.URL_MERCH_FALLBACK);
|
|
})
|
|
.send();
|
|
}
|
|
#end
|
|
|
|
public function openPrompt(prompt:Prompt, onClose:Void->Void):Void
|
|
{
|
|
menuItems.enabled = false;
|
|
persistentUpdate = false;
|
|
|
|
prompt.closeCallback = function() {
|
|
menuItems.enabled = true;
|
|
if (onClose != null) onClose();
|
|
}
|
|
|
|
openSubState(prompt);
|
|
}
|
|
|
|
function startExitState(state:NextState):Void
|
|
{
|
|
menuItems.enabled = false; // disable for exit
|
|
rememberedSelectedIndex = menuItems.selectedIndex;
|
|
|
|
var duration = 0.4;
|
|
menuItems.forEach(function(item) {
|
|
if (menuItems.selectedIndex != item.ID)
|
|
{
|
|
FlxTween.tween(item, {alpha: 0}, duration, {ease: FlxEase.quadOut});
|
|
}
|
|
else
|
|
{
|
|
item.visible = false;
|
|
}
|
|
});
|
|
|
|
new FlxTimer().start(duration, function(_) FlxG.switchState(state));
|
|
}
|
|
|
|
override function update(elapsed:Float):Void
|
|
{
|
|
super.update(elapsed);
|
|
|
|
Conductor.instance.update();
|
|
|
|
// Open the debug menu, defaults to ` / ~
|
|
// This includes stuff like the Chart Editor, so it should be present on all builds.
|
|
if (controls.DEBUG_MENU)
|
|
{
|
|
persistentUpdate = false;
|
|
|
|
FlxG.state.openSubState(new DebugMenuSubState());
|
|
}
|
|
|
|
#if FEATURE_DEBUG_FUNCTIONS
|
|
// Ctrl+Alt+Shift+P = Character Unlock screen
|
|
// Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock
|
|
// Ctrl+Alt+Shift+M = Revoke requirements for Pico Unlock
|
|
// Ctrl+Alt+Shift+R = Score/Rank conflict test
|
|
// Ctrl+Alt+Shift+N = Mark all characters as not seen
|
|
// Ctrl+Alt+Shift+E = Dump save data
|
|
// Ctrl+Alt+Shift+L = Force crash and create a log dump
|
|
|
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P)
|
|
{
|
|
FlxG.switchState(() -> new funkin.ui.charSelect.CharacterUnlockState('pico'));
|
|
}
|
|
|
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
|
|
{
|
|
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
|
// Give the user a score of 1 point on Weekend 1 story mode (Easy difficulty).
|
|
// This makes the level count as cleared and displays the songs in Freeplay.
|
|
funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
|
|
{
|
|
score: 1,
|
|
tallies:
|
|
{
|
|
sick: 0,
|
|
good: 0,
|
|
bad: 0,
|
|
shit: 0,
|
|
missed: 0,
|
|
combo: 0,
|
|
maxCombo: 0,
|
|
totalNotesHit: 0,
|
|
totalNotes: 0,
|
|
}
|
|
});
|
|
}
|
|
|
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.M)
|
|
{
|
|
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
|
// Give the user a score of 0 points on Weekend 1 story mode (all difficulties).
|
|
// This makes the level count as uncleared and no longer displays the songs in Freeplay.
|
|
for (diff in ['easy', 'normal', 'hard'])
|
|
{
|
|
funkin.save.Save.instance.setLevelScore('weekend1', diff,
|
|
{
|
|
score: 0,
|
|
tallies:
|
|
{
|
|
sick: 0,
|
|
good: 0,
|
|
bad: 0,
|
|
shit: 0,
|
|
missed: 0,
|
|
combo: 0,
|
|
maxCombo: 0,
|
|
totalNotesHit: 0,
|
|
totalNotes: 0,
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R)
|
|
{
|
|
// Give the user a hypothetical overridden score,
|
|
// and see if we can maintain that golden P rank.
|
|
funkin.save.Save.instance.setSongScore('tutorial', 'easy',
|
|
{
|
|
score: 1234567,
|
|
tallies:
|
|
{
|
|
sick: 0,
|
|
good: 0,
|
|
bad: 0,
|
|
shit: 1,
|
|
missed: 0,
|
|
combo: 0,
|
|
maxCombo: 0,
|
|
totalNotesHit: 1,
|
|
totalNotes: 10,
|
|
}
|
|
});
|
|
}
|
|
|
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.N)
|
|
{
|
|
@:privateAccess
|
|
{
|
|
funkin.save.Save.instance.data.unlocks.charactersSeen = ["bf"];
|
|
funkin.save.Save.instance.data.unlocks.oldChar = false;
|
|
}
|
|
}
|
|
|
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.E)
|
|
{
|
|
funkin.save.Save.instance.debug_dumpSave();
|
|
}
|
|
#end
|
|
|
|
if (FlxG.sound.music != null && FlxG.sound.music.volume < 0.8)
|
|
{
|
|
FlxG.sound.music.volume += 0.5 * elapsed;
|
|
}
|
|
|
|
if (_exiting) menuItems.enabled = false;
|
|
|
|
if (controls.BACK)
|
|
{
|
|
goBack();
|
|
}
|
|
}
|
|
|
|
public function goBack():Void
|
|
{
|
|
if (menuItems.enabled && !menuItems.busy)
|
|
{
|
|
FlxG.switchState(() -> new TitleState());
|
|
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
|
}
|
|
}
|
|
}
|