2023-11-07 09:04:22 +00:00
package funkin . ui . mainmenu ;
2020-11-01 01:11:14 +00:00
2024-03-17 02:20:22 +00:00
import flixel . addons . transition . FlxTransitionableState ;
2025-06-28 19:03:39 +00:00
#if FEATURE_DEBUG_MENU
2023-06-15 04:29:54 +00:00
import funkin . ui . debug . DebugMenuSubState ;
2025-06-28 19:03:39 +00:00
#end
2020-11-01 01:11:14 +00:00
import flixel . FlxObject ;
2025-04-14 15:20:15 +00:00
import flixel . FlxSubState ;
2020-11-01 01:11:14 +00:00
import flixel . FlxSprite ;
import flixel . effects . FlxFlicker ;
2025-06-16 23:50:01 +00:00
import flixel . math . FlxPoint ;
2024-02-06 00:46:11 +00:00
import flixel . util . typeLimit . NextState ;
2025-04-14 15:20:15 +00:00
import flixel . util . FlxColor ;
2020-11-01 01:11:14 +00:00
import flixel . tweens . FlxEase ;
2024-02-13 06:38:11 +00:00
import funkin . graphics . FunkinCamera ;
2024-03-12 03:42:32 +00:00
import funkin . audio . FunkinSound ;
2024-12-15 14:53:02 +00:00
import funkin . util . SwipeUtil ;
2025-08-13 23:14:58 +00:00
import funkin . util . InputUtil ;
2020-11-01 01:11:14 +00:00
import flixel . tweens . FlxTween ;
2023-11-07 09:04:22 +00:00
import funkin . ui . MusicBeatState ;
2025-07-24 17:54:01 +00:00
import funkin . ui . UIStateMachine ;
import funkin . ui . UIStateMachine . UIState ;
2021-02-15 22:04:08 +00:00
import flixel . util . FlxTimer ;
2025-02-13 01:35:37 +00:00
import funkin . ui . AtlasMenuList . AtlasMenuItem ;
2023-11-07 09:04:22 +00:00
import funkin . ui . freeplay . FreeplayState ;
2025-02-13 01:35:37 +00:00
import funkin . ui . MenuList . MenuTypedList ;
import funkin . ui . MenuList . MenuListItem ;
2023-08-08 19:41:25 +00:00
import funkin . ui . title . TitleState ;
2023-05-17 20:42:58 +00:00
import funkin . ui . story . StoryMenuState ;
2022-03-08 08:13:53 +00:00
import funkin . ui . Prompt ;
2022-03-27 02:18:26 +00:00
import funkin . util . WindowUtil ;
2025-07-24 17:54:01 +00:00
import funkin . mobile . ui . FunkinButton ;
2025-06-16 23:50:01 +00:00
import funkin . util . MathUtil ;
2024-12-15 14:53:02 +00:00
import funkin . util . TouchUtil ;
2025-04-22 02:38:50 +00:00
import funkin . api . newgrounds . Referral ;
2025-06-09 16:49:37 +00:00
import funkin . ui . mainmenu . UpgradeSparkle ;
import flixel . group . FlxSpriteGroup . FlxTypedSpriteGroup ;
2024-08-26 22:01:36 +00:00
#if FEATURE_DISCORD_RPC
2024-09-19 14:03:16 +00:00
import funkin . api . discord . DiscordClient ;
2021-03-23 04:05:46 +00:00
#end
2024-11-01 22:35:19 +00:00
#if FEATURE_NEWGROUNDS
2025-04-22 02:38:50 +00:00
import funkin . api . newgrounds . NewgroundsClient ;
2024-11-01 22:35:19 +00:00
#end
2025-06-10 10:13:04 +00:00
#if mobile
2025-06-29 00:17:45 +00:00
import funkin . mobile . input . ControlsHandler ;
2025-06-10 10:13:04 +00:00
import funkin . mobile . util . InAppPurchasesUtil ;
#end
2021-02-20 04:10:16 +00:00
2025-04-23 03:04:09 +00:00
@ : nullSafety
2020-11-01 01:11:14 +00:00
class MainMenuState extends MusicBeatState
{
2025-06-25 22:14:10 +00:00
var menuItems: Null < MenuTypedList < AtlasMenuItem > > ;
2023-01-23 03:25:45 +00:00
2025-06-25 13:04:22 +00:00
var bg: Null < FlxSprite > ;
2023-01-23 03:25:45 +00:00
var magenta: FlxSprite ;
var camFollow: FlxObject ;
2025-06-16 23:50:01 +00:00
#if mobile
2025-06-25 19:27:50 +00:00
var gyroPan: Null < FlxPoint > ;
2025-06-16 23:50:01 +00:00
#end
2024-05-31 09:39:53 +00:00
var overrideMusic: Bool = false ;
2025-08-13 01:07:04 +00:00
var uiStateMachine: UIStateMachine = new UIStateMachine ( ) ;
2025-07-24 17:54:01 +00:00
var canInteract( get , never ) : Bool ;
2025-07-21 20:40:42 +00:00
function get_canInteract ( ) : Bool
{
2025-08-13 01:07:04 +00:00
return uiStateMachine . canInteract ( ) ;
2025-07-21 20:40:42 +00:00
}
2024-05-31 09:39:53 +00:00
2024-05-18 22:29:46 +00:00
static var rememberedSelectedIndex: Int = 0 ;
2025-06-09 16:49:37 +00:00
// this should never be false on non-mobile targets.
2025-06-09 20:21:09 +00:00
var hasUpgraded: Bool = false ;
2025-06-09 16:49:37 +00:00
var upgradeSparkles: FlxTypedSpriteGroup < UpgradeSparkle > ;
2025-06-25 13:04:22 +00:00
public function n e w ( _overrideMusic : Bool = false )
2024-05-31 09:39:53 +00:00
{
super ( ) ;
overrideMusic = _overrideMusic ;
2025-04-23 03:04:09 +00:00
2025-07-24 17:54:01 +00:00
// Start in Entering state during screen fade in
uiStateMachine . transition ( Entering ) ;
2025-08-13 01:07:04 +00:00
2025-06-25 13:04:22 +00:00
upgradeSparkles = new FlxTypedSpriteGroup < UpgradeSparkle > ( ) ;
2025-04-23 03:04:09 +00:00
magenta = new FlxSprite ( Paths . image ( ' m e n u B G M a g e n t a ' ) ) ;
camFollow = new FlxObject ( 0 , 0 , 1 , 1 ) ;
2025-08-13 01:07:04 +00:00
// TODO: enabling and disabling keys is a lil quirky,
// we should move towards unifying the UI and it's inputs into this UIStateMachine managed system
FlxG . keys . enabled = true ;
2024-05-31 09:39:53 +00:00
}
2024-04-24 23:45:17 +00:00
override function create ( ) : Void
2023-01-23 03:25:45 +00:00
{
2024-08-26 22:01:36 +00:00
#if FEATURE_DISCORD_RPC
2024-09-19 14:03:16 +00:00
DiscordClient . instance . setPresence ( { state : " I n t h e M e n u s " , details : null } ) ;
2023-01-23 03:25:45 +00:00
#end
2025-06-25 22:14:10 +00:00
FlxG . cameras . reset ( new FunkinCamera ( ' m a i n M e n u ' ) ) ;
2023-01-23 03:25:45 +00:00
transIn = FlxTransitionableState . defaultTransIn ;
transOut = FlxTransitionableState . defaultTransOut ;
2025-06-10 11:52:06 +00:00
#if FEATURE_MOBILE_IAP
2025-07-21 20:40:42 +00:00
trace ( " h a s I n i t i a l i z e d : " + InAppPurchasesUtil . hasInitialized ) ;
2025-07-17 18:32:10 +00:00
if ( InAppPurchasesUtil . hasInitialized ) Preferences . noAds = InAppPurchasesUtil . isPurchased ( InAppPurchasesUtil . UPGRADE_PRODUCT_ID ) ;
2025-07-17 13:13:22 +00:00
// If the user is faster than their shit wifi, it gets the saved noAds instead.
2025-07-15 17:26:45 +00:00
hasUpgraded = Preferences . noAds ;
2025-06-10 10:13:04 +00:00
#else
2025-06-09 20:43:28 +00:00
// just to make sure its never accidentally turned off
hasUpgraded = true ;
#end
2024-08-29 17:22:56 +00:00
if ( ! overrideMusic ) playMenuMusic ( ) ;
2023-01-23 03:25:45 +00:00
2024-04-30 20:30:22 +00:00
// 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 ;
2023-01-23 03:25:45 +00:00
2025-06-11 23:04:05 +00:00
bg = new FlxSprite ( Paths . image ( ' m e n u B G ' ) ) ;
bg . scrollFactor . x = #if ! mobile 0 #else 0.17 #end ; // we want a lil x scroll on mobile
2023-01-23 03:25:45 +00:00
bg . scrollFactor . y = 0.17 ;
2024-10-31 10:34:38 +00:00
bg . setGraphicSize ( Std . int ( FlxG . width * 1.2 ) ) ;
2023-01-23 03:25:45 +00:00
bg . updateHitbox ( ) ;
bg . screenCenter ( ) ;
add ( bg ) ;
add ( camFollow ) ;
2025-08-13 01:07:04 +00:00
magenta . scrollFactor . copyFrom ( bg . scrollFactor ) ;
2023-01-23 03:25:45 +00:00
magenta . setGraphicSize ( Std . int ( bg . width ) ) ;
magenta . updateHitbox ( ) ;
magenta . x = bg . x ;
magenta . y = bg . y ;
magenta . visible = false ;
2023-10-17 04:38:28 +00:00
if ( Preferences . flashingLights ) add ( magenta ) ;
2023-01-23 03:25:45 +00:00
2025-06-25 22:14:10 +00:00
menuItems = new MenuTypedList < AtlasMenuItem > ( ) ;
2023-01-23 03:25:45 +00:00
add ( menuItems ) ;
2025-08-13 01:07:04 +00:00
2023-01-23 03:25:45 +00:00
menuItems . onChange . add ( onMenuItemChange ) ;
2025-08-13 01:07:04 +00:00
menuItems . onAcceptPress . add ( _ - > {
2024-08-28 10:03:26 +00:00
FlxFlicker . flicker ( magenta , 1.1 , 0.15 , false , true ) ;
2025-08-13 01:07:04 +00:00
uiStateMachine . transition ( Interacting ) ;
2023-01-23 03:25:45 +00:00
} ) ;
2025-08-13 01:07:04 +00:00
menuItems . enabled = true ;
createMenuItem ( ' s t o r y m o d e ' , ' m a i n m e n u / s t o r y m o d e ' , ( ) - > {
FlxG . signals . preStateSwitch . addOnce ( ( ) - > {
2025-03-17 18:41:44 +00:00
funkin . FunkinMemory . clearFreeplay ( ) ;
funkin . FunkinMemory . purgeCache ( ) ;
} ) ;
startExitState ( ( ) - > new StoryMenuState ( ) ) ;
} ) ;
2025-08-13 01:07:04 +00:00
2023-03-16 04:55:25 +00:00
createMenuItem ( ' f r e e p l a y ' , ' m a i n m e n u / f r e e p l a y ' , function ( ) {
2023-01-23 03:25:45 +00:00
persistentDraw = true ;
persistentUpdate = false ;
2025-08-13 01:07:04 +00:00
rememberedSelectedIndex = menuItems ? . selectedIndex ? ? 0 ;
2023-08-02 15:00:23 +00:00
// Freeplay has its own custom transition
2024-03-17 02:20:22 +00:00
FlxTransitionableState . skipNextTransIn = true ;
FlxTransitionableState . skipNextTransOut = true ;
2024-04-30 03:31:55 +00:00
2025-05-29 20:17:41 +00:00
// Since CUTOUT_WIDTH is static it might retain some old inccrect values so we update it before loading freeplay
FreeplayState . CUTOUT_WIDTH = funkin . ui . FullScreenScaleMode . gameCutoutSize . x / 1.5 ;
2024-09-01 07:22:34 +00:00
#if FEATURE_DEBUG_FUNCTIONS
// Debug function: Hold SHIFT when selecting Freeplay to swap character without the char select menu
2025-08-13 01:07:04 +00:00
var targetCharacter: Null < String > = FlxG . keys . pressed . SHIFT ? ( FreeplayState . rememberedCharacterId == " p i c o " ? " b f " : " p i c o " ) : FreeplayState . rememberedCharacterId ;
2024-09-01 07:22:34 +00:00
#else
2025-08-13 01:07:04 +00:00
var targetCharacter: Null < String > = FreeplayState . rememberedCharacterId ;
2024-09-01 07:22:34 +00:00
#end
2025-06-09 16:49:37 +00:00
if ( ! hasUpgraded )
{
for ( i in 0 ... upgradeSparkles . length )
{
upgradeSparkles . members [ i ] . cancelSparkle ( ) ;
}
}
2024-09-01 07:22:34 +00:00
openSubState ( new FreeplayState (
{
character : targetCharacter
} ) ) ;
2023-01-23 03:25:45 +00:00
} ) ;
2025-06-10 10:13:04 +00:00
if ( hasUpgraded )
{
#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.
2025-08-13 01:07:04 +00:00
var hasPopupBlocker: Bool = #if web true #else false #end ;
2025-06-10 10:13:04 +00:00
createMenuItem ( ' m e r c h ' , ' m a i n m e n u / m e r c h ' , selectMerch , hasPopupBlocker ) ;
#end
}
e lse
2025-06-09 16:49:37 +00:00
{
add ( upgradeSparkles ) ;
createMenuItem ( ' u p g r a d e ' , ' m a i n m e n u / u p g r a d e ' , function ( ) {
2025-06-10 11:52:06 +00:00
#if FEATURE_MOBILE_IAP
InAppPurchasesUtil . purchase ( InAppPurchasesUtil . UPGRADE_PRODUCT_ID , FlxG . resetState ) ;
2025-07-24 17:54:01 +00:00
uiStateMachine . transition ( Idle ) ;
2025-06-10 10:13:04 +00:00
#end
2025-06-09 16:49:37 +00:00
} ) ;
}
2025-07-15 17:26:45 +00:00
if ( #if mobile ControlsHandler . usingExternalInputDevice #else t r u e #end )
{
2025-06-29 00:17:45 +00:00
createMenuItem ( ' o p t i o n s ' , ' m a i n m e n u / o p t i o n s ' , function ( ) {
startExitState ( ( ) - > new funkin . ui . options . OptionsState ( ) ) ;
} ) ;
}
2025-06-10 10:13:04 +00:00
2024-03-28 06:57:51 +00:00
createMenuItem ( ' c r e d i t s ' , ' m a i n m e n u / c r e d i t s ' , function ( ) {
2024-03-26 16:33:54 +00:00
startExitState ( ( ) - > new funkin . ui . credits . CreditsState ( ) ) ;
} ) ;
2023-01-23 03:25:45 +00:00
// Reset position of menu items.
2025-08-13 01:07:04 +00:00
final spacing : Float = 160 ;
final top : Float = ( FlxG . height - ( spacing * ( menuItems . length - 1 ) ) ) / 2 ;
for ( index => menuItem in menuItems )
2023-01-23 03:25:45 +00:00
{
menuItem . x = FlxG . width / 2 ;
2025-08-13 01:07:04 +00:00
menuItem . y = top + spacing * index ;
2025-06-11 23:04:05 +00:00
menuItem . scrollFactor . x = #if ! mobile 0.0 #else 0.4 #end ; // we want a lil scroll on mobile, for the cute gyro effect
2024-03-26 16:33:54 +00:00
// This one affects how much the menu items move when you scroll between them.
menuItem . scrollFactor . y = 0.4 ;
2025-06-04 16:41:47 +00:00
2025-08-13 01:07:04 +00:00
if ( index == 1 ) camFollow . setPosition ( menuItem . getGraphicMidpoint ( ) . x , menuItem . getGraphicMidpoint ( ) . y ) ;
2023-01-23 03:25:45 +00:00
}
2024-05-18 22:29:46 +00:00
menuItems . selectItem ( rememberedSelectedIndex ) ;
2025-06-09 16:49:37 +00:00
if ( ! hasUpgraded )
{
// the upgrade item
2025-06-09 20:21:09 +00:00
var targetItem = menuItems . members [ 2 ] ;
2025-08-13 01:07:04 +00:00
for ( _ in 0 ... 8 )
2025-06-09 16:49:37 +00:00
{
var sparkle: UpgradeSparkle = new UpgradeSparkle ( targetItem . x - ( targetItem . width / 2 ) , targetItem . y - ( targetItem . height / 2 ) , targetItem . width ,
targetItem . height , FlxG . random . bool ( 80 ) ) ;
upgradeSparkles . add ( sparkle ) ;
sparkle . scrollFactor . x = 0.0 ;
sparkle . scrollFactor . y = 0.4 ;
}
subStateClosed . add ( _ - > {
for ( i in 0 ... upgradeSparkles . length )
{
upgradeSparkles . members [ i ] . restartSparkle ( ) ;
}
} ) ;
}
2023-03-16 04:55:25 +00:00
resetCamStuff ( ) ;
2024-06-25 03:43:34 +00:00
// reset camera when debug menu is closed
subStateClosed . add ( _ - > resetCamStuff ( false ) ) ;
2025-06-26 01:58:37 +00:00
subStateOpened . add ( ( sub : FlxSubState ) - > {
2025-07-23 15:04:28 +00:00
if ( Std . isOfType ( sub , FreeplayState ) )
2023-03-16 04:55:25 +00:00
{
2025-08-13 01:07:04 +00:00
FlxTimer . wait ( 0.5 , ( ) - > {
2023-03-16 04:55:25 +00:00
magenta . visible = false ;
} ) ;
}
} ) ;
2023-01-23 03:25:45 +00:00
// FlxG.camera.setScrollBounds(bg.x, bg.x + bg.width, bg.y, bg.y + bg.height * 1.2);
2025-04-14 15:20:15 +00:00
#if mobile
2025-06-16 23:50:01 +00:00
gyroPan = new FlxPoint ( ) ;
2025-06-11 23:04:05 +00:00
camFollow . y = bg . getGraphicMidpoint ( ) . y ;
2025-06-09 20:21:09 +00:00
2025-06-23 13:21:31 +00:00
// TODO: This is absolutely disgusting but what the hell sure, fix it later -Zack
2025-06-08 03:09:20 +00:00
addBackButton ( FlxG . width - 230 , FlxG . height - 200 , FlxColor . WHITE , goBack , 1.0 ) ;
2025-06-19 15:12:13 +00:00
2025-06-29 00:17:45 +00:00
if ( ! ControlsHandler . usingExternalInputDevice )
{
2025-07-24 17:54:01 +00:00
addOptionsButton ( 35 , FlxG . height - 210 , goOptions ) ;
2025-06-29 00:17:45 +00:00
}
2025-06-19 15:12:13 +00:00
2025-08-13 01:07:04 +00:00
backButton ? . onConfirmStart . add ( ( ) - > {
uiStateMachine . transition ( Interacting ) ;
trace ( ' B A C K : I n t e r a c t S t a r t ' ) ;
} ) ;
2025-06-23 13:21:31 +00:00
2025-08-13 01:07:04 +00:00
optionsButton ? . onConfirmStart . add ( ( ) - > {
uiStateMachine . transition ( Interacting ) ;
2025-07-24 17:54:01 +00:00
trace ( ' O P T I O N S : I n t e r a c t S t a r t ' ) ;
} ) ;
2025-04-14 15:20:15 +00:00
#end
2023-01-23 03:25:45 +00:00
super . create ( ) ;
// This has to come AFTER!
2025-08-13 01:07:04 +00:00
initLeftWatermarkText ( ) ;
}
2025-04-23 03:04:09 +00:00
2025-08-13 01:07:04 +00:00
function initLeftWatermarkText ( ) : Void
{
if ( leftWatermarkText == null ) return ;
leftWatermarkText . text = Constants . VERSION ;
#if FEATURE_NEWGROUNDS
if ( NewgroundsClient . instance . isLoggedIn ( ) )
{
leftWatermarkText . text += ' | N e w g r o u n d s : L o g g e d i n a s ${ NewgroundsClient . instance . user ? . name } ' ;
2024-11-01 22:35:19 +00:00
}
2025-08-13 01:07:04 +00:00
#end
2023-01-23 03:25:45 +00:00
}
2024-03-12 03:42:32 +00:00
function playMenuMusic ( ) : Void
{
2024-03-23 21:50:48 +00:00
FunkinSound . playMusic ( ' f r e a k y M e n u ' ,
{
overrideExisting : true ,
2024-10-02 05:57:25 +00:00
restartTrack : false ,
// Continue playing this music between states, until a different music track gets played.
persist : true
2024-03-23 21:50:48 +00:00
} ) ;
2024-03-12 03:42:32 +00:00
}
2025-06-25 13:04:22 +00:00
function resetCamStuff ( snap : Bool = true ) : Void
2023-03-16 04:55:25 +00:00
{
FlxG . camera . follow ( camFollow , null , 0.06 ) ;
2024-06-25 03:43:34 +00:00
if ( snap ) FlxG . camera . snapToTarget ( ) ;
2023-03-16 04:55:25 +00:00
}
2023-01-23 03:25:45 +00:00
function createMenuItem ( name : String , atlas : String , callback : Void -> Void , fireInstantly : Bool = false ) : Void
{
2025-08-13 01:07:04 +00:00
if ( menuItems == null ) return ;
2023-01-23 03:25:45 +00:00
2025-08-13 01:07:04 +00:00
var item: AtlasMenuItem = new AtlasMenuItem ( name , Paths . getSparrowAtlas ( atlas ) , callback ) ;
item . fireInstantly = fireInstantly ;
item . ID = menuItems . length ;
item . scrollFactor . set ( ) ;
2023-01-23 03:25:45 +00:00
2025-08-13 01:07:04 +00:00
// Set the offset of the item so the sprite is centered on the origin.
item . centered = true ;
item . changeAnim ( ' i d l e ' ) ;
menuItems . addItem ( name , item ) ;
2023-01-23 03:25:45 +00:00
}
2025-06-04 16:41:47 +00:00
var buttonGrp: Array < FlxSprite > = [ ] ;
function createMenuButtion ( name : String , atlas : String , callback : Void -> Void ) : Void
{
2025-08-13 01:07:04 +00:00
var item: FunkinButton = new FunkinButton ( Math . round ( FlxG . width * 0.8 ) , Math . round ( FlxG . height * 0.7 ) ) ;
2025-06-04 16:41:47 +00:00
item . makeGraphic ( 250 , 250 , FlxColor . BLUE ) ;
item . onDown . add ( callback ) ;
buttonGrp . push ( item ) ;
}
2024-04-24 23:45:17 +00:00
override function closeSubState ( ) : Void
2023-01-23 03:25:45 +00:00
{
magenta . visible = false ;
2025-08-17 21:38:53 +00:00
// when we are in Transition (fade in on new FlxState) we don't really care about substate closing
// this fixes issue when Entering w/ fade -> interacting -> fade ends, so it transitions to Idle on our substate end here
if ( ! ( subState i s f l i x e l . a d d o n s . t r a n s i t i o n . T r a n s i t i o n ) )
{
uiStateMachine . transition ( Idle ) ;
2025-08-18 20:55:16 +00:00
#if FEATURE_TOUCH_CONTROLS
// we want to reset our backButton + optionsButton if we are returning to the main menu from a substate like freeplay
// however, we dont want to trigger these resets if we are entering the state
backButton ? . animation . play ( ' i d l e ' ) ;
backButton ? . resetCallbacks ( ) ;
optionsButton ? . animation . play ( ' i d l e ' ) ;
optionsButton ? . resetCallbacks ( ) ;
#end
2025-08-17 21:38:53 +00:00
}
2023-01-23 03:25:45 +00:00
super . closeSubState ( ) ;
}
2023-11-07 09:04:22 +00:00
function onMenuItemChange ( selected : MenuListItem )
2023-01-23 03:25:45 +00:00
{
2025-07-15 17:26:45 +00:00
if ( #if mobile ControlsHandler . usingExternalInputDevice #else t r u e #end ) camFollow . setPosition ( selected . getGraphicMidpoint ( ) . x ,
selected . getGraphicMidpoint ( ) . y ) ;
2023-01-23 03:25:45 +00:00
}
2024-09-17 04:35:23 +00:00
#if FEATURE_OPEN_URL
2023-01-23 03:25:45 +00:00
function selectDonate ( )
{
WindowUtil . openURL ( Constants . URL_ITCH ) ;
}
2024-03-28 06:57:51 +00:00
function selectMerch ( )
{
2025-04-22 02:38:50 +00:00
Referral . doMerchReferral ( ) ;
2025-07-24 17:54:01 +00:00
uiStateMachine . transition ( Idle ) ;
2024-03-28 06:57:51 +00:00
}
2023-01-23 03:25:45 +00:00
#end
2024-04-24 23:45:17 +00:00
public function openPrompt ( prompt : Prompt , onClose : Void -> Void ) : Void
2023-01-23 03:25:45 +00:00
{
2025-08-13 01:07:04 +00:00
uiStateMachine . transition ( Interacting ) ;
2024-04-30 03:31:55 +00:00
persistentUpdate = false ;
2023-03-16 04:55:25 +00:00
prompt . closeCallback = function ( ) {
2025-08-13 01:07:04 +00:00
// in our closeSubstate override, we set the uiStateMachine, so no need to set here
2023-01-23 03:25:45 +00:00
if ( onClose != null ) onClose ( ) ;
}
openSubState ( prompt ) ;
}
2024-04-24 23:45:17 +00:00
function startExitState ( state : NextState ) : Void
2023-01-23 03:25:45 +00:00
{
2025-08-13 01:07:04 +00:00
if ( menuItems == null ) return ;
2025-07-17 00:33:03 +00:00
2025-08-13 01:07:04 +00:00
uiStateMachine . transition ( Exiting ) ; // Start fade out
rememberedSelectedIndex = menuItems . selectedIndex ;
2024-05-18 22:29:46 +00:00
2025-08-13 01:07:04 +00:00
// the fadeout duration for the initial alpha tweens, not the screen wipe fadeout!
var fadeOutDuration: Float = 0.4 ;
menuItems . forEach ( item - > {
if ( rememberedSelectedIndex != item . ID ) FlxTween . tween ( item , { alpha : 0 } , fadeOutDuration , { ease : FlxEase . quadOut } ) ;
e lse
item . visible = false ;
} ) ;
2023-01-23 03:25:45 +00:00
2025-08-13 01:07:04 +00:00
#if mobile
if ( optionsButton != null ) FlxTween . tween ( optionsButton , { alpha : 0 } , fadeOutDuration , { ease : FlxEase . quadOut } ) ;
if ( backButton != null ) FlxTween . tween ( backButton , { alpha : 0 } , fadeOutDuration , { ease : FlxEase . quadOut } ) ;
#end
2025-06-09 20:21:09 +00:00
2025-08-13 01:07:04 +00:00
FlxTimer . wait ( fadeOutDuration , ( ) - > {
trace ( ' E x i t i n g M a i n M e n u S t a t e . . . ' ) ;
FlxG . switchState ( state ) ;
} ) ;
2023-01-23 03:25:45 +00:00
}
2024-04-24 23:45:17 +00:00
override function update ( elapsed : Float ) : Void
2023-01-23 03:25:45 +00:00
{
super . update ( elapsed ) ;
2024-09-22 00:33:51 +00:00
Conductor . instance . update ( ) ;
2025-06-11 23:04:05 +00:00
#if mobile
2025-06-29 00:17:45 +00:00
if ( gyroPan != null && bg != null && ! ControlsHandler . usingExternalInputDevice )
2025-06-25 19:27:50 +00:00
{
2025-06-27 18:23:32 +00:00
gyroPan . add ( FlxG . gyroscope . pitch * - 1.25 , FlxG . gyroscope . roll * - 1.25 ) ;
2025-06-16 23:50:01 +00:00
2025-06-25 19:27:50 +00:00
// our pseudo damping
2025-06-28 10:18:24 +00:00
gyroPan . x = MathUtil . smoothLerpPrecision ( gyroPan . x , 0 , elapsed , 2.5 ) ;
gyroPan . y = MathUtil . smoothLerpPrecision ( gyroPan . y , 0 , elapsed , 2.5 ) ;
2025-06-23 13:21:31 +00:00
2025-06-25 19:27:50 +00:00
// how far away from bg mid do we want to pan via gyroPan
camFollow . x = bg . getGraphicMidpoint ( ) . x - gyroPan . x ;
camFollow . y = bg . getGraphicMidpoint ( ) . y - gyroPan . y ;
}
2025-06-11 23:04:05 +00:00
#end
2025-07-24 17:54:01 +00:00
if ( ( FlxG . sound . music ? . volume ? ? 1.0 ) < 0 . 8 )
2025-06-23 13:21:31 +00:00
{
FlxG . sound . music . volume += 0.5 * elapsed ;
}
handleInputs ( ) ;
2025-07-24 17:54:01 +00:00
2025-08-13 01:07:04 +00:00
if ( menuItems != null ) menuItems . busy = ! canInteract ;
#if mobile
if ( optionsButton != null )
{
optionsButton . active = canInteract || optionsButton . confirming ;
optionsButton . enabled = optionsButton . active ;
}
if ( backButton != null )
{
backButton . active = canInteract || backButton . confirming ;
backButton . enabled = backButton . active ;
}
#end
2025-06-23 13:21:31 +00:00
}
function handleInputs ( ) : Void
{
if ( ! canInteract ) return ;
2025-06-28 19:03:39 +00:00
#if FEATURE_DEBUG_MENU
2023-11-16 05:02:42 +00:00
// Open the debug menu, defaults to ` / ~
2023-09-24 04:53:53 +00:00
// This includes stuff like the Chart Editor, so it should be present on all builds.
2023-11-16 05:02:42 +00:00
if ( controls . DEBUG_MENU )
2023-06-15 04:29:54 +00:00
{
2024-04-30 03:31:55 +00:00
persistentUpdate = false ;
2025-08-13 01:07:04 +00:00
uiStateMachine . transition ( Interacting ) ;
2024-04-30 03:31:55 +00:00
2025-02-23 03:23:28 +00:00
// Cancel the currently flickering menu item because it's about to call a state switch
2025-06-25 22:14:10 +00:00
if ( menuItems != null && menuItems . busy ) menuItems . cancelAccept ( ) ;
2025-02-23 03:23:28 +00:00
2023-06-15 04:29:54 +00:00
FlxG . state . openSubState ( new DebugMenuSubState ( ) ) ;
}
2025-06-28 19:03:39 +00:00
#end
2023-01-23 03:25:45 +00:00
2023-09-24 04:53:53 +00:00
#if FEATURE_DEBUG_FUNCTIONS
// Ctrl+Alt+Shift+P = Character Unlock screen
// Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock
2024-09-15 00:26:19 +00:00
// Ctrl+Alt+Shift+M = Revoke requirements for Pico Unlock
2023-09-24 04:53:53 +00:00
// Ctrl+Alt+Shift+R = Score/Rank conflict test
2024-09-11 10:18:16 +00:00
// Ctrl+Alt+Shift+N = Mark all characters as not seen
2023-09-24 04:53:53 +00:00
// Ctrl+Alt+Shift+E = Dump save data
2024-09-27 16:20:36 +00:00
// Ctrl+Alt+Shift+L = Force crash and create a log dump
2023-09-24 04:53:53 +00:00
2025-08-13 23:14:58 +00:00
if ( InputUtil . allPressedWithDebounce ( [ CONTROL , ALT , SHIFT , P ] ) )
2023-09-24 04:53:53 +00:00
{
FlxG . switchState ( ( ) - > new funkin . ui . charSelect . CharacterUnlockState ( ' p i c o ' ) ) ;
}
2025-08-13 23:14:58 +00:00
if ( InputUtil . allPressedWithDebounce ( [ CONTROL , ALT , SHIFT , W ] ) )
2024-04-21 21:23:48 +00:00
{
2024-08-28 10:45:18 +00:00
FunkinSound . playOnce ( Paths . sound ( ' c o n f i r m M e n u ' ) ) ;
2024-09-15 00:26:19 +00:00
// Give the user a score of 1 point on Weekend 1 story mode (Easy difficulty).
2024-04-21 21:23:48 +00:00
// This makes the level count as cleared and displays the songs in Freeplay.
funkin . save . Save . instance . setLevelScore ( ' w e e k e n d 1 ' , ' e a s y ' ,
{
score : 1 ,
tallies :
{
sick : 0 ,
good : 0 ,
bad : 0 ,
shit : 0 ,
missed : 0 ,
combo : 0 ,
maxCombo : 0 ,
totalNotesHit : 0 ,
totalNotes : 0 ,
2024-05-18 00:26:34 +00:00
}
2024-04-21 21:23:48 +00:00
} ) ;
}
2024-06-11 04:40:43 +00:00
2025-08-13 23:14:58 +00:00
if ( InputUtil . allPressedWithDebounce ( [ CONTROL , ALT , SHIFT , M ] ) )
2024-08-28 10:03:26 +00:00
{
2024-08-28 10:45:18 +00:00
FunkinSound . playOnce ( Paths . sound ( ' c o n f i r m M e n u ' ) ) ;
2024-09-15 00:26:19 +00:00
// Give the user a score of 0 points on Weekend 1 story mode (all difficulties).
2024-08-28 10:03:26 +00:00
// This makes the level count as uncleared and no longer displays the songs in Freeplay.
2024-09-15 00:26:19 +00:00
for ( diff in [ ' e a s y ' , ' n o r m a l ' , ' h a r d ' ] )
{
funkin . save . Save . instance . setLevelScore ( ' w e e k e n d 1 ' , diff ,
{
score : 0 ,
tallies :
{
sick : 0 ,
good : 0 ,
bad : 0 ,
shit : 0 ,
missed : 0 ,
combo : 0 ,
maxCombo : 0 ,
totalNotesHit : 0 ,
totalNotes : 0 ,
}
} ) ;
}
2024-08-28 10:03:26 +00:00
}
2025-08-13 23:14:58 +00:00
if ( InputUtil . allPressedWithDebounce ( [ CONTROL , ALT , SHIFT , R ] ) )
2024-06-11 04:40:43 +00:00
{
// Give the user a hypothetical overridden score,
// and see if we can maintain that golden P rank.
funkin . save . Save . instance . setSongScore ( ' t u t o r i a l ' , ' e a s y ' ,
{
score : 1234567 ,
tallies :
{
sick : 0 ,
good : 0 ,
bad : 0 ,
shit : 1 ,
missed : 0 ,
combo : 0 ,
maxCombo : 0 ,
totalNotesHit : 1 ,
totalNotes : 10 ,
}
} ) ;
}
2025-08-13 23:14:58 +00:00
if ( InputUtil . allPressedWithDebounce ( [ CONTROL , ALT , SHIFT , N ] ) )
2024-09-11 10:18:16 +00:00
{
@ : privateAccess
2024-09-12 19:34:16 +00:00
{
funkin . save . Save . instance . data . unlocks . charactersSeen = [ " b f " ] ;
funkin . save . Save . instance . data . unlocks . oldChar = false ;
}
2024-09-11 10:18:16 +00:00
}
2025-08-13 23:14:58 +00:00
if ( InputUtil . allPressedWithDebounce ( [ CONTROL , ALT , SHIFT , E ] ) )
2024-06-11 04:40:43 +00:00
{
2025-08-09 19:14:50 +00:00
funkin . save . Save . instance . debug_dumpSaveJsonSave ( ) ;
2024-06-11 04:40:43 +00:00
}
2024-04-21 21:23:48 +00:00
#end
2025-06-23 13:21:31 +00:00
if ( controls . BACK ) goBack ( ) ;
2025-04-14 15:20:15 +00:00
}
2025-07-24 17:54:01 +00:00
function goOptions ( ) : Void
{
2025-08-13 01:07:04 +00:00
trace ( " O P T I O N S : I n t e r a c t c o m p l e t e . " ) ;
startExitState ( ( ) - > new funkin . ui . options . OptionsState ( ) ) ;
2025-07-24 17:54:01 +00:00
}
function goBack ( ) : Void
2025-04-14 15:20:15 +00:00
{
2025-08-13 01:07:04 +00:00
trace ( " B A C K : I n t e r a c t c o m p l e t e . " ) ;
uiStateMachine . transition ( Exiting ) ;
rememberedSelectedIndex = menuItems ? . selectedIndex ? ? 0 ;
FunkinSound . playOnce ( Paths . sound ( ' c a n c e l M e n u ' ) ) ;
FlxG . switchState ( ( ) - > new TitleState ( ) ) ;
2023-01-23 03:25:45 +00:00
}
2020-11-01 01:11:14 +00:00
}