mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-14 19:25:16 -05:00
Remove funkin.play.song.SongData and refactor app to match.
This commit is contained in:
parent
dfedaa8838
commit
f4bc682ea1
40 changed files with 339 additions and 1693 deletions
6
docs/troubleshooting.md
Normal file
6
docs/troubleshooting.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Troubleshooting Common Issues
|
||||
|
||||
- Weird macro error with a very tall call stack: Restart Visual Studio Code
|
||||
- `Get Thread Context Failed`: Turn off other expensive applications while building
|
||||
- `Type not found: T1`: This is thrown by `json2object`, make sure the data type of `@:default` is correct.
|
||||
- NOTE: `flixel.util.typeLimit.OneOfTwo` isn't supported.
|
|
@ -4,7 +4,7 @@ import funkin.util.Constants;
|
|||
import flixel.util.FlxSignal;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.play.song.Song.SongDifficulty;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
|
||||
/**
|
||||
* A core class which handles musical timing throughout the game,
|
||||
|
|
|
@ -37,7 +37,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
{
|
||||
super();
|
||||
|
||||
switch (PlayState.instance.currentSong.songId.toLowerCase())
|
||||
switch (PlayState.instance.currentSong.id.toLowerCase())
|
||||
{
|
||||
case 'senpai':
|
||||
FlxG.sound.playMusic(Paths.music('Lunchbox'), 0);
|
||||
|
@ -78,7 +78,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
box = new FlxSprite(-20, 45);
|
||||
|
||||
var hasDialog:Bool = false;
|
||||
switch (PlayState.instance.currentSong.songId.toLowerCase())
|
||||
switch (PlayState.instance.currentSong.id.toLowerCase())
|
||||
{
|
||||
case 'senpai':
|
||||
hasDialog = true;
|
||||
|
@ -150,8 +150,8 @@ class DialogueBox extends FlxSpriteGroup
|
|||
override function update(elapsed:Float):Void
|
||||
{
|
||||
// HARD CODING CUZ IM STUPDI
|
||||
if (PlayState.instance.currentSong.songId.toLowerCase() == 'roses') portraitLeft.visible = false;
|
||||
if (PlayState.instance.currentSong.songId.toLowerCase() == 'thorns')
|
||||
if (PlayState.instance.currentSong.id.toLowerCase() == 'roses') portraitLeft.visible = false;
|
||||
if (PlayState.instance.currentSong.id.toLowerCase() == 'thorns')
|
||||
{
|
||||
portraitLeft.color = FlxColor.BLACK;
|
||||
swagDialogue.color = FlxColor.WHITE;
|
||||
|
@ -187,8 +187,8 @@ class DialogueBox extends FlxSpriteGroup
|
|||
{
|
||||
isEnding = true;
|
||||
|
||||
if (PlayState.instance.currentSong.songId.toLowerCase() == 'senpai'
|
||||
|| PlayState.instance.currentSong.songId.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0);
|
||||
if (PlayState.instance.currentSong.id.toLowerCase() == 'senpai'
|
||||
|| PlayState.instance.currentSong.id.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0);
|
||||
|
||||
new FlxTimer().start(0.2, function(tmr:FlxTimer) {
|
||||
box.alpha -= 1 / 5;
|
||||
|
|
|
@ -20,6 +20,7 @@ import flixel.text.FlxText;
|
|||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.Controls.Control;
|
||||
|
@ -30,7 +31,6 @@ import funkin.freeplayStuff.LetterSort;
|
|||
import funkin.freeplayStuff.SongMenuItem;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
import funkin.shaderslmfao.StrokeShader;
|
||||
|
@ -843,7 +843,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
}*/
|
||||
|
||||
PlayStatePlaylist.isStoryMode = false;
|
||||
var targetSong:Song = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase());
|
||||
var songId:String = songs[curSelected].songName.toLowerCase();
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(songId);
|
||||
var targetDifficulty:String = switch (curDifficulty)
|
||||
{
|
||||
case 0:
|
||||
|
|
|
@ -17,11 +17,11 @@ import funkin.play.PlayStatePlaylist;
|
|||
import openfl.display.BitmapData;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
|
||||
import funkin.play.cutscene.dialogue.SpeakerDataParser;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
|
@ -197,13 +197,13 @@ class InitState extends FlxState
|
|||
|
||||
// NOTE: Registries and data parsers must be imported and not referenced with fully qualified names,
|
||||
// to ensure build macros work properly.
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventParser.loadEventCache();
|
||||
ConversationDataParser.loadConversationCache();
|
||||
DialogueBoxDataParser.loadDialogueBoxCache();
|
||||
SpeakerDataParser.loadSpeakerCache();
|
||||
SongDataParser.loadSongCache();
|
||||
StageDataParser.loadStageCache();
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
|
@ -276,7 +276,7 @@ class InitState extends FlxState
|
|||
*/
|
||||
function startSong(songId:String, difficultyId:String = 'normal'):Void
|
||||
{
|
||||
var songData:funkin.play.song.Song = funkin.play.song.SongData.SongDataParser.fetchSong(songId);
|
||||
var songData:funkin.play.song.Song = funkin.data.song.SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
if (songData == null)
|
||||
{
|
||||
|
@ -312,7 +312,7 @@ class InitState extends FlxState
|
|||
|
||||
var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift();
|
||||
|
||||
var targetSong:funkin.play.song.Song = funkin.play.song.SongData.SongDataParser.fetchSong(targetSongId);
|
||||
var targetSong:funkin.play.song.Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
|
||||
LoadingState.loadAndSwitchState(new funkin.play.PlayState(
|
||||
{
|
||||
|
|
|
@ -159,7 +159,7 @@ class LoadingState extends MusicBeatState
|
|||
|
||||
static function getSongPath():String
|
||||
{
|
||||
return Paths.inst(PlayState.instance.currentSong.songId);
|
||||
return Paths.inst(PlayState.instance.currentSong.id);
|
||||
}
|
||||
|
||||
inline static public function loadAndSwitchState(nextState:FlxState, shouldStopMusic = false):Void
|
||||
|
|
|
@ -10,7 +10,7 @@ import flixel.tweens.FlxEase;
|
|||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.data.song.SongRegistry;
|
||||
|
||||
class PauseSubState extends MusicBeatSubState
|
||||
{
|
||||
|
@ -197,7 +197,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
regenMenu();
|
||||
|
||||
case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT':
|
||||
PlayState.instance.currentSong = SongDataParser.fetchSong(PlayState.instance.currentSong.songId.toLowerCase());
|
||||
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
|
||||
|
||||
PlayState.instance.currentDifficulty = daSelected.toLowerCase();
|
||||
|
||||
|
|
|
@ -3,18 +3,19 @@ package funkin.modding;
|
|||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.song.SongData;
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.play.stage.StageData;
|
||||
import polymod.Polymod;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
import funkin.play.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
|
||||
import funkin.play.cutscene.dialogue.SpeakerDataParser;
|
||||
import funkin.data.song.SongRegistry;
|
||||
|
||||
class PolymodHandler
|
||||
{
|
||||
|
@ -290,13 +291,13 @@ class PolymodHandler
|
|||
|
||||
// These MUST be imported at the top of the file and not referred to by fully qualified name,
|
||||
// to ensure build macros work properly.
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventParser.loadEventCache();
|
||||
ConversationDataParser.loadConversationCache();
|
||||
DialogueBoxDataParser.loadDialogueBoxCache();
|
||||
SpeakerDataParser.loadSpeakerCache();
|
||||
SongDataParser.loadSongCache();
|
||||
StageDataParser.loadStageCache();
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
|
@ -435,9 +435,9 @@ class SongEventScriptEvent extends ScriptEvent
|
|||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var event(default, null):funkin.play.song.SongData.SongEventData;
|
||||
public var event(default, null):funkin.data.song.SongData.SongEventData;
|
||||
|
||||
public function new(event:funkin.play.song.SongData.SongEventData):Void
|
||||
public function new(event:funkin.data.song.SongData.SongEventData):Void
|
||||
{
|
||||
super(ScriptEvent.SONG_EVENT, true);
|
||||
this.event = event;
|
||||
|
|
|
@ -35,7 +35,7 @@ import funkin.play.cutscene.dialogue.Conversation;
|
|||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.VanillaCutscenes;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.notes.Strumline;
|
||||
|
@ -43,10 +43,10 @@ import funkin.play.notes.SustainTrail;
|
|||
import funkin.play.scoring.Scoring;
|
||||
import funkin.NoteSplash;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongPlayableChar;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.ui.PopUpStuff;
|
||||
|
@ -630,7 +630,7 @@ class PlayState extends MusicBeatSubState
|
|||
startingSong = true;
|
||||
|
||||
// TODO: We hardcoded the transition into Winter Horrorland. Do this with a ScriptedSong instead.
|
||||
if ((currentSong?.songId ?? '').toLowerCase() == 'winter-horrorland')
|
||||
if ((currentSong?.id ?? '').toLowerCase() == 'winter-horrorland')
|
||||
{
|
||||
// VanillaCutscenes will call startCountdown later.
|
||||
VanillaCutscenes.playHorrorStartCutscene();
|
||||
|
@ -2495,9 +2495,9 @@ class PlayState extends MusicBeatSubState
|
|||
if (currentSong != null && currentSong.validScore)
|
||||
{
|
||||
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
||||
Highscore.tallies.isNewHighscore = Highscore.saveScoreForDifficulty(currentSong.songId, songScore, currentDifficulty);
|
||||
Highscore.tallies.isNewHighscore = Highscore.saveScoreForDifficulty(currentSong.id, songScore, currentDifficulty);
|
||||
|
||||
Highscore.saveCompletionForDifficulty(currentSong.songId, Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, currentDifficulty);
|
||||
Highscore.saveCompletionForDifficulty(currentSong.id, Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, currentDifficulty);
|
||||
}
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
|
@ -2549,7 +2549,7 @@ class PlayState extends MusicBeatSubState
|
|||
vocals.stop();
|
||||
|
||||
// TODO: Softcode this cutscene.
|
||||
if (currentSong.songId == 'eggnog')
|
||||
if (currentSong.id == 'eggnog')
|
||||
{
|
||||
var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom,
|
||||
-FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK);
|
||||
|
@ -2560,7 +2560,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
FlxG.sound.play(Paths.sound('Lights_Shut_off'), function() {
|
||||
// no camFollow so it centers on horror tree
|
||||
var targetSong:Song = SongDataParser.fetchSong(targetSongId);
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
targetSong.cacheCharts(true);
|
||||
|
@ -2577,7 +2577,7 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
var targetSong:Song = SongDataParser.fetchSong(targetSongId);
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
targetSong.cacheCharts(true);
|
||||
|
|
|
@ -143,7 +143,7 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
songName.text += PlayState.instance.currentSong.songId;
|
||||
songName.text += PlayState.instance.currentSong.id;
|
||||
}
|
||||
|
||||
songName.letterSpacing = -15;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import funkin.play.song.SongData;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.event.SongEventData.SongEventFieldType;
|
||||
import funkin.play.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
|
||||
/**
|
||||
* This class represents a handler for a type of song event.
|
||||
|
|
|
@ -2,10 +2,13 @@ package funkin.play.event;
|
|||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.event.SongEventData.SongEventFieldType;
|
||||
import funkin.play.event.SongEventData.SongEventSchema;
|
||||
import funkin.play.song.SongData;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
|
||||
class PlayAnimationSongEvent extends SongEvent
|
||||
{
|
||||
|
|
|
@ -3,10 +3,13 @@ package funkin.play.event;
|
|||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.song.SongData;
|
||||
import funkin.play.event.SongEventData;
|
||||
import funkin.play.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
|
||||
/**
|
||||
* This class represents a handler for configuring camera bop intensity and rate.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
|
||||
/**
|
||||
* This class represents a handler for a type of song event.
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import funkin.play.event.SongEventData.SongEventSchema;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.play.event.ScriptedSongEvent;
|
||||
|
||||
/**
|
||||
* This class statically handles the parsing of internal and scripted song event handlers.
|
||||
*/
|
||||
class SongEventParser
|
||||
{
|
||||
/**
|
||||
* Every built-in event class must be added to this list.
|
||||
* Thankfully, with the power of `SongEventMacro`, this is done automatically.
|
||||
*/
|
||||
static final BUILTIN_EVENTS:List<Class<SongEvent>> = ClassMacro.listSubclassesOf(SongEvent);
|
||||
|
||||
/**
|
||||
* Map of internal handlers for song events.
|
||||
* These may be either `ScriptedSongEvents` or built-in classes extending `SongEvent`.
|
||||
*/
|
||||
static final eventCache:Map<String, SongEvent> = new Map<String, SongEvent>();
|
||||
|
||||
public static function loadEventCache():Void
|
||||
{
|
||||
clearEventCache();
|
||||
|
||||
//
|
||||
// BASE GAME EVENTS
|
||||
//
|
||||
registerBaseEvents();
|
||||
registerScriptedEvents();
|
||||
}
|
||||
|
||||
static function registerBaseEvents()
|
||||
{
|
||||
trace('Instantiating ${BUILTIN_EVENTS.length} built-in song events...');
|
||||
for (eventCls in BUILTIN_EVENTS)
|
||||
{
|
||||
var eventClsName:String = Type.getClassName(eventCls);
|
||||
if (eventClsName == 'funkin.play.event.SongEvent' || eventClsName == 'funkin.play.event.ScriptedSongEvent') continue;
|
||||
|
||||
var event:SongEvent = Type.createInstance(eventCls, ["UNKNOWN"]);
|
||||
|
||||
if (event != null)
|
||||
{
|
||||
trace(' Loaded built-in song event: (${event.id})');
|
||||
eventCache.set(event.id, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function registerScriptedEvents()
|
||||
{
|
||||
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
||||
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
||||
|
||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||
for (eventCls in scriptedEventClassNames)
|
||||
{
|
||||
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||
|
||||
if (event != null)
|
||||
{
|
||||
trace(' Loaded scripted song event: ${event.id}');
|
||||
eventCache.set(event.id, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Failed to instantiate scripted song event class: ${eventCls}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function listEventIds():Array<String>
|
||||
{
|
||||
return eventCache.keys().array();
|
||||
}
|
||||
|
||||
public static function listEvents():Array<SongEvent>
|
||||
{
|
||||
return eventCache.values();
|
||||
}
|
||||
|
||||
public static function getEvent(id:String):SongEvent
|
||||
{
|
||||
return eventCache.get(id);
|
||||
}
|
||||
|
||||
public static function getEventSchema(id:String):SongEventSchema
|
||||
{
|
||||
var event:SongEvent = getEvent(id);
|
||||
if (event == null) return null;
|
||||
|
||||
return event.getEventSchema();
|
||||
}
|
||||
|
||||
static function clearEventCache()
|
||||
{
|
||||
eventCache.clear();
|
||||
}
|
||||
|
||||
public static function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
var eventType:String = data.event;
|
||||
var eventHandler:SongEvent = eventCache.get(eventType);
|
||||
|
||||
if (eventHandler != null)
|
||||
{
|
||||
eventHandler.handleEvent(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('WARNING: No event handler for event with id: ${eventType}');
|
||||
}
|
||||
|
||||
data.activated = true;
|
||||
}
|
||||
|
||||
public static inline function handleEvents(events:Array<SongEventData>):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of song events and the current timestamp,
|
||||
* return a list of events that should be handled.
|
||||
*/
|
||||
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
|
||||
{
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
// If the event is already activated, don't activate it again.
|
||||
if (event.activated) return false;
|
||||
|
||||
// If the event is in the future, don't activate it.
|
||||
if (event.time > currentTime) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset activation of all the provided events.
|
||||
*/
|
||||
public static function resetEvents(events:Array<SongEventData>):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
event.activated = false;
|
||||
// TODO: Add an onReset() method to SongEvent?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract SongEventFieldType(String) from String to String
|
||||
{
|
||||
/**
|
||||
* The STRING type will display as a text field.
|
||||
*/
|
||||
var STRING = "string";
|
||||
|
||||
/**
|
||||
* The INTEGER type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var INTEGER = "integer";
|
||||
|
||||
/**
|
||||
* The FLOAT type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var FLOAT = "float";
|
||||
|
||||
/**
|
||||
* The BOOL type will display as a checkbox.
|
||||
*/
|
||||
var BOOL = "bool";
|
||||
|
||||
/**
|
||||
* The ENUM type will display as a dropdown.
|
||||
* Make sure to specify the `keys` field in the schema.
|
||||
*/
|
||||
var ENUM = "enum";
|
||||
}
|
||||
|
||||
typedef SongEventSchemaField =
|
||||
{
|
||||
/**
|
||||
* The name of the property as it should be saved in the event data.
|
||||
*/
|
||||
name:String,
|
||||
|
||||
/**
|
||||
* The title of the field to display in the UI.
|
||||
*/
|
||||
title:String,
|
||||
|
||||
/**
|
||||
* The type of the field.
|
||||
*/
|
||||
type:SongEventFieldType,
|
||||
|
||||
/**
|
||||
* Used for ENUM values.
|
||||
* The key is the display name and the value is the actual value.
|
||||
*/
|
||||
?keys:Map<String, Dynamic>,
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The minimum value that can be entered.
|
||||
*/
|
||||
?min:Float,
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The maximum value that can be entered.
|
||||
*/
|
||||
?max:Float,
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The step value that will be used when incrementing/decrementing the value.
|
||||
*/
|
||||
?step:Float,
|
||||
/**
|
||||
* An optional default value for the field.
|
||||
*/
|
||||
?defaultValue:Dynamic,
|
||||
}
|
||||
|
||||
typedef SongEventSchema = Array<SongEventSchemaField>;
|
|
@ -3,10 +3,13 @@ package funkin.play.event;
|
|||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.song.SongData;
|
||||
import funkin.play.event.SongEventData;
|
||||
import funkin.play.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
|
||||
/**
|
||||
* This class represents a handler for camera zoom events.
|
||||
|
@ -76,8 +79,7 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
return;
|
||||
}
|
||||
|
||||
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepLengthMs * duration / 1000),
|
||||
{ease: easeFunction});
|
||||
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepLengthMs * duration / 1000), {ease: easeFunction});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.play.notes;
|
||||
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
|
|
|
@ -11,7 +11,7 @@ import funkin.play.notes.NoteHoldCover;
|
|||
import funkin.play.notes.NoteSplash;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.SustainTrail;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package funkin.play.notes;
|
|||
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
|
|
|
@ -104,7 +104,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
|
||||
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
|
||||
|
||||
if (noteFrames == null) {
|
||||
if (noteFrames == null)
|
||||
{
|
||||
throw 'Could not load note frames for note style: $id';
|
||||
}
|
||||
|
||||
|
@ -139,13 +140,13 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
function buildNoteAnimations(target:NoteSprite):Void
|
||||
{
|
||||
var leftData:AnimationData = fetchNoteAnimationData(LEFT);
|
||||
target.animation.addByPrefix('purpleScroll', leftData.prefix);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
|
||||
}
|
||||
|
||||
function fetchNoteAnimationData(dir:NoteDirection):AnimationData
|
||||
|
@ -302,7 +303,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
return 'NoteStyle($id)';
|
||||
}
|
||||
|
||||
public function _fetchData(id:String):Null<NoteStyleData>
|
||||
static function _fetchData(id:String):Null<NoteStyleData>
|
||||
{
|
||||
return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
|
|
|
@ -5,14 +5,16 @@ import openfl.utils.Assets;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.play.song.SongData.SongTimeFormat;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongPlayableChar;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongData.SongTimeFormat;
|
||||
import funkin.data.IRegistryEntry;
|
||||
|
||||
/**
|
||||
* This is a data structure managing information about the current song.
|
||||
|
@ -23,9 +25,26 @@ import funkin.play.song.SongData.SongTimeFormat;
|
|||
* It also receives script events; scripted classes which extend this class
|
||||
* can be used to perform custom gameplay behaviors only on specific songs.
|
||||
*/
|
||||
class Song implements IPlayStateScriptedClass
|
||||
@:nullSafety
|
||||
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
||||
{
|
||||
public final songId:String;
|
||||
public static final DEFAULT_SONGNAME:String = "Unknown";
|
||||
public static final DEFAULT_ARTIST:String = "Unknown";
|
||||
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
||||
public static final DEFAULT_DIVISIONS:Null<Int> = null;
|
||||
public static final DEFAULT_LOOPED:Bool = false;
|
||||
public static final DEFAULT_STAGE:String = "mainStage";
|
||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
* Song metadata as parsed from the JSON file.
|
||||
* This is the data for the `default` variation specifically,
|
||||
* and is needed for the IRegistryEntry interface.
|
||||
* Will only be null if the song data could not be loaded.
|
||||
*/
|
||||
public final _data:Null<SongMetadata>;
|
||||
|
||||
final _metadata:Array<SongMetadata>;
|
||||
|
||||
|
@ -39,33 +58,56 @@ class Song implements IPlayStateScriptedClass
|
|||
|
||||
var difficultyIds:Array<String>;
|
||||
|
||||
public var songName(get, never):String;
|
||||
|
||||
function get_songName():String
|
||||
{
|
||||
if (_data != null) return _data?.songName ?? DEFAULT_SONGNAME;
|
||||
if (_metadata.length > 0) return _metadata[0]?.songName ?? DEFAULT_SONGNAME;
|
||||
return DEFAULT_SONGNAME;
|
||||
}
|
||||
|
||||
public var songArtist(get, never):String;
|
||||
|
||||
function get_songArtist():String
|
||||
{
|
||||
if (_data != null) return _data?.artist ?? DEFAULT_ARTIST;
|
||||
if (_metadata.length > 0) return _metadata[0]?.artist ?? DEFAULT_ARTIST;
|
||||
return DEFAULT_ARTIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The ID of the song to load.
|
||||
* @param ignoreErrors If false, an exception will be thrown if the song data could not be loaded.
|
||||
*/
|
||||
public function new(id:String, ignoreErrors:Bool = false)
|
||||
public function new(id:String)
|
||||
{
|
||||
this.songId = id;
|
||||
this.id = id;
|
||||
|
||||
variations = [];
|
||||
difficultyIds = [];
|
||||
difficulties = new Map<String, SongDifficulty>();
|
||||
|
||||
try
|
||||
_data = _fetchData(id);
|
||||
|
||||
_metadata = _data == null ? [] : [_data];
|
||||
|
||||
for (meta in fetchVariationMetadata(id))
|
||||
_metadata.push(meta);
|
||||
|
||||
if (_metadata.length == 0)
|
||||
{
|
||||
_metadata = SongDataParser.loadSongMetadata(songId);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
_metadata = [];
|
||||
trace('[WARN] Could not find song data for songId: $id');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_metadata.length == 0 && !ignoreErrors)
|
||||
{
|
||||
throw 'Could not find song data for songId: $songId';
|
||||
}
|
||||
else
|
||||
variations.clear();
|
||||
variations.push('default');
|
||||
if (_data != null && _data.playData != null)
|
||||
{
|
||||
for (vari in _data.playData.songVariations)
|
||||
variations.push(vari);
|
||||
|
||||
populateFromMetadata();
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +116,7 @@ class Song implements IPlayStateScriptedClass
|
|||
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
|
||||
validScore:Bool = false):Song
|
||||
{
|
||||
var result:Song = new Song(songId, true);
|
||||
var result:Song = new Song(songId);
|
||||
|
||||
result._metadata.clear();
|
||||
for (meta in metadata)
|
||||
|
@ -112,6 +154,8 @@ class Song implements IPlayStateScriptedClass
|
|||
// Variations may have different artist, time format, generatedBy, etc.
|
||||
for (metadata in _metadata)
|
||||
{
|
||||
if (metadata == null || metadata.playData == null) continue;
|
||||
|
||||
// There may be more difficulties in the chart file than in the metadata,
|
||||
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
|
||||
// but all the difficulties in the metadata must be in the chart file.
|
||||
|
@ -134,15 +178,16 @@ class Song implements IPlayStateScriptedClass
|
|||
difficulty.stage = metadata.playData.stage;
|
||||
// difficulty.noteSkin = metadata.playData.noteSkin;
|
||||
|
||||
difficulties.set(diffId, difficulty);
|
||||
|
||||
difficulty.chars = new Map<String, SongPlayableChar>();
|
||||
if (metadata.playData.playableChars == null) continue;
|
||||
for (charId in metadata.playData.playableChars.keys())
|
||||
{
|
||||
var char = metadata.playData.playableChars.get(charId);
|
||||
|
||||
var char:Null<SongPlayableChar> = metadata.playData.playableChars.get(charId);
|
||||
if (char == null) continue;
|
||||
difficulty.chars.set(charId, char);
|
||||
}
|
||||
|
||||
difficulties.set(diffId, difficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,11 +202,14 @@ class Song implements IPlayStateScriptedClass
|
|||
clearCharts();
|
||||
}
|
||||
|
||||
trace('Caching ${variations.length} chart files for song $songId');
|
||||
trace('Caching ${variations.length} chart files for song $id');
|
||||
for (variation in variations)
|
||||
{
|
||||
var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation);
|
||||
applyChartData(chartData, variation);
|
||||
var version:Null<thx.semver.Version> = SongRegistry.instance.fetchEntryChartVersion(id, variation);
|
||||
if (version == null) continue;
|
||||
var chart:Null<SongChartData> = SongRegistry.instance.parseEntryChartDataWithMigration(id, version, variation);
|
||||
if (chart == null) continue;
|
||||
applyChartData(chart, variation);
|
||||
}
|
||||
trace('Done caching charts.');
|
||||
}
|
||||
|
@ -181,8 +229,8 @@ class Song implements IPlayStateScriptedClass
|
|||
difficulties.set(diffId, difficulty);
|
||||
}
|
||||
// Add the chart data to the difficulty.
|
||||
difficulty.notes = chartData.notes.get(diffId);
|
||||
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
||||
difficulty.notes = chartNotes.get(diffId) ?? [];
|
||||
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId) ?? 1.0;
|
||||
|
||||
difficulty.events = chartData.events;
|
||||
}
|
||||
|
@ -193,7 +241,7 @@ class Song implements IPlayStateScriptedClass
|
|||
* @param diffId The difficulty ID, such as `easy` or `hard`.
|
||||
* @return The difficulty data.
|
||||
*/
|
||||
public inline function getDifficulty(diffId:String = null):SongDifficulty
|
||||
public inline function getDifficulty(?diffId:String):Null<SongDifficulty>
|
||||
{
|
||||
if (diffId == null) diffId = difficulties.keys().array()[0];
|
||||
|
||||
|
@ -223,9 +271,11 @@ class Song implements IPlayStateScriptedClass
|
|||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Song($songId)';
|
||||
return 'Song($id)';
|
||||
}
|
||||
|
||||
public function destroy():Void {}
|
||||
|
||||
public function onPause(event:PauseScriptEvent):Void {};
|
||||
|
||||
public function onResume(event:ScriptEvent):Void {};
|
||||
|
@ -265,6 +315,27 @@ class Song implements IPlayStateScriptedClass
|
|||
public function onDestroy(event:ScriptEvent):Void {};
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent):Void {};
|
||||
|
||||
static function _fetchData(id:String):Null<SongMetadata>
|
||||
{
|
||||
trace('Fetching song metadata for $id');
|
||||
var version:Null<thx.semver.Version> = SongRegistry.instance.fetchEntryMetadataVersion(id);
|
||||
if (version == null) return null;
|
||||
return SongRegistry.instance.parseEntryMetadataWithMigration(id, '', version);
|
||||
}
|
||||
|
||||
function fetchVariationMetadata(id:String):Array<SongMetadata>
|
||||
{
|
||||
var result:Array<SongMetadata> = [];
|
||||
for (vari in variations)
|
||||
{
|
||||
var version:Null<thx.semver.Version> = SongRegistry.instance.fetchEntryMetadataVersion(id, vari);
|
||||
if (version == null) continue;
|
||||
var meta:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataWithMigration(id, vari, version);
|
||||
if (meta != null) result.push(meta);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class SongDifficulty
|
||||
|
@ -299,7 +370,7 @@ class SongDifficulty
|
|||
public var timeFormat:SongTimeFormat = SongValidator.DEFAULT_TIMEFORMAT;
|
||||
public var divisions:Null<Int> = SongValidator.DEFAULT_DIVISIONS;
|
||||
public var looped:Bool = SongValidator.DEFAULT_LOOPED;
|
||||
public var generatedBy:String = SongValidator.DEFAULT_GENERATEDBY;
|
||||
public var generatedBy:String = SongRegistry.DEFAULT_GENERATEDBY;
|
||||
|
||||
public var timeChanges:Array<SongTimeChange> = [];
|
||||
|
||||
|
@ -351,18 +422,18 @@ class SongDifficulty
|
|||
var currentPlayer:Null<SongPlayableChar> = getPlayableChar(currentPlayerId);
|
||||
if (currentPlayer != null)
|
||||
{
|
||||
FlxG.sound.cache(Paths.inst(this.song.songId, currentPlayer.inst));
|
||||
FlxG.sound.cache(Paths.inst(this.song.id, currentPlayer.inst));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.sound.cache(Paths.inst(this.song.songId));
|
||||
FlxG.sound.cache(Paths.inst(this.song.id));
|
||||
}
|
||||
}
|
||||
|
||||
public inline function playInst(volume:Float = 1.0, looped:Bool = false):Void
|
||||
{
|
||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||
FlxG.sound.playMusic(Paths.inst(this.song.songId, suffix), volume, looped);
|
||||
FlxG.sound.playMusic(Paths.inst(this.song.id, suffix), volume, looped);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -388,7 +459,7 @@ class SongDifficulty
|
|||
var playableCharData:SongPlayableChar = getPlayableChar(id);
|
||||
if (playableCharData == null)
|
||||
{
|
||||
trace('Could not find playable char $id for song ${this.song.songId}');
|
||||
trace('Could not find playable char $id for song ${this.song.id}');
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -398,24 +469,24 @@ class SongDifficulty
|
|||
// For example, if `Voices-bf-car.ogg` does not exist, check for `Voices-bf.ogg`.
|
||||
|
||||
var playerId:String = id;
|
||||
var voicePlayer:String = Paths.voices(this.song.songId, '-$id$suffix');
|
||||
var voicePlayer:String = Paths.voices(this.song.id, '-$id$suffix');
|
||||
while (voicePlayer != null && !Assets.exists(voicePlayer))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
// For example, bf-car becomes bf.
|
||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.songId, '-${playerId}$suffix');
|
||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
}
|
||||
|
||||
var opponentId:String = playableCharData.opponent;
|
||||
var voiceOpponent:String = Paths.voices(this.song.songId, '-${opponentId}$suffix');
|
||||
var voiceOpponent:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
while (voiceOpponent != null && !Assets.exists(voiceOpponent))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.songId, '-${opponentId}$suffix');
|
||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
}
|
||||
|
||||
var result:Array<String> = [];
|
||||
|
@ -424,7 +495,7 @@ class SongDifficulty
|
|||
if (voicePlayer == null && voiceOpponent == null)
|
||||
{
|
||||
// Try to use `Voices.ogg` if no other voices are found.
|
||||
if (Assets.exists(Paths.voices(this.song.songId, ''))) result.push(Paths.voices(this.song.songId, '$suffix'));
|
||||
if (Assets.exists(Paths.voices(this.song.id, ''))) result.push(Paths.voices(this.song.id, '$suffix'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -442,7 +513,7 @@ class SongDifficulty
|
|||
|
||||
if (voiceList.length == 0)
|
||||
{
|
||||
trace('Could not find any voices for song ${this.song.songId}');
|
||||
trace('Could not find any voices for song ${this.song.id}');
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,232 +0,0 @@
|
|||
package funkin.play.song;
|
||||
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.util.ClipboardUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
|
||||
using Lambda;
|
||||
|
||||
class SongDataUtils
|
||||
{
|
||||
/**
|
||||
* Given an array of SongNoteData objects, return a new array of SongNoteData objects
|
||||
* whose timestamps are shifted by the given amount.
|
||||
* Does not mutate the original array.
|
||||
*
|
||||
* @param notes The notes to modify.
|
||||
* @param offset The time difference to apply in milliseconds.
|
||||
*/
|
||||
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Int):Array<SongNoteData>
|
||||
{
|
||||
return notes.map(function(note:SongNoteData):SongNoteData {
|
||||
return new SongNoteData(note.time + offset, note.data, note.length, note.kind);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of SongEventData objects, return a new array of SongEventData objects
|
||||
* whose timestamps are shifted by the given amount.
|
||||
* Does not mutate the original array.
|
||||
*
|
||||
* @param events The events to modify.
|
||||
* @param offset The time difference to apply in milliseconds.
|
||||
*/
|
||||
public static function offsetSongEventData(events:Array<SongEventData>, offset:Int):Array<SongEventData>
|
||||
{
|
||||
return events.map(function(event:SongEventData):SongEventData {
|
||||
return new SongEventData(event.time + offset, event.event, event.value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new array without a certain subset of notes from an array of SongNoteData objects.
|
||||
* Does not mutate the original array.
|
||||
*
|
||||
* @param notes The array of notes to be subtracted from.
|
||||
* @param subtrahend The notes to remove from the `notes` array. Yes, subtrahend is a real word.
|
||||
*/
|
||||
public static function subtractNotes(notes:Array<SongNoteData>, subtrahend:Array<SongNoteData>)
|
||||
{
|
||||
if (notes.length == 0 || subtrahend.length == 0) return notes;
|
||||
|
||||
var result = notes.filter(function(note:SongNoteData):Bool {
|
||||
for (x in subtrahend)
|
||||
// SongNoteData's == operation has been overridden so that this will work.
|
||||
if (x == note) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new array without a certain subset of events from an array of SongEventData objects.
|
||||
* Does not mutate the original array.
|
||||
*
|
||||
* @param events The array of events to be subtracted from.
|
||||
* @param subtrahend The events to remove from the `events` array. Yes, subtrahend is a real word.
|
||||
*/
|
||||
public static function subtractEvents(events:Array<SongEventData>, subtrahend:Array<SongEventData>)
|
||||
{
|
||||
if (events.length == 0 || subtrahend.length == 0) return events;
|
||||
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
// SongEventData's == operation has been overridden so that this will work.
|
||||
return !subtrahend.has(event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of notes whose note data is flipped (player becomes opponent and vice versa)
|
||||
* Does not mutate the original array.
|
||||
*/
|
||||
public static function flipNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4):Array<SongNoteData>
|
||||
{
|
||||
return notes.map(function(note:SongNoteData):SongNoteData {
|
||||
var newData = note.data;
|
||||
|
||||
if (newData < strumlineSize) newData += strumlineSize;
|
||||
else
|
||||
newData -= strumlineSize;
|
||||
|
||||
return new SongNoteData(note.time, newData, note.length, note.kind);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an array of notes to be used as the clipboard data.
|
||||
*
|
||||
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
||||
*/
|
||||
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int = null):Array<SongNoteData>
|
||||
{
|
||||
if (notes.length == 0) return notes;
|
||||
if (timeOffset == null) timeOffset = -Std.int(notes[0].time);
|
||||
return offsetSongNoteData(sortNotes(notes), timeOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an array of events to be used as the clipboard data.
|
||||
*
|
||||
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
||||
*/
|
||||
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int = null):Array<SongEventData>
|
||||
{
|
||||
if (events.length == 0) return events;
|
||||
if (timeOffset == null) timeOffset = -Std.int(events[0].time);
|
||||
return offsetSongEventData(sortEvents(events), timeOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array of notes by strum time.
|
||||
*/
|
||||
public static function sortNotes(notes:Array<SongNoteData>, desc:Bool = false):Array<SongNoteData>
|
||||
{
|
||||
// TODO: Modifies the array in place. Is this okay?
|
||||
notes.sort(function(a:SongNoteData, b:SongNoteData):Int {
|
||||
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
|
||||
});
|
||||
return notes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array of events by strum time.
|
||||
*/
|
||||
public static function sortEvents(events:Array<SongEventData>, desc:Bool = false):Array<SongEventData>
|
||||
{
|
||||
// TODO: Modifies the array in place. Is this okay?
|
||||
events.sort(function(a:SongEventData, b:SongEventData):Int {
|
||||
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize note and event data and write it to the clipboard.
|
||||
*/
|
||||
public static function writeItemsToClipboard(data:SongClipboardItems):Void
|
||||
{
|
||||
var dataString = SerializerUtil.toJSON(data);
|
||||
|
||||
ClipboardUtil.setClipboard(dataString);
|
||||
|
||||
trace('Wrote ' + data.notes.length + ' notes and ' + data.events.length + ' events to clipboard.');
|
||||
|
||||
trace(dataString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an array of note data from the clipboard and deserialize it.
|
||||
*/
|
||||
public static function readItemsFromClipboard():SongClipboardItems
|
||||
{
|
||||
var notesString = ClipboardUtil.getClipboard();
|
||||
|
||||
trace('Read ${notesString.length} characters from clipboard.');
|
||||
|
||||
var data:SongClipboardItems = notesString.parseJSON();
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
trace('Failed to parse notes from clipboard.');
|
||||
return {
|
||||
notes: [],
|
||||
events: []
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Parsed ' + data.notes.length + ' notes and ' + data.events.length + ' from clipboard.');
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of notes to only include notes that are within the given time range.
|
||||
*/
|
||||
public static function getNotesInTimeRange(notes:Array<SongNoteData>, start:Float, end:Float):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return note.time >= start && note.time <= end;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of events to only include events that are within the given time range.
|
||||
*/
|
||||
public static function getEventsInTimeRange(events:Array<SongEventData>, start:Float, end:Float):Array<SongEventData>
|
||||
{
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
return event.time >= start && event.time <= end;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of notes to only include notes whose data is within the given range.
|
||||
*/
|
||||
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return note.data >= start && note.data <= end;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of notes to only include notes whose data is one of the given values.
|
||||
*/
|
||||
public static function getNotesWithData(notes:Array<SongNoteData>, data:Array<Int>):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return data.indexOf(note.data) != -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
typedef SongClipboardItems =
|
||||
{
|
||||
notes:Array<SongNoteData>,
|
||||
events:Array<SongEventData>
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package funkin.play.song;
|
||||
|
||||
import funkin.play.song.formats.FNFLegacy;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongPlayableChar;
|
||||
import funkin.util.VersionUtil;
|
||||
|
||||
class SongMigrator
|
||||
|
@ -176,7 +176,7 @@ class SongMigrator
|
|||
songMetadata.playData.songVariations = [];
|
||||
|
||||
// Set the song's song variations.
|
||||
songMetadata.playData.playableChars = {};
|
||||
songMetadata.playData.playableChars = [];
|
||||
try
|
||||
{
|
||||
Reflect.setField(songMetadata.playData.playableChars, songData.song.player1, new SongPlayableChar('', songData.song.player2));
|
||||
|
@ -203,7 +203,7 @@ class SongMigrator
|
|||
|
||||
var songData:FNFLegacy = cast jsonData;
|
||||
|
||||
var songChartData:SongChartData = new SongChartData(1.0, [], []);
|
||||
var songChartData:SongChartData = new SongChartData(["normal" => 1.0], [], ["normal" => []]);
|
||||
|
||||
var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
|
||||
if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.play.song;
|
||||
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.util.SerializerUtil;
|
||||
import lime.utils.Bytes;
|
||||
import openfl.events.Event;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package funkin.play.song;
|
||||
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.play.song.SongData.SongPlayData;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.play.song.SongData.SongTimeFormat;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongPlayData;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongData.SongTimeFormat;
|
||||
|
||||
/**
|
||||
* For SongMetadata and SongChartData objects,
|
||||
|
@ -20,13 +21,6 @@ class SongValidator
|
|||
public static final DEFAULT_STAGE:String = "mainStage";
|
||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||
|
||||
public static var DEFAULT_GENERATEDBY(get, null):String;
|
||||
|
||||
static function get_DEFAULT_GENERATEDBY():String
|
||||
{
|
||||
return '${Constants.TITLE} - ${Constants.VERSION}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the fields of a SongMetadata object (excluding the version field).
|
||||
*
|
||||
|
@ -59,7 +53,7 @@ class SongValidator
|
|||
}
|
||||
if (input.generatedBy == null)
|
||||
{
|
||||
input.generatedBy = DEFAULT_GENERATEDBY;
|
||||
input.generatedBy = SongRegistry.DEFAULT_GENERATEDBY;
|
||||
}
|
||||
|
||||
input.timeChanges = validateTimeChanges(input.timeChanges, songId);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.play.song.SongDataUtils;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
using Lambda;
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package funkin.ui.debug.charting;
|
|||
import funkin.play.character.CharacterData;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.SerializerUtil;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.input.Cursor;
|
||||
|
@ -13,9 +13,9 @@ import funkin.play.character.CharacterData.CharacterDataParser;
|
|||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongMigrator;
|
||||
import funkin.play.song.SongValidator;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongPlayableChar;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Button;
|
||||
|
@ -106,18 +106,17 @@ class ChartEditorDialogHandler
|
|||
|
||||
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
||||
|
||||
var songList:Array<String> = SongDataParser.listSongIds();
|
||||
var songList:Array<String> = SongRegistry.instance.listEntryIds();
|
||||
songList.sort(SortUtil.alphabetically);
|
||||
|
||||
for (targetSongId in songList)
|
||||
{
|
||||
var songData:Song = SongDataParser.fetchSong(targetSongId);
|
||||
|
||||
var songData:Null<Song> = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
if (songData == null) continue;
|
||||
|
||||
var songName:Null<String> = songData.getDifficulty('normal') ?.songName;
|
||||
if (songName == null) songName = songData.getDifficulty() ?.songName;
|
||||
if (songName == null)
|
||||
var songName:Null<String> = songData.getDifficulty('normal')?.songName;
|
||||
if (songName == null) songName = songData.getDifficulty()?.songName;
|
||||
if (songName == null) // Still null?
|
||||
{
|
||||
trace('[WARN] Could not fetch song name for ${targetSongId}');
|
||||
}
|
||||
|
@ -470,9 +469,9 @@ class ChartEditorDialogHandler
|
|||
var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown);
|
||||
dialogNoteSkin.onChange = function(event:UIEvent) {
|
||||
if (event.data.id == null) return;
|
||||
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
||||
state.currentSongNoteSkin = event.data.id;
|
||||
};
|
||||
state.currentSongMetadata.playData.noteSkin = null;
|
||||
state.currentSongNoteSkin = 'funkin';
|
||||
|
||||
var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper);
|
||||
dialogBPM.onChange = function(event:UIEvent) {
|
||||
|
@ -481,7 +480,7 @@ class ChartEditorDialogHandler
|
|||
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||
if (timeChanges == null || timeChanges.length == 0)
|
||||
{
|
||||
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
||||
timeChanges = [new SongTimeChange(0, event.value)];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -502,7 +501,7 @@ class ChartEditorDialogHandler
|
|||
};
|
||||
|
||||
// Empty the character list.
|
||||
state.currentSongMetadata.playData.playableChars = {};
|
||||
state.currentSongMetadata.playData.playableChars = [];
|
||||
// Add at least one character group with no Remove button.
|
||||
dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null));
|
||||
|
||||
|
@ -516,7 +515,8 @@ class ChartEditorDialogHandler
|
|||
{
|
||||
var groupKey:String = key;
|
||||
|
||||
var getCharData:Void->SongPlayableChar = function() {
|
||||
var getCharData:Void->Null<SongPlayableChar> = function():Null<SongPlayableChar> {
|
||||
if (state.currentSongMetadata.playData == null) return null;
|
||||
if (groupKey == null) groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}';
|
||||
|
||||
var result = state.currentSongMetadata.playData.playableChars.get(groupKey);
|
||||
|
@ -528,42 +528,53 @@ class ChartEditorDialogHandler
|
|||
return result;
|
||||
}
|
||||
|
||||
var moveCharGroup:String->Void = function(target:String) {
|
||||
var charData = getCharData();
|
||||
var moveCharGroup:String->Void = function(target:String):Void {
|
||||
var charData:Null<SongPlayableChar> = getCharData();
|
||||
if (charData == null) return;
|
||||
|
||||
if (state.currentSongMetadata.playData.playableChars == null) return;
|
||||
state.currentSongMetadata.playData.playableChars.remove(groupKey);
|
||||
state.currentSongMetadata.playData.playableChars.set(target, charData);
|
||||
groupKey = target;
|
||||
}
|
||||
|
||||
var removeGroup:Void->Void = function() {
|
||||
var removeGroup:Void->Void = function():Void {
|
||||
if (state?.currentSongMetadata?.playData?.playableChars == null) return;
|
||||
state.currentSongMetadata.playData.playableChars.remove(groupKey);
|
||||
removeFunc();
|
||||
}
|
||||
|
||||
var charData:SongPlayableChar = getCharData();
|
||||
var charData:Null<SongPlayableChar> = getCharData();
|
||||
|
||||
var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT);
|
||||
|
||||
var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown);
|
||||
charGroupPlayer.onChange = function(event:UIEvent) {
|
||||
var charGroupPlayer:Null<DropDown> = charGroup.findComponent('charGroupPlayer', DropDown);
|
||||
if (charGroupPlayer == null) throw 'Could not locate charGroupPlayer DropDown in Song Metadata dialog';
|
||||
charGroupPlayer.onChange = function(event:UIEvent):Void {
|
||||
if (charData != null) return;
|
||||
charGroup.text = event.data.text;
|
||||
moveCharGroup(event.data.id);
|
||||
};
|
||||
|
||||
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
|
||||
charGroupOpponent.onChange = function(event:UIEvent) {
|
||||
var charGroupOpponent:Null<DropDown> = charGroup.findComponent('charGroupOpponent', DropDown);
|
||||
if (charGroupOpponent == null) throw 'Could not locate charGroupOpponent DropDown in Song Metadata dialog';
|
||||
charGroupOpponent.onChange = function(event:UIEvent):Void {
|
||||
if (charData == null) return;
|
||||
charData.opponent = event.data.id;
|
||||
};
|
||||
charGroupOpponent.value = getCharData().opponent;
|
||||
charGroupOpponent.value = charData.opponent;
|
||||
|
||||
var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown);
|
||||
charGroupGirlfriend.onChange = function(event:UIEvent) {
|
||||
var charGroupGirlfriend:Null<DropDown> = charGroup.findComponent('charGroupGirlfriend', DropDown);
|
||||
if (charGroupGirlfriend == null) throw 'Could not locate charGroupGirlfriend DropDown in Song Metadata dialog';
|
||||
charGroupGirlfriend.onChange = function(event:UIEvent):Void {
|
||||
if (charData == null) return;
|
||||
charData.girlfriend = event.data.id;
|
||||
};
|
||||
charGroupGirlfriend.value = getCharData().girlfriend;
|
||||
charGroupGirlfriend.value = charData.girlfriend;
|
||||
|
||||
var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button);
|
||||
charGroupRemove.onClick = function(event:UIEvent) {
|
||||
var charGroupRemove:Null<Button> = charGroup.findComponent('charGroupRemove', Button);
|
||||
if (charGroupRemove == null) throw 'Could not locate charGroupRemove Button in Song Metadata dialog';
|
||||
charGroupRemove.onClick = function(event:UIEvent):Void {
|
||||
removeGroup();
|
||||
};
|
||||
|
||||
|
@ -584,7 +595,8 @@ class ChartEditorDialogHandler
|
|||
|
||||
for (charKey in state.currentSongMetadata.playData.playableChars.keys())
|
||||
{
|
||||
var charData:SongPlayableChar = state.currentSongMetadata.playData.playableChars.get(charKey);
|
||||
var charData:Null<SongPlayableChar> = state.currentSongMetadata.playData.playableChars.get(charKey);
|
||||
if (charData == null) continue;
|
||||
charIdsForVocals.push(charKey);
|
||||
if (charData.opponent != null) charIdsForVocals.push(charData.opponent);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import funkin.play.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
|
@ -10,7 +10,7 @@ import flixel.FlxSprite;
|
|||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import flixel.graphics.frames.FlxTileFrames;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* A event sprite that can be used to display a song event in a chart.
|
||||
|
|
|
@ -8,7 +8,7 @@ import flixel.graphics.frames.FlxFramesCollection;
|
|||
import flixel.graphics.frames.FlxTileFrames;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.notes.SustainTrail;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
|
||||
/**
|
||||
* A hold note sprite that can be used to display a note in a chart.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
|
|
|
@ -5,7 +5,7 @@ import flixel.FlxSprite;
|
|||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import flixel.graphics.frames.FlxTileFrames;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
|
||||
/**
|
||||
* A note sprite that can be used to display a note in a chart.
|
||||
|
|
|
@ -36,13 +36,13 @@ import funkin.play.notes.NoteSprite;
|
|||
import funkin.play.notes.Strumline;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.play.song.SongDataUtils;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongPlayableChar;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.ui.debug.charting.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme;
|
||||
|
@ -844,7 +844,7 @@ class ChartEditorState extends HaxeUIState
|
|||
var result:Null<SongChartData> = songChartData.get(selectedVariation);
|
||||
if (result == null)
|
||||
{
|
||||
result = new SongChartData(1.0, [], []);
|
||||
result = new SongChartData(["normal" => 1.0], [], ["normal" => []]);
|
||||
songChartData.set(selectedVariation, result);
|
||||
}
|
||||
return result;
|
||||
|
@ -2555,8 +2555,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
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,
|
||||
selectedNoteKind);
|
||||
var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, selectedNoteKind);
|
||||
|
||||
if (cursorColumn != noteData.data || selectedNoteKind != noteData.kind)
|
||||
{
|
||||
|
@ -3947,7 +3946,7 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
public function loadSongAsTemplate(songId:String):Void
|
||||
{
|
||||
var song:Null<Song> = SongDataParser.fetchSong(songId);
|
||||
var song:Null<Song> = SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
if (song == null) return;
|
||||
|
||||
|
@ -3965,7 +3964,7 @@ class ChartEditorState extends HaxeUIState
|
|||
var metadataClone = Reflect.copy(metadata);
|
||||
if (metadataClone != null) songMetadata.set(variation, metadataClone);
|
||||
|
||||
songChartData.set(variation, SongDataParser.parseSongChartData(songId, metadata.variation));
|
||||
songChartData.set(variation, SongRegistry.instance.parseEntryChartData(songId, metadata.variation));
|
||||
}
|
||||
|
||||
loadSong(songMetadata, songChartData);
|
||||
|
|
|
@ -4,8 +4,8 @@ import haxe.ui.containers.TreeView;
|
|||
import haxe.ui.containers.TreeViewNode;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.event.SongEventData;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.data.event.SongEventData;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.play.song.SongSerializer;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import haxe.ui.components.Button;
|
||||
|
@ -541,9 +541,9 @@ class ChartEditorToolboxHandler
|
|||
var inputNoteSkin:DropDown = toolbox.findComponent('inputNoteSkin', DropDown);
|
||||
inputNoteSkin.onChange = function(event:UIEvent) {
|
||||
if ((event?.data?.id ?? null) == null) return;
|
||||
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
||||
state.currentSongNoteSkin = event.data.id;
|
||||
};
|
||||
inputNoteSkin.value = state.currentSongMetadata.playData.noteSkin;
|
||||
inputNoteSkin.value = state.currentSongNoteSkin;
|
||||
|
||||
var inputBPM:NumberStepper = toolbox.findComponent('inputBPM', NumberStepper);
|
||||
inputBPM.onChange = function(event:UIEvent) {
|
||||
|
@ -552,7 +552,7 @@ class ChartEditorToolboxHandler
|
|||
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||
if (timeChanges == null || timeChanges.length == 0)
|
||||
{
|
||||
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
||||
timeChanges = [new SongTimeChange(0, event.value)];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ import flixel.FlxSprite;
|
|||
import flixel.util.FlxColor;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.level.LevelData;
|
||||
|
||||
|
@ -70,17 +71,20 @@ class Level implements IRegistryEntry<LevelData>
|
|||
public function getSongDisplayNames(difficulty:String):Array<String>
|
||||
{
|
||||
var songList:Array<String> = getSongs() ?? [];
|
||||
var songNameList:Array<String> = songList.map(function(songId) {
|
||||
var song:Song = funkin.play.song.SongData.SongDataParser.fetchSong(songId);
|
||||
if (song == null) return 'Unknown';
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(difficulty);
|
||||
if (songDifficulty == null) songDifficulty = song.getDifficulty();
|
||||
var songName:String = songDifficulty?.songName;
|
||||
return songName ?? 'Unknown';
|
||||
var songNameList:Array<String> = songList.map(function(songId:String) {
|
||||
return getSongDisplayName(songId, difficulty);
|
||||
});
|
||||
return songNameList;
|
||||
}
|
||||
|
||||
static function getSongDisplayName(songId:String, difficulty:String):String
|
||||
{
|
||||
var song:Null<Song> = SongRegistry.instance.fetchEntry(songId);
|
||||
if (song == null) return 'Unknown';
|
||||
|
||||
return song.songName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this level is unlocked. If not, it will be greyed out on the menu and have a lock icon.
|
||||
* TODO: Change this behavior in a later release.
|
||||
|
@ -120,7 +124,7 @@ class Level implements IRegistryEntry<LevelData>
|
|||
var songList = getSongs();
|
||||
|
||||
var firstSongId:String = songList[0];
|
||||
var firstSong:Song = funkin.play.song.SongData.SongDataParser.fetchSong(firstSongId);
|
||||
var firstSong:Song = SongRegistry.instance.fetchEntry(firstSongId);
|
||||
|
||||
if (firstSong != null)
|
||||
{
|
||||
|
@ -134,7 +138,7 @@ class Level implements IRegistryEntry<LevelData>
|
|||
for (songIndex in 1...songList.length)
|
||||
{
|
||||
var songId:String = songList[songIndex];
|
||||
var song:Song = funkin.play.song.SongData.SongDataParser.fetchSong(songId);
|
||||
var song:Song = SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
if (song == null) continue;
|
||||
|
||||
|
@ -179,7 +183,7 @@ class Level implements IRegistryEntry<LevelData>
|
|||
return 'Level($id)';
|
||||
}
|
||||
|
||||
public function _fetchData(id:String):Null<LevelData>
|
||||
static function _fetchData(id:String):Null<LevelData>
|
||||
{
|
||||
return LevelRegistry.instance.parseEntryDataWithMigration(id, LevelRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ import funkin.modding.events.ScriptEventDispatcher;
|
|||
import funkin.play.PlayState;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import funkin.data.song.SongRegistry;
|
||||
|
||||
class StoryMenuState extends MusicBeatState
|
||||
{
|
||||
|
@ -201,8 +201,11 @@ class StoryMenuState extends MusicBeatState
|
|||
{
|
||||
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
|
||||
{
|
||||
var freakyMenuMetadata:SongMetadata = SongDataParser.parseMusicMetadata('freakyMenu');
|
||||
Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges);
|
||||
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu');
|
||||
if (freakyMenuMetadata != null)
|
||||
{
|
||||
Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges);
|
||||
}
|
||||
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
|
@ -509,7 +512,7 @@ class StoryMenuState extends MusicBeatState
|
|||
|
||||
var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift();
|
||||
|
||||
var targetSong:Song = SongDataParser.fetchSong(targetSongId);
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
|
||||
PlayStatePlaylist.campaignId = currentLevel.id;
|
||||
PlayStatePlaylist.campaignTitle = currentLevel.getTitle();
|
||||
|
|
|
@ -12,8 +12,8 @@ import flixel.util.FlxTimer;
|
|||
import funkin.audiovis.SpectogramSprite;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
import funkin.shaderslmfao.LeftMaskShader;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import funkin.shaderslmfao.TitleOutline;
|
||||
import funkin.ui.AtlasText;
|
||||
import openfl.Assets;
|
||||
|
@ -216,9 +216,11 @@ class TitleState extends MusicBeatState
|
|||
{
|
||||
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
|
||||
{
|
||||
var freakyMenuMetadata:SongMetadata = SongDataParser.parseMusicMetadata('freakyMenu');
|
||||
Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges);
|
||||
|
||||
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu');
|
||||
if (freakyMenuMetadata != null)
|
||||
{
|
||||
Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges);
|
||||
}
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
#if !macro
|
||||
import flixel.FlxBasic;
|
||||
import flixel.util.FlxSort;
|
||||
|
@ -41,6 +42,16 @@ class SortUtil
|
|||
return FlxSort.byValues(order, a.noteData.time, b.noteData.time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two FlxFrames, sort their names alphabetically.
|
||||
*
|
||||
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
|
||||
*/
|
||||
public static inline function byFrameName(a:FlxFrame, b:FlxFrame)
|
||||
{
|
||||
return alphabetically(a.name, b.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort predicate for sorting strings alphabetically.
|
||||
* @param a The first string to compare.
|
||||
|
|
|
@ -13,4 +13,22 @@ class IteratorTools
|
|||
{
|
||||
return [for (i in iterator) i];
|
||||
}
|
||||
|
||||
public static function count<T>(iterator:Iterator<T>, ?predicate:(item:T) -> Bool):Int
|
||||
{
|
||||
var n = 0;
|
||||
|
||||
if (predicate == null)
|
||||
{
|
||||
for (_ in iterator)
|
||||
n++;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (x in iterator)
|
||||
if (predicate(x)) n++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue