Merge branch 'bugfix/chart-editor-event-toolbox-crash' into rewrite/master

This commit is contained in:
Cameron Taylor 2024-06-10 12:26:47 -04:00
commit 4691e3d249
10 changed files with 120 additions and 27 deletions

View file

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Bug where Dadbattle shows up as Dadbattle Erect when returning to freeplay - Bug where Dadbattle shows up as Dadbattle Erect when returning to freeplay
- Fixed 2Hot not appearing under the "#" category in Freeplay menu - 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 ## [0.4.0] - 2024-06-06
### Added ### 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!) - 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!) - 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. - 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
- Fixed an issue where Nene's visualizer would not play on Desktop builds - 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 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 - 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!) - Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
- Optimized animation handling for characters (thanks richTrash21!) - Optimized animation handling for characters (thanks richTrash21!)
- Made improvements to compiling documentation (thanks gedehari!) - Made improvements to compiling documentation (thanks gedehari!)

2
assets

@ -1 +1 @@
Subproject commit 6421057faae19782fcda81349bbdeb40f014323e Subproject commit f5a9f84e7f5246a27bd9397784d00f74973be825

View file

@ -227,12 +227,12 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
// already paused before we lost focus. // already paused before we lost focus.
if (_lostFocus && !_alreadyPaused) if (_lostFocus && !_alreadyPaused)
{ {
trace('Resuming audio (${this._label}) on focus!'); // trace('Resuming audio (${this._label}) on focus!');
resume(); resume();
} }
else else
{ {
trace('Not resuming audio (${this._label}) on focus!'); // trace('Not resuming audio (${this._label}) on focus!');
} }
_lostFocus = false; _lostFocus = false;
} }
@ -242,7 +242,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
*/ */
override function onFocusLost():Void override function onFocusLost():Void
{ {
trace('Focus lost, pausing audio!'); // trace('Focus lost, pausing audio!');
_lostFocus = true; _lostFocus = true;
_alreadyPaused = _paused; _alreadyPaused = _paused;
pause(); pause();

View file

@ -237,7 +237,7 @@ class GameOverSubState extends MusicBeatSubState
} }
// KEYBOARD ONLY: Restart the level when pressing the assigned key. // KEYBOARD ONLY: Restart the level when pressing the assigned key.
if (controls.ACCEPT && blueballed) if (controls.ACCEPT && blueballed && !mustNotExit)
{ {
blueballed = false; blueballed = false;
confirmDeath(); confirmDeath();

View file

@ -53,8 +53,9 @@ class HealthIcon extends FunkinSprite
/** /**
* Apply the "bop" animation once every X steps. * 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. * The amount, in degrees, to rotate the icon by when boping.

View file

@ -904,7 +904,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function set_notePreviewDirty(value:Bool):Bool function set_notePreviewDirty(value:Bool):Bool
{ {
trace('Note preview dirtied!'); // trace('Note preview dirtied!');
return notePreviewDirty = value; return notePreviewDirty = value;
} }

View file

@ -35,7 +35,15 @@ class SetItemSelectionCommand implements ChartEditorCommand
{ {
var eventSelected = this.events[0]; var eventSelected = this.events[0];
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; state.eventKindToPlace = eventSelected.eventKind;
}
// This code is here to parse event data that's not built as a struct for some reason. // 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. // TODO: Clean this up or get rid of it.

View file

@ -201,7 +201,8 @@ class ChartEditorThemeHandler
// Selection borders horizontally in the middle. // Selection borders horizontally in the middle.
for (i in 1...(Conductor.instance.stepsPerMeasure)) 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, state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width,
GRID_BEAT_DIVIDER_WIDTH), GRID_BEAT_DIVIDER_WIDTH),

View file

@ -58,17 +58,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
function initialize():Void function initialize():Void
{ {
toolboxEventsEventKind.dataSource = new ArrayDataSource();
var songEvents:Array<SongEvent> = SongEventRegistry.listEvents();
for (event in songEvents)
{
toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id});
}
toolboxEventsEventKind.onChange = function(event:UIEvent) { toolboxEventsEventKind.onChange = function(event:UIEvent) {
var eventType:String = event.data.value; var eventType:String = event.data.id;
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType');
@ -83,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
return; return;
} }
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema); buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace);
if (!_initializing && chartEditorState.currentEventSelection.length > 0) if (!_initializing && chartEditorState.currentEventSelection.length > 0)
{ {
@ -98,14 +89,40 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
chartEditorState.notePreviewDirty = true; 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 public override function refresh():Void
{ {
super.refresh(); 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()) for (pair in chartEditorState.eventDataToPlace.keyValueIterator())
{ {
@ -116,7 +133,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
if (field == null) 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 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. // Clear the frame.
target.removeAllComponents(); target.removeAllComponents();
@ -188,6 +211,9 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
var dropDown:DropDown = new DropDown(); var dropDown:DropDown = new DropDown();
dropDown.id = field.name; dropDown.id = field.name;
dropDown.width = 200.0; dropDown.width = 200.0;
dropDown.dropdownSize = 10;
dropDown.dropdownWidth = 300;
dropDown.searchable = true;
dropDown.dataSource = new ArrayDataSource(); dropDown.dataSource = new ArrayDataSource();
if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; 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()) for (optionName in field.keys.keys())
{ {
var optionValue:Null<Dynamic> = field.keys.get(optionName); var optionValue:Null<Dynamic> = field.keys.get(optionName);
trace('$optionName : $optionValue'); // trace('$optionName : $optionValue');
dropDown.dataSource.add({value: optionValue, text: optionName}); dropDown.dataSource.add({value: optionValue, text: optionName});
} }
dropDown.value = field.defaultValue; dropDown.value = field.defaultValue;
// TODO: Add an option to customize sort.
dropDown.dataSource.sort('text', ASCENDING);
input = dropDown; input = dropDown;
case STRING: case STRING:
input = new TextField(); input = new TextField();

View file

@ -3,11 +3,13 @@ package funkin.ui.debug.charting.util;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notestyle.NoteStyle;
import funkin.data.stage.StageData; import funkin.data.stage.StageData;
import funkin.play.event.SongEvent;
import funkin.data.stage.StageRegistry; import funkin.data.stage.StageRegistry;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import haxe.ui.components.DropDown; import haxe.ui.components.DropDown;
import funkin.play.stage.Stage; import funkin.play.stage.Stage;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.data.event.SongEventRegistry;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
/** /**
@ -81,6 +83,42 @@ class ChartEditorDropdowns
return returnValue; 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<SongEvent> = 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<DropDownEntry>
{
// 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. * Populate a dropdown with a list of note styles.
*/ */