From dd413d42ab5bdd63b18e2663f69d8182b032c76e Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Wed, 19 Jun 2024 23:47:11 -0400
Subject: [PATCH] more retro week 6 stuff

---
 assets                                        |   2 +-
 source/funkin/effects/RetroCameraFade.hx      | 106 ++++++++++++++++++
 source/funkin/graphics/FunkinSprite.hx        | 101 +++++++++++++++++
 source/funkin/play/Countdown.hx               |  19 +++-
 source/funkin/play/GameOverSubState.hx        |  25 ++++-
 .../play/character/MultiSparrowCharacter.hx   |   2 +
 .../funkin/play/character/PackerCharacter.hx  |   2 +
 .../funkin/play/character/SparrowCharacter.hx |   2 +
 source/funkin/play/components/PopUpStuff.hx   |  30 +++--
 source/funkin/play/stage/Stage.hx             |   4 +
 10 files changed, 277 insertions(+), 16 deletions(-)
 create mode 100644 source/funkin/effects/RetroCameraFade.hx

diff --git a/assets b/assets
index 4b9507525..225e248f1 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 4b95075255baeaba3585fabff7052c257856fafe
+Subproject commit 225e248f148a92500a6fe90e4f10e4cd2acee782
diff --git a/source/funkin/effects/RetroCameraFade.hx b/source/funkin/effects/RetroCameraFade.hx
new file mode 100644
index 000000000..d4c1da5ef
--- /dev/null
+++ b/source/funkin/effects/RetroCameraFade.hx
@@ -0,0 +1,106 @@
+package funkin.effects;
+
+import flixel.util.FlxTimer;
+import flixel.FlxCamera;
+import openfl.filters.ColorMatrixFilter;
+
+class RetroCameraFade
+{
+  // im lazy, but we only use this for week 6
+  // and also sorta yoinked for djflixel, lol !
+  public static function fadeWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
+  {
+    var steps:Int = 0;
+    var stepsTotal:Int = camSteps;
+
+    new FlxTimer().start(time / stepsTotal, _ -> {
+      var V:Float = (1 / stepsTotal) * steps;
+      if (steps == stepsTotal) V = 1;
+
+      var matrix = [
+        1, 0, 0, 0, V * 255,
+        0, 1, 0, 0, V * 255,
+        0, 0, 1, 0, V * 255,
+        0, 0, 0, 1,       0
+      ];
+      camera.filters = [new ColorMatrixFilter(matrix)];
+      steps++;
+    }, stepsTotal + 1);
+  }
+
+  public static function fadeFromWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
+  {
+    var steps:Int = camSteps;
+    var stepsTotal:Int = camSteps;
+
+    var matrixDerp = [
+      1, 0, 0, 0, 1.0 * 255,
+      0, 1, 0, 0, 1.0 * 255,
+      0, 0, 1, 0, 1.0 * 255,
+      0, 0, 0, 1,         0
+    ];
+    camera.filters = [new ColorMatrixFilter(matrixDerp)];
+
+    new FlxTimer().start(time / stepsTotal, _ -> {
+      var V:Float = (1 / stepsTotal) * steps;
+      if (steps == stepsTotal) V = 1;
+
+      var matrix = [
+        1, 0, 0, 0, V * 255,
+        0, 1, 0, 0, V * 255,
+        0, 0, 1, 0, V * 255,
+        0, 0, 0, 1,       0
+      ];
+      camera.filters = [new ColorMatrixFilter(matrix)];
+      steps--;
+    }, camSteps);
+  }
+
+  public static function fadeToBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
+  {
+    var steps:Int = 0;
+    var stepsTotal:Int = camSteps;
+
+    new FlxTimer().start(time / stepsTotal, _ -> {
+      var V:Float = (1 / stepsTotal) * steps;
+      if (steps == stepsTotal) V = 1;
+
+      var matrix = [
+        1, 0, 0, 0, -V * 255,
+        0, 1, 0, 0, -V * 255,
+        0, 0, 1, 0, -V * 255,
+        0, 0, 0, 1,        0
+      ];
+      camera.filters = [new ColorMatrixFilter(matrix)];
+      steps++;
+    }, camSteps);
+  }
+
+  public static function fadeBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
+  {
+    var steps:Int = camSteps;
+    var stepsTotal:Int = camSteps;
+
+    var matrixDerp = [
+      1, 0, 0, 0, -1.0 * 255,
+      0, 1, 0, 0, -1.0 * 255,
+      0, 0, 1, 0, -1.0 * 255,
+      0, 0, 0, 1,          0
+    ];
+    camera.filters = [new ColorMatrixFilter(matrixDerp)];
+
+    new FlxTimer().start(time / stepsTotal, _ -> {
+      var V:Float = (1 / stepsTotal) * steps;
+      if (steps == stepsTotal) V = 1;
+
+      var matrix = [
+        1, 0, 0, 0, -V * 255,
+        0, 1, 0, 0, -V * 255,
+        0, 0, 1, 0, -V * 255,
+        0, 0, 0, 1,        0
+      ];
+      camera.filters = [new ColorMatrixFilter(matrix)];
+      steps--;
+    }, camSteps + 1);
+  }
+}
diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx
index bfd2e8028..521553527 100644
--- a/source/funkin/graphics/FunkinSprite.hx
+++ b/source/funkin/graphics/FunkinSprite.hx
@@ -7,6 +7,10 @@ import flixel.tweens.FlxTween;
 import openfl.display3D.textures.TextureBase;
 import funkin.graphics.framebuffer.FixedBitmapData;
 import openfl.display.BitmapData;
