Custom coutndown, judgements, and combo count now use the note style file instead of hardcoded paths.

This commit is contained in:
EliteMasterEric 2024-07-28 17:10:32 -04:00
parent d07564cdc2
commit 41bd0246ac
11 changed files with 840 additions and 338 deletions

2
assets

@ -1 +1 @@
Subproject commit 3a9e0510841533f96228609dcd50f2515443bb90
Subproject commit c4bd5281880ac2a1e26016c1219824d2f4247536

View file

@ -263,7 +263,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
* @param version The entry's version (use `fetchEntryVersion(id)`).
* @return The created entry.
*/
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
public function parseEntryDataWithMigration(id:String, version:Null<thx.semver.Version>):Null<J>
{
if (version == null)
{

View file

@ -0,0 +1,31 @@
# Note Style Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0]
### Added
- Added several new `assets`:
- `countdownThree`
- `countdownTwo`
- `countdownOne`
- `countdownGo`
- `judgementSick`
- `judgementGood`
- `judgementBad`
- `judgementShit`
- `comboNumber0`
- `comboNumber1`
- `comboNumber2`
- `comboNumber3`
- `comboNumber4`
- `comboNumber5`
- `comboNumber6`
- `comboNumber7`
- `comboNumber8`
- `comboNumber9`
## [1.0.0]
Initial version.

View file

@ -74,6 +74,84 @@ typedef NoteStyleAssetsData =
*/
@:optional
var holdNoteCover:NoteStyleAssetData<NoteStyleData_HoldNoteCover>;
/**
* The THREE sound (and an optional pre-READY graphic).
*/
@:optional
var countdownThree:NoteStyleAssetData<NoteStyleData_Countdown>;
/**
* The TWO sound and READY graphic.
*/
@:optional
var countdownTwo:NoteStyleAssetData<NoteStyleData_Countdown>;
/**
* The ONE sound and SET graphic.
*/
@:optional
var countdownOne:NoteStyleAssetData<NoteStyleData_Countdown>;
/**
* The GO sound and GO! graphic.
*/
@:optional
var countdownGo:NoteStyleAssetData<NoteStyleData_Countdown>;
/**
* The SICK! judgement.
*/
@:optional
var judgementSick:NoteStyleAssetData<NoteStyleData_Judgement>;
/**
* The GOOD! judgement.
*/
@:optional
var judgementGood:NoteStyleAssetData<NoteStyleData_Judgement>;
/**
* The BAD! judgement.
*/
@:optional
var judgementBad:NoteStyleAssetData<NoteStyleData_Judgement>;
/**
* The SHIT! judgement.
*/
@:optional
var judgementShit:NoteStyleAssetData<NoteStyleData_Judgement>;
@:optional
var comboNumber0:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber1:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber2:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber3:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber4:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber5:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber6:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber7:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber8:NoteStyleAssetData<NoteStyleData_ComboNum>;
@:optional
var comboNumber9:NoteStyleAssetData<NoteStyleData_ComboNum>;
}
/**
@ -120,7 +198,8 @@ typedef NoteStyleAssetData<T> =
/**
* The structure of this data depends on the asset.
*/
var data:T;
@:optional
var data:Null<T>;
}
typedef NoteStyleData_Note =
@ -131,7 +210,14 @@ typedef NoteStyleData_Note =
var right:UnnamedAnimationData;
}
typedef NoteStyleData_Countdown =
{
var audioPath:String;
}
typedef NoteStyleData_HoldNote = {}
typedef NoteStyleData_Judgement = {}
typedef NoteStyleData_ComboNum = {}
/**
* Data on animations for each direction of the strumline.

View file

@ -11,9 +11,9 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateNoteStyleData()` function.
*/
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.0.0";
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.1.0";
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
public static var instance(get, never):NoteStyleRegistry;
static var _instance:Null<NoteStyleRegistry> = null;

View file

