Files
Funkin/source/funkin/ui/mainmenu/MainMenuState.hx
T
zacksgamerzandKarimAkra 18dcb8f446 Replaced all old flick checks, swipe checks, etc. with the proper checks from Flixel. Updated flixel ref. Removed justFlicked checks as they are unused.
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.
2025-04-16 15:22:10 +03:00

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'));
}
}
}