Merge pull request #102 from FunkinCrew/bugfix/character-data

Play State refactor and character data fixes
This commit is contained in:
Cameron Taylor 2023-06-13 19:39:46 -04:00 committed by GitHub
commit c969a53396
37 changed files with 1752 additions and 3790 deletions

View file

@ -105,7 +105,7 @@
<haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel --> <haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel -->
<haxelib name="polymod" /> <!-- Modding framework --> <haxelib name="polymod" /> <!-- Modding framework -->
<haxelib name="flxanimate" /> <!-- Texture atlas rendering --> <haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
<!-- <haxelib name="hxcodec" /> Video playback --> <haxelib name="hxCodec" /> <!-- Video playback -->
<haxelib name="json2object" /> <!-- JSON parsing --> <haxelib name="json2object" /> <!-- JSON parsing -->
<haxelib name="thx.semver" /> <haxelib name="thx.semver" />
<haxelib name="hxcpp-debug-server" if="desktop debug" /> <haxelib name="hxcpp-debug-server" if="desktop debug" />

View file

@ -65,7 +65,7 @@
"version": "2.5.0" "version": "2.5.0"
}, },
{ {
"name": "hxcodec", "name": "hxCodec",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "c42ab99", "ref": "c42ab99",

View file

@ -50,6 +50,16 @@ class Conductor
// OLD, replaced with timeChanges. // OLD, replaced with timeChanges.
public static var bpmChangeMap:Array<BPMChangeEvent> = []; public static var bpmChangeMap:Array<BPMChangeEvent> = [];
/**
* Duration of a measure in milliseconds. Calculated based on bpm.
*/
public static var measureLengthMs(get, null):Float;
static function get_measureLengthMs():Float
{
return crochet * timeSignatureNumerator;
}
/** /**
* Duration of a beat in millisecond. Calculated based on bpm. * Duration of a beat in millisecond. Calculated based on bpm.
*/ */

View file

@ -20,13 +20,6 @@ import openfl.filters.ShaderFilter;
class CoolUtil class CoolUtil
{ {
public static var difficultyArray:Array<String> = ['EASY', "NORMAL", "HARD"];
public static function difficultyString():String
{
return difficultyArray[PlayState.storyDifficulty];
}
public static function coolBaseLog(base:Float, fin:Float):Float public static function coolBaseLog(base:Float, fin:Float):Float
{ {
return Math.log(fin) / Math.log(base); return Math.log(fin) / Math.log(base);

View file

@ -1,80 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.addons.display.FlxGridOverlay;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import openfl.Assets;
import openfl.display.BitmapData;
import openfl.display.MovieClip;
import openfl.display.Timeline;
import openfl.geom.Matrix;
import openfl.geom.Rectangle;
class CutsceneAnimTestState extends FlxState
{
var cutsceneGroup:CutsceneCharacter;
var curSelected:Int = 0;
var debugTxt:FlxText;
var funnySprite:FlxSprite = new FlxSprite();
var clip:MovieClip;
public function new()
{
super();
var gridBG:FlxSprite = FlxGridOverlay.create(10, 10);
gridBG.scrollFactor.set(0.5, 0.5);
add(gridBG);
debugTxt = new FlxText(900, 20, 0, "", 20);
debugTxt.color = FlxColor.BLUE;
add(debugTxt);
clip = Assets.getMovieClip("tanky:");
// clip.x = FlxG.width/2;
// clip.y = FlxG.height/2;
FlxG.stage.addChild(clip);
var swagShit:MovieClip = Assets.getMovieClip('tankBG:');
// swagShit.scaleX = 5;
FlxG.stage.addChild(swagShit);
swagShit.gotoAndStop(13);
var swfMountain = new BitmapData(FlxG.width, FlxG.height, true, 0x00000000);
swfMountain.draw(swagShit, swagShit.transform.matrix);
var mountains:FlxSprite = new FlxSprite().loadGraphic(swfMountain);
// add(mountains);
FlxG.stage.removeChild(swagShit);
funnySprite.x = FlxG.width / 2;
funnySprite.y = FlxG.height / 2;
add(funnySprite);
}
override function update(elapsed:Float)
{
super.update(elapsed);
// jam sprite into top left corner
var drawMatrix:Matrix = clip.transform.matrix;
var bounds:Rectangle = clip.getBounds(null);
drawMatrix.tx = -bounds.x;
drawMatrix.ty = -bounds.y;
// make bitmapdata only as big as it needs to be
var funnyBmp:BitmapData = new BitmapData(Math.ceil(bounds.width), Math.ceil(bounds.height), true, 0x00000000);
funnyBmp.draw(clip, drawMatrix, true);
funnySprite.loadGraphic(funnyBmp);
// jam sprite back into place lol
funnySprite.offset.x = -bounds.x;
funnySprite.offset.y = -bounds.y;
}
}

View file

@ -1,74 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxPoint;
class CutsceneCharacter extends FlxTypedGroup<FlxSprite>
{
public var coolPos:FlxPoint = FlxPoint.get();
public var animShit:Map<String, FlxPoint> = new Map();
var imageShit:String;
public function new(x:Float, y:Float, imageShit:String)
{
super();
coolPos.set(x, y);
this.imageShit = imageShit;
parseOffsets();
createCutscene(0);
}
// shitshow, oh well
var arrayLMFAOOOO:Array<String> = [];
function parseOffsets()
{
var splitShit:Array<String> = CoolUtil.coolTextFile(Paths.file('images/cutsceneStuff/' + imageShit + "CutsceneOffsets.txt"));
for (i in splitShit)
{
var xAndY:FlxPoint = FlxPoint.get();
var dumbSplit:Array<String> = i.split('---')[1].trim().split(' ');
trace('cool split: ' + i.split('---')[1]);
trace(dumbSplit);
xAndY.set(Std.parseFloat(dumbSplit[0]), Std.parseFloat(dumbSplit[1]));
animShit.set(i.split('---')[0].trim(), xAndY);
arrayLMFAOOOO.push(i.split('---')[0].trim());
}
trace(animShit);
}
public function createCutscene(daNum:Int = 0)
{
var cutScene:FlxSprite = new FlxSprite(coolPos.x + animShit.get(arrayLMFAOOOO[daNum]).x, coolPos.y + animShit.get(arrayLMFAOOOO[daNum]).y);
cutScene.frames = Paths.getSparrowAtlas('cutsceneStuff/' + imageShit + "-" + daNum);
cutScene.animation.addByPrefix('weed', arrayLMFAOOOO[daNum], 24, false);
cutScene.animation.play('weed');
cutScene.antialiasing = true;
cutScene.animation.finishCallback = function(anim:String) {
cutScene.kill();
cutScene.destroy();
cutScene = null;
if (daNum + 1 < arrayLMFAOOOO.length) createCutscene(daNum + 1);
else
ended();
};
add(cutScene);
}
public var onFinish:Void->Void;
public function ended():Void
{
if (onFinish != null) onFinish();
}
}

View file

@ -38,7 +38,7 @@ class DialogueBox extends FlxSpriteGroup
{ {
super(); super();
switch (PlayState.currentSong.song.toLowerCase()) switch (PlayState.instance.currentSong.songId.toLowerCase())
{ {
case 'senpai': case 'senpai':
FlxG.sound.playMusic(Paths.music('Lunchbox'), 0); FlxG.sound.playMusic(Paths.music('Lunchbox'), 0);
@ -79,7 +79,7 @@ class DialogueBox extends FlxSpriteGroup
box = new FlxSprite(-20, 45); box = new FlxSprite(-20, 45);
var hasDialog:Bool = false; var hasDialog:Bool = false;
switch (PlayState.currentSong.song.toLowerCase()) switch (PlayState.instance.currentSong.songId.toLowerCase())
{ {
case 'senpai': case 'senpai':
hasDialog = true; hasDialog = true;
@ -151,8 +151,8 @@ class DialogueBox extends FlxSpriteGroup
override function update(elapsed:Float):Void override function update(elapsed:Float):Void
{ {
// HARD CODING CUZ IM STUPDI // HARD CODING CUZ IM STUPDI
if (PlayState.currentSong.song.toLowerCase() == 'roses') portraitLeft.visible = false; if (PlayState.instance.currentSong.songId.toLowerCase() == 'roses') portraitLeft.visible = false;
if (PlayState.currentSong.song.toLowerCase() == 'thorns') if (PlayState.instance.currentSong.songId.toLowerCase() == 'thorns')
{ {
portraitLeft.color = FlxColor.BLACK; portraitLeft.color = FlxColor.BLACK;
swagDialogue.color = FlxColor.WHITE; swagDialogue.color = FlxColor.WHITE;
@ -188,8 +188,8 @@ class DialogueBox extends FlxSpriteGroup
{ {
isEnding = true; isEnding = true;
if (PlayState.currentSong.song.toLowerCase() == 'senpai' if (PlayState.instance.currentSong.songId.toLowerCase() == 'senpai'
|| PlayState.currentSong.song.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0); || PlayState.instance.currentSong.songId.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0);
new FlxTimer().start(0.2, function(tmr:FlxTimer) { new FlxTimer().start(0.2, function(tmr:FlxTimer) {
box.alpha -= 1 / 5; box.alpha -= 1 / 5;

View file

@ -34,12 +34,14 @@ import funkin.play.song.SongData.SongDataParser;
import funkin.shaderslmfao.AngleMask; import funkin.shaderslmfao.AngleMask;
import funkin.shaderslmfao.PureColor; import funkin.shaderslmfao.PureColor;
import funkin.shaderslmfao.StrokeShader; import funkin.shaderslmfao.StrokeShader;
import funkin.play.PlayStatePlaylist;
import funkin.play.song.Song;
import lime.app.Future; import lime.app.Future;
import lime.utils.Assets; import lime.utils.Assets;
class FreeplayState extends MusicBeatSubState class FreeplayState extends MusicBeatSubState
{ {
var songs:Array<SongMetadata> = []; var songs:Array<FreeplaySongData> = [];
// var selector:FlxText; // var selector:FlxText;
var curSelected:Int = 0; var curSelected:Int = 0;
@ -112,15 +114,15 @@ class FreeplayState extends MusicBeatSubState
#if debug #if debug
isDebug = true; isDebug = true;
addSong('Test', 1, 'bf-pixel'); addSong('Test', 'tutorial', 'bf-pixel');
addSong('Pyro', 8, 'darnell'); addSong('Pyro', 'weekend1', 'darnell');
#end #end
var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
for (i in 0...initSonglist.length) for (i in 0...initSonglist.length)
{ {
songs.push(new SongMetadata(initSonglist[i], 1, 'gf')); songs.push(new FreeplaySongData(initSonglist[i], 'tutorial', 'gf'));
} }
if (FlxG.sound.music != null) if (FlxG.sound.music != null)
@ -128,22 +130,28 @@ class FreeplayState extends MusicBeatSubState
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu')); if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu'));
} }
if (StoryMenuState.weekUnlocked[2] || isDebug) addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']); // if (StoryMenuState.weekUnlocked[2] || isDebug)
addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 'week1', ['dad']);
if (StoryMenuState.weekUnlocked[2] || isDebug) addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky', 'spooky', 'monster']); // if (StoryMenuState.weekUnlocked[2] || isDebug)
addWeek(['Spookeez', 'South', 'Monster'], 'week2', ['spooky', 'spooky', 'monster']);
if (StoryMenuState.weekUnlocked[3] || isDebug) addWeek(['Pico', 'Philly', 'Blammed'], 3, ['pico']); // if (StoryMenuState.weekUnlocked[3] || isDebug)
addWeek(['Pico', 'Philly-Nice', 'Blammed'], 'week3', ['pico']);
if (StoryMenuState.weekUnlocked[4] || isDebug) addWeek(['Satin-Panties', 'High', 'Milf'], 4, ['mom']); // if (StoryMenuState.weekUnlocked[4] || isDebug)
addWeek(['Satin-Panties', 'High', 'MILF'], 'week4', ['mom']);
if (StoryMenuState.weekUnlocked[5] || isDebug) addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 5, // if (StoryMenuState.weekUnlocked[5] || isDebug)
['parents-christmas', 'parents-christmas', 'monster-christmas']); addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 'week5', ['parents-christmas', 'parents-christmas', 'monster-christmas']);
if (StoryMenuState.weekUnlocked[6] || isDebug) addWeek(['Senpai', 'Roses', 'Thorns'], 6, ['senpai', 'senpai', 'spirit']); // if (StoryMenuState.weekUnlocked[6] || isDebug)
addWeek(['Senpai', 'Roses', 'Thorns'], 'week6', ['senpai', 'senpai', 'spirit']);
if (StoryMenuState.weekUnlocked[7] || isDebug) addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']); // if (StoryMenuState.weekUnlocked[7] || isDebug)
addWeek(['Ugh', 'Guns', 'Stress'], 'week7', ['tankman']);
addWeek(["Darnell", "lit-up", "2hot", "blazin"], 8, ['darnell']); addWeek(["Darnell", "lit-up", "2hot", "blazin"], 'weekend1', ['darnell']);
// LOAD MUSIC // LOAD MUSIC
@ -464,7 +472,7 @@ class FreeplayState extends MusicBeatSubState
grpCapsules.clear(); grpCapsules.clear();
// var regexp:EReg = regexp; // var regexp:EReg = regexp;
var tempSongs:Array<SongMetadata> = songs; var tempSongs:Array<FreeplaySongData> = songs;
if (filterStuff != null) if (filterStuff != null)
{ {
@ -553,19 +561,19 @@ class FreeplayState extends MusicBeatSubState
changeDiff(); changeDiff();
} }
public function addSong(songName:String, weekNum:Int, songCharacter:String) public function addSong(songName:String, levelId:String, songCharacter:String)
{ {
songs.push(new SongMetadata(songName, weekNum, songCharacter)); songs.push(new FreeplaySongData(songName, levelId, songCharacter));
} }
public function addWeek(songs:Array<String>, weekNum:Int, ?songCharacters:Array<String>) public function addWeek(songs:Array<String>, levelId:String, ?songCharacters:Array<String>)
{ {
if (songCharacters == null) songCharacters = ['bf']; if (songCharacters == null) songCharacters = ['bf'];
var num:Int = 0; var num:Int = 0;
for (song in songs) for (song in songs)
{ {
addSong(song, weekNum, songCharacters[num]); addSong(song, levelId, songCharacters[num]);
if (songCharacters.length != 1) num++; if (songCharacters.length != 1) num++;
} }
@ -851,11 +859,9 @@ class FreeplayState extends MusicBeatSubState
curDifficulty = 1; curDifficulty = 1;
}*/ }*/
PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase()); PlayStatePlaylist.isStoryMode = false;
PlayState.currentSong_NEW = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase()); var targetSong:Song = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase());
PlayState.isStoryMode = false; var targetDifficulty:String = switch (curDifficulty)
PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = switch (curDifficulty)
{ {
case 0: case 0:
'easy'; 'easy';
@ -865,27 +871,41 @@ class FreeplayState extends MusicBeatSubState
'hard'; 'hard';
default: 'normal'; default: 'normal';
}; };
// SongLoad.curDiff = Highscore.formatSong()
SongLoad.curDiff = PlayState.storyDifficulty_NEW; // TODO: Implement additional difficulties into the interface properly.
if (FlxG.keys.pressed.E)
{
targetDifficulty = 'erect';
}
PlayState.storyWeek = songs[curSelected].week; // TODO: Implement Pico into the interface properly.
// trace(' CUR WEEK ' + PlayState.storyWeek); var targetCharacter:String = 'bf';
if (FlxG.keys.pressed.P)
{
targetCharacter = 'pico';
}
PlayStatePlaylist.campaignId = songs[curSelected].levelId;
// Visual and audio effects. // Visual and audio effects.
FlxG.sound.play(Paths.sound('confirmMenu')); FlxG.sound.play(Paths.sound('confirmMenu'));
dj.confirm(); dj.confirm();
new FlxTimer().start(1, function(tmr:FlxTimer) { new FlxTimer().start(1, function(tmr:FlxTimer) {
LoadingState.loadAndSwitchState(new PlayState(), true); LoadingState.loadAndSwitchState(new PlayState(
{
targetSong: targetSong,
targetDifficulty: targetDifficulty,
targetCharacter: targetCharacter,
}), true);
}); });
} }
} }
override function startOutro(onComplete:() -> Void):Void override function switchTo(nextState:FlxState):Bool
{ {
clearDaCache(songs[curSelected].songName); clearDaCache(songs[curSelected].songName);
super.startOutro(onComplete); return super.switchTo(nextState);
} }
function changeDiff(change:Int = 0) function changeDiff(change:Int = 0)
@ -901,19 +921,6 @@ class FreeplayState extends MusicBeatSubState
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty); intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty);
PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = switch (curDifficulty)
{
case 0:
'easy';
case 1:
'normal';
case 2:
'hard';
default:
'normal';
};
grpDifficulties.group.forEach(function(spr) { grpDifficulties.group.forEach(function(spr) {
spr.visible = false; spr.visible = false;
}); });
@ -1040,17 +1047,17 @@ enum abstract FilterType(String)
var ALL; var ALL;
} }
class SongMetadata class FreeplaySongData
{ {
public var songName:String = ""; public var songName:String = "";
public var week:Int = 0; public var levelId:String = "";
public var songCharacter:String = ""; public var songCharacter:String = "";
public var isFav:Bool = false; public var isFav:Bool = false;
public function new(song:String, week:Int, songCharacter:String, ?isFav:Bool = false) public function new(song:String, levelId:String, songCharacter:String, ?isFav:Bool = false)
{ {
this.songName = song; this.songName = song;
this.week = week; this.levelId = levelId;
this.songCharacter = songCharacter; this.songCharacter = songCharacter;
this.isFav = isFav; this.isFav = isFav;
} }

View file

@ -11,12 +11,16 @@ class GitarooPause extends MusicBeatState
var replaySelect:Bool = false; var replaySelect:Bool = false;
public function new():Void var previousParams:PlayStateParams;
public function new(previousParams:PlayStateParams):Void
{ {
super(); super();
this.previousParams = previousParams;
} }
override function create() override function create():Void
{ {
if (FlxG.sound.music != null) FlxG.sound.music.stop(); if (FlxG.sound.music != null) FlxG.sound.music.stop();
@ -49,7 +53,7 @@ class GitarooPause extends MusicBeatState
super.create(); super.create();
} }
override function update(elapsed:Float) override function update(elapsed:Float):Void
{ {
if (controls.UI_LEFT_P || controls.UI_RIGHT_P) changeThing(); if (controls.UI_LEFT_P || controls.UI_RIGHT_P) changeThing();
@ -57,7 +61,7 @@ class GitarooPause extends MusicBeatState
{ {
if (replaySelect) if (replaySelect)
{ {
FlxG.switchState(new PlayState()); FlxG.switchState(new PlayState(previousParams));
} }
else else
{ {

View file

@ -39,7 +39,17 @@ class Highscore
return false; return false;
} }
public static function saveCompletion(song:String, completion:Float, ?diff:Int = 0):Bool public static function saveScoreForDifficulty(song:String, score:Int = 0, diff:String = 'normal'):Bool
{
var diffInt:Int = 1;
if (diff == 'easy') diffInt = 0;
else if (diff == 'hard') diffInt = 2;
return saveScore(song, score, diffInt);
}
public static function saveCompletion(song:String, completion:Float, diff:Int = 0):Bool
{ {
var formattedSong:String = formatSong(song, diff); var formattedSong:String = formatSong(song, diff);
@ -57,20 +67,42 @@ class Highscore
return false; return false;
} }
public static function saveWeekScore(week:Int = 1, score:Int = 0, ?diff:Int = 0):Void public static function saveCompletionForDifficulty(song:String, completion:Float, diff:String = 'normal'):Bool
{
var diffInt:Int = 1;
if (diff == 'easy') diffInt = 0;
else if (diff == 'hard') diffInt = 2;
return saveCompletion(song, completion, diffInt);
}
public static function saveWeekScore(week:String, score:Int = 0, diff:Int = 0):Void
{ {
#if newgrounds #if newgrounds
NGio.postScore(score, "Week " + week); NGio.postScore(score, 'Campaign ID $week');
#end #end
var formattedSong:String = formatSong('week' + week, diff); var formattedSong:String = formatSong(week, diff);
if (songScores.exists(formattedSong)) if (songScores.exists(formattedSong))
{ {
if (songScores.get(formattedSong) < score) setScore(formattedSong, score); if (songScores.get(formattedSong) < score) setScore(formattedSong, score);
} }
else else
{
setScore(formattedSong, score); setScore(formattedSong, score);
}
}
public static function saveWeekScoreForDifficulty(week:String, score:Int = 0, diff:String = 'normal'):Void
{
var diffInt:Int = 1;
if (diff == 'easy') diffInt = 0;
else if (diff == 'hard') diffInt = 2;
saveWeekScore(week, score, diffInt);
} }
static function setCompletion(formattedSong:String, completion:Float):Void static function setCompletion(formattedSong:String, completion:Float):Void
@ -122,7 +154,7 @@ class Highscore
return songCompletion.get(formatSong(song, diff)); return songCompletion.get(formatSong(song, diff));
} }
public static function getAllScores() public static function getAllScores():Void
{ {
trace(songScores.toString()); trace(songScores.toString());
} }

View file

@ -12,7 +12,7 @@ import flixel.util.FlxColor;
import funkin.modding.module.ModuleHandler; import funkin.modding.module.ModuleHandler;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.event.SongEvent.SongEventParser; import funkin.play.event.SongEventData.SongEventParser;
import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongDataParser;
import funkin.ui.PreferencesMenu; import funkin.ui.PreferencesMenu;
import funkin.util.WindowUtil; import funkin.util.WindowUtil;
@ -140,10 +140,10 @@ class InitState extends FlxTransitionableState
// WEEK UNLOCK PROGRESSION!! // WEEK UNLOCK PROGRESSION!!
// StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked; // StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked;
if (StoryMenuState.weekUnlocked.length < 4) StoryMenuState.weekUnlocked.insert(0, true); // if (StoryMenuState.weekUnlocked.length < 4) StoryMenuState.weekUnlocked.insert(0, true);
// QUICK PATCH OOPS! // QUICK PATCH OOPS!
if (!StoryMenuState.weekUnlocked[0]) StoryMenuState.weekUnlocked[0] = true; // if (!StoryMenuState.weekUnlocked[0]) StoryMenuState.weekUnlocked[0] = true;
} }
if (FlxG.save.data.seenVideo != null) VideoState.seenVideo = FlxG.save.data.seenVideo; if (FlxG.save.data.seenVideo != null) VideoState.seenVideo = FlxG.save.data.seenVideo;
@ -237,20 +237,18 @@ class InitState extends FlxTransitionableState
{ {
var dif:Int = getDif(); var dif:Int = getDif();
PlayState.currentSong = SongLoad.loadFromJson(song, song); var targetDifficulty = switch (dif)
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
PlayState.isStoryMode = isStoryMode;
PlayState.storyDifficulty = dif;
PlayState.storyDifficulty_NEW = switch (dif)
{ {
case 0: 'easy'; case 0: 'easy';
case 1: 'normal'; case 1: 'normal';
case 2: 'hard'; case 2: 'hard';
default: 'normal'; default: 'normal';
}; };
SongLoad.curDiff = PlayState.storyDifficulty_NEW; LoadingState.loadAndSwitchState(new PlayState(
PlayState.storyWeek = week; {
LoadingState.loadAndSwitchState(new PlayState()); targetSong: SongDataParser.fetchSong(song),
targetDifficulty: targetDifficulty,
}));
} }
} }

View file

@ -1,5 +1,6 @@
package funkin; package funkin;
import funkin.play.PlayStatePlaylist;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.FlxState; import flixel.FlxState;
import flixel.math.FlxMath; import flixel.math.FlxMath;
@ -32,7 +33,7 @@ class LoadingState extends MusicBeatState
this.stopMusic = stopMusic; this.stopMusic = stopMusic;
} }
override function create() override function create():Void
{ {
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0xFFcaff4d); var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0xFFcaff4d);
add(bg); add(bg);
@ -52,57 +53,56 @@ class LoadingState extends MusicBeatState
initSongsManifest().onComplete(function(lib) { initSongsManifest().onComplete(function(lib) {
callbacks = new MultiCallback(onLoad); callbacks = new MultiCallback(onLoad);
var introComplete = callbacks.add("introComplete"); var introComplete = callbacks.add('introComplete');
checkLoadSong(getSongPath()); // checkLoadSong(getSongPath());
if (PlayState.currentSong.needsVoices) // if (PlayState.currentSong.needsVoices)
{ // {
var files = PlayState.currentSong.voiceList; // var files = PlayState.currentSong.voiceList;
//
// if (files == null) files = ['']; // loads with no file name assumption, to load 'Voices.ogg' or whatev normally
//
// for (sndFile in files)
// {
// checkLoadSong(getVocalPath(sndFile));
// }
// }
if (files == null) files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally checkLibrary('shared');
checkLibrary(PlayStatePlaylist.campaignId);
checkLibrary('tutorial');
for (sndFile in files) var fadeTime:Float = 0.5;
{
checkLoadSong(getVocalPath(sndFile));
}
}
checkLibrary("shared");
if (PlayState.storyWeek > 0) checkLibrary("week" + PlayState.storyWeek);
else
checkLibrary("tutorial");
var fadeTime = 0.5;
FlxG.camera.fade(FlxG.camera.bgColor, fadeTime, true); FlxG.camera.fade(FlxG.camera.bgColor, fadeTime, true);
new FlxTimer().start(fadeTime + MIN_TIME, function(_) introComplete()); new FlxTimer().start(fadeTime + MIN_TIME, function(_) introComplete());
}); });
} }
function checkLoadSong(path:String) function checkLoadSong(path:String):Void
{ {
if (!Assets.cache.hasSound(path)) if (!Assets.cache.hasSound(path))
{ {
var library = Assets.getLibrary("songs"); var library = Assets.getLibrary('songs');
var symbolPath = path.split(":").pop(); var symbolPath = path.split(':').pop();
// @:privateAccess // @:privateAccess
// library.types.set(symbolPath, SOUND); // library.types.set(symbolPath, SOUND);
// @:privateAccess // @:privateAccess
// library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]); // library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]);
var callback = callbacks.add("song:" + path); var callback = callbacks.add('song:' + path);
Assets.loadSound(path).onComplete(function(_) { Assets.loadSound(path).onComplete(function(_) {
callback(); callback();
}); });
} }
} }
function checkLibrary(library:String) function checkLibrary(library:String):Void
{ {
trace(Assets.hasLibrary(library)); trace(Assets.hasLibrary(library));
if (Assets.getLibrary(library) == null) if (Assets.getLibrary(library) == null)
{ {
@:privateAccess @:privateAccess
if (!LimeAssets.libraryPaths.exists(library)) throw "Missing library: " + library; if (!LimeAssets.libraryPaths.exists(library)) throw 'Missing library: ' + library;
var callback = callbacks.add("library:" + library); var callback = callbacks.add('library:' + library);
Assets.loadLibrary(library).onComplete(function(_) { Assets.loadLibrary(library).onComplete(function(_) {
callback(); callback();
}); });
@ -121,7 +121,7 @@ class LoadingState extends MusicBeatState
var targetShit:Float = 0; var targetShit:Float = 0;
override function update(elapsed:Float) override function update(elapsed:Float):Void
{ {
super.update(elapsed); super.update(elapsed);
@ -147,57 +147,41 @@ class LoadingState extends MusicBeatState
} }
#if debug #if debug
if (FlxG.keys.justPressed.SPACE) trace('fired: ' + callbacks.getFired() + " unfired:" + callbacks.getUnfired()); if (FlxG.keys.justPressed.SPACE) trace('fired: ' + callbacks.getFired() + ' unfired:' + callbacks.getUnfired());
#end #end
} }
function onLoad() function onLoad():Void
{ {
if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop();
FlxG.switchState(target); FlxG.switchState(target);
} }
static function getSongPath() static function getSongPath():String
{ {
return Paths.inst(PlayState.currentSong.song); return Paths.inst(PlayState.instance.currentSong.songId);
} }
static function getVocalPath(?suffix:String) inline static public function loadAndSwitchState(nextState:FlxState, shouldStopMusic = false):Void
{ {
return Paths.voices(PlayState.currentSong.song, suffix); FlxG.switchState(getNextState(nextState, shouldStopMusic));
} }
inline static public function loadAndSwitchState(target:FlxState, stopMusic = false) static function getNextState(nextState:FlxState, shouldStopMusic = false):FlxState
{ {
FlxG.switchState(getNextState(target, stopMusic)); Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
}
static function getNextState(target:FlxState, stopMusic = false):FlxState
{
if (PlayState.storyWeek == 0)
{
Paths.setCurrentLevel('tutorial');
}
else if (PlayState.storyWeek == 8)
{
// TODO: Refactor this code.
Paths.setCurrentLevel("weekend1");
}
else
{
Paths.setCurrentLevel("week" + PlayState.storyWeek);
}
#if NO_PRELOAD_ALL #if NO_PRELOAD_ALL
var loaded = isSoundLoaded(getSongPath()) // var loaded = isSoundLoaded(getSongPath())
&& (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath())) // && (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath()))
&& isLibraryLoaded("shared"); // && isLibraryLoaded('shared');
//
if (!loaded) return new LoadingState(target, stopMusic); if (true) return new LoadingState(nextState, shouldStopMusic);
#end #end
if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); if (shouldStopMusic && FlxG.sound.music != null) FlxG.sound.music.stop();
return target; return nextState;
} }
#if NO_PRELOAD_ALL #if NO_PRELOAD_ALL
@ -212,16 +196,16 @@ class LoadingState extends MusicBeatState
} }
#end #end
override function destroy() override function destroy():Void
{ {
super.destroy(); super.destroy();
callbacks = null; callbacks = null;
} }
static function initSongsManifest() static function initSongsManifest():Future<AssetLibrary>
{ {
var id = "songs"; var id = 'songs';
var promise = new Promise<AssetLibrary>(); var promise = new Promise<AssetLibrary>();
var library = LimeAssets.getLibrary(id); var library = LimeAssets.getLibrary(id);
@ -243,10 +227,10 @@ class LoadingState extends MusicBeatState
} }
else else
{ {
if (path.endsWith(".bundle")) if (path.endsWith('.bundle'))
{ {
rootPath = path; rootPath = path;
path += "/library.json"; path += '/library.json';
} }
else else
{ {
@ -259,7 +243,7 @@ class LoadingState extends MusicBeatState
AssetManifest.loadFromFile(path, rootPath).onComplete(function(manifest) { AssetManifest.loadFromFile(path, rootPath).onComplete(function(manifest) {
if (manifest == null) if (manifest == null)
{ {
promise.error("Cannot parse asset manifest for library \"" + id + "\""); promise.error('Cannot parse asset manifest for library \'' + id + '\'');
return; return;
} }
@ -267,7 +251,7 @@ class LoadingState extends MusicBeatState
if (library == null) if (library == null)
{ {
promise.error("Cannot open library \"" + id + "\""); promise.error('Cannot open library \'' + id + '\'');
} }
else else
{ {
@ -277,7 +261,7 @@ class LoadingState extends MusicBeatState
promise.completeWith(Future.withValue(library)); promise.completeWith(Future.withValue(library));
} }
}).onError(function(_) { }).onError(function(_) {
promise.error("There is no asset library with an ID of \"" + id + "\""); promise.error('There is no asset library with an ID of \'' + id + '\'');
}); });
return promise.future; return promise.future;
@ -300,7 +284,7 @@ class MultiCallback
this.logId = logId; this.logId = logId;
} }
public function add(id = "untitled") public function add(id = 'untitled'):Void->Void
{ {
id = '$length:$id'; id = '$length:$id';
length++; length++;
@ -333,9 +317,9 @@ class MultiCallback
if (logId != null) trace('$logId: $msg'); if (logId != null) trace('$logId: $msg');
} }
public function getFired() public function getFired():Array<String>
return fired.copy(); return fired.copy();
public function getUnfired() public function getUnfired():Array<Void->Void>
return unfired.array(); return unfired.array();
} }

