2022-12-17 20:19:42 +00:00
|
|
|
package funkin.ui.haxeui.components;
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
import funkin.modding.events.ScriptEvent.GhostMissNoteScriptEvent;
|
|
|
|
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
|
|
|
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
|
|
|
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
|
|
|
import haxe.ui.core.IDataComponent;
|
2022-12-17 20:19:42 +00:00
|
|
|
import funkin.play.character.BaseCharacter;
|
|
|
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
|
|
|
import haxe.ui.containers.Box;
|
|
|
|
import haxe.ui.core.Component;
|
|
|
|
import haxe.ui.events.AnimationEvent;
|
|
|
|
import haxe.ui.geom.Size;
|
|
|
|
import haxe.ui.layouts.DefaultLayout;
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
typedef AnimationInfo =
|
2022-12-17 20:19:42 +00:00
|
|
|
{
|
2023-01-23 03:25:45 +00:00
|
|
|
var name:String;
|
|
|
|
var prefix:String;
|
|
|
|
var frameRate:Null<Int>; // default 30
|
|
|
|
var looped:Null<Bool>; // default true
|
|
|
|
var flipX:Null<Bool>; // default false
|
|
|
|
var flipY:Null<Bool>; // default false
|
2022-12-17 20:19:42 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* A variant of SparrowPlayer which loads a BaseCharacter instead.
|
|
|
|
* This allows it to play appropriate animations based on song events.
|
|
|
|
*/
|
2022-12-17 20:19:42 +00:00
|
|
|
@:composite(Layout)
|
|
|
|
class CharacterPlayer extends Box
|
|
|
|
{
|
2023-09-12 22:37:59 +00:00
|
|
|
var character:Null<BaseCharacter>;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-08-28 19:03:29 +00:00
|
|
|
public function new(defaultToBf:Bool = true)
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
|
|
|
super();
|
2023-12-19 21:12:25 +00:00
|
|
|
// _overrideSkipTransformChildren = false;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
if (defaultToBf)
|
|
|
|
{
|
|
|
|
loadCharacter('bf');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public var charId(get, set):String;
|
|
|
|
|
|
|
|
function get_charId():String
|
|
|
|
{
|
2023-09-12 22:37:59 +00:00
|
|
|
return character?.characterId ?? '';
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function set_charId(value:String):String
|
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
loadCharacter(value);
|
2023-01-23 03:25:45 +00:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
public var charName(get, never):String;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
function get_charName():String
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-09-12 22:37:59 +00:00
|
|
|
return character?.characterName ?? "Unknown";
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
// possible haxeui bug: if listener is added after event is dispatched, event is "lost"... is it smart to "collect and redispatch"? Not sure
|
|
|
|
var _redispatchLoaded:Bool = false;
|
|
|
|
// possible haxeui bug: if listener is added after event is dispatched, event is "lost"... is it smart to "collect and redispatch"? Not sure
|
|
|
|
var _redispatchStart:Bool = false;
|
|
|
|
var _characterLoaded:Bool = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads a character by ID.
|
|
|
|
* @param id The ID of the character to load.
|
|
|
|
*/
|
|
|
|
public function loadCharacter(id:String):Void
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
if (id == null) return;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
if (character != null)
|
|
|
|
{
|
|
|
|
remove(character);
|
|
|
|
character.destroy();
|
|
|
|
character = null;
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
// Prevent script issues by fetching with debug=true.
|
|
|
|
var newCharacter:BaseCharacter = CharacterDataParser.fetchCharacter(id, true);
|
2023-09-12 22:37:59 +00:00
|
|
|
if (newCharacter == null)
|
|
|
|
{
|
|
|
|
character = null;
|
|
|
|
return; // Fail if character doesn't exist.
|
|
|
|
}
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
// Assign character.
|
2023-01-23 03:25:45 +00:00
|
|
|
character = newCharacter;
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
// Set character properties.
|
|
|
|
if (characterType != null) character.characterType = characterType;
|
|
|
|
if (flip) character.flipX = !character.flipX;
|
|
|
|
if (targetScale != 1.0) character.setScale(targetScale);
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
character.animation.callback = function(name:String = '', frameNumber:Int = -1, frameIndex:Int = -1) {
|
2023-01-23 03:25:45 +00:00
|
|
|
@:privateAccess
|
|
|
|
character.onAnimationFrame(name, frameNumber, frameIndex);
|
|
|
|
dispatch(new AnimationEvent(AnimationEvent.FRAME));
|
|
|
|
};
|
2023-06-08 20:48:13 +00:00
|
|
|
character.animation.finishCallback = function(name:String = '') {
|
2023-01-23 03:25:45 +00:00
|
|
|
@:privateAccess
|
|
|
|
character.onAnimationFinished(name);
|
|
|
|
dispatch(new AnimationEvent(AnimationEvent.END));
|
|
|
|
};
|
|
|
|
add(character);
|
|
|
|
|
|
|
|
invalidateComponentLayout();
|
|
|
|
|
|
|
|
if (hasEvent(AnimationEvent.LOADED))
|
|
|
|
{
|
|
|
|
dispatch(new AnimationEvent(AnimationEvent.LOADED));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_redispatchLoaded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* The character type (such as BF, Dad, GF, etc).
|
|
|
|
*/
|
|
|
|
public var characterType(default, set):CharacterType;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
function set_characterType(value:CharacterType):CharacterType
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
if (character != null) character.characterType = value;
|
|
|
|
return characterType = value;
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public var flip(default, set):Bool;
|
|
|
|
|
|
|
|
function set_flip(value:Bool):Bool
|
|
|
|
{
|
|
|
|
if (value == flip) return value;
|
|
|
|
|
|
|
|
if (character != null)
|
|
|
|
{
|
|
|
|
character.flipX = !character.flipX;
|
|
|
|
}
|
|
|
|
|
|
|
|
return flip = value;
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
public var targetScale(default, set):Float = 1.0;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
function set_targetScale(value:Float):Float
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
if (value == targetScale) return value;
|
|
|
|
|
2023-01-23 03:25:45 +00:00
|
|
|
if (character != null)
|
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
character.setScale(value);
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
2023-06-08 20:48:13 +00:00
|
|
|
|
|
|
|
return targetScale = value;
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
function onFrame(name:String, frameNumber:Int, frameIndex:Int):Void
|
|
|
|
{
|
|
|
|
dispatch(new AnimationEvent(AnimationEvent.FRAME));
|
|
|
|
}
|
|
|
|
|
|
|
|
function onFinish(name:String):Void
|
|
|
|
{
|
|
|
|
dispatch(new AnimationEvent(AnimationEvent.END));
|
|
|
|
}
|
|
|
|
|
|
|
|
override function repositionChildren():Void
|
|
|
|
{
|
|
|
|
super.repositionChildren();
|
|
|
|
character.x = this.screenX;
|
|
|
|
character.y = this.screenY;
|
|
|
|
|
|
|
|
// Apply animation offsets, so the character is positioned correctly based on the animation.
|
|
|
|
@:privateAccess var animOffsets:Array<Float> = character.animOffsets;
|
|
|
|
|
|
|
|
character.x -= animOffsets[0] * targetScale * (flip ? -1 : 1);
|
|
|
|
character.y -= animOffsets[1] * targetScale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when an update event is hit in the song.
|
|
|
|
* Used to play character animations.
|
|
|
|
* @param event The event.
|
|
|
|
*/
|
|
|
|
public function onUpdate(event:UpdateScriptEvent):Void
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
|
|
|
if (character != null) character.onUpdate(event);
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* Called when an beat is hit in the song
|
|
|
|
* Used to play character animations.
|
|
|
|
* @param event The event.
|
|
|
|
*/
|
2023-01-23 03:25:45 +00:00
|
|
|
public function onBeatHit(event:SongTimeScriptEvent):Void
|
|
|
|
{
|
|
|
|
if (character != null) character.onBeatHit(event);
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* Called when a step is hit in the song
|
|
|
|
* Used to play character animations.
|
|
|
|
* @param event The event.
|
|
|
|
*/
|
2023-01-23 03:25:45 +00:00
|
|
|
public function onStepHit(event:SongTimeScriptEvent):Void
|
|
|
|
{
|
|
|
|
if (character != null) character.onStepHit(event);
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* Called when a note is hit in the song
|
|
|
|
* Used to play character animations.
|
|
|
|
* @param event The event.
|
|
|
|
*/
|
2023-01-23 03:25:45 +00:00
|
|
|
public function onNoteHit(event:NoteScriptEvent):Void
|
|
|
|
{
|
|
|
|
if (character != null) character.onNoteHit(event);
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* Called when a note is missed in the song
|
|
|
|
* Used to play character animations.
|
|
|
|
* @param event The event.
|
|
|
|
*/
|
2023-01-23 03:25:45 +00:00
|
|
|
public function onNoteMiss(event:NoteScriptEvent):Void
|
|
|
|
{
|
|
|
|
if (character != null) character.onNoteMiss(event);
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
/**
|
|
|
|
* Called when a key is pressed but no note is hit in the song
|
|
|
|
* Used to play character animations.
|
|
|
|
* @param event The event.
|
|
|
|
*/
|
2023-01-23 03:25:45 +00:00
|
|
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void
|
|
|
|
{
|
|
|
|
if (character != null) character.onNoteGhostMiss(event);
|
|
|
|
}
|
2022-12-17 20:19:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@:access(funkin.ui.haxeui.components.CharacterPlayer)
|
|
|
|
private class Layout extends DefaultLayout
|
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
public override function resizeChildren():Void
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
super.resizeChildren();
|
|
|
|
|
|
|
|
var player:CharacterPlayer = cast(_component, CharacterPlayer);
|
|
|
|
var character:BaseCharacter = player.character;
|
|
|
|
if (character == null)
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
return super.resizeChildren();
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 20:48:13 +00:00
|
|
|
character.cornerPosition.set(0, 0);
|
|
|
|
// character.setGraphicSize(Std.int(innerWidth), Std.int(innerHeight));
|
2023-01-23 03:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public override function calcAutoSize(exclusions:Array<Component> = null):Size
|
|
|
|
{
|
2023-06-08 20:48:13 +00:00
|
|
|
var player:CharacterPlayer = cast(_component, CharacterPlayer);
|
|
|
|
var character:BaseCharacter = player.character;
|
|
|
|
if (character == null)
|
2023-01-23 03:25:45 +00:00
|
|
|
{
|
|
|
|
return super.calcAutoSize(exclusions);
|
|
|
|
}
|
2023-06-08 20:48:13 +00:00
|
|
|
var size:Size = new Size();
|
|
|
|
size.width = character.width + paddingLeft + paddingRight;
|
|
|
|
size.height = character.height + paddingTop + paddingBottom;
|
2023-01-23 03:25:45 +00:00
|
|
|
return size;
|
|
|
|
}
|
2022-12-17 20:19:42 +00:00
|
|
|
}
|