Note overwrite on paste again

This commit is contained in:
Hyper_ 2024-11-02 22:28:40 -03:00
parent 9cb171a5b5
commit cfb27a5095
2 changed files with 20 additions and 42 deletions

View file

@ -16,7 +16,7 @@ class SongNoteDataUtils
* @param threshold Threshold in ms * @param threshold Threshold in ms
* @return Stacked notes * @return Stacked notes
*/ */
public static function listStackedNotes(notes:Array<SongNoteData>, threshold:Float = 20):Array<SongNoteData> public static function listStackedNotes(notes:Array<SongNoteData>, threshold:Float):Array<SongNoteData>
{ {
var stackedNotes:Array<SongNoteData> = []; var stackedNotes:Array<SongNoteData> = [];
@ -76,7 +76,7 @@ class SongNoteDataUtils
* @param threshold Threshold in ms. * @param threshold Threshold in ms.
* @return The unsorted resulting array. * @return The unsorted resulting array.
*/ */
public static function concatNoOverlap(notesA:Array<SongNoteData>, notesB:Array<SongNoteData>, threshold:Float = 20):Array<SongNoteData> public static function concatNoOverlap(notesA:Array<SongNoteData>, notesB:Array<SongNoteData>, threshold:Float):Array<SongNoteData>
{ {
if (notesA == null || notesA.length == 0) return notesB; if (notesA == null || notesA.length == 0) return notesB;
if (notesB == null || notesB.length == 0) return notesA; if (notesB == null || notesB.length == 0) return notesA;
@ -98,37 +98,38 @@ class SongNoteDataUtils
} }
/** /**
* Concatenates two arrays of notes but overwrites notes in `lhs` that are overlapped by notes from `rhs`. * Concatenates two arrays of notes but overwrites notes in `lhs` that are overlapped by notes in `rhs`.
* Hold notes are only overwritten by longer hold notes.
* This operation only modifies the second array and `overwrittenNotes`. * This operation only modifies the second array and `overwrittenNotes`.
* *
* @param lhs An array of notes * @param lhs An array of notes
* @param rhs An array of notes to concatenate into `lhs` * @param rhs An array of notes to concatenate into `lhs`
* @param overwrittenNotes An optional array that is modified in-place with the notes in `lhs` that were overwritten. * @param overwrittenNotes An optional array that is modified in-place with the notes in `lhs` that were overwritten.
* @param threshold Threshold in ms * @param threshold Threshold in ms.
* @return The resulting array, note that the added notes are placed at the end of the array. * @return The unsorted resulting array.
*/ */
public static function concatOverwrite(lhs:Array<SongNoteData>, rhs:Array<SongNoteData>, ?overwrittenNotes:Array<SongNoteData>, public static function concatOverwrite(lhs:Array<SongNoteData>, rhs:Array<SongNoteData>, ?overwrittenNotes:Array<SongNoteData>,
threshold:Float = 20):Array<SongNoteData> threshold:Float):Array<SongNoteData>
{ {
if (lhs == null || rhs == null || rhs.length == 0) return lhs; if (lhs == null || rhs == null || rhs.length == 0) return lhs;
if (lhs.length == 0) return rhs;
var result = lhs.copy(); var result = lhs.copy();
for (i in 0...rhs.length) for (i in 0...rhs.length)
{ {
if (rhs[i] == null) continue;
var noteB:SongNoteData = rhs[i]; var noteB:SongNoteData = rhs[i];
var hasOverlap:Bool = false; var hasOverlap:Bool = false;
// TODO: Since notes are generally sorted this could probably benefit of only cycling through notes in a certain range
for (j in 0...lhs.length) for (j in 0...lhs.length)
{ {
var noteA:SongNoteData = lhs[j]; var noteA:SongNoteData = lhs[j];
if (doNotesStack(noteA, noteB, threshold)) if (doNotesStack(noteA, noteB, threshold))
{ {
if (noteA.length < noteB.length || !noteEquals(noteA, noteB)) if (noteA.length <= noteB.length)
{ {
overwrittenNotes?.push(result[j].clone()); overwrittenNotes?.push(result[j].clone());
result[j] = noteB; result[j] = noteB;
rhs[i] = null;
} }
hasOverlap = true; hasOverlap = true;
break; break;
@ -137,7 +138,6 @@ class SongNoteDataUtils
if (!hasOverlap) result.push(noteB); if (!hasOverlap) result.push(noteB);
} }
rhs = rhs.filterNull();
return result; return result;
} }
@ -151,24 +151,4 @@ class SongNoteDataUtils
// TODO: Make this function inline again when I'm done debugging. // TODO: Make this function inline again when I'm done debugging.
return noteA.data == noteB.data && Math.ffloor(Math.abs(noteA.time - noteB.time)) <= threshold; return noteA.data == noteB.data && Math.ffloor(Math.abs(noteA.time - noteB.time)) <= threshold;
} }
// This is replacing SongNoteData's equals operator because for some reason its params check is unreliable.
static function noteEquals(note:SongNoteData, other:SongNoteData):Bool
{
if (note == null) return other == null;
if (other == null) return false;
// TESTME: These checks seem redundant when get_kind already returns null if it's an empty string.
/*if (noteA.kind == null)
{
if (other.kind != null) return false;
}
else
{
if (other.kind == null) return false;
}*/
// params check is unreliable and doNotesStack already checks data
return note.time == other.time && note.length == other.length && note.kind == other.kind;
}
} }

View file

@ -4,6 +4,7 @@ import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongDataUtils; import funkin.data.song.SongDataUtils;
import funkin.data.song.SongDataUtils.SongClipboardItems; import funkin.data.song.SongDataUtils.SongClipboardItems;
import funkin.data.song.SongNoteDataUtils;
import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.debug.charting.ChartEditorState;
/** /**
@ -17,6 +18,7 @@ class PasteItemsCommand implements ChartEditorCommand
// Notes we added and removed with this command, for undo. // Notes we added and removed with this command, for undo.
var addedNotes:Array<SongNoteData> = []; var addedNotes:Array<SongNoteData> = [];
var addedEvents:Array<SongEventData> = []; var addedEvents:Array<SongEventData> = [];
var removedNotes:Array<SongNoteData> = [];
public function new(targetTimestamp:Float) public function new(targetTimestamp:Float)
{ {
@ -41,15 +43,12 @@ class PasteItemsCommand implements ChartEditorCommand
addedNotes = SongDataUtils.clampSongNoteData(addedNotes, 0.0, msCutoff); addedNotes = SongDataUtils.clampSongNoteData(addedNotes, 0.0, msCutoff);
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp)); addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
addedEvents = SongDataUtils.clampSongEventData(addedEvents, 0.0, msCutoff); addedEvents = SongDataUtils.clampSongEventData(addedEvents, 0.0, msCutoff);
var removedNotes = addedNotes.copy();
state.currentSongChartNoteData = SongDataUtils.addNotes(state.currentSongChartNoteData, addedNotes); state.currentSongChartNoteData = SongNoteDataUtils.concatOverwrite(state.currentSongChartNoteData, addedNotes, removedNotes,
// SongNoteDataUtils.concatOverwrite(state.currentSongChartNoteData, addedNotes, removedNotes, ChartEditorState.stackNoteThreshold);
// ChartEditorState.stackNoteThreshold);
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents); state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
state.currentNoteSelection = removedNotes.copy(); state.currentNoteSelection = addedNotes.copy();
state.currentEventSelection = addedEvents.copy(); state.currentEventSelection = addedEvents.copy();
removedNotes = SongDataUtils.subtractNotes(removedNotes, addedNotes);
state.saveDataDirty = true; state.saveDataDirty = true;
state.noteDisplayDirty = true; state.noteDisplayDirty = true;
@ -58,8 +57,7 @@ class PasteItemsCommand implements ChartEditorCommand
state.sortChartData(); state.sortChartData();
// FIXME: execute() is reused as a redo function so these messages show up even when not actually pasting // FIXME: execute() is reused as a redo function so these messages show up even when not actually pasting
if (addedNotes.length == 0) state.error('Paste Failed', 'All notes would overlap already placed notes.') if (removedNotes.length > 0) state.warning('Paste Successful', 'However overlapped notes were overwritten.');
else if (removedNotes.length > 0) state.warning('Paste Successful', 'However overlapping notes were ignored.');
else else
state.success('Paste Successful', 'Successfully pasted clipboard contents.'); state.success('Paste Successful', 'Successfully pasted clipboard contents.');
} }
@ -68,9 +66,9 @@ class PasteItemsCommand implements ChartEditorCommand
{ {
state.playSound(Paths.sound('chartingSounds/undo')); state.playSound(Paths.sound('chartingSounds/undo'));
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes); state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes).concat(removedNotes);
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents); state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents);
state.currentNoteSelection = []; state.currentNoteSelection = removedNotes.copy();
state.currentEventSelection = []; state.currentEventSelection = [];
state.saveDataDirty = true; state.saveDataDirty = true;
@ -83,7 +81,7 @@ class PasteItemsCommand implements ChartEditorCommand
public function shouldAddToHistory(state:ChartEditorState):Bool public function shouldAddToHistory(state:ChartEditorState):Bool
{ {
// This command is undoable. Add to the history if we actually performed an action. // This command is undoable. Add to the history if we actually performed an action.
return (addedNotes.length > 0 || addedEvents.length > 0); return (addedNotes.length > 0 || addedEvents.length > 0 || removedNotes.length > 0);
} }
public function toString():String public function toString():String