View file

@ -1,5 +1,6 @@
package funkin; package funkin;
import funkin.play.Strumline.StrumlineArrow;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import funkin.noteStuff.NoteBasic.NoteData; import funkin.noteStuff.NoteBasic.NoteData;
@ -215,6 +216,24 @@ class Note extends FlxSprite
} }
} }
public function alignToSturmlineArrow(arrow:StrumlineArrow):Void
{
x = arrow.x;
if (isSustainNote && prevNote != null)
{
if (prevNote.isSustainNote)
{
x = prevNote.x;
}
else
{
x += prevNote.width / 2;
x -= width / 2;
}
}
}
override function destroy() override function destroy()
{ {
prevNote = null; prevNote = null;

View file

@ -1,9 +1,10 @@
package funkin; package funkin;
import funkin.play.PlayStatePlaylist;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.sound.FlxSound; import flixel.system.FlxSound;
import flixel.text.FlxText; import flixel.text.FlxText;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
@ -20,9 +21,9 @@ class PauseSubState extends MusicBeatSubState
'Restart Song', 'Restart Song',
'Change Difficulty', 'Change Difficulty',
'Toggle Practice Mode', 'Toggle Practice Mode',
'Exit to menu' 'Exit to Menu'
]; ];
var difficultyChoices:Array<String> = ['EASY', 'NORMAL', 'HARD', 'BACK']; var difficultyChoices:Array<String> = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK'];
var menuItems:Array<String> = []; var menuItems:Array<String> = [];
var curSelected:Int = 0; var curSelected:Int = 0;
@ -41,10 +42,14 @@ class PauseSubState extends MusicBeatSubState
menuItems = pauseOG; menuItems = pauseOG;
if (PlayState.storyWeek == 6) // consistent with logic that decides asset lib!! if (PlayStatePlaylist.campaignId == 'week6')
{
pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast-pixel'), true, true); pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast-pixel'), true, true);
}
else else
{
pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast'), true, true); pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast'), true, true);
}
pauseMusic.volume = 0; pauseMusic.volume = 0;
pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2)));
@ -58,43 +63,38 @@ class PauseSubState extends MusicBeatSubState
metaDataGrp = new FlxTypedGroup<FlxSprite>(); metaDataGrp = new FlxTypedGroup<FlxSprite>();
add(metaDataGrp); add(metaDataGrp);
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32); var levelInfo:FlxText = new FlxText(20, 15, 0, '', 32);
if (PlayState.instance.currentChart != null) if (PlayState.instance.currentChart != null)
{ {
levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
} }
else
{
levelInfo.text += PlayState.currentSong.song;
}
levelInfo.scrollFactor.set(); levelInfo.scrollFactor.set();
levelInfo.setFormat(Paths.font("vcr.ttf"), 32); levelInfo.setFormat(Paths.font('vcr.ttf'), 32);
levelInfo.updateHitbox(); levelInfo.updateHitbox();
metaDataGrp.add(levelInfo); metaDataGrp.add(levelInfo);
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, "", 32); var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, '', 32);
levelDifficulty.text += CoolUtil.difficultyString(); levelDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
levelDifficulty.scrollFactor.set(); levelDifficulty.scrollFactor.set();
levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32); levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32);
levelDifficulty.updateHitbox(); levelDifficulty.updateHitbox();
metaDataGrp.add(levelDifficulty); metaDataGrp.add(levelDifficulty);
var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, "", 32); var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, '', 32);
deathCounter.text = "Blue balled: " + PlayState.deathCounter; deathCounter.text = 'Blue balled: ${PlayState.instance.deathCounter}';
deathCounter.text += "\n" + Highscore.tallies.totalNotesHit; FlxG.watch.addQuick('totalNotesHit', Highscore.tallies.totalNotesHit);
deathCounter.text += "\n" + Highscore.tallies.totalNotes; FlxG.watch.addQuick('totalNotes', Highscore.tallies.totalNotes);
deathCounter.text += "\n" + Std.string(Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes);
deathCounter.scrollFactor.set(); deathCounter.scrollFactor.set();
deathCounter.setFormat(Paths.font('vcr.ttf'), 32); deathCounter.setFormat(Paths.font('vcr.ttf'), 32);
deathCounter.updateHitbox(); deathCounter.updateHitbox();
metaDataGrp.add(deathCounter); metaDataGrp.add(deathCounter);
practiceText = new FlxText(20, 15 + 64 + 32, 0, "PRACTICE MODE", 32); practiceText = new FlxText(20, 15 + 64 + 32, 0, 'PRACTICE MODE', 32);
practiceText.scrollFactor.set(); practiceText.scrollFactor.set();
practiceText.setFormat(Paths.font('vcr.ttf'), 32); practiceText.setFormat(Paths.font('vcr.ttf'), 32);
practiceText.updateHitbox(); practiceText.updateHitbox();
practiceText.x = FlxG.width - (practiceText.width + 20); practiceText.x = FlxG.width - (practiceText.width + 20);
practiceText.visible = PlayState.isPracticeMode; practiceText.visible = PlayState.instance.isPracticeMode;
metaDataGrp.add(practiceText); metaDataGrp.add(practiceText);
levelDifficulty.alpha = 0; levelDifficulty.alpha = 0;
@ -137,7 +137,7 @@ class PauseSubState extends MusicBeatSubState
changeSelection(); changeSelection();
} }
override function update(elapsed:Float) override function update(elapsed:Float):Void
{ {
if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed; if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed;
@ -180,41 +180,39 @@ class PauseSubState extends MusicBeatSubState
{ {
var daSelected:String = menuItems[curSelected]; var daSelected:String = menuItems[curSelected];
// TODO: Why is this based on the menu item's name? Make this an enum or something.
switch (daSelected) switch (daSelected)
{ {
case "Resume": case 'Resume':
close(); close();
case "EASY" | 'NORMAL' | "HARD":
PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase());
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.currentSong.song.toLowerCase());
SongLoad.curDiff = daSelected.toLowerCase();
PlayState.storyDifficulty = curSelected;
PlayState.storyDifficulty_NEW = daSelected.toLowerCase();
PlayState.needsReset = true;
close();
case 'Toggle Practice Mode':
PlayState.isPracticeMode = !PlayState.isPracticeMode;
practiceText.visible = PlayState.isPracticeMode;
case 'Change Difficulty': case 'Change Difficulty':
menuItems = difficultyChoices; menuItems = difficultyChoices;
regenMenu(); regenMenu();
case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT':
PlayState.instance.currentSong = SongDataParser.fetchSong(PlayState.instance.currentSong.songId.toLowerCase());
PlayState.instance.currentDifficulty = daSelected.toLowerCase();
PlayState.instance.needsReset = true;
close();
case 'BACK': case 'BACK':
menuItems = pauseOG; menuItems = pauseOG;
regenMenu(); regenMenu();
case "Restart Song":
PlayState.needsReset = true;
case 'Toggle Practice Mode':
PlayState.instance.isPracticeMode = true;
practiceText.visible = PlayState.instance.isPracticeMode;
case 'Restart Song':
PlayState.instance.needsReset = true;
close(); close();
// FlxG.resetState();
case "Exit to menu": case 'Exit to Menu':
exitingToMenu = true; exitingToMenu = true;
PlayState.seenCutscene = false; PlayState.instance.deathCounter = 0;
PlayState.deathCounter = 0;
for (item in grpMenuShit.members) for (item in grpMenuShit.members)
{ {
@ -225,9 +223,9 @@ class PauseSubState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true; FlxTransitionableState.skipNextTransOut = true;
if (PlayState.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY)); if (PlayStatePlaylist.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY));
else else
openSubState(new funkin.ui.StickerSubState()); openSubState(new funkin.ui.StickerSubState(null, FREEPLAY));
} }
} }
@ -239,7 +237,7 @@ class PauseSubState extends MusicBeatSubState
} }
} }
override function destroy() override function destroy():Void
{ {
pauseMusic.destroy(); pauseMusic.destroy();
@ -260,12 +258,10 @@ class PauseSubState extends MusicBeatSubState
item.targetY = index - curSelected; item.targetY = index - curSelected;
item.alpha = 0.6; item.alpha = 0.6;
// item.setGraphicSize(Std.int(item.width * 0.8));
if (item.targetY == 0) if (item.targetY == 0)
{ {
item.alpha = 1; item.alpha = 1;
// item.setGraphicSize(Std.int(item.width));
} }
} }
} }

