This commit is contained in:
Lasercar 2025-04-05 06:12:02 +10:00 committed by GitHub
commit 439955a434
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 173 additions and 2 deletions
source/funkin

View file

@ -145,6 +145,64 @@ class SongDataUtils
});
}
/**
* Create an array of notes whose note data is mirrored.
* Does not mutate the original array.
*
* @param flip Flip the notes if the notes given are in both strumlines, so that result isn't inverted when mirrored.
* @param mirrorX Mirror along the X axis, aka the directions of the notes.
* @param mirrorY Mirror along the Y axis, aka the time of the notes. If mirror X is true it won't mirror along Y.
*/
public static function mirrorNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4, flip:Bool = false, mirrorX:Bool = true,
mirrorY:Bool = true):Array<SongNoteData>
{
var minTime = notes[0].time;
var maxTime = notes[0].time;
var minStrumline = notes[0].data;
var maxStrumline = notes[0].data;
for (note in notes)
{
// Find the maximum and minimum time and strumline positions
// I wish there was a better way of doing this
if (flip)
{
if (note.data < minStrumline) minStrumline = note.data;
else if (note.data > maxStrumline) maxStrumline = note.data;
}
if (note.time < minTime) minTime = note.time;
else if (note.time > maxTime) maxTime = note.time;
}
var timeDiff = minTime + (maxTime - minTime) / 2;
if (flip && minStrumline < strumlineSize && strumlineSize < maxStrumline)
{
// Flip the notes if one of the notes is on the other strum
// Otherwise they'll be inverted when mirrored
notes = flipNotes(notes);
}
return notes.map(function(note:SongNoteData):SongNoteData {
var newData = note.data;
var newTime = note.time;
if (mirrorX)
{
if (newData < strumlineSize) newData = strumlineSize - 1 - newData;
else
newData = strumlineSize + strumlineSize * 2 - 1 - newData;
}
// No need to do this if both are true, otherwise the note positions don't actually change and we waste our time
if (mirrorY && !mirrorX)
{
if (newTime < timeDiff) newTime += (timeDiff - newTime) * 2;
else if (newTime > timeDiff) newTime -= (newTime - timeDiff) * 2;
}
return new SongNoteData(newTime, newData, note.length, note.kind);
});
}
/**
* Prepare an array of notes to be used as the clipboard data.
*

View file

@ -53,6 +53,7 @@ import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
import funkin.ui.debug.charting.commands.FlipNotesCommand;
import funkin.ui.debug.charting.commands.MirrorNotesCommand;
import funkin.ui.debug.charting.commands.InvertSelectedItemsCommand;
import funkin.ui.debug.charting.commands.MoveEventsCommand;
import funkin.ui.debug.charting.commands.MoveItemsCommand;
@ -3399,7 +3400,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
currentScrollEase = scrollPositionInPixels;
if (FlxG.keys.pressed.ALT)
if (FlxG.keys.pressed.ALT && !FlxG.keys.pressed.CONTROL)
{
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
@ -5389,7 +5390,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleFileKeybinds():Void
{
// CTRL + N = New Chart
if (pressingControl() && FlxG.keys.justPressed.N && !isHaxeUIDialogOpen)
if (pressingControl()
&& FlxG.keys.justPressed.N
&& !isHaxeUIDialogOpen
&& !FlxG.keys.pressed.SHIFT
&& !FlxG.keys.pressed.ALT)
{
this.openWelcomeDialog(true);
}
@ -5522,6 +5527,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
performCommand(new FlipNotesCommand(currentNoteSelection));
}
// CTRL + SHIFT + M = Mirror Notes along X axis
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.M)
{
performCommand(new MirrorNotesCommand(currentNoteSelection, true, false, true, false));
}
// CTRL + ALT + M = Mirror Notes along the Y axis
else if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.justPressed.M)
{
performCommand(new MirrorNotesCommand(currentNoteSelection, true, false, false, true));
}
// CTRL + A = Select All Notes
if (pressingControl() && FlxG.keys.justPressed.A)
{

View file

@ -0,0 +1,97 @@
package funkin.ui.debug.charting.commands;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongDataUtils;
/**
* Command that mirrors a given array of notes on either or strumline individually,
* along either the X (note direction) axis or Y (note time) axis.
* Flip middle will only work when the given notes are in both strumlines - it's incompatible with individually mirroring the selection.
*/
@:nullSafety
@:access(funkin.ui.debug.charting.ChartEditorState)
class MirrorNotesCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData> = [];
var mirroredNotes:Array<SongNoteData> = [];
var mirrorX:Bool = true;
var mirrorY:Bool = true;
public function new(notes:Array<SongNoteData>, mirrorIndividually:Bool = true, flipMiddle:Bool = false, mirrorX:Bool = true, mirrorY:Bool = true)
{
this.notes = notes;
this.mirrorX = mirrorX;
this.mirrorY = mirrorY;
if (mirrorIndividually)
{
var playerNotes:Array<SongNoteData> = [];
var opponentNotes:Array<SongNoteData> = [];
// Sort the selection by the strumline positions and then mirror each individually
for (note in notes)
{
if (note.data < ChartEditorState.STRUMLINE_SIZE)
{
playerNotes.push(note);
}
else if (note.data >= ChartEditorState.STRUMLINE_SIZE)
{
opponentNotes.push(note);
}
}
if (playerNotes.length > 0)
{
this.mirroredNotes = mirroredNotes.concat(SongDataUtils.mirrorNotes(playerNotes, ChartEditorState.STRUMLINE_SIZE, flipMiddle, mirrorX, mirrorY));
}
if (opponentNotes.length > 0)
{
this.mirroredNotes = mirroredNotes.concat(SongDataUtils.mirrorNotes(opponentNotes, ChartEditorState.STRUMLINE_SIZE, flipMiddle, mirrorX, mirrorY));
}
}
else
this.mirroredNotes = SongDataUtils.mirrorNotes(notes, ChartEditorState.STRUMLINE_SIZE, flipMiddle, mirrorX, mirrorY);
}
public function execute(state:ChartEditorState):Void
{
// Delete the notes.
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
// Add the flipped notes.
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(mirroredNotes);
state.currentNoteSelection = mirroredNotes;
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, mirroredNotes);
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
state.currentNoteSelection = notes;
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0 && mirrorX || !mirrorX && mirrorY && notes.length > 1);
}
public function toString():String
{
var len:Int = notes.length;
return 'Mirror ${(notes.length > 1) ? '$len Notes' : 'Note'} on ${(mirrorX) ? 'X' : (mirrorY) ? 'Y' : 'huh?'} Axis';
}
}