diff --git a/CHANGELOG.md b/CHANGELOG.md index 8397af666..20b277b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Bug where Dadbattle shows up as Dadbattle Erect when returning to freeplay - Fixed 2Hot not appearing under the "#" category in Freeplay menu +- Fixed a bug where the Chart Editor would crash when attempting to select an event with the Event toolbox open +- Improved offsets for Pico and Tankman opponents so they don't slide around as much. + + ## [0.4.0] - 2024-06-06 ### Added @@ -37,11 +41,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. (thanks Keoiki!) - Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. (thanks gamerbross!) - Remember that for more complex behaviors such as animations or transitions, you should use an XML file to define each frame. +- Improved the Event Toolbox in the Chart Editor; dropdowns are now bigger, include search field, and display elements in alphabetical order rather than a random order. ### Fixed - Fixed an issue where Nene's visualizer would not play on Desktop builds - Fixed a bug where the game would silently fail to load saves on HTML5 - Fixed some bugs with the props on the Story Menu not bopping properly -- Improved offsets for Pico and Tankman opponents so they don't slide around as much. +- Additional fixes to the Loading bar on HTML5 (thanks lemz1!) +- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!) +- Fixed a camera bug in the Main Menu (thanks richTrash21!) +- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!) +- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!) +- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!) +- Fixed a bug where the Chart Editor Playtest would crash when losing (thanks gamerbross!) +- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay (thanks gamerbross!) +- Fixed a bug where hold notes would be positioned wrong on downscroll (thanks MaybeMaru!) +- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!) +- Improved debug logging for unscripted stages (thanks gamerbross!) +- Made improvements to compiling documentation (thanks gedehari!) - Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!) - Optimized animation handling for characters (thanks richTrash21!) - Made improvements to compiling documentation (thanks gedehari!) diff --git a/assets b/assets index 6421057fa..f5a9f84e7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 6421057faae19782fcda81349bbdeb40f014323e +Subproject commit f5a9f84e7f5246a27bd9397784d00f74973be825 diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 4f61e70c2..c70f195d2 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -227,12 +227,12 @@ class FunkinSound extends FlxSound implements ICloneable // already paused before we lost focus. if (_lostFocus && !_alreadyPaused) { - trace('Resuming audio (${this._label}) on focus!'); + // trace('Resuming audio (${this._label}) on focus!'); resume(); } else { - trace('Not resuming audio (${this._label}) on focus!'); + // trace('Not resuming audio (${this._label}) on focus!'); } _lostFocus = false; } @@ -242,7 +242,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocusLost():Void { - trace('Focus lost, pausing audio!'); + // trace('Focus lost, pausing audio!'); _lostFocus = true; _alreadyPaused = _paused; pause(); diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 4d50d75cc..ac45dc194 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -237,7 +237,7 @@ class GameOverSubState extends MusicBeatSubState } // KEYBOARD ONLY: Restart the level when pressing the assigned key. - if (controls.ACCEPT && blueballed) + if (controls.ACCEPT && blueballed && !mustNotExit) { blueballed = false; confirmDeath(); diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index a3204329a..2442b0dc5 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -53,8 +53,9 @@ class HealthIcon extends FunkinSprite /** * Apply the "bop" animation once every X steps. + * Defaults to once per beat. */ - public var bopEvery:Int = 4; + public var bopEvery:Int = Constants.STEPS_PER_BEAT; /** * The amount, in degrees, to rotate the icon by when boping. diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 1e3f011c1..f72cca77f 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -904,7 +904,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function set_notePreviewDirty(value:Bool):Bool { - trace('Note preview dirtied!'); + // trace('Note preview dirtied!'); return notePreviewDirty = value; } diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 73cf80fa0..661c44d85 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -35,7 +35,15 @@ class SetItemSelectionCommand implements ChartEditorCommand { var eventSelected = this.events[0]; - state.eventKindToPlace = eventSelected.eventKind; + if (state.eventKindToPlace == eventSelected.eventKind) + { + trace('Target event kind matches selection: ${eventSelected.eventKind}'); + } + else + { + trace('Switching target event kind to match selection: ${state.eventKindToPlace} != ${eventSelected.eventKind}'); + state.eventKindToPlace = eventSelected.eventKind; + } // This code is here to parse event data that's not built as a struct for some reason. // TODO: Clean this up or get rid of it. diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index b1af0ce4c..e42102a52 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -201,7 +201,8 @@ class ChartEditorThemeHandler // Selection borders horizontally in the middle. for (i in 1...(Conductor.instance.stepsPerMeasure)) { - if ((i % Conductor.instance.beatsPerMeasure) == 0) + // There may be a different number of beats per measure, but there's always 4 steps per beat. + if ((i % Constants.STEPS_PER_BEAT) == 0) { state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width, GRID_BEAT_DIVIDER_WIDTH), diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index f0949846d..8f021840a 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -58,17 +58,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox function initialize():Void { - toolboxEventsEventKind.dataSource = new ArrayDataSource(); - - var songEvents:Array = SongEventRegistry.listEvents(); - - for (event in songEvents) - { - toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); - } - toolboxEventsEventKind.onChange = function(event:UIEvent) { - var eventType:String = event.data.value; + var eventType:String = event.data.id; trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); @@ -83,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox return; } - buildEventDataFormFromSchema(toolboxEventsDataGrid, schema); + buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace); if (!_initializing && chartEditorState.currentEventSelection.length > 0) { @@ -98,14 +89,40 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox chartEditorState.notePreviewDirty = true; } } - toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + var startingEventValue = ChartEditorDropdowns.populateDropdownWithSongEvents(toolboxEventsEventKind, chartEditorState.eventKindToPlace); + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Starting event kind: ${startingEventValue}'); + toolboxEventsEventKind.value = startingEventValue; } public override function refresh():Void { super.refresh(); - toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + var newDropdownElement = ChartEditorDropdowns.findDropdownElement(chartEditorState.eventKindToPlace, toolboxEventsEventKind); + + if (newDropdownElement == null) + { + throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind not in dropdown: ${chartEditorState.eventKindToPlace}'; + } + else if (toolboxEventsEventKind.value != newDropdownElement || lastEventKind != toolboxEventsEventKind.value.id) + { + toolboxEventsEventKind.value = newDropdownElement; + + var schema:SongEventSchema = SongEventRegistry.getEventSchema(chartEditorState.eventKindToPlace); + if (schema == null) + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: ${chartEditorState.eventKindToPlace}'); + } + else + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind changed: ${toolboxEventsEventKind.value.id} != ${newDropdownElement.id} != ${lastEventKind}, rebuilding form'); + buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace); + } + } + else + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind not changed: ${toolboxEventsEventKind.value} == ${newDropdownElement} == ${lastEventKind}'); + } for (pair in chartEditorState.eventDataToPlace.keyValueIterator()) { @@ -116,7 +133,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox if (field == null) { - throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form.'; + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form for kind ${lastEventKind}.'; } else { @@ -141,9 +158,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox } } - function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema):Void + var lastEventKind:String = 'unknown'; + + function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema, eventKind:String):Void { - trace(schema); + trace('Building event data form from schema for event kind: ${eventKind}'); + // trace(schema); + + lastEventKind = eventKind ?? 'unknown'; + // Clear the frame. target.removeAllComponents(); @@ -188,6 +211,9 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox var dropDown:DropDown = new DropDown(); dropDown.id = field.name; dropDown.width = 200.0; + dropDown.dropdownSize = 10; + dropDown.dropdownWidth = 300; + dropDown.searchable = true; dropDown.dataSource = new ArrayDataSource(); if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; @@ -197,12 +223,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox for (optionName in field.keys.keys()) { var optionValue:Null = field.keys.get(optionName); - trace('$optionName : $optionValue'); + // trace('$optionName : $optionValue'); dropDown.dataSource.add({value: optionValue, text: optionName}); } dropDown.value = field.defaultValue; + // TODO: Add an option to customize sort. + dropDown.dataSource.sort('text', ASCENDING); + input = dropDown; case STRING: input = new TextField(); diff --git a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx index d2a0a053e..55aab0ab0 100644 --- a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx +++ b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx @@ -3,11 +3,13 @@ package funkin.ui.debug.charting.util; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import funkin.data.stage.StageData; +import funkin.play.event.SongEvent; import funkin.data.stage.StageRegistry; import funkin.play.character.CharacterData; import haxe.ui.components.DropDown; import funkin.play.stage.Stage; import funkin.play.character.BaseCharacter.CharacterType; +import funkin.data.event.SongEventRegistry; import funkin.play.character.CharacterData.CharacterDataParser; /** @@ -81,6 +83,42 @@ class ChartEditorDropdowns return returnValue; } + public static function populateDropdownWithSongEvents(dropDown:DropDown, startingEventId:String):DropDownEntry + { + dropDown.dataSource.clear(); + + var returnValue:DropDownEntry = {id: "FocusCamera", text: "Focus Camera"}; + + var songEvents:Array = SongEventRegistry.listEvents(); + + for (event in songEvents) + { + var value = {id: event.id, text: event.getTitle()}; + if (startingEventId == event.id) returnValue = value; + dropDown.dataSource.add(value); + } + + dropDown.dataSource.sort('text', ASCENDING); + + return returnValue; + } + + /** + * Given the ID of a dropdown element, find the corresponding entry in the dropdown's dataSource. + */ + public static function findDropdownElement(id:String, dropDown:DropDown):Null + { + // Attempt to find the entry. + for (entryIndex in 0...dropDown.dataSource.size) + { + var entry = dropDown.dataSource.get(entryIndex); + if (entry.id == id) return entry; + } + + // Not found. + return null; + } + /** * Populate a dropdown with a list of note styles. */