View file

@ -1,497 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.addons.transition.FlxTransitionableState;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxGroup;
import flixel.math.FlxMath;
import flixel.text.FlxText;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.MenuItem.WeekType;
import funkin.play.PlayState;
import funkin.play.song.SongData.SongDataParser;
import lime.net.curl.CURLCode;
import openfl.Assets;
import funkin.ui.StickerSubState;
#if discord_rpc
import Discord.DiscordClient;
#end
class StoryMenuState extends MusicBeatState
{
var scoreText:FlxText;
var weekData:Array<Array<String>> = [
['Tutorial'],
['Bopeebo', 'Fresh', 'Dadbattle'],
['Spookeez', 'South', "Monster"],
['Pico', 'Philly', "Blammed"],
['Satin-Panties', "High", "Milf"],
['Cocoa', 'Eggnog', 'Winter-Horrorland'],
['Senpai', 'Roses', 'Thorns'],
['Ugh', 'Guns', 'Stress'],
['Darnell', "lit-up", "2hot", "blazin"]
];
var curDifficulty:Int = 1;
// TODO: This info is just hardcoded right now.
// We should probably make it so that weeks must be completed in order to unlock the next week.
public static var weekUnlocked:Array<Bool> = [true, true, true, true, true, true, true, true, true];
var weekCharacters:Array<Dynamic> = [
['dad', 'bf', 'gf'],
['dad', 'bf', 'gf'],
['spooky', 'bf', 'gf'],
['pico', 'bf', 'gf'],
['mom', 'bf', 'gf'],
['parents-christmas', 'bf', 'gf'],
['senpai', 'bf', 'gf'],
['tankman', 'bf', 'gf'],
['darnell', 'pico', 'nene']
];
var weekNames:Array<String> = [
"",
"Daddy Dearest",
"Spooky Month",
"PICO",
"MOMMY MUST MURDER",
"RED SNOW",
"hating simulator ft. moawling",
"TANKMAN",
"Due Debts"
];
var weekType:Array<WeekType> = [WEEK, WEEK, WEEK, WEEK, WEEK, WEEK, WEEK, WEEK, WEEKEND];
var weekTypeInc:Map<WeekType, Int> = new Map();
var txtWeekTitle:FlxText;
var curWeek:Int = 0;
var txtTracklist:FlxText;
var grpWeekText:FlxTypedGroup<MenuItem>;
var grpWeekCharacters:Array<FlxTypedGroup<MenuCharacter>>;
// var grpWeekCharacters:FlxTypedGroup<MenuCharacter>;
var grpLocks:FlxTypedGroup<FlxSprite>;
var difficultySelectors:FlxGroup;
var sprDifficulty:FlxSprite;
var leftArrow:FlxSprite;
var rightArrow:FlxSprite;
var yellowBG:FlxSprite; // not actually, yellow, lol!
var targetColor:Int = 0xFFF9CF51;
var stickerSubState:StickerSubState;
public function new(?stickers:StickerSubState = null)
{
if (stickers != null)
{
stickerSubState = stickers;
}
super();
}
override function create()
{
transIn = FlxTransitionableState.defaultTransIn;
transOut = FlxTransitionableState.defaultTransOut;
if (FlxG.sound.music != null)
{
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu'));
}
if (stickerSubState != null)
{
this.persistentUpdate = true;
this.persistentDraw = true;
openSubState(stickerSubState);
stickerSubState.degenStickers();
// resetSubState();
}
persistentUpdate = persistentDraw = true;
scoreText = new FlxText(10, 10, 0, "SCORE: 49324858");
scoreText.setFormat("VCR OSD Mono", 32);
txtWeekTitle = new FlxText(FlxG.width * 0.7, 10, 0, "");
txtWeekTitle.setFormat("VCR OSD Mono", 32, FlxColor.WHITE, RIGHT);
txtWeekTitle.alpha = 0.7;
var rankText:FlxText = new FlxText(0, 10);
rankText.text = 'RANK: GREAT';
rankText.setFormat(Paths.font("vcr.ttf"), 32);
rankText.size = scoreText.size;
rankText.screenCenter(X);
var ui_tex = Paths.getSparrowAtlas('campaign_menu_UI_assets');
yellowBG = new FlxSprite(0, 56).makeGraphic(FlxG.width, 400, FlxColor.WHITE);
yellowBG.color = 0xFFF9CF51;
// 0xFF413CAE blue
// 0xFFF9CF51 yello
grpWeekText = new FlxTypedGroup<MenuItem>();
add(grpWeekText);
var blackBarThingie:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 56, FlxColor.BLACK);
add(blackBarThingie);
// grpWeekCharacters = new FlxTypedGroup<MenuCharacter>();
grpWeekCharacters = [];
grpLocks = new FlxTypedGroup<FlxSprite>();
add(grpLocks);
#if discord_rpc
// Updating Discord Rich Presence
DiscordClient.changePresence("In the Menus", null);
#end
for (i in 0...weekData.length)
{
if (!weekTypeInc.exists(weekType[i])) weekTypeInc[weekType[i]] = 1;
if (i == 0 && weekType[i] == WEEK) weekTypeInc[weekType[i]] = 0; // set week to 0 by default?
var weekThing:MenuItem = new MenuItem(0, yellowBG.y + yellowBG.height + 10, weekTypeInc[weekType[i]], weekType[i]);
weekThing.y += ((weekThing.height + 20) * i);
weekThing.targetY = i;
grpWeekText.add(weekThing);
weekTypeInc[weekType[i]] += 1;
weekThing.screenCenter(X);
weekThing.antialiasing = true;
// weekThing.updateHitbox();
// Needs an offset thingie
if (!weekUnlocked[i])
{
var lock:FlxSprite = new FlxSprite(weekThing.width + 10 + weekThing.x);
lock.frames = ui_tex;
lock.animation.addByPrefix('lock', 'lock');
lock.animation.play('lock');
lock.ID = i;
lock.antialiasing = true;
grpLocks.add(lock);
}
}
var sizeChart:Map<String, Array<Float>> = new Map();
var sizeTxt:Array<String> = Assets.getText(Paths.file("data/storychardata.txt")).split("\n");
for (item in sizeTxt)
{
var items:Array<String> = item.split(" ");
var stuf:Array<Float> = [];
var name:String = items.shift();
for (num in items)
stuf.push(Std.parseFloat(num));
sizeChart.set(name, stuf);
}
for (index => week in weekCharacters)
{
grpWeekCharacters.push(new FlxTypedGroup<MenuCharacter>());
for (char in 0...week.length)
{
var weekCharacterThing:MenuCharacter = new MenuCharacter((FlxG.width * 0.25) * (1 + char) - 150, weekCharacters[index][char]);
weekCharacterThing.y += 70;
weekCharacterThing.antialiasing = true;
var size:Float = 0.9;
switch (char)
{
case 0 | 2:
size = 0.5;
if (char == 0 && weekCharacterThing.character == "pico") weekCharacterThing.flipX = true;
case 1:
size = 0.9;
weekCharacterThing.x -= 80;
}
if (sizeChart.exists(weekCharacterThing.character))
{
var nums:Array<Float> = sizeChart[weekCharacterThing.character];
size = nums[char];
// IDK, this might be busted ass null shit?
if (char != 1)
{
weekCharacterThing.x += nums[3];
weekCharacterThing.y += nums[4];
}
}
weekCharacterThing.setGraphicSize(Std.int(weekCharacterThing.width * size));
weekCharacterThing.updateHitbox();
grpWeekCharacters[index].add(weekCharacterThing);
trace("ADD CHARACTER");
}
trace(grpWeekCharacters[index].toString());
}
difficultySelectors = new FlxGroup();
add(difficultySelectors);
leftArrow = new FlxSprite(grpWeekText.members[0].x + grpWeekText.members[0].width + 10, grpWeekText.members[0].y + 10);
leftArrow.frames = ui_tex;
leftArrow.animation.addByPrefix('idle', "arrow left");
leftArrow.animation.addByPrefix('press', "arrow push left");
leftArrow.animation.play('idle');
difficultySelectors.add(leftArrow);
sprDifficulty = new FlxSprite(leftArrow.x + 130, leftArrow.y);
sprDifficulty.frames = ui_tex;
sprDifficulty.animation.addByPrefix('easy', 'EASY');
sprDifficulty.animation.addByPrefix('normal', 'NORMAL');
sprDifficulty.animation.addByPrefix('hard', 'HARD');
sprDifficulty.animation.play('easy');
changeDifficulty();
difficultySelectors.add(sprDifficulty);
rightArrow = new FlxSprite(sprDifficulty.x + sprDifficulty.width + 50, leftArrow.y);
rightArrow.frames = ui_tex;
rightArrow.animation.addByPrefix('idle', 'arrow right');
rightArrow.animation.addByPrefix('press', "arrow push right", 24, false);
rightArrow.animation.play('idle');
difficultySelectors.add(rightArrow);
add(yellowBG);
for (grp in grpWeekCharacters)
{
add(grp);
// trace("ADDED GRP");
}
// add(grpWeekCharacters);
txtTracklist = new FlxText(FlxG.width * 0.05, yellowBG.x + yellowBG.height + 100, 0, "Tracks", 32);
txtTracklist.alignment = CENTER;
txtTracklist.font = rankText.font;
txtTracklist.color = 0xFFe55777;
add(txtTracklist);
// add(rankText);
add(scoreText);
add(txtWeekTitle);
updateText();
super.create();
}
override function update(elapsed:Float)
{
// scoreText.setFormat('VCR OSD Mono', 32);
yellowBG.color = FlxColor.interpolate(yellowBG.color, targetColor, 0.06);
lerpScore = CoolUtil.coolLerp(lerpScore, intendedScore, 0.5);
scoreText.text = "WEEK SCORE:" + Math.round(lerpScore);
txtWeekTitle.text = weekNames[curWeek].toUpperCase();
txtWeekTitle.x = FlxG.width - (txtWeekTitle.width + 10);
// FlxG.watch.addQuick('font', scoreText.font);
difficultySelectors.visible = weekUnlocked[curWeek];
grpLocks.forEach(function(lock:FlxSprite) {
lock.y = grpWeekText.members[lock.ID].y;
});
if (!movedBack)
{
if (!selectedWeek)
{
if (controls.UI_UP_P)
{
changeWeek(-1);
}
if (controls.UI_DOWN_P)
{
changeWeek(1);
}
if (controls.UI_RIGHT) rightArrow.animation.play('press')
else
rightArrow.animation.play('idle');
if (controls.UI_LEFT) leftArrow.animation.play('press');
else
leftArrow.animation.play('idle');
if (controls.UI_RIGHT_P) changeDifficulty(1);
if (controls.UI_LEFT_P) changeDifficulty(-1);
}
if (controls.ACCEPT)
{
selectWeek();
}
}
if (controls.BACK && !movedBack && !selectedWeek)
{
FlxG.sound.play(Paths.sound('cancelMenu'));
movedBack = true;
FlxG.switchState(new MainMenuState());
}
super.update(elapsed);
}
var movedBack:Bool = false;
var selectedWeek:Bool = false;
var stopspamming:Bool = false;
function selectWeek()
{
if (weekUnlocked[curWeek])
{
if (stopspamming == false)
{
FlxG.sound.play(Paths.sound('confirmMenu'));
grpWeekText.members[curWeek].startFlashing();
grpWeekCharacters[curWeek].members[1].animation.play('bfConfirm');
stopspamming = true;
}
PlayState.storyPlaylist = weekData[curWeek];
PlayState.isStoryMode = true;
selectedWeek = true;
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase());
PlayState.storyWeek = curWeek;
PlayState.campaignScore = 0;
PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = switch (curDifficulty)
{
case 0:
'easy';
case 1:
'normal';
case 2:
'hard';
default:
'normal';
};
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
new FlxTimer().start(1, function(tmr:FlxTimer) {
LoadingState.loadAndSwitchState(new PlayState(), true);
});
}
}
function changeDifficulty(change:Int = 0):Void
{
curDifficulty += change;
if (curDifficulty < 0) curDifficulty = 2;
if (curDifficulty > 2) curDifficulty = 0;
sprDifficulty.offset.x = 0;
switch (curDifficulty)
{
case 0:
sprDifficulty.animation.play('easy');
sprDifficulty.offset.x = 20;
case 1:
sprDifficulty.animation.play('normal');
sprDifficulty.offset.x = 70;
case 2:
sprDifficulty.animation.play('hard');
sprDifficulty.offset.x = 20;
}
sprDifficulty.alpha = 0;
// USING THESE WEIRD VALUES SO THAT IT DOESNT FLOAT UP
sprDifficulty.y = leftArrow.y - 15;
intendedScore = Highscore.getWeekScore(curWeek, curDifficulty);
FlxTween.tween(sprDifficulty, {y: leftArrow.y + 15, alpha: 1}, 0.07);
}
var lerpScore:Float = 0;
var intendedScore:Int = 0;
function changeWeek(change:Int = 0):Void
{
curWeek += change;
if (curWeek >= weekData.length) curWeek = 0;
if (curWeek < 0) curWeek = weekData.length - 1;
var bullShit:Int = 0;
for (item in grpWeekText.members)
{
item.targetY = bullShit - curWeek;
if (item.targetY == Std.int(0) && weekUnlocked[curWeek]) item.alpha = 1;
else
item.alpha = 0.6;
bullShit++;
}
FlxG.sound.play(Paths.sound('scrollMenu'));
updateText();
}
function updateText()
{
switch (weekType[curWeek])
{
case WEEK:
targetColor = 0xFFF9CF51;
case WEEKEND:
targetColor = 0xFF413CAE;
}
for (ind => grp in grpWeekCharacters)
grp.visible = ind == curWeek;
txtTracklist.text = "Tracks\n";
var trackNames:Array<String> = weekData[curWeek];
for (i in trackNames)
{
txtTracklist.text += '\n${i}';
}
txtTracklist.text = txtTracklist.text.toUpperCase();
txtTracklist.screenCenter(X);
txtTracklist.x -= FlxG.width * 0.35;
intendedScore = Highscore.getWeekScore(curWeek, curDifficulty);
}
}

