2023-10-26 09:46:22 +00:00
|
|
|
package funkin.ui.debug.charting.components;
|
2023-01-23 00:55:30 +00:00
|
|
|
|
2024-01-04 02:10:14 +00:00
|
|
|
import funkin.data.event.SongEventRegistry;
|
2023-07-19 05:29:13 +00:00
|
|
|
import flixel.graphics.frames.FlxAtlasFrames;
|
2023-01-23 00:55:30 +00:00
|
|
|
import openfl.display.BitmapData;
|
|
|
|
import openfl.utils.Assets;
|
|
|
|
import flixel.FlxObject;
|
|
|
|
import flixel.FlxBasic;
|
|
|
|
import flixel.FlxSprite;
|
|
|
|
import flixel.graphics.frames.FlxFramesCollection;
|
|
|
|
import flixel.graphics.frames.FlxTileFrames;
|
|
|
|
import flixel.math.FlxPoint;
|
2023-09-08 21:46:44 +00:00
|
|
|
import funkin.data.song.SongData.SongEventData;
|
2024-01-04 15:00:39 +00:00
|
|
|
import haxe.ui.tooltips.ToolTipRegionOptions;
|
|
|
|
import funkin.util.HaxeUIUtil;
|
|
|
|
import haxe.ui.tooltips.ToolTipManager;
|
2023-01-23 00:55:30 +00:00
|
|
|
|
|
|
|
/**
|
2023-10-26 09:46:22 +00:00
|
|
|
* A sprite that can be used to display a song event in a chart.
|
2023-01-23 00:55:30 +00:00
|
|
|
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
|
|
|
*/
|
2023-08-30 06:24:35 +00:00
|
|
|
@:nullSafety
|
2023-01-23 00:55:30 +00:00
|
|
|
class ChartEditorEventSprite extends FlxSprite
|
|
|
|
{
|
2023-07-19 05:29:13 +00:00
|
|
|
public static final DEFAULT_EVENT = 'Default';
|
|
|
|
|
2023-01-23 03:25:45 +00:00
|
|
|
public var parentState:ChartEditorState;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The note data that this sprite represents.
|
|
|
|
* You can set this to null to kill the sprite and flag it for recycling.
|
|
|
|
*/
|
2023-08-28 19:03:29 +00:00
|
|
|
public var eventData(default, set):Null<SongEventData> = null;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The image used for all song events. Cached for performance.
|
|
|
|
*/
|
2023-08-28 19:03:29 +00:00
|
|
|
static var eventSpriteBasic:Null<BitmapData> = null;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-10-26 09:46:22 +00:00
|
|
|
public var overrideStepTime(default, set):Null<Float> = null;
|
|
|
|
|
2024-01-04 15:00:39 +00:00
|
|
|
public var tooltip:ToolTipRegionOptions;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether this sprite is a "ghost" sprite used when hovering to place a new event.
|
|
|
|
*/
|
|
|
|
public var isGhost:Bool = false;
|
|
|
|
|
2023-10-26 09:46:22 +00:00
|
|
|
function set_overrideStepTime(value:Null<Float>):Null<Float>
|
|
|
|
{
|
|
|
|
if (overrideStepTime == value) return overrideStepTime;
|
|
|
|
|
|
|
|
overrideStepTime = value;
|
|
|
|
updateEventPosition();
|
|
|
|
return overrideStepTime;
|
|
|
|
}
|
|
|
|
|
2024-01-04 15:00:39 +00:00
|
|
|
public function new(parent:ChartEditorState, isGhost:Bool = false)
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
|
|
|
super();
|
|
|
|
|
|
|
|
this.parentState = parent;
|
2024-01-04 15:00:39 +00:00
|
|
|
this.isGhost = isGhost;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2024-01-04 15:00:39 +00:00
|
|
|
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
|
2023-07-19 05:29:13 +00:00
|
|
|
this.frames = buildFrames();
|
|
|
|
|
|
|
|
buildAnimations();
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
static var eventFrames:Null<FlxFramesCollection> = null;
|
|
|
|
|
2023-07-19 05:29:13 +00:00
|
|
|
/**
|
|
|
|
* Build a set of animations to allow displaying different types of chart events.
|
|
|
|
* @param force `true` to force rebuilding the frames.
|
|
|
|
*/
|
2023-08-28 19:03:29 +00:00
|
|
|
static function buildFrames(force:Bool = false):FlxFramesCollection
|
2023-07-19 05:29:13 +00:00
|
|
|
{
|
|
|
|
if (eventFrames != null && !force) return eventFrames;
|
2023-08-31 22:47:23 +00:00
|
|
|
|
|
|
|
initEmptyEventFrames();
|
|
|
|
if (eventFrames == null) throw 'Failed to initialize empty event frames.';
|
2023-07-19 05:29:13 +00:00
|
|
|
|
|
|
|
// Push the default event as a frame.
|
|
|
|
var defaultFrames:FlxAtlasFrames = Paths.getSparrowAtlas('ui/chart-editor/events/$DEFAULT_EVENT');
|
|
|
|
defaultFrames.parent.persist = true;
|
|
|
|
for (frame in defaultFrames.frames)
|
|
|
|
{
|
|
|
|
eventFrames.pushFrame(frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Push all the other events as frames.
|
2024-01-04 02:10:14 +00:00
|
|
|
for (eventName in SongEventRegistry.listEventIds())
|
2023-07-19 05:29:13 +00:00
|
|
|
{
|
2023-07-23 00:16:43 +00:00
|
|
|
var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName'));
|
|
|
|
if (!exists) continue; // No graphic for this event.
|
|
|
|
|
2023-07-19 05:29:13 +00:00
|
|
|
var frames:FlxAtlasFrames = Paths.getSparrowAtlas('ui/chart-editor/events/$eventName');
|
2023-07-23 00:16:43 +00:00
|
|
|
if (frames == null) continue; // Could not load graphic for this event.
|
|
|
|
|
2023-07-19 05:29:13 +00:00
|
|
|
frames.parent.persist = true;
|
|
|
|
for (frame in frames.frames)
|
|
|
|
{
|
|
|
|
eventFrames.pushFrame(frame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return eventFrames;
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
@:nullSafety(Off)
|
|
|
|
static function initEmptyEventFrames():Void
|
|
|
|
{
|
|
|
|
eventFrames = new FlxAtlasFrames(null);
|
|
|
|
}
|
|
|
|
|
2023-07-19 05:29:13 +00:00
|
|
|
function buildAnimations():Void
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2024-01-04 02:10:14 +00:00
|
|
|
var eventNames:Array<String> = [DEFAULT_EVENT].concat(SongEventRegistry.listEventIds());
|
2023-07-19 05:29:13 +00:00
|
|
|
for (eventName in eventNames)
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-07-19 05:29:13 +00:00
|
|
|
this.animation.addByPrefix(eventName, '${eventName}0', 24, false);
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
2023-07-19 05:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function correctAnimationName(name:String):String
|
|
|
|
{
|
|
|
|
if (this.animation.exists(name)) return name;
|
2023-08-22 08:27:30 +00:00
|
|
|
trace('Warning: Invalid animation name "${name}" for song event. Using "${DEFAULT_EVENT}"');
|
2023-07-19 05:29:13 +00:00
|
|
|
return DEFAULT_EVENT;
|
|
|
|
}
|
|
|
|
|
2024-01-04 13:20:34 +00:00
|
|
|
public function playAnimation(?name:String):Void
|
2023-07-19 05:29:13 +00:00
|
|
|
{
|
2024-01-04 13:20:34 +00:00
|
|
|
if (name == null) name = eventData?.event ?? DEFAULT_EVENT;
|
|
|
|
|
2023-07-19 05:29:13 +00:00
|
|
|
var correctedName = correctAnimationName(name);
|
|
|
|
this.animation.play(correctedName);
|
|
|
|
refresh();
|
|
|
|
}
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-07-19 05:29:13 +00:00
|
|
|
function refresh():Void
|
|
|
|
{
|
2023-01-23 03:25:45 +00:00
|
|
|
setGraphicSize(ChartEditorState.GRID_SIZE);
|
|
|
|
this.updateHitbox();
|
|
|
|
}
|
|
|
|
|
2023-08-28 19:03:29 +00:00
|
|
|
function set_eventData(value:Null<SongEventData>):Null<SongEventData>
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-10-27 05:42:05 +00:00
|
|
|
if (value == null)
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-10-27 05:42:05 +00:00
|
|
|
this.eventData = null;
|
2023-07-19 05:29:13 +00:00
|
|
|
// Disown parent. MAKE SURE TO REVIVE BEFORE REUSING
|
2023-01-23 03:25:45 +00:00
|
|
|
this.kill();
|
2023-10-27 05:42:05 +00:00
|
|
|
this.visible = false;
|
2024-01-04 15:00:39 +00:00
|
|
|
updateTooltipPosition();
|
2023-10-27 05:42:05 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this.visible = true;
|
|
|
|
playAnimation(value.event);
|
|
|
|
this.eventData = value;
|
|
|
|
// Update the position to match the note data.
|
|
|
|
updateEventPosition();
|
2024-01-04 15:00:39 +00:00
|
|
|
// Update the tooltip text.
|
|
|
|
this.tooltip.tipData = {text: this.eventData.buildTooltip()};
|
2023-01-23 03:25:45 +00:00
|
|
|
return this.eventData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function updateEventPosition(?origin:FlxObject)
|
|
|
|
{
|
2023-08-31 22:47:23 +00:00
|
|
|
if (this.eventData == null) return;
|
|
|
|
|
2023-01-23 03:25:45 +00:00
|
|
|
this.x = (ChartEditorState.STRUMLINE_SIZE * 2 + 1 - 1) * ChartEditorState.GRID_SIZE;
|
2023-09-13 18:51:12 +00:00
|
|
|
|
2023-10-26 09:46:22 +00:00
|
|
|
var stepTime:Float = (overrideStepTime != null) ? overrideStepTime : eventData.getStepTime();
|
2023-09-13 18:51:12 +00:00
|
|
|
this.y = stepTime * ChartEditorState.GRID_SIZE;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
if (origin != null)
|
|
|
|
{
|
|
|
|
this.x += origin.x;
|
|
|
|
this.y += origin.y;
|
|
|
|
}
|
2024-01-04 15:00:39 +00:00
|
|
|
|
|
|
|
this.updateTooltipPosition();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function updateTooltipPosition():Void
|
|
|
|
{
|
|
|
|
// No tooltip for ghost sprites.
|
|
|
|
if (this.isGhost) return;
|
|
|
|
|
|
|
|
if (this.eventData == null)
|
|
|
|
{
|
|
|
|
// Disable the tooltip.
|
|
|
|
ToolTipManager.instance.unregisterTooltipRegion(this.tooltip);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Update the position.
|
|
|
|
this.tooltip.left = this.x;
|
|
|
|
this.tooltip.top = this.y;
|
|
|
|
this.tooltip.width = this.width;
|
|
|
|
this.tooltip.height = this.height;
|
|
|
|
|
|
|
|
// Enable the tooltip.
|
|
|
|
ToolTipManager.instance.registerTooltipRegion(this.tooltip);
|
|
|
|
}
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-07-23 00:16:43 +00:00
|
|
|
* Return whether this event is currently visible.
|
2023-01-23 03:25:45 +00:00
|
|
|
*/
|
2023-07-26 03:11:12 +00:00
|
|
|
public function isEventVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-07-23 00:16:43 +00:00
|
|
|
// True if the note is above the view area.
|
|
|
|
var aboveViewArea = (this.y + this.height < viewAreaTop);
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-07-23 00:16:43 +00:00
|
|
|
// True if the note is below the view area.
|
|
|
|
var belowViewArea = (this.y > viewAreaBottom);
|
|
|
|
|
|
|
|
return !aboveViewArea && !belowViewArea;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return whether an event, if placed in the scene, would be visible.
|
|
|
|
*/
|
2023-07-26 03:11:12 +00:00
|
|
|
public static function wouldEventBeVisible(viewAreaBottom:Float, viewAreaTop:Float, eventData:SongEventData, ?origin:FlxObject):Bool
|
2023-07-23 00:16:43 +00:00
|
|
|
{
|
|
|
|
var noteHeight:Float = ChartEditorState.GRID_SIZE;
|
2023-09-13 18:51:12 +00:00
|
|
|
var notePosY:Float = eventData.getStepTime() * ChartEditorState.GRID_SIZE;
|
2023-07-23 00:16:43 +00:00
|
|
|
if (origin != null) notePosY += origin.y;
|
|
|
|
|
|
|
|
// True if the note is above the view area.
|
|
|
|
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-07-23 00:16:43 +00:00
|
|
|
// True if the note is below the view area.
|
|
|
|
var belowViewArea = (notePosY > viewAreaBottom);
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-07-23 00:16:43 +00:00
|
|
|
return !aboveViewArea && !belowViewArea;
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
2023-01-23 00:55:30 +00:00
|
|
|
}
|