Reworked Story Menu (data driven)

This commit is contained in:
EliteMasterEric 2023-05-17 16:42:58 -04:00
parent 021f7a0a1c
commit a599dbea11
18 changed files with 416 additions and 80 deletions

View file

@ -130,6 +130,7 @@
<haxelib name="polymod" /> <!-- Modding framework -->
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
<!-- <haxelib name="hxcodec" /> Video playback -->
<haxelib name="json2object" /> <!-- JSON parsing -->
<haxelib name="thx.semver" />
@ -144,6 +145,9 @@
<!--Enable this for Nape release builds for a serious peformance improvement-->
<haxedef name="NAPE_RELEASE_BUILD" unless="debug" />
<!-- TODO: REMOVE THIS!!!! -->
<haxeflag name="-w" value="-WDeprecated" />
<!-- _________________________________ Custom _______________________________ -->
<!-- Disable trace() calls in release builds to bump up performance. -->

View file

@ -207,9 +207,15 @@ class Conductor
}
// FlxSignals are really cool.
if (currentStep != oldStep) stepHit.dispatch();
if (currentStep != oldStep)
{
stepHit.dispatch();
}
if (currentBeat != oldBeat) beatHit.dispatch();
if (currentBeat != oldBeat)
{
beatHit.dispatch();
}
}
@:deprecated // Switch to TimeChanges instead.

View file

@ -153,6 +153,7 @@ class InitState extends FlxTransitionableState
// TODO: Register custom event callbacks here
funkin.data.level.LevelRegistry.instance.loadEntries();
SongEventParser.loadEventCache();
SongDataParser.loadSongCache();
StageDataParser.loadStageCache();

View file

@ -21,6 +21,7 @@ import funkin.shaderslmfao.ScreenWipeShader;
import funkin.ui.AtlasMenuList;
import funkin.ui.MenuList.MenuItem;
import funkin.ui.MenuList;
import funkin.ui.story.StoryMenuState;
import funkin.ui.OptionsState;
import funkin.ui.PreferencesMenu;
import funkin.ui.Prompt;

View file

@ -66,7 +66,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
//
// UNSCRIPTED ENTRIES
//
var entryIdList:Array<String> = DataAssets.listDataFilesInPath(dataFilePath);
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('${dataFilePath}/');
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
return !entries.exists(entryId);
});
@ -101,6 +101,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
return entries.size();
}
public function fetchEntry(id:String):Null<T>
{
return entries.get(id);
}
public function toString():String
{
return 'Registry(' + registryId + ', ${countEntries()} entries)';

View file

@ -13,7 +13,7 @@ typedef LevelData =
* When making changes to the level data format, this should be incremented,
* and a migration function should be added to LevelDataParser to handle old versions.
*/
@:default(LevelRegistry.LEVEL_DATA_VERSION)
@:default(funkin.data.level.LevelRegistry.LEVEL_DATA_VERSION)
var version:String;
/**
@ -50,6 +50,14 @@ typedef LevelPropData =
@:optional
var scale:Float;
/**
* The opacity to render the prop at.
* @default 1.0
*/
@:default(1.0)
@:optional
var alpha:Float;
/**
* If true, the prop is a pixel sprite, and will be rendered without smoothing.
*/
@ -59,11 +67,11 @@ typedef LevelPropData =
/**
* The frequency to bop at, in beats.
* @default 1.0 = every beat
* @default 1 = every beat, 2 = every other beat, etc.
*/
@:default(1.0)
@:default(1)
@:optional
var danceEvery:Float;
var danceEvery:Int;
/**
* The offset on the position to render the prop at.
@ -71,7 +79,7 @@ typedef LevelPropData =
*/
@:default([0, 0])
@:optional
var offset:Array<Float>;
var offsets:Array<Float>;
/**
* A set of animations to play on the prop.

View file

@ -55,10 +55,10 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
}
/**
* A list of all the story weeks, in order.
* A list of all the story weeks from the base game, in order.
* TODO: Should this be hardcoded?
*/
public function listDefaultLevelIds():String
public function listBaseGameLevelIds():Array<String>
{
return [
"tutorial",
@ -70,6 +70,16 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
"week6",
"week7",
"weekend1"
]
];
}
/**
* A list of all installed story weeks that are not from the base game.
*/
public function listModdedLevelIds():Array<String>
{
return listEntryIds().filter(function(id:String):Bool {
return listBaseGameLevelIds().indexOf(id) == -1;
});
}
}