View file

@ -283,44 +283,6 @@ class TitleState extends MusicBeatState
FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG}); FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG});
} }
/*
FlxG.watch.addQuick('cur display', FlxG.stage.window.display.id);
if (FlxG.keys.justPressed.Y)
{
// trace(FlxG.stage.window.display.name);
if (FlxG.gamepads.firstActive != null)
{
trace(FlxG.gamepads.firstActive.model);
FlxG.gamepads.firstActive.id
}
else
trace('gamepad null');
// FlxG.stage.window.title = Std.string(FlxG.random.int(0, 20000));
// FlxG.stage.window.setIcon(Image.fromFile('assets/images/icon16.png'));
// FlxG.stage.window.readPixels;
if (FlxG.stage.window.width == Std.int(FlxG.stage.window.display.bounds.width))
{
FlxG.stage.window.width = 1280;
FlxG.stage.window.height = 720;
FlxG.stage.window.y = 30;
}
else
{
FlxG.stage.window.width = Std.int(FlxG.stage.window.display.bounds.width);
FlxG.stage.window.height = Std.int(FlxG.stage.window.display.bounds.height);
FlxG.stage.window.x = Std.int(FlxG.stage.window.display.bounds.x);
FlxG.stage.window.y = Std.int(FlxG.stage.window.display.bounds.y);
}
}
*/
#if debug
if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new CutsceneAnimTestState());
#end
if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time; if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time;
if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen; if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen;

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ class ScriptEvent
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final CREATE:ScriptEventType = "CREATE"; public static inline final CREATE:ScriptEventType = 'CREATE';
/** /**
* Called when the relevant object is destroyed. * Called when the relevant object is destroyed.
@ -30,7 +30,7 @@ class ScriptEvent
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final DESTROY:ScriptEventType = "DESTROY"; public static inline final DESTROY:ScriptEventType = 'DESTROY';
/** /**
* Called when the relevent object is added to the game state. * Called when the relevent object is added to the game state.
@ -46,35 +46,35 @@ class ScriptEvent
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final UPDATE:ScriptEventType = "UPDATE"; public static inline final UPDATE:ScriptEventType = 'UPDATE';
/** /**
* Called when the player moves to pause the game. * Called when the player moves to pause the game.
* *
* This event IS cancelable! Canceling the event will prevent the game from pausing. * This event IS cancelable! Canceling the event will prevent the game from pausing.
*/ */
public static inline final PAUSE:ScriptEventType = "PAUSE"; public static inline final PAUSE:ScriptEventType = 'PAUSE';
/** /**
* Called when the player moves to unpause the game while paused. * Called when the player moves to unpause the game while paused.
* *
* This event IS cancelable! Canceling the event will prevent the game from resuming. * This event IS cancelable! Canceling the event will prevent the game from resuming.
*/ */
public static inline final RESUME:ScriptEventType = "RESUME"; public static inline final RESUME:ScriptEventType = 'RESUME';
/** /**
* Called once per step in the song. This happens 4 times per measure. * Called once per step in the song. This happens 4 times per measure.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SONG_BEAT_HIT:ScriptEventType = "BEAT_HIT"; public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT';
/** /**
* Called once per step in the song. This happens 16 times per measure. * Called once per step in the song. This happens 16 times per measure.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SONG_STEP_HIT:ScriptEventType = "STEP_HIT"; public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT';
/** /**
* Called when a character hits a note. * Called when a character hits a note.
@ -83,7 +83,7 @@ class ScriptEvent
* This event IS cancelable! Canceling this event prevents the note from being hit, * This event IS cancelable! Canceling this event prevents the note from being hit,
* and will likely result in a miss later. * and will likely result in a miss later.
*/ */
public static inline final NOTE_HIT:ScriptEventType = "NOTE_HIT"; public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT';
/** /**
* Called when a character misses a note. * Called when a character misses a note.
@ -92,7 +92,7 @@ class ScriptEvent
* This event IS cancelable! Canceling this event prevents the note from being considered missed, * This event IS cancelable! Canceling this event prevents the note from being considered missed,
* avoiding a combo break and lost health. * avoiding a combo break and lost health.
*/ */
public static inline final NOTE_MISS:ScriptEventType = "NOTE_MISS"; public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS';
/** /**
* Called when a character presses a note when there was none there, causing them to lose health. * Called when a character presses a note when there was none there, causing them to lose health.
@ -101,7 +101,7 @@ class ScriptEvent
* This event IS cancelable! Canceling this event prevents the note from being considered missed, * This event IS cancelable! Canceling this event prevents the note from being considered missed,
* avoiding lost health/score and preventing the miss animation. * avoiding lost health/score and preventing the miss animation.
*/ */
public static inline final NOTE_GHOST_MISS:ScriptEventType = "NOTE_GHOST_MISS"; public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS';
/** /**
* Called when a song event is reached in the chart. * Called when a song event is reached in the chart.
@ -109,21 +109,21 @@ class ScriptEvent
* This event IS cancelable! Cancelling this event prevents the event from being triggered, * This event IS cancelable! Cancelling this event prevents the event from being triggered,
* thus blocking its normal functionality. * thus blocking its normal functionality.
*/ */
public static inline final SONG_EVENT:ScriptEventType = "SONG_EVENT"; public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT';
/** /**
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin. * Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SONG_START:ScriptEventType = "SONG_START"; public static inline final SONG_START:ScriptEventType = 'SONG_START';
/** /**
* Called when the song ends. This happens as the instrumental and vocals end. * Called when the song ends. This happens as the instrumental and vocals end.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SONG_END:ScriptEventType = "SONG_END"; public static inline final SONG_END:ScriptEventType = 'SONG_END';
/** /**
* Called when the countdown begins. This occurs before the song starts. * Called when the countdown begins. This occurs before the song starts.
@ -132,7 +132,7 @@ class ScriptEvent
* - The song will not start until you call Countdown.performCountdown() later. * - The song will not start until you call Countdown.performCountdown() later.
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it. * - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
*/ */
public static inline final COUNTDOWN_START:ScriptEventType = "COUNTDOWN_START"; public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START';
/** /**
* Called when a step of the countdown happens. * Called when a step of the countdown happens.
@ -141,21 +141,21 @@ class ScriptEvent
* This event IS cancelable! Canceling this event will pause the countdown. * This event IS cancelable! Canceling this event will pause the countdown.
* - The countdown will not resume until you call PlayState.resumeCountdown(). * - The countdown will not resume until you call PlayState.resumeCountdown().
*/ */
public static inline final COUNTDOWN_STEP:ScriptEventType = "COUNTDOWN_STEP"; public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP';
/** /**
* Called when the countdown is done but just before the song starts. * Called when the countdown is done but just before the song starts.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final COUNTDOWN_END:ScriptEventType = "COUNTDOWN_END"; public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END';
/** /**
* Called before the game over screen triggers and the death animation plays. * Called before the game over screen triggers and the death animation plays.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER"; public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER';
/** /**
* Called after the player presses a key to restart the game. * Called after the player presses a key to restart the game.
@ -163,21 +163,21 @@ class ScriptEvent
* *
* This event IS cancelable! Canceling this event will prevent the game from restarting. * This event IS cancelable! Canceling this event will prevent the game from restarting.
*/ */
public static inline final SONG_RETRY:ScriptEventType = "SONG_RETRY"; public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY';
/** /**
* Called when the player pushes down any key on the keyboard. * Called when the player pushes down any key on the keyboard.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final KEY_DOWN:ScriptEventType = "KEY_DOWN"; public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN';
/** /**
* Called when the player releases a key on the keyboard. * Called when the player releases a key on the keyboard.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final KEY_UP:ScriptEventType = "KEY_UP"; public static inline final KEY_UP:ScriptEventType = 'KEY_UP';
/** /**
* Called when the game has finished loading the notes from JSON. * Called when the game has finished loading the notes from JSON.
@ -185,49 +185,49 @@ class ScriptEvent
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SONG_LOADED:ScriptEventType = "SONG_LOADED"; public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED';
/** /**
* Called when the game is about to switch the current FlxState. * Called when the game is about to switch the current FlxState.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final STATE_CHANGE_BEGIN:ScriptEventType = "STATE_CHANGE_BEGIN"; public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN';
/** /**
* Called when the game has finished switching the current FlxState. * Called when the game has finished switching the current FlxState.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final STATE_CHANGE_END:ScriptEventType = "STATE_CHANGE_END"; public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END';
/** /**
* Called when the game is about to open a new FlxSubState. * Called when the game is about to open a new FlxSubState.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = "SUBSTATE_OPEN_BEGIN"; public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN';
/** /**
* Called when the game has finished opening a new FlxSubState. * Called when the game has finished opening a new FlxSubState.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SUBSTATE_OPEN_END:ScriptEventType = "SUBSTATE_OPEN_END"; public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END';
/** /**
* Called when the game is about to close the current FlxSubState. * Called when the game is about to close the current FlxSubState.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = "SUBSTATE_CLOSE_BEGIN"; public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN';
/** /**
* Called when the game has finished closing the current FlxSubState. * Called when the game has finished closing the current FlxSubState.
* *
* This event is not cancelable. * This event is not cancelable.
*/ */
public static inline final SUBSTATE_CLOSE_END:ScriptEventType = "SUBSTATE_CLOSE_END"; public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END';
/** /**
* Called when the game is exiting the current FlxState. * Called when the game is exiting the current FlxState.
@ -276,9 +276,12 @@ class ScriptEvent
} }
} }
/**
* Cancel this event.
* This is an alias for cancelEvent() but I make this typo all the time.
*/
public function cancel():Void public function cancel():Void
{ {
// This typo happens enough that I just added this.
cancelEvent(); cancelEvent();
} }
@ -316,11 +319,17 @@ class NoteScriptEvent extends ScriptEvent
*/ */
public var comboCount(default, null):Int; public var comboCount(default, null):Int;
/**
* Whether to play the record scratch sound (if this eventn type is `NOTE_MISS`).
*/
public var playSound(default, default):Bool;
public function new(type:ScriptEventType, note:Note, comboCount:Int = 0, cancelable:Bool = false):Void public function new(type:ScriptEventType, note:Note, comboCount:Int = 0, cancelable:Bool = false):Void
{ {
super(type, cancelable); super(type, cancelable);
this.note = note; this.note = note;
this.comboCount = comboCount; this.comboCount = comboCount;
this.playSound = true;
} }
public override function toString():String public override function toString():String
@ -468,7 +477,7 @@ class CountdownScriptEvent extends ScriptEvent
*/ */
public var step(default, null):CountdownStep; public var step(default, null):CountdownStep;
public function new(type:ScriptEventType, step:CountdownStep, cancelable = true):Void public function new(type:ScriptEventType, step:CountdownStep, cancelable:Bool = true):Void
{ {
super(type, cancelable); super(type, cancelable);
this.step = step; this.step = step;

View file

@ -37,7 +37,7 @@ class Countdown
// Stop any existing countdown. // Stop any existing countdown.
stopCountdown(); stopCountdown();
PlayState.isInCountdown = true; PlayState.instance.isInCountdown = true;
Conductor.songPosition = Conductor.crochet * -5; Conductor.songPosition = Conductor.crochet * -5;
// Handle onBeatHit events manually // Handle onBeatHit events manually
@:privateAccess @:privateAccess

View file

@ -153,11 +153,9 @@ class GameOverSubState extends MusicBeatSubState
// KEYBOARD ONLY: Return to the menu when pressing the assigned key. // KEYBOARD ONLY: Return to the menu when pressing the assigned key.
if (controls.BACK) if (controls.BACK)
{ {
PlayState.deathCounter = 0;
PlayState.seenCutscene = false;
gameOverMusic.stop(); gameOverMusic.stop();
if (PlayState.isStoryMode) FlxG.switchState(new StoryMenuState()); if (PlayStatePlaylist.isStoryMode) FlxG.switchState(new StoryMenuState());
else else
FlxG.switchState(new FreeplayState()); FlxG.switchState(new FreeplayState());
} }
@ -171,11 +169,11 @@ class GameOverSubState extends MusicBeatSubState
else else
{ {
// Music hasn't started yet. // Music hasn't started yet.
switch (PlayState.storyWeek) switch (PlayStatePlaylist.campaignId)
{ {
// TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
// This will simplify the class and make it easier for mods to add death quotes. // This will simplify the class and make it easier for mods to add death quotes.
case 7: case "week7":
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
{ {
playingJeffQuote = true; playingJeffQuote = true;
@ -214,7 +212,7 @@ class GameOverSubState extends MusicBeatSubState
FlxG.camera.fade(FlxColor.BLACK, 2, false, function() { FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
// ...close the GameOverSubState. // ...close the GameOverSubState.
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
PlayState.needsReset = true; PlayState.instance.needsReset = true;
// Readd Boyfriend to the stage. // Readd Boyfriend to the stage.
boyfriend.isDead = false; boyfriend.isDead = false;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
package funkin.play;
import funkin.util.Constants;
/**
* Manages playback of multiple songs in a row.
*
* TODO: Add getters/setters for all these properties to validate them.
*/
class PlayStatePlaylist
{
/**
* Whether the game is currently in Story Mode. If false, we are in Free Play Mode.
*/
public static var isStoryMode(default, default):Bool = false;
/**
* The loist of upcoming songs to be played.
* When the user completes a song in Story Mode, the first entry in this list is played.
* When this list is empty, move to the Results screen instead.
*/
public static var playlistSongIds:Array<String> = [];
/**
* The cumulative score for all the songs in the playlist.
*/
public static var campaignScore:Int = 0;
/**
* The title of this playlist, for example `Week 4` or `Weekend 1`
*/
public static var campaignTitle:String = 'UNKNOWN';
/**
* The internal ID of the current playlist, for example `week4` or `weekend-1`.
*/
public static var campaignId:String = 'unknown';
/**
* The current difficulty selected for this level (as a named ID).
*/
public static var currentDifficulty(default, default):String = Constants.DEFAULT_DIFFICULTY;
/**
* Resets the playlist to its default state.
*/
public static function reset():Void
{
isStoryMode = false;
playlistSongIds = [];
campaignScore = 0;
campaignTitle = 'UNKNOWN';
campaignId = 'unknown';
currentDifficulty = Constants.DEFAULT_DIFFICULTY;
}
}

View file

@ -118,16 +118,16 @@ class ResultState extends MusicBeatSubState
difficulty = new FlxSprite(555); difficulty = new FlxSprite(555);
var diffSpr:String = switch (CoolUtil.difficultyString()) var diffSpr:String = switch (PlayState.instance.currentDifficulty)
{ {
case "EASY": case 'EASY':
"difEasy"; 'difEasy';
case "NORMAL": case 'NORMAL':
"difNormal"; 'difNormal';
case "HARD": case 'HARD':
"difHard"; 'difHard';
case _: case _:
"difNormal"; 'difNormal';
} }
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr)); difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
@ -144,7 +144,7 @@ class ResultState extends MusicBeatSubState
} }
else else
{ {
songName.text += PlayState.currentSong.song; songName.text += PlayState.instance.currentSong.songId;
} }
songName.antialiasing = true; songName.antialiasing = true;

View file

@ -1,13 +1,10 @@
package funkin.play.cutscene; package funkin.play.cutscene;
// import hxcodec.flixel.FlxVideoSprite;
// import hxcodec.flixel.FlxCutsceneState;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.graphics.video.FlxVideo;
/** /**
* Static methods for playing cutscenes in the PlayState. * Static methods for playing cutscenes in the PlayState.
@ -15,112 +12,46 @@ import funkin.graphics.video.FlxVideo;
*/ */
class VanillaCutscenes class VanillaCutscenes
{ {
/**
* Well, well, well, what have we got here?
*/
public static function playUghCutscene():Void
{
playVideoCutscene('music/ughCutscene.mp4');
}
/**
* Nice bars for an ugly, boring teenager!
*/
public static function playGunsCutscene():Void
{
playVideoCutscene('music/gunsCutscene.mp4');
}
/**
* Don't you have a school to shoot up?
*/
public static function playStressCutscene():Void
{
playVideoCutscene('music/stressCutscene.mp4');
}
static var blackScreen:FlxSprite; static var blackScreen:FlxSprite;
/** static final TWEEN_DURATION:Float = 2.0;
* Plays a cutscene from a video file, then starts the countdown once the video is done.
* TODO: Cutscene is currently skipped on native platforms.
*/
static function playVideoCutscene(path:String):Void
{
// Tell PlayState to stop the song until the video is done.
PlayState.isInCutscene = true;
PlayState.instance.camHUD.visible = false;
// Display a black screen to hide the game while the video is playing.
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
blackScreen.scrollFactor.set(0, 0);
blackScreen.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(blackScreen);
#if html5
// Video displays OVER the FlxState.
vid = new FlxVideo(path);
vid.finishCallback = finishCutscene.bind(0.5);
#else
// Video displays OVER the FlxState.
// vid = new FlxVideoSprite(0, 0);
vid.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(vid);
vid.playVideo(Paths.file(path), false);
vid.onEndReached.add(finishCutscene.bind(0.5));
#end
}
static var vid:#if html5 FlxVideo #else Dynamic /**FlxVideoSprite **/ #end;
/** /**
* Does the cleanup to start the countdown after the video is done. * Plays the cutscene that appears at the start of Winter Horrorland.
* Gets called immediately if the video can't be played. * TODO: Move this to `winter-horrorland.hxc`
*/
public static function finishCutscene(?transitionTime:Float = 2.5):Void
{
trace('ALERT: Finish cutscene called!');
#if html5
#else
vid.stop();
PlayState.instance.remove(vid);
#end
PlayState.instance.camHUD.visible = true;
FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
PlayState.instance.remove(blackScreen);
blackScreen = null;
}
});
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, transitionTime,
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
PlayState.instance.startCountdown();
}
});
}
/**
* FNF corruption mod???
*/ */
public static function playHorrorStartCutscene():Void public static function playHorrorStartCutscene():Void
{ {
PlayState.isInCutscene = true; PlayState.instance.isInCutscene = true;
PlayState.instance.camHUD.visible = false; PlayState.instance.camHUD.visible = false;
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
blackScreen.scrollFactor.set(0, 0); blackScreen.scrollFactor.set(0, 0);
blackScreen.zIndex = 1000000;
PlayState.instance.add(blackScreen); PlayState.instance.add(blackScreen);
new FlxTimer().start(0.1, _ -> finishCutscene(2.5)); new FlxTimer().start(0.1, function(_) {
trace('Playing horrorland cutscene...');
PlayState.instance.remove(blackScreen);
// Force set the camera position and zoom.
PlayState.instance.cameraFollowPoint.setPosition(400, -2050);
PlayState.instance.resetCamera();
FlxG.camera.zoom = 2.5;
// Play the Sound effect.
FlxG.sound.play(Paths.sound('Lights_Turn_On'), function() {
// Fade in the HUD.
trace('SFX done...');
PlayState.instance.camHUD.visible = true;
PlayState.instance.camHUD.alpha = 0.0; // Use alpha rather than visible to let us fade it in.
FlxTween.tween(PlayState.instance.camHUD, {alpha: 1.0}, TWEEN_DURATION, {ease: FlxEase.quadInOut});
// Start the countdown.
trace('Zoom out done...');
trace('Begin countdown (ends cutscene)');
PlayState.instance.startCountdown();
});
});
} }
} }

