From f51592963e96808c4959ead04ba6acb6670f22dc Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 17 Oct 2023 02:42:52 -0400 Subject: [PATCH] Fixes for a few bugs in the chart editor. --- assets | 2 +- source/funkin/play/HealthIcon.hx | 51 ++++++-- source/funkin/play/character/BaseCharacter.hx | 15 +-- .../ui/debug/charting/ChartEditorState.hx | 117 ++++++++++-------- .../ui/haxeui/components/FunkinClickLabel.hx | 30 +++++ source/funkin/util/Constants.hx | 5 + 6 files changed, 146 insertions(+), 74 deletions(-) create mode 100644 source/funkin/ui/haxeui/components/FunkinClickLabel.hx diff --git a/assets b/assets index 8104d43e5..b9338b972 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8104d43e584a1f25e574438d7b21a7e671358969 +Subproject commit b9338b97214f71b192f5cec760c5442ec2e8cbed diff --git a/source/funkin/play/HealthIcon.hx b/source/funkin/play/HealthIcon.hx index 7785fb4b1..5b9c8ec75 100644 --- a/source/funkin/play/HealthIcon.hx +++ b/source/funkin/play/HealthIcon.hx @@ -24,13 +24,14 @@ import openfl.utils.Assets; * - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);` * @author MasterEric */ +@:nullSafety class HealthIcon extends FlxSprite { /** * The character this icon is representing. * Setting this variable will automatically update the graphic. */ - public var characterId(default, set):String; + public var characterId(default, set):Null; /** * Whether this health icon should automatically update its state based on the character's health. @@ -123,13 +124,13 @@ class HealthIcon extends FlxSprite initTargetSize(); } - function set_characterId(value:String):String + function set_characterId(value:Null):Null { if (value == characterId) return value; - characterId = value; + characterId = value ?? Constants.DEFAULT_HEALTH_ICON; loadCharacter(characterId); - return value; + return characterId; } function set_isPixel(value:Bool):Bool @@ -138,7 +139,7 @@ class HealthIcon extends FlxSprite isPixel = value; loadCharacter(characterId); - return value; + return isPixel; } /** @@ -156,6 +157,32 @@ class HealthIcon extends FlxSprite } } + /** + * Use the provided CharacterHealthIconData to configure this health icon's appearance. + * @param data The data to use to configure this health icon. + */ + public function configure(data:Null):Void + { + if (data == null) + { + this.isPixel = false; + this.characterId = Constants.DEFAULT_HEALTH_ICON; + this.size.set(1.0, 1.0); + this.offset.x = 0.0; + this.offset.y = 0.0; + this.flipX = false; + } + else + { + this.isPixel = data.isPixel ?? false; + this.characterId = data.id; + this.size.set(data.scale ?? 1.0, data.scale ?? 1.0); + this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0; + this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0; + this.flipX = data.flipX ?? false; // Face the OTHER way by default, since that is more common. + } + } + /** * Called by Flixel every frame. Includes logic to manage the currently playing animation. */ @@ -341,12 +368,17 @@ class HealthIcon extends FlxSprite this.animation.add(Losing, [1], 0, false, false); } - function correctCharacterId(charId:String):String + function correctCharacterId(charId:Null):String { + if (charId == null) + { + return Constants.DEFAULT_HEALTH_ICON; + } + if (!Assets.exists(Paths.image('icons/icon-$charId'))) { FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!'); - return 'face'; + return Constants.DEFAULT_HEALTH_ICON; } return charId; @@ -357,10 +389,11 @@ class HealthIcon extends FlxSprite return Assets.exists(Paths.file('images/icons/icon-$characterId.xml')); } - function loadCharacter(charId:String):Void + function loadCharacter(charId:Null):Void { - if (correctCharacterId(charId) != charId) + if (charId == null || correctCharacterId(charId) != charId) { + // This will recursively trigger loadCharacter to be called again. characterId = correctCharacterId(charId); return; } diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 30b549fd3..5362df61c 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -312,12 +312,8 @@ class BaseCharacter extends Bopper trace('[WARN] Player 1 health icon not found!'); return; } - PlayState.instance.iconP1.isPixel = _data.healthIcon?.isPixel ?? false; - PlayState.instance.iconP1.characterId = _data.healthIcon.id; - PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale); - PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0]; - PlayState.instance.iconP1.offset.y = _data.healthIcon.offsets[1]; - PlayState.instance.iconP1.flipX = !_data.healthIcon.flipX; + PlayState.instance.iconP1.configure(_data.healthIcon); + PlayState.instance.iconP1.flipX = !PlayState.instance.iconP1.flipX; // BF is looking the other way. } else { @@ -326,12 +322,7 @@ class BaseCharacter extends Bopper trace('[WARN] Player 2 health icon not found!'); return; } - PlayState.instance.iconP2.isPixel = _data.healthIcon?.isPixel ?? false; - PlayState.instance.iconP2.characterId = _data.healthIcon.id; - PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale); - PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0]; - PlayState.instance.iconP2.offset.y = _data.healthIcon.offsets[1]; - PlayState.instance.iconP2.flipX = _data.healthIcon.flipX; + PlayState.instance.iconP2.configure(_data.healthIcon); } } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index b4c8c3483..43ab61d88 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -87,9 +87,8 @@ using Lambda; * * @author MasterEric */ +@:nullSafety // Give other classes access to private instance fields -// @:nullSafety(Loose) // Enable this while developing, then disable to keep unit tests functional! - @:allow(funkin.ui.debug.charting.ChartEditorCommand) @:allow(funkin.ui.debug.charting.ChartEditorDropdowns) @:allow(funkin.ui.debug.charting.ChartEditorDialogHandler) @@ -965,7 +964,7 @@ class ChartEditorState extends HaxeUIState function get_currentSongChartNoteData():Array { - var result:Array = currentSongChartData.notes.get(selectedDifficulty); + var result:Null> = currentSongChartData.notes.get(selectedDifficulty); if (result == null) { // Initialize to the default value if not set. @@ -1391,16 +1390,12 @@ class ChartEditorState extends HaxeUIState healthIconDad = new HealthIcon(currentSongMetadata.playData.characters.opponent); healthIconDad.autoUpdate = false; healthIconDad.size.set(0.5, 0.5); - healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5); - healthIconDad.y = gridTiledSprite.y + 5; add(healthIconDad); healthIconDad.zIndex = 30; healthIconBF = new HealthIcon(currentSongMetadata.playData.characters.player); healthIconBF.autoUpdate = false; healthIconBF.size.set(0.5, 0.5); - healthIconBF.x = gridTiledSprite.x + gridTiledSprite.width + 15; - healthIconBF.y = gridTiledSprite.y + 5; healthIconBF.flipX = true; add(healthIconBF); healthIconBF.zIndex = 30; @@ -1627,6 +1622,12 @@ class ChartEditorState extends HaxeUIState addUIClickListener('playbarForward', _ -> playbarButtonPressed = 'playbarForward'); addUIClickListener('playbarEnd', _ -> playbarButtonPressed = 'playbarEnd'); + // Cycle note snap quant. + addUIClickListener('playbarNoteSnap', function(_) { + noteSnapQuantIndex++; + if (noteSnapQuantIndex >= SNAP_QUANTS.length) noteSnapQuantIndex = 0; + }); + // Add functionality to the menu items. addUIClickListener('menubarItemNewChart', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true)); @@ -2477,19 +2478,22 @@ class ChartEditorState extends HaxeUIState var dragLengthMs:Float = dragLengthSteps * Conductor.stepLengthMs; var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE; - if (dragLengthSteps > 0) + if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null) { - gridGhostHoldNote.visible = true; - gridGhostHoldNote.noteData = gridGhostNote.noteData; - gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection(); + if (dragLengthSteps > 0) + { + gridGhostHoldNote.visible = true; + gridGhostHoldNote.noteData = gridGhostNote.noteData; + gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection(); - gridGhostHoldNote.setHeightDirectly(dragLengthPixels); + gridGhostHoldNote.setHeightDirectly(dragLengthPixels); - gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes); - } - else - { - gridGhostHoldNote.visible = false; + gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes); + } + else + { + gridGhostHoldNote.visible = false; + } } if (FlxG.mouse.justReleased) @@ -2644,7 +2648,7 @@ class ChartEditorState extends HaxeUIState if (cursorColumn == eventColumn) { if (gridGhostNote != null) gridGhostNote.visible = false; - gridGhostHoldNote.visible = false; + if (gridGhostHoldNote != null) gridGhostHoldNote.visible = false; if (gridGhostEvent == null) throw "ERROR: Tried to handle cursor, but gridGhostEvent is null! Check ChartEditorState.buildGrid()"; @@ -2704,11 +2708,11 @@ class ChartEditorState extends HaxeUIState } else { - if (FlxG.mouse.overlaps(notePreview)) + if (notePreview != null && FlxG.mouse.overlaps(notePreview)) { targetCursorMode = Pointer; } - else if (FlxG.mouse.overlaps(gridPlayheadScrollArea)) + else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea)) { targetCursorMode = Pointer; } @@ -3020,18 +3024,35 @@ class ChartEditorState extends HaxeUIState { if (healthIconsDirty) { - if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player; - if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent; + var charDataBF = CharacterDataParser.fetchCharacterData(currentSongMetadata.playData.characters.player); + var charDataDad = CharacterDataParser.fetchCharacterData(currentSongMetadata.playData.characters.opponent); + if (healthIconBF != null) + { + healthIconBF.configure(charDataBF?.healthIcon); + healthIconBF.size *= 0.5; // Make the icon smaller in Chart Editor. + healthIconBF.flipX = !healthIconBF.flipX; // BF faces the other way. + } + if (healthIconDad != null) + { + healthIconDad.configure(charDataDad?.healthIcon); + healthIconDad.size *= 0.5; // Make the icon smaller in Chart Editor. + } + healthIconsDirty = false; } - // Right align the BF health icon. + // Right align, and visibly center, the BF health icon. if (healthIconBF != null) { // Base X position to the right of the grid. - var baseHealthIconXPos:Float = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x + gridTiledSprite.width + 15); - // Will be 0 when not bopping. When bopping, will increase to push the icon left. - var healthIconOffset:Float = healthIconBF.width - (HealthIcon.HEALTH_ICON_SIZE * 0.5); - healthIconBF.x = baseHealthIconXPos - healthIconOffset; + 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)); + } + + // Visibly center the Dad health icon. + if (healthIconDad != null) + { + healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 45 - (healthIconDad.width / 2)); + healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2)); } } @@ -3656,49 +3677,41 @@ class ChartEditorState extends HaxeUIState var inputStage:Null = toolbox.findComponent('inputStage', DropDown); var stageId:String = currentSongMetadata.playData.stage; var stageData:Null = StageDataParser.parseStageData(stageId); - if (stageData != null) + if (inputStage != null) { - inputStage.value = {id: stageId, text: stageData.name}; - } - else - { - inputStage.value = {id: "mainStage", text: "Main Stage"}; + inputStage.value = (stageData != null) ? + {id: stageId, text: stageData.name} : + {id: "mainStage", text: "Main Stage"}; } var inputCharacterPlayer:Null = toolbox.findComponent('inputCharacterPlayer', DropDown); var charIdPlayer:String = currentSongMetadata.playData.characters.player; var charDataPlayer:Null = CharacterDataParser.fetchCharacterData(charIdPlayer); - if (charDataPlayer != null) + if (inputCharacterPlayer != null) { - inputCharacterPlayer.value = {id: charIdPlayer, text: charDataPlayer.name}; - } - else - { - inputCharacterPlayer.value = {id: "bf", text: "Boyfriend"}; + inputCharacterPlayer.value = (charDataPlayer != null) ? + {id: charIdPlayer, text: charDataPlayer.name} : + {id: "bf", text: "Boyfriend"}; } var inputCharacterOpponent:Null = toolbox.findComponent('inputCharacterOpponent', DropDown); var charIdOpponent:String = currentSongMetadata.playData.characters.opponent; var charDataOpponent:Null = CharacterDataParser.fetchCharacterData(charIdOpponent); - if (charDataOpponent != null) + if (inputCharacterOpponent != null) { - inputCharacterOpponent.value = {id: charIdOpponent, text: charDataOpponent.name}; - } - else - { - inputCharacterOpponent.value = {id: "dad", text: "Dad"}; + inputCharacterOpponent.value = (charDataOpponent != null) ? + {id: charIdOpponent, text: charDataOpponent.name} : + {id: "dad", text: "Dad"}; } var inputCharacterGirlfriend:Null = toolbox.findComponent('inputCharacterGirlfriend', DropDown); var charIdGirlfriend:String = currentSongMetadata.playData.characters.girlfriend; var charDataGirlfriend:Null = CharacterDataParser.fetchCharacterData(charIdGirlfriend); - if (charDataGirlfriend != null) + if (inputCharacterGirlfriend != null) { - inputCharacterGirlfriend.value = {id: charIdGirlfriend, text: charDataGirlfriend.name}; - } - else - { - inputCharacterGirlfriend.value = {id: "none", text: "None"}; + inputCharacterGirlfriend.value = (charDataGirlfriend != null) ? + {id: charIdGirlfriend, text: charDataGirlfriend.name} : + {id: "none", text: "None"}; } } @@ -4004,7 +4017,7 @@ class ChartEditorState extends HaxeUIState this.scrollPositionInPixels = value; // Move the grid sprite to the correct position. - if (gridTiledSprite != null) + if (gridTiledSprite != null && gridPlayheadScrollArea != null) { if (isViewDownscroll) { diff --git a/source/funkin/ui/haxeui/components/FunkinClickLabel.hx b/source/funkin/ui/haxeui/components/FunkinClickLabel.hx new file mode 100644 index 000000000..77c9dbc0f --- /dev/null +++ b/source/funkin/ui/haxeui/components/FunkinClickLabel.hx @@ -0,0 +1,30 @@ +package funkin.ui.haxeui.components; + +import haxe.ui.components.Label; +import funkin.input.Cursor; +import haxe.ui.events.MouseEvent; + +/** + * A HaxeUI label which: + * - Changes the current cursor when hovered over (assume an onClick handler will be added!). + */ +class FunkinClickLabel extends Label +{ + public function new() + { + super(); + + this.onMouseOver = handleMouseOver; + this.onMouseOut = handleMouseOut; + } + + private function handleMouseOver(event:MouseEvent) + { + Cursor.cursorMode = Pointer; + } + + private function handleMouseOut(event:MouseEvent) + { + Cursor.cursorMode = Default; + } +} diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index c606e469f..11529774f 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -126,6 +126,11 @@ class Constants */ public static final DEFAULT_CHARACTER:String = 'bf'; + /** + * Default player character for health icons. + */ + public static final DEFAULT_HEALTH_ICON:String = 'face'; + /** * Default stage for charts. */