View file

@ -277,6 +277,7 @@ class PolymodHandler
// TODO: Reload event callbacks
funkin.data.level.LevelRegistry.instance.loadEntries();
SongDataParser.loadSongCache();
StageDataParser.loadStageCache();
CharacterDataParser.loadCharacterCache();

View file

@ -21,36 +21,48 @@ typedef AnimationData =
* ONLY for use by MultiSparrow characters.
* @default The assetPath of the parent sprite
*/
@:default(null)
@:optional
var assetPath:Null<String>;
/**
* Offset the character's position by this amount when playing this animation.
* @default [0, 0]
*/
@:default([0, 0])
@:optional
var offsets:Null<Array<Float>>;
/**
* Whether the animation should loop when it finishes.
* @default false
*/
@:default(false)
@:optional
var looped:Null<Bool>;
/**
* Whether the animation's sprites should be flipped horizontally.
* @default false
*/
@:default(false)
@:optional
var flipX:Null<Bool>;
/**
* Whether the animation's sprites should be flipped vertically.
* @default false
*/
@:default(false)
@:optional
var flipY:Null<Bool>;
/**
* The frame rate of the animation.
* @default 24
*/
@:default(24)
@:optional
var frameRate:Null<Int>;
/**
@ -59,5 +71,7 @@ typedef AnimationData =
* @example [0, 1, 2, 3] (use only the first four frames)
* @default [] (all frames)
*/
@:default([])
@:optional
var frameIndices:Null<Array<Int>>;
}

View file

