package funkin.game; import funkin.options.Options; import funkin.scripting.Script; import flixel.util.FlxDestroyUtil; #if desktop import funkin.system.Discord.DiscordClient; #end import funkin.system.Section.SwagSection; import funkin.system.Song.SwagSong; import funkin.scripting.ScriptPack; import funkin.shaders.WiggleEffect.WiggleEffectType; import flixel.FlxBasic; import flixel.FlxCamera; import flixel.FlxG; import flixel.FlxGame; import flixel.FlxObject; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxSubState; import flixel.addons.display.FlxGridOverlay; import flixel.addons.effects.FlxTrail; import flixel.addons.effects.FlxTrailArea; import flixel.addons.effects.chainable.FlxEffectSprite; import flixel.addons.effects.chainable.FlxWaveEffect; import flixel.addons.transition.FlxTransitionableState; import flixel.graphics.atlas.FlxAtlas; import flixel.graphics.frames.FlxAtlasFrames; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.system.FlxSound; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.ui.FlxBar; import flixel.util.FlxCollision; import flixel.util.FlxColor; import flixel.util.FlxSort; import flixel.util.FlxStringUtil; import flixel.util.FlxTimer; import haxe.Json; import haxe.io.Path; import lime.utils.Assets; import openfl.display.BlendMode; import openfl.display.StageQuality; import openfl.filters.ShaderFilter; import funkin.system.Conductor; import funkin.system.Song; import funkin.editors.ChartingState; import funkin.debug.AnimationDebug; import funkin.menus.*; import funkin.scripting.events.*; using StringTools; class PlayState extends MusicBeatState { public static var instance:PlayState = null; /** * SONG METADATA */ public static var SONG:SwagSong; public static var isStoryMode:Bool = false; public static var storyWeek:Int = 0; public static var storyPlaylist:Array = []; public static var storyDifficulty:Int = 1; public static var fromMods:Bool = false; public var scripts:ScriptPack; public var halloweenLevel:Bool = false; public var stage:Stage; public var scrollSpeed:Float = 0; public var vocals:FlxSound; public var dad:Character; public var gf:Character; public var boyfriend:Character; public var notes:NoteGroup; public var strumLine:FlxSprite; public var curSection:Int = 0; public var camFollow:FlxObject; private static var prevCamFollow:FlxObject; public var strumLineNotes:FlxTypedGroup; public var playerStrums:FlxTypedGroup; public var cpuStrums:FlxTypedGroup; public var muteVocalsOnMiss:Bool = true; public var camZooming:Bool = false; public var camZoomingInterval:Int = 4; public var curSong:String = ""; public var curStage:String = ""; public var gfSpeed:Int = 1; public var health:Float = 1; public var combo:Int = 0; public var comboBreaks:Bool = false; public var healthBarBG:FlxSprite; public var healthBar:FlxBar; public var generatedMusic:Bool = false; public var startingSong:Bool = false; public var iconP1:HealthIcon; public var iconP2:HealthIcon; public var camHUD:FlxCamera; public var camGame:FlxCamera; public var songScore:Int = 0; public var downscroll:Bool = Options.downscroll; public var misses:Int = 0; public var scoreTxt:FlxText; public var missesTxt:FlxText; public var accuracyTxt:FlxText; public static var campaignScore:Int = 0; public var defaultCamZoom:Float = 1.05; // how big to stretch the pixel art assets public static var daPixelZoom:Float = 6; public var inCutscene:Bool = false; #if desktop // Discord RPC variables public var storyDifficultyText:String = ""; public var iconRPC:String = ""; public var songLength:Float = 0; public var detailsText:String = ""; public var detailsPausedText:String = ""; #end override public function create() { instance = this; if (FlxG.sound.music != null) FlxG.sound.music.stop(); scripts = new ScriptPack("PlayState"); scripts.setParent(this); camGame = new FlxCamera(); camHUD = new FlxCamera(); camHUD.bgColor.alpha = 0; FlxG.cameras.reset(camGame); FlxG.cameras.add(camHUD, false); // camGame.widescreen = true; persistentUpdate = true; persistentDraw = true; if (SONG == null) SONG = Song.loadFromJson('tutorial', 'normal'); scrollSpeed = SONG.speed; Conductor.mapBPMChanges(SONG); Conductor.changeBPM(SONG.bpm); #if desktop // TODO: Scriptable custom RPC iconRPC = SONG.player2; // String that contains the mode defined here so it isn't necessary to call changePresence for each mode if (isStoryMode) { detailsText = "Story Mode: Week " + storyWeek; } else { detailsText = "Freeplay"; } // String for when the game is paused detailsPausedText = "Paused - " + detailsText; // Updating Discord Rich Presence. DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC); #end dad = new Character(100, 100, SONG.player2); if (dad != null && dad.isGF) { dad.setPosition(400, 130); gf = dad; dad.scrollFactor.set(0.95, 0.95); } else { var gfVersion = SONG.gf; if (gfVersion == null) gfVersion = "gf"; gf = new Character(400, 130, gfVersion); gf.scrollFactor.set(0.95, 0.95); } var camPos:FlxPoint = new FlxPoint(dad.getGraphicMidpoint().x, dad.getGraphicMidpoint().y); boyfriend = new Character(770, 100, SONG.player1, true); if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = "stage"; add(new Stage(SONG.stage)); switch(SONG.song) { // case "": // ADD YOUR HARDCODED SCRIPTS HERE! default: var content = Paths.getFolderContent('data/charts/${SONG.song}/', false, true, !fromMods); for(file in content) { var ext = Path.extension(file).toLowerCase(); if (Script.scriptExtensions.contains(ext)) { scripts.add(Script.create(file)); } } } scripts.load(); scripts.call("create"); add(gf); add(dad); add(boyfriend); strumLine = new FlxSprite(0, 50).makeGraphic(FlxG.width, 10); strumLine.scrollFactor.set(); strumLineNotes = new FlxTypedGroup(); playerStrums = new FlxTypedGroup(); cpuStrums = new FlxTypedGroup(); add(strumLineNotes); generateSong(SONG.song); if (prevCamFollow != null) { camFollow = prevCamFollow; prevCamFollow = null; } else { camFollow = new FlxObject(0, 0, 2, 2); camFollow.setPosition(camPos.x, camPos.y); } add(camFollow); FlxG.camera.follow(camFollow, LOCKON, 0.04); FlxG.camera.zoom = defaultCamZoom; FlxG.camera.focusOn(camFollow.getPosition()); FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height); // TODO: cool healthbar healthBarBG = new FlxSprite(0, FlxG.height * 0.9).loadGraphic(Paths.image('healthBar')); healthBarBG.screenCenter(X); healthBarBG.scrollFactor.set(); add(healthBarBG); healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this, 'health', 0, 2); healthBar.scrollFactor.set(); healthBar.createFilledBar(0xFFFF0000, 0xFF66FF33); add(healthBar); iconP1 = new HealthIcon(boyfriend.getIcon(), true); iconP2 = new HealthIcon(dad.getIcon(), false); for(icon in [iconP1, iconP2]) { icon.y = healthBar.y - (icon.height / 2); add(icon); } scoreTxt = new FlxText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), "Score:0", 20); missesTxt = new FlxText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), "Misses:0", 20); accuracyTxt = new FlxText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), "Accuracy:-%", 20); for(text in [scoreTxt, missesTxt, accuracyTxt]) { text.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE); text.borderStyle = OUTLINE; text.borderSize = 1; text.borderColor = 0xFF000000; text.scrollFactor.set(); add(text); } scoreTxt.alignment = RIGHT; missesTxt.alignment = CENTER; accuracyTxt.alignment = LEFT; for(e in [strumLineNotes, notes, healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt]) e.cameras = [camHUD]; startingSong = true; super.create(); } public override function createPost() { super.createPost(); startCountdown(); scripts.call("createPost"); } public var startTimer:FlxTimer; public var perfectMode:Bool = false; public var introLength:Int = 5; function startCountdown():Void { inCutscene = false; generateStaticArrows(0); generateStaticArrows(1); scripts.call("onStartCountdown"); startedCountdown = true; Conductor.songPosition = 0; Conductor.songPosition -= Conductor.crochet * introLength; var swagCounter:Int = 0; var introSprites:Array = [null, 'ready', "set", "go"]; var introSounds:Array = ['intro3', 'intro2', "intro1", "introGo"]; startTimer = new FlxTimer().start(Conductor.crochet / 1000, function(tmr:FlxTimer) { dad.dance(); gf.dance(); boyfriend.dance(); var event:CountdownEvent = scripts.event("onCountdown", new CountdownEvent( introSprites[swagCounter], introSounds[swagCounter], 1, 0.6, true)); var sprite:FlxSprite = null; var sound:FlxSound = null; var tween:FlxTween = null; if (!event.cancelled) { if (event.spritePath != null) { var spr = event.spritePath; if (!Assets.exists(spr)) spr = Paths.image(spr); sprite = new FlxSprite().loadGraphic(spr); sprite.scrollFactor.set(); sprite.updateHitbox(); sprite.screenCenter(); add(sprite); tween = FlxTween.tween(sprite, {y: sprite.y + 100, alpha: 0}, Conductor.crochet / 1000, { ease: FlxEase.cubeInOut, onComplete: function(twn:FlxTween) { sprite.destroy(); } }); } if (event.soundPath != null) { var sfx = event.soundPath; if (!Assets.exists(sfx)) sfx = Paths.sound(sfx); sound = FlxG.sound.play(sfx, event.volume); } } event.sprite = sprite; event.sound = sound; event.spriteTween = tween; event.cancelled = false; scripts.event("onCountdownPost", event); swagCounter += 1; }, introLength); } public var previousFrameTime:Int = 0; public var lastReportedPlayheadPosition:Int = 0; public var songTime:Float = 0; function startSong():Void { startingSong = false; previousFrameTime = FlxG.game.ticks; lastReportedPlayheadPosition = 0; if (!paused) FlxG.sound.playMusic(Paths.inst(PlayState.SONG.song), 1, false); FlxG.sound.music.onComplete = endSong; vocals.play(); updateDiscordStatus(); } public override function destroy() { scripts.call("destroy"); super.destroy(); FlxDestroyUtil.destroy(scripts); instance = null; } public var debugNum:Int = 0; private function generateSong(dataPath:String, ?songData:SwagSong):Void { if (songData == null) songData = SONG; Conductor.changeBPM(songData.bpm); curSong = songData.song; vocals = new FlxSound(); if (SONG.needsVoices) vocals.loadEmbedded(Paths.voices(PlayState.SONG.song)); FlxG.sound.list.add(vocals); notes = new NoteGroup(); add(notes); var noteData:Array = songData.notes; var playerCounter:Int = 0; var daBeats:Int = 0; // Not exactly representative of 'daBeats' lol, just how much it has looped for (section in noteData) { if (section == null || section.sectionNotes == null) continue; // var coolSection:Int = Std.int(section.lengthInSteps / 4); for (songNotes in section.sectionNotes) { if (songNotes == null) continue; var daStrumTime:Float = songNotes[0]; var daNoteData:Int = Std.int(songNotes[1] % 8); var daNoteType:Int = Std.int(songNotes[1] / 8); var gottaHitNote:Bool = daNoteData >= 4 ? !section.mustHitSection : section.mustHitSection; if (songNotes.length > 2) { if (songNotes[3] is Int) daNoteType = songNotes[3]; else if (songNotes[3] is String) daNoteType = addNoteType(songNotes[3]); } var swagNote:Note; if (notes.length > 0) swagNote = notes.members[Std.int(notes.members.length - 1)]; else swagNote = null; swagNote = new Note(daStrumTime, daNoteData % 4, daNoteType, gottaHitNote, swagNote); swagNote.sustainLength = songNotes[2]; swagNote.scrollFactor.set(0, 0); notes.add(swagNote); // calculate sustain length and fix var susLength:Float = swagNote.sustainLength / Conductor.stepCrochet; if (susLength > 0.75) susLength++; // create sustains for (susNote in 0...Math.floor(susLength)) { swagNote = new Note(daStrumTime + (Conductor.stepCrochet * susNote), daNoteData % 4, daNoteType, gottaHitNote, swagNote, true); swagNote.scrollFactor.set(); notes.add(swagNote); } } daBeats += 1; } notes.sortNotes(); generatedMusic = true; } public function addNoteType(name:String) { // TODO: add note type by string return 0; } function sortByShit(Obj1:Note, Obj2:Note):Int { return FlxSort.byValues(FlxSort.ASCENDING, Obj1.strumTime, Obj2.strumTime); } private function generateStaticArrows(player:Int):Void { for (i in 0...4) { // FlxG.log.add(i); var babyArrow:Strum = new Strum(0, strumLine.y); switch (curStage) { case 'school' | 'schoolEvil': babyArrow.loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17); babyArrow.animation.add('green', [6]); babyArrow.animation.add('red', [7]); babyArrow.animation.add('blue', [5]); babyArrow.animation.add('purplel', [4]); babyArrow.setGraphicSize(Std.int(babyArrow.width * daPixelZoom)); babyArrow.updateHitbox(); babyArrow.antialiasing = false; switch (Math.abs(i)) { case 0: babyArrow.animation.add('static', [0]); babyArrow.animation.add('pressed', [4, 8], 12, false); babyArrow.animation.add('confirm', [12, 16], 24, false); case 1: babyArrow.animation.add('static', [1]); babyArrow.animation.add('pressed', [5, 9], 12, false); babyArrow.animation.add('confirm', [13, 17], 24, false); case 2: babyArrow.animation.add('static', [2]); babyArrow.animation.add('pressed', [6, 10], 12, false); babyArrow.animation.add('confirm', [14, 18], 12, false); case 3: babyArrow.animation.add('static', [3]); babyArrow.animation.add('pressed', [7, 11], 12, false); babyArrow.animation.add('confirm', [15, 19], 24, false); } default: babyArrow.frames = Paths.getSparrowAtlas('NOTE_assets'); babyArrow.animation.addByPrefix('green', 'arrowUP'); babyArrow.animation.addByPrefix('blue', 'arrowDOWN'); babyArrow.animation.addByPrefix('purple', 'arrowLEFT'); babyArrow.animation.addByPrefix('red', 'arrowRIGHT'); babyArrow.antialiasing = true; babyArrow.setGraphicSize(Std.int(babyArrow.width * 0.7)); switch (Math.abs(i)) { case 0: babyArrow.animation.addByPrefix('static', 'arrowLEFT'); babyArrow.animation.addByPrefix('pressed', 'left press', 24, false); babyArrow.animation.addByPrefix('confirm', 'left confirm', 24, false); case 1: babyArrow.animation.addByPrefix('static', 'arrowDOWN'); babyArrow.animation.addByPrefix('pressed', 'down press', 24, false); babyArrow.animation.addByPrefix('confirm', 'down confirm', 24, false); case 2: babyArrow.animation.addByPrefix('static', 'arrowUP'); babyArrow.animation.addByPrefix('pressed', 'up press', 24, false); babyArrow.animation.addByPrefix('confirm', 'up confirm', 24, false); case 3: babyArrow.animation.addByPrefix('static', 'arrowRIGHT'); babyArrow.animation.addByPrefix('pressed', 'right press', 24, false); babyArrow.animation.addByPrefix('confirm', 'right confirm', 24, false); } } // babyArrow.x += Note.swagWidth * 1; babyArrow.x = (FlxG.width * 0.25) + (Note.swagWidth * (i - 2)); babyArrow.updateHitbox(); babyArrow.scrollFactor.set(); if (!isStoryMode) { babyArrow.y -= 10; babyArrow.alpha = 0; FlxTween.tween(babyArrow, {y: babyArrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * i)}); } babyArrow.ID = i; if (player == 1) { playerStrums.add(babyArrow); } else { babyArrow.cpu = true; cpuStrums.add(babyArrow); } babyArrow.animation.play('static'); babyArrow.x += ((FlxG.width / 2) * player); strumLineNotes.add(babyArrow); } } function tweenCamIn():Void { FlxTween.tween(FlxG.camera, {zoom: 1.3}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut}); } override function openSubState(SubState:FlxSubState) { // TODO: Cancellable substate opening. scripts.call("onSubstateOpen", [SubState]); if (paused) { if (FlxG.sound.music != null) { FlxG.sound.music.pause(); vocals.pause(); } if (!startTimer.finished) startTimer.active = false; } super.openSubState(SubState); } override function closeSubState() { scripts.call("onSubstateClose"); if (paused) { if (FlxG.sound.music != null && !startingSong) { resyncVocals(); } if (!startTimer.finished) startTimer.active = true; paused = false; #if desktop if (startTimer.finished) { DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength - Conductor.songPosition); } else { DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC); } #end } super.closeSubState(); } override public function onFocus():Void { scripts.call("onFocus"); #if desktop if (health > 0 && !paused) { if (Conductor.songPosition > 0.0) { DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength - Conductor.songPosition); } else { DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC); } } #end super.onFocus(); } override public function onFocusLost():Void { scripts.call("onFocusLost"); #if desktop if (health > 0 && !paused) { DiscordClient.changePresence(detailsPausedText, SONG.song + " (" + storyDifficultyText + ")", iconRPC); } #end super.onFocusLost(); } function resyncVocals():Void { vocals.pause(); FlxG.sound.music.play(); Conductor.songPosition = FlxG.sound.music.time; vocals.time = Conductor.songPosition; vocals.play(); scripts.call("onVocalsResync"); } public var paused:Bool = false; public var startedCountdown:Bool = false; public var canPause:Bool = true; public function pauseGame() { // TODO: Cancellable game pause scripts.call("onGamePause"); persistentUpdate = false; persistentDraw = true; paused = true; // 1 / 1000 chance for Gitaroo Man easter egg if (FlxG.random.bool(0.1)) { // gitaroo man easter egg FlxG.switchState(new GitarooPause()); } else openSubState(new PauseSubState(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y)); updateDiscordStatus(); } // TODO: Update Discord Status public function updateDiscordStatus() { // TODO: Cancellable Discord Update Presence #if desktop // Song duration in a float, useful for the time left feature songLength = FlxG.sound.music.length; // Updating Discord Rich Presence (with Time Left) DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength); #end scripts.call("onDiscordPresenceUpdate"); } override public function update(elapsed:Float) { #if !debug perfectMode = false; #end super.update(elapsed); scripts.call("update", [elapsed]); scoreTxt.text = 'Score:$songScore'; missesTxt.text = '${comboBreaks ? "Combo Breaks" : "Misses"}:$misses'; if (FlxG.keys.justPressed.ENTER && startedCountdown && canPause) pauseGame(); if (FlxG.keys.justPressed.SEVEN) { FlxG.switchState(new ChartingState()); #if desktop DiscordClient.changePresence("Chart Editor", null, null, true); #end } // FlxG.watch.addQuick('VOL', vocals.amplitudeLeft); // FlxG.watch.addQuick('VOLRight', vocals.amplitudeRight); // TODO: FPS-compatible lerping iconP1.scale.set(lerp(iconP1.scale.x, 1, 0.33), lerp(iconP1.scale.y, 1, 0.33)); iconP2.scale.set(lerp(iconP2.scale.x, 1, 0.33), lerp(iconP2.scale.y, 1, 0.33)); iconP1.updateHitbox(); iconP2.updateHitbox(); var iconOffset:Int = 26; iconP1.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 100, 0) * 0.01) - iconOffset); iconP2.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 100, 0) * 0.01)) - (iconP2.width - iconOffset); if (health > 2) health = 2; iconP1.health = health / 2; iconP2.health = 1 - (health / 2); /* if (FlxG.keys.justPressed.NINE) FlxG.switchState(new Charting()); */ if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new AnimationDebug(SONG.player2)); if (startingSong) { if (startedCountdown) { Conductor.songPosition += FlxG.elapsed * 1000; if (Conductor.songPosition >= 0) startSong(); } } else { Conductor.songPosition += FlxG.elapsed * 1000; if (!paused) { songTime += FlxG.game.ticks - previousFrameTime; previousFrameTime = FlxG.game.ticks; if (Math.abs(FlxG.sound.music.time - Conductor.songPosition) > 20) resyncVocals(); } // Conductor.lastSongPos = FlxG.sound.music.time; } if (generatedMusic && PlayState.SONG.notes[Std.int(curStep / 16)] != null) { if (PlayState.SONG.notes[Std.int(curStep / 16)].mustHitSection) { var pos = boyfriend.getCameraPosition(); camFollow.setPosition(pos.x, pos.y); // switch (curStage) // { // case 'limo': // camFollow.x = boyfriend.getMidpoint().x - 300; // case 'mall': // camFollow.y = boyfriend.getMidpoint().y - 200; // case 'school': // camFollow.x = boyfriend.getMidpoint().x - 200; // camFollow.y = boyfriend.getMidpoint().y - 200; // case 'schoolEvil': // camFollow.x = boyfriend.getMidpoint().x - 200; // camFollow.y = boyfriend.getMidpoint().y - 200; // } if (SONG.song.toLowerCase() == 'tutorial') { FlxTween.tween(FlxG.camera, {zoom: 1}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut}); } } else { var pos = dad.getCameraPosition(); camFollow.setPosition(pos.x, pos.y); // camFollow.setPosition(dad.getMidpoint().x + 150, dad.getMidpoint().y - 100); // camFollow.setPosition(lucky.getMidpoint().x - 120, lucky.getMidpoint().y + 210); // switch (dad.curCharacter) // { // case 'mom': // camFollow.y = dad.getMidpoint().y; // case 'senpai': // camFollow.y = dad.getMidpoint().y - 430; // camFollow.x = dad.getMidpoint().x - 100; // case 'senpai-angry': // camFollow.y = dad.getMidpoint().y - 430; // camFollow.x = dad.getMidpoint().x - 100; // } if (dad.curCharacter == 'mom') vocals.volume = 1; if (SONG.song.toLowerCase() == 'tutorial') { tweenCamIn(); } } } if (camZooming) { FlxG.camera.zoom = lerp(FlxG.camera.zoom, defaultCamZoom, 0.05); camHUD.zoom = lerp(camHUD.zoom, 1, 0.05); } FlxG.watch.addQuick("beatShit", curBeat); FlxG.watch.addQuick("stepShit", curStep); if (curSong == 'Fresh') { switch (curBeat) { case 16: camZooming = true; gfSpeed = 2; case 48: gfSpeed = 1; case 80: gfSpeed = 2; case 112: gfSpeed = 1; case 163: // FlxG.sound.music.stop(); // FlxG.switchState(new TitleState()); } } if (curSong == 'Bopeebo') { switch (curBeat) { case 128, 129, 130: vocals.volume = 0; // FlxG.sound.music.stop(); // FlxG.switchState(new PlayState()); } } // better streaming of shit // RESET = Quick Game Over Screen if (controls.RESET) { health = 0; trace("RESET = True"); } // CHEAT = brandon's a pussy if (controls.CHEAT) { health += 1; trace("User is cheating!"); } if (health <= 0) { boyfriend.stunned = true; persistentUpdate = false; persistentDraw = false; paused = true; vocals.stop(); FlxG.sound.music.stop(); openSubState(new GameOverSubstate(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y)); // FlxG.switchState(new GameOverState(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y)); #if desktop // Game Over doesn't get his own variable because it's only used here DiscordClient.changePresence("Game Over - " + detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC); #end } // while(unspawnNotes[0] != null && unspawnNotes[0].strumTime - Conductor.songPosition < 1500) // notes.add(unspawnNotes.shift()); if (generatedMusic) { notes.forEachAlive(function(daNote:Note) { if (!daNote.mustPress && !daNote.wasGoodHit && daNote.strumTime <= Conductor.songPosition) goodNoteHit(daNote); // TODO: FIXED STEP CROCHET PER NOTES FOR BPM CHANGES if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (Conductor.stepCrochet) < Conductor.songPosition) { deleteNote(daNote); return; } if (daNote.tooLate) { noteMiss(daNote); return; } var strum:Strum = null; for(e in (daNote.mustPress ? playerStrums : cpuStrums).members) { if (e.ID == daNote.noteData % 4) { strum = e; break; } } if (strum == null) return; strum.updateNotePosition(daNote); strum.updateClipRect(daNote); }); } if (!inCutscene) keyShit(); #if debug if (FlxG.keys.justPressed.ONE) endSong(); #end scripts.call("updatePost", [elapsed]); } function endSong():Void { scripts.call("onSongEnd"); canPause = false; FlxG.sound.music.volume = 0; vocals.volume = 0; if (SONG.validScore) { #if !switch Highscore.saveScore(SONG.song, songScore, storyDifficulty); #end } if (isStoryMode) { campaignScore += songScore; storyPlaylist.remove(storyPlaylist[0]); if (storyPlaylist.length <= 0) { FlxG.sound.playMusic(Paths.music('freakyMenu')); transIn = FlxTransitionableState.defaultTransIn; transOut = FlxTransitionableState.defaultTransOut; FlxG.switchState(new StoryMenuState()); // if () StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true; if (SONG.validScore) { Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty); } FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked; FlxG.save.flush(); } else { // TODO: NOT INT DIFFICULTIES var difficulty:String = CoolUtil.difficultyString(); trace('LOADING NEXT SONG'); trace(PlayState.storyPlaylist[0].toLowerCase() + difficulty); if (SONG.song.toLowerCase() == 'eggnog') { var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom, -FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); blackShit.scrollFactor.set(); add(blackShit); camHUD.visible = false; FlxG.sound.play(Paths.sound('Lights_Shut_off')); } FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; prevCamFollow = camFollow; PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), difficulty); FlxG.sound.music.stop(); FlxG.switchState(new PlayState()); } } else { trace('WENT BACK TO FREEPLAY??'); FlxG.switchState(new FreeplayState()); } } public var endingSong:Bool = false; private function popUpScore(strumtime:Float, score:Int = 350):Void { var noteDiff:Float = Math.abs(strumtime - Conductor.songPosition); vocals.volume = 1; var placement:String = Std.string(combo); var coolText:FlxText = new FlxText(0, 0, 0, placement, 32); coolText.screenCenter(); coolText.x = FlxG.width * 0.55; // var rating:FlxSprite = new FlxSprite(); var daRating:String = "sick"; if (noteDiff > Conductor.safeZoneOffset * 0.9) { daRating = 'shit'; score = 50; } else if (noteDiff > Conductor.safeZoneOffset * 0.75) { daRating = 'bad'; score = 100; } else if (noteDiff > Conductor.safeZoneOffset * 0.2) { daRating = 'good'; score = 200; } songScore += score; /* if (combo > 60) daRating = 'sick'; else if (combo > 12) daRating = 'good' else if (combo > 4) daRating = 'bad'; */ var pixelShitPart1:String = ""; var pixelShitPart2:String = ''; if (curStage.startsWith('school')) { pixelShitPart1 = 'weeb/pixelUI/'; pixelShitPart2 = '-pixel'; } rating.loadGraphic(Paths.image(pixelShitPart1 + daRating + pixelShitPart2)); rating.screenCenter(); rating.x = coolText.x - 40; rating.y -= 60; rating.acceleration.y = 550; rating.velocity.y -= FlxG.random.int(140, 175); rating.velocity.x -= FlxG.random.int(0, 10); var comboSpr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2)); comboSpr.screenCenter(); comboSpr.x = coolText.x; comboSpr.acceleration.y = 600; comboSpr.velocity.y -= 150; comboSpr.velocity.x += FlxG.random.int(1, 10); add(rating); if (!curStage.startsWith('school')) { rating.setGraphicSize(Std.int(rating.width * 0.7)); rating.antialiasing = true; comboSpr.setGraphicSize(Std.int(comboSpr.width * 0.7)); comboSpr.antialiasing = true; } else { rating.setGraphicSize(Std.int(rating.width * daPixelZoom * 0.7)); comboSpr.setGraphicSize(Std.int(comboSpr.width * daPixelZoom * 0.7)); } comboSpr.updateHitbox(); rating.updateHitbox(); var seperatedScore:Array = []; seperatedScore.push(Math.floor(combo / 100)); seperatedScore.push(Math.floor((combo - (seperatedScore[0] * 100)) / 10)); seperatedScore.push(combo % 10); var daLoop:Int = 0; for (i in seperatedScore) { var numScore:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2)); numScore.screenCenter(); numScore.x = coolText.x + (43 * daLoop) - 90; numScore.y += 80; if (!curStage.startsWith('school')) { numScore.antialiasing = true; numScore.setGraphicSize(Std.int(numScore.width * 0.5)); } else { numScore.setGraphicSize(Std.int(numScore.width * daPixelZoom)); } numScore.updateHitbox(); numScore.acceleration.y = FlxG.random.int(200, 300); numScore.velocity.y -= FlxG.random.int(140, 160); numScore.velocity.x = FlxG.random.float(-5, 5); if (combo >= 10 || combo == 0) add(numScore); FlxTween.tween(numScore, {alpha: 0}, 0.2, { onComplete: function(tween:FlxTween) { numScore.destroy(); }, startDelay: Conductor.crochet * 0.002 }); daLoop++; } /* trace(combo); trace(seperatedScore); */ coolText.text = Std.string(seperatedScore); // add(coolText); FlxTween.tween(rating, {alpha: 0}, 0.2, { startDelay: Conductor.crochet * 0.001 }); FlxTween.tween(comboSpr, {alpha: 0}, 0.2, { onComplete: function(tween:FlxTween) { coolText.destroy(); comboSpr.destroy(); rating.destroy(); }, startDelay: Conductor.crochet * 0.001 }); curSection += 1; } private function keyShit():Void { var pressed = [controls.NOTE_LEFT, controls.NOTE_DOWN, controls.NOTE_UP, controls.NOTE_RIGHT]; var justPressed = [controls.NOTE_LEFT_P, controls.NOTE_DOWN_P, controls.NOTE_UP_P, controls.NOTE_RIGHT_P]; var justReleased = [controls.NOTE_LEFT_R, controls.NOTE_DOWN_R, controls.NOTE_UP_R, controls.NOTE_RIGHT_R]; var event = scripts.event("onKeyShit", new InputSystemEvent(pressed, justPressed, justReleased)); if (event.cancelled) return; pressed = CoolUtil.getDefault(event.pressed, []); justPressed = CoolUtil.getDefault(event.justPressed, []); justReleased = CoolUtil.getDefault(event.justReleased, []); var funcsToExec:ArrayVoid> = []; if (pressed.contains(true)) { funcsToExec.push(function(note:Note) { if (pressed[note.strumID] && note.isSustainNote && note.canBeHit && note.mustPress && !note.wasGoodHit) { goodNoteHit(note); } }); } var notePerStrum = [for(i in 0...4) null]; var additionalNotes:Array = []; if (justPressed.contains(true)) { funcsToExec.push(function(note:Note) { if (justPressed[note.strumID] && !note.isSustainNote && note.mustPress && !note.wasGoodHit && note.canBeHit) { if (notePerStrum[note.strumID] == null) notePerStrum[note.strumID] = note; else if (Math.abs(notePerStrum[note.strumID].strumTime - note.strumTime) <= 10) additionalNotes.push(note); else if (note.strumTime < notePerStrum[note.strumID].strumTime) notePerStrum[note.strumID] = note; } }); } notes.forEachAlive(function(note:Note) { for(e in funcsToExec) e(note); }); for(e in notePerStrum) if (e != null) goodNoteHit(e); for(e in additionalNotes) goodNoteHit(e); playerStrums.forEach(function(str:Strum) { str.updatePlayerInput(pressed[str.ID], justPressed[str.ID], justReleased[str.ID]); }); scripts.call("onKeyShitPost"); } function noteMiss(note:Note):Void { if (!boyfriend.stunned) { var event:NoteHitEvent = scripts.event("onNoteMiss", new NoteHitEvent(note, boyfriend, true, note.noteType, note.strumID, -0.04, false, -10)); if (event.cancelled) return; health += event.healthGain; if (combo > 5 && gf.animOffsets.exists('sad')) { gf.playAnim('sad'); } combo = 0; songScore -= 10; misses++; FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); if (muteVocalsOnMiss) vocals.volume = 0; boyfriend.stunned = true; switch (note.strumID) { case 0: boyfriend.playAnim('singLEFTmiss', true); case 1: boyfriend.playAnim('singDOWNmiss', true); case 2: boyfriend.playAnim('singUPmiss', true); case 3: boyfriend.playAnim('singRIGHTmiss', true); } deleteNote(note); } } public function getNoteType(id:Int) { // TODO: Note Types return null; } function goodNoteHit(note:Note):Void { if (!note.wasGoodHit) { note.wasGoodHit = true; var event:NoteHitEvent; if (note.mustPress) event = scripts.event("onPlayerHit", new NoteHitEvent(note, boyfriend, true, note.noteType, note.strumID, note.noteData > 0 ? 0.023 : 0.004)); else event = scripts.event("onDadHit", new NoteHitEvent(note, dad, false, note.noteType, note.strumID, 0)); if (!event.cancelled) { if (event.player && !note.isSustainNote) { combo++; popUpScore(note.strumTime, event.score); } health += event.healthGain; if (!event.animCancelled) { event.character.playSingAnim(event.direction); } (event.player ? playerStrums : cpuStrums).forEach(function(str:Strum) { if (str.ID == Math.abs(note.strumID)) { str.press(note.strumTime); } }); } if (event.unmuteVocals) vocals.volume = 1; if (event.enableCamZooming) camZooming = true; if (event.deleteNote && !note.isSustainNote) deleteNote(note); } } public function deleteNote(note:Note) { var event:SimpleNoteEvent = scripts.event("onNoteDelete", new SimpleNoteEvent(note)); if (!event.cancelled) { scripts.call("onNoteDelete", [note]); note.kill(); notes.remove(note, true); note.destroy(); } } override function stepHit() { super.stepHit(); scripts.call("stepHit", [curStep]); } override function beatHit() { super.beatHit(); scripts.call("beatHit", [curBeat]); if (SONG.notes[Math.floor(curStep / 16)] != null && SONG.notes[Math.floor(curStep / 16)].changeBPM) Conductor.changeBPM(SONG.notes[Math.floor(curStep / 16)].bpm); if (camZoomingInterval < 1) camZoomingInterval = 1; if (camZooming && FlxG.camera.zoom < 1.35 && curBeat % camZoomingInterval == 0) { FlxG.camera.zoom += 0.015; camHUD.zoom += 0.03; } iconP1.scale.set(1.2, 1.2); iconP2.scale.set(1.2, 1.2); iconP1.updateHitbox(); iconP2.updateHitbox(); if (curBeat % gfSpeed == 0) { gf.dance(); } } public var curLight:Int = 0; }