+import flixel.math.FlxRect;
+import flixel.math.FlxPoint;
+import flixel.graphics.frames.FlxFrame;
+import flixel.FlxCamera;
 
 /**
  * An FlxSprite with additional functionality.
@@ -269,6 +273,103 @@ class FunkinSprite extends FlxSprite
     return result;
   }
 
+  @:access(flixel.FlxCamera)
+  override function getBoundingBox(camera:FlxCamera):FlxRect
+  {
+    getScreenPosition(_point, camera);
+
+    _rect.set(_point.x, _point.y, width, height);
+    _rect = camera.transformRect(_rect);
+
+    if (isPixelPerfectRender(camera))
+    {
+      _rect.width = _rect.width / this.scale.x;
+      _rect.height = _rect.height / this.scale.y;
+      _rect.x = _rect.x / this.scale.x;
+      _rect.y = _rect.y / this.scale.y;
+      _rect.floor();
+      _rect.x = _rect.x * this.scale.x;
+      _rect.y = _rect.y * this.scale.y;
+      _rect.width = _rect.width * this.scale.x;
+      _rect.height = _rect.height * this.scale.y;
+    }
+
+    return _rect;
+  }
+
+  /**
+   * Returns the screen position of this object.
+   *
+   * @param   result  Optional arg for the returning point
+   * @param   camera  The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
+   * @return  The screen position of this object.
+   */
+  public override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
+  {
+    if (result == null) result = FlxPoint.get();
+
+    if (camera == null) camera = FlxG.camera;
+
+    result.set(x, y);
+    if (pixelPerfectPosition)
+    {
+      _rect.width = _rect.width / this.scale.x;
+      _rect.height = _rect.height / this.scale.y;
+      _rect.x = _rect.x / this.scale.x;
+      _rect.y = _rect.y / this.scale.y;
+      _rect.round();
+      _rect.x = _rect.x * this.scale.x;
+      _rect.y = _rect.y * this.scale.y;
+      _rect.width = _rect.width * this.scale.x;
+      _rect.height = _rect.height * this.scale.y;
+    }
+
+    return result.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y);
+  }
+
+  override function drawSimple(camera:FlxCamera):Void
+  {
+    getScreenPosition(_point, camera).subtractPoint(offset);
+    if (isPixelPerfectRender(camera))
+    {
+      _point.x = _point.x / this.scale.x;
+      _point.y = _point.y / this.scale.y;
+      _point.round();
+
+      _point.x = _point.x * this.scale.x;
+      _point.y = _point.y * this.scale.y;
+    }
+
+    _point.copyToFlash(_flashPoint);
+    camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing);
+  }
+
+  override function drawComplex(camera:FlxCamera):Void
+  {
+    _frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
+    _matrix.translate(-origin.x, -origin.y);
+    _matrix.scale(scale.x, scale.y);
+
+    if (bakedRotationAngle <= 0)
+    {
+      updateTrig();
+
+      if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
+    }
+
+    getScreenPosition(_point, camera).subtractPoint(offset);
+    _point.add(origin.x, origin.y);
+    _matrix.translate(_point.x, _point.y);
+
+    if (isPixelPerfectRender(camera))
+    {
+      _matrix.tx = Math.round(_matrix.tx / this.scale.x) * this.scale.x;
+      _matrix.ty = Math.round(_matrix.ty / this.scale.y) * this.scale.y;
+    }
+
+    camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
+  }
+
   public override function destroy():Void
   {
     frames = null;
diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx
index 10636afdf..55c2a8992 100644
--- a/source/funkin/play/Countdown.hx
+++ b/source/funkin/play/Countdown.hx
@@ -9,6 +9,7 @@ import funkin.modding.module.ModuleHandler;
 import funkin.modding.events.ScriptEvent;
 import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
 import flixel.util.FlxTimer;
+import funkin.util.EaseUtil;
 import funkin.audio.FunkinSound;
 
 class Countdown
@@ -117,7 +118,7 @@ class Countdown
    *
    * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
    */
-  public static function pauseCountdown()
+  public static function pauseCountdown():Void
   {
     if (countdownTimer != null && !countdownTimer.finished)
     {
@@ -130,7 +131,7 @@ class Countdown
    *
    * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
    */
-  public static function resumeCountdown()
+  public static function resumeCountdown():Void
   {
     if (countdownTimer != null && !countdownTimer.finished)
     {
@@ -143,7 +144,7 @@ class Countdown
    *
    * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event.
    */
-  public static function stopCountdown()
+  public static function stopCountdown():Void
   {
     if (countdownTimer != null)
     {
@@ -156,7 +157,7 @@ class Countdown
   /**
    * Stops the current countdown, then starts the song for you.
    */
-  public static function skipCountdown()
+  public static function skipCountdown():Void
   {
     stopCountdown();
     // This will trigger PlayState.startSong()
@@ -185,8 +186,11 @@ class Countdown
   {
     var spritePath:String = null;
 
+    var fadeEase = FlxEase.cubeInOut;
+
     if (isPixelStyle)
     {
+      fadeEase = EaseUtil.stepped(8);
       switch (index)
       {
         case TWO:
@@ -227,7 +231,7 @@ class Countdown
     countdownSprite.screenCenter();
 
     // Fade sprite in, then out, then destroy it.
-    FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000,
+    FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100}, Conductor.instance.beatLengthMs / 1000,
       {
         ease: FlxEase.cubeInOut,
         onComplete: function(twn:FlxTween) {
@@ -235,6 +239,11 @@ class Countdown
         }
       });
 
+    FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000,
+      {
+        ease: fadeEase
+      });
+
     PlayState.instance.add(countdownSprite);
   }
 
diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx
index c84d5b154..1e1c91dc5 100644
--- a/source/funkin/play/GameOverSubState.hx
+++ b/source/funkin/play/GameOverSubState.hx
@@ -16,6 +16,7 @@ import funkin.ui.MusicBeatSubState;
 import funkin.ui.story.StoryMenuState;
 import funkin.util.MathUtil;
 import openfl.utils.Assets;
+import funkin.effects.RetroCameraFade;
 
 /**
  * A substate which renders over the PlayState when the player dies.
@@ -331,9 +332,12 @@ class GameOverSubState extends MusicBeatSubState
       // After the animation finishes...
       new FlxTimer().start(0.7, function(tmr:FlxTimer) {
         // ...fade out the graphics. Then after that happens...
-        FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
+
+        var resetPlaying = function(pixel:Bool = false) {
           // ...close the GameOverSubState.
-          FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
+          if (pixel) RetroCameraFade.fadeBlack(FlxG.camera, 10, 1);
+          else
+            FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
           PlayState.instance.needsReset = true;
 
           if (PlayState.instance.isMinimalMode || boyfriend == null) {}
@@ -350,7 +354,22 @@ class GameOverSubState extends MusicBeatSubState
 
           // Close the substate.
           close();
-        });
+        };
+
+        if (musicSuffix == '-pixel')
+        {
+          RetroCameraFade.fadeToBlack(FlxG.camera, 10, 2);
+          new FlxTimer().start(2, _ -> {
+            FlxG.camera.filters = [];
+            resetPlaying(true);
+          });
+        }
+        else
+        {
+          FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
+            resetPlaying();
+          });
+        }
       });
     }
   }
diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx
index 48c5afb58..41c96fbfa 100644
--- a/source/funkin/play/character/MultiSparrowCharacter.hx
+++ b/source/funkin/play/character/MultiSparrowCharacter.hx
@@ -41,6 +41,8 @@ class MultiSparrowCharacter extends BaseCharacter
     {
       this.isPixel = true;
       this.antialiasing = false;
+      pixelPerfectRender = true;
+      pixelPerfectPosition = true;
     }
     else
     {
diff --git a/source/funkin/play/character/PackerCharacter.hx b/source/funkin/play/character/PackerCharacter.hx
index 2bfac800a..22edbe339 100644
--- a/source/funkin/play/character/PackerCharacter.hx
+++ b/source/funkin/play/character/PackerCharacter.hx
@@ -43,6 +43,8 @@ class PackerCharacter extends BaseCharacter
     {
       this.isPixel = true;
       this.antialiasing = false;
+      pixelPerfectRender = true;
+      pixelPerfectPosition = true;
     }
     else
     {
diff --git a/source/funkin/play/character/SparrowCharacter.hx b/source/funkin/play/character/SparrowCharacter.hx
index a36aed84d..81d98b138 100644
--- a/source/funkin/play/character/SparrowCharacter.hx
+++ b/source/funkin/play/character/SparrowCharacter.hx
@@ -46,6 +46,8 @@ class SparrowCharacter extends BaseCharacter
     {
       this.isPixel = true;
       this.antialiasing = false;
+      pixelPerfectRender = true;
+      pixelPerfectPosition = true;
     }
     else
     {
diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx
index b7e206e97..1bdfd98a8 100644
--- a/source/funkin/play/components/PopUpStuff.hx
+++ b/source/funkin/play/components/PopUpStuff.hx
@@ -7,8 +7,9 @@ import flixel.util.FlxDirection;
 import funkin.graphics.FunkinSprite;
 import funkin.play.PlayState;
 import funkin.util.TimerUtil;
+import funkin.util.EaseUtil;
 
-class PopUpStuff extends FlxTypedGroup<FlxSprite>
+class PopUpStuff extends FlxTypedGroup<FunkinSprite>
 {
   public var offsets:Array<Int> = [0, 0];
 
@@ -17,7 +18,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
     super();
   }
 
-  public function displayRating(daRating:String)
+  public function displayRating(daRating:String):Void
   {
     var perfStart:Float = TimerUtil.start();
 
@@ -40,10 +41,15 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
 
     add(rating);
 
+    var fadeEase = null;
+
     if (PlayState.instance.currentStageId.startsWith('school'))
     {
       rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
       rating.antialiasing = false;
+      rating.pixelPerfectRender = true;
+      rating.pixelPerfectPosition = true;
+      fadeEase = EaseUtil.stepped(2);
     }
     else
     {
@@ -61,7 +67,8 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
           remove(rating, true);
           rating.destroy();
         },
-        startDelay: Conductor.instance.beatLengthMs * 0.001
+        startDelay: Conductor.instance.beatLengthMs * 0.001,
+        ease: fadeEase
       });
 
     trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
@@ -92,10 +99,15 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
 
     // add(comboSpr);
 
+    var fadeEase = null;
+
     if (PlayState.instance.currentStageId.startsWith('school'))
     {
-      comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 0.7));
+      comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 1));
       comboSpr.antialiasing = false;
+      comboSpr.pixelPerfectRender = true;
+      comboSpr.pixelPerfectPosition = true;
+      fadeEase = EaseUtil.stepped(2);
     }
     else
     {
@@ -110,7 +122,8 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
           remove(comboSpr, true);
           comboSpr.destroy();
         },
-        startDelay: Conductor.instance.beatLengthMs * 0.001
+        startDelay: Conductor.instance.beatLengthMs * 0.001,
+        ease: fadeEase
       });
 
     var seperatedScore:Array<Int> = [];
@@ -133,8 +146,10 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
 
       if (PlayState.instance.currentStageId.startsWith('school'))
       {
-        numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 0.7));
+        numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 1));
         numScore.antialiasing = false;
+        numScore.pixelPerfectRender = true;
+        numScore.pixelPerfectPosition = true;
       }
       else
       {
@@ -156,7 +171,8 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
             remove(numScore, true);
             numScore.destroy();
           },
-          startDelay: Conductor.instance.beatLengthMs * 0.002
+          startDelay: Conductor.instance.beatLengthMs * 0.002,
+          ease: fadeEase
         });
 
       daLoop++;
diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx
index 4f8ab4434..f4e22e380 100644
--- a/source/funkin/play/stage/Stage.hx
+++ b/source/funkin/play/stage/Stage.hx
@@ -249,6 +249,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
       // If pixel, disable antialiasing.
       propSprite.antialiasing = !dataProp.isPixel;
 
+      // If pixel, we render it pixel perfect so there's less "mixels"
+      propSprite.pixelPerfectRender = dataProp.isPixel;
+      propSprite.pixelPerfectPosition = dataProp.isPixel;
+
       propSprite.scrollFactor.x = dataProp.scroll[0];
       propSprite.scrollFactor.y = dataProp.scroll[1];