View file

@ -0,0 +1,155 @@
package funkin.play.cutscene;
import funkin.play.PlayState;
import flixel.FlxSprite;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
#if html5
import funkin.graphics.video.FlxVideo;
#else
import hxcodec.flixel.FlxVideoSprite;
#end
/**
* Assumes you are in the PlayState.
*/
class VideoCutscene
{
static var blackScreen:FlxSprite;
/**
* Play a video cutscene.
* TODO: Currently this is hardcoded to start the countdown after the video is done.
* @param path The path to the video file. Use Paths.file(path) to get the correct path.
*/
public static function play(filePath:String):Void
{
if (PlayState.instance == null) return;
if (!openfl.Assets.exists(filePath))
{
trace('ERROR: Video file does not exist: ${filePath}');
return;
}
// Trigger the cutscene. Don't play the song in the background.
PlayState.instance.isInCutscene = true;
PlayState.instance.camHUD.visible = false;
PlayState.instance.camCutscene.visible = true;
// Display a black screen to hide the game while the video is playing.
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
blackScreen.scrollFactor.set(0, 0);
blackScreen.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(blackScreen);
#if html5
playVideoHTML5(filePath);
#else
playVideoNative(filePath);
#end
}
public static function isPlaying():Bool
{
return vid != null;
}
#if html5
static var vid:FlxVideo;
static function playVideoHTML5(filePath:String):Void
{
// Video displays OVER the FlxState.
vid = new FlxVideo(filePath);
if (vid != null)
{
vid.zIndex = 0;
vid.finishCallback = finishVideo.bind(0.5);
vid.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(vid);
PlayState.instance.refresh();
}
else
{
trace('ALERT: Video is null! Could not play cutscene!');
}
}
#else
static var vid:FlxVideoSprite;
static function playVideoNative(filePath:String):Void
{
// Video displays OVER the FlxState.
vid = new FlxVideoSprite(0, 0);
if (vid != null)
{
vid.zIndex = 0;
vid.bitmap.onEndReached.add(finishVideo.bind(0.5));
vid.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(vid);
PlayState.instance.refresh();
vid.play(filePath, false);
}
else
{
trace('ALERT: Video is null! Could not play cutscene!');
}
}
#end
public static function finishVideo(?transitionTime:Float = 0.5):Void
{
trace('ALERT: Finish video cutscene called!');
#if html5
if (vid != null)
{
PlayState.instance.remove(vid);
}
#else
if (vid != null)
{
vid.stop();
PlayState.instance.remove(vid);
}
#end
vid.destroy();
vid = null;
PlayState.instance.camCutscene.visible = true;
PlayState.instance.camHUD.visible = true;
FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
PlayState.instance.remove(blackScreen);
blackScreen = null;
}
});
FlxTween.tween(FlxG.camera, {zoom: PlayState.instance.defaultCameraZoom}, transitionTime,
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
PlayState.instance.startCountdown();
}
});
}
}
/*
trace('Video playback failed (${e})');
vid = null;
finishCutscene(0.5);
*/

