diff --git a/assets b/assets index 37b30c8d3..49e375616 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 37b30c8d3934617f2a4630e69a51887555a605eb +Subproject commit 49e375616e0c4f3fc66874ebe4262ac389d8c746 diff --git a/source/funkin/graphics/FlxFilteredSprite.hx b/source/funkin/graphics/FlxFilteredSprite.hx index af4418d60..ea0376c3d 100644 --- a/source/funkin/graphics/FlxFilteredSprite.hx +++ b/source/funkin/graphics/FlxFilteredSprite.hx @@ -193,7 +193,6 @@ class FlxFilteredSprite extends FlxSprite } else { - trace("GAGAGA"); resetFrame(); filtered = false; } diff --git a/source/funkin/ui/charSelect/CharSelectPlayer.hx b/source/funkin/ui/charSelect/CharSelectPlayer.hx index 9767c2d3b..710bdd45a 100644 --- a/source/funkin/ui/charSelect/CharSelectPlayer.hx +++ b/source/funkin/ui/charSelect/CharSelectPlayer.hx @@ -8,14 +8,10 @@ import funkin.modding.events.ScriptEvent; class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass { - var desLp:FlxKeyFrame = null; - public function new(x:Float, y:Float) { super(x, y, Paths.animateAtlas("charSelect/bfChill")); - desLp = anim.getFrameLabel("deselect loop start"); - onAnimationComplete.add(function(animLabel:String) { switch (animLabel) { @@ -80,8 +76,6 @@ class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass playAnimation("slidein", true, false, false); - desLp = anim.getFrameLabel("deselect loop start"); - updateHitbox(); updatePosition(str); diff --git a/source/funkin/ui/charSelect/CharSelectSubState.hx b/source/funkin/ui/charSelect/CharSelectSubState.hx index db8844288..aa207c865 100644 --- a/source/funkin/ui/charSelect/CharSelectSubState.hx +++ b/source/funkin/ui/charSelect/CharSelectSubState.hx @@ -125,6 +125,11 @@ class CharSelectSubState extends MusicBeatSubState override public function create():Void { + openSubState(new IntroSubState()); + subStateClosed.addOnce((_) -> { + camera.flash(); + checkNewChar(); + }); super.create(); bopInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/iconBopInfo/iconBopInfo.txt")); @@ -287,6 +292,7 @@ class CharSelectSubState extends MusicBeatSubState selectSound.volume = 0.7; FlxG.sound.defaultSoundGroup.add(selectSound); + FlxG.sound.list.add(selectSound); unlockSound = new FunkinSound(); unlockSound.loadEmbedded(Paths.sound('CS_unlock')); @@ -296,6 +302,7 @@ class CharSelectSubState extends MusicBeatSubState unlockSound.play(true); FlxG.sound.defaultSoundGroup.add(unlockSound); + FlxG.sound.list.add(unlockSound); // playing it here to preload it. not doing this makes a super awkward pause at the end of the intro // TODO: probably make an intro thing for funkinSound itself that preloads the next audio? @@ -360,6 +367,32 @@ class CharSelectSubState extends MusicBeatSubState }); } + function checkNewChar():Void + { + if (nonLocks.length > 0) selectTimer.start(0.5, (_) -> { + unLock(); + }); + else + { + FunkinSound.playMusic('stayFunky', + { + startingVolume: 1, + overrideExisting: true, + restartTrack: true, + onLoad: function() { + @:privateAccess + gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1); + #if desktop + // On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5 + // So we want to manually change it! + @:privateAccess + gfChill.analyzer.fftN = 512; + #end + } + }); + } + } + var grpIcons:FlxSpriteGroup; var grpXSpread(default, set):Float = 107; var grpYSpread(default, set):Float = 127; @@ -404,31 +437,14 @@ class CharSelectSubState extends MusicBeatSubState updateIconPositions(); grpIcons.scrollFactor.set(); - - if (nonLocks.length > 0) unLock(); - else - FunkinSound.playMusic('stayFunky', - { - startingVolume: 1, - overrideExisting: true, - restartTrack: true, - onLoad: function() { - @:privateAccess - gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1); - #if desktop - // On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5 - // So we want to manually change it! - @:privateAccess - gfChill.analyzer.fftN = 512; - #end - } - }); } function unLock() { var index = nonLocks[0]; + // pressedSelect = true; + var copy = 3; var yThing = -1; @@ -448,7 +464,7 @@ class CharSelectSubState extends MusicBeatSubState nonLocks.shift(); - selectTimer.start(1, function(_) { + selectTimer.start(0.5, function(_) { var lock:Lock = cast grpIcons.group.members[index]; lock.anim.getFrameLabel("unlockAnim").add(function() { @@ -460,7 +476,12 @@ class CharSelectSubState extends MusicBeatSubState unlockSound.volume = 0.7; unlockSound.play(true); + syncLock = lock; + + // sync = true; + lock.onAnimationComplete.addOnce(function(_) { + syncLock = null; var char = availableChars.get(index); camera.flash(0xFFFFFFFF, 0.1); playerChill.playAnimation("unlock"); @@ -482,15 +503,18 @@ class CharSelectSubState extends MusicBeatSubState bopPlay = true; updateIconPositions(); + playerChillOut.onAnimationComplete.addOnce((_) -> if (_ == "death") + { + sync = false; + playerChillOut.visible = false; + playerChillOut.switchChar(char); + }); + Save.instance.addCharacterSeen(char); if (nonLocks.length == 0) { - playerChillOut.onAnimationComplete.addOnce((_) -> { - playerChillOut.visible = false; - playerChillOut.switchChar(char); - }); + pressedSelect = false; @:bypassAccessor curChar = char; - Save.instance.addCharacterSeen(char); FunkinSound.playMusic('stayFunky', { startingVolume: 1, @@ -536,6 +560,38 @@ class CharSelectSubState extends MusicBeatSubState } } + var sync:Bool = false; + + var syncLock:Lock = null; + + var audioBizz:Float = 0; + + function syncAudio(elapsed:Float):Void + { + @:privateAccess + if (sync && !unlockSound.paused) + { + // if (playerChillOut.anim.framerate > 0) + // { + // if (syncLock != null) syncLock.anim.framerate = 0; + // playerChillOut.anim.framerate = 0; + // } + + playerChillOut.anim._tick = 0; + if (syncLock != null) syncLock.anim._tick = 0; + + trace(unlockSound.time); + + if ((unlockSound.time - audioBizz) >= (delay * 1000)) + { + if (syncLock != null) syncLock.anim._tick = delay; + + playerChillOut.anim._tick = delay; + audioBizz += delay * 1000; + } + } + } + function goToFreeplay():Void { autoFollow = false; @@ -589,71 +645,76 @@ class CharSelectSubState extends MusicBeatSubState if (controls.UI_UP_R || controls.UI_DOWN_R || controls.UI_LEFT_R || controls.UI_RIGHT_R) selectSound.pitch = 1; - if (controls.UI_UP) holdTmrUp += elapsed; - if (controls.UI_UP_R) + syncAudio(elapsed); + + if (!pressedSelect) { - holdTmrUp = 0; - spamUp = false; - } + if (controls.UI_UP) holdTmrUp += elapsed; + if (controls.UI_UP_R) + { + holdTmrUp = 0; + spamUp = false; + } - if (controls.UI_DOWN) holdTmrDown += elapsed; - if (controls.UI_DOWN_R) - { - holdTmrDown = 0; - spamDown = false; - } + if (controls.UI_DOWN) holdTmrDown += elapsed; + if (controls.UI_DOWN_R) + { + holdTmrDown = 0; + spamDown = false; + } - if (controls.UI_LEFT) holdTmrLeft += elapsed; - if (controls.UI_LEFT_R) - { - holdTmrLeft = 0; - spamLeft = false; - } + if (controls.UI_LEFT) holdTmrLeft += elapsed; + if (controls.UI_LEFT_R) + { + holdTmrLeft = 0; + spamLeft = false; + } - if (controls.UI_RIGHT) holdTmrRight += elapsed; - if (controls.UI_RIGHT_R) - { - holdTmrRight = 0; - spamRight = false; - } + if (controls.UI_RIGHT) holdTmrRight += elapsed; + if (controls.UI_RIGHT_R) + { + holdTmrRight = 0; + spamRight = false; + } - var initSpam = 0.5; + var initSpam = 0.5; - if (holdTmrUp >= initSpam) spamUp = true; - if (holdTmrDown >= initSpam) spamDown = true; - if (holdTmrLeft >= initSpam) spamLeft = true; - if (holdTmrRight >= initSpam) spamRight = true; + if (holdTmrUp >= initSpam) spamUp = true; + if (holdTmrDown >= initSpam) spamDown = true; + if (holdTmrLeft >= initSpam) spamLeft = true; + if (holdTmrRight >= initSpam) spamRight = true; - if (controls.UI_UP_P) - { - cursorY -= 1; - cursorDenied.visible = false; + if (controls.UI_UP_P) + { + cursorY -= 1; + cursorDenied.visible = false; - holdTmrUp = 0; + holdTmrUp = 0; - selectSound.play(true); - } - if (controls.UI_DOWN_P) - { - cursorY += 1; - cursorDenied.visible = false; - holdTmrDown = 0; - selectSound.play(true); - } - if (controls.UI_LEFT_P) - { - cursorX -= 1; - cursorDenied.visible = false; + selectSound.play(true); + } + if (controls.UI_DOWN_P) + { + cursorY += 1; + cursorDenied.visible = false; + holdTmrDown = 0; + selectSound.play(true); + } + if (controls.UI_LEFT_P) + { + cursorX -= 1; + cursorDenied.visible = false; - holdTmrLeft = 0; - selectSound.play(true); - } - if (controls.UI_RIGHT_P) - { - cursorX += 1; - cursorDenied.visible = false; - holdTmrRight = 0; - selectSound.play(true); + holdTmrLeft = 0; + selectSound.play(true); + } + if (controls.UI_RIGHT_P) + { + cursorX += 1; + cursorDenied.visible = false; + holdTmrRight = 0; + selectSound.play(true); + } } if (cursorX < -1) @@ -694,7 +755,7 @@ class CharSelectSubState extends MusicBeatSubState gfChill.playAnimation("confirm"); pressedSelect = true; selectTimer.start(1.5, (_) -> { - pressedSelect = false; + // pressedSelect = false; // FlxG.switchState(FreeplayState.build( // { // { @@ -735,6 +796,8 @@ class CharSelectSubState extends MusicBeatSubState cursorDenied.visible = true; cursorDenied.x = cursor.x - 2; cursorDenied.y = cursor.y - 4; + + playerChill.playAnimation("cannot select", true); cursorDenied.animation.play("idle", true); cursorDenied.animation.finishCallback = (_) -> { cursorDenied.visible = false; @@ -796,6 +859,7 @@ class CharSelectSubState extends MusicBeatSubState var refFrame = bopInfo.frames[bopInfo.frames.length - 1]; var curFrame = bopInfo.frames[bopFr]; + if (bopFr >= 13) icon.filters = selectedBizz; var scaleXDiff:Float = curFrame.scaleX - refFrame.scaleX; var scaleYDiff:Float = curFrame.scaleY - refFrame.scaleY; @@ -878,7 +942,6 @@ class CharSelectSubState extends MusicBeatSubState if (index == getCurrentSelected()) { // memb.pixels = memb.withDropShadow.clone(); - memb.filters = selectedBizz; if (bopPlay) { @@ -890,12 +953,14 @@ class CharSelectSubState extends MusicBeatSubState doBop(memb, FlxG.elapsed); } else - memb.scale.set(2.6, 2.6); - - if (controls.ACCEPT) memb.animation.play("confirm"); - if (memb.animation.curAnim.name == "confirm" && controls.BACK) { - memb.animation.play("confirm", false, true); + memb.filters = selectedBizz; + memb.scale.set(2.6, 2.6); + } + if (controls.ACCEPT && memb.animation.curAnim.name == "confirm") memb.animation.play("confirm"); + if (pressedSelect && controls.BACK) + { + memb.animation.play("confirm", true, true); member.animation.finishCallback = (_) -> { member.animation.play("idle"); member.animation.finishCallback = null; diff --git a/source/funkin/ui/charSelect/IntroSubState.hx b/source/funkin/ui/charSelect/IntroSubState.hx new file mode 100644 index 000000000..04503cbb9 --- /dev/null +++ b/source/funkin/ui/charSelect/IntroSubState.hx @@ -0,0 +1,141 @@ +package funkin.ui.charSelect; + +#if html5 +import funkin.graphics.video.FlxVideo; +#end +#if hxCodec +import hxcodec.flixel.FlxVideoSprite; +#end +import funkin.ui.MusicBeatSubState; +import funkin.audio.FunkinSound; + +/** + * After about 2 minutes of inactivity on the title screen, + * the game will enter the Attract state, as a reference to physical arcade machines. + * + * In the current version, this just plays the ~~Kickstarter trailer~~ Erect teaser, but this can be changed to + * gameplay footage, a generic game trailer, or something more elaborate. + */ +class IntroSubState extends MusicBeatSubState +{ + static final ATTRACT_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('introSelect')); + + var introSound:FunkinSound = null; + + public override function create():Void + { + // Pause existing music. + if (FlxG.sound.music != null) + { + FlxG.sound.music.destroy(); + FlxG.sound.music = null; + } + + #if html5 + trace('Playing web video ${ATTRACT_VIDEO_PATH}'); + playVideoHTML5(ATTRACT_VIDEO_PATH); + #end + + #if hxCodec + trace('Playing native video ${ATTRACT_VIDEO_PATH}'); + playVideoNative(ATTRACT_VIDEO_PATH); + #end + + introSound = new FunkinSound(); + introSound.loadEmbedded(Paths.sound('CS_Lights')); + introSound.pitch = 1; + + FlxG.sound.defaultSoundGroup.add(introSound); + FlxG.sound.list.add(introSound); + + introSound.play(true); + } + + #if html5 + var vid:FlxVideo; + + function playVideoHTML5(filePath:String):Void + { + // Video displays OVER the FlxState. + vid = new FlxVideo(filePath); + + vid.scrollFactor.set(); + if (vid != null) + { + vid.zIndex = 0; + + vid.finishCallback = onAttractEnd; + + add(vid); + } + else + { + trace('ALERT: Video is null! Could not play cutscene!'); + } + } + #end + + #if hxCodec + var vid:FlxVideoSprite; + + function playVideoNative(filePath:String):Void + { + // Video displays OVER the FlxState. + vid = new FlxVideoSprite(0, 0); + + vid.scrollFactor.set(); + + if (vid != null) + { + vid.zIndex = 0; + vid.bitmap.onEndReached.add(onAttractEnd); + + add(vid); + vid.play(filePath, false); + } + else + { + trace('ALERT: Video is null! Could not play cutscene!'); + } + } + #end + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (controls.ACCEPT) + { + onAttractEnd(); + } + } + + /** + * When the attraction state ends (after the video ends or the user presses any button), + * switch immediately to the title screen. + */ + function onAttractEnd():Void + { + #if html5 + if (vid != null) + { + remove(vid); + } + #end + + #if hxCodec + if (vid != null) + { + vid.stop(); + remove(vid); + } + #end + + #if (html5 || hxCodec) + vid.destroy(); + vid = null; + #end + + close(); + } +}