Merge branch 'rewrite/master' into haxelib/flixels

This commit is contained in:
Eric 2024-07-25 06:38:18 -04:00 committed by GitHub
commit a2838fffd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 846 additions and 241 deletions

2
assets

@ -1 +1 @@
Subproject commit 005c96f85f4304865acb196e7cc4d6d83f9d76d8 Subproject commit aa1231e8cf2990bb902eac3b37815c010fa9919a

View file

@ -27,6 +27,7 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.album.AlbumRegistry; import funkin.data.freeplay.album.AlbumRegistry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.modding.module.ModuleHandler; import funkin.modding.module.ModuleHandler;
import funkin.ui.title.TitleState; import funkin.ui.title.TitleState;
import funkin.util.CLIUtil; import funkin.util.CLIUtil;
@ -176,6 +177,8 @@ class InitState extends FlxState
// Move it to use a BaseRegistry. // Move it to use a BaseRegistry.
CharacterDataParser.loadCharacterCache(); CharacterDataParser.loadCharacterCache();
NoteKindManager.loadScripts();
ModuleHandler.buildModuleCallbacks(); ModuleHandler.buildModuleCallbacks();
ModuleHandler.loadModuleCache(); ModuleHandler.loadModuleCache();
ModuleHandler.callOnCreate(); ModuleHandler.callOnCreate();

View file

@ -109,6 +109,14 @@ typedef NoteStyleAssetData<T> =
@:optional @:optional
var isPixel:Bool; var isPixel:Bool;
/**
* If true, animations will be played on the graphic.
* @default `false` to save performance.
*/
@:default(false)
@:optional
var animated:Bool;
/** /**
* The structure of this data depends on the asset. * The structure of this data depends on the asset.
*/ */

View file