View file

@ -1,7 +1,9 @@
package funkin.play.event; package funkin.play.event;
import funkin.play.event.SongEvent;
import funkin.play.song.SongData; import funkin.play.song.SongData;
import funkin.play.event.SongEvent;
import funkin.play.event.SongEventData.SongEventFieldType;
import funkin.play.event.SongEventData.SongEventSchema;
/** /**
* This class represents a handler for a type of song event. * This class represents a handler for a type of song event.

View file

@ -3,6 +3,8 @@ package funkin.play.event;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.play.character.BaseCharacter; import funkin.play.character.BaseCharacter;
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.play.event.SongEventData.SongEventFieldType;
import funkin.play.event.SongEventData.SongEventSchema;
import funkin.play.song.SongData; import funkin.play.song.SongData;
class PlayAnimationSongEvent extends SongEvent class PlayAnimationSongEvent extends SongEvent

View file

@ -0,0 +1,90 @@
package funkin.play.event;
import funkin.util.Constants;
import flixel.tweens.FlxTween;
import flixel.FlxCamera;
import flixel.tweens.FlxEase;
import funkin.play.event.SongEvent;
import funkin.play.song.SongData;
import funkin.play.event.SongEventData;
import funkin.play.event.SongEventData.SongEventFieldType;
/**
* This class represents a handler for configuring camera bop intensity and rate.
*
* Example: Bop the camera twice as hard, once per beat (rather than once every four beats).
* ```
* {
* 'e': 'SetCameraBop',
* 'v': {
* 'intensity': 2.0,
* 'rate': 1,
* }
* }
* ```
*
* Example: Reset the camera bop to default values.
* ```
* {
* 'e': 'SetCameraBop',
* 'v': {}
* }
* ```
*/
class SetCameraBopSongEvent extends SongEvent
{
public function new()
{
super('SetCameraBop');
}
public override function handleEvent(data:SongEventData):Void
{
// Does nothing if there is no PlayState camera or stage.
if (PlayState.instance == null) return;
var rate:Null<Int> = data.getInt('rate');
if (rate == null) rate = Constants.DEFAULT_ZOOM_RATE;
var intensity:Null<Float> = data.getFloat('intensity');
if (intensity == null) intensity = 1.0;
PlayState.instance.cameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * intensity;
PlayState.instance.hudCameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * intensity * 2.0;
PlayState.instance.cameraZoomRate = rate;
trace('Set camera zoom rate to ${PlayState.instance.cameraZoomRate}');
}
public override function getTitle():String
{
return 'Set Camera Bop';
}
/**
* ```
* {
* 'intensity': FLOAT, // Zoom amount
* 'rate': INT, // Zoom rate (beats/zoom)
* }
* ```
* @return SongEventSchema
*/
public override function getEventSchema():SongEventSchema
{
return [
{
name: 'intensity',
title: 'Intensity',
defaultValue: 1.0,
step: 0.1,
type: SongEventFieldType.FLOAT
},
{
name: 'rate',
title: 'Rate (beats/zoom)',
defaultValue: 4,
step: 1,
type: SongEventFieldType.INTEGER,
}
];
}
}

