2023-11-07 04:04:22 -05:00
|
|
|
package funkin.play;
|
2020-10-09 17:24:20 -04:00
|
|
|
|
2024-02-28 00:19:08 -05:00
|
|
|
import flixel.addons.transition.FlxTransitionableState;
|
|
|
|
import flixel.FlxG;
|
|
|
|
import flixel.util.FlxTimer;
|
|
|
|
import flixel.FlxSprite;
|
|
|
|
import flixel.group.FlxSpriteGroup;
|
|
|
|
import flixel.math.FlxMath;
|
|
|
|
import flixel.text.FlxText;
|
|
|
|
import flixel.tweens.FlxEase;
|
|
|
|
import flixel.tweens.FlxTween;
|
|
|
|
import flixel.util.FlxColor;
|
|
|
|
import funkin.audio.FunkinSound;
|
|
|
|
import funkin.data.song.SongRegistry;
|
2024-03-28 22:33:50 -04:00
|
|
|
import funkin.ui.freeplay.FreeplayState;
|
2024-02-28 00:19:08 -05:00
|
|
|
import funkin.graphics.FunkinSprite;
|
|
|
|
import funkin.play.cutscene.VideoCutscene;
|
|
|
|
import funkin.play.PlayState;
|
|
|
|
import funkin.ui.AtlasText;
|
|
|
|
import funkin.ui.MusicBeatSubState;
|
|
|
|
import funkin.ui.transition.StickerSubState;
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Parameters for initializing the PauseSubState.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
typedef PauseSubStateParams =
|
|
|
|
{
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Which mode to start in. Dictates what entries are displayed.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
?mode:PauseMode,
|
|
|
|
};
|
2020-10-09 17:24:20 -04:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
/**
|
|
|
|
* The menu displayed when the Play State is paused.
|
|
|
|
*/
|
2023-06-01 18:52:58 -04:00
|
|
|
class PauseSubState extends MusicBeatSubState
|
2020-10-09 17:24:20 -04:00
|
|
|
{
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Constants
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pause menu entries for when the game is paused during a song.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static final PAUSE_MENU_ENTRIES_STANDARD:Array<PauseMenuEntry> = [
|
2024-02-26 23:18:56 -05:00
|
|
|
{text: 'Resume', callback: resume},
|
|
|
|
{text: 'Restart Song', callback: restartPlayState},
|
|
|
|
{text: 'Change Difficulty', callback: switchMode.bind(_, Difficulty)},
|
2024-02-28 00:19:08 -05:00
|
|
|
{text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> !(PlayState.instance?.isPracticeMode ?? false)},
|
2024-02-26 23:18:56 -05:00
|
|
|
{text: 'Exit to Menu', callback: quitToMenu},
|
2023-01-22 22:25:45 -05:00
|
|
|
];
|
2023-07-26 16:52:58 -04:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Pause menu entries for when the game is paused in the Chart Editor preview.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static final PAUSE_MENU_ENTRIES_CHARTING:Array<PauseMenuEntry> = [
|
2024-02-26 23:18:56 -05:00
|
|
|
{text: 'Resume', callback: resume},
|
|
|
|
{text: 'Restart Song', callback: restartPlayState},
|
|
|
|
{text: 'Return to Chart Editor', callback: quitToChartEditor},
|
|
|
|
];
|
2023-07-26 16:52:58 -04:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Pause menu entries for when the user selects "Change Difficulty".
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static final PAUSE_MENU_ENTRIES_DIFFICULTY:Array<PauseMenuEntry> = [
|
2024-02-26 23:18:56 -05:00
|
|
|
{text: 'Back', callback: switchMode.bind(_, Standard)}
|
|
|
|
// Other entries are added dynamically.
|
|
|
|
];
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Pause menu entries for when the game is paused during a video cutscene.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array<PauseMenuEntry> = [
|
2024-02-26 23:18:56 -05:00
|
|
|
{text: 'Resume', callback: resume},
|
2024-02-28 00:19:08 -05:00
|
|
|
{text: 'Skip Cutscene', callback: skipVideoCutscene},
|
2024-03-15 17:16:44 -04:00
|
|
|
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
2024-02-28 00:19:08 -05:00
|
|
|
{text: 'Exit to Menu', callback: quitToMenu},
|
|
|
|
];
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Pause menu entries for when the game is paused during a conversation.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static final PAUSE_MENU_ENTRIES_CONVERSATION:Array<PauseMenuEntry> = [
|
|
|
|
{text: 'Resume', callback: resume},
|
|
|
|
{text: 'Restart Dialogue', callback: restartConversation},
|
|
|
|
{text: 'Skip Dialogue', callback: skipConversation},
|
2024-02-26 23:18:56 -05:00
|
|
|
{text: 'Exit to Menu', callback: quitToMenu},
|
|
|
|
];
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Duration for the music to fade in when the pause menu is opened.
|
|
|
|
*/
|
|
|
|
static final MUSIC_FADE_IN_TIME:Float = 5;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The final volume for the music when the pause menu is opened.
|
|
|
|
*/
|
|
|
|
static final MUSIC_FINAL_VOLUME:Float = 0.75;
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Defines which pause music to use.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
public static var musicSuffix:String = '';
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Reset the pause configuration to the default.
|
|
|
|
*/
|
|
|
|
public static function reset():Void
|
|
|
|
{
|
|
|
|
musicSuffix = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// ===============
|
|
|
|
// Status Variables
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disallow input until transitions are complete!
|
|
|
|
* This prevents the pause menu from immediately closing when opened, among other things.
|
|
|
|
*/
|
|
|
|
public var allowInput:Bool = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The entries currently displayed in the pause menu.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
var currentMenuEntries:Array<PauseMenuEntry>;
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The index of `currentMenuEntries` that is currently selected.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
var currentEntry:Int = 0;
|
2024-02-28 00:19:08 -05:00
|
|
|
|
|
|
|
/**
|
2024-02-28 14:51:39 -05:00
|
|
|
* The mode that the pause menu is currently in.
|
2024-02-28 00:19:08 -05:00
|
|
|
*/
|
2024-02-28 14:51:39 -05:00
|
|
|
var currentMode:PauseMode;
|
2024-02-13 01:38:11 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Graphics Variables
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The semi-transparent black background that appears when the game is paused.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
var background:FunkinSprite;
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The metadata displayed in the top right.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
var metadata:FlxTypedSpriteGroup<FlxText>;
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A text object that displays the current practice mode status.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
var metadataPractice:FlxText;
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A text object that displays the current death count.
|
|
|
|
*/
|
2024-02-28 03:53:36 -05:00
|
|
|
var metadataDeaths:FlxText;
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The actual text objects for the menu entries.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
var menuEntryText:FlxTypedSpriteGroup<AtlasText>;
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Audio Variables
|
|
|
|
// ===============
|
2024-02-26 23:18:56 -05:00
|
|
|
var pauseMusic:FunkinSound;
|
2023-07-26 16:52:58 -04:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Constructor
|
|
|
|
// ===============
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
public function new(?params:PauseSubStateParams)
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
|
|
|
super();
|
2024-02-28 00:19:08 -05:00
|
|
|
this.currentMode = params?.mode ?? Standard;
|
2024-02-26 23:18:56 -05:00
|
|
|
}
|
2023-07-26 16:52:58 -04:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Lifecycle Functions
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the state is first loaded.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
public override function create():Void
|
|
|
|
{
|
|
|
|
super.create();
|
2023-10-11 17:33:55 -04:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
startPauseMusic();
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 00:19:08 -05:00
|
|
|
buildBackground();
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
buildMetadata();
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 00:19:08 -05:00
|
|
|
regenerateMenu();
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
transitionIn();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Called every frame.
|
|
|
|
* @param elapsed The time elapsed since the last frame, in seconds.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
public override function update(elapsed:Float):Void
|
|
|
|
{
|
|
|
|
super.update(elapsed);
|
|
|
|
|
|
|
|
handleInputs();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Called when the state is closed.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
public override function destroy():Void
|
|
|
|
{
|
|
|
|
super.destroy();
|
|
|
|
pauseMusic.stop();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Initialization Functions
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Play the pause music.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
function startPauseMusic():Void
|
|
|
|
{
|
2024-03-27 17:42:29 -04:00
|
|
|
var pauseMusicPath:String = Paths.music('breakfast$musicSuffix/breakfast$musicSuffix');
|
2024-02-28 14:51:39 -05:00
|
|
|
pauseMusic = FunkinSound.load(pauseMusicPath, true, true);
|
|
|
|
|
|
|
|
if (pauseMusic == null)
|
|
|
|
{
|
|
|
|
FlxG.log.warn('Could not play pause music: ${pauseMusicPath} does not exist!');
|
|
|
|
}
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
// Start playing at a random point in the song.
|
|
|
|
pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2)));
|
|
|
|
pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME);
|
|
|
|
}
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Render the semi-transparent black background.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
function buildBackground():Void
|
|
|
|
{
|
|
|
|
// Using state.bgColor causes bugs!
|
|
|
|
background = new FunkinSprite(0, 0);
|
|
|
|
background.makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK);
|
|
|
|
background.alpha = 0.0;
|
|
|
|
background.scrollFactor.set(0, 0);
|
|
|
|
background.updateHitbox();
|
|
|
|
add(background);
|
|
|
|
}
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
/**
|
|
|
|
* Render the metadata in the top right.
|
|
|
|
*/
|
|
|
|
function buildMetadata():Void
|
|
|
|
{
|
2024-02-28 00:19:08 -05:00
|
|
|
metadata = new FlxTypedSpriteGroup<FlxText>();
|
|
|
|
metadata.scrollFactor.set(0, 0);
|
2024-02-26 23:18:56 -05:00
|
|
|
add(metadata);
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 00:19:08 -05:00
|
|
|
var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name - Artist');
|
2024-02-26 23:18:56 -05:00
|
|
|
metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
|
|
|
if (PlayState.instance?.currentChart != null)
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
2024-02-28 00:19:08 -05:00
|
|
|
metadataSong.text = '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
|
2023-01-22 22:25:45 -05:00
|
|
|
}
|
2024-02-28 00:19:08 -05:00
|
|
|
metadataSong.scrollFactor.set(0, 0);
|
2024-02-26 23:18:56 -05:00
|
|
|
metadata.add(metadataSong);
|
|
|
|
|
2024-02-28 00:19:08 -05:00
|
|
|
var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, FlxG.width - 40, 'Difficulty: ');
|
2024-02-26 23:18:56 -05:00
|
|
|
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
|
|
|
if (PlayState.instance?.currentDifficulty != null)
|
|
|
|
{
|
|
|
|
metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
|
|
|
|
}
|
2024-02-28 00:19:08 -05:00
|
|
|
metadataDifficulty.scrollFactor.set(0, 0);
|
2024-02-26 23:18:56 -05:00
|
|
|
metadata.add(metadataDifficulty);
|
|
|
|
|
2024-02-28 03:53:36 -05:00
|
|
|
metadataDeaths = new FlxText(20, 15 + 64, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls');
|
2024-02-26 23:18:56 -05:00
|
|
|
metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
2024-02-28 00:19:08 -05:00
|
|
|
metadataDeaths.scrollFactor.set(0, 0);
|
2024-02-26 23:18:56 -05:00
|
|
|
metadata.add(metadataDeaths);
|
|
|
|
|
2024-02-28 00:19:08 -05:00
|
|
|
metadataPractice = new FlxText(20, 15 + 96, FlxG.width - 40, 'PRACTICE MODE');
|
2024-02-26 23:18:56 -05:00
|
|
|
metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
|
|
|
metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false;
|
2024-02-28 00:19:08 -05:00
|
|
|
metadataPractice.scrollFactor.set(0, 0);
|
2024-02-26 23:18:56 -05:00
|
|
|
metadata.add(metadataPractice);
|
2024-02-28 03:53:36 -05:00
|
|
|
|
|
|
|
updateMetadataText();
|
2023-01-22 22:25:45 -05:00
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Perform additional animations to transition the pause menu in when it is first displayed.
|
|
|
|
*/
|
|
|
|
function transitionIn():Void
|
|
|
|
{
|
2024-02-29 13:53:09 -05:00
|
|
|
FlxTween.tween(background, {alpha: 0.6}, 0.8, {ease: FlxEase.quartOut});
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
// Animate each element a little bit downwards.
|
|
|
|
var delay:Float = 0.1;
|
|
|
|
for (child in metadata.members)
|
|
|
|
{
|
2024-02-29 13:53:09 -05:00
|
|
|
FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 1.8, {ease: FlxEase.quartOut, startDelay: delay});
|
|
|
|
delay += 0.1;
|
2024-02-28 14:51:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
new FlxTimer().start(0.2, (_) -> {
|
|
|
|
allowInput = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// ===============
|
|
|
|
// Input Handling
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process user inputs every frame.
|
|
|
|
*/
|
|
|
|
function handleInputs():Void
|
|
|
|
{
|
|
|
|
if (!allowInput) return;
|
|
|
|
|
|
|
|
if (controls.UI_UP_P)
|
|
|
|
{
|
|
|
|
changeSelection(-1);
|
|
|
|
}
|
|
|
|
if (controls.UI_DOWN_P)
|
|
|
|
{
|
|
|
|
changeSelection(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (controls.ACCEPT)
|
|
|
|
{
|
|
|
|
currentMenuEntries[currentEntry].callback(this);
|
|
|
|
}
|
|
|
|
else if (controls.PAUSE)
|
|
|
|
{
|
|
|
|
resume(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if (debug || FORCE_DEBUG_VERSION)
|
|
|
|
// to pause the game and get screenshots easy, press H on pause menu!
|
|
|
|
if (FlxG.keys.justPressed.H)
|
|
|
|
{
|
|
|
|
var visible = !metadata.visible;
|
|
|
|
|
|
|
|
metadata.visible = visible;
|
|
|
|
menuEntryText.visible = visible;
|
|
|
|
this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move the current selection up or down.
|
|
|
|
* @param change The amount to change the selection by, with sign indicating direction.
|
|
|
|
*/
|
|
|
|
function changeSelection(change:Int = 0):Void
|
|
|
|
{
|
2024-03-23 17:50:48 -04:00
|
|
|
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
currentEntry += change;
|
|
|
|
|
|
|
|
if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1;
|
|
|
|
if (currentEntry >= currentMenuEntries.length) currentEntry = 0;
|
|
|
|
|
|
|
|
for (entryIndex in 0...currentMenuEntries.length)
|
|
|
|
{
|
|
|
|
var isCurrent:Bool = entryIndex == currentEntry;
|
|
|
|
|
|
|
|
var entry:PauseMenuEntry = currentMenuEntries[entryIndex];
|
|
|
|
var text:AtlasText = entry.sprite;
|
|
|
|
|
|
|
|
// Set the transparency.
|
|
|
|
text.alpha = isCurrent ? 1.0 : 0.6;
|
|
|
|
|
|
|
|
// Set the position.
|
|
|
|
var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90;
|
|
|
|
var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48);
|
2024-02-29 13:53:09 -05:00
|
|
|
FlxTween.globalManager.cancelTweensOf(text);
|
|
|
|
FlxTween.tween(text, {x: targetX, y: targetY}, 0.33, {ease: FlxEase.quartOut});
|
2024-02-28 14:51:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ===============
|
|
|
|
// Menu Functions
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the current menu entries and regenerate them based on the current mode.
|
|
|
|
* @param targetMode Optionally specify a mode to switch to before regenerating the menu.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
function regenerateMenu(?targetMode:PauseMode):Void
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
2024-02-28 14:51:39 -05:00
|
|
|
// If targetMode is null, keep the current mode.
|
|
|
|
if (targetMode == null) targetMode = this.currentMode;
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
var previousMode:PauseMode = this.currentMode;
|
2024-02-28 14:51:39 -05:00
|
|
|
this.currentMode = targetMode;
|
2024-02-26 23:18:56 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
resetSelection();
|
|
|
|
chooseMenuEntries();
|
|
|
|
clearAndAddMenuEntries();
|
|
|
|
updateMetadataText();
|
|
|
|
changeSelection();
|
|
|
|
}
|
2024-02-26 23:18:56 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Reset the current selection to the first entry.
|
|
|
|
*/
|
|
|
|
function resetSelection():Void
|
|
|
|
{
|
|
|
|
this.currentEntry = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Select which menu entries to display based on the current mode.
|
|
|
|
*/
|
|
|
|
function chooseMenuEntries():Void
|
|
|
|
{
|
2024-02-26 23:18:56 -05:00
|
|
|
// Choose the correct menu entries.
|
2024-02-28 14:51:39 -05:00
|
|
|
// NOTE: We clone the arrays to prevent modifications to the arrays from affecting the original.
|
2024-02-26 23:18:56 -05:00
|
|
|
switch (this.currentMode)
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
2024-02-26 23:18:56 -05:00
|
|
|
case PauseMode.Standard:
|
|
|
|
currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone();
|
|
|
|
case PauseMode.Charting:
|
|
|
|
currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone();
|
|
|
|
case PauseMode.Difficulty:
|
|
|
|
// Prepend the difficulties.
|
|
|
|
var entries:Array<PauseMenuEntry> = [];
|
|
|
|
if (PlayState.instance.currentChart != null)
|
|
|
|
{
|
2024-04-04 04:08:07 -04:00
|
|
|
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation, true);
|
2024-02-26 23:18:56 -05:00
|
|
|
trace('DIFFICULTIES: ${difficultiesInVariation}');
|
|
|
|
for (difficulty in difficultiesInVariation)
|
|
|
|
{
|
2024-02-28 00:19:08 -05:00
|
|
|
entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)});
|
2024-02-26 23:18:56 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the back button.
|
|
|
|
currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone());
|
2024-02-28 00:19:08 -05:00
|
|
|
case PauseMode.Conversation:
|
|
|
|
currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone();
|
2024-02-26 23:18:56 -05:00
|
|
|
case PauseMode.Cutscene:
|
2024-02-28 00:19:08 -05:00
|
|
|
currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone();
|
2023-01-22 22:25:45 -05:00
|
|
|
}
|
2024-02-28 14:51:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the `menuEntryText` group and render the current menu entries to it.
|
|
|
|
* We first create the `menuEntryText` group if it doesn't already exist.
|
|
|
|
*/
|
|
|
|
function clearAndAddMenuEntries():Void
|
|
|
|
{
|
|
|
|
if (menuEntryText == null)
|
|
|
|
{
|
|
|
|
menuEntryText = new FlxTypedSpriteGroup<AtlasText>();
|
|
|
|
menuEntryText.scrollFactor.set(0, 0);
|
|
|
|
add(menuEntryText);
|
|
|
|
}
|
|
|
|
menuEntryText.clear();
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
// Render out the entries depending on the mode.
|
2024-02-28 00:19:08 -05:00
|
|
|
var entryIndex:Int = 0;
|
|
|
|
var toRemove = [];
|
|
|
|
for (entry in currentMenuEntries)
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
2024-02-28 00:19:08 -05:00
|
|
|
if (entry == null || (entry.filter != null && !entry.filter()))
|
|
|
|
{
|
|
|
|
// Remove entries that should be hidden.
|
|
|
|
toRemove.push(entry);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Handle visible entries.
|
|
|
|
var yPos:Float = 70 * entryIndex + 30;
|
|
|
|
var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD);
|
|
|
|
text.scrollFactor.set(0, 0);
|
|
|
|
text.alpha = 0;
|
|
|
|
menuEntryText.add(text);
|
|
|
|
|
|
|
|
entry.sprite = text;
|
|
|
|
|
|
|
|
entryIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (entry in toRemove)
|
|
|
|
{
|
|
|
|
currentMenuEntries.remove(entry);
|
2023-01-22 22:25:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
// ===============
|
|
|
|
// Metadata Functions
|
|
|
|
// ===============
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the values for the metadata text in the top right.
|
|
|
|
*/
|
2024-02-28 03:53:36 -05:00
|
|
|
function updateMetadataText():Void
|
|
|
|
{
|
|
|
|
metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false;
|
|
|
|
|
|
|
|
switch (this.currentMode)
|
|
|
|
{
|
|
|
|
case Standard | Difficulty:
|
|
|
|
metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls';
|
|
|
|
case Charting:
|
|
|
|
metadataDeaths.text = 'Chart Editor Preview';
|
|
|
|
case Conversation:
|
|
|
|
metadataDeaths.text = 'Dialogue Paused';
|
|
|
|
case Cutscene:
|
|
|
|
metadataDeaths.text = 'Video Paused';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
// ===============
|
|
|
|
// Menu Callbacks
|
|
|
|
// ===============
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Close the pause menu and resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function resume(state:PauseSubState):Void
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
2024-02-28 03:53:36 -05:00
|
|
|
// Resume a paused video if it exists.
|
|
|
|
VideoCutscene.resumeVideo();
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
state.close();
|
|
|
|
}
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Switch the pause menu to the indicated mode.
|
|
|
|
* Create a callback from this using `.bind(_, targetMode)`.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
* @param targetMode The mode to switch to.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function switchMode(state:PauseSubState, targetMode:PauseMode):Void
|
|
|
|
{
|
|
|
|
state.regenerateMenu(targetMode);
|
2023-01-22 22:25:45 -05:00
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Switch the game's difficulty to the indicated difficulty, then resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
* @param difficulty The difficulty to switch to.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function changeDifficulty(state:PauseSubState, difficulty:String):Void
|
2023-01-22 22:25:45 -05:00
|
|
|
{
|
2024-02-26 23:18:56 -05:00
|
|
|
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
// Reset campaign score when changing difficulty
|
|
|
|
// So if you switch difficulty on the last song of a week you get a really low overall score.
|
|
|
|
PlayStatePlaylist.campaignScore = 0;
|
|
|
|
PlayStatePlaylist.campaignDifficulty = difficulty;
|
|
|
|
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-04-01 21:59:53 -04:00
|
|
|
FreeplayState.rememberedDifficulty = difficulty;
|
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
PlayState.instance.needsReset = true;
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
state.close();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Restart the current level, then resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function restartPlayState(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
PlayState.instance.needsReset = true;
|
|
|
|
state.close();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Force the game into practice mode, then update the pause menu.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function enablePracticeMode(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
if (PlayState.instance == null) return;
|
|
|
|
|
|
|
|
PlayState.instance.isPracticeMode = true;
|
2024-02-28 00:19:08 -05:00
|
|
|
state.regenerateMenu();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Restart the paused video cutscene, then resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static function restartVideoCutscene(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
VideoCutscene.restartVideo();
|
|
|
|
state.close();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Skip the paused video cutscene, then resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static function skipVideoCutscene(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
VideoCutscene.finishVideo();
|
|
|
|
state.close();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Restart the paused conversation, then resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static function restartConversation(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
if (PlayState.instance?.currentConversation == null) return;
|
|
|
|
|
|
|
|
PlayState.instance.currentConversation.resetConversation();
|
|
|
|
state.close();
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Skip the paused conversation, then resume the game.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-28 00:19:08 -05:00
|
|
|
static function skipConversation(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
if (PlayState.instance?.currentConversation == null) return;
|
|
|
|
|
|
|
|
PlayState.instance.currentConversation.skipConversation();
|
|
|
|
state.close();
|
2024-02-26 23:18:56 -05:00
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Quit the game and return to the main menu.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function quitToMenu(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
state.allowInput = false;
|
|
|
|
|
|
|
|
PlayState.instance.deathCounter = 0;
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
FlxTransitionableState.skipNextTransIn = true;
|
|
|
|
FlxTransitionableState.skipNextTransOut = true;
|
2023-01-22 22:25:45 -05:00
|
|
|
|
2024-02-26 23:18:56 -05:00
|
|
|
if (PlayStatePlaylist.isStoryMode)
|
|
|
|
{
|
|
|
|
PlayStatePlaylist.reset();
|
2024-02-28 00:19:08 -05:00
|
|
|
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker)));
|
2024-02-26 23:18:56 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-03-28 22:33:50 -04:00
|
|
|
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
2023-01-22 22:25:45 -05:00
|
|
|
}
|
|
|
|
}
|
2024-02-26 23:18:56 -05:00
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Quit the game and return to the chart editor.
|
|
|
|
* @param state The current PauseSubState.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
static function quitToChartEditor(state:PauseSubState):Void
|
|
|
|
{
|
|
|
|
state.close();
|
|
|
|
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
|
|
|
PlayState.instance.close(); // This only works because PlayState is a substate!
|
|
|
|
}
|
2020-10-09 17:24:20 -04:00
|
|
|
}
|
2024-02-26 23:18:56 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Which set of options the pause menu should display.
|
|
|
|
*/
|
|
|
|
enum PauseMode
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* The menu displayed when the player pauses the game during a song.
|
|
|
|
*/
|
|
|
|
Standard;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The menu displayed when the player pauses the game during a song while in charting mode.
|
|
|
|
*/
|
|
|
|
Charting;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The menu displayed when the player moves to change the game's difficulty.
|
|
|
|
*/
|
|
|
|
Difficulty;
|
|
|
|
|
|
|
|
/**
|
2024-02-28 00:19:08 -05:00
|
|
|
* The menu displayed when the player pauses the game during a conversation.
|
|
|
|
*/
|
|
|
|
Conversation;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The menu displayed when the player pauses the game during a video cutscene.
|
2024-02-26 23:18:56 -05:00
|
|
|
*/
|
|
|
|
Cutscene;
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* Represents a single entry in the pause menu.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
typedef PauseMenuEntry =
|
|
|
|
{
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* The text to display for this entry.
|
|
|
|
* TODO: Implement localization.
|
|
|
|
*/
|
2024-02-26 23:18:56 -05:00
|
|
|
var text:String;
|
|
|
|
|
2024-02-28 14:51:39 -05:00
|
|
|
/**
|
|
|
|
* The callback to execute when the user selects this entry.
|
|
|
|
*/
|
|
|
|
var callback:PauseSubState->Void;
|
2024-02-26 23:18:56 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* If this returns true, the entry will be displayed. If it returns false, the entry will be hidden.
|
|
|
|
*/
|
|
|
|
var ?filter:Void->Bool;
|
2024-02-28 14:51:39 -05:00
|
|
|
|
|
|
|
// Instance-specific properties
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The text object currently displaying this entry.
|
|
|
|
*/
|
|
|
|
var ?sprite:AtlasText;
|
2024-02-26 23:18:56 -05:00
|
|
|
};
|