@ -40,8 +40,6 @@ class Countdown
static var fallbackNoteStyle:Null<NoteStyle>;
static var isPixel:Bool = false;
/**
* The currently running countdown. This will be null if there is no countdown running.
*/
@ -204,18 +202,22 @@ class Countdown
*/
public static function reset()
{
noteStyle = NoteStyleRegistry.instance.fetchDefault();
isPixel = false;
noteStyle = null;
}
static function fetchNoteStyle():Void
/**
* Retrieve the note style data (if we haven't already)
* @param noteStyleId The id of the note style to fetch. Defaults to the one used by the current PlayState.
* @param force Fetch the note style from the registry even if we've already fetched it.
*/
static function fetchNoteStyle(?noteStyleId:String, force:Bool = false):Void
{
var fetchedNoteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(PlayState.instance.currentChart.noteStyle);
if (fetchedNoteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
else
noteStyle = fetchedNoteStyle;
fallbackNoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyle.getFallbackID());
isPixel = false;
if (noteStyle != null && !force) return;
if (noteStyleId == null) noteStyleId = PlayState.instance?.currentChart?.noteStyle;
noteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
}
/**
@ -223,40 +225,13 @@ class Countdown
*/
public static function showCountdownGraphic(index:CountdownStep):Void
{
var indexString:String = null;
switch (index)
{
case TWO:
indexString = 'ready';
case ONE:
indexString = 'set';
case GO:
indexString = 'go';
default:
// null
}
if (indexString == null) return;
fetchNoteStyle();
var spritePath:String = null;
spritePath = resolveGraphicPath(indexString);
if (spritePath == null) return;
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
countdownSprite.scrollFactor.set(0, 0);
if (isGraphicPixel) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
else
countdownSprite.setGraphicSize(Std.int(countdownSprite.width * 0.7));
var countdownSprite = noteStyle.buildCountdownSprite(index);
if (countdownSprite == null) return;
var fadeEase = FlxEase.cubeInOut;
if (isGraphicPixel) fadeEase = EaseUtil.stepped(8);
countdownSprite.antialiasing = !isPixel;
countdownSprite.cameras = [PlayState.instance.camHUD];
countdownSprite.updateHitbox();
if (noteStyle.isCountdownSpritePixel(index)) fadeEase = EaseUtil.stepped(8);
// Fade sprite in, then out, then destroy it.
FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000,
@ -267,69 +242,25 @@ class Countdown
}
});
countdownSprite.cameras = [PlayState.instance.camHUD];
PlayState.instance.add(countdownSprite);
countdownSprite.screenCenter();
}
static function resolveGraphicPath(index:String):Null<String>
{
fetchNoteStyle();
var basePath:String = 'ui/countdown/';
var spritePath:String = basePath + noteStyle.id + '/$index';
while (!Assets.exists(Paths.image(spritePath)) && fallbackNoteStyle != null)
{
noteStyle = fallbackNoteStyle;
fallbackNoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyle.getFallbackID());
spritePath = basePath + noteStyle.id + '/$index';
}
if (noteStyle.isHoldNotePixel()) isPixel = true;
// If ABSOLUTELY nothing is found, revert it to default notestyle skin
if (!Assets.exists(Paths.image(spritePath)))
{
if (!isPixel) spritePath = basePath + Constants.DEFAULT_NOTE_STYLE + '/$index';
else
spritePath = basePath + Constants.DEFAULT_PIXEL_NOTE_STYLE + '/$index';
}
trace('Resolved sprite path: ' + Paths.image(spritePath));
return spritePath;
var offsets = noteStyle.getCountdownSpriteOffsets(index);
countdownSprite.x += offsets[0];
countdownSprite.y += offsets[1];
}
/**
* Retrieves the sound file to use for this step of the countdown.
*/
public static function playCountdownSound(step:CountdownStep):Void
public static function playCountdownSound(step:CountdownStep):FunkinSound
{
return FunkinSound.playOnce(Paths.sound(resolveSoundPath(step)), Constants.COUNTDOWN_VOLUME);
}
static function resolveSoundPath(step:CountdownStep):Null<String>
{
if (step == CountdownStep.BEFORE || step == CountdownStep.AFTER) return null;
fetchNoteStyle();
var basePath:String = 'gameplay/countdown/';
var soundPath:String = basePath + noteStyle.id + '/intro$step';
var path = noteStyle.getCountdownSoundPath(step);
if (path == null) return null;
while (!Assets.exists(Paths.sound(soundPath)) && fallbackNoteStyle != null)
{
noteStyle = fallbackNoteStyle;
fallbackNoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyle.getFallbackID());
soundPath = basePath + noteStyle.id + '/intro$step';
}
if (noteStyle.isHoldNotePixel()) isPixel = true;
// If ABSOLUTELY nothing is found, revert it to default notestyle sound
if (!Assets.exists(Paths.sound(soundPath)))
{
if (!isPixel) soundPath = basePath + Constants.DEFAULT_NOTE_STYLE + '/intro$step';
else
soundPath = basePath + Constants.DEFAULT_PIXEL_NOTE_STYLE + '/intro$step';
}
trace('Resolved sound path: ' + soundPath);
return soundPath;
return FunkinSound.playOnce(path, Constants.COUNTDOWN_VOLUME);
}
public static function decrement(step:CountdownStep):CountdownStep

View file

@ -694,12 +694,7 @@ class PlayState extends MusicBeatSubState
initMinimalMode();
}
initStrumlines();
// Initialize the judgements and combo meter.
comboPopUps = new PopUpStuff();
comboPopUps.zIndex = 900;
add(comboPopUps);
comboPopUps.cameras = [camHUD];
initPopups();
#if discord_rpc
// Initialize Discord Rich Presence.
@ -1727,6 +1722,21 @@ class PlayState extends MusicBeatSubState
opponentStrumline.fadeInArrows();
}
/**
* Configures the judgement and combo popups.
*/
function initPopups():Void
{
var noteStyleId:String = currentChart.noteStyle;
var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
// Initialize the judgements and combo meter.
comboPopUps = new PopUpStuff(noteStyle);
comboPopUps.zIndex = 900;
add(comboPopUps);
comboPopUps.cameras = [camHUD];
}
/**
* Initializes the Discord Rich Presence.
*/
@ -3005,7 +3015,6 @@ class PlayState extends MusicBeatSubState
GameOverSubState.reset();
PauseSubState.reset();
Countdown.reset();
PopUpStuff.reset();
// Clear the static reference to this state.
instance = null;

