diff --git a/artDUCK/duck_assets.fla b/artDUCK/duck_assets.fla
new file mode 100644
index 00000000..bb5b1013
Binary files /dev/null and b/artDUCK/duck_assets.fla differ
diff --git a/campaign_menu_UI_characters.png b/campaign_menu_UI_characters.png
new file mode 100644
index 00000000..e8453a10
Binary files /dev/null and b/campaign_menu_UI_characters.png differ
diff --git a/campaign_menu_UI_characters.xml b/campaign_menu_UI_characters.xml
new file mode 100644
index 00000000..79b862b3
--- /dev/null
+++ b/campaign_menu_UI_characters.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/duck/Dia/Dialougue Box.png b/duck/Dia/Dialougue Box.png
new file mode 100644
index 00000000..059e575d
Binary files /dev/null and b/duck/Dia/Dialougue Box.png differ
diff --git a/duck/Dia/Dialougue Box.xml b/duck/Dia/Dialougue Box.xml
new file mode 100644
index 00000000..15d1b245
--- /dev/null
+++ b/duck/Dia/Dialougue Box.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/duck/Portrait/DuckPortrait.png b/duck/Portrait/DuckPortrait.png
new file mode 100644
index 00000000..61c06ec2
Binary files /dev/null and b/duck/Portrait/DuckPortrait.png differ
diff --git a/duck/Portrait/DuckPortrait.xml b/duck/Portrait/DuckPortrait.xml
new file mode 100644
index 00000000..59b7b86e
--- /dev/null
+++ b/duck/Portrait/DuckPortrait.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/duck/bg.png b/duck/bg.png
new file mode 100644
index 00000000..8456c70e
Binary files /dev/null and b/duck/bg.png differ
diff --git a/duck/duck_assets.png b/duck/duck_assets.png
new file mode 100644
index 00000000..b1c80f8a
Binary files /dev/null and b/duck/duck_assets.png differ
diff --git a/duck/duck_assets.xml b/duck/duck_assets.xml
new file mode 100644
index 00000000..164a7fbc
--- /dev/null
+++ b/duck/duck_assets.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iconGrid.png b/iconGrid.png
new file mode 100644
index 00000000..df92f7ce
Binary files /dev/null and b/iconGrid.png differ
diff --git a/source_duck/APIStuff.hx b/source_duck/APIStuff.hx
new file mode 100644
index 00000000..f05fa065
--- /dev/null
+++ b/source_duck/APIStuff.hx
@@ -0,0 +1,7 @@
+package;
+
+class APIStuff
+{
+ public static var API:String = "";
+ public static var EncKey:String = "";
+}
diff --git a/source_duck/Alphabet.hx b/source_duck/Alphabet.hx
new file mode 100644
index 00000000..6f7e711b
--- /dev/null
+++ b/source_duck/Alphabet.hx
@@ -0,0 +1,309 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.group.FlxSpriteGroup;
+import flixel.math.FlxMath;
+import flixel.util.FlxTimer;
+
+using StringTools;
+
+/**
+ * Loosley based on FlxTypeText lolol
+ */
+class Alphabet extends FlxSpriteGroup
+{
+ public var delay:Float = 0.05;
+ public var paused:Bool = false;
+
+ // for menu shit
+ public var targetY:Float = 0;
+ public var isMenuItem:Bool = false;
+
+ public var text:String = "";
+
+ var _finalText:String = "";
+ var _curText:String = "";
+
+ public var widthOfWords:Float = FlxG.width;
+
+ var yMulti:Float = 1;
+
+ // custom shit
+ // amp, backslash, question mark, apostrophy, comma, angry faic, period
+ var lastSprite:AlphaCharacter;
+ var xPosResetted:Bool = false;
+ var lastWasSpace:Bool = false;
+
+ var splitWords:Array = [];
+
+ var isBold:Bool = false;
+
+ public function new(x:Float, y:Float, text:String = "", ?bold:Bool = false, typed:Bool = false)
+ {
+ super(x, y);
+
+ _finalText = text;
+ this.text = text;
+ isBold = bold;
+
+ if (text != "")
+ {
+ if (typed)
+ {
+ startTypedText();
+ }
+ else
+ {
+ addText();
+ }
+ }
+ }
+
+ public function addText()
+ {
+ doSplitWords();
+
+ var xPos:Float = 0;
+ for (character in splitWords)
+ {
+ // if (character.fastCodeAt() == " ")
+ // {
+ // }
+
+ if (character == " " || character == "-")
+ {
+ lastWasSpace = true;
+ }
+
+ if (AlphaCharacter.alphabet.indexOf(character.toLowerCase()) != -1)
+ // if (AlphaCharacter.alphabet.contains(character.toLowerCase()))
+ {
+ if (lastSprite != null)
+ {
+ xPos = lastSprite.x + lastSprite.width;
+ }
+
+ if (lastWasSpace)
+ {
+ xPos += 40;
+ lastWasSpace = false;
+ }
+
+ // var letter:AlphaCharacter = new AlphaCharacter(30 * loopNum, 0);
+ var letter:AlphaCharacter = new AlphaCharacter(xPos, 0);
+
+ if (isBold)
+ letter.createBold(character);
+ else
+ {
+ letter.createLetter(character);
+ }
+
+ add(letter);
+
+ lastSprite = letter;
+ }
+
+ // loopNum += 1;
+ }
+ }
+
+ function doSplitWords():Void
+ {
+ splitWords = _finalText.split("");
+ }
+
+ public var personTalking:String = 'gf';
+
+ public function startTypedText():Void
+ {
+ _finalText = text;
+ doSplitWords();
+
+ // trace(arrayShit);
+
+ var loopNum:Int = 0;
+
+ var xPos:Float = 0;
+ var curRow:Int = 0;
+
+ new FlxTimer().start(0.05, function(tmr:FlxTimer)
+ {
+ // trace(_finalText.fastCodeAt(loopNum) + " " + _finalText.charAt(loopNum));
+ if (_finalText.fastCodeAt(loopNum) == "\n".code)
+ {
+ yMulti += 1;
+ xPosResetted = true;
+ xPos = 0;
+ curRow += 1;
+ }
+
+ if (splitWords[loopNum] == " ")
+ {
+ lastWasSpace = true;
+ }
+
+ #if (haxe >= "4.0.0")
+ var isNumber:Bool = AlphaCharacter.numbers.contains(splitWords[loopNum]);
+ var isSymbol:Bool = AlphaCharacter.symbols.contains(splitWords[loopNum]);
+ #else
+ var isNumber:Bool = AlphaCharacter.numbers.indexOf(splitWords[loopNum]) != -1;
+ var isSymbol:Bool = AlphaCharacter.symbols.indexOf(splitWords[loopNum]) != -1;
+ #end
+
+ if (AlphaCharacter.alphabet.indexOf(splitWords[loopNum].toLowerCase()) != -1 || isNumber || isSymbol)
+ // if (AlphaCharacter.alphabet.contains(splitWords[loopNum].toLowerCase()) || isNumber || isSymbol)
+
+ {
+ if (lastSprite != null && !xPosResetted)
+ {
+ lastSprite.updateHitbox();
+ xPos += lastSprite.width + 3;
+ // if (isBold)
+ // xPos -= 80;
+ }
+ else
+ {
+ xPosResetted = false;
+ }
+
+ if (lastWasSpace)
+ {
+ xPos += 20;
+ lastWasSpace = false;
+ }
+ // trace(_finalText.fastCodeAt(loopNum) + " " + _finalText.charAt(loopNum));
+
+ // var letter:AlphaCharacter = new AlphaCharacter(30 * loopNum, 0);
+ var letter:AlphaCharacter = new AlphaCharacter(xPos, 55 * yMulti);
+ letter.row = curRow;
+ if (isBold)
+ {
+ letter.createBold(splitWords[loopNum]);
+ }
+ else
+ {
+ if (isNumber)
+ {
+ letter.createNumber(splitWords[loopNum]);
+ }
+ else if (isSymbol)
+ {
+ letter.createSymbol(splitWords[loopNum]);
+ }
+ else
+ {
+ letter.createLetter(splitWords[loopNum]);
+ }
+
+ letter.x += 90;
+ }
+
+ if (FlxG.random.bool(40))
+ {
+ var daSound:String = "GF_";
+ FlxG.sound.play(Paths.soundRandom(daSound, 1, 4));
+ }
+
+ add(letter);
+
+ lastSprite = letter;
+ }
+
+ loopNum += 1;
+
+ tmr.time = FlxG.random.float(0.04, 0.09);
+ }, splitWords.length);
+ }
+
+ override function update(elapsed:Float)
+ {
+ if (isMenuItem)
+ {
+ var scaledY = FlxMath.remapToRange(targetY, 0, 1, 0, 1.3);
+
+ y = FlxMath.lerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.30);
+ x = FlxMath.lerp(x, (targetY * 20) + 90, 0.30);
+ }
+
+ super.update(elapsed);
+ }
+}
+
+class AlphaCharacter extends FlxSprite
+{
+ public static var alphabet:String = "abcdefghijklmnopqrstuvwxyz";
+
+ public static var numbers:String = "1234567890";
+
+ public static var symbols:String = "|~#$%()*+-:;<=>@[]^_.,'!?";
+
+ public var row:Int = 0;
+
+ public function new(x:Float, y:Float)
+ {
+ super(x, y);
+ var tex = Paths.getSparrowAtlas('alphabet');
+ frames = tex;
+
+ antialiasing = true;
+ }
+
+ public function createBold(letter:String)
+ {
+ animation.addByPrefix(letter, letter.toUpperCase() + " bold", 24);
+ animation.play(letter);
+ updateHitbox();
+ }
+
+ public function createLetter(letter:String):Void
+ {
+ var letterCase:String = "lowercase";
+ if (letter.toLowerCase() != letter)
+ {
+ letterCase = 'capital';
+ }
+
+ animation.addByPrefix(letter, letter + " " + letterCase, 24);
+ animation.play(letter);
+ updateHitbox();
+
+ FlxG.log.add('the row' + row);
+
+ y = (110 - height);
+ y += row * 60;
+ }
+
+ public function createNumber(letter:String):Void
+ {
+ animation.addByPrefix(letter, letter, 24);
+ animation.play(letter);
+
+ updateHitbox();
+ }
+
+ public function createSymbol(letter:String)
+ {
+ switch (letter)
+ {
+ case '.':
+ animation.addByPrefix(letter, 'period', 24);
+ animation.play(letter);
+ y += 50;
+ case "'":
+ animation.addByPrefix(letter, 'apostraphie', 24);
+ animation.play(letter);
+ y -= 0;
+ case "?":
+ animation.addByPrefix(letter, 'question mark', 24);
+ animation.play(letter);
+ case "!":
+ animation.addByPrefix(letter, 'exclamation point', 24);
+ animation.play(letter);
+ }
+
+ updateHitbox();
+ }
+}
diff --git a/source_duck/AnimationDebug.hx b/source_duck/AnimationDebug.hx
new file mode 100644
index 00000000..05b40d63
--- /dev/null
+++ b/source_duck/AnimationDebug.hx
@@ -0,0 +1,195 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxObject;
+import flixel.FlxSprite;
+import flixel.FlxState;
+import flixel.addons.display.FlxGridOverlay;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
+
+/**
+ *DEBUG MODE
+ */
+class AnimationDebug extends FlxState
+{
+ var bf:Boyfriend;
+ var dad:Character;
+ var char:Character;
+ var textAnim:FlxText;
+ var dumbTexts:FlxTypedGroup;
+ var animList:Array = [];
+ var curAnim:Int = 0;
+ var isDad:Bool = true;
+ var daAnim:String = 'spooky';
+ var camFollow:FlxObject;
+
+ public function new(daAnim:String = 'spooky')
+ {
+ super();
+ this.daAnim = daAnim;
+ }
+
+ override function create()
+ {
+ FlxG.sound.music.stop();
+
+ var gridBG:FlxSprite = FlxGridOverlay.create(10, 10);
+ gridBG.scrollFactor.set(0.5, 0.5);
+ add(gridBG);
+
+ if (daAnim == 'bf')
+ isDad = false;
+
+ if (isDad)
+ {
+ dad = new Character(0, 0, daAnim);
+ dad.screenCenter();
+ dad.debugMode = true;
+ add(dad);
+
+ char = dad;
+ dad.flipX = false;
+ }
+ else
+ {
+ bf = new Boyfriend(0, 0);
+ bf.screenCenter();
+ bf.debugMode = true;
+ add(bf);
+
+ char = bf;
+ bf.flipX = false;
+ }
+
+ dumbTexts = new FlxTypedGroup();
+ add(dumbTexts);
+
+ textAnim = new FlxText(300, 16);
+ textAnim.size = 26;
+ textAnim.scrollFactor.set();
+ add(textAnim);
+
+ genBoyOffsets();
+
+ camFollow = new FlxObject(0, 0, 2, 2);
+ camFollow.screenCenter();
+ add(camFollow);
+
+ FlxG.camera.follow(camFollow);
+
+ super.create();
+ }
+
+ function genBoyOffsets(pushList:Bool = true):Void
+ {
+ var daLoop:Int = 0;
+
+ for (anim => offsets in char.animOffsets)
+ {
+ var text:FlxText = new FlxText(10, 20 + (18 * daLoop), 0, anim + ": " + offsets, 15);
+ text.scrollFactor.set();
+ text.color = FlxColor.BLUE;
+ dumbTexts.add(text);
+
+ if (pushList)
+ animList.push(anim);
+
+ daLoop++;
+ }
+ }
+
+ function updateTexts():Void
+ {
+ dumbTexts.forEach(function(text:FlxText)
+ {
+ text.kill();
+ dumbTexts.remove(text, true);
+ });
+ }
+
+ override function update(elapsed:Float)
+ {
+ textAnim.text = char.animation.curAnim.name;
+
+ if (FlxG.keys.justPressed.E)
+ FlxG.camera.zoom += 0.25;
+ if (FlxG.keys.justPressed.Q)
+ FlxG.camera.zoom -= 0.25;
+
+ if (FlxG.keys.pressed.I || FlxG.keys.pressed.J || FlxG.keys.pressed.K || FlxG.keys.pressed.L)
+ {
+ if (FlxG.keys.pressed.I)
+ camFollow.velocity.y = -90;
+ else if (FlxG.keys.pressed.K)
+ camFollow.velocity.y = 90;
+ else
+ camFollow.velocity.y = 0;
+
+ if (FlxG.keys.pressed.J)
+ camFollow.velocity.x = -90;
+ else if (FlxG.keys.pressed.L)
+ camFollow.velocity.x = 90;
+ else
+ camFollow.velocity.x = 0;
+ }
+ else
+ {
+ camFollow.velocity.set();
+ }
+
+ if (FlxG.keys.justPressed.W)
+ {
+ curAnim -= 1;
+ }
+
+ if (FlxG.keys.justPressed.S)
+ {
+ curAnim += 1;
+ }
+
+ if (curAnim < 0)
+ curAnim = animList.length - 1;
+
+ if (curAnim >= animList.length)
+ curAnim = 0;
+
+ if (FlxG.keys.justPressed.S || FlxG.keys.justPressed.W || FlxG.keys.justPressed.SPACE)
+ {
+ char.playAnim(animList[curAnim]);
+
+ updateTexts();
+ genBoyOffsets(false);
+ }
+
+ var upP = FlxG.keys.anyJustPressed([UP]);
+ var rightP = FlxG.keys.anyJustPressed([RIGHT]);
+ var downP = FlxG.keys.anyJustPressed([DOWN]);
+ var leftP = FlxG.keys.anyJustPressed([LEFT]);
+
+ var holdShift = FlxG.keys.pressed.SHIFT;
+ var multiplier = 1;
+ if (holdShift)
+ multiplier = 10;
+
+ if (upP || rightP || downP || leftP)
+ {
+ updateTexts();
+ if (upP)
+ char.animOffsets.get(animList[curAnim])[1] += 1 * multiplier;
+ if (downP)
+ char.animOffsets.get(animList[curAnim])[1] -= 1 * multiplier;
+ if (leftP)
+ char.animOffsets.get(animList[curAnim])[0] += 1 * multiplier;
+ if (rightP)
+ char.animOffsets.get(animList[curAnim])[0] -= 1 * multiplier;
+
+ updateTexts();
+ genBoyOffsets(false);
+ char.playAnim(animList[curAnim]);
+ }
+
+ super.update(elapsed);
+ }
+}
diff --git a/source_duck/AssetPaths.hx b/source_duck/AssetPaths.hx
new file mode 100644
index 00000000..5202a17d
--- /dev/null
+++ b/source_duck/AssetPaths.hx
@@ -0,0 +1,4 @@
+package;
+
+@:build(flixel.system.FlxAssets.buildFileReferences("assets", true))
+class AssetPaths {}
diff --git a/source_duck/BackgroundDancer.hx b/source_duck/BackgroundDancer.hx
new file mode 100644
index 00000000..a619dacc
--- /dev/null
+++ b/source_duck/BackgroundDancer.hx
@@ -0,0 +1,30 @@
+package;
+
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+
+class BackgroundDancer extends FlxSprite
+{
+ public function new(x:Float, y:Float)
+ {
+ super(x, y);
+
+ frames = Paths.getSparrowAtlas("limo/limoDancer");
+ animation.addByIndices('danceLeft', 'bg dancer sketch PINK', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
+ animation.addByIndices('danceRight', 'bg dancer sketch PINK', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
+ animation.play('danceLeft');
+ antialiasing = true;
+ }
+
+ var danceDir:Bool = false;
+
+ public function dance():Void
+ {
+ danceDir = !danceDir;
+
+ if (danceDir)
+ animation.play('danceRight', true);
+ else
+ animation.play('danceLeft', true);
+ }
+}
diff --git a/source_duck/BackgroundGirls.hx b/source_duck/BackgroundGirls.hx
new file mode 100644
index 00000000..c37b16f9
--- /dev/null
+++ b/source_duck/BackgroundGirls.hx
@@ -0,0 +1,39 @@
+package;
+
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+
+class BackgroundGirls extends FlxSprite
+{
+ public function new(x:Float, y:Float)
+ {
+ super(x, y);
+
+ // BG fangirls dissuaded
+ frames = Paths.getSparrowAtlas('weeb/bgFreaks');
+
+ animation.addByIndices('danceLeft', 'BG girls group', CoolUtil.numberArray(14), "", 24, false);
+ animation.addByIndices('danceRight', 'BG girls group', CoolUtil.numberArray(30, 15), "", 24, false);
+
+ animation.play('danceLeft');
+ }
+
+ var danceDir:Bool = false;
+
+ public function getScared():Void
+ {
+ animation.addByIndices('danceLeft', 'BG fangirls dissuaded', CoolUtil.numberArray(14), "", 24, false);
+ animation.addByIndices('danceRight', 'BG fangirls dissuaded', CoolUtil.numberArray(30, 15), "", 24, false);
+ dance();
+ }
+
+ public function dance():Void
+ {
+ danceDir = !danceDir;
+
+ if (danceDir)
+ animation.play('danceRight', true);
+ else
+ animation.play('danceLeft', true);
+ }
+}
diff --git a/source_duck/BlendModeEffect.hx b/source_duck/BlendModeEffect.hx
new file mode 100644
index 00000000..1c9038be
--- /dev/null
+++ b/source_duck/BlendModeEffect.hx
@@ -0,0 +1,34 @@
+package;
+
+import flixel.util.FlxColor;
+import openfl.display.ShaderParameter;
+
+typedef BlendModeShader =
+{
+ var uBlendColor:ShaderParameter;
+}
+
+class BlendModeEffect
+{
+ public var shader(default, null):BlendModeShader;
+
+ @:isVar
+ public var color(default, set):FlxColor;
+
+ public function new(shader:BlendModeShader, color:FlxColor):Void
+ {
+ shader.uBlendColor.value = [];
+ this.shader = shader;
+ this.color = color;
+ }
+
+ function set_color(color:FlxColor):FlxColor
+ {
+ shader.uBlendColor.value[0] = color.redFloat;
+ shader.uBlendColor.value[1] = color.greenFloat;
+ shader.uBlendColor.value[2] = color.blueFloat;
+ shader.uBlendColor.value[3] = color.alphaFloat;
+
+ return this.color = color;
+ }
+}
diff --git a/source_duck/Boyfriend.hx b/source_duck/Boyfriend.hx
new file mode 100644
index 00000000..c9f87f9b
--- /dev/null
+++ b/source_duck/Boyfriend.hx
@@ -0,0 +1,43 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.util.FlxTimer;
+
+using StringTools;
+
+class Boyfriend extends Character
+{
+ public var stunned:Bool = false;
+
+ public function new(x:Float, y:Float, ?char:String = 'bf')
+ {
+ super(x, y, char, true);
+ }
+
+ override function update(elapsed:Float)
+ {
+ if (!debugMode)
+ {
+ if (animation.curAnim.name.startsWith('sing'))
+ {
+ holdTimer += elapsed;
+ }
+ else
+ holdTimer = 0;
+
+ if (animation.curAnim.name.endsWith('miss') && animation.curAnim.finished && !debugMode)
+ {
+ playAnim('idle', true, false, 10);
+ }
+
+ if (animation.curAnim.name == 'firstDeath' && animation.curAnim.finished)
+ {
+ playAnim('deathLoop');
+ }
+ }
+
+ super.update(elapsed);
+ }
+}
diff --git a/source_duck/ButtonRemapSubstate.hx b/source_duck/ButtonRemapSubstate.hx
new file mode 100644
index 00000000..64ff0794
--- /dev/null
+++ b/source_duck/ButtonRemapSubstate.hx
@@ -0,0 +1,11 @@
+package;
+
+import flixel.FlxSubState;
+
+class ButtonRemapSubstate extends FlxSubState
+{
+ public function new()
+ {
+ super();
+ }
+}
diff --git a/source_duck/Caching.hx b/source_duck/Caching.hx
new file mode 100644
index 00000000..1cbbcd66
--- /dev/null
+++ b/source_duck/Caching.hx
@@ -0,0 +1,131 @@
+package;
+
+import haxe.Exception;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+import sys.FileSystem;
+import sys.io.File;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
+import flixel.addons.transition.FlxTransitionableState;
+import flixel.addons.transition.TransitionData;
+import flixel.graphics.FlxGraphic;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.math.FlxPoint;
+import flixel.math.FlxRect;
+import flixel.util.FlxColor;
+import flixel.util.FlxTimer;
+import flixel.text.FlxText;
+
+using StringTools;
+
+class Caching extends MusicBeatState
+{
+ var toBeDone = 0;
+ var done = 0;
+
+ var text:FlxText;
+ var kadeLogo:FlxSprite;
+
+ override function create()
+ {
+ FlxG.mouse.visible = false;
+
+ FlxG.worldBounds.set(0,0);
+
+ text = new FlxText(FlxG.width / 2, FlxG.height / 2 + 300,0,"Loading...");
+ text.size = 34;
+ text.alignment = FlxTextAlign.CENTER;
+ text.alpha = 0;
+
+ kadeLogo = new FlxSprite(FlxG.width / 2, FlxG.height / 2).loadGraphic(Paths.image('KadeEngineLogo'));
+ kadeLogo.x -= kadeLogo.width / 2;
+ kadeLogo.y -= kadeLogo.height / 2 + 100;
+ text.y -= kadeLogo.height / 2 - 125;
+ text.x -= 170;
+ kadeLogo.setGraphicSize(Std.int(kadeLogo.width * 0.6));
+
+ kadeLogo.alpha = 0;
+
+ add(kadeLogo);
+ add(text);
+
+ trace('starting caching..');
+
+ sys.thread.Thread.create(() -> {
+ cache();
+ });
+
+
+ super.create();
+ }
+
+ var calledDone = false;
+
+ override function update(elapsed)
+ {
+
+ if (toBeDone != 0 && done != toBeDone)
+ {
+ var alpha = HelperFunctions.truncateFloat(done / toBeDone * 100,2) / 100;
+ kadeLogo.alpha = alpha;
+ text.alpha = alpha;
+ text.text = "Loading... (" + done + "/" + toBeDone + ")";
+ }
+
+ super.update(elapsed);
+ }
+
+
+ function cache()
+ {
+
+ var images = [];
+ var music = [];
+ var charts = [];
+
+ trace("caching images...");
+
+ for (i in FileSystem.readDirectory(FileSystem.absolutePath("assets/shared/images/characters")))
+ {
+ if (!i.endsWith(".png"))
+ continue;
+ images.push(i);
+ }
+
+ trace("caching music...");
+
+ for (i in FileSystem.readDirectory(FileSystem.absolutePath("assets/songs")))
+ {
+ music.push(i);
+ }
+
+
+ toBeDone = Lambda.count(images) + Lambda.count(music);
+
+ trace("LOADING: " + toBeDone + " OBJECTS.");
+
+ for (i in images)
+ {
+ var replaced = i.replace(".png","");
+ FlxG.bitmap.add(Paths.image("characters/" + replaced,"shared"));
+ trace("cached " + replaced);
+ done++;
+ }
+
+ for (i in music)
+ {
+ FlxG.sound.cache(Paths.inst(i));
+ FlxG.sound.cache(Paths.voices(i));
+ trace("cached " + i);
+ done++;
+ }
+
+
+ trace("Finished caching...");
+
+ FlxG.switchState(new TitleState());
+ }
+
+}
\ No newline at end of file
diff --git a/source_duck/Character.hx b/source_duck/Character.hx
new file mode 100644
index 00000000..a2f88274
--- /dev/null
+++ b/source_duck/Character.hx
@@ -0,0 +1,672 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.animation.FlxBaseAnimation;
+import flixel.graphics.frames.FlxAtlasFrames;
+
+using StringTools;
+
+class Character extends FlxSprite
+{
+ public var animOffsets:Map>;
+ public var debugMode:Bool = false;
+
+ public var isPlayer:Bool = false;
+ public var curCharacter:String = 'bf';
+
+ public var holdTimer:Float = 0;
+
+ public function new(x:Float, y:Float, ?character:String = "bf", ?isPlayer:Bool = false)
+ {
+ super(x, y);
+
+ animOffsets = new Map>();
+ curCharacter = character;
+ this.isPlayer = isPlayer;
+
+ var tex:FlxAtlasFrames;
+ antialiasing = true;
+
+ switch (curCharacter)
+ {
+ case 'gf':
+ // GIRLFRIEND CODE
+ tex = Paths.getSparrowAtlas('characters/GF_assets');
+ frames = tex;
+ animation.addByPrefix('cheer', 'GF Cheer', 24, false);
+ animation.addByPrefix('singLEFT', 'GF left note', 24, false);
+ animation.addByPrefix('singRIGHT', 'GF Right Note', 24, false);
+ animation.addByPrefix('singUP', 'GF Up Note', 24, false);
+ animation.addByPrefix('singDOWN', 'GF Down Note', 24, false);
+ animation.addByIndices('sad', 'gf sad', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "", 24, false);
+ animation.addByIndices('danceLeft', 'GF Dancing Beat', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
+ animation.addByIndices('danceRight', 'GF Dancing Beat', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
+ animation.addByIndices('hairBlow', "GF Dancing Beat Hair blowing", [0, 1, 2, 3], "", 24);
+ animation.addByIndices('hairFall', "GF Dancing Beat Hair Landing", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "", 24, false);
+ animation.addByPrefix('scared', 'GF FEAR', 24);
+
+ addOffset('cheer');
+ addOffset('sad', -2, -2);
+ addOffset('danceLeft', 0, -9);
+ addOffset('danceRight', 0, -9);
+
+ addOffset("singUP", 0, 4);
+ addOffset("singRIGHT", 0, -20);
+ addOffset("singLEFT", 0, -19);
+ addOffset("singDOWN", 0, -20);
+ addOffset('hairBlow', 45, -8);
+ addOffset('hairFall', 0, -9);
+
+ addOffset('scared', -2, -17);
+
+ playAnim('danceRight');
+
+ case 'gf-christmas':
+ tex = Paths.getSparrowAtlas('characters/gfChristmas');
+ frames = tex;
+ animation.addByPrefix('cheer', 'GF Cheer', 24, false);
+ animation.addByPrefix('singLEFT', 'GF left note', 24, false);
+ animation.addByPrefix('singRIGHT', 'GF Right Note', 24, false);
+ animation.addByPrefix('singUP', 'GF Up Note', 24, false);
+ animation.addByPrefix('singDOWN', 'GF Down Note', 24, false);
+ animation.addByIndices('sad', 'gf sad', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "", 24, false);
+ animation.addByIndices('danceLeft', 'GF Dancing Beat', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
+ animation.addByIndices('danceRight', 'GF Dancing Beat', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
+ animation.addByIndices('hairBlow', "GF Dancing Beat Hair blowing", [0, 1, 2, 3], "", 24);
+ animation.addByIndices('hairFall', "GF Dancing Beat Hair Landing", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "", 24, false);
+ animation.addByPrefix('scared', 'GF FEAR', 24);
+
+ addOffset('cheer');
+ addOffset('sad', -2, -2);
+ addOffset('danceLeft', 0, -9);
+ addOffset('danceRight', 0, -9);
+
+ addOffset("singUP", 0, 4);
+ addOffset("singRIGHT", 0, -20);
+ addOffset("singLEFT", 0, -19);
+ addOffset("singDOWN", 0, -20);
+ addOffset('hairBlow', 45, -8);
+ addOffset('hairFall', 0, -9);
+
+ addOffset('scared', -2, -17);
+
+ playAnim('danceRight');
+
+ case 'gf-car':
+ tex = Paths.getSparrowAtlas('characters/gfCar');
+ frames = tex;
+ animation.addByIndices('singUP', 'GF Dancing Beat Hair blowing CAR', [0], "", 24, false);
+ animation.addByIndices('danceLeft', 'GF Dancing Beat Hair blowing CAR', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
+ animation.addByIndices('danceRight', 'GF Dancing Beat Hair blowing CAR', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24,
+ false);
+
+ addOffset('danceLeft', 0);
+ addOffset('danceRight', 0);
+
+ playAnim('danceRight');
+
+ case 'gf-pixel':
+ tex = Paths.getSparrowAtlas('characters/gfPixel');
+ frames = tex;
+ animation.addByIndices('singUP', 'GF IDLE', [2], "", 24, false);
+ animation.addByIndices('danceLeft', 'GF IDLE', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
+ animation.addByIndices('danceRight', 'GF IDLE', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
+
+ addOffset('danceLeft', 0);
+ addOffset('danceRight', 0);
+
+ playAnim('danceRight');
+
+ setGraphicSize(Std.int(width * PlayState.daPixelZoom));
+ updateHitbox();
+ antialiasing = false;
+
+ case 'dad':
+ // DAD ANIMATION LOADING CODE
+ tex = Paths.getSparrowAtlas('characters/DADDY_DEAREST');
+ frames = tex;
+ animation.addByPrefix('idle', 'Dad idle dance', 24);
+ animation.addByPrefix('singUP', 'Dad Sing Note UP', 24);
+ animation.addByPrefix('singRIGHT', 'Dad Sing Note RIGHT', 24);
+ animation.addByPrefix('singDOWN', 'Dad Sing Note DOWN', 24);
+ animation.addByPrefix('singLEFT', 'Dad Sing Note LEFT', 24);
+
+ addOffset('idle');
+ addOffset("singUP", -6, 50);
+ addOffset("singRIGHT", 0, 27);
+ addOffset("singLEFT", -10, 10);
+ addOffset("singDOWN", 0, -30);
+
+ playAnim('idle');
+ case 'spooky':
+ tex = Paths.getSparrowAtlas('characters/spooky_kids_assets');
+ frames = tex;
+ animation.addByPrefix('singUP', 'spooky UP NOTE', 24, false);
+ animation.addByPrefix('singDOWN', 'spooky DOWN note', 24, false);
+ animation.addByPrefix('singLEFT', 'note sing left', 24, false);
+ animation.addByPrefix('singRIGHT', 'spooky sing right', 24, false);
+ animation.addByIndices('danceLeft', 'spooky dance idle', [0, 2, 6], "", 12, false);
+ animation.addByIndices('danceRight', 'spooky dance idle', [8, 10, 12, 14], "", 12, false);
+
+ addOffset('danceLeft');
+ addOffset('danceRight');
+
+ addOffset("singUP", -20, 26);
+ addOffset("singRIGHT", -130, -14);
+ addOffset("singLEFT", 130, -10);
+ addOffset("singDOWN", -50, -130);
+
+ playAnim('danceRight');
+ case 'mom':
+ tex = Paths.getSparrowAtlas('characters/Mom_Assets');
+ frames = tex;
+
+ animation.addByPrefix('idle', "Mom Idle", 24, false);
+ animation.addByPrefix('singUP', "Mom Up Pose", 24, false);
+ animation.addByPrefix('singDOWN', "MOM DOWN POSE", 24, false);
+ animation.addByPrefix('singLEFT', 'Mom Left Pose', 24, false);
+ // ANIMATION IS CALLED MOM LEFT POSE BUT ITS FOR THE RIGHT
+ // CUZ DAVE IS DUMB!
+ animation.addByPrefix('singRIGHT', 'Mom Pose Left', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", 14, 71);
+ addOffset("singRIGHT", 10, -60);
+ addOffset("singLEFT", 250, -23);
+ addOffset("singDOWN", 20, -160);
+
+ playAnim('idle');
+
+ case 'mom-car':
+ tex = Paths.getSparrowAtlas('characters/momCar');
+ frames = tex;
+
+ animation.addByPrefix('idle', "Mom Idle", 24, false);
+ animation.addByPrefix('singUP', "Mom Up Pose", 24, false);
+ animation.addByPrefix('singDOWN', "MOM DOWN POSE", 24, false);
+ animation.addByPrefix('singLEFT', 'Mom Left Pose', 24, false);
+ // ANIMATION IS CALLED MOM LEFT POSE BUT ITS FOR THE RIGHT
+ // CUZ DAVE IS DUMB!
+ animation.addByPrefix('singRIGHT', 'Mom Pose Left', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", 14, 71);
+ addOffset("singRIGHT", 10, -60);
+ addOffset("singLEFT", 250, -23);
+ addOffset("singDOWN", 20, -160);
+
+ playAnim('idle');
+ case 'monster':
+ tex = Paths.getSparrowAtlas('characters/Monster_Assets');
+ frames = tex;
+ animation.addByPrefix('idle', 'monster idle', 24, false);
+ animation.addByPrefix('singUP', 'monster up note', 24, false);
+ animation.addByPrefix('singDOWN', 'monster down', 24, false);
+ animation.addByPrefix('singLEFT', 'Monster left note', 24, false);
+ animation.addByPrefix('singRIGHT', 'Monster Right note', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", -20, 50);
+ addOffset("singRIGHT", -51);
+ addOffset("singLEFT", -30);
+ addOffset("singDOWN", -30, -40);
+ playAnim('idle');
+ case 'monster-christmas':
+ tex = Paths.getSparrowAtlas('characters/monsterChristmas');
+ frames = tex;
+ animation.addByPrefix('idle', 'monster idle', 24, false);
+ animation.addByPrefix('singUP', 'monster up note', 24, false);
+ animation.addByPrefix('singDOWN', 'monster down', 24, false);
+ animation.addByPrefix('singLEFT', 'Monster left note', 24, false);
+ animation.addByPrefix('singRIGHT', 'Monster Right note', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", -20, 50);
+ addOffset("singRIGHT", -51);
+ addOffset("singLEFT", -30);
+ addOffset("singDOWN", -40, -94);
+ playAnim('idle');
+ case 'pico':
+ tex = Paths.getSparrowAtlas('characters/Pico_FNF_assetss');
+ frames = tex;
+ animation.addByPrefix('idle', "Pico Idle Dance", 24);
+ animation.addByPrefix('singUP', 'pico Up note0', 24, false);
+ animation.addByPrefix('singDOWN', 'Pico Down Note0', 24, false);
+ if (isPlayer)
+ {
+ animation.addByPrefix('singLEFT', 'Pico NOTE LEFT0', 24, false);
+ animation.addByPrefix('singRIGHT', 'Pico Note Right0', 24, false);
+ animation.addByPrefix('singRIGHTmiss', 'Pico Note Right Miss', 24, false);
+ animation.addByPrefix('singLEFTmiss', 'Pico NOTE LEFT miss', 24, false);
+ }
+ else
+ {
+ // Need to be flipped! REDO THIS LATER!
+ animation.addByPrefix('singLEFT', 'Pico Note Right0', 24, false);
+ animation.addByPrefix('singRIGHT', 'Pico NOTE LEFT0', 24, false);
+ animation.addByPrefix('singRIGHTmiss', 'Pico NOTE LEFT miss', 24, false);
+ animation.addByPrefix('singLEFTmiss', 'Pico Note Right Miss', 24, false);
+ }
+
+ animation.addByPrefix('singUPmiss', 'pico Up note miss', 24);
+ animation.addByPrefix('singDOWNmiss', 'Pico Down Note MISS', 24);
+
+ addOffset('idle');
+ addOffset("singUP", -29, 27);
+ addOffset("singRIGHT", -68, -7);
+ addOffset("singLEFT", 65, 9);
+ addOffset("singDOWN", 200, -70);
+ addOffset("singUPmiss", -19, 67);
+ addOffset("singRIGHTmiss", -60, 41);
+ addOffset("singLEFTmiss", 62, 64);
+ addOffset("singDOWNmiss", 210, -28);
+
+ playAnim('idle');
+
+ flipX = true;
+
+ case 'bf':
+ var tex = Paths.getSparrowAtlas('characters/BOYFRIEND');
+ frames = tex;
+ animation.addByPrefix('idle', 'BF idle dance', 24, false);
+ animation.addByPrefix('singUP', 'BF NOTE UP0', 24, false);
+ animation.addByPrefix('singLEFT', 'BF NOTE LEFT0', 24, false);
+ animation.addByPrefix('singRIGHT', 'BF NOTE RIGHT0', 24, false);
+ animation.addByPrefix('singDOWN', 'BF NOTE DOWN0', 24, false);
+ animation.addByPrefix('singUPmiss', 'BF NOTE UP MISS', 24, false);
+ animation.addByPrefix('singLEFTmiss', 'BF NOTE LEFT MISS', 24, false);
+ animation.addByPrefix('singRIGHTmiss', 'BF NOTE RIGHT MISS', 24, false);
+ animation.addByPrefix('singDOWNmiss', 'BF NOTE DOWN MISS', 24, false);
+ animation.addByPrefix('hey', 'BF HEY', 24, false);
+
+ animation.addByPrefix('firstDeath', "BF dies", 24, false);
+ animation.addByPrefix('deathLoop', "BF Dead Loop", 24, true);
+ animation.addByPrefix('deathConfirm', "BF Dead confirm", 24, false);
+
+ animation.addByPrefix('scared', 'BF idle shaking', 24);
+
+ addOffset('idle', -5);
+ addOffset("singUP", -29, 27);
+ addOffset("singRIGHT", -38, -7);
+ addOffset("singLEFT", 12, -6);
+ addOffset("singDOWN", -10, -50);
+ addOffset("singUPmiss", -29, 27);
+ addOffset("singRIGHTmiss", -30, 21);
+ addOffset("singLEFTmiss", 12, 24);
+ addOffset("singDOWNmiss", -11, -19);
+ addOffset("hey", 7, 4);
+ addOffset('firstDeath', 37, 11);
+ addOffset('deathLoop', 37, 5);
+ addOffset('deathConfirm', 37, 69);
+ addOffset('scared', -4);
+
+ playAnim('idle');
+
+ flipX = true;
+
+ case 'bf-christmas':
+ var tex = Paths.getSparrowAtlas('characters/bfChristmas');
+ frames = tex;
+ animation.addByPrefix('idle', 'BF idle dance', 24, false);
+ animation.addByPrefix('singUP', 'BF NOTE UP0', 24, false);
+ animation.addByPrefix('singLEFT', 'BF NOTE LEFT0', 24, false);
+ animation.addByPrefix('singRIGHT', 'BF NOTE RIGHT0', 24, false);
+ animation.addByPrefix('singDOWN', 'BF NOTE DOWN0', 24, false);
+ animation.addByPrefix('singUPmiss', 'BF NOTE UP MISS', 24, false);
+ animation.addByPrefix('singLEFTmiss', 'BF NOTE LEFT MISS', 24, false);
+ animation.addByPrefix('singRIGHTmiss', 'BF NOTE RIGHT MISS', 24, false);
+ animation.addByPrefix('singDOWNmiss', 'BF NOTE DOWN MISS', 24, false);
+ animation.addByPrefix('hey', 'BF HEY', 24, false);
+
+ addOffset('idle', -5);
+ addOffset("singUP", -29, 27);
+ addOffset("singRIGHT", -38, -7);
+ addOffset("singLEFT", 12, -6);
+ addOffset("singDOWN", -10, -50);
+ addOffset("singUPmiss", -29, 27);
+ addOffset("singRIGHTmiss", -30, 21);
+ addOffset("singLEFTmiss", 12, 24);
+ addOffset("singDOWNmiss", -11, -19);
+ addOffset("hey", 7, 4);
+
+ playAnim('idle');
+
+ flipX = true;
+ case 'bf-car':
+ var tex = Paths.getSparrowAtlas('characters/bfCar');
+ frames = tex;
+ animation.addByPrefix('idle', 'BF idle dance', 24, false);
+ animation.addByPrefix('singUP', 'BF NOTE UP0', 24, false);
+ animation.addByPrefix('singLEFT', 'BF NOTE LEFT0', 24, false);
+ animation.addByPrefix('singRIGHT', 'BF NOTE RIGHT0', 24, false);
+ animation.addByPrefix('singDOWN', 'BF NOTE DOWN0', 24, false);
+ animation.addByPrefix('singUPmiss', 'BF NOTE UP MISS', 24, false);
+ animation.addByPrefix('singLEFTmiss', 'BF NOTE LEFT MISS', 24, false);
+ animation.addByPrefix('singRIGHTmiss', 'BF NOTE RIGHT MISS', 24, false);
+ animation.addByPrefix('singDOWNmiss', 'BF NOTE DOWN MISS', 24, false);
+
+ addOffset('idle', -5);
+ addOffset("singUP", -29, 27);
+ addOffset("singRIGHT", -38, -7);
+ addOffset("singLEFT", 12, -6);
+ addOffset("singDOWN", -10, -50);
+ addOffset("singUPmiss", -29, 27);
+ addOffset("singRIGHTmiss", -30, 21);
+ addOffset("singLEFTmiss", 12, 24);
+ addOffset("singDOWNmiss", -11, -19);
+ playAnim('idle');
+
+ flipX = true;
+ case 'bf-pixel':
+ frames = Paths.getSparrowAtlas('characters/bfPixel');
+ animation.addByPrefix('idle', 'BF IDLE', 24, false);
+ animation.addByPrefix('singUP', 'BF UP NOTE', 24, false);
+ animation.addByPrefix('singLEFT', 'BF LEFT NOTE', 24, false);
+ animation.addByPrefix('singRIGHT', 'BF RIGHT NOTE', 24, false);
+ animation.addByPrefix('singDOWN', 'BF DOWN NOTE', 24, false);
+ animation.addByPrefix('singUPmiss', 'BF UP MISS', 24, false);
+ animation.addByPrefix('singLEFTmiss', 'BF LEFT MISS', 24, false);
+ animation.addByPrefix('singRIGHTmiss', 'BF RIGHT MISS', 24, false);
+ animation.addByPrefix('singDOWNmiss', 'BF DOWN MISS', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP");
+ addOffset("singRIGHT");
+ addOffset("singLEFT");
+ addOffset("singDOWN");
+ addOffset("singUPmiss");
+ addOffset("singRIGHTmiss");
+ addOffset("singLEFTmiss");
+ addOffset("singDOWNmiss");
+
+ setGraphicSize(Std.int(width * 6));
+ updateHitbox();
+
+ playAnim('idle');
+
+ width -= 100;
+ height -= 100;
+
+ antialiasing = false;
+
+ flipX = true;
+ case 'bf-pixel-dead':
+ frames = Paths.getSparrowAtlas('characters/bfPixelsDEAD');
+ animation.addByPrefix('singUP', "BF Dies pixel", 24, false);
+ animation.addByPrefix('firstDeath', "BF Dies pixel", 24, false);
+ animation.addByPrefix('deathLoop', "Retry Loop", 24, true);
+ animation.addByPrefix('deathConfirm', "RETRY CONFIRM", 24, false);
+ animation.play('firstDeath');
+
+ addOffset('firstDeath');
+ addOffset('deathLoop', -37);
+ addOffset('deathConfirm', -37);
+ playAnim('firstDeath');
+ // pixel bullshit
+ setGraphicSize(Std.int(width * 6));
+ updateHitbox();
+ antialiasing = false;
+ flipX = true;
+
+ case 'senpai':
+ frames = Paths.getSparrowAtlas('characters/senpai');
+ animation.addByPrefix('idle', 'Senpai Idle', 24, false);
+ animation.addByPrefix('singUP', 'SENPAI UP NOTE', 24, false);
+ animation.addByPrefix('singLEFT', 'SENPAI LEFT NOTE', 24, false);
+ animation.addByPrefix('singRIGHT', 'SENPAI RIGHT NOTE', 24, false);
+ animation.addByPrefix('singDOWN', 'SENPAI DOWN NOTE', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", 5, 37);
+ addOffset("singRIGHT");
+ addOffset("singLEFT", 40);
+ addOffset("singDOWN", 14);
+
+ playAnim('idle');
+
+ setGraphicSize(Std.int(width * 6));
+ updateHitbox();
+
+ antialiasing = false;
+ case 'senpai-angry':
+ frames = Paths.getSparrowAtlas('characters/senpai');
+ animation.addByPrefix('idle', 'Angry Senpai Idle', 24, false);
+ animation.addByPrefix('singUP', 'Angry Senpai UP NOTE', 24, false);
+ animation.addByPrefix('singLEFT', 'Angry Senpai LEFT NOTE', 24, false);
+ animation.addByPrefix('singRIGHT', 'Angry Senpai RIGHT NOTE', 24, false);
+ animation.addByPrefix('singDOWN', 'Angry Senpai DOWN NOTE', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", 5, 37);
+ addOffset("singRIGHT");
+ addOffset("singLEFT", 40);
+ addOffset("singDOWN", 14);
+ playAnim('idle');
+
+ setGraphicSize(Std.int(width * 6));
+ updateHitbox();
+
+ antialiasing = false;
+
+ case 'spirit':
+ frames = Paths.getPackerAtlas('characters/spirit');
+ animation.addByPrefix('idle', "idle spirit_", 24, false);
+ animation.addByPrefix('singUP', "up_", 24, false);
+ animation.addByPrefix('singRIGHT', "right_", 24, false);
+ animation.addByPrefix('singLEFT', "left_", 24, false);
+ animation.addByPrefix('singDOWN', "spirit down_", 24, false);
+
+ addOffset('idle', -220, -280);
+ addOffset('singUP', -220, -240);
+ addOffset("singRIGHT", -220, -280);
+ addOffset("singLEFT", -200, -280);
+ addOffset("singDOWN", 170, 110);
+
+ setGraphicSize(Std.int(width * 6));
+ updateHitbox();
+
+ playAnim('idle');
+
+ antialiasing = false;
+
+ case 'parents-christmas':
+ frames = Paths.getSparrowAtlas('characters/mom_dad_christmas_assets');
+ animation.addByPrefix('idle', 'Parent Christmas Idle', 24, false);
+ animation.addByPrefix('singUP', 'Parent Up Note Dad', 24, false);
+ animation.addByPrefix('singDOWN', 'Parent Down Note Dad', 24, false);
+ animation.addByPrefix('singLEFT', 'Parent Left Note Dad', 24, false);
+ animation.addByPrefix('singRIGHT', 'Parent Right Note Dad', 24, false);
+
+ animation.addByPrefix('singUP-alt', 'Parent Up Note Mom', 24, false);
+
+ animation.addByPrefix('singDOWN-alt', 'Parent Down Note Mom', 24, false);
+ animation.addByPrefix('singLEFT-alt', 'Parent Left Note Mom', 24, false);
+ animation.addByPrefix('singRIGHT-alt', 'Parent Right Note Mom', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP", -47, 24);
+ addOffset("singRIGHT", -1, -23);
+ addOffset("singLEFT", -30, 16);
+ addOffset("singDOWN", -31, -29);
+ addOffset("singUP-alt", -47, 24);
+ addOffset("singRIGHT-alt", -1, -24);
+ addOffset("singLEFT-alt", -30, 15);
+ addOffset("singDOWN-alt", -30, -27);
+
+ playAnim('idle');
+
+ case 'duck':
+ frames = Paths.getSparrowAtlas('duck/duck_assets');
+ animation.addByPrefix('idle', 'duck idle', 24, false);
+ animation.addByPrefix('singUP', 'duck up', 24, false);
+ animation.addByPrefix('singDOWN', 'duck down', 24, false);
+ animation.addByPrefix('singLEFT', 'duck left', 24, false);
+ animation.addByPrefix('singRIGHT', 'duck right', 24, false);
+
+ addOffset('idle');
+ addOffset("singUP");
+ addOffset('singRIGHT');
+ addOffset('singLEFT');
+ addOffset('singDOWN');
+
+ playAnim('idle');
+ }
+
+ dance();
+
+ if (isPlayer)
+ {
+ flipX = !flipX;
+
+ // Doesn't flip for BF, since his are already in the right place???
+ if (!curCharacter.startsWith('bf'))
+ {
+ // var animArray
+ var oldRight = animation.getByName('singRIGHT').frames;
+ animation.getByName('singRIGHT').frames = animation.getByName('singLEFT').frames;
+ animation.getByName('singLEFT').frames = oldRight;
+
+ // IF THEY HAVE MISS ANIMATIONS??
+ if (animation.getByName('singRIGHTmiss') != null)
+ {
+ var oldMiss = animation.getByName('singRIGHTmiss').frames;
+ animation.getByName('singRIGHTmiss').frames = animation.getByName('singLEFTmiss').frames;
+ animation.getByName('singLEFTmiss').frames = oldMiss;
+ }
+ }
+ }
+ }
+
+ override function update(elapsed:Float)
+ {
+ if (!curCharacter.startsWith('bf'))
+ {
+ if (animation.curAnim.name.startsWith('sing'))
+ {
+ holdTimer += elapsed;
+ }
+
+ var dadVar:Float = 4;
+
+ if (curCharacter == 'dad')
+ dadVar = 6.1;
+ if (holdTimer >= Conductor.stepCrochet * dadVar * 0.001)
+ {
+ dance();
+ holdTimer = 0;
+ }
+ }
+
+ switch (curCharacter)
+ {
+ case 'gf':
+ if (animation.curAnim.name == 'hairFall' && animation.curAnim.finished)
+ playAnim('danceRight');
+ }
+
+ super.update(elapsed);
+ }
+
+ private var danced:Bool = false;
+
+ /**
+ * FOR GF DANCING SHIT
+ */
+ public function dance()
+ {
+ if (!debugMode)
+ {
+ switch (curCharacter)
+ {
+ case 'gf':
+ if (!animation.curAnim.name.startsWith('hair'))
+ {
+ danced = !danced;
+
+ if (danced)
+ playAnim('danceRight');
+ else
+ playAnim('danceLeft');
+ }
+
+ case 'gf-christmas':
+ if (!animation.curAnim.name.startsWith('hair'))
+ {
+ danced = !danced;
+
+ if (danced)
+ playAnim('danceRight');
+ else
+ playAnim('danceLeft');
+ }
+
+ case 'gf-car':
+ if (!animation.curAnim.name.startsWith('hair'))
+ {
+ danced = !danced;
+
+ if (danced)
+ playAnim('danceRight');
+ else
+ playAnim('danceLeft');
+ }
+ case 'gf-pixel':
+ if (!animation.curAnim.name.startsWith('hair'))
+ {
+ danced = !danced;
+
+ if (danced)
+ playAnim('danceRight');
+ else
+ playAnim('danceLeft');
+ }
+
+ case 'spooky':
+ danced = !danced;
+
+ if (danced)
+ playAnim('danceRight');
+ else
+ playAnim('danceLeft');
+ default:
+ playAnim('idle');
+ }
+ }
+ }
+
+ public function playAnim(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
+ {
+ animation.play(AnimName, Force, Reversed, Frame);
+
+ var daOffset = animOffsets.get(AnimName);
+ if (animOffsets.exists(AnimName))
+ {
+ offset.set(daOffset[0], daOffset[1]);
+ }
+ else
+ offset.set(0, 0);
+
+ if (curCharacter == 'gf')
+ {
+ if (AnimName == 'singLEFT')
+ {
+ danced = true;
+ }
+ else if (AnimName == 'singRIGHT')
+ {
+ danced = false;
+ }
+
+ if (AnimName == 'singUP' || AnimName == 'singDOWN')
+ {
+ danced = !danced;
+ }
+ }
+ }
+
+ public function addOffset(name:String, x:Float = 0, y:Float = 0)
+ {
+ animOffsets[name] = [x, y];
+ }
+}
diff --git a/source_duck/ChartParser.hx b/source_duck/ChartParser.hx
new file mode 100644
index 00000000..3884c0e5
--- /dev/null
+++ b/source_duck/ChartParser.hx
@@ -0,0 +1,80 @@
+package;
+
+import flixel.util.FlxStringUtil;
+
+using StringTools;
+
+class ChartParser
+{
+ static public function parse(songName:String, section:Int):Array
+ {
+ var IMG_WIDTH:Int = 8;
+ var regex:EReg = new EReg("[ \t]*((\r\n)|\r|\n)[ \t]*", "g");
+
+ var csvData = FlxStringUtil.imageToCSV(Paths.file('data/' + songName + '/' + songName + '_section' + section + '.png'));
+
+ var lines:Array = regex.split(csvData);
+ var rows:Array = lines.filter(function(line) return line != "");
+ csvData.replace("\n", ',');
+
+ var heightInTiles = rows.length;
+ var widthInTiles = 0;
+
+ var row:Int = 0;
+
+ // LMAOOOO STOLE ALL THIS FROM FLXBASETILEMAP LOLOL
+
+ var dopeArray:Array = [];
+ while (row < heightInTiles)
+ {
+ var rowString = rows[row];
+ if (rowString.endsWith(","))
+ rowString = rowString.substr(0, rowString.length - 1);
+ var columns = rowString.split(",");
+
+ if (columns.length == 0)
+ {
+ heightInTiles--;
+ continue;
+ }
+ if (widthInTiles == 0)
+ {
+ widthInTiles = columns.length;
+ }
+
+ var column = 0;
+ var pushedInColumn:Bool = false;
+ while (column < widthInTiles)
+ {
+ // the current tile to be added:
+ var columnString = columns[column];
+ var curTile = Std.parseInt(columnString);
+
+ if (curTile == null)
+ throw 'String in row $row, column $column is not a valid integer: "$columnString"';
+
+ if (curTile == 1)
+ {
+ if (column < 4)
+ dopeArray.push(column + 1);
+ else
+ {
+ var tempCol = (column + 1) * -1;
+ tempCol += 4;
+ dopeArray.push(tempCol);
+ }
+
+ pushedInColumn = true;
+ }
+
+ column++;
+ }
+
+ if (!pushedInColumn)
+ dopeArray.push(0);
+
+ row++;
+ }
+ return dopeArray;
+ }
+}
diff --git a/source_duck/ChartingState.hx b/source_duck/ChartingState.hx
new file mode 100644
index 00000000..93c5cfa1
--- /dev/null
+++ b/source_duck/ChartingState.hx
@@ -0,0 +1,1351 @@
+package;
+
+import flixel.addons.ui.FlxUIText;
+import haxe.zip.Writer;
+import Conductor.BPMChangeEvent;
+import Section.SwagSection;
+import Song.SwagSong;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.display.FlxGridOverlay;
+import flixel.addons.ui.FlxInputText;
+import flixel.addons.ui.FlxUI9SliceSprite;
+import flixel.addons.ui.FlxUI;
+import flixel.addons.ui.FlxUICheckBox;
+import flixel.addons.ui.FlxUIDropDownMenu;
+import flixel.addons.ui.FlxUIInputText;
+import flixel.addons.ui.FlxUINumericStepper;
+import flixel.addons.ui.FlxUITabMenu;
+import flixel.addons.ui.FlxUITooltip.FlxUITooltipStyle;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.group.FlxGroup;
+import flixel.math.FlxMath;
+import flixel.math.FlxPoint;
+import flixel.system.FlxSound;
+import flixel.text.FlxText;
+import flixel.ui.FlxButton;
+import flixel.ui.FlxSpriteButton;
+import flixel.util.FlxColor;
+import haxe.Json;
+import lime.utils.Assets;
+import openfl.events.Event;
+import openfl.events.IOErrorEvent;
+import openfl.events.IOErrorEvent;
+import openfl.events.IOErrorEvent;
+import openfl.media.Sound;
+import openfl.net.FileReference;
+import openfl.utils.ByteArray;
+
+using StringTools;
+
+class ChartingState extends MusicBeatState
+{
+ var _file:FileReference;
+
+ var UI_box:FlxUITabMenu;
+
+ /**
+ * Array of notes showing when each section STARTS in STEPS
+ * Usually rounded up??
+ */
+ var curSection:Int = 0;
+
+ public static var lastSection:Int = 0;
+
+ var bpmTxt:FlxText;
+
+ var strumLine:FlxSprite;
+ var curSong:String = 'Dadbattle';
+ var amountSteps:Int = 0;
+ var bullshitUI:FlxGroup;
+ var writingNotesText:FlxText;
+ var highlight:FlxSprite;
+
+ var GRID_SIZE:Int = 40;
+
+ var dummyArrow:FlxSprite;
+
+ var curRenderedNotes:FlxTypedGroup;
+ var curRenderedSustains:FlxTypedGroup;
+
+ var gridBG:FlxSprite;
+
+ var _song:SwagSong;
+
+ var typingShit:FlxInputText;
+ /*
+ * WILL BE THE CURRENT / LAST PLACED NOTE
+ **/
+ var curSelectedNote:Array;
+
+ var tempBpm:Int = 0;
+ var gridBlackLine:FlxSprite;
+ var vocals:FlxSound;
+
+ var player2:Character = new Character(0,0, "dad");
+ var player1:Boyfriend = new Boyfriend(0,0, "bf");
+
+ var leftIcon:HealthIcon;
+ var rightIcon:HealthIcon;
+
+ private var lastNote:Note;
+
+ override function create()
+ {
+ curSection = lastSection;
+
+ if (PlayState.SONG != null)
+ _song = PlayState.SONG;
+ else
+ {
+ _song = {
+ song: 'Test',
+ notes: [],
+ bpm: 150,
+ needsVoices: true,
+ player1: 'bf',
+ player2: 'dad',
+ gfVersion: 'gf',
+ noteStyle: 'normal',
+ stage: 'stage',
+ speed: 1,
+ validScore: false
+ };
+ }
+
+ gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 8, GRID_SIZE * 16);
+ add(gridBG);
+
+ gridBlackLine = new FlxSprite(gridBG.x + gridBG.width / 2).makeGraphic(2, Std.int(gridBG.height), FlxColor.BLACK);
+ add(gridBlackLine);
+
+ curRenderedNotes = new FlxTypedGroup();
+ curRenderedSustains = new FlxTypedGroup();
+
+ FlxG.mouse.visible = true;
+ FlxG.save.bind('funkin', 'ninjamuffin99');
+
+ tempBpm = _song.bpm;
+
+ addSection();
+
+ // sections = _song.notes;
+
+ updateGrid();
+
+ loadSong(_song.song);
+ Conductor.changeBPM(_song.bpm);
+ Conductor.mapBPMChanges(_song);
+
+ leftIcon = new HealthIcon(_song.player1);
+ rightIcon = new HealthIcon(_song.player2);
+ leftIcon.scrollFactor.set(1, 1);
+ rightIcon.scrollFactor.set(1, 1);
+
+ leftIcon.setGraphicSize(0, 45);
+ rightIcon.setGraphicSize(0, 45);
+
+ add(leftIcon);
+ add(rightIcon);
+
+ leftIcon.setPosition(0, -100);
+ rightIcon.setPosition(gridBG.width / 2, -100);
+
+ bpmTxt = new FlxText(1000, 50, 0, "", 16);
+ bpmTxt.scrollFactor.set();
+ add(bpmTxt);
+
+ strumLine = new FlxSprite(0, 50).makeGraphic(Std.int(FlxG.width / 2), 4);
+ add(strumLine);
+
+ dummyArrow = new FlxSprite().makeGraphic(GRID_SIZE, GRID_SIZE);
+ add(dummyArrow);
+
+ var tabs = [
+ {name: "Song", label: 'Song Data'},
+ {name: "Section", label: 'Section Data'},
+ {name: "Note", label: 'Note Data'},
+ {name: "Assets", label: 'Assets'}
+ ];
+
+ UI_box = new FlxUITabMenu(null, tabs, true);
+
+ UI_box.resize(300, 400);
+ UI_box.x = FlxG.width / 2;
+ UI_box.y = 20;
+ add(UI_box);
+
+ addSongUI();
+ addSectionUI();
+ addNoteUI();
+
+ add(curRenderedNotes);
+ add(curRenderedSustains);
+
+ super.create();
+ }
+
+ function addSongUI():Void
+ {
+ var UI_songTitle = new FlxUIInputText(10, 10, 70, _song.song, 8);
+ typingShit = UI_songTitle;
+
+ var check_voices = new FlxUICheckBox(10, 25, null, null, "Has voice track", 100);
+ check_voices.checked = _song.needsVoices;
+ // _song.needsVoices = check_voices.checked;
+ check_voices.callback = function()
+ {
+ _song.needsVoices = check_voices.checked;
+ trace('CHECKED!');
+ };
+
+ var check_mute_inst = new FlxUICheckBox(10, 200, null, null, "Mute Instrumental (in editor)", 100);
+ check_mute_inst.checked = false;
+ check_mute_inst.callback = function()
+ {
+ var vol:Float = 1;
+
+ if (check_mute_inst.checked)
+ vol = 0;
+
+ FlxG.sound.music.volume = vol;
+ };
+
+ var saveButton:FlxButton = new FlxButton(110, 8, "Save", function()
+ {
+ saveLevel();
+ });
+
+ var reloadSong:FlxButton = new FlxButton(saveButton.x + saveButton.width + 10, saveButton.y, "Reload Audio", function()
+ {
+ loadSong(_song.song);
+ });
+
+ var reloadSongJson:FlxButton = new FlxButton(reloadSong.x, saveButton.y + 30, "Reload JSON", function()
+ {
+ loadJson(_song.song.toLowerCase());
+ });
+
+
+ var restart = new FlxButton(10,140,"Reset Chart", function()
+ {
+ for (ii in 0..._song.notes.length)
+ {
+ for (i in 0..._song.notes[ii].sectionNotes.length)
+ {
+ _song.notes[ii].sectionNotes = [];
+ }
+ }
+ resetSection(true);
+ });
+
+ var loadAutosaveBtn:FlxButton = new FlxButton(reloadSongJson.x, reloadSongJson.y + 30, 'load autosave', loadAutosave);
+
+ var stepperSpeed:FlxUINumericStepper = new FlxUINumericStepper(10, 80, 0.1, 1, 0.1, 10, 1);
+ stepperSpeed.value = _song.speed;
+ stepperSpeed.name = 'song_speed';
+
+ var stepperSpeedLabel = new FlxText(74,80,'Scroll Speed');
+
+ var stepperBPM:FlxUINumericStepper = new FlxUINumericStepper(10, 65, 0.1, 1, 1.0, 5000.0, 1);
+ stepperBPM.value = Conductor.bpm;
+ stepperBPM.name = 'song_bpm';
+
+ var stepperBPMLabel = new FlxText(74,65,'BPM');
+
+ var characters:Array = CoolUtil.coolTextFile(Paths.txt('characterList'));
+ var gfVersions:Array = CoolUtil.coolTextFile(Paths.txt('gfVersionList'));
+ var stages:Array = CoolUtil.coolTextFile(Paths.txt('stageList'));
+ var noteStyles:Array = CoolUtil.coolTextFile(Paths.txt('noteStyleList'));
+
+ var player1DropDown = new FlxUIDropDownMenu(10, 100, FlxUIDropDownMenu.makeStrIdLabelArray(characters, true), function(character:String)
+ {
+ _song.player1 = characters[Std.parseInt(character)];
+ });
+ player1DropDown.selectedLabel = _song.player1;
+
+ var player1Label = new FlxText(10,80,64,'Player 1');
+
+ var player2DropDown = new FlxUIDropDownMenu(140, 100, FlxUIDropDownMenu.makeStrIdLabelArray(characters, true), function(character:String)
+ {
+ _song.player2 = characters[Std.parseInt(character)];
+ });
+ player2DropDown.selectedLabel = _song.player2;
+
+ var player2Label = new FlxText(140,80,64,'Player 2');
+
+ var gfVersionDropDown = new FlxUIDropDownMenu(10, 200, FlxUIDropDownMenu.makeStrIdLabelArray(gfVersions, true), function(gfVersion:String)
+ {
+ _song.gfVersion = gfVersions[Std.parseInt(gfVersion)];
+ });
+ gfVersionDropDown.selectedLabel = _song.gfVersion;
+
+ var gfVersionLabel = new FlxText(10,180,64,'Girlfriend');
+
+ var stageDropDown = new FlxUIDropDownMenu(140, 200, FlxUIDropDownMenu.makeStrIdLabelArray(stages, true), function(stage:String)
+ {
+ _song.stage = stages[Std.parseInt(stage)];
+ });
+ stageDropDown.selectedLabel = _song.stage;
+
+ var stageLabel = new FlxText(140,180,64,'Stage');
+
+ var noteStyleDropDown = new FlxUIDropDownMenu(10, 300, FlxUIDropDownMenu.makeStrIdLabelArray(noteStyles, true), function(noteStyle:String)
+ {
+ _song.noteStyle = noteStyles[Std.parseInt(noteStyle)];
+ });
+ noteStyleDropDown.selectedLabel = _song.noteStyle;
+
+ var noteStyleLabel = new FlxText(10,280,64,'Note Skin');
+
+ var tab_group_song = new FlxUI(null, UI_box);
+ tab_group_song.name = "Song";
+ tab_group_song.add(UI_songTitle);
+ tab_group_song.add(restart);
+ tab_group_song.add(check_voices);
+ tab_group_song.add(check_mute_inst);
+ tab_group_song.add(saveButton);
+ tab_group_song.add(reloadSong);
+ tab_group_song.add(reloadSongJson);
+ tab_group_song.add(loadAutosaveBtn);
+ tab_group_song.add(stepperBPM);
+ tab_group_song.add(stepperBPMLabel);
+ tab_group_song.add(stepperSpeed);
+ tab_group_song.add(stepperSpeedLabel);
+
+ var tab_group_assets = new FlxUI(null, UI_box);
+ tab_group_assets.name = "Assets";
+ tab_group_assets.add(noteStyleDropDown);
+ tab_group_assets.add(noteStyleLabel);
+ tab_group_assets.add(gfVersionDropDown);
+ tab_group_assets.add(gfVersionLabel);
+ tab_group_assets.add(stageDropDown);
+ tab_group_assets.add(stageLabel);
+ tab_group_assets.add(player1DropDown);
+ tab_group_assets.add(player2DropDown);
+ tab_group_assets.add(player1Label);
+ tab_group_assets.add(player2Label);
+
+ UI_box.addGroup(tab_group_song);
+ UI_box.addGroup(tab_group_assets);
+ UI_box.scrollFactor.set();
+
+ FlxG.camera.follow(strumLine);
+ }
+
+ var stepperLength:FlxUINumericStepper;
+ var check_mustHitSection:FlxUICheckBox;
+ var check_changeBPM:FlxUICheckBox;
+ var stepperSectionBPM:FlxUINumericStepper;
+ var check_altAnim:FlxUICheckBox;
+
+ function addSectionUI():Void
+ {
+ var tab_group_section = new FlxUI(null, UI_box);
+ tab_group_section.name = 'Section';
+
+ stepperLength = new FlxUINumericStepper(10, 10, 4, 0, 0, 999, 0);
+ stepperLength.value = _song.notes[curSection].lengthInSteps;
+ stepperLength.name = "section_length";
+
+ var stepperLengthLabel = new FlxText(74,10,'Section Length (in steps)');
+
+ stepperSectionBPM = new FlxUINumericStepper(10, 80, 1, Conductor.bpm, 0, 999, 0);
+ stepperSectionBPM.value = Conductor.bpm;
+ stepperSectionBPM.name = 'section_bpm';
+
+ var stepperCopy:FlxUINumericStepper = new FlxUINumericStepper(110, 132, 1, 1, -999, 999, 0);
+ var stepperCopyLabel = new FlxText(174,132,'sections back');
+
+ var copyButton:FlxButton = new FlxButton(10, 130, "Copy last section", function()
+ {
+ copySection(Std.int(stepperCopy.value));
+ });
+
+ var clearSectionButton:FlxButton = new FlxButton(10, 150, "Clear Section", clearSection);
+
+ var swapSection:FlxButton = new FlxButton(10, 170, "Swap Section", function()
+ {
+ for (i in 0..._song.notes[curSection].sectionNotes.length)
+ {
+ var note = _song.notes[curSection].sectionNotes[i];
+ note[1] = (note[1] + 4) % 8;
+ _song.notes[curSection].sectionNotes[i] = note;
+ updateGrid();
+ }
+ });
+ check_mustHitSection = new FlxUICheckBox(10, 30, null, null, "Camera Points to P1?", 100);
+ check_mustHitSection.name = 'check_mustHit';
+ check_mustHitSection.checked = true;
+ // _song.needsVoices = check_mustHit.checked;
+
+ check_altAnim = new FlxUICheckBox(10, 400, null, null, "Alternate Animation", 100);
+ check_altAnim.name = 'check_altAnim';
+
+ check_changeBPM = new FlxUICheckBox(10, 60, null, null, 'Change BPM', 100);
+ check_changeBPM.name = 'check_changeBPM';
+
+ tab_group_section.add(stepperLength);
+ tab_group_section.add(stepperLengthLabel);
+ tab_group_section.add(stepperSectionBPM);
+ tab_group_section.add(stepperCopy);
+ tab_group_section.add(stepperCopyLabel);
+ tab_group_section.add(check_mustHitSection);
+ tab_group_section.add(check_altAnim);
+ tab_group_section.add(check_changeBPM);
+ tab_group_section.add(copyButton);
+ tab_group_section.add(clearSectionButton);
+ tab_group_section.add(swapSection);
+
+ UI_box.addGroup(tab_group_section);
+ }
+
+ var stepperSusLength:FlxUINumericStepper;
+
+ var tab_group_note:FlxUI;
+
+ function addNoteUI():Void
+ {
+ tab_group_note = new FlxUI(null, UI_box);
+ tab_group_note.name = 'Note';
+
+ writingNotesText = new FlxUIText(20,100, 0, "");
+ writingNotesText.setFormat("Arial",20,FlxColor.WHITE,FlxTextAlign.LEFT,FlxTextBorderStyle.OUTLINE,FlxColor.BLACK);
+
+ stepperSusLength = new FlxUINumericStepper(10, 10, Conductor.stepCrochet / 2, 0, 0, Conductor.stepCrochet * _song.notes[curSection].lengthInSteps * 4);
+ stepperSusLength.value = 0;
+ stepperSusLength.name = 'note_susLength';
+
+ var stepperSusLengthLabel = new FlxText(74,10,'Note Sustain Length');
+
+ var applyLength:FlxButton = new FlxButton(10, 100, 'Apply Data');
+
+ tab_group_note.add(writingNotesText);
+ tab_group_note.add(stepperSusLength);
+ tab_group_note.add(stepperSusLengthLabel);
+ tab_group_note.add(applyLength);
+
+ UI_box.addGroup(tab_group_note);
+
+ /*player2 = new Character(0,gridBG.y, _song.player2);
+ player1 = new Boyfriend(player2.width * 0.2,gridBG.y + player2.height, _song.player1);
+
+ player1.y = player1.y - player1.height;
+
+ player2.setGraphicSize(Std.int(player2.width * 0.2));
+ player1.setGraphicSize(Std.int(player1.width * 0.2));
+
+ UI_box.add(player1);
+ UI_box.add(player2);*/
+
+ }
+
+ function loadSong(daSong:String):Void
+ {
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.stop();
+ // vocals.stop();
+ }
+
+ FlxG.sound.playMusic(Paths.inst(daSong), 0.6);
+
+ // WONT WORK FOR TUTORIAL OR TEST SONG!!! REDO LATER
+ vocals = new FlxSound().loadEmbedded(Paths.voices(daSong));
+ FlxG.sound.list.add(vocals);
+
+ FlxG.sound.music.pause();
+ vocals.pause();
+
+ FlxG.sound.music.onComplete = function()
+ {
+ vocals.pause();
+ vocals.time = 0;
+ FlxG.sound.music.pause();
+ FlxG.sound.music.time = 0;
+ changeSection();
+ };
+ }
+
+ function generateUI():Void
+ {
+ while (bullshitUI.members.length > 0)
+ {
+ bullshitUI.remove(bullshitUI.members[0], true);
+ }
+
+ // general shit
+ var title:FlxText = new FlxText(UI_box.x + 20, UI_box.y + 20, 0);
+ bullshitUI.add(title);
+ /*
+ var loopCheck = new FlxUICheckBox(UI_box.x + 10, UI_box.y + 50, null, null, "Loops", 100, ['loop check']);
+ loopCheck.checked = curNoteSelected.doesLoop;
+ tooltips.add(loopCheck, {title: 'Section looping', body: "Whether or not it's a simon says style section", style: tooltipType});
+ bullshitUI.add(loopCheck);
+
+ */
+ }
+
+ override function getEvent(id:String, sender:Dynamic, data:Dynamic, ?params:Array)
+ {
+ if (id == FlxUICheckBox.CLICK_EVENT)
+ {
+ var check:FlxUICheckBox = cast sender;
+ var label = check.getLabel().text;
+ switch (label)
+ {
+ case 'Must hit section':
+ _song.notes[curSection].mustHitSection = check.checked;
+ case 'Change BPM':
+ _song.notes[curSection].changeBPM = check.checked;
+ FlxG.log.add('changed bpm shit');
+ case "Alt Animation":
+ _song.notes[curSection].altAnim = check.checked;
+ }
+ }
+ else if (id == FlxUINumericStepper.CHANGE_EVENT && (sender is FlxUINumericStepper))
+ {
+ var nums:FlxUINumericStepper = cast sender;
+ var wname = nums.name;
+ FlxG.log.add(wname);
+ if (wname == 'section_length')
+ {
+ if (nums.value <= 4)
+ nums.value = 4;
+ _song.notes[curSection].lengthInSteps = Std.int(nums.value);
+ updateGrid();
+ }
+ else if (wname == 'song_speed')
+ {
+ if (nums.value <= 0)
+ nums.value = 0;
+ _song.speed = nums.value;
+ }
+ else if (wname == 'song_bpm')
+ {
+ if (nums.value <= 0)
+ nums.value = 1;
+ tempBpm = Std.int(nums.value);
+ Conductor.mapBPMChanges(_song);
+ Conductor.changeBPM(Std.int(nums.value));
+ }
+ else if (wname == 'note_susLength')
+ {
+ if (curSelectedNote == null)
+ return;
+
+ if (nums.value <= 0)
+ nums.value = 0;
+ curSelectedNote[2] = nums.value;
+ updateGrid();
+ }
+ else if (wname == 'section_bpm')
+ {
+ if (nums.value <= 0.1)
+ nums.value = 0.1;
+ _song.notes[curSection].bpm = Std.int(nums.value);
+ updateGrid();
+ }
+ }
+
+ // FlxG.log.add(id + " WEED " + sender + " WEED " + data + " WEED " + params);
+ }
+
+ var updatedSection:Bool = false;
+
+ /* this function got owned LOL
+ function lengthBpmBullshit():Float
+ {
+ if (_song.notes[curSection].changeBPM)
+ return _song.notes[curSection].lengthInSteps * (_song.notes[curSection].bpm / _song.bpm);
+ else
+ return _song.notes[curSection].lengthInSteps;
+ }*/
+ function sectionStartTime():Float
+ {
+ var daBPM:Int = _song.bpm;
+ var daPos:Float = 0;
+ for (i in 0...curSection)
+ {
+ if (_song.notes[i].changeBPM)
+ {
+ daBPM = _song.notes[i].bpm;
+ }
+ daPos += 4 * (1000 * 60 / daBPM);
+ }
+ return daPos;
+ }
+
+ var writingNotes:Bool = false;
+
+ override function update(elapsed:Float)
+ {
+ updateHeads();
+
+ curStep = recalculateSteps();
+
+ if (FlxG.keys.justPressed.ALT && UI_box.selected_tab == 0)
+ {
+ writingNotes = !writingNotes;
+ }
+
+ if (writingNotes)
+ writingNotesText.text = "WRITING NOTES";
+ else
+ writingNotesText.text = "";
+
+ Conductor.songPosition = FlxG.sound.music.time;
+ _song.song = typingShit.text;
+
+ var upP = controls.UP_P;
+ var rightP = controls.RIGHT_P;
+ var downP = controls.DOWN_P;
+ var leftP = controls.LEFT_P;
+
+ var controlArray:Array = [leftP, downP, upP, rightP];
+
+ if ((upP || rightP || downP || leftP) && writingNotes)
+ {
+ for(i in 0...controlArray.length)
+ {
+ if (controlArray[i])
+ {
+ for (n in 0..._song.notes[curSection].sectionNotes.length)
+ {
+ var note = _song.notes[curSection].sectionNotes[n];
+ if (note == null)
+ continue;
+ if (note[0] == Conductor.songPosition && note[1] % 4 == i)
+ {
+ trace('GAMING');
+ _song.notes[curSection].sectionNotes.remove(note);
+ }
+ }
+ trace('adding note');
+ _song.notes[curSection].sectionNotes.push([Conductor.songPosition, i, 0]);
+ updateGrid();
+ }
+ }
+
+ }
+
+ strumLine.y = getYfromStrum((Conductor.songPosition - sectionStartTime()) % (Conductor.stepCrochet * _song.notes[curSection].lengthInSteps));
+
+
+ /*curRenderedNotes.forEach(function(note:Note) {
+ if (strumLine.overlaps(note) && strumLine.y == note.y) // yandere dev type shit
+ {
+ if (_song.notes[curSection].mustHitSection)
+ {
+ trace('must hit ' + Math.abs(note.noteData));
+ if (note.noteData < 4)
+ {
+ switch (Math.abs(note.noteData))
+ {
+ case 2:
+ player1.playAnim('singUP', true);
+ case 3:
+ player1.playAnim('singRIGHT', true);
+ case 1:
+ player1.playAnim('singDOWN', true);
+ case 0:
+ player1.playAnim('singLEFT', true);
+ }
+ }
+ if (note.noteData >= 4)
+ {
+ switch (note.noteData)
+ {
+ case 6:
+ player2.playAnim('singUP', true);
+ case 7:
+ player2.playAnim('singRIGHT', true);
+ case 5:
+ player2.playAnim('singDOWN', true);
+ case 4:
+ player2.playAnim('singLEFT', true);
+ }
+ }
+ }
+ else
+ {
+ trace('hit ' + Math.abs(note.noteData));
+ if (note.noteData < 4)
+ {
+ switch (Math.abs(note.noteData))
+ {
+ case 2:
+ player2.playAnim('singUP', true);
+ case 3:
+ player2.playAnim('singRIGHT', true);
+ case 1:
+ player2.playAnim('singDOWN', true);
+ case 0:
+ player2.playAnim('singLEFT', true);
+ }
+ }
+ if (note.noteData >= 4)
+ {
+ switch (note.noteData)
+ {
+ case 6:
+ player1.playAnim('singUP', true);
+ case 7:
+ player1.playAnim('singRIGHT', true);
+ case 5:
+ player1.playAnim('singDOWN', true);
+ case 4:
+ player1.playAnim('singLEFT', true);
+ }
+ }
+ }
+ }
+ });*/
+
+ if (curBeat % 4 == 0 && curStep >= 16 * (curSection + 1))
+ {
+ trace(curStep);
+ trace((_song.notes[curSection].lengthInSteps) * (curSection + 1));
+ trace('DUMBSHIT');
+
+ if (_song.notes[curSection + 1] == null)
+ {
+ addSection();
+ }
+
+ changeSection(curSection + 1, false);
+ }
+
+ FlxG.watch.addQuick('daBeat', curBeat);
+ FlxG.watch.addQuick('daStep', curStep);
+
+ if (FlxG.mouse.justPressed)
+ {
+ if (FlxG.mouse.overlaps(curRenderedNotes))
+ {
+ curRenderedNotes.forEach(function(note:Note)
+ {
+ if (FlxG.mouse.overlaps(note))
+ {
+ if (FlxG.keys.pressed.CONTROL)
+ {
+ selectNote(note);
+ }
+ else
+ {
+ deleteNote(note);
+ }
+ }
+ });
+ }
+ else
+ {
+ if (FlxG.mouse.x > gridBG.x
+ && FlxG.mouse.x < gridBG.x + gridBG.width
+ && FlxG.mouse.y > gridBG.y
+ && FlxG.mouse.y < gridBG.y + (GRID_SIZE * _song.notes[curSection].lengthInSteps))
+ {
+ FlxG.log.add('added note');
+ addNote();
+ }
+ }
+ }
+
+ if (FlxG.mouse.x > gridBG.x
+ && FlxG.mouse.x < gridBG.x + gridBG.width
+ && FlxG.mouse.y > gridBG.y
+ && FlxG.mouse.y < gridBG.y + (GRID_SIZE * _song.notes[curSection].lengthInSteps))
+ {
+ dummyArrow.x = Math.floor(FlxG.mouse.x / GRID_SIZE) * GRID_SIZE;
+ if (FlxG.keys.pressed.SHIFT)
+ dummyArrow.y = FlxG.mouse.y;
+ else
+ dummyArrow.y = Math.floor(FlxG.mouse.y / GRID_SIZE) * GRID_SIZE;
+ }
+
+ if (FlxG.keys.justPressed.ENTER)
+ {
+ lastSection = curSection;
+
+ PlayState.SONG = _song;
+ FlxG.sound.music.stop();
+ vocals.stop();
+ LoadingState.loadAndSwitchState(new PlayState());
+ }
+
+ if (FlxG.keys.justPressed.E)
+ {
+ changeNoteSustain(Conductor.stepCrochet);
+ }
+ if (FlxG.keys.justPressed.Q)
+ {
+ changeNoteSustain(-Conductor.stepCrochet);
+ }
+
+ if (FlxG.keys.justPressed.TAB)
+ {
+ if (FlxG.keys.pressed.SHIFT)
+ {
+ UI_box.selected_tab -= 1;
+ if (UI_box.selected_tab < 0)
+ UI_box.selected_tab = 2;
+ }
+ else
+ {
+ UI_box.selected_tab += 1;
+ if (UI_box.selected_tab >= 3)
+ UI_box.selected_tab = 0;
+ }
+ }
+
+ if (!typingShit.hasFocus)
+ {
+
+ if (FlxG.keys.pressed.CONTROL)
+ {
+ if (FlxG.keys.justPressed.Z && lastNote != null)
+ {
+ trace(curRenderedNotes.members.contains(lastNote) ? "delete note" : "add note");
+ if (curRenderedNotes.members.contains(lastNote))
+ deleteNote(lastNote);
+ else
+ addNote(lastNote);
+ }
+ }
+
+ var shiftThing:Int = 1;
+ if (FlxG.keys.pressed.SHIFT)
+ shiftThing = 4;
+ if (!writingNotes)
+ {
+ if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.D)
+ changeSection(curSection + shiftThing);
+ if (FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.A)
+ changeSection(curSection - shiftThing);
+ }
+ if (FlxG.keys.justPressed.SPACE)
+ {
+ if (FlxG.sound.music.playing)
+ {
+ FlxG.sound.music.pause();
+ vocals.pause();
+ }
+ else
+ {
+ vocals.play();
+ FlxG.sound.music.play();
+ }
+ }
+
+ if (FlxG.keys.justPressed.R)
+ {
+ if (FlxG.keys.pressed.SHIFT)
+ resetSection(true);
+ else
+ resetSection();
+ }
+
+ if (FlxG.mouse.wheel != 0)
+ {
+ FlxG.sound.music.pause();
+ vocals.pause();
+
+ FlxG.sound.music.time -= (FlxG.mouse.wheel * Conductor.stepCrochet * 0.4);
+ vocals.time = FlxG.sound.music.time;
+ }
+
+ if (!FlxG.keys.pressed.SHIFT)
+ {
+ if (FlxG.keys.pressed.W || FlxG.keys.pressed.S)
+ {
+ FlxG.sound.music.pause();
+ vocals.pause();
+
+ var daTime:Float = 700 * FlxG.elapsed;
+
+ if (FlxG.keys.pressed.W)
+ {
+ FlxG.sound.music.time -= daTime;
+ }
+ else
+ FlxG.sound.music.time += daTime;
+
+ vocals.time = FlxG.sound.music.time;
+ }
+ }
+ else
+ {
+ if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S)
+ {
+ FlxG.sound.music.pause();
+ vocals.pause();
+
+ var daTime:Float = Conductor.stepCrochet * 2;
+
+ if (FlxG.keys.justPressed.W)
+ {
+ FlxG.sound.music.time -= daTime;
+ }
+ else
+ FlxG.sound.music.time += daTime;
+
+ vocals.time = FlxG.sound.music.time;
+ }
+ }
+ }
+
+ _song.bpm = tempBpm;
+
+ /* if (FlxG.keys.justPressed.UP)
+ Conductor.changeBPM(Conductor.bpm + 1);
+ if (FlxG.keys.justPressed.DOWN)
+ Conductor.changeBPM(Conductor.bpm - 1); */
+
+ bpmTxt.text = bpmTxt.text = Std.string(FlxMath.roundDecimal(Conductor.songPosition / 1000, 2))
+ + " / "
+ + Std.string(FlxMath.roundDecimal(FlxG.sound.music.length / 1000, 2))
+ + "\nSection: "
+ + curSection
+ + "\nCurStep: "
+ + curStep;
+ super.update(elapsed);
+ }
+
+ function changeNoteSustain(value:Float):Void
+ {
+ if (curSelectedNote != null)
+ {
+ if (curSelectedNote[2] != null)
+ {
+ curSelectedNote[2] += value;
+ curSelectedNote[2] = Math.max(curSelectedNote[2], 0);
+ }
+ }
+
+ updateNoteUI();
+ updateGrid();
+ }
+
+ override function beatHit()
+ {
+ trace('beat');
+
+ super.beatHit();
+ if (!player2.animation.curAnim.name.startsWith("sing"))
+ {
+ player2.playAnim('idle');
+ }
+ player1.dance();
+ }
+
+ function recalculateSteps():Int
+ {
+ var lastChange:BPMChangeEvent = {
+ stepTime: 0,
+ songTime: 0,
+ bpm: 0
+ }
+ for (i in 0...Conductor.bpmChangeMap.length)
+ {
+ if (FlxG.sound.music.time > Conductor.bpmChangeMap[i].songTime)
+ lastChange = Conductor.bpmChangeMap[i];
+ }
+
+ curStep = lastChange.stepTime + Math.floor((FlxG.sound.music.time - lastChange.songTime) / Conductor.stepCrochet);
+ updateBeat();
+
+ return curStep;
+ }
+
+ function resetSection(songBeginning:Bool = false):Void
+ {
+ updateGrid();
+
+ FlxG.sound.music.pause();
+ vocals.pause();
+
+ // Basically old shit from changeSection???
+ FlxG.sound.music.time = sectionStartTime();
+
+ if (songBeginning)
+ {
+ FlxG.sound.music.time = 0;
+ curSection = 0;
+ }
+
+ vocals.time = FlxG.sound.music.time;
+ updateCurStep();
+
+ updateGrid();
+ updateSectionUI();
+ }
+
+ function changeSection(sec:Int = 0, ?updateMusic:Bool = true):Void
+ {
+ trace('changing section' + sec);
+
+ if (_song.notes[sec] != null)
+ {
+ trace('naw im not null');
+ curSection = sec;
+
+ updateGrid();
+
+ if (updateMusic)
+ {
+ FlxG.sound.music.pause();
+ vocals.pause();
+
+ /*var daNum:Int = 0;
+ var daLength:Float = 0;
+ while (daNum <= sec)
+ {
+ daLength += lengthBpmBullshit();
+ daNum++;
+ }*/
+
+ FlxG.sound.music.time = sectionStartTime();
+ vocals.time = FlxG.sound.music.time;
+ updateCurStep();
+ }
+
+ updateGrid();
+ updateSectionUI();
+ }
+ else
+ trace('bro wtf I AM NULL');
+ }
+
+ function copySection(?sectionNum:Int = 1)
+ {
+ var daSec = FlxMath.maxInt(curSection, sectionNum);
+
+ for (note in _song.notes[daSec - sectionNum].sectionNotes)
+ {
+ var strum = note[0] + Conductor.stepCrochet * (_song.notes[daSec].lengthInSteps * sectionNum);
+
+ var copiedNote:Array = [strum, note[1], note[2]];
+ _song.notes[daSec].sectionNotes.push(copiedNote);
+ }
+
+ updateGrid();
+ }
+
+ function updateSectionUI():Void
+ {
+ var sec = _song.notes[curSection];
+
+ stepperLength.value = sec.lengthInSteps;
+ check_mustHitSection.checked = sec.mustHitSection;
+ check_altAnim.checked = sec.altAnim;
+ check_changeBPM.checked = sec.changeBPM;
+ stepperSectionBPM.value = sec.bpm;
+ }
+
+ function updateHeads():Void
+ {
+ if (check_mustHitSection.checked)
+ {
+ leftIcon.animation.play(_song.player1);
+ rightIcon.animation.play(_song.player2);
+ }
+ else
+ {
+ leftIcon.animation.play(_song.player2);
+ rightIcon.animation.play(_song.player1);
+ }
+ }
+
+ function updateNoteUI():Void
+ {
+ if (curSelectedNote != null)
+ stepperSusLength.value = curSelectedNote[2];
+ }
+
+ function updateGrid():Void
+ {
+ remove(gridBG);
+ gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 8, GRID_SIZE * _song.notes[curSection].lengthInSteps);
+ add(gridBG);
+
+ remove(gridBlackLine);
+ gridBlackLine = new FlxSprite(gridBG.x + gridBG.width / 2).makeGraphic(2, Std.int(gridBG.height), FlxColor.BLACK);
+ add(gridBlackLine);
+
+ while (curRenderedNotes.members.length > 0)
+ {
+ curRenderedNotes.remove(curRenderedNotes.members[0], true);
+ }
+
+ while (curRenderedSustains.members.length > 0)
+ {
+ curRenderedSustains.remove(curRenderedSustains.members[0], true);
+ }
+
+ var sectionInfo:Array = _song.notes[curSection].sectionNotes;
+
+ if (_song.notes[curSection].changeBPM && _song.notes[curSection].bpm > 0)
+ {
+ Conductor.changeBPM(_song.notes[curSection].bpm);
+ FlxG.log.add('CHANGED BPM!');
+ }
+ else
+ {
+ // get last bpm
+ var daBPM:Int = _song.bpm;
+ for (i in 0...curSection)
+ if (_song.notes[i].changeBPM)
+ daBPM = _song.notes[i].bpm;
+ Conductor.changeBPM(daBPM);
+ }
+
+ /* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE
+ for (sec in 0..._song.notes.length)
+ {
+ for (notesse in 0..._song.notes[sec].sectionNotes.length)
+ {
+ if (_song.notes[sec].sectionNotes[notesse][2] == null)
+ {
+ trace('SUS NULL');
+ _song.notes[sec].sectionNotes[notesse][2] = 0;
+ }
+ }
+ }
+ */
+
+ for (i in sectionInfo)
+ {
+ var daNoteInfo = i[1];
+ var daStrumTime = i[0];
+ var daSus = i[2];
+
+ var note:Note = new Note(daStrumTime, daNoteInfo % 4);
+ note.sustainLength = daSus;
+ note.setGraphicSize(GRID_SIZE, GRID_SIZE);
+ note.updateHitbox();
+ note.x = Math.floor(daNoteInfo * GRID_SIZE);
+ note.y = Math.floor(getYfromStrum((daStrumTime - sectionStartTime()) % (Conductor.stepCrochet * _song.notes[curSection].lengthInSteps)));
+
+ if (curSelectedNote != null)
+ if (curSelectedNote[0] == note.strumTime)
+ lastNote = note;
+
+ curRenderedNotes.add(note);
+
+ if (daSus > 0)
+ {
+ var sustainVis:FlxSprite = new FlxSprite(note.x + (GRID_SIZE / 2),
+ note.y + GRID_SIZE).makeGraphic(8, Math.floor(FlxMath.remapToRange(daSus, 0, Conductor.stepCrochet * _song.notes[curSection].lengthInSteps, 0, gridBG.height)));
+ curRenderedSustains.add(sustainVis);
+ }
+ }
+ }
+
+ private function addSection(lengthInSteps:Int = 16):Void
+ {
+ var sec:SwagSection = {
+ lengthInSteps: lengthInSteps,
+ bpm: _song.bpm,
+ changeBPM: false,
+ mustHitSection: true,
+ sectionNotes: [],
+ typeOfSection: 0,
+ altAnim: false
+ };
+
+ _song.notes.push(sec);
+ }
+
+ function selectNote(note:Note):Void
+ {
+ var swagNum:Int = 0;
+
+ for (i in _song.notes[curSection].sectionNotes)
+ {
+ if (i.strumTime == note.strumTime && i.noteData % 4 == note.noteData)
+ {
+ curSelectedNote = _song.notes[curSection].sectionNotes[swagNum];
+ }
+
+ swagNum += 1;
+ }
+
+ updateGrid();
+ updateNoteUI();
+ }
+
+
+ function deleteNote(note:Note):Void
+ {
+ lastNote = note;
+ for (i in _song.notes[curSection].sectionNotes)
+ {
+ if (i[0] == note.strumTime && i[1] % 4 == note.noteData)
+ {
+ _song.notes[curSection].sectionNotes.remove(i);
+ }
+ }
+
+ updateGrid();
+ }
+
+ function clearSection():Void
+ {
+ _song.notes[curSection].sectionNotes = [];
+
+ updateGrid();
+ }
+
+ function clearSong():Void
+ {
+ for (daSection in 0..._song.notes.length)
+ {
+ _song.notes[daSection].sectionNotes = [];
+ }
+
+ updateGrid();
+ }
+
+ private function addNote(?n:Note):Void
+ {
+ var noteStrum = getStrumTime(dummyArrow.y) + sectionStartTime();
+ var noteData = Math.floor(FlxG.mouse.x / GRID_SIZE);
+ var noteSus = 0;
+
+ if (n != null)
+ _song.notes[curSection].sectionNotes.push([n.strumTime, n.noteData, n.sustainLength]);
+ else
+ _song.notes[curSection].sectionNotes.push([noteStrum, noteData, noteSus]);
+
+ var thingy = _song.notes[curSection].sectionNotes[_song.notes[curSection].sectionNotes.length - 1];
+
+ curSelectedNote = thingy;
+
+ updateGrid();
+ updateNoteUI();
+
+ autosaveSong();
+ }
+
+ function getStrumTime(yPos:Float):Float
+ {
+ return FlxMath.remapToRange(yPos, gridBG.y, gridBG.y + gridBG.height, 0, 16 * Conductor.stepCrochet);
+ }
+
+ function getYfromStrum(strumTime:Float):Float
+ {
+ return FlxMath.remapToRange(strumTime, 0, 16 * Conductor.stepCrochet, gridBG.y, gridBG.y + gridBG.height);
+ }
+
+ /*
+ function calculateSectionLengths(?sec:SwagSection):Int
+ {
+ var daLength:Int = 0;
+
+ for (i in _song.notes)
+ {
+ var swagLength = i.lengthInSteps;
+
+ if (i.typeOfSection == Section.COPYCAT)
+ swagLength * 2;
+
+ daLength += swagLength;
+
+ if (sec != null && sec == i)
+ {
+ trace('swag loop??');
+ break;
+ }
+ }
+
+ return daLength;
+ }*/
+ private var daSpacing:Float = 0.3;
+
+ function loadLevel():Void
+ {
+ trace(_song.notes);
+ }
+
+ function getNotes():Array
+ {
+ var noteData:Array = [];
+
+ for (i in _song.notes)
+ {
+ noteData.push(i.sectionNotes);
+ }
+
+ return noteData;
+ }
+
+ function loadJson(song:String):Void
+ {
+ PlayState.SONG = Song.loadFromJson(song.toLowerCase(), song.toLowerCase());
+ LoadingState.loadAndSwitchState(new ChartingState());
+ }
+
+ function loadAutosave():Void
+ {
+ PlayState.SONG = Song.parseJSONshit(FlxG.save.data.autosave);
+ LoadingState.loadAndSwitchState(new ChartingState());
+ }
+
+ function autosaveSong():Void
+ {
+ FlxG.save.data.autosave = Json.stringify({
+ "song": _song
+ });
+ FlxG.save.flush();
+ }
+
+ private function saveLevel()
+ {
+ var json = {
+ "song": _song
+ };
+
+ var data:String = Json.stringify(json);
+
+ if ((data != null) && (data.length > 0))
+ {
+ _file = new FileReference();
+ _file.addEventListener(Event.COMPLETE, onSaveComplete);
+ _file.addEventListener(Event.CANCEL, onSaveCancel);
+ _file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
+ _file.save(data.trim(), _song.song.toLowerCase() + ".json");
+ }
+ }
+
+ function onSaveComplete(_):Void
+ {
+ _file.removeEventListener(Event.COMPLETE, onSaveComplete);
+ _file.removeEventListener(Event.CANCEL, onSaveCancel);
+ _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
+ _file = null;
+ FlxG.log.notice("Successfully saved LEVEL DATA.");
+ }
+
+ /**
+ * Called when the save file dialog is cancelled.
+ */
+ function onSaveCancel(_):Void
+ {
+ _file.removeEventListener(Event.COMPLETE, onSaveComplete);
+ _file.removeEventListener(Event.CANCEL, onSaveCancel);
+ _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
+ _file = null;
+ }
+
+ /**
+ * Called if there is an error while saving the gameplay recording.
+ */
+ function onSaveError(_):Void
+ {
+ _file.removeEventListener(Event.COMPLETE, onSaveComplete);
+ _file.removeEventListener(Event.CANCEL, onSaveCancel);
+ _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
+ _file = null;
+ FlxG.log.error("Problem saving Level data");
+ }
+}
diff --git a/source_duck/Conductor.hx b/source_duck/Conductor.hx
new file mode 100644
index 00000000..68634a3b
--- /dev/null
+++ b/source_duck/Conductor.hx
@@ -0,0 +1,78 @@
+package;
+
+import Song.SwagSong;
+import flixel.FlxG;
+
+/**
+ * ...
+ * @author
+ */
+
+typedef BPMChangeEvent =
+{
+ var stepTime:Int;
+ var songTime:Float;
+ var bpm:Int;
+}
+
+class Conductor
+{
+ public static var bpm:Int = 100;
+ public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds
+ public static var stepCrochet:Float = crochet / 4; // steps in milliseconds
+ public static var songPosition:Float;
+ public static var lastSongPos:Float;
+ public static var offset:Float = 0;
+
+ public static var safeFrames:Int = 10;
+ public static var safeZoneOffset:Float = Math.floor((safeFrames / 60) * 1000); // is calculated in create(), is safeFrames in milliseconds
+ public static var timeScale:Float = Conductor.safeZoneOffset / 166;
+
+ public static var bpmChangeMap:Array = [];
+
+ public function new()
+ {
+ }
+
+ public static function recalculateTimings()
+ {
+ Conductor.safeFrames = FlxG.save.data.frames;
+ Conductor.safeZoneOffset = Math.floor((Conductor.safeFrames / 60) * 1000);
+ Conductor.timeScale = Conductor.safeZoneOffset / 166;
+ }
+
+ public static function mapBPMChanges(song:SwagSong)
+ {
+ bpmChangeMap = [];
+
+ var curBPM:Int = song.bpm;
+ var totalSteps:Int = 0;
+ var totalPos:Float = 0;
+ for (i in 0...song.notes.length)
+ {
+ if(song.notes[i].changeBPM && song.notes[i].bpm != curBPM)
+ {
+ curBPM = song.notes[i].bpm;
+ var event:BPMChangeEvent = {
+ stepTime: totalSteps,
+ songTime: totalPos,
+ bpm: curBPM
+ };
+ bpmChangeMap.push(event);
+ }
+
+ var deltaSteps:Int = song.notes[i].lengthInSteps;
+ totalSteps += deltaSteps;
+ totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
+ }
+ trace("new BPM map BUDDY " + bpmChangeMap);
+ }
+
+ public static function changeBPM(newBpm:Int)
+ {
+ bpm = newBpm;
+
+ crochet = ((60 / bpm) * 1000);
+ stepCrochet = crochet / 4;
+ }
+}
\ No newline at end of file
diff --git a/source_duck/Controls.hx b/source_duck/Controls.hx
new file mode 100644
index 00000000..49776674
--- /dev/null
+++ b/source_duck/Controls.hx
@@ -0,0 +1,741 @@
+package;
+
+import flixel.FlxG;
+import flixel.input.FlxInput;
+import flixel.input.actions.FlxAction;
+import flixel.input.actions.FlxActionInput;
+import flixel.input.actions.FlxActionInputDigital;
+import flixel.input.actions.FlxActionManager;
+import flixel.input.actions.FlxActionSet;
+import flixel.input.gamepad.FlxGamepadButton;
+import flixel.input.gamepad.FlxGamepadInputID;
+import flixel.input.keyboard.FlxKey;
+
+#if (haxe >= "4.0.0")
+enum abstract Action(String) to String from String
+{
+ var UP = "up";
+ var LEFT = "left";
+ var RIGHT = "right";
+ var DOWN = "down";
+ var UP_P = "up-press";
+ var LEFT_P = "left-press";
+ var RIGHT_P = "right-press";
+ var DOWN_P = "down-press";
+ var UP_R = "up-release";
+ var LEFT_R = "left-release";
+ var RIGHT_R = "right-release";
+ var DOWN_R = "down-release";
+ var ACCEPT = "accept";
+ var BACK = "back";
+ var PAUSE = "pause";
+ var RESET = "reset";
+ var CHEAT = "cheat";
+}
+#else
+@:enum
+abstract Action(String) to String from String
+{
+ var UP = "up";
+ var LEFT = "left";
+ var RIGHT = "right";
+ var DOWN = "down";
+ var UP_P = "up-press";
+ var LEFT_P = "left-press";
+ var RIGHT_P = "right-press";
+ var DOWN_P = "down-press";
+ var UP_R = "up-release";
+ var LEFT_R = "left-release";
+ var RIGHT_R = "right-release";
+ var DOWN_R = "down-release";
+ var ACCEPT = "accept";
+ var BACK = "back";
+ var PAUSE = "pause";
+ var RESET = "reset";
+ var CHEAT = "cheat";
+}
+#end
+
+enum Device
+{
+ Keys;
+ Gamepad(id:Int);
+}
+
+/**
+ * Since, in many cases multiple actions should use similar keys, we don't want the
+ * rebinding UI to list every action. ActionBinders are what the user percieves as
+ * an input so, for instance, they can't set jump-press and jump-release to different keys.
+ */
+enum Control
+{
+ UP;
+ LEFT;
+ RIGHT;
+ DOWN;
+ RESET;
+ ACCEPT;
+ BACK;
+ PAUSE;
+ CHEAT;
+}
+
+enum KeyboardScheme
+{
+ Solo;
+ Duo(first:Bool);
+ None;
+ Custom;
+}
+
+/**
+ * A list of actions that a player would invoke via some input device.
+ * Uses FlxActions to funnel various inputs to a single action.
+ */
+class Controls extends FlxActionSet
+{
+ var _up = new FlxActionDigital(Action.UP);
+ var _left = new FlxActionDigital(Action.LEFT);
+ var _right = new FlxActionDigital(Action.RIGHT);
+ var _down = new FlxActionDigital(Action.DOWN);
+ var _upP = new FlxActionDigital(Action.UP_P);
+ var _leftP = new FlxActionDigital(Action.LEFT_P);
+ var _rightP = new FlxActionDigital(Action.RIGHT_P);
+ var _downP = new FlxActionDigital(Action.DOWN_P);
+ var _upR = new FlxActionDigital(Action.UP_R);
+ var _leftR = new FlxActionDigital(Action.LEFT_R);
+ var _rightR = new FlxActionDigital(Action.RIGHT_R);
+ var _downR = new FlxActionDigital(Action.DOWN_R);
+ var _accept = new FlxActionDigital(Action.ACCEPT);
+ var _back = new FlxActionDigital(Action.BACK);
+ var _pause = new FlxActionDigital(Action.PAUSE);
+ var _reset = new FlxActionDigital(Action.RESET);
+ var _cheat = new FlxActionDigital(Action.CHEAT);
+
+ #if (haxe >= "4.0.0")
+ var byName:Map = [];
+ #else
+ var byName:Map = new Map();
+ #end
+
+ public var gamepadsAdded:Array = [];
+ public var keyboardScheme = KeyboardScheme.None;
+
+ public var UP(get, never):Bool;
+
+ inline function get_UP()
+ return _up.check();
+
+ public var LEFT(get, never):Bool;
+
+ inline function get_LEFT()
+ return _left.check();
+
+ public var RIGHT(get, never):Bool;
+
+ inline function get_RIGHT()
+ return _right.check();
+
+ public var DOWN(get, never):Bool;
+
+ inline function get_DOWN()
+ return _down.check();
+
+ public var UP_P(get, never):Bool;
+
+ inline function get_UP_P()
+ return _upP.check();
+
+ public var LEFT_P(get, never):Bool;
+
+ inline function get_LEFT_P()
+ return _leftP.check();
+
+ public var RIGHT_P(get, never):Bool;
+
+ inline function get_RIGHT_P()
+ return _rightP.check();
+
+ public var DOWN_P(get, never):Bool;
+
+ inline function get_DOWN_P()
+ return _downP.check();
+
+ public var UP_R(get, never):Bool;
+
+ inline function get_UP_R()
+ return _upR.check();
+
+ public var LEFT_R(get, never):Bool;
+
+ inline function get_LEFT_R()
+ return _leftR.check();
+
+ public var RIGHT_R(get, never):Bool;
+
+ inline function get_RIGHT_R()
+ return _rightR.check();
+
+ public var DOWN_R(get, never):Bool;
+
+ inline function get_DOWN_R()
+ return _downR.check();
+
+ public var ACCEPT(get, never):Bool;
+
+ inline function get_ACCEPT()
+ return _accept.check();
+
+ public var BACK(get, never):Bool;
+
+ inline function get_BACK()
+ return _back.check();
+
+ public var PAUSE(get, never):Bool;
+
+ inline function get_PAUSE()
+ return _pause.check();
+
+ public var RESET(get, never):Bool;
+
+ inline function get_RESET()
+ return _reset.check();
+
+ public var CHEAT(get, never):Bool;
+
+ inline function get_CHEAT()
+ return _cheat.check();
+
+ #if (haxe >= "4.0.0")
+ public function new(name, scheme = None)
+ {
+ super(name);
+
+ add(_up);
+ add(_left);
+ add(_right);
+ add(_down);
+ add(_upP);
+ add(_leftP);
+ add(_rightP);
+ add(_downP);
+ add(_upR);
+ add(_leftR);
+ add(_rightR);
+ add(_downR);
+ add(_accept);
+ add(_back);
+ add(_pause);
+ add(_reset);
+ add(_cheat);
+
+ for (action in digitalActions)
+ byName[action.name] = action;
+
+ setKeyboardScheme(scheme, false);
+ }
+ #else
+ public function new(name, scheme:KeyboardScheme = null)
+ {
+ super(name);
+
+ add(_up);
+ add(_left);
+ add(_right);
+ add(_down);
+ add(_upP);
+ add(_leftP);
+ add(_rightP);
+ add(_downP);
+ add(_upR);
+ add(_leftR);
+ add(_rightR);
+ add(_downR);
+ add(_accept);
+ add(_back);
+ add(_pause);
+ add(_reset);
+ add(_cheat);
+
+ for (action in digitalActions)
+ byName[action.name] = action;
+
+ if (scheme == null)
+ scheme = None;
+ setKeyboardScheme(scheme, false);
+ }
+ #end
+
+ override function update()
+ {
+ super.update();
+ }
+
+ // inline
+ public function checkByName(name:Action):Bool
+ {
+ #if debug
+ if (!byName.exists(name))
+ throw 'Invalid name: $name';
+ #end
+ return byName[name].check();
+ }
+
+ public function getDialogueName(action:FlxActionDigital):String
+ {
+ var input = action.inputs[0];
+ return switch input.device
+ {
+ case KEYBOARD: return '[${(input.inputID : FlxKey)}]';
+ case GAMEPAD: return '(${(input.inputID : FlxGamepadInputID)})';
+ case device: throw 'unhandled device: $device';
+ }
+ }
+
+ public function getDialogueNameFromToken(token:String):String
+ {
+ return getDialogueName(getActionFromControl(Control.createByName(token.toUpperCase())));
+ }
+
+ function getActionFromControl(control:Control):FlxActionDigital
+ {
+ return switch (control)
+ {
+ case UP: _up;
+ case DOWN: _down;
+ case LEFT: _left;
+ case RIGHT: _right;
+ case ACCEPT: _accept;
+ case BACK: _back;
+ case PAUSE: _pause;
+ case RESET: _reset;
+ case CHEAT: _cheat;
+ }
+ }
+
+ static function init():Void
+ {
+ var actions = new FlxActionManager();
+ FlxG.inputs.add(actions);
+ }
+
+ /**
+ * Calls a function passing each action bound by the specified control
+ * @param control
+ * @param func
+ * @return ->Void)
+ */
+ function forEachBound(control:Control, func:FlxActionDigital->FlxInputState->Void)
+ {
+ switch (control)
+ {
+ case UP:
+ func(_up, PRESSED);
+ func(_upP, JUST_PRESSED);
+ func(_upR, JUST_RELEASED);
+ case LEFT:
+ func(_left, PRESSED);
+ func(_leftP, JUST_PRESSED);
+ func(_leftR, JUST_RELEASED);
+ case RIGHT:
+ func(_right, PRESSED);
+ func(_rightP, JUST_PRESSED);
+ func(_rightR, JUST_RELEASED);
+ case DOWN:
+ func(_down, PRESSED);
+ func(_downP, JUST_PRESSED);
+ func(_downR, JUST_RELEASED);
+ case ACCEPT:
+ func(_accept, JUST_PRESSED);
+ case BACK:
+ func(_back, JUST_PRESSED);
+ case PAUSE:
+ func(_pause, JUST_PRESSED);
+ case RESET:
+ func(_reset, JUST_PRESSED);
+ case CHEAT:
+ func(_cheat, JUST_PRESSED);
+ }
+ }
+
+ public function replaceBinding(control:Control, device:Device, ?toAdd:Int, ?toRemove:Int)
+ {
+ if (toAdd == toRemove)
+ return;
+
+ switch (device)
+ {
+ case Keys:
+ if (toRemove != null)
+ unbindKeys(control, [toRemove]);
+ if (toAdd != null)
+ bindKeys(control, [toAdd]);
+
+ case Gamepad(id):
+ if (toRemove != null)
+ unbindButtons(control, id, [toRemove]);
+ if (toAdd != null)
+ bindButtons(control, id, [toAdd]);
+ }
+ }
+
+ public function copyFrom(controls:Controls, ?device:Device)
+ {
+ #if (haxe >= "4.0.0")
+ for (name => action in controls.byName)
+ {
+ for (input in action.inputs)
+ {
+ if (device == null || isDevice(input, device))
+ byName[name].add(cast input);
+ }
+ }
+ #else
+ for (name in controls.byName.keys())
+ {
+ var action = controls.byName[name];
+ for (input in action.inputs)
+ {
+ if (device == null || isDevice(input, device))
+ byName[name].add(cast input);
+ }
+ }
+ #end
+
+ switch (device)
+ {
+ case null:
+ // add all
+ #if (haxe >= "4.0.0")
+ for (gamepad in controls.gamepadsAdded)
+ if (!gamepadsAdded.contains(gamepad))
+ gamepadsAdded.push(gamepad);
+ #else
+ for (gamepad in controls.gamepadsAdded)
+ if (gamepadsAdded.indexOf(gamepad) == -1)
+ gamepadsAdded.push(gamepad);
+ #end
+
+ mergeKeyboardScheme(controls.keyboardScheme);
+
+ case Gamepad(id):
+ gamepadsAdded.push(id);
+ case Keys:
+ mergeKeyboardScheme(controls.keyboardScheme);
+ }
+ }
+
+ inline public function copyTo(controls:Controls, ?device:Device)
+ {
+ controls.copyFrom(this, device);
+ }
+
+ function mergeKeyboardScheme(scheme:KeyboardScheme):Void
+ {
+ if (scheme != None)
+ {
+ switch (keyboardScheme)
+ {
+ case None:
+ keyboardScheme = scheme;
+ default:
+ keyboardScheme = Custom;
+ }
+ }
+ }
+
+ /**
+ * Sets all actions that pertain to the binder to trigger when the supplied keys are used.
+ * If binder is a literal you can inline this
+ */
+ public function bindKeys(control:Control, keys:Array)
+ {
+ #if (haxe >= "4.0.0")
+ inline forEachBound(control, (action, state) -> addKeys(action, keys, state));
+ #else
+ forEachBound(control, function(action, state) addKeys(action, keys, state));
+ #end
+ }
+
+ /**
+ * Sets all actions that pertain to the binder to trigger when the supplied keys are used.
+ * If binder is a literal you can inline this
+ */
+ public function unbindKeys(control:Control, keys:Array)
+ {
+ #if (haxe >= "4.0.0")
+ inline forEachBound(control, (action, _) -> removeKeys(action, keys));
+ #else
+ forEachBound(control, function(action, _) removeKeys(action, keys));
+ #end
+ }
+
+ inline static function addKeys(action:FlxActionDigital, keys:Array, state:FlxInputState)
+ {
+ for (key in keys)
+ action.addKey(key, state);
+ }
+
+ static function removeKeys(action:FlxActionDigital, keys:Array)
+ {
+ var i = action.inputs.length;
+ while (i-- > 0)
+ {
+ var input = action.inputs[i];
+ if (input.device == KEYBOARD && keys.indexOf(cast input.inputID) != -1)
+ action.remove(input);
+ }
+ }
+
+ public function setKeyboardScheme(scheme:KeyboardScheme, reset = true)
+ {
+ if (reset)
+ removeKeyboard();
+
+ keyboardScheme = scheme;
+
+ #if (haxe >= "4.0.0")
+ switch (scheme)
+ {
+ case Solo:
+ inline bindKeys(Control.UP, [J, FlxKey.UP]);
+ inline bindKeys(Control.DOWN, [F, FlxKey.DOWN]);
+ inline bindKeys(Control.LEFT, [D, FlxKey.LEFT]);
+ inline bindKeys(Control.RIGHT, [K, FlxKey.RIGHT]);
+ inline bindKeys(Control.ACCEPT, [Z, SPACE, ENTER]);
+ inline bindKeys(Control.BACK, [BACKSPACE, ESCAPE]);
+ inline bindKeys(Control.PAUSE, [P, ENTER, ESCAPE]);
+ inline bindKeys(Control.RESET, [R]);
+ case Duo(true):
+ inline bindKeys(Control.UP, [W, FlxKey.UP]);
+ inline bindKeys(Control.DOWN, [S, FlxKey.DOWN]);
+ inline bindKeys(Control.LEFT, [A, FlxKey.LEFT]);
+ inline bindKeys(Control.RIGHT, [D, FlxKey.RIGHT]);
+ inline bindKeys(Control.ACCEPT, [G, Z, SPACE, ENTER]);
+ inline bindKeys(Control.BACK, [BACKSPACE, ESCAPE]);
+ inline bindKeys(Control.RESET, [R]);
+ case Duo(false):
+ inline bindKeys(Control.UP, [FlxKey.UP]);
+ inline bindKeys(Control.DOWN, [FlxKey.DOWN]);
+ inline bindKeys(Control.LEFT, [FlxKey.LEFT]);
+ inline bindKeys(Control.RIGHT, [FlxKey.RIGHT]);
+ inline bindKeys(Control.ACCEPT, [O]);
+ inline bindKeys(Control.BACK, [P]);
+ inline bindKeys(Control.PAUSE, [ENTER]);
+ inline bindKeys(Control.RESET, [BACKSPACE]);
+ case None: // nothing
+ case Custom: // nothing
+ }
+ #else
+ switch (scheme)
+ {
+ case Solo:
+ bindKeys(Control.UP, [W, FlxKey.UP]);
+ bindKeys(Control.DOWN, [S, FlxKey.DOWN]);
+ bindKeys(Control.LEFT, [A, FlxKey.LEFT]);
+ bindKeys(Control.RIGHT, [D, FlxKey.RIGHT]);
+ bindKeys(Control.ACCEPT, [Z, SPACE, ENTER]);
+ bindKeys(Control.BACK, [BACKSPACE, ESCAPE]);
+ bindKeys(Control.PAUSE, [P, ENTER, ESCAPE]);
+ bindKeys(Control.RESET, [R]);
+ case Duo(true):
+ bindKeys(Control.UP, [W]);
+ bindKeys(Control.DOWN, [S]);
+ bindKeys(Control.LEFT, [A]);
+ bindKeys(Control.RIGHT, [D]);
+ bindKeys(Control.ACCEPT, [G, Z]);
+ bindKeys(Control.BACK, [H, X]);
+ bindKeys(Control.PAUSE, [ONE]);
+ bindKeys(Control.RESET, [R]);
+ case Duo(false):
+ bindKeys(Control.UP, [FlxKey.UP]);
+ bindKeys(Control.DOWN, [FlxKey.DOWN]);
+ bindKeys(Control.LEFT, [FlxKey.LEFT]);
+ bindKeys(Control.RIGHT, [FlxKey.RIGHT]);
+ bindKeys(Control.ACCEPT, [O]);
+ bindKeys(Control.BACK, [P]);
+ bindKeys(Control.PAUSE, [ENTER]);
+ bindKeys(Control.RESET, [BACKSPACE]);
+ case None: // nothing
+ case Custom: // nothing
+ }
+ #end
+ }
+
+ function removeKeyboard()
+ {
+ for (action in this.digitalActions)
+ {
+ var i = action.inputs.length;
+ while (i-- > 0)
+ {
+ var input = action.inputs[i];
+ if (input.device == KEYBOARD)
+ action.remove(input);
+ }
+ }
+ }
+
+ public function addGamepad(id:Int, ?buttonMap:Map>):Void
+ {
+ gamepadsAdded.push(id);
+
+ #if (haxe >= "4.0.0")
+ for (control => buttons in buttonMap)
+ inline bindButtons(control, id, buttons);
+ #else
+ for (control in buttonMap.keys())
+ bindButtons(control, id, buttonMap[control]);
+ #end
+ }
+
+ inline function addGamepadLiteral(id:Int, ?buttonMap:Map>):Void
+ {
+ gamepadsAdded.push(id);
+
+ #if (haxe >= "4.0.0")
+ for (control => buttons in buttonMap)
+ inline bindButtons(control, id, buttons);
+ #else
+ for (control in buttonMap.keys())
+ bindButtons(control, id, buttonMap[control]);
+ #end
+ }
+
+ public function removeGamepad(deviceID:Int = FlxInputDeviceID.ALL):Void
+ {
+ for (action in this.digitalActions)
+ {
+ var i = action.inputs.length;
+ while (i-- > 0)
+ {
+ var input = action.inputs[i];
+ if (input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID))
+ action.remove(input);
+ }
+ }
+
+ gamepadsAdded.remove(deviceID);
+ }
+
+ public function addDefaultGamepad(id):Void
+ {
+ #if !switch
+ addGamepadLiteral(id, [
+ Control.ACCEPT => [A],
+ Control.BACK => [B],
+ Control.UP => [DPAD_UP, LEFT_STICK_DIGITAL_UP],
+ Control.DOWN => [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN],
+ Control.LEFT => [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT],
+ Control.RIGHT => [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT],
+ Control.PAUSE => [START],
+ Control.RESET => [Y]
+ ]);
+ #else
+ addGamepadLiteral(id, [
+ //Swap A and B for switch
+ Control.ACCEPT => [B],
+ Control.BACK => [A],
+ Control.UP => [DPAD_UP, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP],
+ Control.DOWN => [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN],
+ Control.LEFT => [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT],
+ Control.RIGHT => [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT],
+ Control.PAUSE => [START],
+ //Swap Y and X for switch
+ Control.RESET => [Y],
+ Control.CHEAT => [X]
+ ]);
+ #end
+ }
+
+ /**
+ * Sets all actions that pertain to the binder to trigger when the supplied keys are used.
+ * If binder is a literal you can inline this
+ */
+ public function bindButtons(control:Control, id, buttons)
+ {
+ #if (haxe >= "4.0.0")
+ inline forEachBound(control, (action, state) -> addButtons(action, buttons, state, id));
+ #else
+ forEachBound(control, function(action, state) addButtons(action, buttons, state, id));
+ #end
+ }
+
+ /**
+ * Sets all actions that pertain to the binder to trigger when the supplied keys are used.
+ * If binder is a literal you can inline this
+ */
+ public function unbindButtons(control:Control, gamepadID:Int, buttons)
+ {
+ #if (haxe >= "4.0.0")
+ inline forEachBound(control, (action, _) -> removeButtons(action, gamepadID, buttons));
+ #else
+ forEachBound(control, function(action, _) removeButtons(action, gamepadID, buttons));
+ #end
+ }
+
+ inline static function addButtons(action:FlxActionDigital, buttons:Array, state, id)
+ {
+ for (button in buttons)
+ action.addGamepad(button, state, id);
+ }
+
+ static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array)
+ {
+ var i = action.inputs.length;
+ while (i-- > 0)
+ {
+ var input = action.inputs[i];
+ if (isGamepad(input, gamepadID) && buttons.indexOf(cast input.inputID) != -1)
+ action.remove(input);
+ }
+ }
+
+ public function getInputsFor(control:Control, device:Device, ?list:Array):Array
+ {
+ if (list == null)
+ list = [];
+
+ switch (device)
+ {
+ case Keys:
+ for (input in getActionFromControl(control).inputs)
+ {
+ if (input.device == KEYBOARD)
+ list.push(input.inputID);
+ }
+ case Gamepad(id):
+ for (input in getActionFromControl(control).inputs)
+ {
+ if (input.deviceID == id)
+ list.push(input.inputID);
+ }
+ }
+ return list;
+ }
+
+ public function removeDevice(device:Device)
+ {
+ switch (device)
+ {
+ case Keys:
+ setKeyboardScheme(None);
+ case Gamepad(id):
+ removeGamepad(id);
+ }
+ }
+
+ static function isDevice(input:FlxActionInput, device:Device)
+ {
+ return switch device
+ {
+ case Keys: input.device == KEYBOARD;
+ case Gamepad(id): isGamepad(input, id);
+ }
+ }
+
+ inline static function isGamepad(input:FlxActionInput, deviceID:Int)
+ {
+ return input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID);
+ }
+}
diff --git a/source_duck/ControlsSubState.hx b/source_duck/ControlsSubState.hx
new file mode 100644
index 00000000..f35d74cc
--- /dev/null
+++ b/source_duck/ControlsSubState.hx
@@ -0,0 +1,12 @@
+package;
+
+import flixel.FlxSprite;
+import flixel.FlxSubState;
+
+class ControlsSubState extends FlxSubState
+{
+ public function new()
+ {
+ super();
+ }
+}
diff --git a/source_duck/ConvertScore.hx b/source_duck/ConvertScore.hx
new file mode 100644
index 00000000..9eba969f
--- /dev/null
+++ b/source_duck/ConvertScore.hx
@@ -0,0 +1,21 @@
+class ConvertScore
+{
+ public static function convertScore(noteDiff:Float):Int
+ {
+ var daRating:String = Ratings.CalculateRating(noteDiff, 166);
+
+ switch(daRating)
+ {
+ case 'shit':
+ return -300;
+ case 'bad':
+ return 0;
+ case 'good':
+ return 200;
+ case 'sick':
+ return 350;
+ }
+ return 0;
+ }
+
+}
\ No newline at end of file
diff --git a/source_duck/CoolUtil.hx b/source_duck/CoolUtil.hx
new file mode 100644
index 00000000..92cc2c0c
--- /dev/null
+++ b/source_duck/CoolUtil.hx
@@ -0,0 +1,49 @@
+package;
+
+import lime.utils.Assets;
+
+using StringTools;
+
+class CoolUtil
+{
+ public static var difficultyArray:Array = ['EASY', "NORMAL", "HARD"];
+
+ public static function difficultyString():String
+ {
+ return difficultyArray[PlayState.storyDifficulty];
+ }
+
+ public static function coolTextFile(path:String):Array
+ {
+ var daList:Array = Assets.getText(path).trim().split('\n');
+
+ for (i in 0...daList.length)
+ {
+ daList[i] = daList[i].trim();
+ }
+
+ return daList;
+ }
+
+ public static function coolStringFile(path:String):Array
+ {
+ var daList:Array = path.trim().split('\n');
+
+ for (i in 0...daList.length)
+ {
+ daList[i] = daList[i].trim();
+ }
+
+ return daList;
+ }
+
+ public static function numberArray(max:Int, ?min = 0):Array
+ {
+ var dumbArray:Array = [];
+ for (i in min...max)
+ {
+ dumbArray.push(i);
+ }
+ return dumbArray;
+ }
+}
diff --git a/source_duck/DialogueBox.hx b/source_duck/DialogueBox.hx
new file mode 100644
index 00000000..edd67d12
--- /dev/null
+++ b/source_duck/DialogueBox.hx
@@ -0,0 +1,265 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.text.FlxTypeText;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.group.FlxSpriteGroup;
+import flixel.input.FlxKeyManager;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
+import flixel.util.FlxTimer;
+
+using StringTools;
+
+class DialogueBox extends FlxSpriteGroup
+{
+ var box:FlxSprite;
+
+ var curCharacter:String = '';
+
+ var dialogue:Alphabet;
+ var dialogueList:Array = [];
+
+ // SECOND DIALOGUE FOR THE PIXEL SHIT INSTEAD???
+ var swagDialogue:FlxTypeText;
+
+ var dropText:FlxText;
+
+ public var finishThing:Void->Void;
+
+ var portraitLeft:FlxSprite;
+ var portraitRight:FlxSprite;
+
+ var handSelect:FlxSprite;
+ var bgFade:FlxSprite;
+
+ public function new(talkingRight:Bool = true, ?dialogueList:Array)
+ {
+ super();
+
+ switch (PlayState.SONG.song.toLowerCase())
+ {
+ case 'senpai':
+ FlxG.sound.playMusic(Paths.music('Lunchbox'), 0);
+ FlxG.sound.music.fadeIn(1, 0, 0.8);
+ case 'thorns':
+ FlxG.sound.playMusic(Paths.music('LunchboxScary'), 0);
+ FlxG.sound.music.fadeIn(1, 0, 0.8);
+ }
+
+ bgFade = new FlxSprite(-200, -200).makeGraphic(Std.int(FlxG.width * 1.3), Std.int(FlxG.height * 1.3), 0xFFB3DFd8);
+ bgFade.scrollFactor.set();
+ bgFade.alpha = 0;
+ add(bgFade);
+
+ new FlxTimer().start(0.83, function(tmr:FlxTimer)
+ {
+ bgFade.alpha += (1 / 5) * 0.7;
+ if (bgFade.alpha > 0.7)
+ bgFade.alpha = 0.7;
+ }, 5);
+
+ box = new FlxSprite(-20, 45);
+
+ var hasDialog = false;
+ switch (PlayState.SONG.song.toLowerCase())
+ {
+ case 'senpai':
+ hasDialog = true;
+ box.frames = Paths.getSparrowAtlas('weeb/pixelUI/dialogueBox-pixel');
+ box.animation.addByPrefix('normalOpen', 'Text Box Appear', 24, false);
+ box.animation.addByIndices('normal', 'Text Box Appear', [4], "", 24);
+ case 'roses':
+ hasDialog = true;
+ FlxG.sound.play(Paths.sound('ANGRY_TEXT_BOX'));
+
+ box.frames = Paths.getSparrowAtlas('weeb/pixelUI/dialogueBox-senpaiMad');
+ box.animation.addByPrefix('normalOpen', 'SENPAI ANGRY IMPACT SPEECH', 24, false);
+ box.animation.addByIndices('normal', 'SENPAI ANGRY IMPACT SPEECH', [4], "", 24);
+
+ case 'thorns':
+ hasDialog = true;
+ box.frames = Paths.getSparrowAtlas('weeb/pixelUI/dialogueBox-evil');
+ box.animation.addByPrefix('normalOpen', 'Spirit Textbox spawn', 24, false);
+ box.animation.addByIndices('normal', 'Spirit Textbox spawn', [11], "", 24);
+
+ case 'timer':
+ hasDialog = true;
+ box.frames = Paths.getSparrowAtlas('duck/Dia/Dialougue Box');
+ box.animation.addByPrefix('normalOpen', 'Dialogue Box', 24, false);
+ box.animation.addByIndices('normal', 'Dialogue Box', [11], "", 24);
+
+ var face:FlxSprite = new FlxSprite(320, 170).loadGraphic(Paths.image('weeb/spiritFaceForward'));
+ face.setGraphicSize(Std.int(face.width * 6));
+ add(face);
+ }
+
+ this.dialogueList = dialogueList;
+
+ if (!hasDialog)
+ return;
+
+ portraitLeft = new FlxSprite(-20, 40);
+ portraitLeft.frames = Paths.getSparrowAtlas('weeb/senpaiPortrait');
+ portraitLeft.animation.addByPrefix('enter', 'Senpai Portrait Enter', 24, false);
+ portraitLeft.setGraphicSize(Std.int(portraitLeft.width * PlayState.daPixelZoom * 0.9));
+ portraitLeft.updateHitbox();
+ portraitLeft.scrollFactor.set();
+ add(portraitLeft);
+ portraitLeft.visible = false;
+
+ portraitRight = new FlxSprite(0, 40);
+ portraitRight.frames = Paths.getSparrowAtlas('weeb/bfPortrait');
+ portraitRight.animation.addByPrefix('enter', 'Boyfriend portrait enter', 24, false);
+ portraitRight.setGraphicSize(Std.int(portraitRight.width * PlayState.daPixelZoom * 0.9));
+ portraitRight.updateHitbox();
+ portraitRight.scrollFactor.set();
+ add(portraitRight);
+ portraitRight.visible = false;
+
+ box.animation.play('normalOpen');
+ box.setGraphicSize(Std.int(box.width * PlayState.daPixelZoom * 0.9));
+ box.updateHitbox();
+ add(box);
+
+ box.screenCenter(X);
+ portraitLeft.screenCenter(X);
+
+ handSelect = new FlxSprite(FlxG.width * 0.9, FlxG.height * 0.9).loadGraphic(Paths.image('weeb/pixelUI/hand_textbox'));
+ add(handSelect);
+
+ if (!talkingRight)
+ {
+ // box.flipX = true;
+ }
+
+ dropText = new FlxText(242, 502, Std.int(FlxG.width * 0.6), "", 32);
+ dropText.font = 'Pixel Arial 11 Bold';
+ dropText.color = 0xFFD89494;
+ add(dropText);
+
+ swagDialogue = new FlxTypeText(240, 500, Std.int(FlxG.width * 0.6), "", 32);
+ swagDialogue.font = 'Pixel Arial 11 Bold';
+ swagDialogue.color = 0xFF3F2021;
+ swagDialogue.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)];
+ add(swagDialogue);
+
+ dialogue = new Alphabet(0, 80, "", false, true);
+ // dialogue.x = 90;
+ // add(dialogue);
+ }
+
+ var dialogueOpened:Bool = false;
+ var dialogueStarted:Bool = false;
+
+ override function update(elapsed:Float)
+ {
+ // HARD CODING CUZ IM STUPDI
+ if (PlayState.SONG.song.toLowerCase() == 'roses')
+ portraitLeft.visible = false;
+ if (PlayState.SONG.song.toLowerCase() == 'thorns')
+ {
+ portraitLeft.color = FlxColor.BLACK;
+ swagDialogue.color = FlxColor.WHITE;
+ dropText.color = FlxColor.BLACK;
+ }
+
+ dropText.text = swagDialogue.text;
+
+ if (box.animation.curAnim != null)
+ {
+ if (box.animation.curAnim.name == 'normalOpen' && box.animation.curAnim.finished)
+ {
+ box.animation.play('normal');
+ dialogueOpened = true;
+ }
+ }
+
+ if (dialogueOpened && !dialogueStarted)
+ {
+ startDialogue();
+ dialogueStarted = true;
+ }
+
+ if (FlxG.keys.justPressed.ANY && dialogueStarted == true)
+ {
+ remove(dialogue);
+
+ FlxG.sound.play(Paths.sound('clickText'), 0.8);
+
+ if (dialogueList[1] == null && dialogueList[0] != null)
+ {
+ if (!isEnding)
+ {
+ isEnding = true;
+
+ if (PlayState.SONG.song.toLowerCase() == 'senpai' || PlayState.SONG.song.toLowerCase() == 'thorns')
+ FlxG.sound.music.fadeOut(2.2, 0);
+
+ new FlxTimer().start(0.2, function(tmr:FlxTimer)
+ {
+ box.alpha -= 1 / 5;
+ bgFade.alpha -= 1 / 5 * 0.7;
+ portraitLeft.visible = false;
+ portraitRight.visible = false;
+ swagDialogue.alpha -= 1 / 5;
+ dropText.alpha = swagDialogue.alpha;
+ }, 5);
+
+ new FlxTimer().start(1.2, function(tmr:FlxTimer)
+ {
+ finishThing();
+ kill();
+ });
+ }
+ }
+ else
+ {
+ dialogueList.remove(dialogueList[0]);
+ startDialogue();
+ }
+ }
+
+ super.update(elapsed);
+ }
+
+ var isEnding:Bool = false;
+
+ function startDialogue():Void
+ {
+ cleanDialog();
+ // var theDialog:Alphabet = new Alphabet(0, 70, dialogueList[0], false, true);
+ // dialogue = theDialog;
+ // add(theDialog);
+
+ // swagDialogue.text = ;
+ swagDialogue.resetText(dialogueList[0]);
+ swagDialogue.start(0.04, true);
+
+ switch (curCharacter)
+ {
+ case 'dad':
+ portraitRight.visible = false;
+ if (!portraitLeft.visible)
+ {
+ portraitLeft.visible = true;
+ portraitLeft.animation.play('enter');
+ }
+ case 'bf':
+ portraitLeft.visible = false;
+ if (!portraitRight.visible)
+ {
+ portraitRight.visible = true;
+ portraitRight.animation.play('enter');
+ }
+ }
+ }
+
+ function cleanDialog():Void
+ {
+ var splitName:Array = dialogueList[0].split(":");
+ curCharacter = splitName[1];
+ dialogueList[0] = dialogueList[0].substr(splitName[1].length + 2).trim();
+ }
+}
diff --git a/source_duck/DiffCalc.hx b/source_duck/DiffCalc.hx
new file mode 100644
index 00000000..a1c7a7a5
--- /dev/null
+++ b/source_duck/DiffCalc.hx
@@ -0,0 +1,140 @@
+import Song.SwagSong;
+
+class SmallNote // basically Note.hx but small as fuck
+{
+ public var strumTime:Float;
+ public var noteData:Int;
+
+ public function new(strum,data)
+ {
+ strumTime = strum;
+ noteData = data;
+ }
+}
+
+class DiffCalc
+{
+ public static function CalculateDiff(song:SwagSong)
+ {
+ // cleaned notes
+ var cleanedNotes:Array = [];
+
+ // find all of the notes
+ for(i in song.notes) // sections
+ {
+ for (ii in i.sectionNotes) // notes
+ {
+ if (ii[2] != 0) // skip helds
+ continue;
+ var gottaHitNote:Bool = i.mustHitSection;
+
+ if (ii[1] > 3)
+ gottaHitNote = !i.mustHitSection;
+
+ if (gottaHitNote)
+ cleanedNotes.push(new SmallNote(ii[0],Math.floor(Math.abs(ii[1]))));
+ }
+ }
+
+ var handOne:Array = [];
+ var handTwo:Array = [];
+
+ cleanedNotes.sort((a, b) -> Std.int(a.strumTime - b.strumTime));
+
+ var firstNoteTime = cleanedNotes[0].strumTime;
+
+ // normalize the notes
+
+ for(i in cleanedNotes)
+ {
+ i.strumTime = (i.strumTime - firstNoteTime) * 2;
+ }
+
+ for (i in cleanedNotes)
+ {
+ switch(i.noteData)
+ {
+ case 0:
+ handOne.push(i);
+ case 1:
+ handTwo.push(i);
+ case 2:
+ handTwo.push(i);
+ case 3:
+ handOne.push(i);
+ }
+ }
+
+
+ // length in segments of the song
+ var length = ((cleanedNotes[cleanedNotes.length - 1].strumTime / 1000) / 0.5);
+
+ // hackey way of creating a array with a length
+ var segmentsOne:Array = new_Array(1,Std.int(length));
+ var segmentsTwo:Array = new_Array(1,Std.int(length));
+
+ // algo loop
+ for(i in handOne)
+ {
+ var index = Std.int(((i.strumTime / 1000)));
+ if (index + 1 > segmentsOne.length)
+ continue;
+ segmentsOne[index] = segmentsOne[index] + 1;
+ }
+
+ for(i in handTwo)
+ {
+ var index = Std.int(((i.strumTime / 1000)));
+ if (index + 1 > segmentsTwo.length)
+ continue;
+ segmentsTwo[index] = segmentsTwo[index] + 1;
+ }
+
+ // get the average of all of the segments
+ var sumOne:Float = 0;
+ var sumTwo:Float = 0;
+
+
+ var lone = segmentsOne.length;
+ var ltwo = segmentsOne.length;
+
+ for (i in segmentsOne)
+ {
+ if (i == 0) // remove empty/breaks
+ {
+ lone--;
+ continue;
+ }
+ //trace(i);
+ sumOne += i / .5; // half it because otherwise instead of nps its just fucking notes per half second which is dumb and stupid
+ }
+
+ for (i in segmentsTwo)
+ {
+ if (i == 0) // remove empty/breaks
+ {
+ ltwo--;
+ continue;
+ }
+ //trace(i);
+ sumTwo += i / .5; // half it because otherwise instead of nps its just fucking notes per half second which is dumb and stupid
+ }
+
+
+ var handOneAvg = sumOne / lone;
+ var handTwoAvg = sumTwo / ltwo;
+
+ return HelperFunctions.truncateFloat(handOneAvg > handTwoAvg ? handOneAvg : handTwoAvg,2);
+ }
+
+ static public function new_Array( ArrayType:T, Length:Int ):Array {
+ var empty:Null = null;
+ var newArray:Array = new Array();
+
+ for ( i in 0...Length ) {
+ newArray.push( empty );
+ }
+
+ return newArray;
+ }
+}
\ No newline at end of file
diff --git a/source_duck/Discord.hx b/source_duck/Discord.hx
new file mode 100644
index 00000000..00018e78
--- /dev/null
+++ b/source_duck/Discord.hx
@@ -0,0 +1,89 @@
+package;
+
+#if windows
+import Sys.sleep;
+import discord_rpc.DiscordRpc;
+
+using StringTools;
+
+class DiscordClient
+{
+ public function new()
+ {
+ trace("Discord Client starting...");
+ DiscordRpc.start({
+ clientID: "557069829501091850", // change this to what ever the fuck you want lol
+ onReady: onReady,
+ onError: onError,
+ onDisconnected: onDisconnected
+ });
+ trace("Discord Client started.");
+
+ while (true)
+ {
+ DiscordRpc.process();
+ sleep(2);
+ //trace("Discord Client Update");
+ }
+
+ DiscordRpc.shutdown();
+ }
+
+ public static function shutdown()
+ {
+ DiscordRpc.shutdown();
+ }
+
+ static function onReady()
+ {
+ DiscordRpc.presence({
+ details: "In the Menus",
+ state: null,
+ largeImageKey: 'icon',
+ largeImageText: "fridaynightfunkin"
+ });
+ }
+
+ static function onError(_code:Int, _message:String)
+ {
+ trace('Error! $_code : $_message');
+ }
+
+ static function onDisconnected(_code:Int, _message:String)
+ {
+ trace('Disconnected! $_code : $_message');
+ }
+
+ public static function initialize()
+ {
+ var DiscordDaemon = sys.thread.Thread.create(() ->
+ {
+ new DiscordClient();
+ });
+ trace("Discord Client initialized");
+ }
+
+ public static function changePresence(details:String, state:Null, ?smallImageKey : String, ?hasStartTimestamp : Bool, ?endTimestamp: Float)
+ {
+ var startTimestamp:Float = if(hasStartTimestamp) Date.now().getTime() else 0;
+
+ if (endTimestamp > 0)
+ {
+ endTimestamp = startTimestamp + endTimestamp;
+ }
+
+ DiscordRpc.presence({
+ details: details,
+ state: state,
+ largeImageKey: 'icon',
+ largeImageText: "fridaynightfunkin",
+ smallImageKey : smallImageKey,
+ // Obtained times are in milliseconds so they are divided so Discord can use it
+ startTimestamp : Std.int(startTimestamp / 1000),
+ endTimestamp : Std.int(endTimestamp / 1000)
+ });
+
+ //trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
+ }
+}
+#end
\ No newline at end of file
diff --git a/source_duck/EtternaFunctions.hx b/source_duck/EtternaFunctions.hx
new file mode 100644
index 00000000..9fcfbaea
--- /dev/null
+++ b/source_duck/EtternaFunctions.hx
@@ -0,0 +1,84 @@
+class EtternaFunctions
+{
+ // erf constants
+ public static var a1 = 0.254829592;
+ public static var a2 = -0.284496736;
+ public static var a3 = 1.421413741;
+ public static var a4 = -1.453152027;
+ public static var a5 = 1.061405429;
+ public static var p = 0.3275911;
+
+ public static function erf(x:Float):Float
+ {
+ // Save the sign of x
+ var sign = 1;
+ if (x < 0)
+ sign = -1;
+ x = Math.abs(x);
+
+ // A&S formula 7.1.26
+ var t = 1.0/(1.0 + p*x);
+ var y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-x*x);
+
+ return sign*y;
+ }
+
+ public static function getNotes():Int
+ {
+ var notes:Int = 0;
+ for (i in 0...PlayState.SONG.notes.length)
+ {
+ for (ii in 0...PlayState.SONG.notes[i].sectionNotes.length)
+ {
+ var n = PlayState.SONG.notes[i].sectionNotes[ii];
+ if (n[1] <= 0)
+ notes++;
+ }
+ }
+ return notes;
+ }
+
+ public static function getHolds():Int
+ {
+ var notes:Int = 0;
+ for (i in 0...PlayState.SONG.notes.length)
+ {
+ trace(PlayState.SONG.notes[i]);
+ for (ii in 0...PlayState.SONG.notes[i].sectionNotes.length)
+ {
+ var n = PlayState.SONG.notes[i].sectionNotes[ii];
+ trace(n);
+ if (n[1] > 0)
+ notes++;
+ }
+ }
+ return notes;
+ }
+
+ public static function getMapMaxScore():Int
+ {
+ return (getNotes() * 350);
+ }
+
+ public static function wife3(maxms:Float, ts:Float)
+ {
+ var max_points = 1.0;
+ var miss_weight = -5.5;
+ var ridic= 5 * ts;
+ var max_boo_weight = 180 * ts;
+ var ts_pow = 0.75;
+ var zero = 65 * (Math.pow(ts,ts_pow));
+ var power = 2.5;
+ var dev = 22.7 * (Math.pow(ts,ts_pow));
+
+ if (maxms <= ridic) // anything below this (judge scaled) threshold is counted as full pts
+ return max_points;
+ else if (maxms <= zero) // ma/pa region, exponential
+ return max_points * erf((zero - maxms) / dev);
+ else if (maxms <= max_boo_weight)// cb region, linear
+ return (maxms - zero) * miss_weight / (max_boo_weight - zero);
+ else
+ return miss_weight;
+ }
+
+}
\ No newline at end of file
diff --git a/source_duck/FreeplayState.hx b/source_duck/FreeplayState.hx
new file mode 100644
index 00000000..59ecc708
--- /dev/null
+++ b/source_duck/FreeplayState.hx
@@ -0,0 +1,307 @@
+package;
+
+import flash.text.TextField;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.display.FlxGridOverlay;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.math.FlxMath;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
+import lime.utils.Assets;
+
+
+#if windows
+import Discord.DiscordClient;
+#end
+
+using StringTools;
+
+class FreeplayState extends MusicBeatState
+{
+ var songs:Array = [];
+
+ var selector:FlxText;
+ var curSelected:Int = 0;
+ var curDifficulty:Int = 1;
+
+ var scoreText:FlxText;
+ var diffText:FlxText;
+ var lerpScore:Int = 0;
+ var intendedScore:Int = 0;
+
+ private var grpSongs:FlxTypedGroup;
+ private var curPlaying:Bool = false;
+
+ private var iconArray:Array = [];
+
+ override function create()
+ {
+ var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
+
+ for (i in 0...initSonglist.length)
+ {
+ var data:Array = initSonglist[i].split(':');
+ songs.push(new SongMetadata(data[0], Std.parseInt(data[2]), data[1]));
+ }
+
+ /*
+ if (FlxG.sound.music != null)
+ {
+ if (!FlxG.sound.music.playing)
+ FlxG.sound.playMusic(Paths.music('freakyMenu'));
+ }
+ */
+
+ #if windows
+ // Updating Discord Rich Presence
+ DiscordClient.changePresence("In the Freeplay Menu", null);
+ #end
+
+ var isDebug:Bool = false;
+
+ #if debug
+ isDebug = true;
+ #end
+
+ // LOAD MUSIC
+
+ // LOAD CHARACTERS
+
+ var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('menuBGBlue'));
+ add(bg);
+
+ grpSongs = new FlxTypedGroup();
+ add(grpSongs);
+
+ for (i in 0...songs.length)
+ {
+ var songText:Alphabet = new Alphabet(0, (70 * i) + 30, songs[i].songName, true, false);
+ songText.isMenuItem = true;
+ songText.targetY = i;
+ grpSongs.add(songText);
+
+ var icon:HealthIcon = new HealthIcon(songs[i].songCharacter);
+ icon.sprTracker = songText;
+
+ // using a FlxGroup is too much fuss!
+ iconArray.push(icon);
+ add(icon);
+
+ // songText.x += 40;
+ // DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !!
+ // songText.screenCenter(X);
+ }
+
+ scoreText = new FlxText(FlxG.width * 0.7, 5, 0, "", 32);
+ // scoreText.autoSize = false;
+ scoreText.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, RIGHT);
+ // scoreText.alignment = RIGHT;
+
+ var scoreBG:FlxSprite = new FlxSprite(scoreText.x - 6, 0).makeGraphic(Std.int(FlxG.width * 0.35), 66, 0xFF000000);
+ scoreBG.alpha = 0.6;
+ add(scoreBG);
+
+ diffText = new FlxText(scoreText.x, scoreText.y + 36, 0, "", 24);
+ diffText.font = scoreText.font;
+ add(diffText);
+
+ add(scoreText);
+
+ changeSelection();
+ changeDiff();
+
+ // FlxG.sound.playMusic(Paths.music('title'), 0);
+ // FlxG.sound.music.fadeIn(2, 0, 0.8);
+ selector = new FlxText();
+
+ selector.size = 40;
+ selector.text = ">";
+ // add(selector);
+
+ var swag:Alphabet = new Alphabet(1, 0, "swag");
+
+ // JUST DOIN THIS SHIT FOR TESTING!!!
+ /*
+ var md:String = Markdown.markdownToHtml(Assets.getText('CHANGELOG.md'));
+
+ var texFel:TextField = new TextField();
+ texFel.width = FlxG.width;
+ texFel.height = FlxG.height;
+ // texFel.
+ texFel.htmlText = md;
+
+ FlxG.stage.addChild(texFel);
+
+ // scoreText.textField.htmlText = md;
+
+ trace(md);
+ */
+
+ super.create();
+ }
+
+ public function addSong(songName:String, weekNum:Int, songCharacter:String)
+ {
+ songs.push(new SongMetadata(songName, weekNum, songCharacter));
+ }
+
+ public function addWeek(songs:Array, weekNum:Int, ?songCharacters:Array)
+ {
+ if (songCharacters == null)
+ songCharacters = ['bf'];
+
+ var num:Int = 0;
+ for (song in songs)
+ {
+ addSong(song, weekNum, songCharacters[num]);
+
+ if (songCharacters.length != 1)
+ num++;
+ }
+ }
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ if (FlxG.sound.music.volume < 0.7)
+ {
+ FlxG.sound.music.volume += 0.5 * FlxG.elapsed;
+ }
+
+ lerpScore = Math.floor(FlxMath.lerp(lerpScore, intendedScore, 0.4));
+
+ if (Math.abs(lerpScore - intendedScore) <= 10)
+ lerpScore = intendedScore;
+
+ scoreText.text = "PERSONAL BEST:" + lerpScore;
+
+ var upP = controls.UP_P;
+ var downP = controls.DOWN_P;
+ var accepted = controls.ACCEPT;
+
+ if (upP)
+ {
+ changeSelection(-1);
+ }
+ if (downP)
+ {
+ changeSelection(1);
+ }
+
+ if (controls.LEFT_P)
+ changeDiff(-1);
+ if (controls.RIGHT_P)
+ changeDiff(1);
+
+ if (controls.BACK)
+ {
+ FlxG.switchState(new MainMenuState());
+ }
+
+ if (accepted)
+ {
+ var poop:String = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), curDifficulty);
+
+ trace(poop);
+
+ PlayState.SONG = Song.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
+ PlayState.isStoryMode = false;
+ PlayState.storyDifficulty = curDifficulty;
+ PlayState.storyWeek = songs[curSelected].week;
+ trace('CUR WEEK' + PlayState.storyWeek);
+ LoadingState.loadAndSwitchState(new PlayState());
+ }
+ }
+
+ function changeDiff(change:Int = 0)
+ {
+ curDifficulty += change;
+
+ if (curDifficulty < 0)
+ curDifficulty = 2;
+ if (curDifficulty > 2)
+ curDifficulty = 0;
+
+ #if !switch
+ intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
+ #end
+
+ switch (curDifficulty)
+ {
+ case 0:
+ diffText.text = "EASY";
+ case 1:
+ diffText.text = 'NORMAL';
+ case 2:
+ diffText.text = "HARD";
+ }
+ }
+
+ function changeSelection(change:Int = 0)
+ {
+ #if !switch
+ // NGio.logEvent('Fresh');
+ #end
+
+ // NGio.logEvent('Fresh');
+ FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
+
+ curSelected += change;
+
+ if (curSelected < 0)
+ curSelected = songs.length - 1;
+ if (curSelected >= songs.length)
+ curSelected = 0;
+
+ // selector.y = (70 * curSelected) + 30;
+
+ #if !switch
+ intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
+ // lerpScore = 0;
+ #end
+
+ #if PRELOAD_ALL
+ FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName), 0);
+ #end
+
+ var bullShit:Int = 0;
+
+ for (i in 0...iconArray.length)
+ {
+ iconArray[i].alpha = 0.6;
+ }
+
+ iconArray[curSelected].alpha = 1;
+
+ for (item in grpSongs.members)
+ {
+ item.targetY = bullShit - curSelected;
+ bullShit++;
+
+ item.alpha = 0.6;
+ // item.setGraphicSize(Std.int(item.width * 0.8));
+
+ if (item.targetY == 0)
+ {
+ item.alpha = 1;
+ // item.setGraphicSize(Std.int(item.width));
+ }
+ }
+ }
+}
+
+class SongMetadata
+{
+ public var songName:String = "";
+ public var week:Int = 0;
+ public var songCharacter:String = "";
+
+ public function new(song:String, week:Int, songCharacter:String)
+ {
+ this.songName = song;
+ this.week = week;
+ this.songCharacter = songCharacter;
+ }
+}
diff --git a/source_duck/GameOverState.hx b/source_duck/GameOverState.hx
new file mode 100644
index 00000000..57c60d1f
--- /dev/null
+++ b/source_duck/GameOverState.hx
@@ -0,0 +1,82 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.transition.FlxTransitionableState;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.input.gamepad.FlxGamepad;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+
+class GameOverState extends FlxTransitionableState
+{
+ var bfX:Float = 0;
+ var bfY:Float = 0;
+
+ public function new(x:Float, y:Float)
+ {
+ super();
+
+ bfX = x;
+ bfY = y;
+ }
+
+ override function create()
+ {
+ /* var loser:FlxSprite = new FlxSprite(100, 100);
+ var loseTex = FlxAtlasFrames.fromSparrow(AssetPaths.lose.png, AssetPaths.lose.xml);
+ loser.frames = loseTex;
+ loser.animation.addByPrefix('lose', 'lose', 24, false);
+ loser.animation.play('lose');
+ // add(loser); */
+
+ var bf:Boyfriend = new Boyfriend(bfX, bfY);
+ // bf.scrollFactor.set();
+ add(bf);
+ bf.playAnim('firstDeath');
+
+ FlxG.camera.follow(bf, LOCKON, 0.001);
+ /*
+ var restart:FlxSprite = new FlxSprite(500, 50).loadGraphic(AssetPaths.restart.png);
+ restart.setGraphicSize(Std.int(restart.width * 0.6));
+ restart.updateHitbox();
+ restart.alpha = 0;
+ restart.antialiasing = true;
+ // add(restart); */
+
+ FlxG.sound.music.fadeOut(2, FlxG.sound.music.volume * 0.6);
+
+ // FlxTween.tween(restart, {alpha: 1}, 1, {ease: FlxEase.quartInOut});
+ // FlxTween.tween(restart, {y: restart.y + 40}, 7, {ease: FlxEase.quartInOut, type: PINGPONG});
+
+ super.create();
+ }
+
+ private var fading:Bool = false;
+
+ override function update(elapsed:Float)
+ {
+ var pressed:Bool = FlxG.keys.justPressed.ANY;
+
+ var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
+
+ if (gamepad != null)
+ {
+ if (gamepad.justPressed.ANY)
+ pressed = true;
+ }
+
+ pressed = false;
+
+ if (pressed && !fading)
+ {
+ fading = true;
+ FlxG.sound.music.fadeOut(0.5, 0, function(twn:FlxTween)
+ {
+ FlxG.sound.music.stop();
+ LoadingState.loadAndSwitchState(new PlayState());
+ });
+ }
+ super.update(elapsed);
+ }
+}
diff --git a/source_duck/GameOverSubstate.hx b/source_duck/GameOverSubstate.hx
new file mode 100644
index 00000000..1a2752e9
--- /dev/null
+++ b/source_duck/GameOverSubstate.hx
@@ -0,0 +1,116 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxObject;
+import flixel.FlxSubState;
+import flixel.math.FlxPoint;
+import flixel.util.FlxColor;
+import flixel.util.FlxTimer;
+
+class GameOverSubstate extends MusicBeatSubstate
+{
+ var bf:Boyfriend;
+ var camFollow:FlxObject;
+
+ var stageSuffix:String = "";
+
+ public function new(x:Float, y:Float)
+ {
+ var daStage = PlayState.curStage;
+ var daBf:String = '';
+ switch (daStage)
+ {
+ case 'school':
+ stageSuffix = '-pixel';
+ daBf = 'bf-pixel-dead';
+ case 'schoolEvil':
+ stageSuffix = '-pixel';
+ daBf = 'bf-pixel-dead';
+ default:
+ daBf = 'bf';
+ }
+
+ super();
+
+ Conductor.songPosition = 0;
+
+ bf = new Boyfriend(x, y, daBf);
+ add(bf);
+
+ camFollow = new FlxObject(bf.getGraphicMidpoint().x, bf.getGraphicMidpoint().y, 1, 1);
+ add(camFollow);
+
+ FlxG.sound.play(Paths.sound('fnf_loss_sfx' + stageSuffix));
+ Conductor.changeBPM(100);
+
+ // FlxG.camera.followLerp = 1;
+ // FlxG.camera.focusOn(FlxPoint.get(FlxG.width / 2, FlxG.height / 2));
+ FlxG.camera.scroll.set();
+ FlxG.camera.target = null;
+
+ bf.playAnim('firstDeath');
+ }
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ if (controls.ACCEPT)
+ {
+ endBullshit();
+ }
+
+ if (controls.BACK)
+ {
+ FlxG.sound.music.stop();
+
+ if (PlayState.isStoryMode)
+ FlxG.switchState(new StoryMenuState());
+ else
+ FlxG.switchState(new FreeplayState());
+ PlayState.loadRep = false;
+ }
+
+ if (bf.animation.curAnim.name == 'firstDeath' && bf.animation.curAnim.curFrame == 12)
+ {
+ FlxG.camera.follow(camFollow, LOCKON, 0.01);
+ }
+
+ if (bf.animation.curAnim.name == 'firstDeath' && bf.animation.curAnim.finished)
+ {
+ FlxG.sound.playMusic(Paths.music('gameOver' + stageSuffix));
+ }
+
+ if (FlxG.sound.music.playing)
+ {
+ Conductor.songPosition = FlxG.sound.music.time;
+ }
+ }
+
+ override function beatHit()
+ {
+ super.beatHit();
+
+ FlxG.log.add('beat');
+ }
+
+ var isEnding:Bool = false;
+
+ function endBullshit():Void
+ {
+ if (!isEnding)
+ {
+ isEnding = true;
+ bf.playAnim('deathConfirm', true);
+ FlxG.sound.music.stop();
+ FlxG.sound.play(Paths.music('gameOverEnd' + stageSuffix));
+ new FlxTimer().start(0.7, function(tmr:FlxTimer)
+ {
+ FlxG.camera.fade(FlxColor.BLACK, 2, false, function()
+ {
+ LoadingState.loadAndSwitchState(new PlayState());
+ });
+ });
+ }
+ }
+}
diff --git a/source_duck/GameplayCustomizeState.hx b/source_duck/GameplayCustomizeState.hx
new file mode 100644
index 00000000..b3991c80
--- /dev/null
+++ b/source_duck/GameplayCustomizeState.hx
@@ -0,0 +1,213 @@
+import flixel.math.FlxMath;
+import flixel.FlxCamera;
+import flixel.math.FlxPoint;
+import flixel.FlxObject;
+#if windows
+import Discord.DiscordClient;
+import sys.thread.Thread;
+#end
+
+import flixel.group.FlxGroup.FlxTypedGroup;
+import openfl.ui.Keyboard;
+import flixel.FlxSprite;
+import flixel.FlxG;
+
+class GameplayCustomizeState extends MusicBeatState
+{
+
+ var defaultX:Float = FlxG.width * 0.55 - 135;
+ var defaultY:Float = FlxG.height / 2 - 50;
+
+ var background:FlxSprite = new FlxSprite(-600, -200).loadGraphic(Paths.image('stageback','shared'));
+ var curt:FlxSprite = new FlxSprite(-500, -300).loadGraphic(Paths.image('stagecurtains','shared'));
+ var front:FlxSprite = new FlxSprite(-650, 600).loadGraphic(Paths.image('stagefront','shared'));
+
+ var sick:FlxSprite = new FlxSprite().loadGraphic(Paths.image('sick','shared'));
+
+ var bf:Boyfriend = new Boyfriend(770, 450, 'bf');
+ var dad:Character;
+
+ var strumLine:FlxSprite;
+ var strumLineNotes:FlxTypedGroup;
+ var playerStrums:FlxTypedGroup;
+ private var camHUD:FlxCamera;
+
+ public override function create() {
+ #if windows
+ // Updating Discord Rich Presence
+ DiscordClient.changePresence("Customizing Gameplay", null);
+ #end
+
+ Conductor.changeBPM(102);
+ persistentUpdate = true;
+
+ super.create();
+
+ camHUD = new FlxCamera();
+ camHUD.bgColor.alpha = 0;
+ FlxG.cameras.add(camHUD);
+
+ background.scrollFactor.set(0.9,0.9);
+ curt.scrollFactor.set(0.9,0.9);
+ front.scrollFactor.set(0.9,0.9);
+
+ add(background);
+ add(front);
+ add(curt);
+
+ var camFollow = new FlxObject(0, 0, 1, 1);
+
+ dad = new Character(100, 100, 'dad');
+
+ var camPos:FlxPoint = new FlxPoint(dad.getGraphicMidpoint().x + 400, dad.getGraphicMidpoint().y);
+
+ camFollow.setPosition(camPos.x, camPos.y);
+
+ add(bf);
+ add(dad);
+
+ add(sick);
+
+ add(camFollow);
+
+ FlxG.camera.follow(camFollow, LOCKON, 0.01);
+ // FlxG.camera.setScrollBounds(0, FlxG.width, 0, FlxG.height);
+ FlxG.camera.zoom = 0.9;
+ FlxG.camera.focusOn(camFollow.getPosition());
+
+ strumLine = new FlxSprite(0, 25).makeGraphic(FlxG.width, 10);
+ strumLine.scrollFactor.set();
+
+ if (FlxG.save.data.downscroll)
+ strumLine.y = FlxG.height - 165;
+
+ strumLineNotes = new FlxTypedGroup();
+ add(strumLineNotes);
+
+ playerStrums = new FlxTypedGroup();
+
+ sick.cameras = [camHUD];
+ strumLine.cameras = [camHUD];
+ playerStrums.cameras = [camHUD];
+
+ generateStaticArrows(0);
+ generateStaticArrows(1);
+
+
+ if (!FlxG.save.data.changedHit)
+ {
+ FlxG.save.data.changedHitX = defaultX;
+ FlxG.save.data.changedHitY = defaultY;
+ }
+
+ sick.x = FlxG.save.data.changedHitX;
+ sick.y = FlxG.save.data.changedHitY;
+
+ sick.updateHitbox();
+
+ FlxG.mouse.visible = true;
+
+ }
+
+ override function update(elapsed:Float) {
+ if (FlxG.sound.music != null)
+ Conductor.songPosition = FlxG.sound.music.time;
+
+ super.update(elapsed);
+
+ FlxG.camera.zoom = FlxMath.lerp(0.9, FlxG.camera.zoom, 0.95);
+ camHUD.zoom = FlxMath.lerp(1, camHUD.zoom, 0.95);
+
+ if (FlxG.mouse.overlaps(sick) && FlxG.mouse.pressed)
+ {
+ sick.x = FlxG.mouse.x - sick.width / 2;
+ sick.y = FlxG.mouse.y - sick.height / 2;
+ }
+
+ if (FlxG.mouse.overlaps(sick) && FlxG.mouse.justReleased)
+ {
+ FlxG.save.data.changedHitX = sick.x;
+ FlxG.save.data.changedHitY = sick.y;
+ FlxG.save.data.changedHit = true;
+ }
+
+ if (controls.BACK)
+ {
+ FlxG.mouse.visible = false;
+ FlxG.sound.play(Paths.sound('cancelMenu'));
+ FlxG.switchState(new OptionsMenu());
+ }
+
+ }
+
+ override function beatHit()
+ {
+ super.beatHit();
+
+ bf.playAnim('idle');
+ dad.dance();
+
+ FlxG.camera.zoom += 0.015;
+ camHUD.zoom += 0.010;
+
+ trace('beat');
+
+ }
+
+
+ // ripped from play state cuz im lazy
+
+ private function generateStaticArrows(player:Int):Void
+ {
+ for (i in 0...4)
+ {
+ // FlxG.log.add(i);
+ var babyArrow:FlxSprite = new FlxSprite(0, strumLine.y);
+ babyArrow.frames = Paths.getSparrowAtlas('NOTE_assets', 'shared');
+ 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.x += Note.swagWidth * 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.x += Note.swagWidth * 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.x += Note.swagWidth * 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.x += Note.swagWidth * 3;
+ babyArrow.animation.addByPrefix('static', 'arrowRIGHT');
+ babyArrow.animation.addByPrefix('pressed', 'right press', 24, false);
+ babyArrow.animation.addByPrefix('confirm', 'right confirm', 24, false);
+ }
+ babyArrow.updateHitbox();
+ babyArrow.scrollFactor.set();
+
+ babyArrow.ID = i;
+
+ if (player == 1)
+ {
+ playerStrums.add(babyArrow);
+ }
+
+ babyArrow.animation.play('static');
+ babyArrow.x += 50;
+ babyArrow.x += ((FlxG.width / 2) * player);
+
+ strumLineNotes.add(babyArrow);
+ }
+ }
+}
\ No newline at end of file
diff --git a/source_duck/GitarooPause.hx b/source_duck/GitarooPause.hx
new file mode 100644
index 00000000..cb9705c5
--- /dev/null
+++ b/source_duck/GitarooPause.hx
@@ -0,0 +1,88 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+
+class GitarooPause extends MusicBeatState
+{
+ var replayButton:FlxSprite;
+ var cancelButton:FlxSprite;
+
+ var replaySelect:Bool = false;
+
+ public function new():Void
+ {
+ super();
+ }
+
+ override function create()
+ {
+ if (FlxG.sound.music != null)
+ FlxG.sound.music.stop();
+
+ var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('pauseAlt/pauseBG'));
+ add(bg);
+
+ var bf:FlxSprite = new FlxSprite(0, 30);
+ bf.frames = Paths.getSparrowAtlas('pauseAlt/bfLol');
+ bf.animation.addByPrefix('lol', "funnyThing", 13);
+ bf.animation.play('lol');
+ add(bf);
+ bf.screenCenter(X);
+
+ replayButton = new FlxSprite(FlxG.width * 0.28, FlxG.height * 0.7);
+ replayButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI');
+ replayButton.animation.addByPrefix('selected', 'bluereplay', 0, false);
+ replayButton.animation.appendByPrefix('selected', 'yellowreplay');
+ replayButton.animation.play('selected');
+ add(replayButton);
+
+ cancelButton = new FlxSprite(FlxG.width * 0.58, replayButton.y);
+ cancelButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI');
+ cancelButton.animation.addByPrefix('selected', 'bluecancel', 0, false);
+ cancelButton.animation.appendByPrefix('selected', 'cancelyellow');
+ cancelButton.animation.play('selected');
+ add(cancelButton);
+
+ changeThing();
+
+ super.create();
+ }
+
+ override function update(elapsed:Float)
+ {
+ if (controls.LEFT_P || controls.RIGHT_P)
+ changeThing();
+
+ if (controls.ACCEPT)
+ {
+ if (replaySelect)
+ {
+ FlxG.switchState(new PlayState());
+ }
+ else
+ {
+ FlxG.switchState(new MainMenuState());
+ }
+ }
+
+ super.update(elapsed);
+ }
+
+ function changeThing():Void
+ {
+ replaySelect = !replaySelect;
+
+ if (replaySelect)
+ {
+ cancelButton.animation.curAnim.curFrame = 0;
+ replayButton.animation.curAnim.curFrame = 1;
+ }
+ else
+ {
+ cancelButton.animation.curAnim.curFrame = 1;
+ replayButton.animation.curAnim.curFrame = 0;
+ }
+ }
+}
diff --git a/source_duck/GlobalVideo.hx b/source_duck/GlobalVideo.hx
new file mode 100644
index 00000000..2c4f7078
--- /dev/null
+++ b/source_duck/GlobalVideo.hx
@@ -0,0 +1,95 @@
+package;
+
+import openfl.Lib;
+
+class GlobalVideo
+{
+ private static var video:VideoHandler;
+ private static var webm:WebmHandler;
+ public static var isWebm:Bool = false;
+ public static var isAndroid:Bool = false;
+ public static var daAlpha1:Float = 0.2;
+ public static var daAlpha2:Float = 1;
+
+ public static function setVid(vid:VideoHandler):Void
+ {
+ video = vid;
+ }
+
+ public static function getVid():VideoHandler
+ {
+ return video;
+ }
+
+ public static function setWebm(vid:WebmHandler):Void
+ {
+ webm = vid;
+ isWebm = true;
+ }
+
+ public static function getWebm():WebmHandler
+ {
+ return webm;
+ }
+
+ public static function get():Dynamic
+ {
+ if (isWebm)
+ {
+ return getWebm();
+ } else {
+ return getVid();
+ }
+ }
+
+ public static function calc(ind:Int):Dynamic
+ {
+ var stageWidth:Int = Lib.current.stage.stageWidth;
+ var stageHeight:Int = Lib.current.stage.stageHeight;
+
+ var width:Float = GameDimensions.width;
+ var height:Float = GameDimensions.height;
+
+ //trace("AH: " + stageWidth);
+ //trace(width);
+
+ var ratioX:Float = height / width;
+ var ratioY:Float = width / height;
+ var appliedWidth:Float = stageHeight * ratioY;
+ var appliedHeight:Float = stageWidth * ratioX;
+ //trace(appliedWidth);
+ var remainingX:Float = stageWidth - appliedWidth;
+ var remainingY:Float = stageHeight - appliedHeight;
+ remainingX = remainingX / 2;
+ remainingY = remainingY / 2;
+
+ appliedWidth = Std.int(appliedWidth);
+ appliedHeight = Std.int(appliedHeight);
+
+ if (appliedHeight > stageHeight)
+ {
+ remainingY = 0;
+ appliedHeight = stageHeight;
+ }
+
+ if (appliedWidth > stageWidth)
+ {
+ remainingX = 0;
+ appliedWidth = stageWidth;
+ }
+
+ switch(ind)
+ {
+ case 0:
+ return remainingX;
+ case 1:
+ return remainingY;
+ case 2:
+ return appliedWidth;
+ case 3:
+ return appliedHeight;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/source_duck/HealthIcon.hx b/source_duck/HealthIcon.hx
new file mode 100644
index 00000000..a443be8a
--- /dev/null
+++ b/source_duck/HealthIcon.hx
@@ -0,0 +1,59 @@
+package;
+
+import flixel.FlxSprite;
+
+class HealthIcon extends FlxSprite
+{
+ /**
+ * Used for FreeplayState! If you use it elsewhere, prob gonna annoying
+ */
+ public var sprTracker:FlxSprite;
+
+ public function new(char:String = 'bf', isPlayer:Bool = false)
+ {
+ super();
+
+ loadGraphic(Paths.image('iconGrid'), true, 150, 150);
+
+ antialiasing = true;
+ animation.add('bf', [0, 1], 0, false, isPlayer);
+ animation.add('bf-car', [0, 1], 0, false, isPlayer);
+ animation.add('bf-christmas', [0, 1], 0, false, isPlayer);
+ animation.add('bf-pixel', [21, 21], 0, false, isPlayer);
+ animation.add('spooky', [2, 3], 0, false, isPlayer);
+ animation.add('pico', [4, 5], 0, false, isPlayer);
+ animation.add('mom', [6, 7], 0, false, isPlayer);
+ animation.add('mom-car', [6, 7], 0, false, isPlayer);
+ animation.add('tankman', [8, 9], 0, false, isPlayer);
+ animation.add('face', [10, 11], 0, false, isPlayer);
+ animation.add('dad', [12, 13], 0, false, isPlayer);
+ animation.add('senpai', [22, 22], 0, false, isPlayer);
+ animation.add('senpai-angry', [22, 22], 0, false, isPlayer);
+ animation.add('spirit', [23, 23], 0, false, isPlayer);
+ animation.add('bf-old', [14, 15], 0, false, isPlayer);
+ animation.add('gf', [16], 0, false, isPlayer);
+ animation.add('gf-christmas', [16], 0, false, isPlayer);
+ animation.add('gf-pixel', [16], 0, false, isPlayer);
+ animation.add('parents-christmas', [17, 18], 0, false, isPlayer);
+ animation.add('monster', [19, 20], 0, false, isPlayer);
+ animation.add('monster-christmas', [19, 20], 0, false, isPlayer);
+ animation.add('duck', [24, 25], 0, false, isPlayer);
+ animation.play(char);
+
+ switch (char)
+ {
+ case 'bf-pixel' | 'senpai' | 'senpai-angry' | 'spirit' | 'gf-pixel':
+ antialiasing = false;
+ }
+
+ scrollFactor.set();
+ }
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ if (sprTracker != null)
+ setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30);
+ }
+}
diff --git a/source_duck/HelperFunctions.hx b/source_duck/HelperFunctions.hx
new file mode 100644
index 00000000..8f6c6fca
--- /dev/null
+++ b/source_duck/HelperFunctions.hx
@@ -0,0 +1,9 @@
+class HelperFunctions
+{
+ public static function truncateFloat( number : Float, precision : Int): Float {
+ var num = number;
+ num = num * Math.pow(10, precision);
+ num = Math.round( num ) / Math.pow(10, precision);
+ return num;
+ }
+}
\ No newline at end of file
diff --git a/source_duck/Highscore.hx b/source_duck/Highscore.hx
new file mode 100644
index 00000000..5fbc7329
--- /dev/null
+++ b/source_duck/Highscore.hx
@@ -0,0 +1,98 @@
+package;
+
+import flixel.FlxG;
+
+class Highscore
+{
+ #if (haxe >= "4.0.0")
+ public static var songScores:Map = new Map();
+ #else
+ public static var songScores:Map = new Map();
+ #end
+
+
+ public static function saveScore(song:String, score:Int = 0, ?diff:Int = 0):Void
+ {
+ var daSong:String = formatSong(song, diff);
+
+
+ #if !switch
+ NGio.postScore(score, song);
+ #end
+
+
+ if (songScores.exists(daSong))
+ {
+ if (songScores.get(daSong) < score)
+ setScore(daSong, score);
+ }
+ else
+ setScore(daSong, score);
+ }
+
+ public static function saveWeekScore(week:Int = 1, score:Int = 0, ?diff:Int = 0):Void
+ {
+
+ #if !switch
+ NGio.postScore(score, "Week " + week);
+ #end
+
+
+ var daWeek:String = formatSong('week' + week, diff);
+
+ if (songScores.exists(daWeek))
+ {
+ if (songScores.get(daWeek) < score)
+ setScore(daWeek, score);
+ }
+ else
+ setScore(daWeek, score);
+ }
+
+ /**
+ * YOU SHOULD FORMAT SONG WITH formatSong() BEFORE TOSSING IN SONG VARIABLE
+ */
+ static function setScore(song:String, score:Int):Void
+ {
+ // Reminder that I don't need to format this song, it should come formatted!
+ songScores.set(song, score);
+ FlxG.save.data.songScores = songScores;
+ FlxG.save.flush();
+ }
+
+ public static function formatSong(song:String, diff:Int):String
+ {
+ var daSong:String = song;
+
+ if (diff == 0)
+ daSong += '-easy';
+ else if (diff == 2)
+ daSong += '-hard';
+
+ return daSong;
+ }
+
+ public static function getScore(song:String, diff:Int):Int
+ {
+ if (!songScores.exists(formatSong(song, diff)))
+ setScore(formatSong(song, diff), 0);
+
+ return songScores.get(formatSong(song, diff));
+ }
+
+ public static function getWeekScore(week:Int, diff:Int):Int
+ {
+ if (!songScores.exists(formatSong('week' + week, diff)))
+ setScore(formatSong('week' + week, diff), 0);
+
+ return songScores.get(formatSong('week' + week, diff));
+ }
+
+ public static function load():Void
+ {
+ if (FlxG.save.data.songScores != null)
+ {
+ songScores = FlxG.save.data.songScores;
+ }
+ }
+}
diff --git a/source_duck/HitGraph.hx b/source_duck/HitGraph.hx
new file mode 100644
index 00000000..169d8231
--- /dev/null
+++ b/source_duck/HitGraph.hx
@@ -0,0 +1,279 @@
+import flixel.FlxG;
+import openfl.display.Bitmap;
+import openfl.display.BitmapData;
+import openfl.text.TextFieldAutoSize;
+import flixel.system.FlxAssets;
+import openfl.text.TextFormat;
+import flash.display.Graphics;
+import flash.display.Shape;
+import flash.display.Sprite;
+import flash.text.TextField;
+import flash.text.TextFormatAlign;
+import flixel.math.FlxMath;
+import flixel.util.FlxColor;
+import flixel.util.FlxDestroyUtil;
+
+/**
+ * stolen from https://github.com/HaxeFlixel/flixel/blob/master/flixel/system/debug/stats/StatsGraph.hx
+ */
+class HitGraph extends Sprite
+{
+ static inline var AXIS_COLOR:FlxColor = 0xffffff;
+ static inline var AXIS_ALPHA:Float = 0.5;
+ inline static var HISTORY_MAX:Int = 30;
+
+ public var minLabel:TextField;
+ public var curLabel:TextField;
+ public var maxLabel:TextField;
+ public var avgLabel:TextField;
+
+ public var minValue:Float = -(Math.floor((PlayState.rep.replay.sf / 60) * 1000) + 95);
+ public var maxValue:Float = Math.floor((PlayState.rep.replay.sf / 60) * 1000) + 95;
+
+ public var showInput:Bool = FlxG.save.data.inputShow;
+
+ public var graphColor:FlxColor;
+
+ public var history:Array = [];
+
+ public var bitmap:Bitmap;
+
+ var _axis:Shape;
+ var _width:Int;
+ var _height:Int;
+ var _unit:String;
+ var _labelWidth:Int;
+ var _label:String;
+
+ public function new(X:Int, Y:Int, Width:Int, Height:Int)
+ {
+ super();
+ x = X;
+ y = Y;
+ _width = Width;
+ _height = Height;
+
+ var bm = new BitmapData(Width,Height);
+ bm.draw(this);
+ bitmap = new Bitmap(bm);
+
+ _axis = new Shape();
+ _axis.x = _labelWidth + 10;
+
+ var ts = Math.floor((PlayState.rep.replay.sf / 60) * 1000) / 166;
+
+ var early = createTextField(10,10,FlxColor.WHITE,12);
+ var late = createTextField(10,_height - 20,FlxColor.WHITE,12);
+
+ early.text = "Early (" + -166 * ts + "ms)";
+ late.text = "Late (" + 166 * ts + "ms)";
+
+ addChild(early);
+ addChild(late);
+
+ addChild(_axis);
+
+ drawAxes();
+ }
+
+ /**
+ * Redraws the axes of the graph.
+ */
+ function drawAxes():Void
+ {
+ var gfx = _axis.graphics;
+ gfx.clear();
+ gfx.lineStyle(1, AXIS_COLOR, AXIS_ALPHA);
+
+ // y-Axis
+ gfx.moveTo(0, 0);
+ gfx.lineTo(0, _height);
+
+ // x-Axis
+ gfx.moveTo(0, _height);
+ gfx.lineTo(_width, _height);
+
+ gfx.moveTo(0, _height / 2);
+ gfx.lineTo(_width, _height / 2);
+
+ }
+
+ public static function createTextField(X:Float = 0, Y:Float = 0, Color:FlxColor = FlxColor.WHITE, Size:Int = 12):TextField
+ {
+ return initTextField(new TextField(), X, Y, Color, Size);
+ }
+
+ public static function initTextField(tf:T, X:Float = 0, Y:Float = 0, Color:FlxColor = FlxColor.WHITE, Size:Int = 12):T
+ {
+ tf.x = X;
+ tf.y = Y;
+ tf.multiline = false;
+ tf.wordWrap = false;
+ tf.embedFonts = true;
+ tf.selectable = false;
+ #if flash
+ tf.antiAliasType = AntiAliasType.NORMAL;
+ tf.gridFitType = GridFitType.PIXEL;
+ #end
+ tf.defaultTextFormat = new TextFormat("assets/fonts/vcr.ttf", Size, Color.to24Bit());
+ tf.alpha = Color.alphaFloat;
+ tf.autoSize = TextFieldAutoSize.LEFT;
+ return tf;
+ }
+
+ function drawJudgementLine(ms:Float):Void
+ {
+
+ var gfx:Graphics = graphics;
+
+ gfx.lineStyle(1, graphColor, 0.3);
+
+ var ts = Math.floor((PlayState.rep.replay.sf / 60) * 1000) / 166;
+ var range:Float = Math.max(maxValue - minValue, maxValue * 0.1);
+
+ var value = ((ms * ts) - minValue) / range;
+
+ var pointY = _axis.y + ((-value * _height - 1) + _height);
+
+ var graphX = _axis.x + 1;
+
+ if (ms == 45)
+ gfx.moveTo(graphX, _axis.y + pointY);
+
+ var graphX = _axis.x + 1;
+
+ gfx.drawRect(graphX,pointY, _width,1);
+
+ gfx.lineStyle(1, graphColor, 1);
+ }
+
+ /**
+ * Redraws the graph based on the values stored in the history.
+ */
+ function drawGraph():Void
+ {
+ var gfx:Graphics = graphics;
+ gfx.clear();
+ gfx.lineStyle(1, graphColor, 1);
+
+ gfx.beginFill(0x00FF00);
+ drawJudgementLine(45);
+ gfx.endFill();
+
+ gfx.beginFill(0xFF0000);
+ drawJudgementLine(90);
+ gfx.endFill();
+
+ gfx.beginFill(0x8b0000);
+ drawJudgementLine(135);
+ gfx.endFill();
+
+ gfx.beginFill(0x580000);
+ drawJudgementLine(166);
+ gfx.endFill();
+
+ gfx.beginFill(0x00FF00);
+ drawJudgementLine(-45);
+ gfx.endFill();
+
+ gfx.beginFill(0xFF0000);
+ drawJudgementLine(-90);
+ gfx.endFill();
+
+ gfx.beginFill(0x8b0000);
+ drawJudgementLine(-135);
+ gfx.endFill();
+
+ gfx.beginFill(0x580000);
+ drawJudgementLine(-166);
+ gfx.endFill();
+
+ var range:Float = Math.max(maxValue - minValue, maxValue * 0.1);
+ var graphX = _axis.x + 1;
+
+ if (showInput)
+ {
+ for (i in 0...PlayState.rep.replay.ana.anaArray.length)
+ {
+ var ana = PlayState.rep.replay.ana.anaArray[i];
+
+ var value = (ana.key * 25 - minValue) / range;
+
+ if (ana.hit)
+ gfx.beginFill(0xFFFF00);
+ else
+ gfx.beginFill(0xC2B280);
+
+ if (ana.hitTime < 0)
+ continue;
+
+ var pointY = (-value * _height - 1) + _height;
+ gfx.drawRect(graphX + fitX(ana.hitTime), pointY,2,2);
+ gfx.endFill();
+ }
+ }
+
+ for (i in 0...history.length)
+ {
+ var value = (history[i][0] - minValue) / range;
+ var judge = history[i][1];
+
+ switch(judge)
+ {
+ case "sick":
+ gfx.beginFill(0x00FFFF);
+ case "good":
+ gfx.beginFill(0x00FF00);
+ case "bad":
+ gfx.beginFill(0xFF0000);
+ case "shit":
+ gfx.beginFill(0x8b0000);
+ case "miss":
+ gfx.beginFill(0x580000);
+ default:
+ gfx.beginFill(0xFFFFFF);
+ }
+ var pointY = (-value * _height - 1) + _height;
+
+ /*if (i == 0)
+ gfx.moveTo(graphX, _axis.y + pointY);*/
+ gfx.drawRect(fitX(history[i][2]), pointY,4,4);
+
+ gfx.endFill();
+ }
+
+
+ var bm = new BitmapData(_width,_height);
+ bm.draw(this);
+ bitmap = new Bitmap(bm);
+ }
+
+ public function fitX(x:Float)
+ {
+ return (x / FlxG.sound.music.length) * width;
+ }
+
+ public function addToHistory(diff:Float, judge:String, time:Float)
+ {
+ history.push([diff,judge, time]);
+ }
+
+ public function update():Void
+ {
+ drawGraph();
+ }
+
+ public function average():Float
+ {
+ var sum:Float = 0;
+ for (value in history)
+ sum += value;
+ return sum / history.length;
+ }
+
+ public function destroy():Void
+ {
+ _axis = FlxDestroyUtil.removeChild(this, _axis);
+ history = null;
+ }
+}
\ No newline at end of file
diff --git a/source_duck/KadeEngineData.hx b/source_duck/KadeEngineData.hx
new file mode 100644
index 00000000..7978a418
--- /dev/null
+++ b/source_duck/KadeEngineData.hx
@@ -0,0 +1,72 @@
+import openfl.Lib;
+import flixel.FlxG;
+
+class KadeEngineData
+{
+ public static function initSave()
+ {
+ if (FlxG.save.data.newInput == null)
+ FlxG.save.data.newInput = true;
+
+ if (FlxG.save.data.downscroll == null)
+ FlxG.save.data.downscroll = false;
+
+ if (FlxG.save.data.dfjk == null)
+ FlxG.save.data.dfjk = false;
+
+ if (FlxG.save.data.accuracyDisplay == null)
+ FlxG.save.data.accuracyDisplay = true;
+
+ if (FlxG.save.data.offset == null)
+ FlxG.save.data.offset = 0;
+
+ if (FlxG.save.data.songPosition == null)
+ FlxG.save.data.songPosition = false;
+
+ if (FlxG.save.data.fps == null)
+ FlxG.save.data.fps = false;
+
+ if (FlxG.save.data.changedHit == null)
+ {
+ FlxG.save.data.changedHitX = -1;
+ FlxG.save.data.changedHitY = -1;
+ FlxG.save.data.changedHit = false;
+ }
+
+ if (FlxG.save.data.fpsRain == null)
+ FlxG.save.data.fpsRain = false;
+
+ if (FlxG.save.data.fpsCap == null)
+ FlxG.save.data.fpsCap = 120;
+
+ if (FlxG.save.data.fpsCap > 285 || FlxG.save.data.fpsCap < 60)
+ FlxG.save.data.fpsCap = 120; // baby proof so you can't hard lock ur copy of kade engine
+
+ if (FlxG.save.data.scrollSpeed == null)
+ FlxG.save.data.scrollSpeed = 1;
+
+ if (FlxG.save.data.npsDisplay == null)
+ FlxG.save.data.npsDisplay = false;
+
+ if (FlxG.save.data.frames == null)
+ FlxG.save.data.frames = 10;
+
+ if (FlxG.save.data.accuracyMod == null)
+ FlxG.save.data.accuracyMod = 1;
+
+ if (FlxG.save.data.watermark == null)
+ FlxG.save.data.watermark = true;
+
+ if (FlxG.save.data.ghost == null)
+ FlxG.save.data.ghost = true;
+
+ if (FlxG.save.data.distractions == null)
+ FlxG.save.data.distractions = true;
+
+ Conductor.recalculateTimings();
+
+ Main.watermarks = FlxG.save.data.watermark;
+
+ (cast (Lib.current.getChildAt(0), Main)).setFPSCap(FlxG.save.data.fpsCap);
+ }
+}
diff --git a/source_duck/KeyBindMenu.hx b/source_duck/KeyBindMenu.hx
new file mode 100644
index 00000000..d5a3d41f
--- /dev/null
+++ b/source_duck/KeyBindMenu.hx
@@ -0,0 +1,402 @@
+package;
+
+/// Code created by Rozebud for FPS Plus (thanks rozebud)
+// modified by KadeDev for use in Kade Engine/Tricky
+
+import flixel.input.gamepad.FlxGamepad;
+import flixel.util.FlxAxes;
+import flixel.FlxSubState;
+import Options.Option;
+import flixel.input.FlxInput;
+import flixel.input.keyboard.FlxKey;
+import flixel.FlxG;
+import flixel.FlxObject;
+import flixel.FlxSprite;
+import flixel.effects.FlxFlicker;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.text.FlxText;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import io.newgrounds.NG;
+import lime.app.Application;
+import lime.utils.Assets;
+import flixel.math.FlxMath;
+import flixel.text.FlxText;
+import flixel.input.FlxKeyManager;
+
+
+using StringTools;
+
+class KeyBindMenu extends FlxSubState
+{
+
+ var keyTextDisplay:FlxText;
+ var keyWarning:FlxText;
+ var warningTween:FlxTween;
+ var keyText:Array = ["LEFT", "DOWN", "UP", "RIGHT"];
+ var defaultKeys:Array = ["A", "S", "W", "D", "R"];
+ var defaultGpKeys:Array = ["DPAD_LEFT", "DPAD_DOWN", "DPAD_UP", "DPAD_RIGHT"];
+ var curSelected:Int = 0;
+
+ var keys:Array = [FlxG.save.data.leftBind,
+ FlxG.save.data.downBind,
+ FlxG.save.data.upBind,
+ FlxG.save.data.rightBind];
+ var gpKeys:Array = [FlxG.save.data.gpleftBind,
+ FlxG.save.data.gpdownBind,
+ FlxG.save.data.gpupBind,
+ FlxG.save.data.gprightBind];
+ var tempKey:String = "";
+ var blacklist:Array = ["ESCAPE", "ENTER", "BACKSPACE", "SPACE", "TAB"];
+
+ var blackBox:FlxSprite;
+ var infoText:FlxText;
+
+ var state:String = "select";
+
+ override function create()
+ {
+
+ for (i in 0...keys.length)
+ {
+ var k = keys[i];
+ if (k == null)
+ keys[i] = defaultKeys[i];
+ }
+
+ for (i in 0...gpKeys.length)
+ {
+ var k = gpKeys[i];
+ if (k == null)
+ gpKeys[i] = defaultGpKeys[i];
+ }
+
+ //FlxG.sound.playMusic('assets/music/configurator' + TitleState.soundExt);
+
+ persistentUpdate = true;
+
+ keyTextDisplay = new FlxText(-10, 0, 1280, "", 72);
+ keyTextDisplay.scrollFactor.set(0, 0);
+ keyTextDisplay.setFormat("VCR OSD Mono", 42, FlxColor.WHITE, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
+ keyTextDisplay.borderSize = 2;
+ keyTextDisplay.borderQuality = 3;
+
+ blackBox = new FlxSprite(0,0).makeGraphic(FlxG.width,FlxG.height,FlxColor.BLACK);
+ add(blackBox);
+
+ infoText = new FlxText(-10, 580, 1280, 'Current Mode: ${KeyBinds.gamepad ? 'GAMEPAD' : 'KEYBOARD'}. Press TAB to switch\n(${KeyBinds.gamepad ? 'RIGHT Trigger' : 'Escape'} to save, ${KeyBinds.gamepad ? 'LEFT Trigger' : 'Backspace'} to leave without saving. ${KeyBinds.gamepad ? 'START To change a keybind' : ''})', 72);
+ infoText.scrollFactor.set(0, 0);
+ infoText.setFormat("VCR OSD Mono", 24, FlxColor.WHITE, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
+ infoText.borderSize = 2;
+ infoText.borderQuality = 3;
+ infoText.alpha = 0;
+ infoText.screenCenter(FlxAxes.X);
+ add(infoText);
+ add(keyTextDisplay);
+
+ blackBox.alpha = 0;
+ keyTextDisplay.alpha = 0;
+
+ FlxTween.tween(keyTextDisplay, {alpha: 1}, 1, {ease: FlxEase.expoInOut});
+ FlxTween.tween(infoText, {alpha: 1}, 1.4, {ease: FlxEase.expoInOut});
+ FlxTween.tween(blackBox, {alpha: 0.7}, 1, {ease: FlxEase.expoInOut});
+
+ OptionsMenu.instance.acceptInput = false;
+
+ textUpdate();
+
+ super.create();
+ }
+
+ var frames = 0;
+
+ override function update(elapsed:Float)
+ {
+ var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
+
+ if (frames <= 10)
+ frames++;
+
+ switch(state){
+
+ case "select":
+ if (FlxG.keys.justPressed.UP)
+ {
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ changeItem(-1);
+ }
+
+ if (FlxG.keys.justPressed.DOWN)
+ {
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ changeItem(1);
+ }
+
+ if (FlxG.keys.justPressed.TAB)
+ {
+ KeyBinds.gamepad = !KeyBinds.gamepad;
+ infoText.text = 'Current Mode: ${KeyBinds.gamepad ? 'GAMEPAD' : 'KEYBOARD'}. Press TAB to switch\n(${KeyBinds.gamepad ? 'RIGHT Trigger' : 'Escape'} to save, ${KeyBinds.gamepad ? 'LEFT Trigger' : 'Backspace'} to leave without saving. ${KeyBinds.gamepad ? 'START To change a keybind' : ''})';
+ textUpdate();
+ }
+
+ if (FlxG.keys.justPressed.ENTER){
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ state = "input";
+ }
+ else if(FlxG.keys.justPressed.ESCAPE){
+ quit();
+ }
+ else if (FlxG.keys.justPressed.BACKSPACE){
+ reset();
+ }
+ if (gamepad != null) // GP Logic
+ {
+ if (gamepad.justPressed.DPAD_UP)
+ {
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ changeItem(-1);
+ textUpdate();
+ }
+ if (gamepad.justPressed.DPAD_DOWN)
+ {
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ changeItem(1);
+ textUpdate();
+ }
+
+ if (gamepad.justPressed.START && frames > 10){
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ state = "input";
+ }
+ else if(gamepad.justPressed.LEFT_TRIGGER){
+ quit();
+ }
+ else if (gamepad.justPressed.RIGHT_TRIGGER){
+ reset();
+ }
+ }
+
+ case "input":
+ tempKey = keys[curSelected];
+ keys[curSelected] = "?";
+ if (KeyBinds.gamepad)
+ gpKeys[curSelected] = "?";
+ textUpdate();
+ state = "waiting";
+
+ case "waiting":
+ if (gamepad != null && KeyBinds.gamepad) // GP Logic
+ {
+ if(FlxG.keys.justPressed.ESCAPE){ // just in case you get stuck
+ gpKeys[curSelected] = tempKey;
+ state = "select";
+ FlxG.sound.play(Paths.sound('confirmMenu'));
+ }
+
+ if (gamepad.justPressed.START)
+ {
+ addKeyGamepad(defaultKeys[curSelected]);
+ save();
+ state = "select";
+ }
+
+ if (gamepad.justPressed.ANY)
+ {
+ trace(gamepad.firstJustPressedID());
+ addKeyGamepad(gamepad.firstJustPressedID());
+ save();
+ state = "select";
+ textUpdate();
+ }
+
+ }
+ else
+ {
+ if(FlxG.keys.justPressed.ESCAPE){
+ keys[curSelected] = tempKey;
+ state = "select";
+ FlxG.sound.play(Paths.sound('confirmMenu'));
+ }
+ else if(FlxG.keys.justPressed.ENTER){
+ addKey(defaultKeys[curSelected]);
+ save();
+ state = "select";
+ }
+ else if(FlxG.keys.justPressed.ANY){
+ addKey(FlxG.keys.getIsDown()[0].ID.toString());
+ save();
+ state = "select";
+ }
+ }
+
+
+ case "exiting":
+
+
+ default:
+ state = "select";
+
+ }
+
+ if(FlxG.keys.justPressed.ANY)
+ textUpdate();
+
+ super.update(elapsed);
+
+ }
+
+ function textUpdate(){
+
+ keyTextDisplay.text = "\n\n";
+
+ if (KeyBinds.gamepad)
+ {
+ for(i in 0...4){
+
+ var textStart = (i == curSelected) ? "> " : " ";
+ trace(gpKeys[i]);
+ keyTextDisplay.text += textStart + keyText[i] + ": " + gpKeys[i] + "\n";
+
+ }
+ }
+ else
+ {
+ for(i in 0...4){
+
+ var textStart = (i == curSelected) ? "> " : " ";
+ keyTextDisplay.text += textStart + keyText[i] + ": " + ((keys[i] != keyText[i]) ? (keys[i] + " / ") : "" ) + keyText[i] + " ARROW\n";
+
+ }
+ }
+
+ keyTextDisplay.screenCenter();
+
+ }
+
+ function save(){
+
+ FlxG.save.data.upBind = keys[2];
+ FlxG.save.data.downBind = keys[1];
+ FlxG.save.data.leftBind = keys[0];
+ FlxG.save.data.rightBind = keys[3];
+
+ FlxG.save.data.gpupBind = gpKeys[2];
+ FlxG.save.data.gpdownBind = gpKeys[1];
+ FlxG.save.data.gpleftBind = gpKeys[0];
+ FlxG.save.data.gprightBind = gpKeys[3];
+
+ FlxG.save.flush();
+
+ PlayerSettings.player1.controls.loadKeyBinds();
+
+ }
+
+ function reset(){
+
+ for(i in 0...5){
+ keys[i] = defaultKeys[i];
+ }
+ quit();
+
+ }
+
+ function quit(){
+
+ state = "exiting";
+
+ save();
+
+ OptionsMenu.instance.acceptInput = true;
+
+ FlxTween.tween(keyTextDisplay, {alpha: 0}, 1, {ease: FlxEase.expoInOut});
+ FlxTween.tween(blackBox, {alpha: 0}, 1.1, {ease: FlxEase.expoInOut, onComplete: function(flx:FlxTween){close();}});
+ FlxTween.tween(infoText, {alpha: 0}, 1, {ease: FlxEase.expoInOut});
+ }
+
+
+ function addKeyGamepad(r:String){
+
+ var shouldReturn:Bool = true;
+
+ var notAllowed:Array = ["START", "RIGHT_TRIGGER", "LEFT_TRIGGER"];
+
+ for(x in 0...gpKeys.length)
+ {
+ var oK = gpKeys[x];
+ if(oK == r)
+ gpKeys[x] = null;
+ if (notAllowed.contains(oK))
+ {
+ gpKeys[x] = null;
+ return;
+ }
+ }
+
+ if(shouldReturn){
+ gpKeys[curSelected] = r;
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ }
+ else{
+ gpKeys[curSelected] = tempKey;
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ keyWarning.alpha = 1;
+ warningTween.cancel();
+ warningTween = FlxTween.tween(keyWarning, {alpha: 0}, 0.5, {ease: FlxEase.circOut, startDelay: 2});
+ }
+
+ }
+
+ function addKey(r:String){
+
+ var shouldReturn:Bool = true;
+
+ var notAllowed:Array = [];
+
+ for(x in blacklist){notAllowed.push(x);}
+
+ trace(notAllowed);
+
+ for(x in 0...keys.length)
+ {
+ var oK = keys[x];
+ if(oK == r)
+ keys[x] = null;
+ if (notAllowed.contains(oK))
+ {
+ keys[x] = null;
+ return;
+ }
+ }
+
+ if (r.contains("NUMPAD"))
+ {
+ keys[curSelected] = null;
+ return;
+ }
+
+ if(shouldReturn){
+ keys[curSelected] = r;
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ }
+ else{
+ keys[curSelected] = tempKey;
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ keyWarning.alpha = 1;
+ warningTween.cancel();
+ warningTween = FlxTween.tween(keyWarning, {alpha: 0}, 0.5, {ease: FlxEase.circOut, startDelay: 2});
+ }
+
+ }
+
+ function changeItem(_amount:Int = 0)
+ {
+ curSelected += _amount;
+
+ if (curSelected > 3)
+ curSelected = 0;
+ if (curSelected < 0)
+ curSelected = 3;
+ }
+}
diff --git a/source_duck/KeyBinds.hx b/source_duck/KeyBinds.hx
new file mode 100644
index 00000000..b9c704d2
--- /dev/null
+++ b/source_duck/KeyBinds.hx
@@ -0,0 +1,83 @@
+import flixel.FlxG;
+import flixel.input.FlxInput;
+import flixel.input.actions.FlxAction;
+import flixel.input.actions.FlxActionInput;
+import flixel.input.actions.FlxActionInputDigital;
+import flixel.input.actions.FlxActionManager;
+import flixel.input.actions.FlxActionSet;
+import flixel.input.gamepad.FlxGamepadButton;
+import flixel.input.gamepad.FlxGamepadInputID;
+import flixel.input.keyboard.FlxKey;
+
+class KeyBinds
+{
+
+ public static var gamepad:Bool = false;
+
+ public static function resetBinds():Void{
+
+ FlxG.save.data.upBind = "W";
+ FlxG.save.data.downBind = "S";
+ FlxG.save.data.leftBind = "A";
+ FlxG.save.data.rightBind = "D";
+ FlxG.save.data.killBind = "R";
+ FlxG.save.data.gpupBind = "DPAD_UP";
+ FlxG.save.data.gpdownBind = "DPAD_DOWN";
+ FlxG.save.data.gpleftBind = "DPAD_LEFT";
+ FlxG.save.data.gprightBind = "DPAD_RIGHT";
+ PlayerSettings.player1.controls.loadKeyBinds();
+
+ }
+
+ public static function keyCheck():Void
+ {
+ if(FlxG.save.data.upBind == null){
+ FlxG.save.data.upBind = "W";
+ trace("No UP");
+ }
+ if (StringTools.contains(FlxG.save.data.upBind,"NUMPAD"))
+ FlxG.save.data.upBind = "W";
+ if(FlxG.save.data.downBind == null){
+ FlxG.save.data.downBind = "S";
+ trace("No DOWN");
+ }
+ if (StringTools.contains(FlxG.save.data.downBind,"NUMPAD"))
+ FlxG.save.data.downBind = "S";
+ if(FlxG.save.data.leftBind == null){
+ FlxG.save.data.leftBind = "A";
+ trace("No LEFT");
+ }
+ if (StringTools.contains(FlxG.save.data.leftBind,"NUMPAD"))
+ FlxG.save.data.leftBind = "A";
+ if(FlxG.save.data.rightBind == null){
+ FlxG.save.data.rightBind = "D";
+ trace("No RIGHT");
+ }
+ if (StringTools.contains(FlxG.save.data.rightBind,"NUMPAD"))
+ FlxG.save.data.rightBind = "D";
+
+ if(FlxG.save.data.gpupBind == null){
+ FlxG.save.data.gpupBind = "DPAD_UP";
+ trace("No GUP");
+ }
+ if(FlxG.save.data.gpdownBind == null){
+ FlxG.save.data.gpdownBind = "DPAD_DOWN";
+ trace("No GDOWN");
+ }
+ if(FlxG.save.data.gpleftBind == null){
+ FlxG.save.data.gpleftBind = "DPAD_LEFT";
+ trace("No GLEFT");
+ }
+ if(FlxG.save.data.gprightBind == null){
+ FlxG.save.data.gprightBind = "DPAD_RIGHT";
+ trace("No GRIGHT");
+ }
+ if(FlxG.save.data.killBind == null){
+ FlxG.save.data.killBind = "R";
+ trace("No KILL");
+ }
+
+ trace('${FlxG.save.data.leftBind}-${FlxG.save.data.downBind}-${FlxG.save.data.upBind}-${FlxG.save.data.rightBind}');
+ }
+
+}
diff --git a/source_duck/LoadReplayState.hx b/source_duck/LoadReplayState.hx
new file mode 100644
index 00000000..1be8679e
--- /dev/null
+++ b/source_duck/LoadReplayState.hx
@@ -0,0 +1,201 @@
+package;
+
+import Controls.KeyboardScheme;
+import Controls.Control;
+import flash.text.TextField;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.display.FlxGridOverlay;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.input.keyboard.FlxKey;
+import flixel.math.FlxMath;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
+import lime.utils.Assets;
+#if sys
+import sys.io.File;
+#end
+
+class LoadReplayState extends MusicBeatState
+{
+ var selector:FlxText;
+ var curSelected:Int = 0;
+
+ var songs:Array = [];
+
+ var controlsStrings:Array = [];
+ var actualNames:Array = [];
+
+ private var grpControls:FlxTypedGroup;
+ var versionShit:FlxText;
+ var poggerDetails:FlxText;
+ override function create()
+ {
+ var menuBG:FlxSprite = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
+ #if sys
+ controlsStrings = sys.FileSystem.readDirectory(Sys.getCwd() + "/assets/replays/");
+ #end
+ trace(controlsStrings);
+
+ controlsStrings.sort(Reflect.compare);
+
+ addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']);
+ addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky']);
+ addWeek(['Pico', 'Philly', 'Blammed'], 3, ['pico']);
+
+ addWeek(['Satin-Panties', 'High', 'Milf'], 4, ['mom']);
+ addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 5, ['parents-christmas', 'parents-christmas', 'monster-christmas']);
+
+ addWeek(['Senpai', 'Roses', 'Thorns'], 6, ['senpai', 'senpai', 'spirit']);
+
+
+ for(i in 0...controlsStrings.length)
+ {
+ var string:String = controlsStrings[i];
+ actualNames[i] = string;
+ var rep:Replay = Replay.LoadReplay(string);
+ controlsStrings[i] = string.split("time")[0] + " " + (rep.replay.songDiff == 2 ? "HARD" : rep.replay.songDiff == 1 ? "EASY" : "NORMAL");
+ }
+
+ if (controlsStrings.length == 0)
+ controlsStrings.push("No Replays...");
+
+ menuBG.color = 0xFFea71fd;
+ menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
+ menuBG.updateHitbox();
+ menuBG.screenCenter();
+ menuBG.antialiasing = true;
+ add(menuBG);
+
+ grpControls = new FlxTypedGroup();
+ add(grpControls);
+
+ for (i in 0...controlsStrings.length)
+ {
+ var controlLabel:Alphabet = new Alphabet(0, (70 * i) + 30, controlsStrings[i], true, false);
+ controlLabel.isMenuItem = true;
+ controlLabel.targetY = i;
+ grpControls.add(controlLabel);
+ // DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !!
+ }
+
+
+ versionShit = new FlxText(5, FlxG.height - 34, 0, "Replay Loader (ESCAPE TO GO BACK)\nNOTICE!!!! Replays are in a beta stage, and they are probably not 100% correct. expect misses and other stuff that isn't there!", 12);
+ versionShit.scrollFactor.set();
+ versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
+ add(versionShit);
+
+
+ poggerDetails = new FlxText(5, 34, 0, "Replay Details - \nnone", 12);
+ poggerDetails.scrollFactor.set();
+ poggerDetails.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
+ add(poggerDetails);
+
+ changeSelection(0);
+
+ super.create();
+ }
+
+ public function getWeekNumbFromSong(songName:String):Int
+ {
+ var week:Int = 0;
+ for (i in 0...songs.length)
+ {
+ var pog:FreeplayState.SongMetadata = songs[i];
+ if (pog.songName.toLowerCase() == songName)
+ week = pog.week;
+ }
+ return week;
+ }
+
+ public function addSong(songName:String, weekNum:Int, songCharacter:String)
+ {
+ songs.push(new FreeplayState.SongMetadata(songName, weekNum, songCharacter));
+ }
+
+ public function addWeek(songs:Array, weekNum:Int, ?songCharacters:Array)
+ {
+ if (songCharacters == null)
+ songCharacters = ['bf'];
+
+ var num:Int = 0;
+ for (song in songs)
+ {
+ addSong(song, weekNum, songCharacters[num]);
+
+ if (songCharacters.length != 1)
+ num++;
+ }
+ }
+
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ if (controls.BACK)
+ FlxG.switchState(new OptionsMenu());
+ if (controls.UP_P)
+ changeSelection(-1);
+ if (controls.DOWN_P)
+ changeSelection(1);
+
+
+ if (controls.ACCEPT && grpControls.members[curSelected].text != "No Replays...")
+ {
+ trace('loading ' + actualNames[curSelected]);
+ PlayState.rep = Replay.LoadReplay(actualNames[curSelected]);
+
+ PlayState.loadRep = true;
+
+ var poop:String = Highscore.formatSong(PlayState.rep.replay.songName.toLowerCase(), PlayState.rep.replay.songDiff);
+
+ PlayState.SONG = Song.loadFromJson(poop, PlayState.rep.replay.songName.toLowerCase());
+ PlayState.isStoryMode = false;
+ PlayState.storyDifficulty = PlayState.rep.replay.songDiff;
+ PlayState.storyWeek = getWeekNumbFromSong(PlayState.rep.replay.songName);
+ LoadingState.loadAndSwitchState(new PlayState());
+ }
+ }
+
+ var isSettingControl:Bool = false;
+
+ function changeSelection(change:Int = 0)
+ {
+ #if !switch
+ // NGio.logEvent('Fresh');
+ #end
+
+ FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
+
+ curSelected += change;
+
+ if (curSelected < 0)
+ curSelected = grpControls.length - 1;
+ if (curSelected >= grpControls.length)
+ curSelected = 0;
+
+ var rep:Replay = Replay.LoadReplay(actualNames[curSelected]);
+
+ poggerDetails.text = "Replay Details - \nDate Created: " + rep.replay.timestamp + "\nSong: " + rep.replay.songName + "\nReplay Version: " + (rep.replay.replayGameVer != Replay.version ? "OUTDATED" : "Latest");
+
+ // selector.y = (70 * curSelected) + 30;
+
+ var bullShit:Int = 0;
+
+ for (item in grpControls.members)
+ {
+ item.targetY = bullShit - curSelected;
+ bullShit++;
+
+ item.alpha = 0.6;
+ // item.setGraphicSize(Std.int(item.width * 0.8));
+
+ if (item.targetY == 0)
+ {
+ item.alpha = 1;
+ // item.setGraphicSize(Std.int(item.width));
+ }
+ }
+ }
+}
diff --git a/source_duck/LoadingState.hx b/source_duck/LoadingState.hx
new file mode 100644
index 00000000..23bce2e6
--- /dev/null
+++ b/source_duck/LoadingState.hx
@@ -0,0 +1,310 @@
+package;
+
+import lime.app.Promise;
+import lime.app.Future;
+import flixel.FlxG;
+import flixel.FlxState;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.util.FlxTimer;
+
+import openfl.utils.Assets;
+import lime.utils.Assets as LimeAssets;
+import lime.utils.AssetLibrary;
+import lime.utils.AssetManifest;
+
+import haxe.io.Path;
+
+class LoadingState extends MusicBeatState
+{
+ inline static var MIN_TIME = 1.0;
+
+ var target:FlxState;
+ var stopMusic = false;
+ var callbacks:MultiCallback;
+
+ var logo:FlxSprite;
+ var gfDance:FlxSprite;
+ var danceLeft = false;
+
+ function new(target:FlxState, stopMusic:Bool)
+ {
+ super();
+ this.target = target;
+ this.stopMusic = stopMusic;
+ }
+
+ override function create()
+ {
+ logo = new FlxSprite(-150, -100);
+ logo.frames = Paths.getSparrowAtlas('logoBumpin');
+ logo.antialiasing = true;
+ logo.animation.addByPrefix('bump', 'logo bumpin', 24);
+ logo.animation.play('bump');
+ logo.updateHitbox();
+ // logoBl.screenCenter();
+ // logoBl.color = FlxColor.BLACK;
+
+ gfDance = new FlxSprite(FlxG.width * 0.4, FlxG.height * 0.07);
+ gfDance.frames = Paths.getSparrowAtlas('gfDanceTitle');
+ gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
+ gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
+ gfDance.antialiasing = true;
+ add(gfDance);
+ add(logo);
+
+ initSongsManifest().onComplete
+ (
+ function (lib)
+ {
+ callbacks = new MultiCallback(onLoad);
+ var introComplete = callbacks.add("introComplete");
+ checkLoadSong(getSongPath());
+ if (PlayState.SONG.needsVoices)
+ checkLoadSong(getVocalPath());
+ checkLibrary("shared");
+ if (PlayState.storyWeek > 0)
+ checkLibrary("week" + PlayState.storyWeek);
+ else
+ checkLibrary("tutorial");
+
+ var fadeTime = 0.5;
+ FlxG.camera.fade(FlxG.camera.bgColor, fadeTime, true);
+ new FlxTimer().start(fadeTime + MIN_TIME, function(_) introComplete());
+ }
+ );
+ }
+
+ function checkLoadSong(path:String)
+ {
+ if (!Assets.cache.hasSound(path))
+ {
+ var library = Assets.getLibrary("songs");
+ final symbolPath = path.split(":").pop();
+ // @:privateAccess
+ // library.types.set(symbolPath, SOUND);
+ // @:privateAccess
+ // library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]);
+ var callback = callbacks.add("song:" + path);
+ Assets.loadSound(path).onComplete(function (_) { callback(); });
+ }
+ }
+
+ function checkLibrary(library:String)
+ {
+ trace(Assets.hasLibrary(library));
+ if (Assets.getLibrary(library) == null)
+ {
+ @:privateAccess
+ if (!LimeAssets.libraryPaths.exists(library))
+ throw "Missing library: " + library;
+
+ var callback = callbacks.add("library:" + library);
+ Assets.loadLibrary(library).onComplete(function (_) { callback(); });
+ }
+ }
+
+ override function beatHit()
+ {
+ super.beatHit();
+
+ logo.animation.play('bump');
+ danceLeft = !danceLeft;
+
+ if (danceLeft)
+ gfDance.animation.play('danceRight');
+ else
+ gfDance.animation.play('danceLeft');
+ }
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+ #if debug
+ if (FlxG.keys.justPressed.SPACE)
+ trace('fired: ' + callbacks.getFired() + " unfired:" + callbacks.getUnfired());
+ #end
+ }
+
+ function onLoad()
+ {
+ if (stopMusic && FlxG.sound.music != null)
+ FlxG.sound.music.stop();
+
+ FlxG.switchState(target);
+ }
+
+ static function getSongPath()
+ {
+ return Paths.inst(PlayState.SONG.song);
+ }
+
+ static function getVocalPath()
+ {
+ return Paths.voices(PlayState.SONG.song);
+ }
+
+ inline static public function loadAndSwitchState(target:FlxState, stopMusic = false)
+ {
+ FlxG.switchState(getNextState(target, stopMusic));
+ }
+
+ static function getNextState(target:FlxState, stopMusic = false):FlxState
+ {
+ Paths.setCurrentLevel("week" + PlayState.storyWeek);
+ #if NO_PRELOAD_ALL
+ var loaded = isSoundLoaded(getSongPath())
+ && (!PlayState.SONG.needsVoices || isSoundLoaded(getVocalPath()))
+ && isLibraryLoaded("shared");
+
+ if (!loaded)
+ return new LoadingState(target, stopMusic);
+ #end
+ if (stopMusic && FlxG.sound.music != null)
+ FlxG.sound.music.stop();
+
+ return target;
+ }
+
+ #if NO_PRELOAD_ALL
+ static function isSoundLoaded(path:String):Bool
+ {
+ return Assets.cache.hasSound(path);
+ }
+
+ static function isLibraryLoaded(library:String):Bool
+ {
+ return Assets.getLibrary(library) != null;
+ }
+ #end
+
+ override function destroy()
+ {
+ super.destroy();
+
+ callbacks = null;
+ }
+
+ static function initSongsManifest()
+ {
+ var id = "songs";
+ var promise = new Promise();
+
+ var library = LimeAssets.getLibrary(id);
+
+ if (library != null)
+ {
+ return Future.withValue(library);
+ }
+
+ var path = id;
+ var rootPath = null;
+
+ @:privateAccess
+ var libraryPaths = LimeAssets.libraryPaths;
+ if (libraryPaths.exists(id))
+ {
+ path = libraryPaths[id];
+ rootPath = Path.directory(path);
+ }
+ else
+ {
+ if (StringTools.endsWith(path, ".bundle"))
+ {
+ rootPath = path;
+ path += "/library.json";
+ }
+ else
+ {
+ rootPath = Path.directory(path);
+ }
+ @:privateAccess
+ path = LimeAssets.__cacheBreak(path);
+ }
+
+ AssetManifest.loadFromFile(path, rootPath).onComplete(function(manifest)
+ {
+ if (manifest == null)
+ {
+ promise.error("Cannot parse asset manifest for library \"" + id + "\"");
+ return;
+ }
+
+ var library = AssetLibrary.fromManifest(manifest);
+
+ if (library == null)
+ {
+ promise.error("Cannot open library \"" + id + "\"");
+ }
+ else
+ {
+ @:privateAccess
+ LimeAssets.libraries.set(id, library);
+ library.onChange.add(LimeAssets.onChange.dispatch);
+ promise.completeWith(Future.withValue(library));
+ }
+ }).onError(function(_)
+ {
+ promise.error("There is no asset library with an ID of \"" + id + "\"");
+ });
+
+ return promise.future;
+ }
+}
+
+class MultiCallback
+{
+ public var callback:Void->Void;
+ public var logId:String = null;
+ public var length(default, null) = 0;
+ public var numRemaining(default, null) = 0;
+
+ var unfired = new MapVoid>();
+ var fired = new Array();
+
+ public function new (callback:Void->Void, logId:String = null)
+ {
+ this.callback = callback;
+ this.logId = logId;
+ }
+
+ public function add(id = "untitled")
+ {
+ id = '$length:$id';
+ length++;
+ numRemaining++;
+ var func:Void->Void = null;
+ func = function ()
+ {
+ if (unfired.exists(id))
+ {
+ unfired.remove(id);
+ fired.push(id);
+ numRemaining--;
+
+ if (logId != null)
+ log('fired $id, $numRemaining remaining');
+
+ if (numRemaining == 0)
+ {
+ if (logId != null)
+ log('all callbacks fired');
+ callback();
+ }
+ }
+ else
+ log('already fired $id');
+ }
+ unfired[id] = func;
+ return func;
+ }
+
+ inline function log(msg):Void
+ {
+ if (logId != null)
+ trace('$logId: $msg');
+ }
+
+ public function getFired() return fired.copy();
+ public function getUnfired() return [for (id in unfired.keys()) id];
+}
\ No newline at end of file
diff --git a/source_duck/LuaShader.hx b/source_duck/LuaShader.hx
new file mode 100644
index 00000000..4e403c69
--- /dev/null
+++ b/source_duck/LuaShader.hx
@@ -0,0 +1,72 @@
+/*import flixel.system.FlxAssets.FlxShader;
+
+class LuaShader extends FlxShader
+{
+ // SHADER SHIT FOR LUA CODE
+
+ public function new(frag,vert)
+ {
+ glFragmentSource = '
+ #pragma header
+ varying float openfl_Alphav;
+ varying vec4 openfl_ColorMultiplierv;
+ varying vec4 openfl_ColorOffsetv;
+ varying vec2 openfl_TextureCoordv;
+
+ uniform bool openfl_HasColorTransform;
+ uniform vec2 openfl_TextureSize;
+ uniform sampler2D bitmap;
+
+ uniform bool hasTransform;
+ uniform bool hasColorTransform;
+
+ uniform vec3 iResolution; // viewport resolution (in pixels)
+ uniform float iTime; // shader playback time (in seconds)
+ uniform float iTimeDelta; // render time (in seconds)
+ uniform int iFrame; // shader playback frame
+ uniform float iChannelTime[4]; // channel playback time (in seconds)
+ uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
+ uniform vec4 iMouse; // mouse pixel coords. xy: current, zw: click
+ uniform samplerXX iChannel0..3; // input channel. XX = 2D/Cube
+ uniform vec4 iDate; // (year, month, day, time in seconds)
+ uniform float iSampleRate; // sound sample rate (i.e., 44100)
+
+ vec4 flixel_texture2D(sampler2D bitmap, vec2 coord)
+ {
+ vec4 color = texture2D(bitmap, coord);
+ if (!hasTransform)
+ {
+ return color;
+ }
+
+ if (color.a == 0.0)
+ {
+ return vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ if (!hasColorTransform)
+ {
+ return color * openfl_Alphav;
+ }
+
+ color = vec4(color.rgb / color.a, color.a);
+
+ mat4 colorMultiplier = mat4(0);
+ colorMultiplier[0][0] = openfl_ColorMultiplierv.x;
+ colorMultiplier[1][1] = openfl_ColorMultiplierv.y;
+ colorMultiplier[2][2] = openfl_ColorMultiplierv.z;
+ colorMultiplier[3][3] = openfl_ColorMultiplierv.w;
+
+ color = clamp(openfl_ColorOffsetv + (color * colorMultiplier), 0.0, 1.0);
+
+ if (color.a > 0.0)
+ {
+ return vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ ' + frag;
+
+ iResolution
+ super();
+ }
+}*/
\ No newline at end of file
diff --git a/source_duck/Main.hx b/source_duck/Main.hx
new file mode 100644
index 00000000..48f0fc34
--- /dev/null
+++ b/source_duck/Main.hx
@@ -0,0 +1,119 @@
+package;
+
+import openfl.display.BlendMode;
+import openfl.text.TextFormat;
+import openfl.display.Application;
+import flixel.util.FlxColor;
+import flixel.FlxG;
+import flixel.FlxGame;
+import flixel.FlxState;
+import openfl.Assets;
+import openfl.Lib;
+import openfl.display.FPS;
+import openfl.display.Sprite;
+import openfl.events.Event;
+
+class Main extends Sprite
+{
+ var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
+ var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
+ var initialState:Class = TitleState; // The FlxState the game starts with.
+ var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
+ var framerate:Int = 120; // How many frames per second the game should run at.
+ var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode.
+ var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets
+
+ public static var watermarks = true; // Whether to put Kade Engine liteartly anywhere
+
+ // You can pretty much ignore everything from here on - your code should go in your states.
+
+ public static function main():Void
+ {
+
+ // quick checks
+
+ Lib.current.addChild(new Main());
+ }
+
+ public function new()
+ {
+ super();
+
+ if (stage != null)
+ {
+ init();
+ }
+ else
+ {
+ addEventListener(Event.ADDED_TO_STAGE, init);
+ }
+ }
+
+ private function init(?E:Event):Void
+ {
+ if (hasEventListener(Event.ADDED_TO_STAGE))
+ {
+ removeEventListener(Event.ADDED_TO_STAGE, init);
+ }
+
+ setupGame();
+ }
+
+ private function setupGame():Void
+ {
+ var stageWidth:Int = Lib.current.stage.stageWidth;
+ var stageHeight:Int = Lib.current.stage.stageHeight;
+
+ if (zoom == -1)
+ {
+ var ratioX:Float = stageWidth / gameWidth;
+ var ratioY:Float = stageHeight / gameHeight;
+ zoom = Math.min(ratioX, ratioY);
+ gameWidth = Math.ceil(stageWidth / zoom);
+ gameHeight = Math.ceil(stageHeight / zoom);
+ }
+
+ #if !debug
+ initialState = TitleState;
+ #end
+
+ game = new FlxGame(gameWidth, gameHeight, initialState, zoom, framerate, framerate, skipSplash, startFullscreen);
+
+ addChild(game);
+
+ #if !mobile
+ fpsCounter = new FPS(10, 3, 0xFFFFFF);
+ addChild(fpsCounter);
+ toggleFPS(FlxG.save.data.fps);
+
+ #end
+ }
+
+ var game:FlxGame;
+
+ var fpsCounter:FPS;
+
+ public function toggleFPS(fpsEnabled:Bool):Void {
+ fpsCounter.visible = fpsEnabled;
+ }
+
+ public function changeFPSColor(color:FlxColor)
+ {
+ fpsCounter.textColor = color;
+ }
+
+ public function setFPSCap(cap:Float)
+ {
+ openfl.Lib.current.stage.frameRate = cap;
+ }
+
+ public function getFPSCap():Float
+ {
+ return openfl.Lib.current.stage.frameRate;
+ }
+
+ public function getFPS():Float
+ {
+ return fpsCounter.currentFPS;
+ }
+}
diff --git a/source_duck/MainMenuState.hx b/source_duck/MainMenuState.hx
new file mode 100644
index 00000000..d57308b7
--- /dev/null
+++ b/source_duck/MainMenuState.hx
@@ -0,0 +1,237 @@
+package;
+
+import Controls.KeyboardScheme;
+import flixel.FlxG;
+import flixel.FlxObject;
+import flixel.FlxSprite;
+import flixel.effects.FlxFlicker;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.text.FlxText;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import io.newgrounds.NG;
+import lime.app.Application;
+
+#if windows
+import Discord.DiscordClient;
+#end
+
+using StringTools;
+
+class MainMenuState extends MusicBeatState
+{
+ var curSelected:Int = 0;
+
+ var menuItems:FlxTypedGroup;
+
+ #if !switch
+ var optionShit:Array = ['story mode', 'freeplay', 'donate', 'options'];
+ #else
+ var optionShit:Array = ['story mode', 'freeplay'];
+ #end
+
+ var newGaming:FlxText;
+ var newGaming2:FlxText;
+ var newInput:Bool = true;
+
+ public static var nightly:String = "";
+
+ public static var kadeEngineVer:String = "1.4.2" + nightly;
+ public static var gameVer:String = "0.2.7.1";
+
+ var magenta:FlxSprite;
+ var camFollow:FlxObject;
+
+ override function create()
+ {
+ #if windows
+ // Updating Discord Rich Presence
+ DiscordClient.changePresence("In the Menus", null);
+ #end
+
+ if (!FlxG.sound.music.playing)
+ {
+ FlxG.sound.playMusic(Paths.music('freakyMenu'));
+ }
+
+ persistentUpdate = persistentDraw = true;
+
+ var bg:FlxSprite = new FlxSprite(-80).loadGraphic(Paths.image('menuBG'));
+ bg.scrollFactor.x = 0;
+ bg.scrollFactor.y = 0.15;
+ bg.setGraphicSize(Std.int(bg.width * 1.1));
+ bg.updateHitbox();
+ bg.screenCenter();
+ bg.antialiasing = true;
+ add(bg);
+
+ camFollow = new FlxObject(0, 0, 1, 1);
+ add(camFollow);
+
+ magenta = new FlxSprite(-80).loadGraphic(Paths.image('menuDesat'));
+ magenta.scrollFactor.x = 0;
+ magenta.scrollFactor.y = 0.18;
+ magenta.setGraphicSize(Std.int(magenta.width * 1.1));
+ magenta.updateHitbox();
+ magenta.screenCenter();
+ magenta.visible = false;
+ magenta.antialiasing = true;
+ magenta.color = 0xFFfd719b;
+ add(magenta);
+ // magenta.scrollFactor.set();
+
+ menuItems = new FlxTypedGroup();
+ add(menuItems);
+
+ var tex = Paths.getSparrowAtlas('FNF_main_menu_assets');
+
+ for (i in 0...optionShit.length)
+ {
+ var menuItem:FlxSprite = new FlxSprite(0, 60 + (i * 160));
+ menuItem.frames = tex;
+ menuItem.animation.addByPrefix('idle', optionShit[i] + " basic", 24);
+ menuItem.animation.addByPrefix('selected', optionShit[i] + " white", 24);
+ menuItem.animation.play('idle');
+ menuItem.ID = i;
+ menuItem.screenCenter(X);
+ menuItems.add(menuItem);
+ menuItem.scrollFactor.set();
+ menuItem.antialiasing = true;
+ }
+
+ FlxG.camera.follow(camFollow, null, 0.60 * (60 / FlxG.save.data.fpsCap));
+
+ var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, gameVer + (Main.watermarks ? " FNF - " + kadeEngineVer + " Kade Engine" : ""), 12);
+ versionShit.scrollFactor.set();
+ versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
+ add(versionShit);
+
+ // NG.core.calls.event.logEvent('swag').send();
+
+
+ if (FlxG.save.data.dfjk)
+ controls.setKeyboardScheme(KeyboardScheme.Solo, true);
+ else
+ controls.setKeyboardScheme(KeyboardScheme.Duo(true), true);
+
+ changeItem();
+
+ super.create();
+ }
+
+ var selectedSomethin:Bool = false;
+
+ override function update(elapsed:Float)
+ {
+ if (FlxG.sound.music.volume < 0.8)
+ {
+ FlxG.sound.music.volume += 0.5 * FlxG.elapsed;
+ }
+
+ if (!selectedSomethin)
+ {
+ if (controls.UP_P)
+ {
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ changeItem(-1);
+ }
+
+ if (controls.DOWN_P)
+ {
+ FlxG.sound.play(Paths.sound('scrollMenu'));
+ changeItem(1);
+ }
+
+ if (controls.BACK)
+ {
+ FlxG.switchState(new TitleState());
+ }
+
+ if (controls.ACCEPT)
+ {
+ if (optionShit[curSelected] == 'donate')
+ {
+ #if linux
+ Sys.command('/usr/bin/xdg-open', ["https://ninja-muffin24.itch.io/funkin", "&"]);
+ #else
+ FlxG.openURL('https://ninja-muffin24.itch.io/funkin');
+ #end
+ }
+ else
+ {
+ selectedSomethin = true;
+ FlxG.sound.play(Paths.sound('confirmMenu'));
+
+ FlxFlicker.flicker(magenta, 1.1, 0.15, false);
+
+ menuItems.forEach(function(spr:FlxSprite)
+ {
+ if (curSelected != spr.ID)
+ {
+ FlxTween.tween(spr, {alpha: 0}, 1.3, {
+ ease: FlxEase.quadOut,
+ onComplete: function(twn:FlxTween)
+ {
+ spr.kill();
+ }
+ });
+ }
+ else
+ {
+ FlxFlicker.flicker(spr, 1, 0.06, false, false, function(flick:FlxFlicker)
+ {
+ var daChoice:String = optionShit[curSelected];
+
+ switch (daChoice)
+ {
+ case 'story mode':
+ FlxG.switchState(new StoryMenuState());
+ trace("Story Menu Selected");
+ case 'freeplay':
+ FlxG.switchState(new FreeplayState());
+
+ trace("Freeplay Menu Selected");
+
+ case 'options':
+ FlxG.switchState(new OptionsMenu());
+ }
+ });
+ }
+ });
+ }
+ }
+ }
+
+ super.update(elapsed);
+
+ menuItems.forEach(function(spr:FlxSprite)
+ {
+ spr.screenCenter(X);
+ });
+ }
+
+ function changeItem(huh:Int = 0)
+ {
+ curSelected += huh;
+
+ if (curSelected >= menuItems.length)
+ curSelected = 0;
+ if (curSelected < 0)
+ curSelected = menuItems.length - 1;
+
+ menuItems.forEach(function(spr:FlxSprite)
+ {
+ spr.animation.play('idle');
+
+ if (spr.ID == curSelected)
+ {
+ spr.animation.play('selected');
+ camFollow.setPosition(spr.getGraphicMidpoint().x, spr.getGraphicMidpoint().y);
+ }
+
+ spr.updateHitbox();
+ });
+ }
+}
diff --git a/source_duck/MenuCharacter.hx b/source_duck/MenuCharacter.hx
new file mode 100644
index 00000000..a09c11ad
--- /dev/null
+++ b/source_duck/MenuCharacter.hx
@@ -0,0 +1,81 @@
+package;
+
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+
+class CharacterSetting
+{
+ public var x(default, null):Int;
+ public var y(default, null):Int;
+ public var scale(default, null):Float;
+ public var flipped(default, null):Bool;
+
+ public function new(x:Int = 0, y:Int = 0, scale:Float = 1.0, flipped:Bool = false)
+ {
+ this.x = x;
+ this.y = y;
+ this.scale = scale;
+ this.flipped = flipped;
+ }
+}
+
+class MenuCharacter extends FlxSprite
+{
+ private static var settings:Map = [
+ 'bf' => new CharacterSetting(0, -20, 1.0, true),
+ 'gf' => new CharacterSetting(50, 80, 1.5, true),
+ 'dad' => new CharacterSetting(-15, 130),
+ 'spooky' => new CharacterSetting(20, 30),
+ 'pico' => new CharacterSetting(0, 0, 1.0, true),
+ 'mom' => new CharacterSetting(-30, 140, 0.85),
+ 'parents-christmas' => new CharacterSetting(100, 130, 1.8),
+ 'senpai' => new CharacterSetting(-40, -45, 1.4),
+ 'duck' => new CharacterSetting(-40, -45, 1.4)
+ ];
+
+ private var flipped:Bool = false;
+
+ public function new(x:Int, y:Int, scale:Float, flipped:Bool)
+ {
+ super(x, y);
+ this.flipped = flipped;
+
+ antialiasing = true;
+
+ frames = Paths.getSparrowAtlas('campaign_menu_UI_characters');
+
+ animation.addByPrefix('bf', "BF idle dance white", 24);
+ animation.addByPrefix('bfConfirm', 'BF HEY!!', 24, false);
+ animation.addByPrefix('gf', "GF Dancing Beat WHITE", 24);
+ animation.addByPrefix('dad', "Dad idle dance BLACK LINE", 24);
+ animation.addByPrefix('spooky', "spooky dance idle BLACK LINES", 24);
+ animation.addByPrefix('pico', "Pico Idle Dance", 24);
+ animation.addByPrefix('mom', "Mom Idle BLACK LINES", 24);
+ animation.addByPrefix('parents-christmas', "Parent Christmas Idle", 24);
+ animation.addByPrefix('senpai', "SENPAI idle Black Lines", 24);
+ animation.addByPrefix('duck', 'Duck Black Lines', 24);
+
+ setGraphicSize(Std.int(width * scale));
+ updateHitbox();
+ }
+
+ public function setCharacter(character:String):Void
+ {
+ if (character == '')
+ {
+ visible = false;
+ return;
+ }
+ else
+ {
+ visible = true;
+ }
+
+ animation.play(character);
+
+ var setting:CharacterSetting = settings[character];
+ offset.set(setting.x, setting.y);
+ setGraphicSize(Std.int(width * setting.scale));
+ flipX = setting.flipped != flipped;
+ }
+}
diff --git a/source_duck/MenuItem.hx b/source_duck/MenuItem.hx
new file mode 100644
index 00000000..44353f8b
--- /dev/null
+++ b/source_duck/MenuItem.hx
@@ -0,0 +1,49 @@
+package;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.group.FlxSpriteGroup;
+import flixel.math.FlxMath;
+import flixel.util.FlxColor;
+
+class MenuItem extends FlxSpriteGroup
+{
+ public var targetY:Float = 0;
+ public var week:FlxSprite;
+ public var flashingInt:Int = 0;
+
+ public function new(x:Float, y:Float, weekNum:Int = 0)
+ {
+ super(x, y);
+ week = new FlxSprite().loadGraphic(Paths.image('storymenu/week' + weekNum));
+ add(week);
+ }
+
+ private var isFlashing:Bool = false;
+
+ public function startFlashing():Void
+ {
+ isFlashing = true;
+ }
+
+ // if it runs at 60fps, fake framerate will be 6
+ // if it runs at 144 fps, fake framerate will be like 14, and will update the graphic every 0.016666 * 3 seconds still???
+ // so it runs basically every so many seconds, not dependant on framerate??
+ // I'm still learning how math works thanks whoever is reading this lol
+ var fakeFramerate:Int = Math.round((1 / FlxG.elapsed) / 10);
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+ y = FlxMath.lerp(y, (targetY * 120) + 480, 0.17 * (60 / FlxG.save.data.fpsCap));
+
+ if (isFlashing)
+ flashingInt += 1;
+
+ if (flashingInt % fakeFramerate >= Math.floor(fakeFramerate / 2))
+ week.color = 0xFF33ffff;
+ else
+ week.color = FlxColor.WHITE;
+ }
+}
diff --git a/source_duck/ModchartState.hx b/source_duck/ModchartState.hx
new file mode 100644
index 00000000..d3b8e535
--- /dev/null
+++ b/source_duck/ModchartState.hx
@@ -0,0 +1,667 @@
+// this file is for modchart things, this is to declutter playstate.hx
+
+// Lua
+#if windows
+import flixel.tweens.FlxEase;
+import openfl.filters.ShaderFilter;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import openfl.geom.Matrix;
+import openfl.display.BitmapData;
+import lime.app.Application;
+import flixel.FlxSprite;
+import llua.Convert;
+import llua.Lua;
+import llua.State;
+import llua.LuaL;
+import flixel.FlxBasic;
+import flixel.FlxCamera;
+import flixel.FlxG;
+
+class ModchartState
+{
+ //public static var shaders:Array = null;
+
+ public static var lua:State = null;
+
+ function callLua(func_name : String, args : Array, ?type : String) : Dynamic
+ {
+ var result : Any = null;
+
+ Lua.getglobal(lua, func_name);
+
+ for( arg in args ) {
+ Convert.toLua(lua, arg);
+ }
+
+ result = Lua.pcall(lua, args.length, 1, 0);
+ var p = Lua.tostring(lua,result);
+ var e = getLuaErrorMessage(lua);
+
+ if (e != null)
+ {
+ if (p != null)
+ {
+ Application.current.window.alert("LUA ERROR:\n" + p + "\nhaxe things: " + e,"Kade Engine Modcharts");
+ lua = null;
+ LoadingState.loadAndSwitchState(new MainMenuState());
+ }
+ // trace('err: ' + e);
+ }
+ if( result == null) {
+ return null;
+ } else {
+ return convert(result, type);
+ }
+
+ }
+
+ function getType(l, type):Any
+ {
+ return switch Lua.type(l,type) {
+ case t if (t == Lua.LUA_TNIL): null;
+ case t if (t == Lua.LUA_TNUMBER): Lua.tonumber(l, type);
+ case t if (t == Lua.LUA_TSTRING): (Lua.tostring(l, type):String);
+ case t if (t == Lua.LUA_TBOOLEAN): Lua.toboolean(l, type);
+ case t: throw 'you don goofed up. lua type error ($t)';
+ }
+ }
+
+ function getReturnValues(l) {
+ var lua_v:Int;
+ var v:Any = null;
+ while((lua_v = Lua.gettop(l)) != 0) {
+ var type:String = getType(l,lua_v);
+ v = convert(lua_v, type);
+ Lua.pop(l, 1);
+ }
+ return v;
+ }
+
+
+ private function convert(v : Any, type : String) : Dynamic { // I didn't write this lol
+ if( Std.is(v, String) && type != null ) {
+ var v : String = v;
+ if( type.substr(0, 4) == 'array' ) {
+ if( type.substr(4) == 'float' ) {
+ var array : Array = v.split(',');
+ var array2 : Array = new Array();
+
+ for( vars in array ) {
+ array2.push(Std.parseFloat(vars));
+ }
+
+ return array2;
+ } else if( type.substr(4) == 'int' ) {
+ var array : Array = v.split(',');
+ var array2 : Array = new Array();
+
+ for( vars in array ) {
+ array2.push(Std.parseInt(vars));
+ }
+
+ return array2;
+ } else {
+ var array : Array = v.split(',');
+ return array;
+ }
+ } else if( type == 'float' ) {
+ return Std.parseFloat(v);
+ } else if( type == 'int' ) {
+ return Std.parseInt(v);
+ } else if( type == 'bool' ) {
+ if( v == 'true' ) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return v;
+ }
+ } else {
+ return v;
+ }
+ }
+
+ function getLuaErrorMessage(l) {
+ var v:String = Lua.tostring(l, -1);
+ Lua.pop(l, 1);
+ return v;
+ }
+
+ public function setVar(var_name : String, object : Dynamic){
+ // trace('setting variable ' + var_name + ' to ' + object);
+
+ Lua.pushnumber(lua,object);
+ Lua.setglobal(lua, var_name);
+ }
+
+ public function getVar(var_name : String, type : String) : Dynamic {
+ var result : Any = null;
+
+ // trace('getting variable ' + var_name + ' with a type of ' + type);
+
+ Lua.getglobal(lua, var_name);
+ result = Convert.fromLua(lua,-1);
+ Lua.pop(lua,1);
+
+ if( result == null ) {
+ return null;
+ } else {
+ var result = convert(result, type);
+ //trace(var_name + ' result: ' + result);
+ return result;
+ }
+ }
+
+ function getActorByName(id:String):Dynamic
+ {
+ // pre defined names
+ switch(id)
+ {
+ case 'boyfriend':
+ @:privateAccess
+ return PlayState.boyfriend;
+ case 'girlfriend':
+ @:privateAccess
+ return PlayState.gf;
+ case 'dad':
+ @:privateAccess
+ return PlayState.dad;
+ }
+ // lua objects or what ever
+ if (luaSprites.get(id) == null)
+ {
+ if (Std.parseInt(id) == null)
+ return Reflect.getProperty(PlayState.instance,id);
+ return PlayState.PlayState.strumLineNotes.members[Std.parseInt(id)];
+ }
+ return luaSprites.get(id);
+ }
+
+ public static var luaSprites:Map = [];
+
+
+
+ function makeLuaSprite(spritePath:String,toBeCalled:String, drawBehind:Bool)
+ {
+ #if sys
+ var data:BitmapData = BitmapData.fromFile(Sys.getCwd() + "assets/data/" + PlayState.SONG.song.toLowerCase() + '/' + spritePath + ".png");
+
+ var sprite:FlxSprite = new FlxSprite(0,0);
+ var imgWidth:Float = FlxG.width / data.width;
+ var imgHeight:Float = FlxG.height / data.height;
+ var scale:Float = imgWidth <= imgHeight ? imgWidth : imgHeight;
+
+ // Cap the scale at x1
+ if (scale > 1)
+ {
+ scale = 1;
+ }
+
+ sprite.makeGraphic(Std.int(data.width * scale),Std.int(data.width * scale),FlxColor.TRANSPARENT);
+
+ var data2:BitmapData = sprite.pixels.clone();
+ var matrix:Matrix = new Matrix();
+ matrix.identity();
+ matrix.scale(scale, scale);
+ data2.fillRect(data2.rect, FlxColor.TRANSPARENT);
+ data2.draw(data, matrix, null, null, null, true);
+ sprite.pixels = data2;
+
+ luaSprites.set(toBeCalled,sprite);
+ // and I quote:
+ // shitty layering but it works!
+ @:privateAccess
+ {
+ if (drawBehind)
+ {
+ PlayState.instance.removeObject(PlayState.gf);
+ PlayState.instance.removeObject(PlayState.boyfriend);
+ PlayState.instance.removeObject(PlayState.dad);
+ }
+ PlayState.instance.addObject(sprite);
+ if (drawBehind)
+ {
+ PlayState.instance.addObject(PlayState.gf);
+ PlayState.instance.addObject(PlayState.boyfriend);
+ PlayState.instance.addObject(PlayState.dad);
+ }
+ }
+ #end
+ return toBeCalled;
+ }
+
+ public function die()
+ {
+ Lua.close(lua);
+ lua = null;
+ }
+
+ // LUA SHIT
+
+ function new()
+ {
+ trace('opening a lua state (because we are cool :))');
+ lua = LuaL.newstate();
+ LuaL.openlibs(lua);
+ trace("Lua version: " + Lua.version());
+ trace("LuaJIT version: " + Lua.versionJIT());
+ Lua.init_callbacks(lua);
+
+ //shaders = new Array();
+
+ var result = LuaL.dofile(lua, Paths.lua(PlayState.SONG.song.toLowerCase() + "/modchart")); // execute le file
+
+ if (result != 0)
+ {
+ Application.current.window.alert("LUA COMPILE ERROR:\n" + Lua.tostring(lua,result),"Kade Engine Modcharts");
+ lua = null;
+ LoadingState.loadAndSwitchState(new MainMenuState());
+ }
+
+ // get some fukin globals up in here bois
+
+ setVar("difficulty", PlayState.storyDifficulty);
+ setVar("bpm", Conductor.bpm);
+ setVar("scrollspeed", FlxG.save.data.scrollSpeed != 1 ? FlxG.save.data.scrollSpeed : PlayState.SONG.speed);
+ setVar("fpsCap", FlxG.save.data.fpsCap);
+ setVar("downscroll", FlxG.save.data.downscroll);
+
+ setVar("curStep", 0);
+ setVar("curBeat", 0);
+ setVar("crochet", Conductor.stepCrochet);
+ setVar("safeZoneOffset", Conductor.safeZoneOffset);
+
+ setVar("hudZoom", PlayState.instance.camHUD.zoom);
+ setVar("cameraZoom", FlxG.camera.zoom);
+
+ setVar("cameraAngle", FlxG.camera.angle);
+ setVar("camHudAngle", PlayState.instance.camHUD.angle);
+
+ setVar("followXOffset",0);
+ setVar("followYOffset",0);
+
+ setVar("showOnlyStrums", false);
+ setVar("strumLine1Visible", true);
+ setVar("strumLine2Visible", true);
+
+ setVar("screenWidth",FlxG.width);
+ setVar("screenHeight",FlxG.height);
+ setVar("hudWidth", PlayState.instance.camHUD.width);
+ setVar("hudHeight", PlayState.instance.camHUD.height);
+
+ setVar("mustHit", false);
+
+ setVar("strumLineY", PlayState.instance.strumLine.y);
+
+ // callbacks
+
+ // sprites
+
+ trace(Lua_helper.add_callback(lua,"makeSprite", makeLuaSprite));
+
+ Lua_helper.add_callback(lua,"destroySprite", function(id:String) {
+ var sprite = luaSprites.get(id);
+ if (sprite == null)
+ return false;
+ PlayState.instance.removeObject(sprite);
+ return true;
+ });
+
+ // hud/camera
+
+ trace(Lua_helper.add_callback(lua,"setHudAngle", function (x:Float) {
+ PlayState.instance.camHUD.angle = x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setHudPosition", function (x:Int, y:Int) {
+ PlayState.instance.camHUD.x = x;
+ PlayState.instance.camHUD.y = y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getHudX", function () {
+ return PlayState.instance.camHUD.x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getHudY", function () {
+ return PlayState.instance.camHUD.y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setCamPosition", function (x:Int, y:Int) {
+ FlxG.camera.x = x;
+ FlxG.camera.y = y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getCameraX", function () {
+ return FlxG.camera.x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getCameraY", function () {
+ return FlxG.camera.y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setCamZoom", function(zoomAmount:Int) {
+ FlxG.camera.zoom = zoomAmount;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setHudZoom", function(zoomAmount:Int) {
+ PlayState.instance.camHUD.zoom = zoomAmount;
+ }));
+
+ // actors
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNotes", function() {
+ return PlayState.instance.notes.length;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteX", function(id:Int) {
+ return PlayState.instance.notes.members[id].x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteY", function(id:Int) {
+ return PlayState.instance.notes.members[id].y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteType", function(id:Int) {
+ return PlayState.instance.notes.members[id].noteData;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"isSustain", function(id:Int) {
+ return PlayState.instance.notes.members[id].isSustainNote;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"isParentSustain", function(id:Int) {
+ return PlayState.instance.notes.members[id].prevNote.isSustainNote;
+ }));
+
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteParentX", function(id:Int) {
+ return PlayState.instance.notes.members[id].prevNote.x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteParentY", function(id:Int) {
+ return PlayState.instance.notes.members[id].prevNote.y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteHit", function(id:Int) {
+ return PlayState.instance.notes.members[id].mustPress;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteCalcX", function(id:Int) {
+ if (PlayState.instance.notes.members[id].mustPress)
+ return PlayState.playerStrums.members[Math.floor(Math.abs(PlayState.instance.notes.members[id].noteData))].x;
+ return PlayState.strumLineNotes.members[Math.floor(Math.abs(PlayState.instance.notes.members[id].noteData))].x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"anyNotes", function() {
+ return PlayState.instance.notes.members.length != 0;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteStrumtime", function(id:Int) {
+ return PlayState.instance.notes.members[id].strumTime;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteScaleX", function(id:Int) {
+ return PlayState.instance.notes.members[id].scale.x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setRenderedNotePos", function(x:Float,y:Float, id:Int) {
+ if (PlayState.instance.notes.members[id] == null)
+ throw('error! you cannot set a rendered notes position when it doesnt exist! ID: ' + id);
+ else
+ {
+ PlayState.instance.notes.members[id].modifiedByLua = true;
+ PlayState.instance.notes.members[id].x = x;
+ PlayState.instance.notes.members[id].y = y;
+ }
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setRenderedNoteAlpha", function(alpha:Float, id:Int) {
+ PlayState.instance.notes.members[id].modifiedByLua = true;
+ PlayState.instance.notes.members[id].alpha = alpha;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setRenderedNoteScale", function(scale:Float, id:Int) {
+ PlayState.instance.notes.members[id].modifiedByLua = true;
+ PlayState.instance.notes.members[id].setGraphicSize(Std.int(PlayState.instance.notes.members[id].width * scale));
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setRenderedNoteScale", function(scaleX:Int, scaleY:Int, id:Int) {
+ PlayState.instance.notes.members[id].modifiedByLua = true;
+ PlayState.instance.notes.members[id].setGraphicSize(scaleX,scaleY);
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getRenderedNoteWidth", function(id:Int) {
+ return PlayState.instance.notes.members[id].width;
+ }));
+
+
+ trace(Lua_helper.add_callback(lua,"setRenderedNoteAngle", function(angle:Float, id:Int) {
+ PlayState.instance.notes.members[id].modifiedByLua = true;
+ PlayState.instance.notes.members[id].angle = angle;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setActorX", function(x:Int,id:String) {
+ getActorByName(id).x = x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setActorAlpha", function(alpha:Float,id:String) {
+ getActorByName(id).alpha = alpha;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setActorY", function(y:Int,id:String) {
+ getActorByName(id).y = y;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setActorAngle", function(angle:Int,id:String) {
+ getActorByName(id).angle = angle;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"setActorScale", function(scale:Float,id:String) {
+ getActorByName(id).setGraphicSize(Std.int(getActorByName(id).width * scale));
+ }));
+
+
+ trace(Lua_helper.add_callback(lua,"getActorWidth", function (id:String) {
+ return getActorByName(id).width;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getActorHeight", function (id:String) {
+ return getActorByName(id).height;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getActorAlpha", function(id:String) {
+ return getActorByName(id).alpha;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getActorAngle", function(id:String) {
+ return getActorByName(id).angle;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getActorX", function (id:String) {
+ return getActorByName(id).x;
+ }));
+
+ trace(Lua_helper.add_callback(lua,"getActorY", function (id:String) {
+ return getActorByName(id).y;
+ }));
+
+
+ // tweens
+
+ Lua_helper.add_callback(lua,"tweenCameraPos", function(toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {x: toX, y: toY}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraAngle", function(toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {angle:toAngle}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraZoom", function(toZoom:Float, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {zoom:toZoom}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudPos", function(toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {x: toX, y: toY}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudAngle", function(toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {angle:toAngle}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudZoom", function(toZoom:Float, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {zoom:toZoom}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPos", function(id:String, toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {x: toX, y: toY}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosXAngle", function(id:String, toX:Int, toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {x: toX, angle: toAngle}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosYAngle", function(id:String, toY:Int, toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {y: toY, angle: toAngle}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenAngle", function(id:String, toAngle:Int, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {angle: toAngle}, time, {ease: FlxEase.linear, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraPosOut", function(toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {x: toX, y: toY}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraAngleOut", function(toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {angle:toAngle}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraZoomOut", function(toZoom:Float, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {zoom:toZoom}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudPosOut", function(toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {x: toX, y: toY}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudAngleOut", function(toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {angle:toAngle}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudZoomOut", function(toZoom:Float, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {zoom:toZoom}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosOut", function(id:String, toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {x: toX, y: toY}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosXAngleOut", function(id:String, toX:Int, toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {x: toX, angle: toAngle}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosYAngleOut", function(id:String, toY:Int, toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {y: toY, angle: toAngle}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenAngleOut", function(id:String, toAngle:Int, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {angle: toAngle}, time, {ease: FlxEase.cubeOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraPosIn", function(toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {x: toX, y: toY}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraAngleIn", function(toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {angle:toAngle}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenCameraZoomIn", function(toZoom:Float, time:Float, onComplete:String) {
+ FlxTween.tween(FlxG.camera, {zoom:toZoom}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudPosIn", function(toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {x: toX, y: toY}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudAngleIn", function(toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {angle:toAngle}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenHudZoomIn", function(toZoom:Float, time:Float, onComplete:String) {
+ FlxTween.tween(PlayState.instance.camHUD, {zoom:toZoom}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,["camera"]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosIn", function(id:String, toX:Int, toY:Int, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {x: toX, y: toY}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosXAngleIn", function(id:String, toX:Int, toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {x: toX, angle: toAngle}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenPosYAngleIn", function(id:String, toY:Int, toAngle:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {y: toY, angle: toAngle}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenAngleIn", function(id:String, toAngle:Int, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {angle: toAngle}, time, {ease: FlxEase.cubeIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenFadeIn", function(id:String, toAlpha:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {alpha: toAlpha}, time, {ease: FlxEase.circIn, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+
+ Lua_helper.add_callback(lua,"tweenFadeOut", function(id:String, toAlpha:Float, time:Float, onComplete:String) {
+ FlxTween.tween(getActorByName(id), {alpha: toAlpha}, time, {ease: FlxEase.circOut, onComplete: function(flxTween:FlxTween) { if (onComplete != '' && onComplete != null) {callLua(onComplete,[id]);}}});
+ });
+//forgot and accidentally commit to master branch
+ // shader
+
+ /*Lua_helper.add_callback(lua,"createShader", function(frag:String,vert:String) {
+ var shader:LuaShader = new LuaShader(frag,vert);
+
+ trace(shader.glFragmentSource);
+
+ shaders.push(shader);
+ // if theres 1 shader we want to say theres 0 since 0 index and length returns a 1 index.
+ return shaders.length == 1 ? 0 : shaders.length;
+ });
+
+
+ Lua_helper.add_callback(lua,"setFilterHud", function(shaderIndex:Int) {
+ PlayState.instance.camHUD.setFilters([new ShaderFilter(shaders[shaderIndex])]);
+ });
+
+ Lua_helper.add_callback(lua,"setFilterCam", function(shaderIndex:Int) {
+ FlxG.camera.setFilters([new ShaderFilter(shaders[shaderIndex])]);
+ });*/
+
+ // default strums
+
+ for (i in 0...PlayState.strumLineNotes.length) {
+ var member = PlayState.strumLineNotes.members[i];
+ trace(PlayState.strumLineNotes.members[i].x + " " + PlayState.strumLineNotes.members[i].y + " " + PlayState.strumLineNotes.members[i].angle + " | strum" + i);
+ //setVar("strum" + i + "X", Math.floor(member.x));
+ setVar("defaultStrum" + i + "X", Math.floor(member.x));
+ //setVar("strum" + i + "Y", Math.floor(member.y));
+ setVar("defaultStrum" + i + "Y", Math.floor(member.y));
+ //setVar("strum" + i + "Angle", Math.floor(member.angle));
+ setVar("defaultStrum" + i + "Angle", Math.floor(member.angle));
+ trace("Adding strum" + i);
+ }
+ }
+
+ public function executeState(name,args:Array)
+ {
+ return Lua.tostring(lua,callLua(name, args));
+ }
+
+ public static function createModchartState():ModchartState
+ {
+ return new ModchartState();
+ }
+}
+#end
diff --git a/source_duck/MusicBeatState.hx b/source_duck/MusicBeatState.hx
new file mode 100644
index 00000000..29260f84
--- /dev/null
+++ b/source_duck/MusicBeatState.hx
@@ -0,0 +1,114 @@
+package;
+
+#if windows
+import Discord.DiscordClient;
+#end
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import openfl.Lib;
+import Conductor.BPMChangeEvent;
+import flixel.FlxG;
+import flixel.addons.transition.FlxTransitionableState;
+import flixel.addons.ui.FlxUIState;
+import flixel.math.FlxRect;
+import flixel.util.FlxTimer;
+
+class MusicBeatState extends FlxUIState
+{
+ private var lastBeat:Float = 0;
+ private var lastStep:Float = 0;
+
+ private var curStep:Int = 0;
+ private var curBeat:Int = 0;
+ private var controls(get, never):Controls;
+
+ inline function get_controls():Controls
+ return PlayerSettings.player1.controls;
+
+ override function create()
+ {
+ (cast (Lib.current.getChildAt(0), Main)).setFPSCap(FlxG.save.data.fpsCap);
+
+ if (transIn != null)
+ trace('reg ' + transIn.region);
+
+ super.create();
+ }
+
+
+ var array:Array = [
+ FlxColor.fromRGB(148, 0, 211),
+ FlxColor.fromRGB(75, 0, 130),
+ FlxColor.fromRGB(0, 0, 255),
+ FlxColor.fromRGB(0, 255, 0),
+ FlxColor.fromRGB(255, 255, 0),
+ FlxColor.fromRGB(255, 127, 0),
+ FlxColor.fromRGB(255, 0 , 0)
+ ];
+
+ var skippedFrames = 0;
+
+ override function update(elapsed:Float)
+ {
+ //everyStep();
+ var oldStep:Int = curStep;
+
+ updateCurStep();
+ updateBeat();
+
+ if (oldStep != curStep && curStep > 0)
+ stepHit();
+
+ if (FlxG.save.data.fpsRain && skippedFrames >= 6)
+ {
+ if (currentColor >= array.length)
+ currentColor = 0;
+ (cast (Lib.current.getChildAt(0), Main)).changeFPSColor(array[currentColor]);
+ currentColor++;
+ skippedFrames = 0;
+ }
+ else
+ skippedFrames++;
+
+ if ((cast (Lib.current.getChildAt(0), Main)).getFPSCap != FlxG.save.data.fpsCap)
+ (cast (Lib.current.getChildAt(0), Main)).setFPSCap(FlxG.save.data.fpsCap);
+
+ super.update(elapsed);
+ }
+
+ private function updateBeat():Void
+ {
+ lastBeat = curStep;
+ curBeat = Math.floor(curStep / 4);
+ }
+
+ public static var currentColor = 0;
+
+ private function updateCurStep():Void
+ {
+ var lastChange:BPMChangeEvent = {
+ stepTime: 0,
+ songTime: 0,
+ bpm: 0
+ }
+ for (i in 0...Conductor.bpmChangeMap.length)
+ {
+ if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
+ lastChange = Conductor.bpmChangeMap[i];
+ }
+
+ curStep = lastChange.stepTime + Math.floor((Conductor.songPosition - lastChange.songTime) / Conductor.stepCrochet);
+ }
+
+ public function stepHit():Void
+ {
+
+ if (curStep % 4 == 0)
+ beatHit();
+ }
+
+ public function beatHit():Void
+ {
+ //do literally nothing dumbass
+ }
+}
diff --git a/source_duck/MusicBeatSubstate.hx b/source_duck/MusicBeatSubstate.hx
new file mode 100644
index 00000000..c6da0fb5
--- /dev/null
+++ b/source_duck/MusicBeatSubstate.hx
@@ -0,0 +1,65 @@
+package;
+
+import Conductor.BPMChangeEvent;
+import flixel.FlxG;
+import flixel.FlxSubState;
+
+class MusicBeatSubstate extends FlxSubState
+{
+ public function new()
+ {
+ super();
+ }
+
+ private var lastBeat:Float = 0;
+ private var lastStep:Float = 0;
+
+ private var curStep:Int = 0;
+ private var curBeat:Int = 0;
+ private var controls(get, never):Controls;
+
+ inline function get_controls():Controls
+ return PlayerSettings.player1.controls;
+
+ override function update(elapsed:Float)
+ {
+ //everyStep();
+ var oldStep:Int = curStep;
+
+ updateCurStep();
+ curBeat = Math.floor(curStep / 4);
+
+ if (oldStep != curStep && curStep > 0)
+ stepHit();
+
+
+ super.update(elapsed);
+ }
+
+ private function updateCurStep():Void
+ {
+ var lastChange:BPMChangeEvent = {
+ stepTime: 0,
+ songTime: 0,
+ bpm: 0
+ }
+ for (i in 0...Conductor.bpmChangeMap.length)
+ {
+ if (Conductor.songPosition > Conductor.bpmChangeMap[i].songTime)
+ lastChange = Conductor.bpmChangeMap[i];
+ }
+
+ curStep = lastChange.stepTime + Math.floor((Conductor.songPosition - lastChange.songTime) / Conductor.stepCrochet);
+ }
+
+ public function stepHit():Void
+ {
+ if (curStep % 4 == 0)
+ beatHit();
+ }
+
+ public function beatHit():Void
+ {
+ //do literally nothing dumbass
+ }
+}
diff --git a/source_duck/NGio.hx b/source_duck/NGio.hx
new file mode 100644
index 00000000..f8d8948e
--- /dev/null
+++ b/source_duck/NGio.hx
@@ -0,0 +1,200 @@
+package;
+
+import flixel.FlxG;
+import flixel.util.FlxSignal;
+import flixel.util.FlxTimer;
+import io.newgrounds.NG;
+import io.newgrounds.components.ScoreBoardComponent.Period;
+import io.newgrounds.objects.Medal;
+import io.newgrounds.objects.Score;
+import io.newgrounds.objects.ScoreBoard;
+import io.newgrounds.objects.events.Response;
+import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
+import io.newgrounds.objects.events.Result.GetVersionResult;
+import lime.app.Application;
+import openfl.display.Stage;
+
+using StringTools;
+
+/**
+ * MADE BY GEOKURELI THE LEGENED GOD HERO MVP
+ */
+class NGio
+{
+ public static var isLoggedIn:Bool = false;
+ public static var scoreboardsLoaded:Bool = false;
+
+ public static var scoreboardArray:Array = [];
+
+ public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
+ public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
+
+ public static var GAME_VER:String = "";
+ public static var GAME_VER_NUMS:String = '';
+ public static var gotOnlineVer:Bool = false;
+
+ public static function noLogin(api:String)
+ {
+ trace('INIT NOLOGIN');
+ GAME_VER = "v" + Application.current.meta.get('version');
+
+ if (api.length != 0)
+ {
+ NG.create(api);
+
+ new FlxTimer().start(2, function(tmr:FlxTimer)
+ {
+ var call = NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response:Response)
+ {
+ GAME_VER = response.result.data.currentVersion;
+ GAME_VER_NUMS = GAME_VER.split(" ")[0].trim();
+ trace('CURRENT NG VERSION: ' + GAME_VER);
+ trace('CURRENT NG VERSION: ' + GAME_VER_NUMS);
+ gotOnlineVer = true;
+ });
+
+ call.send();
+ });
+ }
+ }
+
+ public function new(api:String, encKey:String, ?sessionId:String)
+ {
+ trace("connecting to newgrounds");
+
+ NG.createAndCheckSession(api, sessionId);
+
+ NG.core.verbose = true;
+ // Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
+ NG.core.initEncryption(encKey); // Found in you NG project view
+
+ trace(NG.core.attemptingLogin);
+
+ if (NG.core.attemptingLogin)
+ {
+ /* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
+ * and we should login shortly. lets wait for that to happen
+ */
+ trace("attempting login");
+ NG.core.onLogin.add(onNGLogin);
+ }
+ else
+ {
+ /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
+ * Note: This will cause a new browser window to pop up where they can log in to newgrounds
+ */
+ NG.core.requestLogin(onNGLogin);
+ }
+ }
+
+ function onNGLogin():Void
+ {
+ trace('logged in! user:${NG.core.user.name}');
+ isLoggedIn = true;
+ FlxG.save.data.sessionId = NG.core.sessionId;
+ // FlxG.save.flush();
+ // Load medals then call onNGMedalFetch()
+ NG.core.requestMedals(onNGMedalFetch);
+
+ // Load Scoreboards hten call onNGBoardsFetch()
+ NG.core.requestScoreBoards(onNGBoardsFetch);
+
+ ngDataLoaded.dispatch();
+ }
+
+ // --- MEDALS
+ function onNGMedalFetch():Void
+ {
+ /*
+ // Reading medal info
+ for (id in NG.core.medals.keys())
+ {
+ var medal = NG.core.medals.get(id);
+ trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
+ }
+
+ // Unlocking medals
+ var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
+ if (!unlockingMedal.unlocked)
+ unlockingMedal.sendUnlock();
+ */
+ }
+
+ // --- SCOREBOARDS
+ function onNGBoardsFetch():Void
+ {
+ /*
+ // Reading medal info
+ for (id in NG.core.scoreBoards.keys())
+ {
+ var board = NG.core.scoreBoards.get(id);
+ trace('loaded scoreboard id:$id, name:${board.name}');
+ }
+ */
+ // var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
+
+ // Posting a score thats OVER 9000!
+ // board.postScore(FlxG.random.int(0, 1000));
+
+ // --- To view the scores you first need to select the range of scores you want to see ---
+
+ // add an update listener so we know when we get the new scores
+ // board.onUpdate.add(onNGScoresFetch);
+ trace("shoulda got score by NOW!");
+ // board.requestScores(20);// get the best 10 scores ever logged
+ // more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
+ }
+
+ inline static public function postScore(score:Int = 0, song:String)
+ {
+ if (isLoggedIn)
+ {
+ for (id in NG.core.scoreBoards.keys())
+ {
+ var board = NG.core.scoreBoards.get(id);
+
+ if (song == board.name)
+ {
+ board.postScore(score, "Uhh meow?");
+ }
+
+ // trace('loaded scoreboard id:$id, name:${board.name}');
+ }
+ }
+ }
+
+ function onNGScoresFetch():Void
+ {
+ scoreboardsLoaded = true;
+
+ ngScoresLoaded.dispatch();
+ /*
+ for (score in NG.core.scoreBoards.get(8737).scores)
+ {
+ trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
+
+ }
+ */
+
+ // var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
+ // board.postScore(HighScore.score);
+
+ // NGio.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
+ }
+
+ inline static public function logEvent(event:String)
+ {
+ NG.core.calls.event.logEvent(event).send();
+ trace('should have logged: ' + event);
+ }
+
+ inline static public function unlockMedal(id:Int)
+ {
+ if (isLoggedIn)
+ {
+ var medal = NG.core.medals.get(id);
+ if (!medal.unlocked)
+ medal.sendUnlock();
+ }
+ }
+}
diff --git a/source_duck/Note.hx b/source_duck/Note.hx
new file mode 100644
index 00000000..544fe3aa
--- /dev/null
+++ b/source_duck/Note.hx
@@ -0,0 +1,217 @@
+package;
+
+import flixel.addons.effects.FlxSkewedSprite;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxAtlasFrames;
+import flixel.math.FlxMath;
+import flixel.util.FlxColor;
+#if polymod
+import polymod.format.ParseRules.TargetSignatureElement;
+#end
+import PlayState;
+
+using StringTools;
+
+class Note extends FlxSprite
+{
+ public var strumTime:Float = 0;
+
+ public var mustPress:Bool = false;
+ public var noteData:Int = 0;
+ public var canBeHit:Bool = false;
+ public var tooLate:Bool = false;
+ public var wasGoodHit:Bool = false;
+ public var prevNote:Note;
+ public var modifiedByLua:Bool = false;
+ public var sustainLength:Float = 0;
+ public var isSustainNote:Bool = false;
+
+ public var noteScore:Float = 1;
+
+ public static var swagWidth:Float = 160 * 0.7;
+ public static var PURP_NOTE:Int = 0;
+ public static var GREEN_NOTE:Int = 2;
+ public static var BLUE_NOTE:Int = 1;
+ public static var RED_NOTE:Int = 3;
+
+ public var rating:String = "shit";
+
+ public function new(strumTime:Float, noteData:Int, ?prevNote:Note, ?sustainNote:Bool = false)
+ {
+ super();
+
+ if (prevNote == null)
+ prevNote = this;
+
+ this.prevNote = prevNote;
+ isSustainNote = sustainNote;
+
+ x += 50;
+ // MAKE SURE ITS DEFINITELY OFF SCREEN?
+ y -= 2000;
+ this.strumTime = strumTime;
+
+ if (this.strumTime < 0 )
+ this.strumTime = 0;
+
+ this.noteData = noteData;
+
+ var daStage:String = PlayState.curStage;
+
+ switch (PlayState.SONG.noteStyle)
+ {
+ case 'pixel':
+ loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels','week6'), true, 17, 17);
+
+ animation.add('greenScroll', [6]);
+ animation.add('redScroll', [7]);
+ animation.add('blueScroll', [5]);
+ animation.add('purpleScroll', [4]);
+
+ if (isSustainNote)
+ {
+ loadGraphic(Paths.image('weeb/pixelUI/arrowEnds','week6'), true, 7, 6);
+
+ animation.add('purpleholdend', [4]);
+ animation.add('greenholdend', [6]);
+ animation.add('redholdend', [7]);
+ animation.add('blueholdend', [5]);
+
+ animation.add('purplehold', [0]);
+ animation.add('greenhold', [2]);
+ animation.add('redhold', [3]);
+ animation.add('bluehold', [1]);
+ }
+
+ setGraphicSize(Std.int(width * PlayState.daPixelZoom));
+ updateHitbox();
+ default:
+ frames = Paths.getSparrowAtlas('NOTE_assets');
+
+ animation.addByPrefix('greenScroll', 'green0');
+ animation.addByPrefix('redScroll', 'red0');
+ animation.addByPrefix('blueScroll', 'blue0');
+ animation.addByPrefix('purpleScroll', 'purple0');
+
+ animation.addByPrefix('purpleholdend', 'pruple end hold');
+ animation.addByPrefix('greenholdend', 'green hold end');
+ animation.addByPrefix('redholdend', 'red hold end');
+ animation.addByPrefix('blueholdend', 'blue hold end');
+
+ animation.addByPrefix('purplehold', 'purple hold piece');
+ animation.addByPrefix('greenhold', 'green hold piece');
+ animation.addByPrefix('redhold', 'red hold piece');
+ animation.addByPrefix('bluehold', 'blue hold piece');
+
+ setGraphicSize(Std.int(width * 0.7));
+ updateHitbox();
+ antialiasing = true;
+ }
+
+ switch (noteData)
+ {
+ case 0:
+ x += swagWidth * 0;
+ animation.play('purpleScroll');
+ case 1:
+ x += swagWidth * 1;
+ animation.play('blueScroll');
+ case 2:
+ x += swagWidth * 2;
+ animation.play('greenScroll');
+ case 3:
+ x += swagWidth * 3;
+ animation.play('redScroll');
+ }
+
+ // trace(prevNote);
+
+ // we make sure its downscroll and its a SUSTAIN NOTE (aka a trail, not a note)
+ // and flip it so it doesn't look weird.
+ // THIS DOESN'T FUCKING FLIP THE NOTE, CONTRIBUTERS DON'T JUST COMMENT THIS OUT JESUS
+ if (FlxG.save.data.downscroll && sustainNote)
+ flipY = true;
+
+ if (isSustainNote && prevNote != null)
+ {
+ noteScore * 0.2;
+ alpha = 0.6;
+
+ x += width / 2;
+
+ switch (noteData)
+ {
+ case 2:
+ animation.play('greenholdend');
+ case 3:
+ animation.play('redholdend');
+ case 1:
+ animation.play('blueholdend');
+ case 0:
+ animation.play('purpleholdend');
+ }
+
+ updateHitbox();
+
+ x -= width / 2;
+
+ if (PlayState.curStage.startsWith('school'))
+ x += 30;
+
+ if (prevNote.isSustainNote)
+ {
+ switch (prevNote.noteData)
+ {
+ case 0:
+ prevNote.animation.play('purplehold');
+ case 1:
+ prevNote.animation.play('bluehold');
+ case 2:
+ prevNote.animation.play('greenhold');
+ case 3:
+ prevNote.animation.play('redhold');
+ }
+
+
+ if(FlxG.save.data.scrollSpeed != 1)
+ prevNote.scale.y *= Conductor.stepCrochet / 100 * 1.5 * FlxG.save.data.scrollSpeed;
+ else
+ prevNote.scale.y *= Conductor.stepCrochet / 100 * 1.5 * PlayState.SONG.speed;
+ prevNote.updateHitbox();
+ // prevNote.setGraphicSize();
+ }
+ }
+ }
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ if (mustPress)
+ {
+ // The * 0.5 is so that it's easier to hit them too late, instead of too early
+ if (strumTime > Conductor.songPosition - (Conductor.safeZoneOffset * 1.5)
+ && strumTime < Conductor.songPosition + (Conductor.safeZoneOffset * 0.5))
+ canBeHit = true;
+ else
+ canBeHit = false;
+
+ if (strumTime < Conductor.songPosition - Conductor.safeZoneOffset * Conductor.timeScale && !wasGoodHit)
+ tooLate = true;
+ }
+ else
+ {
+ canBeHit = false;
+
+ if (strumTime <= Conductor.songPosition)
+ wasGoodHit = true;
+ }
+
+ if (tooLate)
+ {
+ if (alpha > 0.3)
+ alpha = 0.3;
+ }
+ }
+}
diff --git a/source_duck/OFLSprite.hx b/source_duck/OFLSprite.hx
new file mode 100644
index 00000000..abea3a09
--- /dev/null
+++ b/source_duck/OFLSprite.hx
@@ -0,0 +1,39 @@
+import flixel.util.FlxColor;
+import openfl.display.Sprite;
+import flixel.FlxSprite;
+
+/**
+ * designed to draw a Open FL Sprite as a FlxSprite (to allow layering and auto sizing for haxe flixel cameras)
+ * Custom made for Kade Engine
+ */
+class OFLSprite extends FlxSprite
+{
+ public var flSprite:Sprite;
+
+ public function new(x,y,width,height,Sprite:Sprite)
+ {
+ super(x,y);
+
+ makeGraphic(width,height,FlxColor.TRANSPARENT);
+
+ flSprite = Sprite;
+
+ pixels.draw(flSprite);
+ }
+
+ private var _frameCount:Int = 0;
+
+ override function update(elapsed:Float)
+ {
+ if (_frameCount != 2)
+ {
+ pixels.draw(flSprite);
+ _frameCount++;
+ }
+ }
+
+ public function updateDisplay()
+ {
+ pixels.draw(flSprite);
+ }
+}
\ No newline at end of file
diff --git a/source_duck/Options.hx b/source_duck/Options.hx
new file mode 100644
index 00000000..3d9c2cd5
--- /dev/null
+++ b/source_duck/Options.hx
@@ -0,0 +1,538 @@
+package;
+
+import flixel.util.FlxColor;
+import Controls.KeyboardScheme;
+import flixel.FlxG;
+import openfl.display.FPS;
+import openfl.Lib;
+
+class OptionCatagory
+{
+ private var _options:Array