mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-08-19 06:24:56 +00:00
Song scripts can now be (optionally) enabled in the Chart Editor playtest
This commit is contained in:
parent
ee35bf3044
commit
66085ff867
|
@ -1,6 +1,5 @@
|
||||||
package funkin.data;
|
package funkin.data;
|
||||||
|
|
||||||
import openfl.Assets;
|
|
||||||
import funkin.util.assets.DataAssets;
|
import funkin.util.assets.DataAssets;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
import haxe.Constraints.Constructible;
|
import haxe.Constraints.Constructible;
|
||||||
|
@ -19,12 +18,23 @@ typedef EntryConstructorFunction = String->Void;
|
||||||
@:generic
|
@:generic
|
||||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The ID of the registry. Used when logging.
|
||||||
|
*/
|
||||||
public final registryId:String;
|
public final registryId:String;
|
||||||
|
|
||||||
final dataFilePath:String;
|
final dataFilePath:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of entry IDs to entries.
|
||||||
|
*/
|
||||||
final entries:Map<String, T>;
|
final entries:Map<String, T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of entry IDs to scripted class names.
|
||||||
|
*/
|
||||||
|
final scriptedEntryIds:Map<String, String>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version rule to use when loading entries.
|
* The version rule to use when loading entries.
|
||||||
* If the entry's version does not match this rule, migration is needed.
|
* If the entry's version does not match this rule, migration is needed.
|
||||||
|
@ -37,17 +47,18 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
* @param registryId A readable ID for this registry, used when logging.
|
* @param registryId A readable ID for this registry, used when logging.
|
||||||
* @param dataFilePath The path (relative to `assets/data`) to search for JSON files.
|
* @param dataFilePath The path (relative to `assets/data`) to search for JSON files.
|
||||||
*/
|
*/
|
||||||
public function new(registryId:String, dataFilePath:String, versionRule:thx.semver.VersionRule = null)
|
public function new(registryId:String, dataFilePath:String, ?versionRule:thx.semver.VersionRule)
|
||||||
{
|
{
|
||||||
this.registryId = registryId;
|
this.registryId = registryId;
|
||||||
this.dataFilePath = dataFilePath;
|
this.dataFilePath = dataFilePath;
|
||||||
this.versionRule = versionRule == null ? "1.0.x" : versionRule;
|
this.versionRule = versionRule == null ? '1.0.x' : versionRule;
|
||||||
|
|
||||||
this.entries = new Map<String, T>();
|
this.entries = new Map<String, T>();
|
||||||
|
this.scriptedEntryIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Create a `loadEntriesAsync()` function.
|
* TODO: Create a `loadEntriesAsync(onProgress, onComplete)` function.
|
||||||
*/
|
*/
|
||||||
public function loadEntries():Void
|
public function loadEntries():Void
|
||||||
{
|
{
|
||||||
|
@ -66,7 +77,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
{
|
{
|
||||||
entry = createScriptedEntry(entryCls);
|
entry = createScriptedEntry(entryCls);
|
||||||
}
|
}
|
||||||
catch (e:Dynamic)
|
catch (e)
|
||||||
{
|
{
|
||||||
log('Failed to create scripted entry (${entryCls})');
|
log('Failed to create scripted entry (${entryCls})');
|
||||||
continue;
|
continue;
|
||||||
|
@ -76,6 +87,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
{
|
{
|
||||||
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
||||||
entries.set(entry.id, entry);
|
entries.set(entry.id, entry);
|
||||||
|
scriptedEntryIds.set(entry.id, entryCls);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -102,7 +114,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
entries.set(entry.id, entry);
|
entries.set(entry.id, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e:Dynamic)
|
catch (e)
|
||||||
{
|
{
|
||||||
// Print the error.
|
// Print the error.
|
||||||
trace(' Failed to load entry data: ${entryId}');
|
trace(' Failed to load entry data: ${entryId}');
|
||||||
|
@ -130,6 +142,36 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
return entries.size();
|
return entries.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the entry ID is known to have an attached script.
|
||||||
|
* @param id The ID of the entry.
|
||||||
|
* @return `true` if the entry has an attached script, `false` otherwise.
|
||||||
|
*/
|
||||||
|
public function isScriptedEntry(id:String):Bool
|
||||||
|
{
|
||||||
|
return scriptedEntryIds.exists(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the class name of the scripted entry with the given ID, if it exists.
|
||||||
|
* @param id The ID of the entry.
|
||||||
|
* @return The class name, or `null` if it does not exist.
|
||||||
|
*/
|
||||||
|
public function getScriptedEntryClassName(id:String):String
|
||||||
|
{
|
||||||
|
return scriptedEntryIds.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the registry has successfully parsed an entry with the given ID.
|
||||||
|
* @param id The ID of the entry.
|
||||||
|
* @return `true` if the entry exists, `false` otherwise.
|
||||||
|
*/
|
||||||
|
public function hasEntry(id:String):Bool
|
||||||
|
{
|
||||||
|
return entries.exists(id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch an entry by its ID.
|
* Fetch an entry by its ID.
|
||||||
* @param id The ID of the entry to fetch.
|
* @param id The ID of the entry to fetch.
|
||||||
|
@ -145,6 +187,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the data for an entry and parse its Semantic Version.
|
||||||
|
* @param id The ID of the entry.
|
||||||
|
* @return The entry's version, or `null` if it does not exist or is invalid.
|
||||||
|
*/
|
||||||
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
||||||
{
|
{
|
||||||
var entryStr:String = loadEntryFile(id).contents;
|
var entryStr:String = loadEntryFile(id).contents;
|
||||||
|
@ -185,6 +232,8 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||||
*
|
*
|
||||||
* NOTE: Must be implemented on the implementation class.
|
* NOTE: Must be implemented on the implementation class.
|
||||||
|
* @param id The ID of the entry.
|
||||||
|
* @return The created entry.
|
||||||
*/
|
*/
|
||||||
public abstract function parseEntryData(id:String):Null<J>;
|
public abstract function parseEntryData(id:String):Null<J>;
|
||||||
|
|
||||||
|
@ -194,6 +243,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
* NOTE: Must be implemented on the implementation class.
|
* NOTE: Must be implemented on the implementation class.
|
||||||
* @param contents The JSON as a string.
|
* @param contents The JSON as a string.
|
||||||
* @param fileName An optional file name for error reporting.
|
* @param fileName An optional file name for error reporting.
|
||||||
|
* @return The created entry.
|
||||||
*/
|
*/
|
||||||
public abstract function parseEntryDataRaw(contents:String, ?fileName:String):Null<J>;
|
public abstract function parseEntryDataRaw(contents:String, ?fileName:String):Null<J>;
|
||||||
|
|
||||||
|
@ -202,6 +252,9 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
* accounting for old versions of the data.
|
* accounting for old versions of the data.
|
||||||
*
|
*
|
||||||
* NOTE: Extend this function to handle migration.
|
* NOTE: Extend this function to handle migration.
|
||||||
|
* @param id The ID of the entry.
|
||||||
|
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
||||||
|
* @return The created entry.
|
||||||
*/
|
*/
|
||||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
||||||
{
|
{
|
||||||
|
@ -220,12 +273,17 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
throw '[${registryId}] Entry ${id} does not support migration to version ${versionRule}.';
|
throw '[${registryId}] Entry ${id} does not support migration to version ${versionRule}.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example:
|
/*
|
||||||
// if (VersionUtil.validateVersion(version, "0.1.x")) {
|
* An example of what you should override this with:
|
||||||
// return parseEntryData_v0_1_x(id);
|
*
|
||||||
// } else {
|
* ```haxe
|
||||||
// super.parseEntryDataWithMigration(id, version);
|
* if (VersionUtil.validateVersion(version, "0.1.x")) {
|
||||||
// }
|
* return parseEntryData_v0_1_x(id);
|
||||||
|
* } else {
|
||||||
|
* super.parseEntryDataWithMigration(id, version);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,10 +313,15 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||||
|
|
||||||
for (error in errors)
|
for (error in errors)
|
||||||
|
{
|
||||||
DataError.printError(error);
|
DataError.printError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pair of a file name and its contents.
|
||||||
|
*/
|
||||||
typedef JsonFile =
|
typedef JsonFile =
|
||||||
{
|
{
|
||||||
fileName:String,
|
fileName:String,
|
||||||
|
|
|
@ -68,6 +68,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
{
|
{
|
||||||
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
||||||
entries.set(entry.id, entry);
|
entries.set(entry.id, entry);
|
||||||
|
scriptedEntryIds.set(entry.id, entryCls);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -81,9 +81,10 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function loadTexture(key:String):FunkinSprite
|
public function loadTexture(key:String):FunkinSprite
|
||||||
{
|
{
|
||||||
if (!isTextureCached(key)) FlxG.log.warn('Texture not cached, may experience stuttering! $key');
|
var graphicKey:String = Paths.image(key);
|
||||||
|
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||||
|
|
||||||
loadGraphic(key);
|
loadGraphic(graphicKey);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +96,7 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function loadSparrow(key:String):FunkinSprite
|
public function loadSparrow(key:String):FunkinSprite
|
||||||
{
|
{
|
||||||
var graphicKey = Paths.image(key);
|
var graphicKey:String = Paths.image(key);
|
||||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||||
|
|
||||||
this.frames = Paths.getSparrowAtlas(key);
|
this.frames = Paths.getSparrowAtlas(key);
|
||||||
|
@ -110,7 +111,7 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function loadPacker(key:String):FunkinSprite
|
public function loadPacker(key:String):FunkinSprite
|
||||||
{
|
{
|
||||||
var graphicKey = Paths.image(key);
|
var graphicKey:String = Paths.image(key);
|
||||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||||
|
|
||||||
this.frames = Paths.getPackerAtlas(key);
|
this.frames = Paths.getPackerAtlas(key);
|
||||||
|
|
|
@ -215,7 +215,7 @@ class Countdown
|
||||||
|
|
||||||
if (spritePath == null) return;
|
if (spritePath == null) return;
|
||||||
|
|
||||||
var countdownSprite:FunkinSprite = FunkinSprite.create(Paths.image(spritePath));
|
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
|
||||||
countdownSprite.scrollFactor.set(0, 0);
|
countdownSprite.scrollFactor.set(0, 0);
|
||||||
|
|
||||||
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
||||||
|
|
|
@ -28,7 +28,7 @@ class GitarooPause extends MusicBeatState
|
||||||
{
|
{
|
||||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||||
|
|
||||||
var bg:FunkinSprite = FunkinSprite.create(Paths.image('pauseAlt/pauseBG'));
|
var bg:FunkinSprite = FunkinSprite.create('pauseAlt/pauseBG');
|
||||||
add(bg);
|
add(bg);
|
||||||
|
|
||||||
var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol');
|
var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol');
|
||||||
|
|
|
@ -1416,7 +1416,7 @@ class PlayState extends MusicBeatSubState
|
||||||
function initHealthBar():Void
|
function initHealthBar():Void
|
||||||
{
|
{
|
||||||
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
|
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
|
||||||
healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar'));
|
healthBarBG = FunkinSprite.create(0, healthBarYPos, 'healthBar');
|
||||||
healthBarBG.screenCenter(X);
|
healthBarBG.screenCenter(X);
|
||||||
healthBarBG.scrollFactor.set(0, 0);
|
healthBarBG.scrollFactor.set(0, 0);
|
||||||
healthBarBG.zIndex = 800;
|
healthBarBG.zIndex = 800;
|
||||||
|
@ -1453,7 +1453,7 @@ class PlayState extends MusicBeatSubState
|
||||||
function initMinimalMode():Void
|
function initMinimalMode():Void
|
||||||
{
|
{
|
||||||
// Create the green background.
|
// Create the green background.
|
||||||
var menuBG = FunkinSprite.create(Paths.image('menuDesat'));
|
var menuBG = FunkinSprite.create('menuDesat');
|
||||||
menuBG.color = 0xFF4CAF50;
|
menuBG.color = 0xFF4CAF50;
|
||||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||||
menuBG.updateHitbox();
|
menuBG.updateHitbox();
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
||||||
|
|
||||||
var rating:FunkinSprite = FunkinSprite.create(0, 0, Paths.image(ratingPath));
|
var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
|
||||||
rating.scrollFactor.set(0.2, 0.2);
|
rating.scrollFactor.set(0.2, 0.2);
|
||||||
|
|
||||||
rating.zIndex = 1000;
|
rating.zIndex = 1000;
|
||||||
|
@ -76,7 +76,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||||
pixelShitPart1 = 'weeb/pixelUI/';
|
pixelShitPart1 = 'weeb/pixelUI/';
|
||||||
pixelShitPart2 = '-pixel';
|
pixelShitPart2 = '-pixel';
|
||||||
}
|
}
|
||||||
var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2));
|
var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
|
||||||
comboSpr.y = FlxG.camera.height * 0.4 + 80;
|
comboSpr.y = FlxG.camera.height * 0.4 + 80;
|
||||||
comboSpr.x = FlxG.width * 0.50;
|
comboSpr.x = FlxG.width * 0.50;
|
||||||
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
||||||
|
@ -124,7 +124,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||||
var daLoop:Int = 1;
|
var daLoop:Int = 1;
|
||||||
for (i in seperatedScore)
|
for (i in seperatedScore)
|
||||||
{
|
{
|
||||||
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
|
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2);
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package funkin.play.song;
|
package funkin.play.song;
|
||||||
|
|
||||||
import flixel.sound.FlxSound;
|
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.data.IRegistryEntry;
|
import funkin.data.IRegistryEntry;
|
||||||
|
@ -13,9 +12,8 @@ import funkin.data.song.SongData.SongOffsets;
|
||||||
import funkin.data.song.SongData.SongTimeChange;
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
import funkin.data.song.SongData.SongTimeFormat;
|
import funkin.data.song.SongData.SongTimeFormat;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.IScriptedClass;
|
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
|
@ -31,14 +29,44 @@ import openfl.utils.Assets;
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
||||||
{
|
{
|
||||||
public static final DEFAULT_SONGNAME:String = "Unknown";
|
/**
|
||||||
public static final DEFAULT_ARTIST:String = "Unknown";
|
* The default value for the song's name
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_SONGNAME:String = 'Unknown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value for the song's artist
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_ARTIST:String = 'Unknown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value for the song's time format
|
||||||
|
*/
|
||||||
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value for the song's divisions
|
||||||
|
*/
|
||||||
public static final DEFAULT_DIVISIONS:Null<Int> = null;
|
public static final DEFAULT_DIVISIONS:Null<Int> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value for whether the song loops.
|
||||||
|
*/
|
||||||
public static final DEFAULT_LOOPED:Bool = false;
|
public static final DEFAULT_LOOPED:Bool = false;
|
||||||
public static final DEFAULT_STAGE:String = "mainStage";
|
|
||||||
|
/**
|
||||||
|
* The default value for the song's playable stage.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_STAGE:String = 'mainStage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value for the song's scroll speed.
|
||||||
|
*/
|
||||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal ID of the song.
|
||||||
|
*/
|
||||||
public final id:String;
|
public final id:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +81,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
final _metadata:Map<String, SongMetadata>;
|
final _metadata:Map<String, SongMetadata>;
|
||||||
final difficulties:Map<String, SongDifficulty>;
|
final difficulties:Map<String, SongDifficulty>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of variations a song has.
|
||||||
|
*/
|
||||||
public var variations(get, never):Array<String>;
|
public var variations(get, never):Array<String>;
|
||||||
|
|
||||||
function get_variations():Array<String>
|
function get_variations():Array<String>
|
||||||
|
@ -65,6 +96,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
*/
|
*/
|
||||||
public var validScore:Bool = true;
|
public var validScore:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The readable name of the song.
|
||||||
|
*/
|
||||||
public var songName(get, never):String;
|
public var songName(get, never):String;
|
||||||
|
|
||||||
function get_songName():String
|
function get_songName():String
|
||||||
|
@ -74,6 +108,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
return DEFAULT_SONGNAME;
|
return DEFAULT_SONGNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The artist of the song.
|
||||||
|
*/
|
||||||
public var songArtist(get, never):String;
|
public var songArtist(get, never):String;
|
||||||
|
|
||||||
function get_songArtist():String
|
function get_songArtist():String
|
||||||
|
@ -101,7 +138,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
{
|
{
|
||||||
for (vari in _data.playData.songVariations)
|
for (vari in _data.playData.songVariations)
|
||||||
{
|
{
|
||||||
var variMeta = fetchVariationMetadata(id, vari);
|
var variMeta:Null<SongMetadata> = fetchVariationMetadata(id, vari);
|
||||||
if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
|
if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,27 +152,62 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
populateDifficulties();
|
populateDifficulties();
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(funkin.play.song.Song)
|
/**
|
||||||
|
* Build a song from existing metadata rather than loading it from the `assets` folder.
|
||||||
|
* Used by the Chart Editor.
|
||||||
|
*
|
||||||
|
* @param songId The ID of the song.
|
||||||
|
* @param metadata The metadata of the song.
|
||||||
|
* @param variations The list of variations this song has.
|
||||||
|
* @param charts The chart data for each variation.
|
||||||
|
* @param includeScript Whether to initialize the scripted class tied to the song, if it exists.
|
||||||
|
* @param validScore Whether the song is elegible for highscores.
|
||||||
|
* @return The constructed song object.
|
||||||
|
*/
|
||||||
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
|
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
|
||||||
validScore:Bool = false):Song
|
includeScript:Bool = true, validScore:Bool = false):Song
|
||||||
{
|
{
|
||||||
var result:Song = new Song(songId);
|
@:privateAccess
|
||||||
|
var result:Null<Song>;
|
||||||
|
|
||||||
|
if (includeScript && SongRegistry.instance.isScriptedEntry(songId))
|
||||||
|
{
|
||||||
|
var songClassName:String = SongRegistry.instance.getScriptedEntryClassName(songId);
|
||||||
|
|
||||||
|
@:privateAccess
|
||||||
|
result = SongRegistry.instance.createScriptedEntry(songClassName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@:privateAccess
|
||||||
|
result = SongRegistry.instance.createEntry(songId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null) throw 'ERROR: Could not build Song instance ($songId), is the attached script bad?';
|
||||||
|
|
||||||
result._metadata.clear();
|
result._metadata.clear();
|
||||||
for (meta in metadata)
|
for (meta in metadata)
|
||||||
|
{
|
||||||
result._metadata.set(meta.variation, meta);
|
result._metadata.set(meta.variation, meta);
|
||||||
|
}
|
||||||
|
|
||||||
result.difficulties.clear();
|
result.difficulties.clear();
|
||||||
result.populateDifficulties();
|
result.populateDifficulties();
|
||||||
|
|
||||||
for (variation => chartData in charts)
|
for (variation => chartData in charts)
|
||||||
|
{
|
||||||
result.applyChartData(chartData, variation);
|
result.applyChartData(chartData, variation);
|
||||||
|
}
|
||||||
|
|
||||||
result.validScore = validScore;
|
result.validScore = validScore;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of the raw metadata for the song.
|
||||||
|
* @return The metadata JSON objects for the song's variations.
|
||||||
|
*/
|
||||||
public function getRawMetadata():Array<SongMetadata>
|
public function getRawMetadata():Array<SongMetadata>
|
||||||
{
|
{
|
||||||
return _metadata.values();
|
return _metadata.values();
|
||||||
|
@ -192,6 +264,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and cache the chart for all difficulties of this song.
|
* Parse and cache the chart for all difficulties of this song.
|
||||||
|
* @param force Whether to forcibly clear the list of charts first.
|
||||||
*/
|
*/
|
||||||
public function cacheCharts(force:Bool = false):Void
|
public function cacheCharts(force:Bool = false):Void
|
||||||
{
|
{
|
||||||
|
|
|
@ -212,7 +212,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Initalize static sprite.
|
// Initalize static sprite.
|
||||||
propSprite.loadTexture(Paths.image(dataProp.assetPath));
|
propSprite.loadTexture(dataProp.assetPath);
|
||||||
|
|
||||||
// Disables calls to update() for a performance boost.
|
// Disables calls to update() for a performance boost.
|
||||||
propSprite.active = false;
|
propSprite.active = false;
|
||||||
|
|
|
@ -602,6 +602,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
var enabledDebuggerPopup:Bool = true;
|
var enabledDebuggerPopup:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether song scripts should be enabled during playtesting.
|
||||||
|
* You should probably check the box if the song has custom mechanics.
|
||||||
|
*/
|
||||||
|
var playtestSongScripts:Bool = true;
|
||||||
|
|
||||||
// Visuals
|
// Visuals
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1396,7 +1402,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
function get_currentSongId():String
|
function get_currentSongId():String
|
||||||
{
|
{
|
||||||
return currentSongName.toLowerKebabCase().replace('.', '').replace(' ', '-');
|
return currentSongName.toLowerKebabCase().replace(' ', '-').sanitize();
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSongArtist(get, set):String;
|
var currentSongArtist(get, set):String;
|
||||||
|
@ -5320,7 +5326,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var targetSong:Song;
|
var targetSong:Song;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
|
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, playtestSongScripts, false);
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
|
@ -5348,7 +5354,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
Paths.setCurrentLevel('week6');
|
Paths.setCurrentLevel('week6');
|
||||||
case 'tankmanBattlefield':
|
case 'tankmanBattlefield':
|
||||||
Paths.setCurrentLevel('week7');
|
Paths.setCurrentLevel('week7');
|
||||||
case 'phillyStreets' | 'phillyBlazin':
|
case 'phillyStreets' | 'phillyBlazin' | 'phillyBlazin2':
|
||||||
Paths.setCurrentLevel('weekend1');
|
Paths.setCurrentLevel('weekend1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -318,6 +318,17 @@ class ChartEditorToolboxHandler
|
||||||
state.enabledDebuggerPopup = checkboxDebugger.selected;
|
state.enabledDebuggerPopup = checkboxDebugger.selected;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var checkboxSongScripts:Null<CheckBox> = toolbox.findComponent('playtestSongScriptsCheckbox', CheckBox);
|
||||||
|
|
||||||
|
if (checkboxSongScripts == null)
|
||||||
|
throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestSongScriptsCheckbox component.';
|
||||||
|
|
||||||
|
state.playtestSongScripts = checkboxSongScripts.selected;
|
||||||
|
|
||||||
|
checkboxSongScripts.onClick = _ -> {
|
||||||
|
state.playtestSongScripts = checkboxSongScripts.selected;
|
||||||
|
};
|
||||||
|
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,7 +227,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
trace(FlxG.camera.initialZoom);
|
trace(FlxG.camera.initialZoom);
|
||||||
trace(FlxCamera.defaultZoom);
|
trace(FlxCamera.defaultZoom);
|
||||||
|
|
||||||
var pinkBack:FunkinSprite = FunkinSprite.create(Paths.image('freeplay/pinkBack'));
|
var pinkBack:FunkinSprite = FunkinSprite.create('freeplay/pinkBack');
|
||||||
pinkBack.color = 0xFFffd4e9; // sets it to pink!
|
pinkBack.color = 0xFFffd4e9; // sets it to pink!
|
||||||
pinkBack.x -= pinkBack.width;
|
pinkBack.x -= pinkBack.width;
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ class LoadingState extends MusicBeatState
|
||||||
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
|
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
|
||||||
add(bg);
|
add(bg);
|
||||||
|
|
||||||
funkay = FunkinSprite.create(Paths.image('funkay'));
|
funkay = FunkinSprite.create('funkay');
|
||||||
funkay.setGraphicSize(0, FlxG.height);
|
funkay.setGraphicSize(0, FlxG.height);
|
||||||
funkay.updateHitbox();
|
funkay.updateHitbox();
|
||||||
add(funkay);
|
add(funkay);
|
||||||
|
@ -389,9 +389,15 @@ class MultiCallback
|
||||||
public function getUnfired():Array<Void->Void>
|
public function getUnfired():Array<Void->Void>
|
||||||
return unfired.array();
|
return unfired.array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an FlxG.switchState with a nice transition
|
||||||
|
* @param state
|
||||||
|
* @param transitionTex
|
||||||
|
* @param time
|
||||||
|
*/
|
||||||
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
|
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
|
||||||
{
|
{
|
||||||
var screenShit:FunkinSprite = FunkinSprite.create(Paths.image("shaderTransitionStuff/coolDots"));
|
var screenShit:FunkinSprite = FunkinSprite.create('shaderTransitionStuff/coolDots');
|
||||||
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
|
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
|
||||||
|
|
||||||
screenWipeShit.funnyShit.input = screenShit.pixels;
|
screenWipeShit.funnyShit.input = screenShit.pixels;
|
||||||
|
|
|
@ -313,7 +313,7 @@ class StickerSprite extends FunkinSprite
|
||||||
public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void
|
public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void
|
||||||
{
|
{
|
||||||
super(x, y);
|
super(x, y);
|
||||||
loadTexture(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName));
|
loadTexture('transitionSwag/' + stickerSet + '/' + stickerName);
|
||||||
updateHitbox();
|
updateHitbox();
|
||||||
scrollFactor.set();
|
scrollFactor.set();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,15 @@ class StringTools
|
||||||
*/
|
*/
|
||||||
public static function toTitleCase(value:String):String
|
public static function toTitleCase(value:String):String
|
||||||
{
|
{
|
||||||
var words:Array<String> = value.split(" ");
|
var words:Array<String> = value.split(' ');
|
||||||
var result:String = "";
|
var result:String = '';
|
||||||
for (i in 0...words.length)
|
for (i in 0...words.length)
|
||||||
{
|
{
|
||||||
var word:String = words[i];
|
var word:String = words[i];
|
||||||
result += word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
|
result += word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
|
||||||
if (i < words.length - 1)
|
if (i < words.length - 1)
|
||||||
{
|
{
|
||||||
result += " ";
|
result += ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -35,7 +35,7 @@ class StringTools
|
||||||
*/
|
*/
|
||||||
public static function toLowerKebabCase(value:String):String
|
public static function toLowerKebabCase(value:String):String
|
||||||
{
|
{
|
||||||
return value.toLowerCase().replace(' ', "-");
|
return value.toLowerCase().replace(' ', '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,13 +46,30 @@ class StringTools
|
||||||
*/
|
*/
|
||||||
public static function toUpperKebabCase(value:String):String
|
public static function toUpperKebabCase(value:String):String
|
||||||
{
|
{
|
||||||
return value.toUpperCase().replace(' ', "-");
|
return value.toUpperCase().replace(' ', '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The regular expression to sanitize strings.
|
||||||
|
*/
|
||||||
|
static final SANTIZE_REGEX:EReg = ~/[^-a-zA-Z0-9]/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all instances of symbols other than alpha-numeric characters (and dashes)from a string.
|
||||||
|
* @param value The string to sanitize.
|
||||||
|
* @return The sanitized string.
|
||||||
|
*/
|
||||||
|
public static function sanitize(value:String):String
|
||||||
|
{
|
||||||
|
return SANTIZE_REGEX.replace(value, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the string data as JSON and returns the resulting object.
|
* Parses the string data as JSON and returns the resulting object.
|
||||||
* This is here so you can use `string.parseJSON()` when `using StringTools`.
|
* This is here so you can use `string.parseJSON()` when `using StringTools`.
|
||||||
*
|
*
|
||||||
|
* TODO: Remove this and replace with `json2object`
|
||||||
|
* @param value The
|
||||||
* @return The parsed object.
|
* @return The parsed object.
|
||||||
*/
|
*/
|
||||||
public static function parseJSON(value:String):Dynamic
|
public static function parseJSON(value:String):Dynamic
|
||||||
|
|
Loading…
Reference in a new issue