From b06a35ea2fa282b06f11418e78c13ccbb1600462 Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Thu, 2 May 2024 03:38:27 -0400
Subject: [PATCH] Add , and . hotkeys to chart editor.

---
 source/funkin/data/song/SongDataUtils.hx      | 10 +++++
 .../ui/debug/charting/ChartEditorState.hx     | 39 +++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx
index c93c5379a..d0e554e01 100644
--- a/source/funkin/data/song/SongDataUtils.hx
+++ b/source/funkin/data/song/SongDataUtils.hx
@@ -290,6 +290,16 @@ class SongDataUtils
       return data.indexOf(note.data) != -1;
     });
   }
+
+  /**
+   * Filter a list of events to only include events whose kind is one of the given values.
+   */
+  public static function getEventsWithKind(events:Array<SongEventData>, kinds:Array<String>):Array<SongEventData>
+  {
+    return events.filter(function(event:SongEventData):Bool {
+      return kinds.indexOf(event.eventKind) != -1;
+    });
+  }
 }
 
 typedef SongClipboardItems =
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index 93907bdda..b75cd8bf1 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -5171,6 +5171,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
         // Do nothing.
     }
 
+    // Place events at playhead.
+    if (FlxG.keys.justPressed.COMMA) placeEventAtPlayhead(true);
+    if (FlxG.keys.justPressed.PERIOD) placeEventAtPlayhead(false);
+
     updatePlayheadGhostHoldNotes();
   }
 
@@ -5207,6 +5211,41 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
     }
   }
 
+  function placeEventAtPlayhead(isOpponent:Bool):Void
+  {
+    // SHIFT + press or LEFT_SHOULDER + press to remove events instead of placing them.
+    var removeEventInstead:Bool = FlxG.keys.pressed.SHIFT || (FlxG.gamepads.firstActive?.pressed?.LEFT_SHOULDER ?? false);
+
+    var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
+    var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
+    var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
+    var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
+
+    // Look for events within 1 step of the playhead.
+    var eventsAtPos:Array<SongEventData> = SongDataUtils.getEventsInTimeRange(currentSongChartEventData, playheadPosSnappedMs,
+      playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
+    eventsAtPos = SongDataUtils.getEventsWithKind(eventsAtPos, ['FocusCamera']);
+
+    if (eventsAtPos.length == 0 && !removeEventInstead)
+    {
+      trace('Placing event ${isOpponent}');
+      var newEventData:SongEventData = new SongEventData(playheadPosSnappedMs, 'FocusCamera',
+        {
+          char: isOpponent ? 1 : 0,
+        });
+      performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL));
+    }
+    else if (removeEventInstead)
+    {
+      trace('Removing existing event at position.');
+      performCommand(new RemoveEventsCommand(eventsAtPos));
+    }
+    else
+    {
+      trace('Already an event there.');
+    }
+  }
+
   function updatePlayheadGhostHoldNotes():Void
   {
     // Ensure all the ghost hold notes exist.