Some cleanup and readd support for fetching URLs on web builds. Link the missing medals base64 medal stuff, and some debug crap remove trace crap update assets stuff assets stuff newgrounds username on titlescreen and replace things with NG.core instead of our own client singleton
426 lines
12 KiB
Haxe
426 lines
12 KiB
Haxe
package funkin.ui.mainmenu;
|
|
|
|
import funkin.graphics.FunkinSprite;
|
|
import flixel.addons.transition.FlxTransitionableState;
|
|
import funkin.ui.debug.DebugMenuSubState;
|
|
import flixel.FlxObject;
|
|
import flixel.FlxSprite;
|
|
import flixel.FlxState;
|
|
import flixel.addons.transition.FlxTransitionableState;
|
|
import flixel.effects.FlxFlicker;
|
|
import flixel.graphics.frames.FlxAtlasFrames;
|
|
import flixel.util.typeLimit.NextState;
|
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
import flixel.input.touch.FlxTouch;
|
|
import flixel.text.FlxText;
|
|
import funkin.data.song.SongData.SongMusicData;
|
|
import flixel.tweens.FlxEase;
|
|
import funkin.graphics.FunkinCamera;
|
|
import funkin.audio.FunkinSound;
|
|
import flixel.tweens.FlxTween;
|
|
import funkin.ui.MusicBeatState;
|
|
import flixel.util.FlxTimer;
|
|
import funkin.ui.AtlasMenuList;
|
|
import funkin.ui.freeplay.FreeplayState;
|
|
import funkin.ui.MenuList;
|
|
import funkin.ui.title.TitleState;
|
|
import funkin.ui.story.StoryMenuState;
|
|
import funkin.ui.Prompt;
|
|
import funkin.util.WindowUtil;
|
|
#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(bg.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);
|
|
|
|
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
|
|
}
|
|
|
|
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;
|
|
|
|
super.closeSubState();
|
|
}
|
|
|
|
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()
|
|
{
|
|
WindowUtil.openURL(Constants.URL_MERCH);
|
|
}
|
|
#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);
|
|
|
|
if (FlxG.onMobile)
|
|
{
|
|
var touch:FlxTouch = FlxG.touches.getFirst();
|
|
|
|
if (touch != null)
|
|
{
|
|
for (item in menuItems)
|
|
{
|
|
if (touch.overlaps(item))
|
|
{
|
|
if (menuItems.selectedIndex == item.ID && touch.justPressed) menuItems.accept();
|
|
else
|
|
menuItems.selectItem(item.ID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 && menuItems.enabled && !menuItems.busy)
|
|
{
|
|
FlxG.switchState(() -> new TitleState());
|
|
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
|
}
|
|
}
|
|
}
|