View file

@ -12,103 +12,47 @@ import openfl.utils.Assets;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
@:nullSafety
class PopUpStuff extends FlxTypedGroup<FunkinSprite>
{
public var offsets:Array<Int> = [0, 0];
/**
* Which alternate graphic on popup to use.
* This is set via the current notestyle.
* For example, in Week 6 it is `pixel`.
* The current note style to use. This determines which graphics to display.
* For example, Week 6 uses the `pixel` note style, and mods can create their own.
*/
static var noteStyle:NoteStyle;
var noteStyle:NoteStyle;
static var fallbackNoteStyle:Null<NoteStyle>;
static var isPixel:Bool = false;
override public function new()
override public function new(noteStyle:NoteStyle)
{
super();
this.noteStyle = noteStyle;
}
static function fetchNoteStyle():Void
public function displayRating(daRating:Null<String>)
{
var fetchedNoteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(PlayState.instance.currentChart.noteStyle);
if (fetchedNoteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
else
noteStyle = fetchedNoteStyle;
fallbackNoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyle.getFallbackID());
isPixel = false;
}
static function resolveGraphicPath(index:String):Null<String>
{
fetchNoteStyle();
var basePath:String = 'ui/popup/';
var spritePath:String = basePath + noteStyle.id + '/$index';
while (!Assets.exists(Paths.image(spritePath)) && fallbackNoteStyle != null)
{
noteStyle = fallbackNoteStyle;
fallbackNoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyle.getFallbackID());
spritePath = basePath + noteStyle.id + '/$index';
}
if (noteStyle.isHoldNotePixel()) isPixel = true;
// If nothing is found, revert it to default notestyle skin
if (!Assets.exists(Paths.image(spritePath)))
{
if (!isPixel) spritePath = basePath + Constants.DEFAULT_NOTE_STYLE + '/$index';
else
spritePath = basePath + Constants.DEFAULT_PIXEL_NOTE_STYLE + '/$index';
}
return spritePath;
}
public function displayRating(daRating:String)
{
var perfStart:Float = TimerUtil.start();
if (daRating == null) daRating = "good";
var ratingPath:String = resolveGraphicPath(daRating);
// if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
rating.scrollFactor.set(0.2, 0.2);
var rating:Null<FunkinSprite> = noteStyle.buildJudgementSprite(daRating);
if (rating == null) return;
rating.zIndex = 1000;
rating.x = (FlxG.width * 0.474) + offsets[0];
// rating.x -= FlxG.camera.scroll.x * 0.2;
rating.y = (FlxG.camera.height * 0.45 - 60) + offsets[1];
rating.x = (FlxG.width * 0.474);
rating.x -= rating.width / 2;
rating.y = (FlxG.camera.height * 0.45 - 60);
rating.y -= rating.height / 2;
var offsets = noteStyle.getJudgementSpriteOffsets(daRating);
rating.x += offsets[0];
rating.y += offsets[1];
rating.acceleration.y = 550;
rating.velocity.y -= FlxG.random.int(140, 175);
rating.velocity.x -= FlxG.random.int(0, 10);
add(rating);
var fadeEase = null;
if (isPixel)
{
rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
rating.antialiasing = false;
rating.pixelPerfectRender = true;
rating.pixelPerfectPosition = true;
fadeEase = EaseUtil.stepped(2);
}
else
{
rating.setGraphicSize(Std.int(rating.width * 0.65));
rating.antialiasing = true;
}
rating.updateHitbox();
rating.x -= rating.width / 2;
rating.y -= rating.height / 2;
var fadeEase = noteStyle.isJudgementSpritePixel(daRating) ? EaseUtil.stepped(2) : null;
FlxTween.tween(rating, {alpha: 0}, 0.2,
{
@ -119,55 +63,10 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
startDelay: Conductor.instance.beatLengthMs * 0.001,
ease: fadeEase
});
trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
}
public function displayCombo(?combo:Int = 0):Int
public function displayCombo(combo:Int = 0):Void
{
var perfStart:Float = TimerUtil.start();
if (combo == null) combo = 0;
var comboPath:String = resolveGraphicPath('combo');
var comboSpr:FunkinSprite = FunkinSprite.create(comboPath);
comboSpr.y = (FlxG.camera.height * 0.44) + offsets[1];
comboSpr.x = (FlxG.width * 0.507) + offsets[0];
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
comboSpr.acceleration.y = 600;
comboSpr.velocity.y -= 150;
comboSpr.velocity.x += FlxG.random.int(1, 10);
// add(comboSpr);
var fadeEase = null;
if (graphicSuffix.toLowerCase().contains('pixel'))
{
comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 1));
comboSpr.antialiasing = false;
comboSpr.pixelPerfectRender = true;
comboSpr.pixelPerfectPosition = true;
fadeEase = EaseUtil.stepped(2);
}
else
{
comboSpr.setGraphicSize(Std.int(comboSpr.width * 0.7));
comboSpr.antialiasing = true;
}
comboSpr.updateHitbox();
FlxTween.tween(comboSpr, {alpha: 0}, 0.2,
{
onComplete: function(tween:FlxTween) {
remove(comboSpr, true);
comboSpr.destroy();
},
startDelay: Conductor.instance.beatLengthMs * 0.001,
ease: fadeEase
});
var seperatedScore:Array<Int> = [];
var tempCombo:Int = combo;
@ -182,31 +81,27 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
// seperatedScore.reverse();
var daLoop:Int = 1;
for (i in seperatedScore)
for (digit in seperatedScore)
{
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, resolveGraphicPath('num' + Std.int(i)));
var numScore:Null<FunkinSprite> = noteStyle.buildComboNumSprite(digit);
if (numScore == null) continue;
if (graphicSuffix.toLowerCase().contains('pixel'))
{
numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 1));
numScore.antialiasing = false;
numScore.pixelPerfectRender = true;
numScore.pixelPerfectPosition = true;
}
else
{
numScore.setGraphicSize(Std.int(numScore.width * 0.45));
numScore.antialiasing = true;
}
numScore.updateHitbox();
numScore.x = (FlxG.width * 0.507) - (36 * daLoop) - 65;
trace('numScore($daLoop) = ${numScore.x}');
numScore.y = (FlxG.camera.height * 0.44);
var offsets = noteStyle.getComboNumSpriteOffsets(digit);
numScore.x += offsets[0];
numScore.y += offsets[1];
numScore.x = comboSpr.x - (36 * daLoop) - 65; //- 90;
numScore.acceleration.y = FlxG.random.int(250, 300);
numScore.velocity.y -= FlxG.random.int(130, 150);
numScore.velocity.x = FlxG.random.float(-5, 5);
add(numScore);
var fadeEase = noteStyle.isComboNumSpritePixel(digit) ? EaseUtil.stepped(2) : null;
FlxTween.tween(numScore, {alpha: 0}, 0.2,
{
onComplete: function(tween:FlxTween) {
@ -219,18 +114,5 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
daLoop++;
}
trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
return combo;
}
/**
* Reset the popup configuration to the default.
*/
public static function reset()
{
noteStyle = NoteStyleRegistry.instance.fetchDefault();
isPixel = false;
}
}

