Fixes for a few bugs in the chart editor.

This commit is contained in:
EliteMasterEric 2023-10-17 02:42:52 -04:00
parent 6dbf958d75
commit f51592963e
6 changed files with 146 additions and 74 deletions

2
assets

@ -1 +1 @@
Subproject commit 8104d43e584a1f25e574438d7b21a7e671358969
Subproject commit b9338b97214f71b192f5cec760c5442ec2e8cbed

View file

@ -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<String>;
/**
* 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<String>):Null<String>
{
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<HealthIconData>):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>):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<String>):Void
{
if (correctCharacterId(charId) != charId)
if (charId == null || correctCharacterId(charId) != charId)
{
// This will recursively trigger loadCharacter to be called again.
characterId = correctCharacterId(charId);
return;
}

View file

@ -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);
}
}

View file

@ -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<SongNoteData>
{
var result:Array<SongNoteData> = currentSongChartData.notes.get(selectedDifficulty);
var result:Null<Array<SongNoteData>> = 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<DropDown> = toolbox.findComponent('inputStage', DropDown);
var stageId:String = currentSongMetadata.playData.stage;
var stageData:Null<StageData> = 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<DropDown> = toolbox.findComponent('inputCharacterPlayer', DropDown);
var charIdPlayer:String = currentSongMetadata.playData.characters.player;
var charDataPlayer:Null<CharacterData> = 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<DropDown> = toolbox.findComponent('inputCharacterOpponent', DropDown);
var charIdOpponent:String = currentSongMetadata.playData.characters.opponent;
var charDataOpponent:Null<CharacterData> = 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<DropDown> = toolbox.findComponent('inputCharacterGirlfriend', DropDown);
var charIdGirlfriend:String = currentSongMetadata.playData.characters.girlfriend;
var charDataGirlfriend:Null<CharacterData> = 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)
{

View file

@ -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;
}
}

View file

@ -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.
*/