View file

@ -1,7 +1,7 @@
package funkin.play.event; package funkin.play.event;
import funkin.util.macro.ClassMacro;
import funkin.play.song.SongData.SongEventData; import funkin.play.song.SongData.SongEventData;
import funkin.play.event.SongEventData.SongEventSchema;
/** /**
* This class represents a handler for a type of song event. * This class represents a handler for a type of song event.
@ -52,232 +52,3 @@ class SongEvent
return 'SongEvent(${this.id})'; return 'SongEvent(${this.id})';
} }
} }
/**
* This class statically handles the parsing of internal and scripted song event handlers.
*/
class SongEventParser
{
/**
* Every built-in event class must be added to this list.
* Thankfully, with the power of `SongEventMacro`, this is done automatically.
*/
static final BUILTIN_EVENTS:List<Class<SongEvent>> = ClassMacro.listSubclassesOf(SongEvent);
/**
* Map of internal handlers for song events.
* These may be either `ScriptedSongEvents` or built-in classes extending `SongEvent`.
*/
static final eventCache:Map<String, SongEvent> = new Map<String, SongEvent>();
public static function loadEventCache():Void
{
clearEventCache();
//
// BASE GAME EVENTS
//
registerBaseEvents();
registerScriptedEvents();
}
static function registerBaseEvents()
{
trace('Instantiating ${BUILTIN_EVENTS.length} built-in song events...');
for (eventCls in BUILTIN_EVENTS)
{
var eventClsName:String = Type.getClassName(eventCls);
if (eventClsName == 'funkin.play.event.SongEvent' || eventClsName == 'funkin.play.event.ScriptedSongEvent') continue;
var event:SongEvent = Type.createInstance(eventCls, ["UNKNOWN"]);
if (event != null)
{
trace(' Loaded built-in song event: (${event.id})');
eventCache.set(event.id, event);
}
else
{
trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}');
}
}
}
static function registerScriptedEvents()
{
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
for (eventCls in scriptedEventClassNames)
{
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
if (event != null)
{
trace(' Loaded scripted song event: ${event.id}');
eventCache.set(event.id, event);
}
else
{
trace(' Failed to instantiate scripted song event class: ${eventCls}');
}
}
}
public static function listEventIds():Array<String>
{
return eventCache.keys().array();
}
public static function listEvents():Array<SongEvent>
{
return eventCache.values();
}
public static function getEvent(id:String):SongEvent
{
return eventCache.get(id);
}
public static function getEventSchema(id:String):SongEventSchema
{
var event:SongEvent = getEvent(id);
if (event == null) return null;
return event.getEventSchema();
}
static function clearEventCache()
{
eventCache.clear();
}
public static function handleEvent(data:SongEventData):Void
{
var eventType:String = data.event;
var eventHandler:SongEvent = eventCache.get(eventType);
if (eventHandler != null)
{
eventHandler.handleEvent(data);
}
else
{
trace('WARNING: No event handler for event with id: ${eventType}');
}
data.activated = true;
}
public static inline function handleEvents(events:Array<SongEventData>):Void
{
for (event in events)
{
handleEvent(event);
}
}
/**
* Given a list of song events and the current timestamp,
* return a list of events that should be handled.
*/
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool {
// If the event is already activated, don't activate it again.
if (event.activated) return false;
// If the event is in the future, don't activate it.
if (event.time > currentTime) return false;
return true;
});
}
/**
* Reset activation of all the provided events.
*/
public static function resetEvents(events:Array<SongEventData>):Void
{
for (event in events)
{
event.activated = false;
// TODO: Add an onReset() method to SongEvent?
}
}
}
enum abstract SongEventFieldType(String) from String to String
{
/**
* The STRING type will display as a text field.
*/
var STRING = "string";
/**
* The INTEGER type will display as a text field that only accepts numbers.
*/
var INTEGER = "integer";
/**
* The FLOAT type will display as a text field that only accepts numbers.
*/
var FLOAT = "float";
/**
* The BOOL type will display as a checkbox.
*/
var BOOL = "bool";
/**
* The ENUM type will display as a dropdown.
* Make sure to specify the `keys` field in the schema.
*/
var ENUM = "enum";
}
typedef SongEventSchemaField =
{
/**
* The name of the property as it should be saved in the event data.
*/
name:String,
/**
* The title of the field to display in the UI.
*/
title:String,
/**
* The type of the field.
*/
type:SongEventFieldType,
/**
* Used for ENUM values.
* The key is the display name and the value is the actual value.
*/
?keys:Map<String, Dynamic>,
/**
* Used for INTEGER and FLOAT values.
* The minimum value that can be entered.
*/
?min:Float,
/**
* Used for INTEGER and FLOAT values.
* The maximum value that can be entered.
*/
?max:Float,
/**
* Used for INTEGER and FLOAT values.
* The step value that will be used when incrementing/decrementing the value.
*/
?step:Float,
/**
* An optional default value for the field.
*/
?defaultValue:Dynamic,
}
typedef SongEventSchema = Array<SongEventSchemaField>;

View file

@ -0,0 +1,235 @@
package funkin.play.event;
import funkin.play.event.SongEventData.SongEventSchema;
import funkin.play.song.SongData.SongEventData;
import funkin.util.macro.ClassMacro;
import funkin.play.event.ScriptedSongEvent;
/**
* This class statically handles the parsing of internal and scripted song event handlers.
*/
class SongEventParser
{
/**
* Every built-in event class must be added to this list.
* Thankfully, with the power of `SongEventMacro`, this is done automatically.
*/
static final BUILTIN_EVENTS:List<Class<SongEvent>> = ClassMacro.listSubclassesOf(SongEvent);
/**
* Map of internal handlers for song events.
* These may be either `ScriptedSongEvents` or built-in classes extending `SongEvent`.
*/
static final eventCache:Map<String, SongEvent> = new Map<String, SongEvent>();
public static function loadEventCache():Void
{
clearEventCache();
//
// BASE GAME EVENTS
//
registerBaseEvents();
registerScriptedEvents();
}
static function registerBaseEvents()
{
trace('Instantiating ${BUILTIN_EVENTS.length} built-in song events...');
for (eventCls in BUILTIN_EVENTS)
{
var eventClsName:String = Type.getClassName(eventCls);
if (eventClsName == 'funkin.play.event.SongEvent' || eventClsName == 'funkin.play.event.ScriptedSongEvent') continue;
var event:SongEvent = Type.createInstance(eventCls, ["UNKNOWN"]);
if (event != null)
{
trace(' Loaded built-in song event: (${event.id})');
eventCache.set(event.id, event);
}
else
{
trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}');
}
}
}
static function registerScriptedEvents()
{
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
for (eventCls in scriptedEventClassNames)
{
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
if (event != null)
{
trace(' Loaded scripted song event: ${event.id}');
eventCache.set(event.id, event);
}
else
{
trace(' Failed to instantiate scripted song event class: ${eventCls}');
}
}
}
public static function listEventIds():Array<String>
{
return eventCache.keys().array();
}
public static function listEvents():Array<SongEvent>
{
return eventCache.values();
}
public static function getEvent(id:String):SongEvent
{
return eventCache.get(id);
}
public static function getEventSchema(id:String):SongEventSchema
{
var event:SongEvent = getEvent(id);
if (event == null) return null;
return event.getEventSchema();
}
static function clearEventCache()
{
eventCache.clear();
}
public static function handleEvent(data:SongEventData):Void
{
var eventType:String = data.event;
var eventHandler:SongEvent = eventCache.get(eventType);
if (eventHandler != null)
{
eventHandler.handleEvent(data);
}
else
{
trace('WARNING: No event handler for event with id: ${eventType}');
}
data.activated = true;
}
public static inline function handleEvents(events:Array<SongEventData>):Void
{
for (event in events)
{
handleEvent(event);
}
}
/**
* Given a list of song events and the current timestamp,
* return a list of events that should be handled.
*/
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool {
// If the event is already activated, don't activate it again.
if (event.activated) return false;
// If the event is in the future, don't activate it.
if (event.time > currentTime) return false;
return true;
});
}
/**
* Reset activation of all the provided events.
*/
public static function resetEvents(events:Array<SongEventData>):Void
{
for (event in events)
{
event.activated = false;
// TODO: Add an onReset() method to SongEvent?
}
}
}
enum abstract SongEventFieldType(String) from String to String
{
/**
* The STRING type will display as a text field.
*/
var STRING = "string";
/**
* The INTEGER type will display as a text field that only accepts numbers.
*/
var INTEGER = "integer";
/**
* The FLOAT type will display as a text field that only accepts numbers.
*/
var FLOAT = "float";
/**
* The BOOL type will display as a checkbox.
*/
var BOOL = "bool";
/**
* The ENUM type will display as a dropdown.
* Make sure to specify the `keys` field in the schema.
*/
var ENUM = "enum";
}
typedef SongEventSchemaField =
{
/**
* The name of the property as it should be saved in the event data.
*/
name:String,
/**
* The title of the field to display in the UI.
*/
title:String,
/**
* The type of the field.
*/
type:SongEventFieldType,
/**
* Used for ENUM values.
* The key is the display name and the value is the actual value.
*/
?keys:Map<String, Dynamic>,
/**
* Used for INTEGER and FLOAT values.
* The minimum value that can be entered.
*/
?min:Float,
/**
* Used for INTEGER and FLOAT values.
* The maximum value that can be entered.
*/
?max:Float,
/**
* Used for INTEGER and FLOAT values.
* The step value that will be used when incrementing/decrementing the value.
*/
?step:Float,
/**
* An optional default value for the field.
*/
?defaultValue:Dynamic,
}
typedef SongEventSchema = Array<SongEventSchemaField>;

