2023-07-13 20:25:44 -04:00
|
|
|
package funkin.play.notes.notestyle;
|
|
|
|
|
|
|
|
import flixel.graphics.frames.FlxAtlasFrames;
|
|
|
|
import flixel.graphics.frames.FlxFramesCollection;
|
|
|
|
import funkin.data.animation.AnimationData;
|
|
|
|
import funkin.data.IRegistryEntry;
|
2024-02-22 18:55:24 -05:00
|
|
|
import funkin.graphics.FunkinSprite;
|
2023-07-13 20:25:44 -04:00
|
|
|
import funkin.data.notestyle.NoteStyleData;
|
|
|
|
import funkin.data.notestyle.NoteStyleRegistry;
|
|
|
|
import funkin.data.notestyle.NoteStyleRegistry;
|
|
|
|
import funkin.util.assets.FlxAnimationUtil;
|
|
|
|
|
|
|
|
using funkin.data.animation.AnimationData.AnimationDataUtil;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds the data for what assets to use for a note style,
|
|
|
|
* and provides convenience methods for building sprites based on them.
|
|
|
|
*/
|
|
|
|
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* The ID of the note style.
|
|
|
|
*/
|
|
|
|
public final id:String;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Note style data as parsed from the JSON file.
|
|
|
|
*/
|
|
|
|
public final _data:NoteStyleData;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The note style to use if this one doesn't have a certain asset.
|
|
|
|
* This can be recursive, ehe.
|
|
|
|
*/
|
|
|
|
final fallback:Null<NoteStyle>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param id The ID of the JSON file to parse.
|
|
|
|
*/
|
|
|
|
public function new(id:String)
|
|
|
|
{
|
|
|
|
this.id = id;
|
|
|
|
_data = _fetchData(id);
|
|
|
|
|
|
|
|
if (_data == null)
|
|
|
|
{
|
|
|
|
throw 'Could not parse note style data for id: $id';
|
|
|
|
}
|
|
|
|
|
|
|
|
this.fallback = NoteStyleRegistry.instance.fetchEntry(getFallbackID());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the readable name of the note style.
|
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
public function getName():String
|
|
|
|
{
|
|
|
|
return _data.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the author of the note style.
|
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
public function getAuthor():String
|
|
|
|
{
|
|
|
|
return _data.author;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the note style ID of the parent note style.
|
|
|
|
* @return The string ID, or `null` if there is no parent.
|
|
|
|
*/
|
|
|
|
function getFallbackID():Null<String>
|
|
|
|
{
|
|
|
|
return _data.fallback;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function buildNoteSprite(target:NoteSprite):Void
|
|
|
|
{
|
|
|
|
// Apply the note sprite frames.
|
|
|
|
var atlas:FlxAtlasFrames = buildNoteFrames(false);
|
|
|
|
|
|
|
|
if (atlas == null)
|
|
|
|
{
|
|
|
|
throw 'Could not load spritesheet for note style: $id';
|
|
|
|
}
|
|
|
|
|
|
|
|
target.frames = atlas;
|
|
|
|
|
|
|
|
target.scale.x = _data.assets.note.scale;
|
|
|
|
target.scale.y = _data.assets.note.scale;
|
|
|
|
target.antialiasing = !_data.assets.note.isPixel;
|
|
|
|
|
|
|
|
// Apply the animations.
|
|
|
|
buildNoteAnimations(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
var noteFrames:FlxAtlasFrames = null;
|
|
|
|
|
|
|
|
function buildNoteFrames(force:Bool = false):FlxAtlasFrames
|
|
|
|
{
|
2024-02-22 18:55:24 -05:00
|
|
|
if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath())))
|
|
|
|
{
|
|
|
|
FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}');
|
|
|
|
}
|
|
|
|
|
2024-02-23 00:16:11 -05:00
|
|
|
// Purge the note frames if the cached atlas is invalid.
|
|
|
|
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
|
|
|
|
|
2023-07-13 20:25:44 -04:00
|
|
|
if (noteFrames != null && !force) return noteFrames;
|
|
|
|
|
|
|
|
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
|
|
|
|
|
2023-09-08 17:46:44 -04:00
|
|
|
if (noteFrames == null)
|
|
|
|
{
|
2023-08-22 04:27:30 -04:00
|
|
|
throw 'Could not load note frames for note style: $id';
|
|
|
|
}
|
|
|
|
|
2023-07-13 20:25:44 -04:00
|
|
|
return noteFrames;
|
|
|
|
}
|
|
|
|
|
2023-08-28 15:03:29 -04:00
|
|
|
function getNoteAssetPath(raw:Bool = false):String
|
2023-07-13 20:25:44 -04:00
|
|
|
{
|
|
|
|
if (raw)
|
|
|
|
{
|
|
|
|
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
|
|
|
|
if (rawPath == null) return fallback.getNoteAssetPath(true);
|
|
|
|
return rawPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// library:path
|
|
|
|
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
|
|
|
if (parts.length == 1) return getNoteAssetPath(true);
|
|
|
|
return parts[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getNoteAssetLibrary():Null<String>
|
|
|
|
{
|
|
|
|
// library:path
|
|
|
|
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
|
|
|
if (parts.length == 1) return null;
|
|
|
|
return parts[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildNoteAnimations(target:NoteSprite):Void
|
|
|
|
{
|
|
|
|
var leftData:AnimationData = fetchNoteAnimationData(LEFT);
|
2023-09-08 17:46:44 -04:00
|
|
|
target.animation.addByPrefix('purpleScroll', leftData.prefix, leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY);
|
2023-07-13 20:25:44 -04:00
|
|
|
var downData:AnimationData = fetchNoteAnimationData(DOWN);
|
2023-09-08 17:46:44 -04:00
|
|
|
target.animation.addByPrefix('blueScroll', downData.prefix, downData.frameRate, downData.looped, downData.flipX, downData.flipY);
|
2023-07-13 20:25:44 -04:00
|
|
|
var upData:AnimationData = fetchNoteAnimationData(UP);
|
2023-09-08 17:46:44 -04:00
|
|
|
target.animation.addByPrefix('greenScroll', upData.prefix, upData.frameRate, upData.looped, upData.flipX, upData.flipY);
|
2023-07-13 20:25:44 -04:00
|
|
|
var rightData:AnimationData = fetchNoteAnimationData(RIGHT);
|
2023-09-08 17:46:44 -04:00
|
|
|
target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
|
2023-07-13 20:25:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function fetchNoteAnimationData(dir:NoteDirection):AnimationData
|
|
|
|
{
|
|
|
|
var result:Null<AnimationData> = switch (dir)
|
|
|
|
{
|
|
|
|
case LEFT: _data.assets.note.data.left.toNamed();
|
|
|
|
case DOWN: _data.assets.note.data.down.toNamed();
|
|
|
|
case UP: _data.assets.note.data.up.toNamed();
|
|
|
|
case RIGHT: _data.assets.note.data.right.toNamed();
|
|
|
|
};
|
|
|
|
|
|
|
|
return (result == null) ? fallback.fetchNoteAnimationData(dir) : result;
|
|
|
|
}
|
|
|
|
|
2023-08-28 15:03:29 -04:00
|
|
|
public function getHoldNoteAssetPath(raw:Bool = false):String
|
2023-07-13 20:25:44 -04:00
|
|
|
{
|
|
|
|
if (raw)
|
|
|
|
{
|
2023-08-22 04:27:30 -04:00
|
|
|
// TODO: figure out why ?. didn't work here
|
|
|
|
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
2023-07-13 20:25:44 -04:00
|
|
|
return (rawPath == null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// library:path
|
|
|
|
var parts = getHoldNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
|
|
|
if (parts.length == 1) return Paths.image(parts[0]);
|
|
|
|
return Paths.image(parts[1], parts[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isHoldNotePixel():Bool
|
|
|
|
{
|
|
|
|
var data = _data?.assets?.holdNote;
|
|
|
|
if (data == null) return fallback.isHoldNotePixel();
|
|
|
|
return data.isPixel;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function fetchHoldNoteScale():Float
|
|
|
|
{
|
|
|
|
var data = _data?.assets?.holdNote;
|
|
|
|
if (data == null) return fallback.fetchHoldNoteScale();
|
|
|
|
return data.scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function applyStrumlineFrames(target:StrumlineNote):Void
|
|
|
|
{
|
|
|
|
// TODO: Add support for multi-Sparrow.
|
|
|
|
// Will be less annoying after this is merged: https://github.com/HaxeFlixel/flixel/pull/2772
|
|
|
|
|
|
|
|
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath(), getStrumlineAssetLibrary());
|
|
|
|
|
|
|
|
if (atlas == null)
|
|
|
|
{
|
|
|
|
throw 'Could not load spritesheet for note style: $id';
|
|
|
|
}
|
|
|
|
|
|
|
|
target.frames = atlas;
|
|
|
|
|
|
|
|
target.scale.x = _data.assets.noteStrumline.scale;
|
|
|
|
target.scale.y = _data.assets.noteStrumline.scale;
|
|
|
|
target.antialiasing = !_data.assets.noteStrumline.isPixel;
|
|
|
|
}
|
|
|
|
|
2023-08-28 15:03:29 -04:00
|
|
|
function getStrumlineAssetPath(raw:Bool = false):String
|
2023-07-13 20:25:44 -04:00
|
|
|
{
|
|
|
|
if (raw)
|
|
|
|
{
|
|
|
|
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
|
|
|
|
if (rawPath == null) return fallback.getStrumlineAssetPath(true);
|
|
|
|
return rawPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// library:path
|
|
|
|
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
|
|
|
if (parts.length == 1) return getStrumlineAssetPath(true);
|
|
|
|
return parts[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getStrumlineAssetLibrary():Null<String>
|
|
|
|
{
|
|
|
|
// library:path
|
|
|
|
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
|
|
|
if (parts.length == 1) return null;
|
|
|
|
return parts[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function applyStrumlineAnimations(target:StrumlineNote, dir:NoteDirection):Void
|
|
|
|
{
|
|
|
|
FlxAnimationUtil.addAtlasAnimations(target, getStrumlineAnimationData(dir));
|
|
|
|
}
|
|
|
|
|
|
|
|
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
|
|
|
|
{
|
|
|
|
var result:Array<AnimationData> = switch (dir)
|
|
|
|
{
|
|
|
|
case NoteDirection.LEFT: [
|
|
|
|
_data.assets.noteStrumline.data.leftStatic.toNamed('static'),
|
|
|
|
_data.assets.noteStrumline.data.leftPress.toNamed('press'),
|
|
|
|
_data.assets.noteStrumline.data.leftConfirm.toNamed('confirm'),
|
|
|
|
_data.assets.noteStrumline.data.leftConfirmHold.toNamed('confirm-hold'),
|
|
|
|
];
|
|
|
|
case NoteDirection.DOWN: [
|
|
|
|
_data.assets.noteStrumline.data.downStatic.toNamed('static'),
|
|
|
|
_data.assets.noteStrumline.data.downPress.toNamed('press'),
|
|
|
|
_data.assets.noteStrumline.data.downConfirm.toNamed('confirm'),
|
|
|
|
_data.assets.noteStrumline.data.downConfirmHold.toNamed('confirm-hold'),
|
|
|
|
];
|
|
|
|
case NoteDirection.UP: [
|
|
|
|
_data.assets.noteStrumline.data.upStatic.toNamed('static'),
|
|
|
|
_data.assets.noteStrumline.data.upPress.toNamed('press'),
|
|
|
|
_data.assets.noteStrumline.data.upConfirm.toNamed('confirm'),
|
|
|
|
_data.assets.noteStrumline.data.upConfirmHold.toNamed('confirm-hold'),
|
|
|
|
];
|
|
|
|
case NoteDirection.RIGHT: [
|
|
|
|
_data.assets.noteStrumline.data.rightStatic.toNamed('static'),
|
|
|
|
_data.assets.noteStrumline.data.rightPress.toNamed('press'),
|
|
|
|
_data.assets.noteStrumline.data.rightConfirm.toNamed('confirm'),
|
|
|
|
_data.assets.noteStrumline.data.rightConfirmHold.toNamed('confirm-hold'),
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function applyStrumlineOffsets(target:StrumlineNote)
|
|
|
|
{
|
|
|
|
target.x += _data.assets.noteStrumline.offsets[0];
|
|
|
|
target.y += _data.assets.noteStrumline.offsets[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getStrumlineScale():Float
|
|
|
|
{
|
|
|
|
return _data.assets.noteStrumline.scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isNoteSplashEnabled():Bool
|
|
|
|
{
|
|
|
|
var data = _data?.assets?.noteSplash?.data;
|
|
|
|
if (data == null) return fallback.isNoteSplashEnabled();
|
|
|
|
return data.enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isHoldNoteCoverEnabled():Bool
|
|
|
|
{
|
|
|
|
var data = _data?.assets?.holdNoteCover?.data;
|
|
|
|
if (data == null) return fallback.isHoldNoteCoverEnabled();
|
|
|
|
return data.enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function destroy():Void {}
|
|
|
|
|
|
|
|
public function toString():String
|
|
|
|
{
|
|
|
|
return 'NoteStyle($id)';
|
|
|
|
}
|
|
|
|
|
2023-09-08 17:46:44 -04:00
|
|
|
static function _fetchData(id:String):Null<NoteStyleData>
|
2023-07-13 20:25:44 -04:00
|
|
|
{
|
2023-08-22 04:27:30 -04:00
|
|
|
return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
2023-07-13 20:25:44 -04:00
|
|
|
}
|
|
|
|
}
|