View file

@ -75,6 +75,13 @@ class StrumlineNote extends FlxSprite
function setup(noteStyle:NoteStyle):Void
{
if (noteStyle == null)
{
// If you get an exception on this line, check the debug console.
// You probably have a parsing error in your note style's JSON file.
throw "FATAL ERROR: Attempted to initialize PlayState with an invalid NoteStyle.";
}
noteStyle.applyStrumlineFrames(this);
noteStyle.applyStrumlineAnimations(this, this.direction);

View file

@ -1,5 +1,6 @@
package funkin.play.notes.notestyle;
import funkin.play.Countdown;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.data.animation.AnimationData;
@ -16,6 +17,7 @@ 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.
*/
@:nullSafety
class NoteStyle implements IRegistryEntry<NoteStyleData>
{
/**
@ -42,12 +44,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
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());
var fallbackID = _data.fallback;
if (fallbackID != null) this.fallback = NoteStyleRegistry.instance.fetchEntry(fallbackID);
}
/**
@ -80,7 +78,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
public function buildNoteSprite(target:NoteSprite):Void
{
// Apply the note sprite frames.
var atlas:FlxAtlasFrames = buildNoteFrames(false);
var atlas:Null<FlxAtlasFrames> = buildNoteFrames(false);
if (atlas == null)
{
@ -89,7 +87,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
target.frames = atlas;
target.antialiasing = !_data.assets.note.isPixel;
target.antialiasing = !(_data.assets?.note?.isPixel ?? false);
// Apply the animations.
buildNoteAnimations(target);
@ -99,21 +97,30 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
target.updateHitbox();
}
var noteFrames:FlxAtlasFrames = null;
var noteFrames:Null<FlxAtlasFrames> = null;
function buildNoteFrames(force:Bool = false):FlxAtlasFrames
function buildNoteFrames(force:Bool = false):Null<FlxAtlasFrames>
{
if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath())))
var noteAssetPath = getNoteAssetPath();
if (noteAssetPath == null) return null;
if (!FunkinSprite.isTextureCached(Paths.image(noteAssetPath)))
{
FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}');
FlxG.log.warn('Note texture is not cached: ${noteAssetPath}');
}
// Purge the note frames if the cached atlas is invalid.
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
@:nullSafety(Off)
{
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
}
if (noteFrames != null && !force) return noteFrames;
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
var noteAssetPath = getNoteAssetPath();
if (noteAssetPath == null) return null;
noteFrames = Paths.getSparrowAtlas(noteAssetPath, getNoteAssetLibrary());
if (noteFrames == null)
{
@ -123,17 +130,18 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return noteFrames;
}
function getNoteAssetPath(raw:Bool = false):String
function getNoteAssetPath(raw:Bool = false):Null<String>
{
if (raw)
{
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
if (rawPath == null) return fallback.getNoteAssetPath(true);
if (rawPath == null && fallback != null) return fallback.getNoteAssetPath(true);
return rawPath;
}
// library:path
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return getNoteAssetPath(true);
return parts[1];
}
@ -141,57 +149,63 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
function getNoteAssetLibrary():Null<String>
{
// library:path
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return null;
return parts[0];
}
function buildNoteAnimations(target:NoteSprite):Void
{
var leftData:AnimationData = fetchNoteAnimationData(LEFT);
target.animation.addByPrefix('purpleScroll', leftData.prefix, leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY);
var downData:AnimationData = fetchNoteAnimationData(DOWN);
target.animation.addByPrefix('blueScroll', downData.prefix, downData.frameRate, downData.looped, downData.flipX, downData.flipY);
var upData:AnimationData = fetchNoteAnimationData(UP);
target.animation.addByPrefix('greenScroll', upData.prefix, upData.frameRate, upData.looped, upData.flipX, upData.flipY);
var rightData:AnimationData = fetchNoteAnimationData(RIGHT);
target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
var leftData:Null<AnimationData> = fetchNoteAnimationData(LEFT);
if (leftData != null) target.animation.addByPrefix('purpleScroll', leftData.prefix ?? '', leftData.frameRate ?? 24, leftData.looped ?? false,
leftData.flipX, leftData.flipY);
var downData:Null<AnimationData> = fetchNoteAnimationData(DOWN);
if (downData != null) target.animation.addByPrefix('blueScroll', downData.prefix ?? '', downData.frameRate ?? 24, downData.looped ?? false,
downData.flipX, downData.flipY);
var upData:Null<AnimationData> = fetchNoteAnimationData(UP);
if (upData != null) target.animation.addByPrefix('greenScroll', upData.prefix ?? '', upData.frameRate ?? 24, upData.looped ?? false, upData.flipX,
upData.flipY);
var rightData:Null<AnimationData> = fetchNoteAnimationData(RIGHT);
if (rightData != null) target.animation.addByPrefix('redScroll', rightData.prefix ?? '', rightData.frameRate ?? 24, rightData.looped ?? false,
rightData.flipX, rightData.flipY);
}
public function isNoteAnimated():Bool
{
return _data.assets.note.animated;
return _data.assets?.note?.animated ?? false;
}
public function getNoteScale():Float
{
return _data.assets.note.scale;
return _data.assets?.note?.scale ?? 1.0;
}
function fetchNoteAnimationData(dir:NoteDirection):AnimationData
function fetchNoteAnimationData(dir:NoteDirection):Null<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();
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;
return (result == null && fallback != null) ? fallback.fetchNoteAnimationData(dir) : result;
}
public function getHoldNoteAssetPath(raw:Bool = false):String
public function getHoldNoteAssetPath(raw:Bool = false):Null<String>
{
if (raw)
{
// TODO: figure out why ?. didn't work here
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
return (rawPath == null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
return (rawPath == null && fallback != null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
}
// library:path
var parts = getHoldNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
var parts = getHoldNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return Paths.image(parts[0]);
return Paths.image(parts[1], parts[0]);
}
@ -199,15 +213,15 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
public function isHoldNotePixel():Bool
{
var data = _data?.assets?.holdNote;
if (data == null) return fallback.isHoldNotePixel();
return data.isPixel;
if (data == null && fallback != null) return fallback.isHoldNotePixel();
return data?.isPixel ?? false;
}
public function fetchHoldNoteScale():Float
{
var data = _data?.assets?.holdNote;
if (data == null) return fallback.fetchHoldNoteScale();
return data.scale;
if (data == null && fallback != null) return fallback.fetchHoldNoteScale();
return data?.scale ?? 1.0;
}
public function applyStrumlineFrames(target:StrumlineNote):Void
@ -215,7 +229,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
// 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());
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath() ?? '', getStrumlineAssetLibrary());
if (atlas == null)
{
@ -224,31 +238,30 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
target.frames = atlas;
target.scale.x = _data.assets.noteStrumline.scale;
target.scale.y = _data.assets.noteStrumline.scale;
target.antialiasing = !_data.assets.noteStrumline.isPixel;
target.scale.set(_data.assets.noteStrumline?.scale ?? 1.0);
target.antialiasing = !(_data.assets.noteStrumline?.isPixel ?? false);
}
function getStrumlineAssetPath(raw:Bool = false):String
function getStrumlineAssetPath(raw:Bool = false):Null<String>
{
if (raw)
{
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
if (rawPath == null) return fallback.getStrumlineAssetPath(true);
if (rawPath == null && fallback != null) return fallback.getStrumlineAssetPath(true);
return rawPath;
}
// library:path
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
if (parts.length == 1) return getStrumlineAssetPath(true);
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;
var parts = getStrumlineAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length <= 1) return null;
return parts[0];
}
@ -259,60 +272,592 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
{
var result:Array<AnimationData> = switch (dir)
var result:Array<Null<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'),
_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'),
_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'),
_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'),
_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'),
];
default: [];
};
return result;
return thx.Arrays.filterNull(result);
}
public function applyStrumlineOffsets(target:StrumlineNote)
public function applyStrumlineOffsets(target:StrumlineNote):Void
{
target.x += _data.assets.noteStrumline.offsets[0];
target.y += _data.assets.noteStrumline.offsets[1];
var offsets = _data?.assets?.noteStrumline?.offsets ?? [0.0, 0.0];
target.x += offsets[0];
target.y += offsets[1];
}
public function getStrumlineScale():Float
{
return _data.assets.noteStrumline.scale;
return _data?.assets?.noteStrumline?.scale ?? 1.0;
}
public function isNoteSplashEnabled():Bool
{
var data = _data?.assets?.noteSplash?.data;
if (data == null) return fallback.isNoteSplashEnabled();
return data.enabled;
if (data == null) return fallback?.isNoteSplashEnabled() ?? false;
return data.enabled ?? false;
}
public function isHoldNoteCoverEnabled():Bool
{
var data = _data?.assets?.holdNoteCover?.data;
if (data == null) return fallback.isHoldNoteCoverEnabled();
return data.enabled;
if (data == null) return fallback?.isHoldNoteCoverEnabled() ?? false;
return data.enabled ?? false;
}
/**
* Build a sprite for the given step of the countdown.
* @param step
* @return A `FunkinSprite`, or `null` if no graphic is available for this step.
*/
public function buildCountdownSprite(step:Countdown.CountdownStep):Null<FunkinSprite>
{
var result = new FunkinSprite();
switch (step)
{
case THREE:
if (_data.assets.countdownThree == null) return fallback?.buildCountdownSprite(step);
var assetPath = buildCountdownSpritePath(step);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.countdownThree?.scale ?? 1.0;
result.scale.y = _data.assets.countdownThree?.scale ?? 1.0;
case TWO:
if (_data.assets.countdownTwo == null) return fallback?.buildCountdownSprite(step);
var assetPath = buildCountdownSpritePath(step);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.countdownTwo?.scale ?? 1.0;
result.scale.y = _data.assets.countdownTwo?.scale ?? 1.0;
case ONE:
if (_data.assets.countdownOne == null) return fallback?.buildCountdownSprite(step);
var assetPath = buildCountdownSpritePath(step);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.countdownOne?.scale ?? 1.0;
result.scale.y = _data.assets.countdownOne?.scale ?? 1.0;
case GO:
if (_data.assets.countdownGo == null) return fallback?.buildCountdownSprite(step);
var assetPath = buildCountdownSpritePath(step);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.countdownGo?.scale ?? 1.0;
result.scale.y = _data.assets.countdownGo?.scale ?? 1.0;
default:
// TODO: Do something here?
return null;
}
result.scrollFactor.set(0, 0);
result.antialiasing = !isCountdownSpritePixel(step);
result.updateHitbox();
return result;
}
function buildCountdownSpritePath(step:Countdown.CountdownStep):Null<String>
{
var basePath:Null<String> = null;
switch (step)
{
case THREE:
basePath = _data.assets.countdownThree?.assetPath;
case TWO:
basePath = _data.assets.countdownTwo?.assetPath;
case ONE:
basePath = _data.assets.countdownOne?.assetPath;
case GO:
basePath = _data.assets.countdownGo?.assetPath;
default:
basePath = null;
}
if (basePath == null) return fallback?.buildCountdownSpritePath(step);
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length < 1) return null;
if (parts.length == 1) return parts[0];
return parts[1];
}
function buildCountdownSpriteLibrary(step:Countdown.CountdownStep):Null<String>
{
var basePath:Null<String> = null;
switch (step)
{
case THREE:
basePath = _data.assets.countdownThree?.assetPath;
case TWO:
basePath = _data.assets.countdownTwo?.assetPath;
case ONE:
basePath = _data.assets.countdownOne?.assetPath;
case GO:
basePath = _data.assets.countdownGo?.assetPath;
default:
basePath = null;
}
if (basePath == null) return fallback?.buildCountdownSpriteLibrary(step);
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length <= 1) return null;
return parts[0];
}
public function isCountdownSpritePixel(step:Countdown.CountdownStep):Bool
{
switch (step)
{
case THREE:
var result = _data.assets.countdownThree?.isPixel;
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
return result ?? false;
case TWO:
var result = _data.assets.countdownTwo?.isPixel;
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
return result ?? false;
case ONE:
var result = _data.assets.countdownOne?.isPixel;
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
return result ?? false;
case GO:
var result = _data.assets.countdownGo?.isPixel;
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
return result ?? false;
default:
return false;
}
}
public function getCountdownSpriteOffsets(step:Countdown.CountdownStep):Array<Float>
{
switch (step)
{
case THREE:
var result = _data.assets.countdownThree?.offsets;
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
return result ?? [0, 0];
case TWO:
var result = _data.assets.countdownTwo?.offsets;
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
return result ?? [0, 0];
case ONE:
var result = _data.assets.countdownOne?.offsets;
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
return result ?? [0, 0];
case GO:
var result = _data.assets.countdownGo?.offsets;
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
return result ?? [0, 0];
default:
return [0, 0];
}
}
public function getCountdownSoundPath(step:Countdown.CountdownStep, raw:Bool = false):Null<String>
{
if (raw)
{
// TODO: figure out why ?. didn't work here
var rawPath:Null<String> = switch (step)
{
case Countdown.CountdownStep.THREE:
_data.assets.countdownThree?.data?.audioPath;
case Countdown.CountdownStep.TWO:
_data.assets.countdownTwo?.data?.audioPath;
case Countdown.CountdownStep.ONE:
_data.assets.countdownOne?.data?.audioPath;
case Countdown.CountdownStep.GO:
_data.assets.countdownGo?.data?.audioPath;
default:
null;
}
return (rawPath == null && fallback != null) ? fallback.getCountdownSoundPath(step, true) : rawPath;
}
// library:path
var parts = getCountdownSoundPath(step, true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return Paths.image(parts[0]);
return Paths.sound(parts[1], parts[0]);
}
public function buildJudgementSprite(rating:String):Null<FunkinSprite>
{
var result = new FunkinSprite();
switch (rating)
{
case "sick":
if (_data.assets.judgementSick == null) return fallback?.buildJudgementSprite(rating);
var assetPath = buildJudgementSpritePath(rating);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.judgementSick?.scale ?? 1.0;
result.scale.y = _data.assets.judgementSick?.scale ?? 1.0;
case "good":
if (_data.assets.judgementGood == null) return fallback?.buildJudgementSprite(rating);
var assetPath = buildJudgementSpritePath(rating);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.judgementGood?.scale ?? 1.0;
result.scale.y = _data.assets.judgementGood?.scale ?? 1.0;
case "bad":
if (_data.assets.judgementBad == null) return fallback?.buildJudgementSprite(rating);
var assetPath = buildJudgementSpritePath(rating);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.judgementBad?.scale ?? 1.0;
result.scale.y = _data.assets.judgementBad?.scale ?? 1.0;
case "shit":
if (_data.assets.judgementShit == null) return fallback?.buildJudgementSprite(rating);
var assetPath = buildJudgementSpritePath(rating);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.judgementShit?.scale ?? 1.0;
result.scale.y = _data.assets.judgementShit?.scale ?? 1.0;
default:
return null;
}
result.scrollFactor.set(0.2, 0.2);
var isPixel = isJudgementSpritePixel(rating);
result.antialiasing = !isPixel;
result.pixelPerfectRender = isPixel;
result.pixelPerfectPosition = isPixel;
result.updateHitbox();
return result;
}
public function isJudgementSpritePixel(rating:String):Bool
{
switch (rating)
{
case "sick":
var result = _data.assets.judgementSick?.isPixel;
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
return result ?? false;
case "good":
var result = _data.assets.judgementGood?.isPixel;
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
return result ?? false;
case "bad":
var result = _data.assets.judgementBad?.isPixel;
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
return result ?? false;
case "GO":
var result = _data.assets.judgementShit?.isPixel;
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
return result ?? false;
default:
return false;
}
}
function buildJudgementSpritePath(rating:String):Null<String>
{
var basePath:Null<String> = null;
switch (rating)
{
case "sick":
basePath = _data.assets.judgementSick?.assetPath;
case "good":
basePath = _data.assets.judgementGood?.assetPath;
case "bad":
basePath = _data.assets.judgementBad?.assetPath;
case "shit":
basePath = _data.assets.judgementShit?.assetPath;
default:
basePath = null;
}
if (basePath == null) return fallback?.buildJudgementSpritePath(rating);
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length < 1) return null;
if (parts.length == 1) return parts[0];
return parts[1];
}
public function getJudgementSpriteOffsets(rating:String):Array<Float>
{
switch (rating)
{
case "sick":
var result = _data.assets.judgementSick?.offsets;
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
return result ?? [0, 0];
case "good":
var result = _data.assets.judgementGood?.offsets;
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
return result ?? [0, 0];
case "bad":
var result = _data.assets.judgementBad?.offsets;
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
return result ?? [0, 0];
case "shit":
var result = _data.assets.judgementShit?.offsets;
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
return result ?? [0, 0];
default:
return [0, 0];
}
}
public function buildComboNumSprite(digit:Int):Null<FunkinSprite>
{
var result = new FunkinSprite();
switch (digit)
{
case 0:
if (_data.assets.comboNumber0 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber0?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber0?.scale ?? 1.0;
case 1:
if (_data.assets.comboNumber1 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber1?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber1?.scale ?? 1.0;
case 2:
if (_data.assets.comboNumber2 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber2?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber2?.scale ?? 1.0;
case 3:
if (_data.assets.comboNumber3 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber3?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber3?.scale ?? 1.0;
case 4:
if (_data.assets.comboNumber4 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber4?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber4?.scale ?? 1.0;
case 5:
if (_data.assets.comboNumber5 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber5?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber5?.scale ?? 1.0;
case 6:
if (_data.assets.comboNumber6 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber6?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber6?.scale ?? 1.0;
case 7:
if (_data.assets.comboNumber7 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber7?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber7?.scale ?? 1.0;
case 8:
if (_data.assets.comboNumber8 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber8?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber8?.scale ?? 1.0;
case 9:
if (_data.assets.comboNumber9 == null) return fallback?.buildComboNumSprite(digit);
var assetPath = buildComboNumSpritePath(digit);
if (assetPath == null) return null;
result.loadTexture(assetPath);
result.scale.x = _data.assets.comboNumber9?.scale ?? 1.0;
result.scale.y = _data.assets.comboNumber9?.scale ?? 1.0;
default:
return null;
}
var isPixel = isComboNumSpritePixel(digit);
result.antialiasing = !isPixel;
result.pixelPerfectRender = isPixel;
result.pixelPerfectPosition = isPixel;
result.updateHitbox();
return result;
}
public function isComboNumSpritePixel(digit:Int):Bool
{
switch (digit)
{
case 0:
var result = _data.assets.comboNumber0?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 1:
var result = _data.assets.comboNumber1?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 2:
var result = _data.assets.comboNumber2?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 3:
var result = _data.assets.comboNumber3?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 4:
var result = _data.assets.comboNumber4?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 5:
var result = _data.assets.comboNumber5?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 6:
var result = _data.assets.comboNumber6?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 7:
var result = _data.assets.comboNumber7?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 8:
var result = _data.assets.comboNumber8?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
case 9:
var result = _data.assets.comboNumber9?.isPixel;
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
return result ?? false;
default:
return false;
}
}
function buildComboNumSpritePath(digit:Int):Null<String>
{
var basePath:Null<String> = null;
switch (digit)
{
case 0:
basePath = _data.assets.comboNumber0?.assetPath;
case 1:
basePath = _data.assets.comboNumber1?.assetPath;
case 2:
basePath = _data.assets.comboNumber2?.assetPath;
case 3:
basePath = _data.assets.comboNumber3?.assetPath;
case 4:
basePath = _data.assets.comboNumber4?.assetPath;
case 5:
basePath = _data.assets.comboNumber5?.assetPath;
case 6:
basePath = _data.assets.comboNumber6?.assetPath;
case 7:
basePath = _data.assets.comboNumber7?.assetPath;
case 8:
basePath = _data.assets.comboNumber8?.assetPath;
case 9:
basePath = _data.assets.comboNumber9?.assetPath;
default:
basePath = null;
}
if (basePath == null) return fallback?.buildComboNumSpritePath(digit);
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length < 1) return null;
if (parts.length == 1) return parts[0];
return parts[1];
}
public function getComboNumSpriteOffsets(digit:Int):Array<Float>
{
switch (digit)
{
case 0:
var result = _data.assets.comboNumber0?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 1:
var result = _data.assets.comboNumber1?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 2:
var result = _data.assets.comboNumber2?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 3:
var result = _data.assets.comboNumber3?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 4:
var result = _data.assets.comboNumber4?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 5:
var result = _data.assets.comboNumber5?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 6:
var result = _data.assets.comboNumber6?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 7:
var result = _data.assets.comboNumber7?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 8:
var result = _data.assets.comboNumber8?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
case 9:
var result = _data.assets.comboNumber9?.offsets;
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
return result ?? [0, 0];
default:
return [0, 0];
}
}
public function destroy():Void {}
@ -322,8 +867,17 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return 'NoteStyle($id)';
}
static function _fetchData(id:String):Null<NoteStyleData>
static function _fetchData(id:String):NoteStyleData
{
return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
var result = NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
if (result == null)
{
throw 'Could not parse note style data for id: $id';
}
else
{
return result;
}
}
}

View file

@ -60,7 +60,9 @@ class ChartEditorHoldNoteSprite extends SustainTrail
override function setupHoldNoteGraphic(noteStyle:NoteStyle):Void
{
loadGraphic(noteStyle.getHoldNoteAssetPath());
var graphicPath = noteStyle.getHoldNoteAssetPath();
if (graphicPath == null) return;
loadGraphic(graphicPath);
antialiasing = true;