@ -3,6 +3,7 @@ package funkin.play;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.system.FlxSound;
import funkin.ui.story.StoryMenuState;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.modding.events.ScriptEvent;
@ -208,11 +209,9 @@ class GameOverSubstate extends MusicBeatSubstate
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
// After the animation finishes...
new FlxTimer().start(0.7, function(tmr:FlxTimer)
{
new FlxTimer().start(0.7, function(tmr:FlxTimer) {
// ...fade out the graphics. Then after that happens...
FlxG.camera.fade(FlxColor.BLACK, 2, false, function()
{
FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
// ...close the GameOverSubstate.
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
PlayState.needsReset = true;
@ -276,8 +275,7 @@ class GameOverSubstate extends MusicBeatSubstate
if (PreferencesMenu.getPref('censor-naughty')) randomCensor = [1, 3, 8, 13, 17, 21];
FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function()
{
FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function() {
// Once the quote ends, fade in the game over music.
if (!isEnding && gameOverMusic != null)
{

View file

@ -2,6 +2,7 @@ package funkin.play;
import flixel.FlxCamera;
import flixel.FlxObject;
import funkin.ui.story.StoryMenuState;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.FlxSubState;

View file

@ -29,11 +29,14 @@ class Song // implements IPlayStateScriptedClass
final variations:Array<String>;
final difficulties:Map<String, SongDifficulty>;
var difficultyIds:Array<String>;
public function new(id:String)
{
this.songId = id;
variations = [];
difficultyIds = [];
difficulties = new Map<String, SongDifficulty>();
_metadata = SongDataParser.parseSongMetadata(songId);
@ -61,6 +64,8 @@ class Song // implements IPlayStateScriptedClass
{
for (diffId in metadata.playData.difficulties)
{
difficultyIds.push(diffId);
var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
variations.push(metadata.variation);
@ -138,7 +143,12 @@ class Song // implements IPlayStateScriptedClass
public function listDifficulties():Array<String>
{
return difficulties.keys().array();
return difficultyIds;
}
public function hasDifficulty(diffId:String):Bool
{
return difficulties.exists(diffId);
}
/**

View file

@ -256,6 +256,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
var correctName = correctAnimationName(name);
if (correctName == null) return;
this.animation.paused = false;
this.animation.play(correctName, restart, false, 0);
if (ignoreOther)

View file

@ -4,6 +4,7 @@ import flixel.FlxSprite;
import haxe.Json;
import lime.utils.Assets;
// import flxtyped group
import funkin.ui.story.StoryMenuState;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.util.FlxTimer;
import flixel.FlxG;

View file

@ -1,6 +1,8 @@
package funkin.ui.story;
import flixel.FlxSprite;
import flixel.util.FlxColor;
import funkin.play.song.Song;
import funkin.data.IRegistryEntry;
import funkin.data.level.LevelRegistry;
import funkin.data.level.LevelData;
@ -66,9 +68,11 @@ class Level implements IRegistryEntry<LevelData>
*/
public function getSongDisplayNames(difficulty:String):Array<String>
{
return getSongs().map(function(songId) {
return funkin.play.song.SongData.SongDataParser.fetchSong(songId).getDifficulty(difficulty).songName;
var songList:Array<String> = getSongs() ?? [];
var songNameList:Array<String> = songList.map(function(songId) {
return funkin.play.song.SongData.SongDataParser.fetchSong(songId) ?.getDifficulty(difficulty) ?.songName ?? 'Unknown';
});
return songNameList;
}
/**
@ -112,14 +116,15 @@ class Level implements IRegistryEntry<LevelData>
var firstSongId:String = songList[0];
var firstSong:Song = funkin.play.song.SongData.SongDataParser.fetchSong(firstSongId);
for (difficulty in firstSong.getDifficulties())
for (difficulty in firstSong.listDifficulties())
{
difficulties.push(difficulty);
}
// Filter to only include difficulties that are present in all songs
for (songId in 1...songList.length)
for (songIndex in 1...songList.length)
{
var songId:String = songList[songIndex];
var song:Song = funkin.play.song.SongData.SongDataParser.fetchSong(songId);
for (difficulty in difficulties)
@ -145,7 +150,10 @@ class Level implements IRegistryEntry<LevelData>
var propData = _data.props[propIndex];
var propSprite:LevelProp = LevelProp.build(propData);
propSprite.x += FlxG.width * 0.25 * propIndex;
props.push(propSprite);
}
return props;
}
public function destroy():Void {}

View file

@ -1,5 +1,9 @@
package funkin.ui.story;
import funkin.play.stage.Bopper;
import funkin.util.assets.FlxAnimationUtil;
import funkin.data.level.LevelData;
class LevelProp extends Bopper
{
public function new(danceEvery:Int)
@ -7,6 +11,11 @@ class LevelProp extends Bopper
super(danceEvery);
}
public function playConfirm():Void
{
playAnimation('confirm', true, true);
}
public static function build(propData:LevelPropData):Null<LevelProp>
{
var isAnimated:Bool = propData.animations.length > 0;
@ -33,8 +42,8 @@ class LevelProp extends Bopper
return null;
}
prop.scale.set(propData.scale * (propData.isPixel ? 6 : 1));
prop.updateHitbox();
var scale:Float = propData.scale * (propData.isPixel ? 6 : 1);
prop.scale.set(scale, scale);
prop.antialiasing = !propData.isPixel;
prop.alpha = propData.alpha;
prop.x = propData.offsets[0];
@ -46,6 +55,9 @@ class LevelProp extends Bopper
prop.setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
}
prop.dance();
prop.animation.paused = true;
return prop;
}
}

View file

@ -1,9 +1,10 @@
package funkin.ui.story;
import funkin.CoolUtil;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup;
import flixel.util.FlxColor;
import funkin.CoolUtil;
class LevelTitle extends FlxSpriteGroup
{
@ -24,16 +25,27 @@ class LevelTitle extends FlxSpriteGroup
super(x, y);
this.level = level;
}
public override function create():Void
{
super.create();
if (this.level == null) throw "Level cannot be null!";
buildLevelTitle();
buildLevelLock();
}
override function get_width():Float
{
if (length == 0) return 0;
if (lock.visible)
{
return title.width + lock.width + LOCK_PAD;
}
else
{
return title.width;
}
}
// if it runs at 60fps, fake framerate will be 6
// if it runs at 144 fps, fake framerate will be like 14, and will update the graphic every 0.016666 * 3 seconds still???
// so it runs basically every so many seconds, not dependant on framerate??
@ -42,12 +54,12 @@ class LevelTitle extends FlxSpriteGroup
public override function update(elapsed:Float):Void
{
this.y = CoolUtil.coolLerp(y, (targetY * 120) + 480, 0.17);
this.y = CoolUtil.coolLerp(y, targetY, 0.17);
if (isFlashing) flashingInt += 1;
if (flashingInt % fakeFramerate >= Math.floor(fakeFramerate / 2)) week.color = 0xFF33ffff;
if (flashingInt % fakeFramerate >= Math.floor(fakeFramerate / 2)) title.color = 0xFF33ffff;
else
week.color = FlxColor.WHITE;
title.color = FlxColor.WHITE;
}
public function showLock():Void

View file

@ -1,5 +1,20 @@
package funkin.ui.story;
import flixel.addons.transition.FlxTransitionableState;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.data.level.LevelRegistry;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayState;
import funkin.play.song.SongData.SongDataParser;
import funkin.util.Constants;
class StoryMenuState extends MusicBeatState
{
static final DEFAULT_BACKGROUND_COLOR:FlxColor = FlxColor.fromString("#F9CF51");
@ -10,12 +25,15 @@ class StoryMenuState extends MusicBeatState
var currentLevelId:String = 'tutorial';
var currentLevel:Level;
var isLevelUnlocked:Bool;
var currentLevelTitle:LevelTitle;
var highScore:Int = 42069420;
var highScoreLerp:Int = 12345678;
var exitingMenu:Bool = false;
var selectedWeek:Bool = false;
var selectedLevel:Bool = false;
var displayingModdedLevels:Bool = false;
//
// RENDER OBJECTS
@ -37,7 +55,7 @@ class StoryMenuState extends MusicBeatState
var tracklistText:FlxText;
/**
* The title of the week in the middle.
* The titles of the levels in the middle.
*/
var levelTitles:FlxTypedGroup<LevelTitle>;
@ -66,18 +84,26 @@ class StoryMenuState extends MusicBeatState
*/
var difficultySprite:FlxSprite;
var difficultySprites:Map<String, FlxSprite>;
var stickerSubState:StickerSubState;
public function new(?stickers:StickerSubState = null)
{
super();
if (stickers != null)
{
stickerSubState = stickers;
}
super();
}
override function create():Void
{
super.create();
difficultySprites = new Map<String, FlxSprite>();
transIn = FlxTransitionableState.defaultTransIn;
transOut = FlxTransitionableState.defaultTransOut;
@ -101,92 +127,131 @@ class StoryMenuState extends MusicBeatState
persistentUpdate = persistentDraw = true;
updateData();
// Explicitly define the background color.
this.bgColor = FlxColor.BLACK;
levelTitles = new FlxTypedGroup<LevelTitle>();
add(levelTitles);
levelBackground = new FlxSprite(0, 56).makeGraphic(FlxG.width, BACKGROUND_HEIGHT, DEFAULT_BACKGROUND_COLOR);
add(levelBackground);
updateBackground();
levelProps = new FlxTypedGroup<LevelProp>();
levelProps.zIndex = 1000;
add(levelProps);
updateProps();
scoreText = new FlxText(10, 10, 0, 'HIGH SCORE: 42069420');
scoreText.setFormat("VCR OSD Mono", 32);
add(scoreText);
tracklistText = new FlxText(FlxG.width * 0.05, yellowBG.x + yellowBG.height + 100, 0, "Tracks", 32);
tracklistText = new FlxText(FlxG.width * 0.05, levelBackground.x + levelBackground.height + 100, 0, "Tracks", 32);
tracklistText.setFormat("VCR OSD Mono", 32);
tracklistText.alignment = CENTER;
tracklistText.font = rankText.font;
tracklistText.color = 0xFFe55777;
add(tracklistText);
levelTitleText = new FlxText(FlxG.width * 0.7, 10, 0, 'WEEK 1');
levelTitleText = new FlxText(FlxG.width * 0.7, 10, 0, 'LEVEL 1');
levelTitleText.setFormat("VCR OSD Mono", 32, FlxColor.WHITE, RIGHT);
levelTitleText.alpha = 0.7;
add(levelTitleText);
buildLevelTitles(false);
buildLevelTitles();
leftArrow = new FlxSprite(grpWeekText.members[0].x + grpWeekText.members[0].width + 10, grpWeekText.members[0].y + 10);
leftArrow.frames = Paths.getSparrowAtlas('storymenu/ui/arrows');
leftArrow.animation.addByPrefix('idle', 'leftIdle0');
leftArrow.animation.addByPrefix('press', 'leftConfirm0');
leftArrow.animation.play('idle');
add(leftArrow);
leftDifficultyArrow = new FlxSprite(levelTitles.members[0].x + levelTitles.members[0].width + 10, levelTitles.members[0].y + 10);
leftDifficultyArrow.frames = Paths.getSparrowAtlas('storymenu/ui/arrows');
leftDifficultyArrow.animation.addByPrefix('idle', 'leftIdle0');
leftDifficultyArrow.animation.addByPrefix('press', 'leftConfirm0');
leftDifficultyArrow.animation.play('idle');
add(leftDifficultyArrow);
rightArrow = new FlxSprite(sprDifficulty.x + sprDifficulty.width + 50, leftArrow.y);
rightArrow.frames = leftArrow.frames;
rightArrow.animation.addByPrefix('idle', 'rightIdle0');
rightArrow.animation.addByPrefix('press', 'rightConfirm0');
rightArrow.animation.play('idle');
add(rightArrow);
buildDifficultySprite();
rightDifficultyArrow = new FlxSprite(difficultySprite.x + difficultySprite.width + 10, leftDifficultyArrow.y);
rightDifficultyArrow.frames = leftDifficultyArrow.frames;
rightDifficultyArrow.animation.addByPrefix('idle', 'rightIdle0');
rightDifficultyArrow.animation.addByPrefix('press', 'rightConfirm0');
rightDifficultyArrow.animation.play('idle');
add(rightDifficultyArrow);
difficultySprite = buildDifficultySprite();
changeDifficulty();
add(difficultySprite);
updateText();
changeDifficulty();
changeLevel();
refresh();
#if discord_rpc
// Updating Discord Rich Presence
DiscordClient.changePresence("In the Menus", null);
#end
}
function buildDifficultySprite():Void
function updateData():Void
{
difficultySprite = new FlxSprite(leftArrow.x + 130, leftArrow.y);
difficultySprite.frames = ui_tex;
difficultySprite.animation.addByPrefix('easy', 'EASY');
difficultySprite.animation.addByPrefix('normal', 'NORMAL');
difficultySprite.animation.addByPrefix('hard', 'HARD');
difficultySprite.animation.play('easy');
currentLevel = LevelRegistry.instance.fetchEntry(currentLevelId);
isLevelUnlocked = currentLevel == null ? false : currentLevel.isUnlocked();
}
function buildLevelTitles(moddedLevels:Bool):Void
function buildDifficultySprite():Void
{
remove(difficultySprite);
difficultySprite = difficultySprites.get(currentDifficultyId);
if (difficultySprite == null)
{
difficultySprite = new FlxSprite(leftDifficultyArrow.x + leftDifficultyArrow.width + 10, leftDifficultyArrow.y);
difficultySprite.loadGraphic(Paths.image('storymenu/difficulties/${currentDifficultyId}'));
difficultySprites.set(currentDifficultyId, difficultySprite);
difficultySprite.x += (difficultySprites.get('normal').width - difficultySprite.width) / 2;
}
difficultySprite.alpha = 0;
difficultySprite.y = leftDifficultyArrow.y - 15;
FlxTween.tween(difficultySprite, {y: leftDifficultyArrow.y + 15, alpha: 1}, 0.07);
add(difficultySprite);
}
function buildLevelTitles():Void
{
levelTitles.clear();
var levelIds:Array<String> = LevelRegistry.instance.getLevelIds();
var levelIds:Array<String> = displayingModdedLevels ? LevelRegistry.instance.listModdedLevelIds() : LevelRegistry.instance.listBaseGameLevelIds();
if (levelIds.length == 0) levelIds = ['tutorial'];
for (levelIndex in 0...levelIds.length)
{
var levelId:String = levelIds[levelIndex];
var level:Level = LevelRegistry.instance.fetchEntry(levelId);
var levelTitleItem:LevelTitle = new LevelTitle(0, yellowBG.y + yellowBG.height + 10, level);
levelTitleItem.targetY = ((weekThing.height + 20) * levelIndex);
if (level == null) continue;
var levelTitleItem:LevelTitle = new LevelTitle(0, Std.int(levelBackground.y + levelBackground.height + 10), level);
levelTitleItem.targetY = ((levelTitleItem.height + 20) * levelIndex);
levelTitleItem.screenCenter(X);
levelTitles.add(levelTitleItem);
}
}
function switchMode(moddedLevels:Bool):Void
{
displayingModdedLevels = moddedLevels;
buildLevelTitles();
changeLevel(0);
changeDifficulty(0);
}
override function update(elapsed:Float)
{
highScoreLerp = CoolUtil.coolLerp(highScoreLerp, highScore, 0.5);
Conductor.update();
scoreText.text = 'WEEK SCORE: ${Math.round(highScoreLerp)}';
highScoreLerp = Std.int(CoolUtil.coolLerp(highScoreLerp, highScore, 0.5));
txtWeekTitle.text = weekNames[curWeek].toUpperCase();
txtWeekTitle.x = FlxG.width - (txtWeekTitle.width + 10);
scoreText.text = 'LEVEL SCORE: ${Math.round(highScoreLerp)}';
levelTitleText.text = currentLevel.getTitle();
levelTitleText.x = FlxG.width - (levelTitleText.width + 10); // Right align.
handleKeyPresses();
@ -197,7 +262,7 @@ class StoryMenuState extends MusicBeatState
{
if (!exitingMenu)
{
if (!selectedWeek)
if (!selectedLevel)
{
if (controls.UI_UP_P)
{
@ -211,20 +276,20 @@ class StoryMenuState extends MusicBeatState
if (controls.UI_RIGHT)
{
rightArrow.animation.play('press')
rightDifficultyArrow.animation.play('press');
}
else
{
rightArrow.animation.play('idle');
rightDifficultyArrow.animation.play('idle');
}
if (controls.UI_LEFT)
{
leftArrow.animation.play('press');
leftDifficultyArrow.animation.play('press');
}
else
{
leftArrow.animation.play('idle');
leftDifficultyArrow.animation.play('idle');
}
if (controls.UI_RIGHT_P)
@ -236,15 +301,20 @@ class StoryMenuState extends MusicBeatState
{
changeDifficulty(-1);
}
if (FlxG.keys.justPressed.TAB)
{
switchMode(!displayingModdedLevels);
}
}
if (controls.ACCEPT)
{
selectWeek();
selectLevel();
}
}
if (controls.BACK && !exitingMenu && !selectedWeek)
if (controls.BACK && !exitingMenu && !selectedLevel)
{
FlxG.sound.play(Paths.sound('cancelMenu'));
exitingMenu = true;
@ -252,7 +322,180 @@ class StoryMenuState extends MusicBeatState
}
}
function changeLevel(change:Int = 0):Void {}
/**
* Changes the selected level.
* @param change +1 (down), -1 (up)
*/
function changeLevel(change:Int = 0):Void
{
var levelList:Array<String> = displayingModdedLevels ? LevelRegistry.instance.listModdedLevelIds() : LevelRegistry.instance.listBaseGameLevelIds();
if (levelList.length == 0) levelList = ['tutorial'];
function changeDifficulty(change:Int = 0):Void {}
var currentIndex:Int = levelList.indexOf(currentLevelId);
currentIndex += change;
// Wrap around
if (currentIndex < 0) currentIndex = levelList.length - 1;
if (currentIndex >= levelList.length) currentIndex = 0;
currentLevelId = levelList[currentIndex];
updateData();
for (index in 0...levelTitles.members.length)
{
var item:LevelTitle = levelTitles.members[index];
item.targetY = (index - currentIndex) * 120 + 480;
if (index == currentIndex)
{
currentLevelTitle = item;
item.alpha = 1.0;
}
else if (index > currentIndex)
{
item.alpha = 0.6;
}
else
{
item.alpha = 0.0;
}
}
updateText();
updateBackground();
updateProps();
refresh();
}
/**
* Changes the selected difficulty.
* @param change +1 (right) to increase difficulty, -1 (left) to decrease difficulty
*/
function changeDifficulty(change:Int = 0):Void
{
var difficultyList:Array<String> = currentLevel.getDifficulties();
var currentIndex:Int = difficultyList.indexOf(currentDifficultyId);
currentIndex += change;
// Wrap around
if (currentIndex < 0) currentIndex = difficultyList.length - 1;
if (currentIndex >= difficultyList.length) currentIndex = 0;
currentDifficultyId = difficultyList[currentIndex];
buildDifficultySprite();
}
override function dispatchEvent(event:ScriptEvent):Void
{
// super.dispatchEvent(event) dispatches event to module scripts.
super.dispatchEvent(event);
if ((levelProps?.length ?? 0) > 0)
{
// Dispatch event to props.
for (prop in levelProps)
{
ScriptEventDispatcher.callEvent(prop, event);
}
}
}
function selectLevel()
{
if (!currentLevel.isUnlocked())
{
FlxG.sound.play(Paths.sound('cancelMenu'));
return;
}
if (selectedLevel) return;
selectedLevel = true;
FlxG.sound.play(Paths.sound('confirmMenu'));
currentLevelTitle.isFlashing = true;
for (prop in levelProps.members)
{
prop.playConfirm();
}
PlayState.storyPlaylist = currentLevel.getSongs();
PlayState.isStoryMode = true;
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase());
// TODO: Fix this.
PlayState.storyWeek = 0;
PlayState.campaignScore = 0;
// TODO: Fix this.
PlayState.storyDifficulty = 0;
PlayState.storyDifficulty_NEW = currentDifficultyId;
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
new FlxTimer().start(1, function(tmr:FlxTimer) {
LoadingState.loadAndSwitchState(new PlayState(), true);
});
}
function updateBackground():Void
{
if (levelBackground != null)
{
var oldBackground:FlxSprite = levelBackground;
FlxTween.tween(oldBackground, {alpha: 0.0}, 0.6,
{
ease: FlxEase.linear,
onComplete: function(_) {
remove(oldBackground);
}
});
}
levelBackground = currentLevel.buildBackground();
levelBackground.x = 0;
levelBackground.y = 56;
levelBackground.alpha = 0.0;
levelBackground.zIndex = 100;
add(levelBackground);
FlxTween.tween(levelBackground, {alpha: 1.0}, 0.6,
{
ease: FlxEase.linear
});
}
function updateProps():Void
{
levelProps.clear();
for (prop in currentLevel.buildProps())
{
prop.zIndex = 1000;
levelProps.add(prop);
}
refresh();
}
function updateText():Void
{
tracklistText.text = 'TRACKS\n\n';
tracklistText.text += currentLevel.getSongDisplayNames(currentDifficultyId).join('\n');
tracklistText.screenCenter(X);
tracklistText.x -= FlxG.width * 0.35;
// TODO: Fix this.
highScore = Highscore.getWeekScore(0, 0);
}
}