View file

@ -0,0 +1,147 @@
package funkin.play.event;
import flixel.tweens.FlxTween;
import flixel.FlxCamera;
import flixel.tweens.FlxEase;
import funkin.play.event.SongEvent;
import funkin.play.song.SongData;
import funkin.play.event.SongEventData;
import funkin.play.event.SongEventData.SongEventFieldType;
/**
* This class represents a handler for camera zoom events.
*
* Example: Zoom to 1.3x:
* ```
* {
* 'e': 'ZoomCamera',
* 'v': 1.3
* }
* ```
*
* Example: Zoom to 1.3x
* ```
* {
* 'e': 'FocusCamera',
* 'v': {
* 'char': 2,
* 'y': -10,
* }
* }
* ```
*
* Example: Focus on (100, 100):
* ```
* {
* 'e': 'FocusCamera',
* 'v': {
* 'char': -1,
* 'x': 100,
* 'y': 100,
* }
* }
* ```
*/
class ZoomCameraSongEvent extends SongEvent
{
public function new()
{
super('ZoomCamera');
}
public override function handleEvent(data:SongEventData):Void
{
// Does nothing if there is no PlayState camera or stage.
if (PlayState.instance == null) return;
var zoom:Null<Float> = data.getFloat('zoom');
if (zoom == null) zoom = 1.0;
var duration:Null<Float> = data.getFloat('duration');
if (duration == null) duration = 4.0;
var ease:Null<String> = data.getString('ease');
if (ease == null) ease = 'linear';
// If it's a string, check the value.
switch (ease)
{
case 'INSTANT':
// Set the zoom. Use defaultCameraZoom to prevent breaking camera bops.
PlayState.instance.defaultCameraZoom = zoom * FlxCamera.defaultZoom;
default:
var easeFunction:Null<Float->Float> = Reflect.field(FlxEase, ease);
if (easeFunction == null)
{
trace('Invalid ease function: $ease');
return;
}
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepCrochet * duration / 1000), {ease: easeFunction});
}
}
public override function getTitle():String
{
return 'Zoom Camera';
}
/**
* ```
* {
* 'zoom': FLOAT, // Target zoom level.
* 'duration': FLOAT, // Optional duration in steps
* 'ease': ENUM, // Optional easing function
* }
* @return SongEventSchema
*/
public override function getEventSchema():SongEventSchema
{
return [
{
name: 'zoom',
title: 'Zoom Level',
defaultValue: 1.0,
step: 0.1,
type: SongEventFieldType.FLOAT
},
{
name: 'duration',
title: 'Duration (in steps)',
defaultValue: 4.0,
step: 0.5,
type: SongEventFieldType.FLOAT,
},
{
name: 'ease',
title: 'Easing Type',
defaultValue: 'linear',
type: SongEventFieldType.ENUM,
keys: [
'Linear' => 'linear',
'Instant' => 'INSTANT',
'Quad In' => 'quadIn',
'Quad Out' => 'quadOut',
'Quad In/Out' => 'quadInOut',
'Cube In' => 'cubeIn',
'Cube Out' => 'cubeOut',
'Cube In/Out' => 'cubeInOut',
'Quart In' => 'quartIn',
'Quart Out' => 'quartOut',
'Quart In/Out' => 'quartInOut',
'Quint In' => 'quintIn',
'Quint Out' => 'quintOut',
'Quint In/Out' => 'quintInOut',
'Smooth Step In' => 'smoothStepIn',
'Smooth Step Out' => 'smoothStepOut',
'Smooth Step In/Out' => 'smoothStepInOut',
'Sine In' => 'sineIn',
'Sine Out' => 'sineOut',
'Sine In/Out' => 'sineInOut',
'Elastic In' => 'elasticIn',
'Elastic Out' => 'elasticOut',
'Elastic In/Out' => 'elasticInOut',
]
}
];
}
}

View file

@ -36,7 +36,8 @@ class StickerSubState extends MusicBeatSubState
add(grpStickers); add(grpStickers);
// makes the stickers on the most recent camera, which is more often than not... a UI camera!! // makes the stickers on the most recent camera, which is more often than not... a UI camera!!
grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; // grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]];
grpStickers.cameras = FlxG.cameras.list;
if (oldStickers != null) if (oldStickers != null)
{ {
@ -208,8 +209,10 @@ class StickerSubState extends MusicBeatSubState
FlxG.switchState(new FreeplayState(this)); FlxG.switchState(new FreeplayState(this));
case STORY: case STORY:
FlxG.switchState(new StoryMenuState(this)); FlxG.switchState(new StoryMenuState(this));
case MAIN_MENU:
FlxG.switchState(new MainMenuState());
default: default:
FlxG.switchState(new FreeplayState(this)); FlxG.switchState(new MainMenuState());
} }
} }
@ -354,6 +357,7 @@ typedef StickerShit =
enum abstract NEXTSTATE(String) enum abstract NEXTSTATE(String)
{ {
var MAIN_MENU = 'mainmenu';
var FREEPLAY = 'freeplay'; var FREEPLAY = 'freeplay';
var STORY = 'story'; var STORY = 'story';
} }

View file

@ -3,6 +3,7 @@ package funkin.ui.debug.charting;
import haxe.ui.data.ArrayDataSource; import haxe.ui.data.ArrayDataSource;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.play.event.SongEventData;
import funkin.play.song.SongData.SongTimeChange; import funkin.play.song.SongData.SongTimeChange;
import funkin.play.song.SongSerializer; import funkin.play.song.SongSerializer;
import funkin.ui.haxeui.components.CharacterPlayer; import funkin.ui.haxeui.components.CharacterPlayer;

View file

@ -1,5 +1,6 @@
package funkin.ui.stageBuildShit; package funkin.ui.stageBuildShit;
import funkin.ui.stageBuildShit.StageOffsetSubState;
import flixel.FlxSprite; import flixel.FlxSprite;
/** /**

View file

@ -260,7 +260,7 @@ class StageOffsetSubState extends HaxeUISubState
// if (uiStuff != null) remove(uiStuff); // if (uiStuff != null) remove(uiStuff);
// uiStuff = null; // uiStuff = null;
PlayState.disableKeys = false; PlayState.instance.disableKeys = false;
PlayState.instance.resetCamera(); PlayState.instance.resetCamera();
FlxG.mouse.visible = false; FlxG.mouse.visible = false;
close(); close();

View file

@ -13,6 +13,8 @@ import funkin.data.level.LevelRegistry;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.play.PlayStatePlaylist;
import funkin.play.song.Song;
import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongDataParser;
import funkin.util.Constants; import funkin.util.Constants;
@ -112,8 +114,8 @@ class StoryMenuState extends MusicBeatState
{ {
FlxG.sound.playMusic(Paths.music('freakyMenu')); FlxG.sound.playMusic(Paths.music('freakyMenu'));
FlxG.sound.music.fadeIn(4, 0, 0.7); FlxG.sound.music.fadeIn(4, 0, 0.7);
Conductor.forceBPM(Constants.FREAKY_MENU_BPM);
} }
Conductor.forceBPM(Constants.FREAKY_MENU_BPM);
if (stickerSubState != null) if (stickerSubState != null)
{ {
@ -474,24 +476,25 @@ class StoryMenuState extends MusicBeatState
prop.playConfirm(); prop.playConfirm();
} }
PlayState.storyPlaylist = currentLevel.getSongs(); Paths.setCurrentLevel(currentLevel.id);
PlayState.isStoryMode = true;
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase()); PlayStatePlaylist.playlistSongIds = currentLevel.getSongs();
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase()); PlayStatePlaylist.isStoryMode = true;
PlayStatePlaylist.campaignScore = 0;
// TODO: Fix this. var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift();
PlayState.storyWeek = 0;
PlayState.campaignScore = 0;
// TODO: Fix this. var targetSong:Song = SongDataParser.fetchSong(targetSongId);
PlayState.storyDifficulty = 0;
PlayState.storyDifficulty_NEW = currentDifficultyId;
SongLoad.curDiff = PlayState.storyDifficulty_NEW; PlayStatePlaylist.campaignId = currentLevel.id;
PlayStatePlaylist.campaignTitle = currentLevel.getTitle();
new FlxTimer().start(1, function(tmr:FlxTimer) { new FlxTimer().start(1, function(tmr:FlxTimer) {
LoadingState.loadAndSwitchState(new PlayState(), true); LoadingState.loadAndSwitchState(new PlayState(
{
targetSong: targetSong,
targetDifficulty: currentDifficultyId,
}), true);
}); });
} }

View file

@ -14,7 +14,7 @@ class Constants
* The title of the game, for debug printing purposes. * The title of the game, for debug printing purposes.
* Change this if you're making an engine. * Change this if you're making an engine.
*/ */
public static final TITLE = "Friday Night Funkin'"; public static final TITLE:String = "Friday Night Funkin'";
/** /**
* The current version number of the game. * The current version number of the game.
@ -26,7 +26,7 @@ class Constants
* A suffix to add to the game version. * A suffix to add to the game version.
* Add a suffix to prototype builds and remove it for releases. * Add a suffix to prototype builds and remove it for releases.
*/ */
public static final VERSION_SUFFIX = ' PROTOTYPE'; public static final VERSION_SUFFIX:String = ' PROTOTYPE';
#if debug #if debug
static function get_VERSION():String static function get_VERSION():String
@ -48,12 +48,12 @@ class Constants
/** /**
* Link to download the game on Itch.io. * Link to download the game on Itch.io.
*/ */
public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin/purchase"; public static final URL_ITCH:String = 'https://ninja-muffin24.itch.io/funkin/purchase';
/** /**
* Link to the game's page on Kickstarter. * Link to the game's page on Kickstarter.
*/ */
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/"; public static final URL_KICKSTARTER:String = 'https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/';
/** /**
* GIT REPO DATA * GIT REPO DATA
@ -64,12 +64,12 @@ class Constants
/** /**
* The current Git branch. * The current Git branch.
*/ */
public static final GIT_BRANCH = funkin.util.macro.GitCommit.getGitBranch(); public static final GIT_BRANCH:String = funkin.util.macro.GitCommit.getGitBranch();
/** /**
* The current Git commit hash. * The current Git commit hash.
*/ */
public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash(); public static final GIT_HASH:String = funkin.util.macro.GitCommit.getGitCommitHash();
#end #end
/** /**
@ -87,27 +87,70 @@ class Constants
*/ */
public static final COLOR_HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33; public static final COLOR_HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
/**
* Default variation for charts.
*/
public static final DEFAULT_VARIATION:String = 'default';
/**
* STAGE DEFAULTS
*/
// ==============================
/**
* Default difficulty for charts.
*/
public static final DEFAULT_DIFFICULTY:String = 'normal';
/**
* Default player character for charts.
*/
public static final DEFAULT_CHARACTER:String = 'bf';
/**
* Default stage for charts.
*/
public static final DEFAULT_STAGE:String = 'mainStage';
/**
* Default song for if the PlayState messes up.
*/
public static final DEFAULT_SONG:String = 'tutorial';
/** /**
* OTHER * OTHER
*/ */
// ============================== // ==============================
/**
* All MP3 decoders introduce a playback delay of `528` samples,
* which at 44,100 Hz (samples per second) is ~12 ms.
*/
public static final MP3_DELAY_MS:Float = 528 / 44100 * 1000;
/** /**
* The scale factor to use when increasing the size of pixel art graphics. * The scale factor to use when increasing the size of pixel art graphics.
*/ */
public static final PIXEL_ART_SCALE = 6; public static final PIXEL_ART_SCALE:Float = 6;
/** /**
* The BPM of the title screen and menu music. * The BPM of the title screen and menu music.
* TODO: Move to metadata file. * TODO: Move to metadata file.
*/ */
public static final FREAKY_MENU_BPM = 102; public static final FREAKY_MENU_BPM:Float = 102;
/** /**
* The volume at which to play the countdown before the song starts. * The volume at which to play the countdown before the song starts.
*/ */
public static final COUNTDOWN_VOLUME = 0.6; public static final COUNTDOWN_VOLUME:Float = 0.6;
public static final DEFAULT_VARIATION = 'default'; /**
public static final DEFAULT_DIFFICULTY = 'normal'; * The default intensity for camera zooms.
*/
public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015;
/**
* The default rate for camera zooms (in beats per zoom).
*/
public static final DEFAULT_ZOOM_RATE:Int = 4;
} }