1821 lines
45 KiB
Haxe
1821 lines
45 KiB
Haxe
package game.state;
|
|
|
|
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.group.FlxGroup;
|
|
import flixel.math.FlxAngle;
|
|
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 lime.utils.Assets;
|
|
import openfl.Lib;
|
|
import openfl.display.BitmapData;
|
|
import openfl.display.BlendMode;
|
|
import openfl.display.StageQuality;
|
|
import openfl.filters.ShaderFilter;
|
|
import game.data.backend.*;
|
|
import game.data.backend.Section.SwagSection;
|
|
import game.data.backend.Song.SwagSong;
|
|
import game.data.debug.AnimationDebug;
|
|
import game.objects.shaders.WiggleEffect;
|
|
import game.objects.shaders.WiggleEffect.WiggleEffectType;
|
|
import game.objects.shaders.ColorSwap;
|
|
import game.objects.cutscene.FlxVideo;
|
|
import game.objects.stages.GameStage;
|
|
import game.objects.*;
|
|
import game.objects.stages.background.*;
|
|
import game.ui.gameplay.*;
|
|
import game.ui.gameplay.note.*;
|
|
import game.state.charting.*;
|
|
import game.state.subState.GameOverSubstate;
|
|
import game.state.subState.PauseSubState;
|
|
import game.state.menus.FreeplayState;
|
|
import game.state.menus.StoryMenuState;
|
|
import game.state.menus.options.PreferencesMenu;
|
|
|
|
using StringTools;
|
|
|
|
#if discord_rpc
|
|
import game.data.backend.Discord.DiscordClient;
|
|
#end
|
|
|
|
class PlayState extends MusicBeatState
|
|
{
|
|
// Organized the vars to the best of my abilites
|
|
// Hope it suffices
|
|
// Song / Gameplay Variables
|
|
public static var SONG:SwagSong;
|
|
|
|
private var vocals:FlxSound;
|
|
private var generatedMusic:Bool = false;
|
|
private var startingSong:Bool = false;
|
|
private var vocalsFinished:Bool = false;
|
|
private var curSong:String = "";
|
|
private var gfSpeed:Int = 1;
|
|
private var combo:Int = 0;
|
|
|
|
public static var health:Float = 1;
|
|
public static var songScore:Int = 0;
|
|
public static var songMisses:Int = 0;
|
|
public static var campaignScore:Int = 0;
|
|
public static var isStoryMode:Bool = false;
|
|
public static var storyWeek:Int = 0;
|
|
public static var storyPlaylist:Array<String> = [];
|
|
public static var storyDifficulty:Int = 1;
|
|
public static var deathCounter:Int = 0;
|
|
public static var practiceMode:Bool = false;
|
|
|
|
///////////// Stage Variables ///////////
|
|
var gameStage:GameStage;
|
|
|
|
// Week 6
|
|
public static var daPixelZoom:Float = 6;
|
|
// Other
|
|
public static var curStage:String = '';
|
|
public static var isPixel:Bool = false;
|
|
|
|
///////////////////////////////////////
|
|
// Character Stuff
|
|
private var dad:Character;
|
|
private var gf:Character;
|
|
private var boyfriend:Boyfriend;
|
|
private var singAnims:Array<String> = ['singLEFT', 'singDOWN', 'singUP', 'singRIGHT'];
|
|
|
|
// Note Stuff
|
|
private var notes:FlxTypedGroup<Note>;
|
|
private var unspawnNotes:Array<Note> = [];
|
|
private var strumLineNotes:FlxTypedGroup<StaticNote>;
|
|
private var playerStrums:FlxTypedGroup<StaticNote>;
|
|
private var oppStrums:FlxTypedGroup<StaticNote>;
|
|
private var strumLine:FlxSprite;
|
|
var grpNoteSplashes:FlxTypedGroup<NoteSplash>;
|
|
|
|
// UI Stuff
|
|
var UI:GameUI;
|
|
|
|
// Camera Stuff
|
|
private var camGame:FlxCamera;
|
|
private var camHUD:FlxCamera;
|
|
private var camOther:FlxCamera;
|
|
private var camFollow:FlxObject;
|
|
private var camZooming:Bool = false;
|
|
|
|
private static var prevCamFollow:FlxObject;
|
|
public static var defaultCamZoom:Float = 1.05;
|
|
|
|
var camPos:FlxPoint;
|
|
|
|
// Cutscene / Dialogue Stuff
|
|
var dialogue:Array<String> = ['blah blah blah', 'coolswag'];
|
|
var talking:Bool = true;
|
|
|
|
public static var inCutscene:Bool = false;
|
|
public static var seenCutscene:Bool = false;
|
|
|
|
var gfCutsceneLayer:FlxGroup;
|
|
var bfTankCutsceneLayer:FlxGroup;
|
|
|
|
// Shaders
|
|
var wiggleShit:WiggleEffect = new WiggleEffect();
|
|
|
|
// Discord RPC variables
|
|
#if discord_rpc
|
|
var storyDifficultyText:String = "";
|
|
var iconRPC:String = "";
|
|
var songLength:Float = 0;
|
|
var detailsText:String = "";
|
|
var detailsPausedText:String = "";
|
|
#end
|
|
|
|
override public function create()
|
|
{
|
|
songScore = 0;
|
|
songMisses = 0;
|
|
health = 1;
|
|
|
|
if (FlxG.sound.music != null)
|
|
FlxG.sound.music.stop();
|
|
|
|
FlxG.sound.cache(Paths.inst(PlayState.SONG.song));
|
|
FlxG.sound.cache(Paths.voices(PlayState.SONG.song));
|
|
|
|
// var gameCam:FlxCamera = FlxG.camera;
|
|
camGame = new SwagCamera();
|
|
camHUD = new FlxCamera();
|
|
camOther = new FlxCamera();
|
|
camHUD.bgColor.alpha = 0;
|
|
camOther.bgColor.alpha = 0;
|
|
|
|
FlxG.cameras.reset(camGame);
|
|
FlxG.cameras.add(camHUD, false);
|
|
FlxG.cameras.add(camOther, false);
|
|
|
|
persistentUpdate = true;
|
|
persistentDraw = true;
|
|
|
|
if (SONG == null)
|
|
SONG = Song.loadFromJson('tutorial');
|
|
|
|
Conductor.mapBPMChanges(SONG);
|
|
Conductor.changeBPM(SONG.bpm);
|
|
|
|
switch (SONG.song.toLowerCase())
|
|
{
|
|
case 'senpai':
|
|
dialogue = CoolUtil.coolTextFile(Paths.txt('senpai/senpaiDialogue'));
|
|
case 'roses':
|
|
dialogue = CoolUtil.coolTextFile(Paths.txt('roses/rosesDialogue'));
|
|
case 'thorns':
|
|
dialogue = CoolUtil.coolTextFile(Paths.txt('thorns/thornsDialogue'));
|
|
}
|
|
|
|
#if discord_rpc
|
|
initDiscord();
|
|
#end
|
|
|
|
isPixel = false;
|
|
|
|
// Add the stage
|
|
gameStage = new GameStage();
|
|
add(gameStage);
|
|
|
|
gf = new Character(400, 130, gameStage.getGF());
|
|
gf.scrollFactor.set(0.95, 0.95);
|
|
|
|
switch (gameStage.getGF())
|
|
{
|
|
case 'pico-speaker':
|
|
gf.x -= 50;
|
|
gf.y -= 200;
|
|
|
|
var tankmanRun = gameStage.tankmanRun;
|
|
var tempTankman:TankmenBG = new TankmenBG(20, 500, true);
|
|
tempTankman.strumTime = 10;
|
|
tempTankman.resetShit(20, 600, true);
|
|
tankmanRun.add(tempTankman);
|
|
|
|
for (i in 0...TankmenBG.animationNotes.length)
|
|
{
|
|
if (FlxG.random.bool(16))
|
|
{
|
|
var tankman:TankmenBG = tankmanRun.recycle(TankmenBG);
|
|
// new TankmenBG(500, 200 + FlxG.random.int(50, 100), TankmenBG.animationNotes[i][1] < 2);
|
|
tankman.strumTime = TankmenBG.animationNotes[i][0];
|
|
tankman.resetShit(500, 200 + FlxG.random.int(50, 100), TankmenBG.animationNotes[i][1] < 2);
|
|
tankmanRun.add(tankman);
|
|
}
|
|
}
|
|
}
|
|
|
|
dad = new Character(100, 100, SONG.player2);
|
|
|
|
camPos = new FlxPoint(dad.getGraphicMidpoint().x, dad.getGraphicMidpoint().y);
|
|
|
|
switch (SONG.player2)
|
|
{
|
|
case 'gf':
|
|
dad.setPosition(gf.x, gf.y);
|
|
gf.visible = false;
|
|
if (isStoryMode)
|
|
{
|
|
camPos.x += 600;
|
|
tweenCamIn();
|
|
}
|
|
case "spooky":
|
|
dad.y += 200;
|
|
case "monster":
|
|
dad.y += 100;
|
|
case 'monster-christmas':
|
|
dad.y += 130;
|
|
case 'dad':
|
|
camPos.x += 400;
|
|
case 'pico':
|
|
camPos.x += 600;
|
|
dad.y += 300;
|
|
case 'parents-christmas':
|
|
dad.x -= 500;
|
|
case 'senpai' | 'senpai-angry':
|
|
dad.x += 150;
|
|
dad.y += 360;
|
|
camPos.set(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
|
case 'spirit':
|
|
dad.x -= 150;
|
|
dad.y += 100;
|
|
camPos.set(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
|
case 'tankman':
|
|
dad.y += 180;
|
|
}
|
|
|
|
boyfriend = new Boyfriend(770, 450, SONG.player1);
|
|
|
|
gameStage.positionChars(boyfriend, dad, gf);
|
|
|
|
add(gf);
|
|
|
|
gfCutsceneLayer = new FlxGroup();
|
|
add(gfCutsceneLayer);
|
|
|
|
bfTankCutsceneLayer = new FlxGroup();
|
|
add(bfTankCutsceneLayer);
|
|
|
|
// Shitty layering but whatev it works LOL
|
|
if (gameStage.getStage() == 'limo')
|
|
add(gameStage.limo);
|
|
|
|
add(dad);
|
|
add(boyfriend);
|
|
|
|
if (gameStage.getStage() == 'tank')
|
|
add(gameStage.foregroundSprites);
|
|
|
|
add(gameStage.fgSprites);
|
|
|
|
var doof:DialogueBox = new DialogueBox(false, dialogue);
|
|
// doof.x += 70;
|
|
// doof.y = FlxG.height * 0.5;
|
|
doof.scrollFactor.set();
|
|
doof.finishThing = startCountdown;
|
|
|
|
Conductor.songPosition = -5000;
|
|
|
|
strumLine = new FlxSprite(0, 50).makeGraphic(FlxG.width, 10);
|
|
|
|
if (PreferencesMenu.getPref('downscroll'))
|
|
strumLine.y = FlxG.height - 150; // 150 just random ass number lol
|
|
|
|
strumLine.scrollFactor.set();
|
|
|
|
strumLineNotes = new FlxTypedGroup<StaticNote>();
|
|
add(strumLineNotes);
|
|
|
|
// fake notesplash cache type deal so that it loads in the graphic?
|
|
|
|
grpNoteSplashes = new FlxTypedGroup<NoteSplash>();
|
|
|
|
var noteSplash:NoteSplash = new NoteSplash(100, 100, 0);
|
|
grpNoteSplashes.add(noteSplash);
|
|
noteSplash.alpha = 0.1;
|
|
|
|
add(grpNoteSplashes);
|
|
|
|
playerStrums = new FlxTypedGroup<StaticNote>();
|
|
oppStrums = new FlxTypedGroup<StaticNote>();
|
|
|
|
generateSong();
|
|
|
|
// Add the games ui
|
|
// Also adding this after the song generates so that the notes don't overlap the ui
|
|
UI = new GameUI();
|
|
add(UI);
|
|
|
|
// add(strumLine);
|
|
|
|
camFollow = new FlxObject(0, 0, 1, 1);
|
|
|
|
camFollow.setPosition(camPos.x, camPos.y);
|
|
|
|
if (prevCamFollow != null)
|
|
{
|
|
camFollow = prevCamFollow;
|
|
prevCamFollow = null;
|
|
}
|
|
|
|
add(camFollow);
|
|
|
|
FlxG.camera.follow(camFollow, LOCKON, 0.04);
|
|
// FlxG.camera.setScrollBounds(0, FlxG.width, 0, FlxG.height);
|
|
FlxG.camera.zoom = defaultCamZoom;
|
|
FlxG.camera.focusOn(camFollow.getPosition());
|
|
|
|
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
|
|
|
|
FlxG.fixedTimestep = false;
|
|
|
|
grpNoteSplashes.cameras = [camHUD];
|
|
strumLineNotes.cameras = [camHUD];
|
|
notes.cameras = [camHUD];
|
|
UI.cameras = [camHUD];
|
|
doof.cameras = [camHUD];
|
|
|
|
// if (SONG.song == 'South')
|
|
// FlxG.camera.alpha = 0.7;
|
|
// UI_camera.zoom = 1;
|
|
|
|
// cameras = [FlxG.cameras.list[1]];
|
|
startingSong = true;
|
|
|
|
if (isStoryMode && !seenCutscene)
|
|
{
|
|
seenCutscene = true;
|
|
|
|
switch (curSong.toLowerCase())
|
|
{
|
|
case "winter-horrorland":
|
|
var blackScreen:FlxSprite = new FlxSprite(0, 0).makeGraphic(Std.int(FlxG.width * 2), Std.int(FlxG.height * 2), FlxColor.BLACK);
|
|
add(blackScreen);
|
|
blackScreen.scrollFactor.set();
|
|
camHUD.visible = false;
|
|
|
|
new FlxTimer().start(0.1, function(tmr:FlxTimer)
|
|
{
|
|
remove(blackScreen);
|
|
FlxG.sound.play(Paths.sound('Lights_Turn_On'));
|
|
camFollow.y = -2050;
|
|
camFollow.x += 200;
|
|
FlxG.camera.focusOn(camFollow.getPosition());
|
|
FlxG.camera.zoom = 1.5;
|
|
|
|
new FlxTimer().start(0.8, function(tmr:FlxTimer)
|
|
{
|
|
camHUD.visible = true;
|
|
remove(blackScreen);
|
|
FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, 2.5, {
|
|
ease: FlxEase.quadInOut,
|
|
onComplete: function(twn:FlxTween)
|
|
{
|
|
startCountdown();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
case 'senpai' | 'roses' | 'thorns':
|
|
schoolIntro(doof);
|
|
case 'ugh':
|
|
ughIntro();
|
|
case 'stress':
|
|
stressIntro();
|
|
case 'guns':
|
|
gunsIntro();
|
|
|
|
default:
|
|
startCountdown();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (curSong.toLowerCase())
|
|
{
|
|
default:
|
|
startCountdown();
|
|
}
|
|
}
|
|
|
|
super.create();
|
|
}
|
|
|
|
function ughIntro()
|
|
{
|
|
inCutscene = true;
|
|
|
|
var blackShit:FlxSprite = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
|
blackShit.scrollFactor.set();
|
|
add(blackShit);
|
|
|
|
var vid:FlxVideo = new FlxVideo('music/ughCutscene.mp4');
|
|
vid.finishCallback = function()
|
|
{
|
|
remove(blackShit);
|
|
FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
|
startCountdown();
|
|
cameraMovement();
|
|
};
|
|
|
|
FlxG.camera.zoom = defaultCamZoom * 1.2;
|
|
|
|
camFollow.x += 100;
|
|
camFollow.y += 100;
|
|
}
|
|
|
|
function gunsIntro()
|
|
{
|
|
inCutscene = true;
|
|
|
|
var blackShit:FlxSprite = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
|
blackShit.scrollFactor.set();
|
|
add(blackShit);
|
|
|
|
var vid:FlxVideo = new FlxVideo('music/gunsCutscene.mp4');
|
|
vid.finishCallback = function()
|
|
{
|
|
remove(blackShit);
|
|
|
|
FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
|
startCountdown();
|
|
cameraMovement();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* [
|
|
* [0, function(){blah;}],
|
|
* [4.6, function(){blah;}],
|
|
* [25.1, function(){blah;}],
|
|
* [30.7, function(){blah;}]
|
|
* ]
|
|
* SOMETHING LIKE THIS
|
|
*/
|
|
// var cutsceneFunctions:Array<Dynamic> = [];
|
|
|
|
function stressIntro()
|
|
{
|
|
inCutscene = true;
|
|
|
|
var blackShit:FlxSprite = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
|
blackShit.scrollFactor.set();
|
|
add(blackShit);
|
|
|
|
var vid:FlxVideo = new FlxVideo('music/stressCutscene.mp4');
|
|
vid.finishCallback = function()
|
|
{
|
|
remove(blackShit);
|
|
|
|
FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
|
startCountdown();
|
|
cameraMovement();
|
|
};
|
|
}
|
|
|
|
function initDiscord():Void
|
|
{
|
|
#if discord_rpc
|
|
storyDifficultyText = difficultyString();
|
|
iconRPC = SONG.player2;
|
|
|
|
// To avoid having duplicate images in Discord assets
|
|
switch (iconRPC)
|
|
{
|
|
case 'senpai-angry':
|
|
iconRPC = 'senpai';
|
|
case 'monster-christmas':
|
|
iconRPC = 'monster';
|
|
case 'mom-car':
|
|
iconRPC = 'mom';
|
|
}
|
|
|
|
// String that contains the mode defined here so it isn't necessary to call changePresence for each mode
|
|
detailsText = isStoryMode ? "Story Mode: Week " + storyWeek : "Freeplay";
|
|
detailsPausedText = "Paused - " + detailsText;
|
|
|
|
// Updating Discord Rich Presence.
|
|
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
|
|
#end
|
|
}
|
|
|
|
function schoolIntro(?dialogueBox:DialogueBox):Void
|
|
{
|
|
var black:FlxSprite = new FlxSprite(-100, -100).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
|
black.scrollFactor.set();
|
|
add(black);
|
|
|
|
var red:FlxSprite = new FlxSprite(-100, -100).makeGraphic(FlxG.width * 2, FlxG.height * 2, 0xFFff1b31);
|
|
red.scrollFactor.set();
|
|
|
|
var senpaiEvil:FlxSprite = new FlxSprite();
|
|
senpaiEvil.frames = Paths.getSparrowAtlas('weeb/senpaiCrazy');
|
|
senpaiEvil.animation.addByPrefix('idle', 'Senpai Pre Explosion', 24, false);
|
|
senpaiEvil.setGraphicSize(Std.int(senpaiEvil.width * daPixelZoom));
|
|
senpaiEvil.scrollFactor.set();
|
|
senpaiEvil.updateHitbox();
|
|
senpaiEvil.screenCenter();
|
|
senpaiEvil.x += senpaiEvil.width / 5;
|
|
|
|
camFollow.setPosition(camPos.x, camPos.y);
|
|
|
|
if (SONG.song.toLowerCase() == 'roses' || SONG.song.toLowerCase() == 'thorns')
|
|
{
|
|
remove(black);
|
|
|
|
if (SONG.song.toLowerCase() == 'thorns')
|
|
{
|
|
add(red);
|
|
camHUD.visible = false;
|
|
}
|
|
else
|
|
FlxG.sound.play(Paths.sound('ANGRY'));
|
|
// moved senpai angry noise in here to clean up cutscene switch case lol
|
|
}
|
|
|
|
new FlxTimer().start(0.3, function(tmr:FlxTimer)
|
|
{
|
|
black.alpha -= 0.15;
|
|
|
|
if (black.alpha > 0)
|
|
tmr.reset(0.3);
|
|
else
|
|
{
|
|
if (dialogueBox != null)
|
|
{
|
|
inCutscene = true;
|
|
|
|
if (SONG.song.toLowerCase() == 'thorns')
|
|
{
|
|
add(senpaiEvil);
|
|
senpaiEvil.alpha = 0;
|
|
new FlxTimer().start(0.3, function(swagTimer:FlxTimer)
|
|
{
|
|
senpaiEvil.alpha += 0.15;
|
|
if (senpaiEvil.alpha < 1)
|
|
swagTimer.reset();
|
|
else
|
|
{
|
|
senpaiEvil.animation.play('idle');
|
|
FlxG.sound.play(Paths.sound('Senpai_Dies'), 1, false, null, true, function()
|
|
{
|
|
remove(senpaiEvil);
|
|
remove(red);
|
|
FlxG.camera.fade(FlxColor.WHITE, 0.01, true, function()
|
|
{
|
|
add(dialogueBox);
|
|
camHUD.visible = true;
|
|
}, true);
|
|
});
|
|
new FlxTimer().start(3.2, function(deadTime:FlxTimer)
|
|
{
|
|
FlxG.camera.fade(FlxColor.WHITE, 1.6, false);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
else
|
|
add(dialogueBox);
|
|
}
|
|
else
|
|
startCountdown();
|
|
|
|
remove(black);
|
|
}
|
|
});
|
|
}
|
|
|
|
var startTimer:FlxTimer = new FlxTimer();
|
|
var perfectMode:Bool = false;
|
|
|
|
function startCountdown():Void
|
|
{
|
|
inCutscene = false;
|
|
camHUD.visible = true;
|
|
|
|
generateStaticArrows(0);
|
|
generateStaticArrows(1);
|
|
|
|
talking = false;
|
|
startedCountdown = true;
|
|
Conductor.songPosition = 0;
|
|
Conductor.songPosition -= Conductor.crochet * 5;
|
|
|
|
var swagCounter:Int = 0;
|
|
|
|
startTimer.start(Conductor.crochet / 1000, function(tmr:FlxTimer)
|
|
{
|
|
// this just based on beatHit stuff but compact
|
|
if (swagCounter % gfSpeed == 0)
|
|
gf.dance();
|
|
if (swagCounter % 2 == 0)
|
|
{
|
|
if (!boyfriend.animation.curAnim.name.startsWith("sing"))
|
|
boyfriend.playAnim('idle');
|
|
if (!dad.animation.curAnim.name.startsWith("sing"))
|
|
dad.dance();
|
|
}
|
|
else if (dad.curCharacter == 'spooky' && !dad.animation.curAnim.name.startsWith("sing"))
|
|
dad.dance();
|
|
if (generatedMusic)
|
|
notes.sort(sortNotes, FlxSort.DESCENDING);
|
|
|
|
var introSprPaths:Array<String> = ["ready", "set", "go"];
|
|
var altSuffix:String = "";
|
|
|
|
if (isPixel)
|
|
{
|
|
altSuffix = '-pixel';
|
|
introSprPaths = ['weeb/pixelUI/ready-pixel', 'weeb/pixelUI/set-pixel', 'weeb/pixelUI/date-pixel'];
|
|
}
|
|
|
|
var introSndPaths:Array<String> = [
|
|
"intro3" + altSuffix, "intro2" + altSuffix,
|
|
"intro1" + altSuffix, "introGo" + altSuffix
|
|
];
|
|
|
|
if (swagCounter > 0)
|
|
readySetGo(introSprPaths[swagCounter - 1]);
|
|
FlxG.sound.play(Paths.sound(introSndPaths[swagCounter]), 0.6);
|
|
|
|
swagCounter += 1;
|
|
}, 4);
|
|
}
|
|
|
|
function readySetGo(path:String):Void
|
|
{
|
|
var spr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(path));
|
|
spr.scrollFactor.set();
|
|
|
|
if (isPixel)
|
|
spr.setGraphicSize(Std.int(spr.width * daPixelZoom));
|
|
|
|
spr.updateHitbox();
|
|
spr.screenCenter();
|
|
add(spr);
|
|
FlxTween.tween(spr, {y: spr.y += 100, alpha: 0}, Conductor.crochet / 1000, {
|
|
ease: FlxEase.cubeInOut,
|
|
onComplete: function(twn:FlxTween)
|
|
{
|
|
spr.destroy();
|
|
}
|
|
});
|
|
}
|
|
|
|
var previousFrameTime:Int = 0;
|
|
var songTime:Float = 0;
|
|
|
|
function startSong():Void
|
|
{
|
|
startingSong = false;
|
|
|
|
previousFrameTime = FlxG.game.ticks;
|
|
|
|
if (!paused)
|
|
FlxG.sound.playMusic(Paths.inst(SONG.song), 1, false);
|
|
FlxG.sound.music.onComplete = endSong;
|
|
vocals.play();
|
|
|
|
#if discord_rpc
|
|
// 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
|
|
}
|
|
|
|
private function generateSong():Void
|
|
{
|
|
// FlxG.log.add(ChartParser.parse());
|
|
|
|
var songData = SONG;
|
|
Conductor.changeBPM(songData.bpm);
|
|
|
|
curSong = songData.song;
|
|
|
|
if (SONG.needsVoices)
|
|
vocals = new FlxSound().loadEmbedded(Paths.voices(SONG.song));
|
|
else
|
|
vocals = new FlxSound();
|
|
|
|
vocals.onComplete = function()
|
|
{
|
|
vocalsFinished = true;
|
|
};
|
|
FlxG.sound.list.add(vocals);
|
|
|
|
notes = new FlxTypedGroup<Note>();
|
|
add(notes);
|
|
|
|
var noteData:Array<SwagSection>;
|
|
|
|
// NEW SHIT
|
|
noteData = songData.notes;
|
|
|
|
for (section in noteData)
|
|
{
|
|
for (songNotes in section.sectionNotes)
|
|
{
|
|
var daStrumTime:Float = songNotes[0];
|
|
var daNoteData:Int = Std.int(songNotes[1] % 4);
|
|
|
|
var gottaHitNote:Bool = section.mustHitSection;
|
|
|
|
if (songNotes[1] > 3)
|
|
gottaHitNote = !section.mustHitSection;
|
|
|
|
var oldNote:Note;
|
|
if (unspawnNotes.length > 0)
|
|
oldNote = unspawnNotes[Std.int(unspawnNotes.length - 1)];
|
|
else
|
|
oldNote = null;
|
|
|
|
var swagNote:Note = new Note(daStrumTime, daNoteData, oldNote);
|
|
swagNote.sustainLength = songNotes[2];
|
|
swagNote.altNote = songNotes[3];
|
|
swagNote.scrollFactor.set(0, 0);
|
|
|
|
var susLength:Float = swagNote.sustainLength;
|
|
|
|
susLength = susLength / Conductor.stepCrochet;
|
|
unspawnNotes.push(swagNote);
|
|
|
|
for (susNote in 0...Math.floor(susLength))
|
|
{
|
|
oldNote = unspawnNotes[Std.int(unspawnNotes.length - 1)];
|
|
|
|
var sustainNote:Note = new Note(daStrumTime + (Conductor.stepCrochet * susNote) + Conductor.stepCrochet, daNoteData, oldNote, true);
|
|
sustainNote.scrollFactor.set();
|
|
unspawnNotes.push(sustainNote);
|
|
|
|
sustainNote.mustPress = gottaHitNote;
|
|
|
|
if (sustainNote.mustPress)
|
|
sustainNote.x += FlxG.width / 2; // general offset
|
|
}
|
|
|
|
swagNote.mustPress = gottaHitNote;
|
|
|
|
if (swagNote.mustPress)
|
|
swagNote.x += FlxG.width / 2; // general offset
|
|
}
|
|
}
|
|
|
|
unspawnNotes.sort(sortByShit);
|
|
|
|
generatedMusic = true;
|
|
}
|
|
|
|
// Now you are probably wondering why I made 2 of these very similar functions
|
|
// sortByShit(), and sortNotes(). sortNotes is meant to be used by both sortByShit(), and the notes FlxGroup
|
|
// sortByShit() is meant to be used only by the unspawnNotes array.
|
|
// and the array sorting function doesnt need that order variable thingie
|
|
// this is good enough for now lololol HERE IS COMMENT FOR THIS SORTA DUMB DECISION LOL
|
|
function sortByShit(Obj1:Note, Obj2:Note):Int
|
|
{
|
|
return sortNotes(FlxSort.ASCENDING, Obj1, Obj2);
|
|
}
|
|
|
|
function sortNotes(order:Int = FlxSort.ASCENDING, Obj1:Note, Obj2:Note)
|
|
{
|
|
return FlxSort.byValues(order, Obj1.strumTime, Obj2.strumTime);
|
|
}
|
|
|
|
// ^ These two sorts also look cute together ^
|
|
|
|
private function generateStaticArrows(player:Int):Void
|
|
{
|
|
for (i in 0...4)
|
|
{
|
|
// FlxG.log.add(i);
|
|
var babyArrow:StaticNote = new StaticNote(48.5, strumLine.y, i, player);
|
|
var colorswap:ColorSwap = new ColorSwap();
|
|
babyArrow.shader = colorswap.shader;
|
|
colorswap.update(Note.arrowColors[i]);
|
|
|
|
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);
|
|
if (player == 2)
|
|
oppStrums.add(babyArrow);
|
|
|
|
babyArrow.playStrumAnim('static');
|
|
babyArrow.x += 50;
|
|
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)
|
|
{
|
|
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()
|
|
{
|
|
if (paused)
|
|
{
|
|
if (FlxG.sound.music != null && !startingSong)
|
|
resyncVocals();
|
|
|
|
if (!startTimer.finished)
|
|
startTimer.active = true;
|
|
paused = false;
|
|
|
|
#if discord_rpc
|
|
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();
|
|
}
|
|
|
|
#if discord_rpc
|
|
override public function onFocus():Void
|
|
{
|
|
if (health > 0 && !paused && FlxG.autoPause)
|
|
{
|
|
if (Conductor.songPosition > 0.0)
|
|
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength - Conductor.songPosition);
|
|
else
|
|
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
|
|
}
|
|
|
|
super.onFocus();
|
|
}
|
|
|
|
override public function onFocusLost():Void
|
|
{
|
|
if (health > 0 && !paused && FlxG.autoPause)
|
|
DiscordClient.changePresence(detailsPausedText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
|
|
|
|
super.onFocusLost();
|
|
}
|
|
#end
|
|
|
|
function resyncVocals():Void
|
|
{
|
|
if (_exiting)
|
|
return;
|
|
|
|
vocals.pause();
|
|
FlxG.sound.music.play();
|
|
Conductor.songPosition = FlxG.sound.music.time + Conductor.offset;
|
|
|
|
if (vocalsFinished)
|
|
return;
|
|
|
|
vocals.time = Conductor.songPosition;
|
|
vocals.play();
|
|
}
|
|
|
|
private var paused:Bool = false;
|
|
var startedCountdown:Bool = false;
|
|
var canPause:Bool = true;
|
|
|
|
override public function update(elapsed:Float)
|
|
{
|
|
// makes the lerp non-dependant on the framerate
|
|
// FlxG.camera.followLerp = CoolUtil.camLerpShit(0.04);
|
|
|
|
#if !debug
|
|
perfectMode = false;
|
|
#end
|
|
|
|
// Update The Stages
|
|
gameStage.updateStage(elapsed, boyfriend, dad, gf);
|
|
|
|
// do this BEFORE super.update() so songPosition is accurate
|
|
if (startingSong)
|
|
{
|
|
if (startedCountdown)
|
|
{
|
|
Conductor.songPosition += FlxG.elapsed * 1000;
|
|
if (Conductor.songPosition >= 0)
|
|
startSong();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Conductor.songPosition = FlxG.sound.music.time + Conductor.offset; // 20 is THE MILLISECONDS??
|
|
Conductor.songPosition += FlxG.elapsed * 1000;
|
|
|
|
if (!paused)
|
|
{
|
|
songTime += FlxG.game.ticks - previousFrameTime;
|
|
previousFrameTime = FlxG.game.ticks;
|
|
|
|
// Interpolation type beat
|
|
if (Conductor.lastSongPos != Conductor.songPosition)
|
|
{
|
|
songTime = (songTime + Conductor.songPosition) / 2;
|
|
Conductor.lastSongPos = Conductor.songPosition;
|
|
// Conductor.songPosition += FlxG.elapsed * 1000;
|
|
// trace('MISSED FRAME');
|
|
}
|
|
}
|
|
// Conductor.lastSongPos = FlxG.sound.music.time;
|
|
}
|
|
|
|
super.update(elapsed);
|
|
|
|
if (controls.PAUSE && startedCountdown && canPause)
|
|
{
|
|
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
|
|
{
|
|
var boyfriendPos = boyfriend.getScreenPosition();
|
|
var pauseSubState = new PauseSubState(boyfriendPos.x, boyfriendPos.y);
|
|
openSubState(pauseSubState);
|
|
pauseSubState.camera = camHUD;
|
|
boyfriendPos.put();
|
|
}
|
|
|
|
#if discord_rpc
|
|
DiscordClient.changePresence(detailsPausedText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
|
|
#end
|
|
}
|
|
|
|
if (FlxG.keys.justPressed.SEVEN)
|
|
{
|
|
FlxG.switchState(new ChartingState());
|
|
|
|
#if discord_rpc
|
|
DiscordClient.changePresence("Chart Editor", null, null, true);
|
|
#end
|
|
}
|
|
|
|
#if debug
|
|
if (FlxG.keys.justPressed.ONE)
|
|
endSong();
|
|
if (FlxG.keys.justPressed.EIGHT)
|
|
{
|
|
/* 8 for opponent char
|
|
SHIFT+8 for player char
|
|
CTRL+SHIFT+8 for gf */
|
|
if (FlxG.keys.pressed.SHIFT)
|
|
if (FlxG.keys.pressed.CONTROL)
|
|
FlxG.switchState(new AnimationDebug(gf.curCharacter));
|
|
else
|
|
FlxG.switchState(new AnimationDebug(SONG.player1));
|
|
else
|
|
FlxG.switchState(new AnimationDebug(SONG.player2));
|
|
}
|
|
if (FlxG.keys.justPressed.PAGEUP)
|
|
changeSection(1);
|
|
if (FlxG.keys.justPressed.PAGEDOWN)
|
|
changeSection(-1);
|
|
#end
|
|
|
|
if (generatedMusic && SONG.notes[Std.int(curStep / 16)] != null)
|
|
{
|
|
cameraRightSide = SONG.notes[Std.int(curStep / 16)].mustHitSection;
|
|
|
|
cameraMovement();
|
|
}
|
|
|
|
if (camZooming)
|
|
{
|
|
// New Cam Zooming
|
|
FlxG.camera.zoom = FlxMath.lerp(defaultCamZoom, FlxG.camera.zoom, CoolUtil.boundTo(1 - (elapsed * 4), 0, 1));
|
|
camHUD.zoom = FlxMath.lerp(1, camHUD.zoom, CoolUtil.boundTo(1 - (elapsed * 4), 0, 1));
|
|
}
|
|
|
|
if (curSong == 'Fresh')
|
|
{
|
|
switch (curBeat)
|
|
{
|
|
case 16:
|
|
camZooming = true;
|
|
gfSpeed = 2;
|
|
case 48:
|
|
gfSpeed = 1;
|
|
case 80:
|
|
gfSpeed = 2;
|
|
case 112:
|
|
gfSpeed = 1;
|
|
}
|
|
}
|
|
|
|
if (curSong == 'Bopeebo')
|
|
{
|
|
switch (curBeat)
|
|
{
|
|
case 128, 129, 130:
|
|
vocals.volume = 0;
|
|
}
|
|
}
|
|
// better streaming of shit
|
|
|
|
if (!inCutscene && !_exiting)
|
|
{
|
|
// RESET = Quick Game Over Screen
|
|
if (controls.RESET)
|
|
{
|
|
health = 0;
|
|
trace("RESET = True");
|
|
}
|
|
|
|
#if CAN_CHEAT // brandon's a pussy
|
|
if (controls.CHEAT)
|
|
{
|
|
health += 1;
|
|
trace("User is cheating!");
|
|
}
|
|
#end
|
|
|
|
if (health <= 0 && !practiceMode)
|
|
{
|
|
// boyfriend.stunned = true;
|
|
|
|
persistentUpdate = false;
|
|
persistentDraw = false;
|
|
paused = true;
|
|
|
|
vocals.stop();
|
|
FlxG.sound.music.stop();
|
|
|
|
// unloadAssets();
|
|
|
|
deathCounter += 1;
|
|
|
|
openSubState(new GameOverSubstate(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y));
|
|
|
|
// FlxG.switchState(new GameOverState(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y));
|
|
|
|
#if discord_rpc
|
|
// 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 < 1800 / SONG.speed)
|
|
{
|
|
var dunceNote:Note = unspawnNotes[0];
|
|
notes.add(dunceNote);
|
|
|
|
var index:Int = unspawnNotes.indexOf(dunceNote);
|
|
unspawnNotes.shift();
|
|
}
|
|
|
|
if (generatedMusic)
|
|
{
|
|
notes.forEachAlive(function(daNote:Note)
|
|
{
|
|
if ((PreferencesMenu.getPref('downscroll') && daNote.y < -daNote.height)
|
|
|| (!PreferencesMenu.getPref('downscroll') && daNote.y > FlxG.height))
|
|
{
|
|
daNote.active = false;
|
|
daNote.visible = false;
|
|
}
|
|
else
|
|
{
|
|
daNote.visible = true;
|
|
daNote.active = true;
|
|
}
|
|
|
|
var strumLineMid = strumLine.y + Note.swagWidth / 2;
|
|
|
|
if (PreferencesMenu.getPref('downscroll'))
|
|
{
|
|
daNote.y = (strumLine.y + (Conductor.songPosition - daNote.strumTime) * (0.45 * FlxMath.roundDecimal(SONG.speed, 2)));
|
|
|
|
if (daNote.isSustainNote)
|
|
{
|
|
if (daNote.animation.curAnim.name.endsWith("end") && daNote.prevNote != null)
|
|
daNote.y += daNote.prevNote.height;
|
|
else
|
|
daNote.y += daNote.height / 2;
|
|
|
|
if ((!daNote.mustPress || (daNote.wasGoodHit || (daNote.prevNote.wasGoodHit && !daNote.canBeHit)))
|
|
&& daNote.y - daNote.offset.y * daNote.scale.y + daNote.height >= strumLineMid)
|
|
{
|
|
// clipRect is applied to graphic itself so use frame Heights
|
|
var swagRect:FlxRect = new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight);
|
|
|
|
swagRect.height = (strumLineMid - daNote.y) / daNote.scale.y;
|
|
swagRect.y = daNote.frameHeight - swagRect.height;
|
|
daNote.clipRect = swagRect;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
daNote.y = (strumLine.y - (Conductor.songPosition - daNote.strumTime) * (0.45 * FlxMath.roundDecimal(SONG.speed, 2)));
|
|
|
|
if (daNote.isSustainNote
|
|
&& (!daNote.mustPress || (daNote.wasGoodHit || (daNote.prevNote.wasGoodHit && !daNote.canBeHit)))
|
|
&& daNote.y + daNote.offset.y * daNote.scale.y <= strumLineMid)
|
|
{
|
|
var swagRect:FlxRect = new FlxRect(0, 0, daNote.width / daNote.scale.x, daNote.height / daNote.scale.y);
|
|
|
|
swagRect.y = (strumLineMid - daNote.y) / daNote.scale.y;
|
|
swagRect.height -= swagRect.y;
|
|
daNote.clipRect = swagRect;
|
|
}
|
|
}
|
|
|
|
if (!daNote.mustPress && daNote.wasGoodHit)
|
|
opponentNoteHit(daNote);
|
|
|
|
// WIP interpolation shit? Need to fix the pause issue
|
|
// daNote.y = (strumLine.y - (songTime - daNote.strumTime) * (0.45 * SONG.speed));
|
|
|
|
// removing this so whether the note misses or not is entirely up to Note class
|
|
// var noteMiss:Bool = daNote.y < -daNote.height;
|
|
|
|
// if (PreferencesMenu.getPref('downscroll'))
|
|
// noteMiss = daNote.y > FlxG.height;
|
|
|
|
if (daNote.isSustainNote && daNote.wasGoodHit)
|
|
{
|
|
if ((!PreferencesMenu.getPref('downscroll') && daNote.y < -daNote.height)
|
|
|| (PreferencesMenu.getPref('downscroll') && daNote.y > FlxG.height))
|
|
{
|
|
daNote.active = false;
|
|
daNote.visible = false;
|
|
|
|
daNote.kill();
|
|
notes.remove(daNote, true);
|
|
daNote.destroy();
|
|
}
|
|
}
|
|
else if (daNote.tooLate || daNote.wasGoodHit)
|
|
{
|
|
if (daNote.tooLate)
|
|
{
|
|
songMisses++;
|
|
health -= 0.0475;
|
|
vocals.volume = 0;
|
|
killCombo();
|
|
UI.updateAcc(0, true);
|
|
}
|
|
|
|
daNote.active = false;
|
|
daNote.visible = false;
|
|
|
|
daNote.kill();
|
|
notes.remove(daNote, true);
|
|
daNote.destroy();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!inCutscene)
|
|
keyShit();
|
|
}
|
|
|
|
function killCombo():Void
|
|
{
|
|
if (combo > 5 && gf.animOffsets.exists('sad'))
|
|
gf.playAnim('sad');
|
|
if (combo != 0)
|
|
{
|
|
combo = 0;
|
|
displayCombo();
|
|
}
|
|
}
|
|
|
|
#if debug
|
|
function changeSection(sec:Int):Void
|
|
{
|
|
FlxG.sound.music.pause();
|
|
|
|
var daBPM:Float = SONG.bpm;
|
|
var daPos:Float = 0;
|
|
for (i in 0...(Std.int(curStep / 16 + sec)))
|
|
{
|
|
if (SONG.notes[i].changeBPM)
|
|
{
|
|
daBPM = SONG.notes[i].bpm;
|
|
}
|
|
daPos += 4 * (1000 * 60 / daBPM);
|
|
}
|
|
Conductor.songPosition = FlxG.sound.music.time = daPos;
|
|
updateCurStep();
|
|
resyncVocals();
|
|
}
|
|
#end
|
|
|
|
function endSong():Void
|
|
{
|
|
seenCutscene = false;
|
|
deathCounter = 0;
|
|
canPause = false;
|
|
FlxG.sound.music.volume = 0;
|
|
vocals.volume = 0;
|
|
if (SONG.validScore)
|
|
{
|
|
Highscore.saveScore(SONG.song, songScore, storyDifficulty);
|
|
}
|
|
|
|
if (isStoryMode)
|
|
{
|
|
campaignScore += songScore;
|
|
|
|
storyPlaylist.remove(storyPlaylist[0]);
|
|
|
|
if (storyPlaylist.length <= 0)
|
|
{
|
|
FlxG.sound.playMusic(Paths.music('freakyMenu'));
|
|
|
|
transIn = FlxTransitionableState.defaultTransIn;
|
|
transOut = FlxTransitionableState.defaultTransOut;
|
|
|
|
switch (PlayState.storyWeek)
|
|
{
|
|
case 7:
|
|
FlxG.switchState(new VideoState());
|
|
default:
|
|
FlxG.switchState(new StoryMenuState());
|
|
}
|
|
|
|
// if ()
|
|
StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true;
|
|
|
|
if (SONG.validScore)
|
|
{
|
|
// NGio.unlockMedal(60961);
|
|
Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty);
|
|
}
|
|
|
|
FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked;
|
|
FlxG.save.flush();
|
|
}
|
|
else
|
|
{
|
|
var difficulty:String = "";
|
|
|
|
if (storyDifficulty == 0)
|
|
difficulty = '-easy';
|
|
|
|
if (storyDifficulty == 2)
|
|
difficulty = '-hard';
|
|
|
|
trace('LOADING NEXT SONG');
|
|
trace(storyPlaylist[0].toLowerCase() + difficulty);
|
|
|
|
FlxTransitionableState.skipNextTransIn = true;
|
|
FlxTransitionableState.skipNextTransOut = true;
|
|
|
|
FlxG.sound.music.stop();
|
|
vocals.stop();
|
|
|
|
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;
|
|
inCutscene = true;
|
|
|
|
FlxG.sound.play(Paths.sound('Lights_Shut_off'), function()
|
|
{
|
|
// no camFollow so it centers on horror tree
|
|
SONG = Song.loadFromJson(storyPlaylist[0].toLowerCase() + difficulty, storyPlaylist[0]);
|
|
LoadingState.loadAndSwitchState(new PlayState());
|
|
});
|
|
}
|
|
else
|
|
{
|
|
prevCamFollow = camFollow;
|
|
|
|
SONG = Song.loadFromJson(storyPlaylist[0].toLowerCase() + difficulty, storyPlaylist[0]);
|
|
LoadingState.loadAndSwitchState(new PlayState());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trace('WENT BACK TO FREEPLAY??');
|
|
// unloadAssets();
|
|
FlxG.switchState(new FreeplayState());
|
|
}
|
|
}
|
|
|
|
// gives score and pops up rating
|
|
private function popUpScore(strumTime:Float, daNote:Note):Void
|
|
{
|
|
var noteDiff:Float = Math.abs(strumTime - Conductor.songPosition);
|
|
// boyfriend.playAnim('hey');
|
|
vocals.volume = 1;
|
|
|
|
// var score:Int = 350;
|
|
|
|
// var daRating:String = "sick";
|
|
var daRating:String = Conductor.findRating(noteDiff); // ms based judgements moment
|
|
|
|
var isSick:Bool = true;
|
|
|
|
if (daRating != 'sick')
|
|
isSick = false;
|
|
|
|
if (isSick)
|
|
{
|
|
var noteSplash:NoteSplash = grpNoteSplashes.recycle(NoteSplash);
|
|
noteSplash.setupNoteSplash(daNote.x, daNote.y, daNote.noteData);
|
|
grpNoteSplashes.add(noteSplash);
|
|
}
|
|
|
|
UI.updateAcc(UI.ratingMap.get(daRating));
|
|
|
|
// Only add the score if you're not on practice mode
|
|
if (!practiceMode)
|
|
songScore += Conductor.getRatingScore(noteDiff);
|
|
|
|
var ratingPath:String = daRating;
|
|
|
|
if (isPixel)
|
|
ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
|
|
|
var rating = JudgeSpr.spawnJudgeSpr(ratingPath, isPixel);
|
|
add(rating);
|
|
|
|
FlxTween.tween(rating, {alpha: 0}, 0.2, {
|
|
onComplete: function(tween:FlxTween)
|
|
{
|
|
rating.destroy();
|
|
},
|
|
startDelay: Conductor.crochet * 0.001
|
|
});
|
|
// if (combo >= 10 || combo == 0)
|
|
displayCombo();
|
|
}
|
|
|
|
function displayCombo():Void
|
|
{
|
|
var imgPath:String = 'combo';
|
|
if (isPixel)
|
|
imgPath = 'weeb/pixelUI/combo-pixel';
|
|
|
|
var comboSpr = JudgeSpr.spawnComboSpr(imgPath, isPixel);
|
|
if (combo >= 10 || combo == 0)
|
|
add(comboSpr);
|
|
|
|
FlxTween.tween(comboSpr, {alpha: 0}, 0.2, {
|
|
onComplete: function(tween:FlxTween)
|
|
{
|
|
comboSpr.destroy();
|
|
},
|
|
startDelay: Conductor.crochet * 0.001
|
|
});
|
|
|
|
var seperatedScore:Array<Int> = [];
|
|
var tempCombo:Int = combo;
|
|
|
|
while (tempCombo != 0)
|
|
{
|
|
seperatedScore.push(tempCombo % 10);
|
|
tempCombo = Std.int(tempCombo / 10);
|
|
}
|
|
while (seperatedScore.length < 3)
|
|
seperatedScore.push(0);
|
|
|
|
// seperatedScore.reverse();
|
|
|
|
var daLoop:Int = 1;
|
|
for (i in seperatedScore)
|
|
{
|
|
var imgPath:String = 'num' + Std.int(i);
|
|
if (isPixel)
|
|
imgPath = 'weeb/pixelUI/num' + Std.int(i) + '-pixel';
|
|
|
|
var numScore = JudgeSpr.spawnComboNum(imgPath, isPixel);
|
|
add(numScore);
|
|
|
|
numScore.y = comboSpr.y;
|
|
numScore.x = comboSpr.x - (43 * daLoop);
|
|
|
|
FlxTween.tween(numScore, {alpha: 0}, 0.2, {
|
|
onComplete: function(tween:FlxTween)
|
|
{
|
|
numScore.destroy();
|
|
},
|
|
startDelay: Conductor.crochet * 0.002
|
|
});
|
|
|
|
daLoop++;
|
|
}
|
|
}
|
|
|
|
var cameraRightSide:Bool = false;
|
|
|
|
function cameraMovement()
|
|
{
|
|
if (camFollow.x != dad.getMidpoint().x + 150 && !cameraRightSide)
|
|
{
|
|
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' | '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 (cameraRightSide && camFollow.x != boyfriend.getMidpoint().x - 100)
|
|
{
|
|
camFollow.setPosition(boyfriend.getMidpoint().x - 100, boyfriend.getMidpoint().y - 100);
|
|
|
|
switch (gameStage.getStage())
|
|
{
|
|
case 'limo':
|
|
camFollow.x = boyfriend.getMidpoint().x - 300;
|
|
case 'mall':
|
|
camFollow.y = boyfriend.getMidpoint().y - 200;
|
|
case 'school' | '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});
|
|
}
|
|
}
|
|
|
|
private function keyShit():Void
|
|
{
|
|
// control arrays, order L D R U
|
|
var holdArray:Array<Bool> = [controls.NOTE_LEFT, controls.NOTE_DOWN, controls.NOTE_UP, controls.NOTE_RIGHT];
|
|
var pressArray:Array<Bool> = [
|
|
controls.NOTE_LEFT_P,
|
|
controls.NOTE_DOWN_P,
|
|
controls.NOTE_UP_P,
|
|
controls.NOTE_RIGHT_P
|
|
];
|
|
var releaseArray:Array<Bool> = [
|
|
controls.NOTE_LEFT_R,
|
|
controls.NOTE_DOWN_R,
|
|
controls.NOTE_UP_R,
|
|
controls.NOTE_RIGHT_R
|
|
];
|
|
|
|
// HOLDS, check for sustain notes
|
|
if (holdArray.contains(true) && /*!boyfriend.stunned && */ generatedMusic)
|
|
{
|
|
notes.forEachAlive(function(daNote:Note)
|
|
{
|
|
if (daNote.isSustainNote && daNote.canBeHit && daNote.mustPress && holdArray[daNote.noteData])
|
|
goodNoteHit(daNote);
|
|
});
|
|
}
|
|
|
|
// PRESSES, check for note hits
|
|
if (pressArray.contains(true) && /*!boyfriend.stunned && */ generatedMusic)
|
|
{
|
|
boyfriend.holdTimer = 0;
|
|
|
|
var possibleNotes:Array<Note> = []; // notes that can be hit
|
|
var directionList:Array<Int> = []; // directions that can be hit
|
|
var dumbNotes:Array<Note> = []; // notes to kill later
|
|
|
|
notes.forEachAlive(function(daNote:Note)
|
|
{
|
|
if (daNote.canBeHit && daNote.mustPress && !daNote.tooLate && !daNote.wasGoodHit)
|
|
{
|
|
if (directionList.contains(daNote.noteData))
|
|
{
|
|
for (coolNote in possibleNotes)
|
|
{
|
|
if (coolNote.noteData == daNote.noteData && Math.abs(daNote.strumTime - coolNote.strumTime) < 10)
|
|
{ // if it's the same note twice at < 10ms distance, just delete it
|
|
// EXCEPT u cant delete it in this loop cuz it fucks with the collection lol
|
|
dumbNotes.push(daNote);
|
|
break;
|
|
}
|
|
else if (coolNote.noteData == daNote.noteData && daNote.strumTime < coolNote.strumTime)
|
|
{ // if daNote is earlier than existing note (coolNote), replace
|
|
possibleNotes.remove(coolNote);
|
|
possibleNotes.push(daNote);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
possibleNotes.push(daNote);
|
|
directionList.push(daNote.noteData);
|
|
}
|
|
}
|
|
});
|
|
|
|
for (note in dumbNotes)
|
|
{
|
|
FlxG.log.add("killing dumb ass note at " + note.strumTime);
|
|
note.kill();
|
|
notes.remove(note, true);
|
|
note.destroy();
|
|
}
|
|
|
|
possibleNotes.sort((a, b) -> Std.int(a.strumTime - b.strumTime));
|
|
|
|
if (perfectMode)
|
|
goodNoteHit(possibleNotes[0]);
|
|
else if (possibleNotes.length > 0)
|
|
{
|
|
for (shit in 0...pressArray.length)
|
|
{ // if a direction is hit that shouldn't be
|
|
if (pressArray[shit] && !directionList.contains(shit))
|
|
noteMiss(shit);
|
|
}
|
|
for (coolNote in possibleNotes)
|
|
{
|
|
if (pressArray[coolNote.noteData])
|
|
goodNoteHit(coolNote);
|
|
}
|
|
}
|
|
/*
|
|
Commented This Out To Enable Ghost Tapping.
|
|
Will be reused later when i add an option to enable / disable ghost tapping.
|
|
|
|
- Zyflx
|
|
else
|
|
{
|
|
for (shit in 0...pressArray.length)
|
|
if (pressArray[shit])
|
|
noteMiss(shit);
|
|
}*/
|
|
}
|
|
|
|
if (boyfriend.holdTimer > Conductor.stepCrochet * 4 * 0.001 && !holdArray.contains(true))
|
|
{
|
|
if (boyfriend.animation.curAnim.name.startsWith('sing') && !boyfriend.animation.curAnim.name.endsWith('miss'))
|
|
{
|
|
boyfriend.playAnim('idle');
|
|
}
|
|
}
|
|
|
|
playerStrums.forEach(function(spr:StaticNote)
|
|
{
|
|
if (pressArray[spr.ID] && spr.animation.curAnim.name != 'confirm')
|
|
spr.playStrumAnim('pressed', true);
|
|
if (!holdArray[spr.ID])
|
|
spr.playStrumAnim('static', true);
|
|
});
|
|
}
|
|
|
|
function noteMiss(direction:Int = 1):Void
|
|
{
|
|
// whole function used to be encased in if (!boyfriend.stunned)
|
|
songMisses++;
|
|
health -= 0.04;
|
|
killCombo();
|
|
UI.updateAcc(0, true);
|
|
|
|
if (!practiceMode)
|
|
songScore -= 10;
|
|
|
|
vocals.volume = 0;
|
|
FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
|
|
|
|
boyfriend.playAnim(singAnims[direction] + 'miss', true);
|
|
}
|
|
|
|
function goodNoteHit(note:Note):Void
|
|
{
|
|
if (!note.wasGoodHit)
|
|
{
|
|
if (!note.isSustainNote)
|
|
{
|
|
combo += 1;
|
|
popUpScore(note.strumTime, note);
|
|
}
|
|
|
|
if (note.noteData >= 0)
|
|
health += 0.023;
|
|
else
|
|
health += 0.004;
|
|
|
|
boyfriend.playAnim(singAnims[note.noteData], true);
|
|
|
|
strumAnim(note.noteData, false, 0);
|
|
|
|
note.wasGoodHit = true;
|
|
vocals.volume = 1;
|
|
|
|
if (!note.isSustainNote)
|
|
{
|
|
note.kill();
|
|
notes.remove(note, true);
|
|
note.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
function opponentNoteHit(note:Note)
|
|
{
|
|
if (SONG.song != 'Tutorial')
|
|
camZooming = true;
|
|
|
|
var altAnim:String = "";
|
|
|
|
if (SONG.notes[Math.floor(curStep / 16)] != null)
|
|
{
|
|
if (SONG.notes[Math.floor(curStep / 16)].altAnim)
|
|
altAnim = '-alt';
|
|
}
|
|
|
|
if (note.altNote)
|
|
altAnim = '-alt';
|
|
|
|
var time:Float = 0.15;
|
|
if (note.isSustainNote && !note.animation.curAnim.name.endsWith('end'))
|
|
time += 0.15;
|
|
|
|
strumAnim(note.noteData, true, time);
|
|
|
|
dad.playAnim(singAnims[note.noteData] + altAnim, true);
|
|
|
|
dad.holdTimer = 0;
|
|
|
|
if (SONG.needsVoices)
|
|
vocals.volume = 1;
|
|
|
|
note.kill();
|
|
notes.remove(note, true);
|
|
note.destroy();
|
|
}
|
|
|
|
override function stepHit()
|
|
{
|
|
super.stepHit();
|
|
|
|
gameStage.stepHit(curStep, boyfriend, dad, gf);
|
|
|
|
if (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 20
|
|
|| (SONG.needsVoices && Math.abs(vocals.time - (Conductor.songPosition - Conductor.offset)) > 20))
|
|
{
|
|
resyncVocals();
|
|
}
|
|
|
|
if (dad.curCharacter == 'spooky' && curStep % 4 == 2)
|
|
{
|
|
// dad.dance();
|
|
}
|
|
}
|
|
|
|
override function beatHit()
|
|
{
|
|
super.beatHit();
|
|
|
|
gameStage.beatHit(curBeat, boyfriend, dad, gf);
|
|
UI.iconBeat();
|
|
|
|
if (generatedMusic)
|
|
{
|
|
notes.sort(sortNotes, FlxSort.DESCENDING);
|
|
}
|
|
|
|
if (SONG.notes[Math.floor(curStep / 16)] != null)
|
|
{
|
|
if (SONG.notes[Math.floor(curStep / 16)].changeBPM)
|
|
{
|
|
Conductor.changeBPM(SONG.notes[Math.floor(curStep / 16)].bpm);
|
|
FlxG.log.add('CHANGED BPM!');
|
|
}
|
|
// else
|
|
// Conductor.changeBPM(SONG.bpm);
|
|
}
|
|
// FlxG.log.add('change bpm' + SONG.notes[Std.int(curStep / 16)].changeBPM);
|
|
wiggleShit.update(Conductor.crochet);
|
|
|
|
// HARDCODING FOR MILF ZOOMS!
|
|
|
|
if (PreferencesMenu.getPref('camera-zoom'))
|
|
{
|
|
if (curSong.toLowerCase() == 'milf' && curBeat >= 168 && curBeat < 200 && camZooming && FlxG.camera.zoom < 1.35)
|
|
{
|
|
FlxG.camera.zoom += 0.015;
|
|
camHUD.zoom += 0.03;
|
|
}
|
|
|
|
if (camZooming && FlxG.camera.zoom < 1.35 && curBeat % 4 == 0)
|
|
{
|
|
FlxG.camera.zoom += 0.015;
|
|
camHUD.zoom += 0.03;
|
|
}
|
|
}
|
|
|
|
if (curBeat % gfSpeed == 0)
|
|
gf.dance();
|
|
|
|
if (curBeat % 2 == 0)
|
|
{
|
|
if (!boyfriend.animation.curAnim.name.startsWith("sing"))
|
|
boyfriend.playAnim('idle');
|
|
if (!dad.animation.curAnim.name.startsWith("sing"))
|
|
dad.dance();
|
|
}
|
|
else if (dad.curCharacter == 'spooky')
|
|
{
|
|
if (!dad.animation.curAnim.name.startsWith("sing"))
|
|
dad.dance();
|
|
}
|
|
|
|
if (curBeat % 8 == 7 && curSong == 'Bopeebo')
|
|
{
|
|
boyfriend.playAnim('hey', true);
|
|
}
|
|
|
|
if (curBeat % 16 == 15 && SONG.song == 'Tutorial' && dad.curCharacter == 'gf' && curBeat > 16 && curBeat < 48)
|
|
{
|
|
boyfriend.playAnim('hey', true);
|
|
dad.playAnim('cheer', true);
|
|
}
|
|
}
|
|
|
|
function strumAnim(id:Int, isDad:Bool, time:Float)
|
|
{
|
|
var strum:StaticNote = null;
|
|
if (isDad)
|
|
strum = strumLineNotes.members[id];
|
|
else
|
|
strum = playerStrums.members[id];
|
|
|
|
if (strum != null)
|
|
{
|
|
strum.playStrumAnim('confirm', true);
|
|
strum.animReset = time;
|
|
}
|
|
}
|
|
}
|