@ -951,12 +951,18 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
return this.kind = value; return this.kind = value;
} }
public function new(time:Float, data:Int, length:Float = 0, kind:String = '') @:alias("p")
@:default([])
@:optional
public var params:Array<NoteParamData>;
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
{ {
this.time = time; this.time = time;
this.data = data; this.data = data;
this.length = length; this.length = length;
this.kind = kind; this.kind = kind;
this.params = params ?? [];
} }
/** /**
@ -1051,9 +1057,19 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
_stepLength = null; _stepLength = null;
} }
public function cloneParams():Array<NoteParamData>
{
var params:Array<NoteParamData> = [];
for (param in this.params)
{
params.push(param.clone());
}
return params;
}
public function clone():SongNoteDataRaw public function clone():SongNoteDataRaw
{ {
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind); return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
} }
public function toString():String public function toString():String
@ -1069,9 +1085,9 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
@:forward @:forward
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
{ {
public function new(time:Float, data:Int, length:Float = 0, kind:String = '') public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
{ {
this = new SongNoteDataRaw(time, data, length, kind); this = new SongNoteDataRaw(time, data, length, kind, params);
} }
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
@ -1115,7 +1131,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
if (other.kind == '' || this.kind == null) return false; if (other.kind == '' || this.kind == null) return false;
} }
return this.time == other.time && this.data == other.data && this.length == other.length; return this.time == other.time && this.data == other.data && this.length == other.length && this.params == other.params;
} }
@:op(A != B) @:op(A != B)
@ -1134,7 +1150,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
if (other.kind == '') return true; if (other.kind == '') return true;
} }
return this.time != other.time || this.data != other.data || this.length != other.length; return this.time != other.time || this.data != other.data || this.length != other.length || this.params != other.params;
} }
@:op(A > B) @:op(A > B)
@ -1171,7 +1187,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
public function clone():SongNoteData public function clone():SongNoteData
{ {
return new SongNoteData(this.time, this.data, this.length, this.kind); return new SongNoteData(this.time, this.data, this.length, this.kind, this.params);
} }
/** /**
@ -1183,3 +1199,30 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); + (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
} }
} }
class NoteParamData implements ICloneable<NoteParamData>
{
@:alias("n")
public var name:String;
@:alias("v")
@:jcustomparse(funkin.data.DataParse.dynamicValue)
@:jcustomwrite(funkin.data.DataWrite.dynamicValue)
public var value:Dynamic;
public function new(name:String, value:Dynamic)
{
this.name = name;
this.value = value;
}
public function clone():NoteParamData
{
return new NoteParamData(this.name, this.value);
}
public function toString():String
{
return 'NoteParamData(${this.name}, ${this.value})';
}
}

View file

@ -199,6 +199,8 @@ class FNFLegacyImporter
{ {
// Handle the dumb logic for mustHitSection. // Handle the dumb logic for mustHitSection.
var noteData = note.data; var noteData = note.data;
if (noteData < 0) continue; // Exclude Psych event notes.
if (noteData > (STRUMLINE_SIZE * 2)) noteData = noteData % (2 * STRUMLINE_SIZE); // Handle other engine event notes.
// Flip notes if mustHitSection is FALSE (not true lol). // Flip notes if mustHitSection is FALSE (not true lol).
if (!mustHitSection) if (!mustHitSection)

View file

@ -7,6 +7,7 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.event.SongEventRegistry; import funkin.data.event.SongEventRegistry;
import funkin.data.story.level.LevelRegistry; import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.data.freeplay.player.PlayerRegistry; import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.stage.StageRegistry; import funkin.data.stage.StageRegistry;
@ -251,6 +252,10 @@ class PolymodHandler
// Lib.load() can load malicious DLLs // Lib.load() can load malicious DLLs
Polymod.blacklistImport('cpp.Lib'); Polymod.blacklistImport('cpp.Lib');
// `Unserializer`
// Unserializerr.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
Polymod.blacklistImport('Unserializer');
// `polymod.*` // `polymod.*`
// You can probably unblacklist a module // You can probably unblacklist a module
for (cls in ClassMacro.listClassesInPackage('polymod')) for (cls in ClassMacro.listClassesInPackage('polymod'))
@ -383,6 +388,7 @@ class PolymodHandler
StageRegistry.instance.loadEntries(); StageRegistry.instance.loadEntries();
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry. CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
NoteKindManager.loadScripts();
ModuleHandler.loadModuleCache(); ModuleHandler.loadModuleCache();
} }
} }

View file

@ -49,6 +49,7 @@ import funkin.play.notes.NoteSprite;
import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.Strumline; import funkin.play.notes.Strumline;
import funkin.play.notes.SustainTrail; import funkin.play.notes.SustainTrail;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring;
import funkin.play.song.Song; import funkin.play.song.Song;
import funkin.play.stage.Stage; import funkin.play.stage.Stage;
@ -503,7 +504,7 @@ class PlayState extends MusicBeatSubState
public var camGame:FlxCamera; public var camGame:FlxCamera;
/** /**
* The camera which contains, and controls visibility of, a video cutscene. * The camera which contains, and controls visibility of, a video cutscene, dialogue, pause menu and sticker transition.
*/ */
public var camCutscene:FlxCamera; public var camCutscene:FlxCamera;
@ -578,7 +579,6 @@ class PlayState extends MusicBeatSubState
// TODO: Refactor or document // TODO: Refactor or document
var generatedMusic:Bool = false; var generatedMusic:Bool = false;
var perfectMode:Bool = false;
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK; static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
@ -975,7 +975,7 @@ class PlayState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true; FlxTransitionableState.skipNextTransOut = true;
pauseSubState.camera = camHUD; pauseSubState.camera = camCutscene;
openSubState(pauseSubState); openSubState(pauseSubState);
// boyfriendPos.put(); // TODO: Why is this here? // boyfriendPos.put(); // TODO: Why is this here?
} }
@ -1165,6 +1165,9 @@ class PlayState extends MusicBeatSubState
// super.dispatchEvent(event) dispatches event to module scripts. // super.dispatchEvent(event) dispatches event to module scripts.
super.dispatchEvent(event); super.dispatchEvent(event);
// Dispatch event to note kind scripts
NoteKindManager.callEvent(event);
// Dispatch event to stage script. // Dispatch event to stage script.
ScriptEventDispatcher.callEvent(currentStage, event); ScriptEventDispatcher.callEvent(currentStage, event);
@ -1176,8 +1179,6 @@ class PlayState extends MusicBeatSubState
// Dispatch event to conversation script. // Dispatch event to conversation script.
ScriptEventDispatcher.callEvent(currentConversation, event); ScriptEventDispatcher.callEvent(currentConversation, event);
// TODO: Dispatch event to note scripts
} }
/** /**
@ -1348,64 +1349,13 @@ class PlayState extends MusicBeatSubState
} }
/** /**
* Removes any references to the current stage, then clears the stage cache,
* then reloads all the stages.
*
* This is useful for when you want to edit a stage without reloading the whole game.
* Reloading works on both the JSON and the HXC, if applicable.
*
* Call this by pressing F5 on a debug build. * Call this by pressing F5 on a debug build.
*/ */
override function debug_refreshModules():Void override function reloadAssets():Void
{ {
// Prevent further gameplay updates, which will try to reference dead objects. funkin.modding.PolymodHandler.forceReloadAssets();
criticalFailure = true; lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
LoadingState.loadPlayState(lastParams);
// Remove the current stage. If the stage gets deleted while it's still in use,
// it'll probably crash the game or something.
if (this.currentStage != null)
{
remove(currentStage);
var event:ScriptEvent = new ScriptEvent(DESTROY, false);
ScriptEventDispatcher.callEvent(currentStage, event);
currentStage = null;
}
if (!overrideMusic)
{
// Stop the instrumental.
if (FlxG.sound.music != null)
{
FlxG.sound.music.destroy();
FlxG.sound.music = null;
}
// Stop the vocals.
if (vocals != null && vocals.exists)
{
vocals.destroy();
vocals = null;
}
}
else
{
// Stop the instrumental.
if (FlxG.sound.music != null)
{
FlxG.sound.music.stop();
}
// Stop the vocals.
if (vocals != null && vocals.exists)
{
vocals.stop();
}
}
super.debug_refreshModules();
var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentSong, event);
} }
override function stepHit():Bool override function stepHit():Bool
@ -1501,9 +1451,6 @@ class PlayState extends MusicBeatSubState
if (playerStrumline != null) playerStrumline.onBeatHit(); if (playerStrumline != null) playerStrumline.onBeatHit();
if (opponentStrumline != null) opponentStrumline.onBeatHit(); if (opponentStrumline != null) opponentStrumline.onBeatHit();
// Make the characters dance on the beat
danceOnBeat();
return true; return true;
} }
@ -1514,26 +1461,6 @@ class PlayState extends MusicBeatSubState
super.destroy(); super.destroy();
} }
/**
* Handles characters dancing to the beat of the current song.
*
* TODO: Move some of this logic into `Bopper.hx`, or individual character scripts.
*/
function danceOnBeat():Void
{
if (currentStage == null) return;
// TODO: Add HEY! song events to Tutorial.
if (Conductor.instance.currentBeat % 16 == 15
&& currentStage.getDad().characterId == 'gf'
&& Conductor.instance.currentBeat > 16
&& Conductor.instance.currentBeat < 48)
{
currentStage.getBoyfriend().playAnimation('hey', true);
currentStage.getDad().playAnimation('cheer', true);
}
}
/** /**
* Initializes the game and HUD cameras. * Initializes the game and HUD cameras.
*/ */
@ -1934,7 +1861,6 @@ class PlayState extends MusicBeatSubState
if (!result) return; if (!result) return;
isInCutscene = false; isInCutscene = false;
camCutscene.visible = false;
// TODO: Maybe tween in the camera after any cutscenes. // TODO: Maybe tween in the camera after any cutscenes.
camHUD.visible = true; camHUD.visible = true;
@ -2610,12 +2536,6 @@ class PlayState extends MusicBeatSubState
*/ */
function debugKeyShit():Void function debugKeyShit():Void
{ {
#if !debug
perfectMode = false;
#else
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
#end
#if CHART_EDITOR_SUPPORTED #if CHART_EDITOR_SUPPORTED
// Open the stage editor overlaying the current state. // Open the stage editor overlaying the current state.
if (controls.DEBUG_STAGE) if (controls.DEBUG_STAGE)
@ -2647,6 +2567,9 @@ class PlayState extends MusicBeatSubState
#end #end
#if (debug || FORCE_DEBUG_VERSION) #if (debug || FORCE_DEBUG_VERSION)
// H: Hide the HUD.
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
// 1: End the song immediately. // 1: End the song immediately.
if (FlxG.keys.justPressed.ONE) endSong(true); if (FlxG.keys.justPressed.ONE) endSong(true);

View file

@ -521,6 +521,9 @@ class BaseCharacter extends Bopper
{ {
super.onNoteHit(event); super.onNoteHit(event);
// If another script cancelled the event, don't do anything.
if (event.eventCanceled) return;
if (event.note.noteData.getMustHitNote() && characterType == BF) if (event.note.noteData.getMustHitNote() && characterType == BF)
{ {
// If the note is from the same strumline, play the sing animation. // If the note is from the same strumline, play the sing animation.
@ -553,6 +556,9 @@ class BaseCharacter extends Bopper
{ {
super.onNoteMiss(event); super.onNoteMiss(event);
// If another script cancelled the event, don't do anything.
if (event.eventCanceled) return;
if (event.note.noteData.getMustHitNote() && characterType == BF) if (event.note.noteData.getMustHitNote() && characterType == BF)
{ {
// If the note is from the same strumline, play the sing animation. // If the note is from the same strumline, play the sing animation.

View file

@ -81,7 +81,6 @@ class VideoCutscene
// Trigger the cutscene. Don't play the song in the background. // Trigger the cutscene. Don't play the song in the background.
PlayState.instance.isInCutscene = true; PlayState.instance.isInCutscene = true;
PlayState.instance.camHUD.visible = false; PlayState.instance.camHUD.visible = false;
PlayState.instance.camCutscene.visible = true;
// Display a black screen to hide the game while the video is playing. // Display a black screen to hide the game while the video is playing.
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
@ -305,7 +304,6 @@ class VideoCutscene
vid = null; vid = null;
#end #end
PlayState.instance.camCutscene.visible = true;
PlayState.instance.camHUD.visible = true; PlayState.instance.camHUD.visible = true;
FlxTween.tween(blackScreen, {alpha: 0}, transitionTime, FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,

View file

@ -1,6 +1,7 @@
package funkin.play.notes; package funkin.play.notes;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.NoteParamData;
import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notestyle.NoteStyle;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import flixel.FlxSprite; import flixel.FlxSprite;
@ -65,6 +66,22 @@ class NoteSprite extends FunkinSprite
return this.noteData.kind = value; return this.noteData.kind = value;
} }
/**
* An array of custom parameters for this note
*/
public var params(get, set):Array<NoteParamData>;
function get_params():Array<NoteParamData>
{
return this.noteData?.params ?? [];
}
function set_params(value:Array<NoteParamData>):Array<NoteParamData>
{
if (this.noteData == null) return value;
return this.noteData.params = value;
}
/** /**
* The data of the note (i.e. the direction.) * The data of the note (i.e. the direction.)
*/ */
@ -74,7 +91,7 @@ class NoteSprite extends FunkinSprite
{ {
if (frames == null) return value; if (frames == null) return value;
animation.play(DIRECTION_COLORS[value] + 'Scroll'); playNoteAnimation(value);
this.direction = value; this.direction = value;
return this.direction; return this.direction;
@ -135,19 +152,37 @@ class NoteSprite extends FunkinSprite
this.hsvShader = new HSVShader(); this.hsvShader = new HSVShader();
setupNoteGraphic(noteStyle); setupNoteGraphic(noteStyle);
// Disables the update() function for performance.
this.active = false;
} }
function setupNoteGraphic(noteStyle:NoteStyle):Void /**
* Creates frames and animations
* @param noteStyle The `NoteStyle` instance
*/
public function setupNoteGraphic(noteStyle:NoteStyle):Void
{ {
noteStyle.buildNoteSprite(this); noteStyle.buildNoteSprite(this);
setGraphicSize(Strumline.STRUMLINE_SIZE);
updateHitbox();
this.shader = hsvShader; this.shader = hsvShader;
// `false` disables the update() function for performance.
this.active = noteStyle.isNoteAnimated();
}
/**
* Retrieve the value of the param with the given name
* @param name Name of the param
* @return Null<Dynamic>
*/
public function getParam(name:String):Null<Dynamic>
{
for (param in params)
{
if (param.name == name)
{
return param.value;
}
}
return null;
} }
#if FLX_DEBUG #if FLX_DEBUG
@ -173,6 +208,11 @@ class NoteSprite extends FunkinSprite
} }
#end #end
function playNoteAnimation(value:Int):Void
{
animation.play(DIRECTION_COLORS[value] + 'Scroll');
}
public function desaturate():Void public function desaturate():Void
{ {
this.hsvShader.saturation = 0.2; this.hsvShader.saturation = 0.2;

View file

@ -16,6 +16,7 @@ import funkin.data.song.SongData.SongNoteData;
import funkin.ui.options.PreferencesMenu; import funkin.ui.options.PreferencesMenu;
import funkin.util.SortUtil; import funkin.util.SortUtil;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.play.notes.notekind.NoteKindManager;
/** /**
* A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player. * A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player.
@ -708,11 +709,15 @@ class Strumline extends FlxSpriteGroup
if (noteSprite != null) if (noteSprite != null)
{ {
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
noteSprite.setupNoteGraphic(noteKindStyle);
noteSprite.direction = note.getDirection(); noteSprite.direction = note.getDirection();
noteSprite.noteData = note; noteSprite.noteData = note;
noteSprite.x = this.x; noteSprite.x = this.x;
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]); noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
noteSprite.x -= NUDGE; noteSprite.x -= NUDGE;
// noteSprite.x += INITIAL_OFFSET; // noteSprite.x += INITIAL_OFFSET;
noteSprite.y = -9999; noteSprite.y = -9999;
@ -727,6 +732,9 @@ class Strumline extends FlxSpriteGroup
if (holdNoteSprite != null) if (holdNoteSprite != null)
{ {
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
holdNoteSprite.setupHoldNoteGraphic(noteKindStyle);
holdNoteSprite.parentStrumline = this; holdNoteSprite.parentStrumline = this;
holdNoteSprite.noteData = note; holdNoteSprite.noteData = note;
holdNoteSprite.strumTime = note.time; holdNoteSprite.strumTime = note.time;

View file

@ -99,7 +99,27 @@ class SustainTrail extends FlxSprite
*/ */
public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle) public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle)
{ {
super(0, 0, noteStyle.getHoldNoteAssetPath()); super(0, 0);
// BASIC SETUP
this.sustainLength = sustainLength;
this.fullSustainLength = sustainLength;
this.noteDirection = noteDirection;
setupHoldNoteGraphic(noteStyle);
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
this.active = true; // This NEEDS to be true for the note to be drawn!
}
/**
* Creates hold note graphic and applies correct zooming
* @param noteStyle The note style
*/
public function setupHoldNoteGraphic(noteStyle:NoteStyle):Void
{
loadGraphic(noteStyle.getHoldNoteAssetPath());
antialiasing = true; antialiasing = true;
@ -109,13 +129,14 @@ class SustainTrail extends FlxSprite
endOffset = bottomClip = 1; endOffset = bottomClip = 1;
antialiasing = false; antialiasing = false;
} }
else
{
endOffset = 0.5;
bottomClip = 0.9;
}
zoom = 1.0;
zoom *= noteStyle.fetchHoldNoteScale(); zoom *= noteStyle.fetchHoldNoteScale();
// BASIC SETUP
this.sustainLength = sustainLength;
this.fullSustainLength = sustainLength;
this.noteDirection = noteDirection;
zoom *= 0.7; zoom *= 0.7;
// CALCULATE SIZE // CALCULATE SIZE
@ -131,9 +152,6 @@ class SustainTrail extends FlxSprite
updateColorTransform(); updateColorTransform();
updateClipping(); updateClipping();
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
this.active = true; // This NEEDS to be true for the note to be drawn!
} }
function getBaseScrollSpeed() function getBaseScrollSpeed()
@ -195,6 +213,11 @@ class SustainTrail extends FlxSprite
*/ */
public function updateClipping(songTime:Float = 0):Void public function updateClipping(songTime:Float = 0):Void
{ {
if (graphic == null)
{
return;
}
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight); var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight);
if (clipHeight <= 0.1) if (clipHeight <= 0.1)
{ {

View file

@ -0,0 +1,119 @@
package funkin.play.notes.notekind;
import funkin.modding.IScriptedClass.INoteScriptedClass;
import funkin.modding.events.ScriptEvent;
import flixel.math.FlxMath;
/**
* Class for note scripts
*/
class NoteKind implements INoteScriptedClass
{
/**
* The name of the note kind
*/
public var noteKind:String;
/**
* Description used in chart editor
*/
public var description:String;
/**
* Custom note style
*/
public var noteStyleId:Null<String>;
/**
* Custom parameters for the chart editor
*/
public var params:Array<NoteKindParam>;
public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
{
this.noteKind = noteKind;
this.description = description;
this.noteStyleId = noteStyleId;
this.params = params ?? [];
}
public function toString():String
{
return noteKind;
}
/**
* Retrieve all notes of this kind
* @return Array<NoteSprite>
*/
function getNotes():Array<NoteSprite>
{
var allNotes:Array<NoteSprite> = PlayState.instance.playerStrumline.notes.members.concat(PlayState.instance.opponentStrumline.notes.members);
return allNotes.filter(function(note:NoteSprite) {
return note != null && note.noteData.kind == this.noteKind;
});
}
public function onScriptEvent(event:ScriptEvent):Void {}
public function onCreate(event:ScriptEvent):Void {}
public function onDestroy(event:ScriptEvent):Void {}
public function onUpdate(event:UpdateScriptEvent):Void {}
public function onNoteIncoming(event:NoteScriptEvent):Void {}
public function onNoteHit(event:HitNoteScriptEvent):Void {}
public function onNoteMiss(event:NoteScriptEvent):Void {}
}
/**
* Abstract for setting the type of the `NoteKindParam`
* This was supposed to be an enum but polymod kept being annoying
*/
abstract NoteKindParamType(String) from String to String
{
public static final STRING:String = 'String';
public static final INT:String = 'Int';
public static final FLOAT:String = 'Float';
}
typedef NoteKindParamData =
{
/**
* If `min` is null, there is no minimum
*/
?min:Null<Float>,
/**
* If `max` is null, there is no maximum
*/
?max:Null<Float>,
/**
* If `step` is null, it will use 1.0
*/
?step:Null<Float>,
/**
* If `precision` is null, there will be 0 decimal places
*/
?precision:Null<Int>,
?defaultValue:Dynamic
}
/**
* Typedef for creating custom parameters in the chart editor
*/
typedef NoteKindParam =
{
name:String,
description:String,
type:NoteKindParamType,
?data:NoteKindParamData
}

View file

@ -0,0 +1,121 @@
package funkin.play.notes.notekind;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.events.ScriptEvent;
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.notekind.ScriptedNoteKind;
import funkin.play.notes.notekind.NoteKind.NoteKindParam;
class NoteKindManager
{
static var noteKinds:Map<String, NoteKind> = [];
public static function loadScripts():Void
{
var scriptedClassName:Array<String> = ScriptedNoteKind.listScriptClasses();
if (scriptedClassName.length > 0)
{
trace('Instantiating ${scriptedClassName.length} scripted note kind(s)...');
for (scriptedClass in scriptedClassName)
{
try
{
var script:NoteKind = ScriptedNoteKind.init(scriptedClass, "unknown");
trace(' Initialized scripted note kind: ${script.noteKind}');
noteKinds.set(script.noteKind, script);
ChartEditorDropdowns.NOTE_KINDS.set(script.noteKind, script.description);
}
catch (e)
{
trace(' FAILED to instantiate scripted note kind: ${scriptedClass}');
trace(e);
}
}
}
}
/**
* Calls the given event for note kind scripts
* @param event The event
*/
public static function callEvent(event:ScriptEvent):Void
{
// if it is a note script event,
// then only call the event for the specific note kind script
if (Std.isOfType(event, NoteScriptEvent))
{
var noteEvent:NoteScriptEvent = cast(event, NoteScriptEvent);
var noteKind:NoteKind = noteKinds.get(noteEvent.note.kind);
if (noteKind != null)
{
ScriptEventDispatcher.callEvent(noteKind, event);
}
}
else // call the event for all note kind scripts
{
for (noteKind in noteKinds.iterator())
{
ScriptEventDispatcher.callEvent(noteKind, event);
}
}
}
/**
* Retrieve the note style from the given note kind
* @param noteKind note kind name
* @param suffix Used for song note styles
* @return NoteStyle
*/
public static function getNoteStyle(noteKind:String, ?suffix:String):Null<NoteStyle>
{
var noteStyleId:Null<String> = getNoteStyleId(noteKind, suffix);
if (noteStyleId == null)
{
return null;
}
return NoteStyleRegistry.instance.fetchEntry(noteStyleId);
}
/**
* Retrieve the note style id from the given note kind
* @param noteKind Note kind name
* @param suffix Used for song note styles
* @return Null<String>
*/
public static function getNoteStyleId(noteKind:String, ?suffix:String):Null<String>
{
if (suffix == '')
{
suffix = null;
}
var noteStyleId:Null<String> = noteKinds.get(noteKind)?.noteStyleId;
if (noteStyleId != null && suffix != null)
{
noteStyleId = NoteStyleRegistry.instance.hasEntry('$noteStyleId-$suffix') ? '$noteStyleId-$suffix' : noteStyleId;
}
return noteStyleId;
}
/**
* Retrive custom params of the given note kind
* @param noteKind Name of the note kind
* @return Array<NoteKindParam>
*/
public static function getParams(noteKind:Null<String>):Array<NoteKindParam>
{
if (noteKind == null)
{
return [];
}
return noteKinds.get(noteKind)?.params ?? [];
}
}

View file

@ -0,0 +1,9 @@
package funkin.play.notes.notekind;
/**
* A script that can be tied to a NoteKind.
* Create a scripted class that extends NoteKind,
* then call `super('noteKind')` in the constructor to use this.
*/
@:hscriptClass
class ScriptedNoteKind extends NoteKind implements polymod.hscript.HScriptedClass {}

View file

@ -89,12 +89,14 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
target.frames = atlas; target.frames = atlas;
target.scale.x = _data.assets.note.scale;
target.scale.y = _data.assets.note.scale;
target.antialiasing = !_data.assets.note.isPixel; target.antialiasing = !_data.assets.note.isPixel;
// Apply the animations. // Apply the animations.
buildNoteAnimations(target); buildNoteAnimations(target);
// Set the scale.
target.setGraphicSize(Strumline.STRUMLINE_SIZE * getNoteScale());
target.updateHitbox();
} }
var noteFrames:FlxAtlasFrames = null; var noteFrames:FlxAtlasFrames = null;
@ -156,6 +158,16 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY); target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
} }
public function isNoteAnimated():Bool
{
return _data.assets.note.animated;
}
public function getNoteScale():Float
{
return _data.assets.note.scale;
}
function fetchNoteAnimationData(dir:NoteDirection):AnimationData function fetchNoteAnimationData(dir:NoteDirection):AnimationData
{ {
var result:Null<AnimationData> = switch (dir) var result:Null<AnimationData> = switch (dir)

View file

@ -277,7 +277,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
// If there are no difficulties in the metadata, there's a problem. // If there are no difficulties in the metadata, there's a problem.
if (metadata.playData.difficulties.length == 0) if (metadata.playData.difficulties.length == 0)
{ {
throw 'Song $id has no difficulties listed in metadata!'; trace('[WARN] Song $id has no difficulties listed in metadata!');
} }
// There may be more difficulties in the chart file than in the metadata, // There may be more difficulties in the chart file than in the metadata,

View file

@ -45,8 +45,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
public var idleSuffix(default, set):String = ''; public var idleSuffix(default, set):String = '';
/** /**
* If this bopper is rendered with pixel art, * If this bopper is rendered with pixel art, disable anti-aliasing.
* disable anti-aliasing and render at 6x scale. * @default `false`
*/ */
public var isPixel(default, set):Bool = false; public var isPixel(default, set):Bool = false;

View file

@ -78,9 +78,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
{ {
// Emergency exit button. // Emergency exit button.
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
// This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules();
} }
override function update(elapsed:Float) override function update(elapsed:Float)
@ -114,12 +111,10 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
ModuleHandler.callEvent(event); ModuleHandler.callEvent(event);
} }
function debug_refreshModules() function reloadAssets()
{ {
PolymodHandler.forceReloadAssets(); PolymodHandler.forceReloadAssets();
this.destroy();
// Create a new instance of the current state, so old data is cleared. // Create a new instance of the current state, so old data is cleared.
FlxG.resetState(); FlxG.resetState();
} }

View file

@ -72,9 +72,6 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
// Emergency exit button. // Emergency exit button.
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
// This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules();
// Display Conductor info in the watch window. // Display Conductor info in the watch window.
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
Conductor.watchQuick(conductorInUse); Conductor.watchQuick(conductorInUse);
@ -82,7 +79,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
dispatchEvent(new UpdateScriptEvent(elapsed)); dispatchEvent(new UpdateScriptEvent(elapsed));
} }
function debug_refreshModules() function reloadAssets()
{ {
PolymodHandler.forceReloadAssets(); PolymodHandler.forceReloadAssets();

View file

@ -35,6 +35,7 @@ import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongMetadata; import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongOffsets; import funkin.data.song.SongData.SongOffsets;
import funkin.data.song.SongData.NoteParamData;
import funkin.data.song.SongDataUtils; import funkin.data.song.SongDataUtils;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.data.stage.StageData; import funkin.data.stage.StageData;
@ -45,6 +46,7 @@ import funkin.input.TurboActionHandler;
import funkin.input.TurboButtonHandler; import funkin.input.TurboButtonHandler;
import funkin.input.TurboKeyHandler; import funkin.input.TurboKeyHandler;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
@ -282,6 +284,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/ */
public static final WELCOME_MUSIC_FADE_IN_DURATION:Float = 10.0; public static final WELCOME_MUSIC_FADE_IN_DURATION:Float = 10.0;
/**
* A map of the keys for every live input style.
*/
public static final LIVE_INPUT_KEYS:Map<ChartEditorLiveInputStyle, Array<FlxKey>> = [
NumberKeys => [
FIVE, SIX, SEVEN, EIGHT,
ONE, TWO, THREE, FOUR
],
WASDKeys => [
LEFT, DOWN, UP, RIGHT,
A, S, W, D
],
None => []
];
/** /**
* INSTANCE DATA * INSTANCE DATA
*/ */
@ -538,6 +555,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/ */
var noteKindToPlace:Null<String> = null; var noteKindToPlace:Null<String> = null;
/**
* The note params to use for notes being placed in the chart. Defaults to `[]`.
*/
var noteParamsToPlace:Array<NoteParamData> = [];
/** /**
* The event type to use for events being placed in the chart. Defaults to `''`. * The event type to use for events being placed in the chart. Defaults to `''`.
*/ */
@ -1401,7 +1423,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_currentSongNoteStyle():String function get_currentSongNoteStyle():String
{ {
if (currentSongMetadata.playData.noteStyle == null) if (currentSongMetadata.playData.noteStyle == null
|| currentSongMetadata.playData.noteStyle == ''
|| currentSongMetadata.playData.noteStyle == 'item')
{ {
// Initialize to the default value if not set. // Initialize to the default value if not set.
currentSongMetadata.playData.noteStyle = Constants.DEFAULT_NOTE_STYLE; currentSongMetadata.playData.noteStyle = Constants.DEFAULT_NOTE_STYLE;
@ -2436,7 +2460,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
gridGhostNote = new ChartEditorNoteSprite(this); gridGhostNote = new ChartEditorNoteSprite(this);
gridGhostNote.alpha = 0.6; gridGhostNote.alpha = 0.6;
gridGhostNote.noteData = new SongNoteData(0, 0, 0, ""); gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
gridGhostNote.visible = false; gridGhostNote.visible = false;
add(gridGhostNote); add(gridGhostNote);
gridGhostNote.zIndex = 11; gridGhostNote.zIndex = 11;
@ -3584,6 +3608,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// The note sprite handles animation playback and positioning. // The note sprite handles animation playback and positioning.
noteSprite.noteData = noteData; noteSprite.noteData = noteData;
noteSprite.noteStyle = NoteKindManager.getNoteStyleId(noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
noteSprite.overrideStepTime = null; noteSprite.overrideStepTime = null;
noteSprite.overrideData = null; noteSprite.overrideData = null;
@ -3607,6 +3632,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
holdNoteSprite.setHeightDirectly(noteLengthPixels); holdNoteSprite.setHeightDirectly(noteLengthPixels);
holdNoteSprite.noteStyle = NoteKindManager.getNoteStyleId(noteSprite.noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes); holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
trace(holdNoteSprite.x + ', ' + holdNoteSprite.y + ', ' + holdNoteSprite.width + ', ' + holdNoteSprite.height); trace(holdNoteSprite.x + ', ' + holdNoteSprite.y + ', ' + holdNoteSprite.width + ', ' + holdNoteSprite.height);
@ -3669,9 +3696,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
holdNoteSprite.noteData = noteData; holdNoteSprite.noteData = noteData;
holdNoteSprite.noteDirection = noteData.getDirection(); holdNoteSprite.noteDirection = noteData.getDirection();
holdNoteSprite.setHeightDirectly(noteLengthPixels); holdNoteSprite.setHeightDirectly(noteLengthPixels);
holdNoteSprite.noteStyle = NoteKindManager.getNoteStyleId(noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes); holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
displayedHoldNoteData.push(noteData); displayedHoldNoteData.push(noteData);
@ -4569,7 +4597,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
gridGhostHoldNote.noteData = currentPlaceNoteData; gridGhostHoldNote.noteData = currentPlaceNoteData;
gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection(); gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true); gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
gridGhostHoldNote.noteStyle = NoteKindManager.getNoteStyleId(currentPlaceNoteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes); gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
} }
else else
@ -4726,7 +4754,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else else
{ {
// Create a note and place it in the chart. // Create a note and place it in the chart.
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace); var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace,
ChartEditorState.cloneNoteParams(noteParamsToPlace));
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
@ -4885,12 +4914,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()"; if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()";
var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace); var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace,
ChartEditorState.cloneNoteParams(noteParamsToPlace));
if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind) if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind || noteParamsToPlace != noteData.params)
{ {
noteData.kind = noteKindToPlace; noteData.kind = noteKindToPlace;
noteData.params = noteParamsToPlace;
noteData.data = cursorColumn; noteData.data = cursorColumn;
gridGhostNote.noteStyle = NoteKindManager.getNoteStyleId(noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
gridGhostNote.playNoteAnimation(); gridGhostNote.playNoteAnimation();
} }
noteData.time = cursorSnappedMs; noteData.time = cursorSnappedMs;
@ -5129,46 +5161,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handlePlayhead():Void function handlePlayhead():Void
{ {
// Place notes at the playhead with the keyboard. // Place notes at the playhead with the keyboard.
switch (currentLiveInputStyle) for (note => key in LIVE_INPUT_KEYS[currentLiveInputStyle])
{ {
case ChartEditorLiveInputStyle.WASDKeys: if (FlxG.keys.checkStatus(key, JUST_PRESSED)) placeNoteAtPlayhead(note)
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4); else if (FlxG.keys.checkStatus(key, JUST_RELEASED)) finishPlaceNoteAtPlayhead(note);
if (FlxG.keys.justReleased.A) finishPlaceNoteAtPlayhead(4);
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
if (FlxG.keys.justReleased.S) finishPlaceNoteAtPlayhead(5);
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
if (FlxG.keys.justReleased.W) finishPlaceNoteAtPlayhead(6);
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
if (FlxG.keys.justReleased.D) finishPlaceNoteAtPlayhead(7);
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
if (FlxG.keys.justReleased.LEFT) finishPlaceNoteAtPlayhead(0);
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
if (FlxG.keys.justReleased.DOWN) finishPlaceNoteAtPlayhead(1);
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
if (FlxG.keys.justReleased.UP) finishPlaceNoteAtPlayhead(2);
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
if (FlxG.keys.justReleased.RIGHT) finishPlaceNoteAtPlayhead(3);
case ChartEditorLiveInputStyle.NumberKeys:
// Flipped because Dad is on the left but represents data 0-3.
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
if (FlxG.keys.justReleased.ONE) finishPlaceNoteAtPlayhead(4);
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
if (FlxG.keys.justReleased.TWO) finishPlaceNoteAtPlayhead(5);
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
if (FlxG.keys.justReleased.THREE) finishPlaceNoteAtPlayhead(6);
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
if (FlxG.keys.justReleased.FOUR) finishPlaceNoteAtPlayhead(7);
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(0);
if (FlxG.keys.justReleased.FIVE) finishPlaceNoteAtPlayhead(0);
if (FlxG.keys.justPressed.SIX) placeNoteAtPlayhead(1);
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
if (FlxG.keys.justReleased.SEVEN) finishPlaceNoteAtPlayhead(2);
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
if (FlxG.keys.justReleased.EIGHT) finishPlaceNoteAtPlayhead(3);
case ChartEditorLiveInputStyle.None:
// Do nothing.
} }
// Place events at playhead. // Place events at playhead.
@ -5196,7 +5192,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (notesAtPos.length == 0 && !removeNoteInstead) if (notesAtPos.length == 0 && !removeNoteInstead)
{ {
trace('Placing note. ${column}'); trace('Placing note. ${column}');
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace); var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace, ChartEditorState.cloneNoteParams(noteParamsToPlace));
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
currentLiveInputPlaceNoteData[column] = newNoteData; currentLiveInputPlaceNoteData[column] = newNoteData;
} }
@ -5282,6 +5278,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
ghostHold.visible = true; ghostHold.visible = true;
ghostHold.alpha = 0.6; ghostHold.alpha = 0.6;
ghostHold.setHeightDirectly(0); ghostHold.setHeightDirectly(0);
ghostHold.noteStyle = NoteKindManager.getNoteStyleId(ghostHold.noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
ghostHold.updateHoldNotePosition(renderedHoldNotes); ghostHold.updateHoldNotePosition(renderedHoldNotes);
} }
@ -5648,6 +5645,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0); FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0);
FlxG.watch.addQuick('noteKindToPlace', noteKindToPlace); FlxG.watch.addQuick('noteKindToPlace', noteKindToPlace);
FlxG.watch.addQuick('noteParamsToPlace', noteParamsToPlace);
FlxG.watch.addQuick('eventKindToPlace', eventKindToPlace); FlxG.watch.addQuick('eventKindToPlace', eventKindToPlace);
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels); FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
@ -6511,6 +6509,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
} }
return input; return input;
} }
public static function cloneNoteParams(paramsToClone:Array<NoteParamData>):Array<NoteParamData>
{
var params:Array<NoteParamData> = [];
for (param in paramsToClone)
{
params.push(param.clone());
}
return params;
}
} }
/** /**

View file

@ -2,6 +2,7 @@ package funkin.ui.debug.charting.components;
import funkin.play.notes.Strumline; import funkin.play.notes.Strumline;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.graphics.frames.FlxFramesCollection; import flixel.graphics.frames.FlxFramesCollection;
@ -15,6 +16,7 @@ import flixel.math.FlxMath;
* A sprite that can be used to display the trail of a hold note in a chart. * A sprite that can be used to display the trail of a hold note in a chart.
* Designed to be used and reused efficiently. Has no gameplay functionality. * Designed to be used and reused efficiently. Has no gameplay functionality.
*/ */
@:access(funkin.ui.debug.charting.ChartEditorState)
@:nullSafety @:nullSafety
class ChartEditorHoldNoteSprite extends SustainTrail class ChartEditorHoldNoteSprite extends SustainTrail
{ {
@ -23,6 +25,22 @@ class ChartEditorHoldNoteSprite extends SustainTrail
*/ */
public var parentState:ChartEditorState; public var parentState:ChartEditorState;
@:isVar
public var noteStyle(get, set):Null<String>;
function get_noteStyle():Null<String>
{
return this.noteStyle ?? this.parentState.currentSongNoteStyle;
}
@:nullSafety(Off)
function set_noteStyle(value:Null<String>):Null<String>
{
this.noteStyle = value;
this.updateHoldNoteGraphic();
return value;
}
public function new(parent:ChartEditorState) public function new(parent:ChartEditorState)
{ {
var noteStyle = NoteStyleRegistry.instance.fetchDefault(); var noteStyle = NoteStyleRegistry.instance.fetchDefault();
@ -30,14 +48,50 @@ class ChartEditorHoldNoteSprite extends SustainTrail
super(0, 100, noteStyle); super(0, 100, noteStyle);
this.parentState = parent; this.parentState = parent;
}
@:nullSafety(Off)
function updateHoldNoteGraphic():Void
{
var bruhStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyle);
if (bruhStyle == null) bruhStyle = NoteStyleRegistry.instance.fetchDefault();
setupHoldNoteGraphic(bruhStyle);
}
override function setupHoldNoteGraphic(noteStyle:NoteStyle):Void
{
loadGraphic(noteStyle.getHoldNoteAssetPath());
antialiasing = true;
this.isPixel = noteStyle.isHoldNotePixel();
if (isPixel)
{
endOffset = bottomClip = 1;
antialiasing = false;
}
else
{
endOffset = 0.5;
bottomClip = 0.9;
}
zoom = 1.0; zoom = 1.0;
zoom *= noteStyle.fetchHoldNoteScale(); zoom *= noteStyle.fetchHoldNoteScale();
zoom *= 0.7; zoom *= 0.7;
zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE; zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE;
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
graphicHeight = sustainLength * 0.45; // sustainHeight
flipY = false; flipY = false;
alpha = 1.0;
updateColorTransform();
updateClipping();
setup(); setup();
} }

View file

@ -7,7 +7,11 @@ import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFrame; import flixel.graphics.frames.FlxFrame;
import flixel.graphics.frames.FlxTileFrames; import flixel.graphics.frames.FlxTileFrames;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
import funkin.data.animation.AnimationData;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.NoteDirection;
/** /**
* A sprite that can be used to display a note in a chart. * A sprite that can be used to display a note in a chart.
@ -36,7 +40,8 @@ class ChartEditorNoteSprite extends FlxSprite
/** /**
* The name of the note style currently in use. * The name of the note style currently in use.
*/ */
public var noteStyle(get, never):String; @:isVar
public var noteStyle(get, set):Null<String>;
public var overrideStepTime(default, set):Null<Float> = null; public var overrideStepTime(default, set):Null<Float> = null;
@ -66,72 +71,80 @@ class ChartEditorNoteSprite extends FlxSprite
this.parentState = parent; this.parentState = parent;
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
if (noteFrameCollection == null) if (noteFrameCollection == null)
{ {
initFrameCollection(); buildEmptyFrameCollection();
for (entry in entries)
{
addNoteStyleFrames(fetchNoteStyle(entry));
}
} }
if (noteFrameCollection == null) throw 'ERROR: Could not initialize note sprite animations.'; if (noteFrameCollection == null) throw 'ERROR: Could not initialize note sprite animations.';
this.frames = noteFrameCollection; this.frames = noteFrameCollection;
// Initialize all the animations, not just the one we're going to use immediately, for (entry in entries)
// so that later we can reuse the sprite without having to initialize more animations during scrolling. {
this.animation.addByPrefix('tapLeftFunkin', 'purple instance'); addNoteStyleAnimations(fetchNoteStyle(entry));
this.animation.addByPrefix('tapDownFunkin', 'blue instance'); }
this.animation.addByPrefix('tapUpFunkin', 'green instance');
this.animation.addByPrefix('tapRightFunkin', 'red instance');
this.animation.addByPrefix('holdLeftFunkin', 'LeftHoldPiece');
this.animation.addByPrefix('holdDownFunkin', 'DownHoldPiece');
this.animation.addByPrefix('holdUpFunkin', 'UpHoldPiece');
this.animation.addByPrefix('holdRightFunkin', 'RightHoldPiece');
this.animation.addByPrefix('holdEndLeftFunkin', 'LeftHoldEnd');
this.animation.addByPrefix('holdEndDownFunkin', 'DownHoldEnd');
this.animation.addByPrefix('holdEndUpFunkin', 'UpHoldEnd');
this.animation.addByPrefix('holdEndRightFunkin', 'RightHoldEnd');
this.animation.addByPrefix('tapLeftPixel', 'pixel4');
this.animation.addByPrefix('tapDownPixel', 'pixel5');
this.animation.addByPrefix('tapUpPixel', 'pixel6');
this.animation.addByPrefix('tapRightPixel', 'pixel7');
} }
static var noteFrameCollection:Null<FlxFramesCollection> = null; static var noteFrameCollection:Null<FlxFramesCollection> = null;
/** function fetchNoteStyle(noteStyleId:String):NoteStyle
* We load all the note frames once, then reuse them.
*/
static function initFrameCollection():Void
{ {
buildEmptyFrameCollection(); var result = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
if (noteFrameCollection == null) return; if (result != null) return result;
return NoteStyleRegistry.instance.fetchDefault();
}
// TODO: Automatically iterate over the list of note skins. @:access(funkin.play.notes.notestyle.NoteStyle)
@:nullSafety(Off)
static function addNoteStyleFrames(noteStyle:NoteStyle):Void
{
var prefix:String = noteStyle.id.toTitleCase();
// Normal notes var frameCollection:FlxAtlasFrames = Paths.getSparrowAtlas(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary());
var frameCollectionNormal:FlxAtlasFrames = Paths.getSparrowAtlas('NOTE_assets'); if (frameCollection == null)
for (frame in frameCollectionNormal.frames)
{ {
noteFrameCollection.pushFrame(frame); trace('Could not retrieve frame collection for ${noteStyle}: ${Paths.image(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary())}');
FlxG.log.error('Could not retrieve frame collection for ${noteStyle}: ${Paths.image(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary())}');
return;
} }
for (frame in frameCollection.frames)
// Pixel notes
var graphicPixel = FlxG.bitmap.add(Paths.image('weeb/pixelUI/arrows-pixels', 'week6'), false, null);
if (graphicPixel == null) trace('ERROR: Could not load graphic: ' + Paths.image('weeb/pixelUI/arrows-pixels', 'week6'));
var frameCollectionPixel = FlxTileFrames.fromGraphic(graphicPixel, new FlxPoint(17, 17));
for (i in 0...frameCollectionPixel.frames.length)
{ {
var frame:Null<FlxFrame> = frameCollectionPixel.frames[i]; // cloning the frame because else
if (frame == null) continue; // we will fuck up the frame data used in game
var clonedFrame:FlxFrame = frame.copyTo();
frame.name = 'pixel' + i; clonedFrame.name = '$prefix${clonedFrame.name}';
noteFrameCollection.pushFrame(frame); noteFrameCollection.pushFrame(clonedFrame);
} }
} }
@:access(funkin.play.notes.notestyle.NoteStyle)
@:nullSafety(Off)
function addNoteStyleAnimations(noteStyle:NoteStyle):Void
{
var prefix:String = noteStyle.id.toTitleCase();
var suffix:String = noteStyle.id.toTitleCase();
var leftData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.LEFT);
this.animation.addByPrefix('tapLeft$suffix', '$prefix${leftData.prefix}', leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY);
var downData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.DOWN);
this.animation.addByPrefix('tapDown$suffix', '$prefix${downData.prefix}', downData.frameRate, downData.looped, downData.flipX, downData.flipY);
var upData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.UP);
this.animation.addByPrefix('tapUp$suffix', '$prefix${upData.prefix}', upData.frameRate, upData.looped, upData.flipX, upData.flipY);
var rightData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.RIGHT);
this.animation.addByPrefix('tapRight$suffix', '$prefix${rightData.prefix}', rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
}
@:nullSafety(Off) @:nullSafety(Off)
static function buildEmptyFrameCollection():Void static function buildEmptyFrameCollection():Void
{ {
@ -185,12 +198,24 @@ class ChartEditorNoteSprite extends FlxSprite
} }
} }
function get_noteStyle():String function get_noteStyle():Null<String>
{ {
// Fall back to Funkin' if it's not a valid note style. if (this.noteStyle == null)
return if (NOTE_STYLES.contains(this.parentState.currentSongNoteStyle)) this.parentState.currentSongNoteStyle else 'funkin'; {
var result = this.parentState.currentSongNoteStyle;
return result;
}
return this.noteStyle;
} }
function set_noteStyle(value:Null<String>):Null<String>
{
this.noteStyle = value;
this.playNoteAnimation();
return value;
}
@:nullSafety(Off)
public function playNoteAnimation():Void public function playNoteAnimation():Void
{ {
if (this.noteData == null) return; if (this.noteData == null) return;
@ -200,6 +225,7 @@ class ChartEditorNoteSprite extends FlxSprite
// Play the appropriate animation for the type, direction, and skin. // Play the appropriate animation for the type, direction, and skin.
var dirName:String = overrideData != null ? SongNoteData.buildDirectionName(overrideData) : this.noteData.getDirectionName(); var dirName:String = overrideData != null ? SongNoteData.buildDirectionName(overrideData) : this.noteData.getDirectionName();
var noteStyleSuffix:String = this.noteStyle?.toTitleCase() ?? Constants.DEFAULT_NOTE_STYLE.toTitleCase();
var animationName:String = '${baseAnimationName}${dirName}${this.noteStyle.toTitleCase()}'; var animationName:String = '${baseAnimationName}${dirName}${this.noteStyle.toTitleCase()}';
this.animation.play(animationName); this.animation.play(animationName);
@ -209,12 +235,12 @@ class ChartEditorNoteSprite extends FlxSprite
switch (baseAnimationName) switch (baseAnimationName)
{ {
case 'tap': case 'tap':
this.setGraphicSize(0, ChartEditorState.GRID_SIZE); this.setGraphicSize(ChartEditorState.GRID_SIZE, 0);
this.updateHitbox();
} }
this.updateHitbox();
// TODO: Make this an attribute of the note skin. var bruhStyle:NoteStyle = fetchNoteStyle(this.noteStyle);
this.antialiasing = (this.parentState.currentSongNoteStyle != 'Pixel'); this.antialiasing = !bruhStyle._data?.assets?.note?.isPixel ?? true;
} }
/** /**

View file

@ -2,8 +2,16 @@ package funkin.ui.debug.charting.toolboxes;
import haxe.ui.components.DropDown; import haxe.ui.components.DropDown;
import haxe.ui.components.TextField; import haxe.ui.components.TextField;
import haxe.ui.components.Label;
import haxe.ui.components.NumberStepper;
import haxe.ui.containers.Grid;
import haxe.ui.core.Component;
import haxe.ui.events.UIEvent; import haxe.ui.events.UIEvent;
import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.ui.debug.charting.util.ChartEditorDropdowns;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.notes.notekind.NoteKind.NoteKindParam;
import funkin.play.notes.notekind.NoteKind.NoteKindParamType;
import funkin.data.song.SongData.NoteParamData;
/** /**
* The toolbox which allows modifying information like Note Kind. * The toolbox which allows modifying information like Note Kind.
@ -12,8 +20,22 @@ import funkin.ui.debug.charting.util.ChartEditorDropdowns;
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/note-data.xml")) @:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/note-data.xml"))
class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
{ {
// 100 is the height used in note-data.xml
static final DIALOG_HEIGHT:Int = 100;
// toolboxNotesGrid.height + 45
// this is what i found out by printing this.height and grid.height
// and then seeing that this.height is 100 and grid.height is 55
static final HEIGHT_OFFSET:Int = 45;
// minimizing creates a gray bar the bottom, which would obscure the components,
// which is why we use an extra offset of 20
static final MINIMIZE_FIX:Int = 20;
var toolboxNotesGrid:Grid;
var toolboxNotesNoteKind:DropDown; var toolboxNotesNoteKind:DropDown;
var toolboxNotesCustomKind:TextField; var toolboxNotesCustomKind:TextField;
var toolboxNotesParams:Array<ToolboxNoteKindParam> = [];
var _initializing:Bool = true; var _initializing:Bool = true;
@ -54,12 +76,35 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace; toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace;
} }
createNoteKindParams(noteKind);
if (!_initializing && chartEditorState.currentNoteSelection.length > 0) if (!_initializing && chartEditorState.currentNoteSelection.length > 0)
{ {
// Edit the note data of any selected notes.
for (note in chartEditorState.currentNoteSelection) for (note in chartEditorState.currentNoteSelection)
{ {
// Edit the note data of any selected notes.
note.kind = chartEditorState.noteKindToPlace; note.kind = chartEditorState.noteKindToPlace;
note.params = ChartEditorState.cloneNoteParams(chartEditorState.noteParamsToPlace);
// update note sprites
for (noteSprite in chartEditorState.renderedNotes.members)
{
if (noteSprite.noteData == note)
{
noteSprite.noteStyle = NoteKindManager.getNoteStyleId(note.kind) ?? chartEditorState.currentSongNoteStyle;
break;
}
}
// update hold note sprites
for (holdNoteSprite in chartEditorState.renderedHoldNotes.members)
{
if (holdNoteSprite.noteData == note)
{
holdNoteSprite.noteStyle = NoteKindManager.getNoteStyleId(note.kind) ?? chartEditorState.currentSongNoteStyle;
break;
}
}
} }
chartEditorState.saveDataDirty = true; chartEditorState.saveDataDirty = true;
chartEditorState.noteDisplayDirty = true; chartEditorState.noteDisplayDirty = true;
@ -94,6 +139,8 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
toolboxNotesNoteKind.value = ChartEditorDropdowns.lookupNoteKind(chartEditorState.noteKindToPlace); toolboxNotesNoteKind.value = ChartEditorDropdowns.lookupNoteKind(chartEditorState.noteKindToPlace);
toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace; toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace;
createNoteKindParams(chartEditorState.noteKindToPlace);
} }
function showCustom():Void function showCustom():Void
@ -108,8 +155,149 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
toolboxNotesCustomKind.hidden = true; toolboxNotesCustomKind.hidden = true;
} }
function createNoteKindParams(noteKind:Null<String>):Void
{
clearNoteKindParams();
var setParamsToPlace:Bool = false;
if (!_initializing)
{
for (note in chartEditorState.currentNoteSelection)
{
if (note.kind == chartEditorState.noteKindToPlace)
{
chartEditorState.noteParamsToPlace = ChartEditorState.cloneNoteParams(note.params);
setParamsToPlace = true;
break;
}
}
}
var noteKindParams:Array<NoteKindParam> = NoteKindManager.getParams(noteKind);
for (i in 0...noteKindParams.length)
{
var param:NoteKindParam = noteKindParams[i];
var paramLabel:Label = new Label();
paramLabel.value = param.description;
paramLabel.verticalAlign = "center";
paramLabel.horizontalAlign = "right";
var paramComponent:Component = null;
switch (param.type)
{
case NoteKindParamType.INT | NoteKindParamType.FLOAT:
var paramStepper:NumberStepper = new NumberStepper();
paramStepper.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? 0.0;
paramStepper.percentWidth = 100;
paramStepper.step = param.data?.step ?? 1;
// this check should be unnecessary but for some reason
// even when these are null it will set it to 0
if (param.data?.min != null)
{
paramStepper.min = param.data.min;
}
if (param.data?.max != null)
{
paramStepper.max = param.data.max;
}
if (param.data?.precision != null)
{
paramStepper.precision = param.data.precision;
}
paramComponent = paramStepper;
case NoteKindParamType.STRING:
var paramTextField:TextField = new TextField();
paramTextField.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? '';
paramTextField.percentWidth = 100;
paramComponent = paramTextField;
}
if (paramComponent == null)
{
continue;
}
paramComponent.onChange = function(event:UIEvent) {
chartEditorState.noteParamsToPlace[i].value = paramComponent.value;
for (note in chartEditorState.currentNoteSelection)
{
if (note.params.length != noteKindParams.length)
{
break;
}
if (note.params[i].name == param.name)
{
note.params[i].value = paramComponent.value;
}
}
}
addNoteKindParam(paramLabel, paramComponent);
}
if (!setParamsToPlace)
{
var noteParamData:Array<NoteParamData> = [];
for (i in 0...noteKindParams.length)
{
noteParamData.push(new NoteParamData(noteKindParams[i].name, toolboxNotesParams[i].component.value));
}
chartEditorState.noteParamsToPlace = noteParamData;
}
}
function addNoteKindParam(label:Label, component:Component):Void
{
toolboxNotesParams.push({label: label, component: component});
toolboxNotesGrid.addComponent(label);
toolboxNotesGrid.addComponent(component);
this.height = Math.max(DIALOG_HEIGHT, DIALOG_HEIGHT - 30 + toolboxNotesParams.length * 30);
}
function clearNoteKindParams():Void
{
for (param in toolboxNotesParams)
{
toolboxNotesGrid.removeComponent(param.component);
toolboxNotesGrid.removeComponent(param.label);
}
toolboxNotesParams = [];
this.height = DIALOG_HEIGHT;
}
override function update(elapsed:Float):Void
{
super.update(elapsed);
// current dialog is minimized, dont change the height
if (this.minimized)
{
return;
}
var heightToSet:Int = Std.int(Math.max(DIALOG_HEIGHT, (toolboxNotesGrid?.height ?? 50) + HEIGHT_OFFSET)) + MINIMIZE_FIX;
if (this.height != heightToSet)
{
this.height = heightToSet;
}
}
public static function build(chartEditorState:ChartEditorState):ChartEditorNoteDataToolbox public static function build(chartEditorState:ChartEditorState):ChartEditorNoteDataToolbox
{ {
return new ChartEditorNoteDataToolbox(chartEditorState); return new ChartEditorNoteDataToolbox(chartEditorState);
} }
} }
typedef ToolboxNoteKindParam =
{
var label:Label;
var component:Component;
}

View file

@ -135,6 +135,14 @@ class ChartEditorDropdowns
var noteStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyleId); var noteStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
if (noteStyle == null) continue; if (noteStyle == null) continue;
// check if the note style has all necessary assets (strums, notes, holdNotes)
if (noteStyle._data?.assets?.noteStrumline == null
|| noteStyle._data?.assets?.note == null
|| noteStyle._data?.assets?.holdNote == null)
{
continue;
}
var value = {id: noteStyleId, text: noteStyle.getName()}; var value = {id: noteStyleId, text: noteStyle.getName()};
if (startingStyleId == noteStyleId) returnValue = value; if (startingStyleId == noteStyleId) returnValue = value;
@ -146,7 +154,7 @@ class ChartEditorDropdowns
return returnValue; return returnValue;
} }
static final NOTE_KINDS:Map<String, String> = [ public static final NOTE_KINDS:Map<String, String> = [
// Base // Base
"" => "Default", "" => "Default",
"~CUSTOM~" => "Custom", "~CUSTOM~" => "Custom",
@ -187,11 +195,11 @@ class ChartEditorDropdowns
{ {
dropDown.dataSource.clear(); dropDown.dataSource.clear();
var returnValue:DropDownEntry = lookupNoteKind('~CUSTOM'); var returnValue:DropDownEntry = lookupNoteKind('');
for (noteKindId in NOTE_KINDS.keys()) for (noteKindId in NOTE_KINDS.keys())
{ {
var noteKind:String = NOTE_KINDS.get(noteKindId) ?? 'Default'; var noteKind:String = NOTE_KINDS.get(noteKindId) ?? 'Unknown';
var value:DropDownEntry = {id: noteKindId, text: noteKind}; var value:DropDownEntry = {id: noteKindId, text: noteKind};
if (startingKindId == noteKindId) returnValue = value; if (startingKindId == noteKindId) returnValue = value;
@ -208,7 +216,7 @@ class ChartEditorDropdowns
{ {
if (noteKindId == null) return lookupNoteKind(''); if (noteKindId == null) return lookupNoteKind('');
if (!NOTE_KINDS.exists(noteKindId)) return {id: '~CUSTOM~', text: 'Custom'}; if (!NOTE_KINDS.exists(noteKindId)) return {id: '~CUSTOM~', text: 'Custom'};
return {id: noteKindId ?? '', text: NOTE_KINDS.get(noteKindId) ?? 'Default'}; return {id: noteKindId ?? '', text: NOTE_KINDS.get(noteKindId) ?? 'Unknown'};
} }
/** /**

View file

@ -1,6 +1,9 @@
package funkin.util.plugins; package funkin.util.plugins;
import flixel.FlxG;
import flixel.FlxBasic; import flixel.FlxBasic;
import funkin.ui.MusicBeatState;
import funkin.ui.MusicBeatSubState;
/** /**
* A plugin which adds functionality to press `F5` to reload all game assets, then reload the current state. * A plugin which adds functionality to press `F5` to reload all game assets, then reload the current state.
@ -28,10 +31,15 @@ class ReloadAssetsDebugPlugin extends FlxBasic
if (FlxG.keys.justPressed.F5) if (FlxG.keys.justPressed.F5)
#end #end
{ {
funkin.modding.PolymodHandler.forceReloadAssets(); var state:Dynamic = FlxG.state;
if (state is MusicBeatState || state is MusicBeatSubState) state.reloadAssets();
else
{
funkin.modding.PolymodHandler.forceReloadAssets();
// Create a new instance of the current state, so old data is cleared. // Create a new instance of the current state, so old data is cleared.
FlxG.resetState(); FlxG.resetState();
}
} }
} }