diff --git a/Project.xml b/Project.xml index 74da3b749..f5d506688 100644 --- a/Project.xml +++ b/Project.xml @@ -111,7 +111,7 @@ <haxelib name="tink_json" /> <!-- JSON parsing (DEPRECATED) --> <haxelib name="thx.semver" /> <!-- Version string handling --> - <haxelib name="hmm" if="debug" /> <!-- Read library version data at compile time --> + <haxelib name="hmm" /> <!-- Read library version data at compile time so it can be baked into logs --> <haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support --> <!--Disable the Flixel core focus lost screen--> @@ -131,8 +131,8 @@ <haxedef name="message.reporting" value="pretty" /> <!-- _________________________________ Custom _______________________________ --> - <!-- Disable trace() calls in release builds to bump up performance. --> - <haxeflag name="--no-traces" unless="debug" /> + <!-- Disable trace() calls in release builds to bump up performance. + <haxeflag name="- -no-traces" unless="debug" />--> <!-- HScript relies heavily on Reflection, which means we can't use DCE. --> <haxeflag name="-dce no" /> <!-- Ensure all Funkin' classes are available at runtime. --> diff --git a/assets b/assets index 7e31e86db..f1e42601b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7e31e86dbeec3df5076895dedc62a45cc14d66e1 +Subproject commit f1e42601b6ea2026c6e2f4627c5738bfb8b7b524 diff --git a/hmm.json b/hmm.json index 8d05a7a2e..67ba4d18d 100644 --- a/hmm.json +++ b/hmm.json @@ -107,7 +107,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "737b86f121cdc90358d59e2e527934f267c94a2c", + "ref": "fff39ba6fc64969cd51987ef7491d9345043dc5d", "url": "https://github.com/FunkinCrew/lime" }, { diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 708881429..07ea38741 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -93,7 +93,7 @@ class SongMetadata implements ICloneable<SongMetadata> result.version = this.version; result.timeFormat = this.timeFormat; result.divisions = this.divisions; - result.offsets = this.offsets.clone(); + result.offsets = this.offsets != null ? this.offsets.clone() : new SongOffsets(); // if no song offsets found (aka null), so just create new ones result.timeChanges = this.timeChanges.deepClone(); result.looped = this.looped; result.playData = this.playData.clone(); diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 309676884..275106f3a 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -273,7 +273,7 @@ class SongDataUtils } /** - * Filter a list of notes to only include notes whose data is within the given range. + * Filter a list of notes to only include notes whose data is within the given range, inclusive. */ public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData> { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 995797dd1..9f98d3b04 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2270,8 +2270,10 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Calculate the input latency (do this as late as possible). - var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0; - trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); + // trace('Compare: ${PreciseInputManager.getCurrentTimestamp()} - ${input.timestamp}'); + var inputLatencyNs:Int64 = PreciseInputManager.getCurrentTimestamp() - input.timestamp; + var inputLatencyMs:Float = inputLatencyNs.toFloat() / Constants.NS_PER_MS; + // trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); // Get the offset and compensate for input latency. // Round inward (trim remainder) for consistency. diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 9e5de6143..cde068f42 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -176,6 +176,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta difficulty.generatedBy = metadata.generatedBy; difficulty.offsets = metadata.offsets; + difficulty.difficultyRating = metadata.playData.ratings.get(diffId) ?? 0; + difficulty.album = metadata.playData.album; + difficulty.stage = metadata.playData.stage; difficulty.noteStyle = metadata.playData.noteStyle; @@ -405,6 +408,9 @@ class SongDifficulty public var scrollSpeed:Float = Constants.DEFAULT_SCROLLSPEED; + public var difficultyRating:Int = 0; + public var album:Null<String> = null; + public function new(song:Song, diffId:String, variation:String) { this.song = song; diff --git a/source/funkin/ui/AtlasText.hx b/source/funkin/ui/AtlasText.hx index fea09de54..186d87c2a 100644 --- a/source/funkin/ui/AtlasText.hx +++ b/source/funkin/ui/AtlasText.hx @@ -274,4 +274,5 @@ enum abstract AtlasFont(String) from String to String { var DEFAULT = "default"; var BOLD = "bold"; + var FREEPLAY_CLEAR = "freeplay-clear"; } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 33bba450f..05ac983ca 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -63,6 +63,7 @@ import funkin.ui.debug.charting.commands.AddNotesCommand; import funkin.ui.debug.charting.commands.ChartEditorCommand; import funkin.ui.debug.charting.commands.ChartEditorCommand; import funkin.ui.debug.charting.commands.CutItemsCommand; +import funkin.ui.debug.charting.commands.CopyItemsCommand; import funkin.ui.debug.charting.commands.DeselectAllItemsCommand; import funkin.ui.debug.charting.commands.DeselectItemsCommand; import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand; @@ -106,6 +107,7 @@ import haxe.ui.components.Slider; import haxe.ui.components.TextField; import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.Frame; +import haxe.ui.containers.Box; import haxe.ui.containers.menus.Menu; import haxe.ui.containers.menus.MenuBar; import haxe.ui.containers.menus.MenuItem; @@ -193,10 +195,40 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ public static final PLAYBAR_HEIGHT:Int = 48; + /** + * The height of the note selection buttons above the grid. + */ + public static final NOTE_SELECT_BUTTON_HEIGHT:Int = 24; + /** * The amount of padding between the menu bar and the chart grid when fully scrolled up. */ - public static final GRID_TOP_PAD:Int = 8; + public static final GRID_TOP_PAD:Int = NOTE_SELECT_BUTTON_HEIGHT + 12; + + /** + * The initial vertical position of the chart grid. + */ + public static final GRID_INITIAL_Y_POS:Int = MENU_BAR_HEIGHT + GRID_TOP_PAD; + + /** + * The X position of the note preview area. + */ + public static final NOTE_PREVIEW_X_POS:Int = 350; + + /** + * The Y position of the note preview area. + */ + public static final NOTE_PREVIEW_Y_POS:Int = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 4; + + /** + * The X position of the note grid. + */ + public static var GRID_X_POS(get, never):Float; + + static function get_GRID_X_POS():Float + { + return FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; + } // Colors // Background color tint. @@ -341,21 +373,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { if (isViewDownscroll) { - gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS); measureTicks.y = gridTiledSprite.y; } else { - gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS); measureTicks.y = gridTiledSprite.y; if (audioVisGroup != null && audioVisGroup.playerVis != null) { - audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, MENU_BAR_HEIGHT); + audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS); } if (audioVisGroup != null && audioVisGroup.opponentVis != null) { - audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, MENU_BAR_HEIGHT); + audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS); } } } @@ -427,7 +459,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.playheadPositionInPixels = value; // Move the playhead sprite to the correct position. - gridPlayhead.y = this.playheadPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridPlayhead.y = this.playheadPositionInPixels + GRID_INITIAL_Y_POS; return this.playheadPositionInPixels; } @@ -1684,6 +1716,24 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var playbarEnd:Button; + /** + * The button above the grid that selects all notes on the opponent's side. + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectOpponent:Button; + + /** + * The button above the grid that selects all notes on the player's side. + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectPlayer:Button; + + /** + * The button above the grid that selects all song events. + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectEvent:Button; + /** * RENDER OBJECTS */ @@ -2119,8 +2169,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (gridBitmap == null) throw 'ERROR: Tried to build grid, but gridBitmap is null! Check ChartEditorThemeHandler.updateTheme().'; gridTiledSprite = new FlxTiledSprite(gridBitmap, gridBitmap.width, 1000, false, true); - gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid. - gridTiledSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; // Push down to account for the menu bar. + gridTiledSprite.x = GRID_X_POS; // Center the grid. + gridTiledSprite.y = GRID_INITIAL_Y_POS; // Push down to account for the menu bar. add(gridTiledSprite); gridTiledSprite.zIndex = 10; @@ -2151,9 +2201,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(gridPlayhead); gridPlayhead.zIndex = 30; - var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH); - var playheadBaseYPos:Float = MENU_BAR_HEIGHT + GRID_TOP_PAD; - gridPlayhead.setPosition(gridTiledSprite.x, playheadBaseYPos); + var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2); + var playheadBaseYPos:Float = GRID_INITIAL_Y_POS; + gridPlayhead.setPosition(GRID_X_POS, playheadBaseYPos); var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR); playheadSprite.x = -PLAYHEAD_SCROLL_AREA_WIDTH; playheadSprite.y = 0; @@ -2195,10 +2245,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function buildNotePreview():Void { - var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - PLAYBAR_HEIGHT - GRID_TOP_PAD - GRID_TOP_PAD; - notePreview = new ChartEditorNotePreview(height); - notePreview.x = 320; - notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; + var playbarHeightWithPad = PLAYBAR_HEIGHT + 10; + var notePreviewHeight:Int = FlxG.height - NOTE_PREVIEW_Y_POS - playbarHeightWithPad; + notePreview = new ChartEditorNotePreview(notePreviewHeight); + notePreview.x = NOTE_PREVIEW_X_POS; + notePreview.y = NOTE_PREVIEW_Y_POS; add(notePreview); if (notePreviewViewport == null) throw 'ERROR: Tried to build note preview, but notePreviewViewport is null! Check ChartEditorThemeHandler.updateTheme().'; @@ -2384,6 +2435,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(playbarHeadLayout); + // Little text that shows up when you copy something. txtCopyNotif = new FlxText(0, 0, 0, '', 24); txtCopyNotif.setBorderStyle(OUTLINE, 0xFF074809, 1); txtCopyNotif.color = 0xFF52FF77; @@ -2408,6 +2460,77 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.openCharacterDropdown(CharacterType.BF, true); } }); + + buttonSelectOpponent = new Button(); + buttonSelectOpponent.allowFocus = false; + buttonSelectOpponent.text = "Opponent"; // Default text. + buttonSelectOpponent.x = GRID_X_POS; + buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 8; + buttonSelectOpponent.width = GRID_SIZE * 4; + buttonSelectOpponent.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectOpponent.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection."; + buttonSelectOpponent.zIndex = 110; + add(buttonSelectOpponent); + + buttonSelectOpponent.onClick = (_) -> { + var notesToSelect:Array<SongNoteData> = currentSongChartNoteData; + notesToSelect = SongDataUtils.getNotesInDataRange(notesToSelect, STRUMLINE_SIZE, STRUMLINE_SIZE * 2 - 1); + if (FlxG.keys.pressed.SHIFT) + { + performCommand(new SelectItemsCommand(notesToSelect, [])); + } + else + { + performCommand(new SetItemSelectionCommand(notesToSelect, [])); + } + } + + buttonSelectPlayer = new Button(); + buttonSelectPlayer.allowFocus = false; + buttonSelectPlayer.text = "Player"; // Default text. + buttonSelectPlayer.x = buttonSelectOpponent.x + buttonSelectOpponent.width; + buttonSelectPlayer.y = buttonSelectOpponent.y; + buttonSelectPlayer.width = GRID_SIZE * 4; + buttonSelectPlayer.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectPlayer.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection."; + buttonSelectPlayer.zIndex = 110; + add(buttonSelectPlayer); + + buttonSelectPlayer.onClick = (_) -> { + var notesToSelect:Array<SongNoteData> = currentSongChartNoteData; + notesToSelect = SongDataUtils.getNotesInDataRange(notesToSelect, 0, STRUMLINE_SIZE - 1); + if (FlxG.keys.pressed.SHIFT) + { + performCommand(new SelectItemsCommand(notesToSelect, [])); + } + else + { + performCommand(new SetItemSelectionCommand(notesToSelect, [])); + } + } + + buttonSelectEvent = new Button(); + buttonSelectEvent.allowFocus = false; + buttonSelectEvent.icon = Paths.image('ui/chart-editor/events/Default'); + buttonSelectEvent.iconPosition = "top"; + buttonSelectEvent.x = buttonSelectPlayer.x + buttonSelectPlayer.width; + buttonSelectEvent.y = buttonSelectPlayer.y; + buttonSelectEvent.width = GRID_SIZE; + buttonSelectEvent.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectEvent.tooltip = "Click to set selection to all events.\nShift-click to add all events to selection."; + buttonSelectEvent.zIndex = 110; + add(buttonSelectEvent); + + buttonSelectEvent.onClick = (_) -> { + if (FlxG.keys.pressed.SHIFT) + { + performCommand(new SelectItemsCommand([], currentSongChartEventData)); + } + else + { + performCommand(new SetItemSelectionCommand([], currentSongChartEventData)); + } + } } /** @@ -2534,11 +2657,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemFlipNotes.onClick = _ -> performCommand(new FlipNotesCommand(currentNoteSelection)); - menubarItemSelectAll.onClick = _ -> performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection)); + menubarItemSelectAllNotes.onClick = _ -> performCommand(new SelectAllItemsCommand(true, false)); - menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection)); + menubarItemSelectAllEvents.onClick = _ -> performCommand(new SelectAllItemsCommand(false, true)); - menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand()); + + menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand()); menubarItemPlaytestFull.onClick = _ -> testSongInPlayState(false); menubarItemPlaytestMinimal.onClick = _ -> testSongInPlayState(true); @@ -3100,7 +3225,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Resolve an issue where dragging an event too far would cause it to be hidden. var isSelectedAndDragged = currentEventSelection.fastContains(eventSprite.eventData) && (dragTargetCurrentStep != 0); - if ((eventSprite.isEventVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD) + if ((eventSprite.isEventVisible(FlxG.height - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT) && currentSongChartEventData.fastContains(eventSprite.eventData)) || isSelectedAndDragged) { @@ -3662,7 +3787,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Clicked on the playhead scroll area. // Move the playhead to the cursor position. - this.playheadPositionInPixels = FlxG.mouse.screenY - MENU_BAR_HEIGHT - GRID_TOP_PAD; + this.playheadPositionInPixels = FlxG.mouse.screenY - (GRID_INITIAL_Y_POS); moveSongToScrollPosition(); // Cursor should be a grabby hand. @@ -3755,7 +3880,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // Set the selection. - performCommand(new SetItemSelectionCommand(notesToSelect, eventsToSelect, currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand(notesToSelect, eventsToSelect)); } } else @@ -3768,7 +3893,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); if (shouldDeselect) { - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } } @@ -3891,17 +4016,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (highlightedNote != null && highlightedNote.noteData != null) { // Click a note to select it. - performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [])); } else if (highlightedEvent != null && highlightedEvent.eventData != null) { // Click an event to select it. - performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData])); } else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) { // Click a hold note to select it. - performCommand(new SetItemSelectionCommand([highlightedHoldNote.noteData], [], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([highlightedHoldNote.noteData], [])); } else { @@ -3909,7 +4034,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); if (shouldDeselect) { - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } } @@ -3924,7 +4049,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); if (shouldDeselect) { - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } } @@ -4193,7 +4318,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // If you click an unselected note, and aren't holding Control, deselect everything else. - performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [])); } } else if (highlightedEvent != null && highlightedEvent.eventData != null) @@ -4206,7 +4331,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // If you click an unselected event, and aren't holding Control, deselect everything else. - performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData])); } } else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) @@ -4559,7 +4684,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (currentSongMetadata.playData.characters.player != charPlayer.charId) { - if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player; + if (healthIconBF != null) + { + healthIconBF.characterId = currentSongMetadata.playData.characters.player; + } charPlayer.loadCharacter(currentSongMetadata.playData.characters.player); charPlayer.characterType = CharacterType.BF; @@ -4595,7 +4723,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (currentSongMetadata.playData.characters.opponent != charPlayer.charId) { - if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent; + if (healthIconDad != null) + { + healthIconDad.characterId = currentSongMetadata.playData.characters.opponent; + } charPlayer.loadCharacter(currentSongMetadata.playData.characters.opponent); charPlayer.characterType = CharacterType.DAD; @@ -4613,6 +4744,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + function handleSelectionButtons():Void + { + // Make sure buttons are never nudged out of the correct spot. + // TODO: Why do these even move in the first place? The camera never moves, LOL. + buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2; + buttonSelectPlayer.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2; + buttonSelectEvent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2; + } + /** * Handles display elements for the playbar at the bottom. */ @@ -4721,11 +4861,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState healthIconBF.size *= 0.5; // Make the icon smaller in Chart Editor. healthIconBF.flipX = !healthIconBF.flipX; // BF faces the other way. } + if (buttonSelectPlayer != null) + { + buttonSelectPlayer.text = charDataBF?.name ?? 'Player'; + } if (healthIconDad != null) { healthIconDad.configure(charDataDad?.healthIcon); healthIconDad.size *= 0.5; // Make the icon smaller in Chart Editor. } + if (buttonSelectOpponent != null) + { + buttonSelectOpponent.text = charDataDad?.name ?? 'Opponent'; + } healthIconsDirty = false; } @@ -4733,15 +4881,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (healthIconBF != null) { // Base X position to the right of the grid. - healthIconBF.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x + gridTiledSprite.width + 45 - (healthIconBF.width / 2)); - healthIconBF.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconBF.height / 2)); + var xOffset = 45 - (healthIconBF.width / 2); + healthIconBF.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS + gridTiledSprite.width + xOffset); + var yOffset = 30 - (healthIconBF.height / 2); + healthIconBF.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; } // Visibly center the Dad health icon. if (healthIconDad != null) { - healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 75 - (healthIconDad.width / 2)); - healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2)); + var xOffset = 45 + (healthIconDad.width / 2); + healthIconDad.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS - xOffset); + var yOffset = 30 - (healthIconDad.height / 2); + healthIconDad.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; } } @@ -4823,54 +4975,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // CTRL + C = Copy if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.C) { - if (currentNoteSelection.length > 0) - { - txtCopyNotif.visible = true; - txtCopyNotif.text = "Copied " + currentNoteSelection.length + " notes to clipboard"; - txtCopyNotif.x = FlxG.mouse.x - (txtCopyNotif.width / 2); - txtCopyNotif.y = FlxG.mouse.y - 16; - FlxTween.tween(txtCopyNotif, {y: txtCopyNotif.y - 32}, 0.5, - { - type: FlxTween.ONESHOT, - ease: FlxEase.quadOut, - onComplete: function(_) { - txtCopyNotif.visible = false; - } - }); - - for (note in renderedNotes.members) - { - if (isNoteSelected(note.noteData)) - { - FlxTween.globalManager.cancelTweensOf(note); - FlxTween.globalManager.cancelTweensOf(note.scale); - note.playNoteAnimation(); - var prevX:Float = note.scale.x; - var prevY:Float = note.scale.y; - - note.scale.x *= 1.2; - note.scale.y *= 1.2; - - note.angle = FlxG.random.bool() ? -10 : 10; - FlxTween.tween(note, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut}); - - FlxTween.tween(note.scale, {"y": prevX, "x": prevY}, 0.7, - { - ease: FlxEase.elasticOut, - onComplete: function(_) { - note.playNoteAnimation(); - } - }); - } - } - } - - // We don't need a command for this since we can't undo it. - SongDataUtils.writeItemsToClipboard( - { - notes: SongDataUtils.buildNoteClipboard(currentNoteSelection), - events: SongDataUtils.buildEventClipboard(currentEventSelection), - }); + performCommand(new CopyItemsCommand(currentNoteSelection, currentEventSelection)); } // CTRL + X = Cut @@ -4931,25 +5036,50 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new FlipNotesCommand(currentNoteSelection)); } - // CTRL + A = Select All + // CTRL + A = Select All Notes if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.A) { // Select all items. - performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection)); + if (FlxG.keys.pressed.ALT) + { + if (FlxG.keys.pressed.SHIFT) + { + // CTRL + ALT + SHIFT + A = Append All Events to Selection + performCommand(new SelectItemsCommand([], currentSongChartEventData)); + } + else + { + // CTRL + ALT + A = Set Selection to All Events + performCommand(new SelectAllItemsCommand(false, true)); + } + } + else + { + if (FlxG.keys.pressed.SHIFT) + { + // CTRL + SHIFT + A = Append All Notes to Selection + performCommand(new SelectItemsCommand(currentSongChartNoteData, [])); + } + else + { + // CTRL + A = Set Selection to All Notes + performCommand(new SelectAllItemsCommand(true, false)); + } + } } // CTRL + I = Select Inverse if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.I) { // Select unselected items and deselect selected items. - performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new InvertSelectedItemsCommand()); } // CTRL + D = Select None if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.D) { // Deselect all items. - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } @@ -5113,13 +5243,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState * Perform (or redo) a command, then add it to the undo stack. * * @param command The command to perform. - * @param purgeRedoStack If true, the redo stack will be cleared. + * @param purgeRedoStack If `true`, the redo stack will be cleared after performing the command. */ function performCommand(command:ChartEditorCommand, purgeRedoStack:Bool = true):Void { command.execute(this); - undoHistory.push(command); - commandHistoryDirty = true; + if (command.shouldAddToHistory(this)) + { + undoHistory.push(command); + commandHistoryDirty = true; + } if (purgeRedoStack) redoHistory = []; } @@ -5130,6 +5263,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function undoCommand(command:ChartEditorCommand):Void { command.undo(this); + // Note, if we are undoing a command, it should already be in the history, + // therefore we don't need to check `shouldAddToHistory(state)` redoHistory.push(command); commandHistoryDirty = true; } @@ -5467,6 +5602,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (displayAutosavePopup) { displayAutosavePopup = false; + #if sys Toolkit.callLater(() -> { var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]); this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [ @@ -5476,6 +5612,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } ]); }); + #else + // TODO: No auto-save on HTML5? + #end } moveSongToScrollPosition(); @@ -5778,6 +5917,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState @:privateAccess ChartEditorNoteSprite.noteFrameCollection = null; + + // Stop the music. + welcomeMusic.destroy(); + audioInstTrack.destroy(); + audioVocalTrackGroup.destroy(); } function applyCanQuickSave():Void diff --git a/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx b/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx index 9bf8ec3db..a878ee687 100644 --- a/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx @@ -59,6 +59,12 @@ class AddEventsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (events.length > 0); + } + public function toString():String { var len:Int = events.length; diff --git a/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx b/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx index ce4e73ea2..ea984c82d 100644 --- a/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx @@ -59,6 +59,12 @@ class AddNotesCommand implements ChartEditorCommand 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); + } + public function toString():String { if (notes.length == 1) diff --git a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx index ea821afa9..bd832fab3 100644 --- a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx @@ -64,6 +64,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (targetBPM != previousBPM); + } + public function toString():String { return 'Change Starting BPM to ${targetBPM}'; diff --git a/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx b/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx index cfa169908..1fa86ad94 100644 --- a/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx @@ -6,6 +6,8 @@ package funkin.ui.debug.charting.commands; * * To make a functionality compatible with the undo/redo history, create a new class * that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())` + * + * NOTE: Make the constructor very simple, as it may be called without executing by the command palette. */ interface ChartEditorCommand { @@ -22,6 +24,15 @@ interface ChartEditorCommand */ public function undo(state:ChartEditorState):Void; + /** + * Return whether or not this command should be appended to the in the undo/redo history. + * Generally this should be true, it should only be false if the command is minor and non-destructive, + * like copying to the clipboard. + * + * Called after `execute()` is performed. + */ + public function shouldAddToHistory(state:ChartEditorState):Bool; + /** * Get a short description of the action (for the UI). * For example, return `Add Left Note` to display `Undo Add Left Note` in the menu. diff --git a/source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx b/source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx new file mode 100644 index 000000000..4361f867f --- /dev/null +++ b/source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx @@ -0,0 +1,144 @@ +package funkin.ui.debug.charting.commands; + +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongDataUtils; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; + +/** + * Command that copies a given set of notes and song events to the clipboard, + * without deleting them from the chart editor. + */ +@:nullSafety +@:access(funkin.ui.debug.charting.ChartEditorState) +class CopyItemsCommand implements ChartEditorCommand +{ + var notes:Array<SongNoteData>; + var events:Array<SongEventData>; + + public function new(notes:Array<SongNoteData>, events:Array<SongEventData>) + { + this.notes = notes; + this.events = events; + } + + public function execute(state:ChartEditorState):Void + { + // Calculate a single time offset for all the notes and events. + var timeOffset:Null<Int> = state.currentNoteSelection.length > 0 ? Std.int(state.currentNoteSelection[0].time) : null; + if (state.currentEventSelection.length > 0) + { + if (timeOffset == null || state.currentEventSelection[0].time < timeOffset) + { + timeOffset = Std.int(state.currentEventSelection[0].time); + } + } + + SongDataUtils.writeItemsToClipboard( + { + notes: SongDataUtils.buildNoteClipboard(state.currentNoteSelection, timeOffset), + events: SongDataUtils.buildEventClipboard(state.currentEventSelection, timeOffset), + }); + + performVisuals(state); + } + + function performVisuals(state:ChartEditorState):Void + { + if (state.currentNoteSelection.length > 0) + { + // Display the "Copied Notes" text. + if (state.txtCopyNotif != null) + { + state.txtCopyNotif.visible = true; + state.txtCopyNotif.text = "Copied " + state.currentNoteSelection.length + " notes to clipboard"; + state.txtCopyNotif.x = FlxG.mouse.x - (state.txtCopyNotif.width / 2); + state.txtCopyNotif.y = FlxG.mouse.y - 16; + FlxTween.tween(state.txtCopyNotif, {y: state.txtCopyNotif.y - 32}, 0.5, + { + type: FlxTween.ONESHOT, + ease: FlxEase.quadOut, + onComplete: function(_) { + state.txtCopyNotif.visible = false; + } + }); + } + + // Wiggle the notes. + for (note in state.renderedNotes.members) + { + if (state.isNoteSelected(note.noteData)) + { + FlxTween.globalManager.cancelTweensOf(note); + FlxTween.globalManager.cancelTweensOf(note.scale); + note.playNoteAnimation(); + var prevX:Float = note.scale.x; + var prevY:Float = note.scale.y; + + note.scale.x *= 1.2; + note.scale.y *= 1.2; + + note.angle = FlxG.random.bool() ? -10 : 10; + FlxTween.tween(note, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut}); + + FlxTween.tween(note.scale, {"y": prevX, "x": prevY}, 0.7, + { + ease: FlxEase.elasticOut, + onComplete: function(_) { + note.playNoteAnimation(); + } + }); + } + } + + // Wiggle the events. + for (event in state.renderedEvents.members) + { + if (state.isEventSelected(event.eventData)) + { + FlxTween.globalManager.cancelTweensOf(event); + FlxTween.globalManager.cancelTweensOf(event.scale); + event.playAnimation(); + var prevX:Float = event.scale.x; + var prevY:Float = event.scale.y; + + event.scale.x *= 1.2; + event.scale.y *= 1.2; + + event.angle = FlxG.random.bool() ? -10 : 10; + FlxTween.tween(event, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut}); + + FlxTween.tween(event.scale, {"y": prevX, "x": prevY}, 0.7, + { + ease: FlxEase.elasticOut, + onComplete: function(_) { + event.playAnimation(); + } + }); + } + } + } + } + + public function undo(state:ChartEditorState):Void + { + // This command is not undoable. Do nothing. + } + + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is not undoable. Don't add it to the history. + return false; + } + + public function toString():String + { + var len:Int = notes.length + events.length; + + if (notes.length == 0) return 'Copy $len Events to Clipboard'; + else if (events.length == 0) return 'Copy $len Notes to Clipboard'; + else + return 'Copy $len Items to Clipboard'; + } +} diff --git a/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx b/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx index d0301b1ec..6cf674f80 100644 --- a/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx @@ -56,6 +56,12 @@ class CutItemsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Always add it to the history. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { var len:Int = notes.length + events.length; diff --git a/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx b/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx index cbde0ab3d..5bfef76cc 100644 --- a/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx @@ -10,17 +10,16 @@ import funkin.data.song.SongData.SongEventData; @:access(funkin.ui.debug.charting.ChartEditorState) class DeselectAllItemsCommand implements ChartEditorCommand { - var previousNoteSelection:Array<SongNoteData>; - var previousEventSelection:Array<SongEventData>; + var previousNoteSelection:Array<SongNoteData> = []; + var previousEventSelection:Array<SongEventData> = []; - public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } + public function new() {} public function execute(state:ChartEditorState):Void { + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + state.currentNoteSelection = []; state.currentEventSelection = []; @@ -35,6 +34,12 @@ class DeselectAllItemsCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (previousNoteSelection.length > 0 || previousEventSelection.length > 0); + } + public function toString():String { return 'Deselect All Items'; diff --git a/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx index d679b5363..6a115a26a 100644 --- a/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx @@ -45,16 +45,27 @@ class DeselectItemsCommand implements ChartEditorCommand state.notePreviewDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { - var noteCount = notes.length + events.length; + var isPlural = (notes.length + events.length) > 1; + var notesOnly = (notes.length > 0 && events.length == 0); + var eventsOnly = (notes.length == 0 && events.length > 0); - if (noteCount == 1) + if (notesOnly) { - var dir:String = notes[0].getDirectionName(); - return 'Deselect $dir Items'; + return 'Deselect ${notes.length} ${isPlural ? 'Notes' : 'Note'}'; + } + else if (eventsOnly) + { + return 'Deselect ${events.length} ${isPlural ? 'Events' : 'Event'}'; } - return 'Deselect ${noteCount} Items'; + return 'Deselect ${notes.length + events.length} Items'; } } diff --git a/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx b/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx index 62ffe63b9..3ef9f22d1 100644 --- a/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx @@ -54,6 +54,12 @@ class ExtendNoteLengthCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (oldLength != newLength); + } + public function toString():String { if (oldLength == 0) diff --git a/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx b/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx index da8ec7fbc..f54ffed15 100644 --- a/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx @@ -51,6 +51,12 @@ class FlipNotesCommand implements ChartEditorCommand 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); + } + public function toString():String { var len:Int = notes.length; diff --git a/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx b/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx index 6e37bcc03..d9a28f463 100644 --- a/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx @@ -12,19 +12,19 @@ import funkin.data.song.SongDataUtils; @:access(funkin.ui.debug.charting.ChartEditorState) class InvertSelectedItemsCommand implements ChartEditorCommand { - var previousNoteSelection:Array<SongNoteData>; - var previousEventSelection:Array<SongEventData>; + var previousNoteSelection:Array<SongNoteData> = []; + var previousEventSelection:Array<SongEventData> = []; - public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } + public function new() {} public function execute(state:ChartEditorState):Void { + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection); state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection); + state.noteDisplayDirty = true; } @@ -36,6 +36,12 @@ class InvertSelectedItemsCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (previousNoteSelection.length > 0 || previousEventSelection.length > 0); + } + public function toString():String { return 'Invert Selected Items'; diff --git a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx index 8331ed397..ed50ad33e 100644 --- a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx @@ -65,6 +65,12 @@ class MoveEventsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (events.length > 0); + } + public function toString():String { var len:Int = events.length; diff --git a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx index 9fac8a0c4..f44cb973a 100644 --- a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx @@ -88,6 +88,12 @@ class MoveItemsCommand implements ChartEditorCommand 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 || events.length > 0); + } + public function toString():String { var len:Int = notes.length + events.length; diff --git a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx index 0308d8fc8..51aeb5bbc 100644 --- a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx @@ -67,6 +67,12 @@ class MoveNotesCommand implements ChartEditorCommand 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); + } + public function toString():String { var len:Int = notes.length; diff --git a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx index 7e40bc49b..257db94b4 100644 --- a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx @@ -71,6 +71,12 @@ class PasteItemsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (addedNotes.length > 0 || addedEvents.length > 0); + } + public function toString():String { var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard(); diff --git a/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx index 7e620c210..b4d913607 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx @@ -48,6 +48,12 @@ class RemoveEventsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (events.length > 0); + } + public function toString():String { if (events.length == 1 && events[0] != null) diff --git a/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx index 77184209e..69317aff4 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx @@ -62,6 +62,12 @@ class RemoveItemsCommand implements ChartEditorCommand 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 || events.length > 0); + } + public function toString():String { return 'Remove ${notes.length + events.length} Items'; diff --git a/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx index e189be83e..4811f831d 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx @@ -50,6 +50,12 @@ class RemoveNotesCommand implements ChartEditorCommand 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); + } + public function toString():String { if (notes.length == 1 && notes[0] != null) diff --git a/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx index e1a4dceaa..f550e044b 100644 --- a/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx @@ -10,19 +10,25 @@ import funkin.data.song.SongData.SongEventData; @:access(funkin.ui.debug.charting.ChartEditorState) class SelectAllItemsCommand implements ChartEditorCommand { - var previousNoteSelection:Array<SongNoteData>; - var previousEventSelection:Array<SongEventData>; + var shouldSelectNotes:Bool; + var shouldSelectEvents:Bool; - public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>) + var previousNoteSelection:Array<SongNoteData> = []; + var previousEventSelection:Array<SongEventData> = []; + + public function new(shouldSelectNotes:Bool, shouldSelectEvents:Bool) { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; + this.shouldSelectNotes = shouldSelectNotes; + this.shouldSelectEvents = shouldSelectEvents; } public function execute(state:ChartEditorState):Void { - state.currentNoteSelection = state.currentSongChartNoteData; - state.currentEventSelection = state.currentSongChartEventData; + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + + state.currentNoteSelection = shouldSelectNotes ? state.currentSongChartNoteData : []; + state.currentEventSelection = shouldSelectEvents ? state.currentSongChartEventData : []; state.noteDisplayDirty = true; } @@ -35,8 +41,29 @@ class SelectAllItemsCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (state.currentNoteSelection.length > 0 || state.currentEventSelection.length > 0); + } + public function toString():String { - return 'Select All Items'; + if (shouldSelectNotes && !shouldSelectEvents) + { + return 'Select All Notes'; + } + else if (shouldSelectEvents && !shouldSelectNotes) + { + return 'Select All Events'; + } + else if (shouldSelectNotes && shouldSelectEvents) + { + return 'Select All Notes and Events'; + } + else + { + return 'Select Nothing (Huh?)'; + } } } diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index 49b2ba585..d6c5beeac 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -15,10 +15,10 @@ class SelectItemsCommand implements ChartEditorCommand var notes:Array<SongNoteData>; var events:Array<SongEventData>; - public function new(notes:Array<SongNoteData>, events:Array<SongEventData>) + public function new(?notes:Array<SongNoteData>, ?events:Array<SongEventData>) { - this.notes = notes; - this.events = events; + this.notes = notes ?? []; + this.events = events ?? []; } public function execute(state:ChartEditorState):Void @@ -72,6 +72,12 @@ class SelectItemsCommand implements ChartEditorCommand state.notePreviewDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { var len:Int = notes.length + events.length; diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 4725fd275..35a00e562 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -13,20 +13,20 @@ class SetItemSelectionCommand implements ChartEditorCommand { var notes:Array<SongNoteData>; var events:Array<SongEventData>; - var previousNoteSelection:Array<SongNoteData>; - var previousEventSelection:Array<SongEventData>; + var previousNoteSelection:Array<SongNoteData> = []; + var previousEventSelection:Array<SongEventData> = []; - public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, previousNoteSelection:Array<SongNoteData>, - previousEventSelection:Array<SongEventData>) + public function new(notes:Array<SongNoteData>, events:Array<SongEventData>) { this.notes = notes; this.events = events; - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; } public function execute(state:ChartEditorState):Void { + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + state.currentNoteSelection = notes; state.currentEventSelection = events; @@ -67,8 +67,14 @@ class SetItemSelectionCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // Add to the history if we actually performed an action. + return (state.currentNoteSelection != previousNoteSelection && state.currentEventSelection != previousEventSelection); + } + public function toString():String { - return 'Select ${notes.length} Items'; + return 'Select ${notes.length + events.length} Items'; } } diff --git a/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx b/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx index 75e7e5afe..30c2edb61 100644 --- a/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx @@ -38,6 +38,12 @@ class SwitchDifficultyCommand implements ChartEditorCommand state.notePreviewDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // Add to the history if we actually performed an action. + return (prevVariation != newVariation || prevDifficulty != newDifficulty); + } + public function toString():String { return 'Switch Difficulty'; diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index 79bcd59af..36c6b1d2f 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -119,8 +119,10 @@ class ChartEditorEventSprite extends FlxSprite return DEFAULT_EVENT; } - public function playAnimation(name:String):Void + public function playAnimation(?name:String):Void { + if (name == null) name = eventData?.event ?? DEFAULT_EVENT; + var correctedName = correctAnimationName(name); this.animation.play(correctedName); refresh(); diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx b/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx index 598cbb544..8d9ec6743 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx @@ -70,9 +70,9 @@ class ChartEditorNotePreview extends FlxSprite * @param event The data for the event. * @param songLengthInMs The total length of the song in milliseconds. */ - public function addEvent(event:SongEventData, songLengthInMs:Int):Void + public function addEvent(event:SongEventData, songLengthInMs:Int, ?isSelection:Bool = false):Void { - drawNote(-1, false, Std.int(event.time), songLengthInMs); + drawNote(-1, false, Std.int(event.time), songLengthInMs, isSelection); } /** @@ -114,6 +114,19 @@ class ChartEditorNotePreview extends FlxSprite } } + /** + * Add an array of selected events to the preview. + * @param events The data for the events. + * @param songLengthInMs The total length of the song in milliseconds. + */ + public function addSelectedEvents(events:Array<SongEventData>, songLengthInMs:Int):Void + { + for (event in events) + { + addEvent(event, songLengthInMs, true); + } + } + /** * Draws a note on the preview. * @param dir Note data. diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 4df53663c..0edba7357 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -188,17 +188,19 @@ class ChartEditorAudioHandler state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; state.audioVisGroup.playerVis.detail = 1; + state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS); state.audioVocalTrackGroup.playerVoicesOffset = state.currentSongOffsets.getVocalOffset(charId); return true; case DAD: state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); state.audioVisGroup.addOpponentVis(vocalTrack); - state.audioVisGroup.opponentVis.x = 405; + state.audioVisGroup.opponentVis.x = 435; state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; state.audioVisGroup.opponentVis.detail = 1; + state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS); state.audioVocalTrackGroup.opponentVoicesOffset = state.currentSongOffsets.getVocalOffset(charId); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx index f7105d2f7..62f1f4cbc 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx @@ -2,6 +2,10 @@ package funkin.ui.debug.charting.handlers; import funkin.util.PlatformUtil; +/** + * Handles modifying the shortcut text of menu items based on the current platform. + * On MacOS, `Ctrl`, `Alt`, and `Shift` are replaced with `⌘` (or `^`), `⌥`, and `⇧`, respectively. + */ @:access(funkin.ui.debug.charting.ChartEditorState) class ChartEditorShortcutHandler { @@ -18,7 +22,8 @@ class ChartEditorShortcutHandler state.menubarItemCopy.shortcutText = ctrlOrCmd('C'); state.menubarItemPaste.shortcutText = ctrlOrCmd('V'); - state.menubarItemSelectAll.shortcutText = ctrlOrCmd('A'); + state.menubarItemSelectAllNotes.shortcutText = ctrlOrCmd('A'); + state.menubarItemSelectAllEvents.shortcutText = ctrlOrCmd(alt('A')); state.menubarItemSelectInverse.shortcutText = ctrlOrCmd('I'); state.menubarItemSelectNone.shortcutText = ctrlOrCmd('D'); state.menubarItemSelectBeforeCursor.shortcutText = shift('Home'); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index f17c3d91e..dec199e98 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1,6 +1,5 @@ package funkin.ui.freeplay; -import funkin.input.Controls; import flash.text.TextField; import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; @@ -23,33 +22,35 @@ import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxSpriteUtil; import flixel.util.FlxTimer; -import funkin.input.Controls.Control; import funkin.data.level.LevelRegistry; import funkin.data.song.SongRegistry; import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.shaders.AngleMask; import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.PureColor; -import funkin.util.MathUtil; import funkin.graphics.shaders.StrokeShader; +import funkin.input.Controls; +import funkin.input.Controls.Control; import funkin.play.components.HealthIcon; import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song; import funkin.save.Save; import funkin.save.Save.SaveScoreData; +import funkin.ui.AtlasText; import funkin.ui.freeplay.BGScrollingText; import funkin.ui.freeplay.DifficultyStars; import funkin.ui.freeplay.DJBoyfriend; import funkin.ui.freeplay.FreeplayScore; import funkin.ui.freeplay.LetterSort; import funkin.ui.freeplay.SongMenuItem; +import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatSubState; -import funkin.ui.mainmenu.MainMenuState; import funkin.ui.transition.LoadingState; import funkin.ui.transition.StickerSubState; import funkin.util.MathUtil; +import funkin.util.MathUtil; import lime.app.Future; import lime.utils.Assets; @@ -64,7 +65,7 @@ class FreeplayState extends MusicBeatSubState var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY; var fp:FreeplayScore; - var txtCompletion:FlxText; + var txtCompletion:AtlasText; var lerpCompletion:Float = 0; var intendedCompletion:Float = 0; var lerpScore:Float = 0; @@ -87,6 +88,8 @@ class FreeplayState extends MusicBeatSubState var grpCapsules:FlxTypedGroup<SongMenuItem>; var curCapsule:SongMenuItem; var curPlaying:Bool = false; + var ostName:FlxText; + var difficultyStars:DifficultyStars; var dj:DJBoyfriend; @@ -150,15 +153,10 @@ class FreeplayState extends MusicBeatSubState for (songId in LevelRegistry.instance.parseEntryData(levelId).songs) { var song:Song = SongRegistry.instance.fetchEntry(songId); - var songBaseDifficulty:SongDifficulty = song.getDifficulty(Constants.DEFAULT_DIFFICULTY); - var songName = songBaseDifficulty.songName; - var songOpponent = songBaseDifficulty.characters.opponent; - var songDifficulties = song.listDifficulties(); + songs.push(new FreeplaySongData(levelId, songId, song)); - songs.push(new FreeplaySongData(songId, songName, levelId, songOpponent, songDifficulties)); - - for (difficulty in songDifficulties) + for (difficulty in song.listDifficulties()) { diffIdsTotal.pushUnique(difficulty); } @@ -334,6 +332,8 @@ class FreeplayState extends MusicBeatSubState if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; } + // NOTE: This is an AtlasSprite because we use an animation to bring it into view. + // TODO: Add the ability to select the album graphic. var albumArt:FlxAtlasSprite = new FlxAtlasSprite(640, 360, Paths.animateAtlas("freeplay/albumRoll")); albumArt.visible = false; add(albumArt); @@ -347,7 +347,7 @@ class FreeplayState extends MusicBeatSubState var albumTitle:FlxSprite = new FlxSprite(947, 491).loadGraphic(Paths.image('freeplay/albumTitle-fnfvol1')); var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite')); - var difficultyStars:DifficultyStars = new DifficultyStars(140, 39); + difficultyStars = new DifficultyStars(140, 39); difficultyStars.stars.visible = false; albumTitle.visible = false; @@ -382,11 +382,16 @@ class FreeplayState extends MusicBeatSubState add(overhangStuff); FlxTween.tween(overhangStuff, {y: 0}, 0.3, {ease: FlxEase.quartOut}); - var fnfFreeplay:FlxText = new FlxText(0, 12, 0, "FREEPLAY", 48); + var fnfFreeplay:FlxText = new FlxText(8, 8, 0, "FREEPLAY", 48); fnfFreeplay.font = "VCR OSD Mono"; fnfFreeplay.visible = false; - exitMovers.set([overhangStuff, fnfFreeplay], + ostName = new FlxText(8, 8, FlxG.width - 8 - 8, "OFFICIAL OST", 48); + ostName.font = "VCR OSD Mono"; + ostName.alignment = RIGHT; + ostName.visible = false; + + exitMovers.set([overhangStuff, fnfFreeplay, ostName], { y: -overhangStuff.height, x: 0, @@ -397,8 +402,9 @@ class FreeplayState extends MusicBeatSubState var sillyStroke = new StrokeShader(0xFFFFFFFF, 2, 2); fnfFreeplay.shader = sillyStroke; add(fnfFreeplay); + add(ostName); - var fnfHighscoreSpr:FlxSprite = new FlxSprite(890, 70); + var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70); fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore'); fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore", 24, false); fnfHighscoreSpr.visible = false; @@ -415,8 +421,10 @@ class FreeplayState extends MusicBeatSubState fp.visible = false; add(fp); - txtCompletion = new FlxText(1200, 77, 0, "0", 32); - txtCompletion.font = "VCR OSD Mono"; + var clearBoxSprite:FlxSprite = new FlxSprite(1165, 65).loadGraphic(Paths.image('freeplay/clearBox')); + add(clearBoxSprite); + + txtCompletion = new AtlasText(1185, 87, "69", AtlasFont.FREEPLAY_CLEAR); txtCompletion.visible = false; add(txtCompletion); @@ -485,6 +493,7 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(1 / 24, function(handShit) { fnfHighscoreSpr.visible = true; fnfFreeplay.visible = true; + ostName.visible = true; fp.visible = true; fp.updateScore(0); @@ -674,9 +683,32 @@ class FreeplayState extends MusicBeatSubState lerpScore = MathUtil.coolLerp(lerpScore, intendedScore, 0.2); lerpCompletion = MathUtil.coolLerp(lerpCompletion, intendedCompletion, 0.9); + if (Math.isNaN(lerpScore)) + { + lerpScore = intendedScore; + } + + if (Math.isNaN(lerpCompletion)) + { + lerpCompletion = intendedCompletion; + } + fp.updateScore(Std.int(lerpScore)); - txtCompletion.text = Math.floor(lerpCompletion * 100) + "%"; + txtCompletion.text = '${Math.floor(lerpCompletion * 100)}'; + + // Right align the completion percentage + switch (txtCompletion.text.length) + { + case 3: + txtCompletion.x = 1185 - 10; + case 2: + txtCompletion.x = 1185; + case 1: + txtCompletion.x = 1185 + 24; + default: + txtCompletion.x = 1185; + } handleInputs(elapsed); } @@ -913,6 +945,11 @@ class FreeplayState extends MusicBeatSubState intendedCompletion = 0.0; } + if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion)) + { + intendedCompletion = 0; + } + grpDifficulties.group.forEach(function(diffSprite) { diffSprite.visible = false; }); @@ -938,6 +975,27 @@ class FreeplayState extends MusicBeatSubState } } } + + if (change != 0) + { + // Update the song capsules to reflect the new difficulty info. + for (songCapsule in grpCapsules.members) + { + if (songCapsule == null) continue; + if (songCapsule.songData != null) + { + songCapsule.songData.currentDifficulty = currentDifficulty; + songCapsule.init(null, null, songCapsule.songData); + } + else + { + songCapsule.init(null, null, null); + } + } + } + + // Set the difficulty star count on the right. + difficultyStars.difficulty = daSong.songRating; } // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) @@ -1046,6 +1104,10 @@ class FreeplayState extends MusicBeatSubState { currentDifficulty = rememberedDifficulty; } + + // Set the difficulty star count on the right. + var daSong = songs[curSelected]; + difficultyStars.difficulty = daSong?.songRating ?? 0; } function changeSelection(change:Int = 0) @@ -1176,19 +1238,47 @@ class FreeplaySongData { public var isFav:Bool = false; - public var songId:String = ""; - public var songName:String = ""; - public var levelId:String = ""; - public var songCharacter:String = ""; - public var songDifficulties:Array<String> = []; + var song:Song; - public function new(songId:String, songName:String, levelId:String, songCharacter:String, songDifficulties:Array<String>) + public var levelId(default, null):String = ""; + public var songId(default, null):String = ""; + + public var songDifficulties(default, null):Array<String> = []; + + public var songName(default, null):String = ""; + public var songCharacter(default, null):String = ""; + public var songRating(default, null):Int = 0; + + public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + + function set_currentDifficulty(value:String):String + { + if (currentDifficulty == value) return value; + + currentDifficulty = value; + updateValues(); + return value; + } + + public function new(levelId:String, songId:String, song:Song) { - this.songId = songId; - this.songName = songName; this.levelId = levelId; - this.songCharacter = songCharacter; - this.songDifficulties = songDifficulties; + this.songId = songId; + this.song = song; + + updateValues(); + } + + function updateValues():Void + { + this.songDifficulties = song.listDifficulties(); + if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY; + + var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty); + if (songDifficulty == null) return; + this.songName = songDifficulty.songName; + this.songCharacter = songDifficulty.characters.opponent; + this.songRating = songDifficulty.difficultyRating; } } diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 4e0772dfe..06d113468 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -35,11 +35,6 @@ class SongMenuItem extends FlxSpriteGroup var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect"]; - // lol... - var diffRanks:Array<String> = [ - "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "14", "15" - ]; - public var targetPos:FlxPoint = new FlxPoint(); public var doLerp:Bool = false; public var doJumpIn:Bool = false; @@ -47,10 +42,12 @@ class SongMenuItem extends FlxSpriteGroup public var doJumpOut:Bool = false; public var onConfirm:Void->Void; - public var diffGrayscale:Grayscale; + public var grayscaleShader:Grayscale; public var hsvShader(default, set):HSVShader; + var diffRatingSprite:FlxSprite; + public function new(x:Float, y:Float) { super(x, y); @@ -75,26 +72,30 @@ class SongMenuItem extends FlxSpriteGroup add(ranking); grpHide.add(ranking); - diffGrayscale = new Grayscale(1); - - var diffRank = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRankings/diff" + FlxG.random.getObject(diffRanks))); - diffRank.shader = diffGrayscale; - diffRank.visible = false; - add(diffRank); - diffRank.origin.set(capsule.origin.x - diffRank.x, capsule.origin.y - diffRank.y); - grpHide.add(diffRank); - switch (rank) { case "perfect": ranking.x -= 10; } + grayscaleShader = new Grayscale(1); + + diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRatings/diff00")); + diffRatingSprite.shader = grayscaleShader; + diffRatingSprite.visible = false; + add(diffRatingSprite); + diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y); + grpHide.add(diffRatingSprite); + songText = new CapsuleText(capsule.width * 0.26, 45, 'Random', Std.int(40 * realScaled)); add(songText); grpHide.add(songText); - pixelIcon = new FlxSprite(155, 15); + // TODO: Use value from metadata instead of random. + updateDifficultyRating(FlxG.random.int(0, 15)); + + pixelIcon = new FlxSprite(160, 35); + pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; pixelIcon.active = false; @@ -113,6 +114,12 @@ class SongMenuItem extends FlxSpriteGroup setVisibleGrp(false); } + function updateDifficultyRating(newRating:Int) + { + var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating'; + diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}')); + } + function set_hsvShader(value:HSVShader):HSVShader { this.hsvShader = value; @@ -149,16 +156,17 @@ class SongMenuItem extends FlxSpriteGroup updateSelected(); } - public function init(x:Float, y:Float, songData:Null<FreeplaySongData>) + public function init(?x:Float, ?y:Float, songData:Null<FreeplaySongData>) { - this.x = x; - this.y = y; + if (x != null) this.x = x; + if (y != null) this.y = y; this.songData = songData; // Update capsule text. songText.text = songData?.songName ?? 'Random'; // Update capsule character. if (songData?.songCharacter != null) setCharacter(songData.songCharacter); + updateDifficultyRating(songData?.songRating ?? 0); // Update opacity, offsets, etc. updateSelected(); } @@ -200,7 +208,14 @@ class SongMenuItem extends FlxSpriteGroup pixelIcon.loadGraphic(Paths.image(charPath)); pixelIcon.scale.x = pixelIcon.scale.y = 2; - pixelIcon.origin.x = 100; + + switch (char) + { + case "parents-christmas": + pixelIcon.origin.x = 140; + default: + pixelIcon.origin.x = 100; + } // pixelIcon.origin.x = capsule.origin.x; // pixelIcon.offset.x -= pixelIcon.origin.x; } @@ -336,7 +351,7 @@ class SongMenuItem extends FlxSpriteGroup function updateSelected():Void { - diffGrayscale.setAmount(this.selected ? 0 : 0.8); + grayscaleShader.setAmount(this.selected ? 0 : 0.8); songText.alpha = this.selected ? 1 : 0.6; songText.blurredText.visible = this.selected ? true : false; capsule.offset.x = this.selected ? 0 : -5; diff --git a/source/funkin/util/macro/HaxelibVersions.hx b/source/funkin/util/macro/HaxelibVersions.hx index f0317c397..1a4699bba 100644 --- a/source/funkin/util/macro/HaxelibVersions.hx +++ b/source/funkin/util/macro/HaxelibVersions.hx @@ -11,12 +11,12 @@ class HaxelibVersions #else // `#if display` is used for code completion. In this case returning an // empty string is good enough; We don't want to call functions on every hint. - var commitHash:String = ""; - return macro $v{commitHashSplice}; + var commitHash:Array<String> = []; + return macro $v{commitHash}; #end } - #if (debug && macro) + #if (macro) static function readHmmData():hmm.HmmConfig { return hmm.HmmConfig.HmmConfigs.readHmmJsonOrThrow(); diff --git a/source/funkin/util/tools/Int64Tools.hx b/source/funkin/util/tools/Int64Tools.hx index 75448b36f..d53fa315d 100644 --- a/source/funkin/util/tools/Int64Tools.hx +++ b/source/funkin/util/tools/Int64Tools.hx @@ -1,32 +1,42 @@ package funkin.util.tools; +import haxe.Int64; + /** - * @see https://github.com/fponticelli/thx.core/blob/master/src/thx/Int64s.hx + * Why `haxe.Int64` doesn't have a built-in `toFloat` function is beyond me. */ class Int64Tools { - static var min = haxe.Int64.make(0x80000000, 0); - static var one = haxe.Int64.make(0, 1); - static var two = haxe.Int64.ofInt(2); - static var zero = haxe.Int64.make(0, 0); - static var ten = haxe.Int64.ofInt(10); + private inline static var MAX_32_PRECISION:Float = 4294967296.0; - public static function toFloat(i:haxe.Int64):Float + public static function fromFloat(f:Float):Int64 { - var isNegative = false; - if (i < 0) + var h = Std.int(f / MAX_32_PRECISION); + var l = Std.int(f); + return Int64.make(h, l); + } + + public static function toFloat(i:Int64):Float + { + var f:Float = Int64.getLow(i); + if (f < 0) f += MAX_32_PRECISION; + return (Int64.getHigh(i) * MAX_32_PRECISION + f); + } + + public static function isToIntSafe(i:Int64):Bool + { + return i.high != i.low >> 31; + } + + public static function toIntSafe(i:Int64):Int + { + try { - if (i < min) return -9223372036854775808.0; // most -ve value can't be made +ve - isNegative = true; - i = -i; + return Int64.toInt(i); } - var multiplier = 1.0, ret = 0.0; - for (_ in 0...64) + catch (e:Dynamic) { - if (haxe.Int64.and(i, one) != zero) ret += multiplier; - multiplier *= 2.0; - i = haxe.Int64.shr(i, 1); + throw 'Could not represent value "${Int64.toStr(i)}" as an integer.'; } - return (isNegative ? -1 : 1) * ret; } }