Merge remote-tracking branch 'upstream/main' into new-editors-main
# Conflicts: # commandline/commands/Compiler.hx # source/funkin/backend/FunkinSprite.hx # source/funkin/backend/MusicBeatState.hx # source/funkin/backend/MusicBeatSubstate.hx # source/funkin/backend/chart/Chart.hx # source/funkin/backend/chart/EventsData.hx # source/funkin/backend/scripting/GlobalScript.hx # source/funkin/backend/system/Controls.hx # source/funkin/backend/system/MainState.hx # source/funkin/backend/utils/AudioAnalyzer.hx # source/funkin/backend/utils/CoolUtil.hx # source/funkin/editors/character/CharacterInfoScreen.hx # source/funkin/editors/charter/Charter.hx # source/funkin/editors/charter/CharterSelection.hx # source/funkin/editors/charter/CharterStrumline.hx # source/funkin/editors/charter/CharterStrumlineButton.hx # source/funkin/editors/charter/SongCreationScreen.hx # source/funkin/editors/ui/UIDropDown.hx # source/funkin/editors/ui/UIFileExplorer.hx # source/funkin/game/Note.hx # source/funkin/game/PlayState.hx # source/funkin/game/SplashGroup.hx # source/funkin/game/Stage.hx # source/funkin/game/Strum.hx # source/funkin/menus/FreeplayState.hx # source/funkin/options/Options.hx # source/funkin/options/keybinds/KeybindsOptions.hx
This commit is contained in:
@@ -85,25 +85,34 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
|
||||
moves = false;
|
||||
}
|
||||
|
||||
public static function copyFrom(source:FunkinSprite)
|
||||
/**
|
||||
* Gets the graphics and copies other properties from another sprite (Works both for `FlxSprite` and `FunkinSprite`!).
|
||||
*/
|
||||
public static function copyFrom(source:FlxSprite):FunkinSprite
|
||||
{
|
||||
var spr = new FunkinSprite();
|
||||
var casted:FunkinSprite = null;
|
||||
if (source is FunkinSprite)
|
||||
casted = cast source;
|
||||
|
||||
@:privateAccess {
|
||||
spr.setPosition(source.x, source.y);
|
||||
spr.frames = source.frames;
|
||||
if (source.animateAtlas != null && source.atlasPath != null)
|
||||
spr.loadSprite(source.atlasPath);
|
||||
if (casted != null && casted.animateAtlas != null && casted.atlasPath != null)
|
||||
spr.loadSprite(casted.atlasPath);
|
||||
spr.animation.copyFrom(source.animation);
|
||||
spr.visible = source.visible;
|
||||
spr.alpha = source.alpha;
|
||||
spr.antialiasing = source.antialiasing;
|
||||
spr.scale.set(source.scale.x, source.scale.y);
|
||||
spr.scrollFactor.set(source.scrollFactor.x, source.scrollFactor.y);
|
||||
spr.skew.set(source.skew.x, source.skew.y);
|
||||
spr.transformMatrix = source.transformMatrix;
|
||||
spr.matrixExposed = source.matrixExposed;
|
||||
spr.zoomFactor = source.zoomFactor;
|
||||
spr.animOffsets = source.animOffsets.copy();
|
||||
|
||||
if (casted != null) {
|
||||
spr.skew.set(casted.skew.x, casted.skew.y);
|
||||
spr.transformMatrix = casted.transformMatrix;
|
||||
spr.matrixExposed = casted.matrixExposed;
|
||||
spr.animOffsets = casted.animOffsets.copy();
|
||||
}
|
||||
}
|
||||
return spr;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ class MusicBeatState extends FlxState implements IBeatReceiver
|
||||
public static var skipTransOut:Bool = false;
|
||||
public static var skipTransIn:Bool = false;
|
||||
|
||||
public static var ALLOW_DEV_RELOAD:Bool = true;
|
||||
|
||||
inline function get_controls():Controls
|
||||
return PlayerSettings.solo.controls;
|
||||
inline function get_controlsP1():Controls
|
||||
@@ -153,16 +155,20 @@ class MusicBeatState extends FlxState implements IBeatReceiver
|
||||
call("postUpdate", [elapsed]);
|
||||
}
|
||||
|
||||
if (_requestSubStateReset)
|
||||
{
|
||||
if (_requestSubStateReset) {
|
||||
_requestSubStateReset = false;
|
||||
resetSubState();
|
||||
}
|
||||
if (subState != null)
|
||||
{
|
||||
subState.tryUpdate(elapsed);
|
||||
|
||||
if (/*subState == null && */(ALLOW_DEV_RELOAD && controls.DEV_RELOAD)) {
|
||||
Logs.trace("Reloading Current State...", INFO, YELLOW);
|
||||
FlxG.resetState();
|
||||
}
|
||||
|
||||
if (subState != null)
|
||||
subState.tryUpdate(elapsed);
|
||||
}
|
||||
|
||||
override function create()
|
||||
{
|
||||
loadScript();
|
||||
@@ -223,12 +229,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
// TODO: DEBUG MODE!!
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
loadScript();
|
||||
}
|
||||
#end
|
||||
call("update", [elapsed]);
|
||||
|
||||
super.update(elapsed);
|
||||
|
||||
@@ -136,15 +136,19 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
|
||||
call("postUpdate", [elapsed]);
|
||||
}
|
||||
|
||||
if (_requestSubStateReset)
|
||||
{
|
||||
// if (subState == null && (MusicBeatState.ALLOW_DEV_RELOAD && controls.DEV_RELOAD)) {
|
||||
// Logs.trace("Reloading Current SubState...", INFO, YELLOW);
|
||||
// var test = Type.createInstance(Type.getClass(this), [this.scriptsAllowed, this.scriptName]);
|
||||
// parent.openSubState(test);
|
||||
// }
|
||||
|
||||
if (_requestSubStateReset) {
|
||||
_requestSubStateReset = false;
|
||||
resetSubState();
|
||||
}
|
||||
|
||||
if (subState != null)
|
||||
{
|
||||
subState.tryUpdate(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
override function close() {
|
||||
@@ -181,12 +185,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
// TODO: DEBUG MODE!!
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
loadScript();
|
||||
}
|
||||
#end
|
||||
call("update", [elapsed]);
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class AssetsLibraryList extends AssetLibrary {
|
||||
if (l is IModsAssetLibrary) {
|
||||
var lib = cast(l, IModsAssetLibrary);
|
||||
for(e in lib.getFiles(folder))
|
||||
content.push(e);
|
||||
content.pushOnce(e);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ class ModsFolder {
|
||||
public static function getModsList():Array<String> {
|
||||
var mods:Array<String> = [];
|
||||
#if MOD_SUPPORT
|
||||
if (!FileSystem.exists(modsPath)) {
|
||||
// Mods directory does not exist yet, create it
|
||||
FileSystem.createDirectory(modsPath);
|
||||
}
|
||||
|
||||
final modsList:Array<String> = FileSystem.readDirectory(modsPath);
|
||||
|
||||
if (modsList == null || modsList.length <= 0)
|
||||
|
||||
@@ -25,10 +25,7 @@ class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
|
||||
this.folderPath = folderPath;
|
||||
this.libName = libName;
|
||||
this.prefix = 'assets/$libName/';
|
||||
if(modName == null)
|
||||
this.modName = libName;
|
||||
else
|
||||
this.modName = modName;
|
||||
this.modName = modName == null ? libName : modName;
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,16 +71,14 @@ class Paths
|
||||
inline static public function voices(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
if (ext == null) ext = Flags.SOUND_EXT;
|
||||
song = song.toLowerCase();
|
||||
var diff = getPath('songs/$song/song/Voices$prefix-${difficulty.toLowerCase()}.${ext}', null);
|
||||
var diff = getPath('songs/$song/song/Voices$prefix-${difficulty}.${ext}', null);
|
||||
return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Voices$prefix.${ext}', null);
|
||||
}
|
||||
|
||||
inline static public function inst(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
if (ext == null) ext = Flags.SOUND_EXT;
|
||||
song = song.toLowerCase();
|
||||
var diff = getPath('songs/$song/song/Inst$prefix-${difficulty.toLowerCase()}.${ext}', null);
|
||||
var diff = getPath('songs/$song/song/Inst$prefix-${difficulty}.${ext}', null);
|
||||
return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Inst$prefix.${ext}', null);
|
||||
}
|
||||
|
||||
@@ -111,8 +109,7 @@ class Paths
|
||||
|
||||
static public function chart(song:String, ?difficulty:String):String
|
||||
{
|
||||
difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY).toLowerCase();
|
||||
song = song.toLowerCase();
|
||||
difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY);
|
||||
|
||||
return getPath('songs/$song/charts/$difficulty.json', null);
|
||||
}
|
||||
@@ -193,6 +190,29 @@ class Paths
|
||||
return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true, ext), false, null, false, ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the images needed for using getFrames() exist.
|
||||
* @param key Path to the image
|
||||
* @param checkAtlas Whenever to check for the Animation.json file (used in FlxAnimate)
|
||||
* @param assetsPath Whenever to use the raw path or to pass it through Paths.image()
|
||||
* @param library (Additional) library to load the frames from.
|
||||
* @return True if the images exist, false otherwise.
|
||||
**/
|
||||
public static function framesExists(key:String, checkAtlas:Bool = false, checkMulti:Bool = true, assetsPath:Bool = false, ?library:String) {
|
||||
var path = assetsPath ? key : Paths.image(key, library, true);
|
||||
var noExt = Path.withoutExtension(path);
|
||||
if(checkAtlas && Assets.exists('$noExt/Animation.json'))
|
||||
return true;
|
||||
if(checkMulti && Assets.exists('$noExt/1.png'))
|
||||
return true;
|
||||
if(Assets.exists('$noExt.xml'))
|
||||
return true;
|
||||
if(Assets.exists('$noExt.txt'))
|
||||
return true;
|
||||
if(Assets.exists('$noExt.json'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads frames from a specific image path. Supports Sparrow Atlases, Packer Atlases, and multiple spritesheets.
|
||||
@@ -247,13 +267,13 @@ class Paths
|
||||
}
|
||||
return content;
|
||||
}
|
||||
static public function getFolderContent(key:String, addPath:Bool = false, source:AssetsLibraryList.AssetSource = BOTH):Array<String> {
|
||||
static public function getFolderContent(key:String, addPath:Bool = false, source:AssetsLibraryList.AssetSource = BOTH, noExtension:Bool = false):Array<String> {
|
||||
// designed to work both on windows and web
|
||||
if (!key.endsWith("/")) key += "/";
|
||||
var content = assetsTree.getFiles('assets/$key', source);
|
||||
if (addPath) {
|
||||
for(k=>e in content)
|
||||
content[k] = '$key$e';
|
||||
for (k => e in content) {
|
||||
if (noExtension) e = Path.withoutExtension(e);
|
||||
content[k] = addPath ? '$key$e' : e;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package funkin.backend.chart;
|
||||
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
|
||||
// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex
|
||||
class BaseGameParser {
|
||||
public static function parseChart(data:Dynamic, metaData:Dynamic, result:ChartData) {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
public static function parseMeta(data:Dynamic, result:ChartMetaData) {
|
||||
var data:SwagMetadata = data;
|
||||
result.name = data.songName;
|
||||
result.artist = data.artist;
|
||||
|
||||
result.customValues = {};
|
||||
result.customValues.timeChanges = data.timeChanges;
|
||||
var firstTimeChange:SwagTimeChange = data.timeChanges[0];
|
||||
result.bpm = firstTimeChange.bpm;
|
||||
result.beatsPerMeasure = firstTimeChange.b != null ? firstTimeChange.b : Flags.DEFAULT_BEATS_PER_MEASURE;
|
||||
|
||||
result.difficulties = data.playData.difficulties.concat(data.playData.songVariations);
|
||||
Reflect.deleteField(data.playData, "difficulties");
|
||||
|
||||
for (field in Reflect.fields(data.playData))
|
||||
Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field));
|
||||
}
|
||||
|
||||
public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata {
|
||||
var addVars:Dynamic = meta.customValues;
|
||||
var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE;
|
||||
var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT};
|
||||
var defTimeCh:Array<SwagTimeChange>;
|
||||
|
||||
if (addVars.timeChanges != null && addVars.timeChanges.length > 0) {
|
||||
defTimeCh = addVars.timeChanges;
|
||||
defTimeCh[0].bpm = meta.bpm;
|
||||
}
|
||||
else defTimeCh = [{bpm: meta.bpm, t: -1}];
|
||||
|
||||
if (chart != null) {
|
||||
defStage = chart.stage;
|
||||
|
||||
var done:Array<Bool> = [false, false, false];
|
||||
for (strumLine in chart.strumLines) switch (strumLine.type) {
|
||||
case OPPONENT:
|
||||
if (!done[0]) {
|
||||
done[0] = true;
|
||||
defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0];
|
||||
}
|
||||
case PLAYER:
|
||||
if (!done[1]) {
|
||||
done[1] = true;
|
||||
defChars.player = strumLine.characters.getDefault([defChars.player])[0];
|
||||
}
|
||||
case ADDITIONAL:
|
||||
if (!done[3]) {
|
||||
done[3] = true;
|
||||
defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result:SwagMetadata = {
|
||||
songName: meta.name,
|
||||
timeFormat: MILLISECONDS,
|
||||
artist: meta.artist,
|
||||
timeChanges: defTimeCh,
|
||||
looped: false,
|
||||
generatedBy: 'Basegame Chart Importer (Codename Engine)',
|
||||
version: Flags.BASEGAME_SONG_METADATA_VERSION,
|
||||
playData: {
|
||||
stage: defStage,
|
||||
characters: defChars,
|
||||
songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [],
|
||||
difficulties: meta.difficulties,
|
||||
noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.BASEGAME_DEFAULT_NOTE_STYLE,
|
||||
album: addVars.album != null ? addVars.album : Flags.BASEGAME_DEFAULT_ALBUM_ID,
|
||||
previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.BASEGAME_DEFAULT_PREVIEW_START,
|
||||
previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.BASEGAME_DEFAULT_PREVIEW_END
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function encodeChart(chart:ChartData):NewSwagSong {
|
||||
// TO DO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// METADATA STRUCTURES
|
||||
typedef SwagMetadata =
|
||||
{
|
||||
var timeFormat:SwagTimeFormat;
|
||||
var artist:String;
|
||||
var songName:String;
|
||||
var playData:SwagPlayData;
|
||||
var timeChanges:Array<SwagTimeChange>;
|
||||
var generatedBy:String;
|
||||
var looped:Bool;
|
||||
var version:String;
|
||||
|
||||
var ?divisions:Int;
|
||||
var ?offsets:SwagSongOffsets;
|
||||
}
|
||||
|
||||
enum abstract SwagTimeFormat(String) from String to String
|
||||
{
|
||||
var TICKS = 'ticks';
|
||||
var FLOAT = 'float';
|
||||
var MILLISECONDS = 'ms';
|
||||
}
|
||||
|
||||
typedef SwagTimeChange =
|
||||
{
|
||||
var ?d:Int; // Time Signature Den
|
||||
var ?n:Int; // Time Signature Num
|
||||
var t:Int; // Time Stamp
|
||||
var ?b:Int; // Beat Time
|
||||
var ?bt:Array<Int>; // Beat Tuplets
|
||||
var bpm:Float;
|
||||
}
|
||||
|
||||
typedef SwagPlayData =
|
||||
{
|
||||
var album:String;
|
||||
var previewStart:Float;
|
||||
var previewEnd:Float;
|
||||
var stage:String;
|
||||
var characters:SwagCharactersList;
|
||||
var songVariations:Array<String>;
|
||||
var difficulties:Array<String>;
|
||||
var noteStyle:String;
|
||||
}
|
||||
|
||||
typedef SwagCharactersList =
|
||||
{
|
||||
var player:String;
|
||||
var girlfriend:String;
|
||||
var opponent:String;
|
||||
}
|
||||
|
||||
typedef SwagSongOffsets =
|
||||
{
|
||||
var ?instrumental:Float;
|
||||
var ?altInstrumentals:Dynamic;
|
||||
var ?vocals:Dynamic;
|
||||
}
|
||||
|
||||
// CHART STRUCTURE
|
||||
typedef NewSwagSong =
|
||||
{
|
||||
var version:String;
|
||||
var scrollSpeed:Dynamic; // Map<String, Float>
|
||||
var events:Array<SwagEvent>;
|
||||
var notes:Dynamic; // Map<String, Array<SwagNote>>
|
||||
var generatedBy:String;
|
||||
}
|
||||
|
||||
typedef SwagEvent =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var e:String; // Event Kind
|
||||
var v:Dynamic; // Value (Map<String, Dynamic>)
|
||||
}
|
||||
|
||||
typedef SwagNote =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var d:Int; // Data
|
||||
var l:Float; // Length
|
||||
var k:String; // Kind
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class Chart {
|
||||
}
|
||||
|
||||
public static function loadEventsJson(songName:String) {
|
||||
var path = Paths.file('songs/${songName.toLowerCase()}/events.json');
|
||||
var path = Paths.file('songs/${songName}/events.json');
|
||||
var data:Array<ChartEvent> = null;
|
||||
if (Assets.exists(path)) {
|
||||
try {
|
||||
@@ -83,31 +83,29 @@ class Chart {
|
||||
return data;
|
||||
}
|
||||
|
||||
public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true) {
|
||||
public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true):ChartMetaData {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
|
||||
var songNameLower = songName.toLowerCase();
|
||||
var metaPath = Paths.file('songs/${songNameLower}/meta.json');
|
||||
var metaDiffPath = Paths.file('songs/${songNameLower}/meta-${difficulty.toLowerCase()}.json');
|
||||
var metaPath = Paths.file('songs/${songName}/meta.json');
|
||||
var metaDiffPath = Paths.file('songs/${songName}/meta-${difficulty}.json');
|
||||
|
||||
var data:ChartMetaData = null;
|
||||
var fromMods:Bool = fromMods;
|
||||
for(path in [metaDiffPath, metaPath]) {
|
||||
if (Assets.exists(path)) {
|
||||
fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS);
|
||||
try {
|
||||
data = Json.parse(Assets.getText(path));
|
||||
} catch(e) {
|
||||
Logs.trace('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}', ERROR);
|
||||
}
|
||||
if (data != null) break;
|
||||
}
|
||||
for (path in [metaDiffPath, metaPath]) if (Assets.exists(path)) {
|
||||
fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS);
|
||||
try {
|
||||
var tempData = Json.parse(Assets.getText(path));
|
||||
tempData.color = CoolUtil.getColorFromDynamic(tempData.color).getDefault(Flags.DEFAULT_COLOR);
|
||||
data = tempData;
|
||||
} catch(e) Logs.trace('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}', ERROR);
|
||||
if (data != null) break;
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
data = {
|
||||
name: songName
|
||||
};
|
||||
if (data == null) data = {
|
||||
name: songName,
|
||||
bpm: 100
|
||||
};
|
||||
|
||||
data.setFieldDefault("name", songName);
|
||||
data.setFieldDefault("bpm", Flags.DEFAULT_BPM);
|
||||
data.setFieldDefault("beatsPerMeasure", Flags.DEFAULT_BEATS_PER_MEASURE);
|
||||
@@ -118,24 +116,19 @@ class Chart {
|
||||
data.setFieldDefault("coopAllowed", Flags.DEFAULT_COOP_ALLOWED);
|
||||
data.setFieldDefault("opponentModeAllowed", Flags.DEFAULT_OPPONENT_MODE_ALLOWED);
|
||||
data.setFieldDefault("displayName", data.name);
|
||||
data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(Flags.DEFAULT_COLOR));
|
||||
|
||||
if (data.difficulties.length <= 0) {
|
||||
data.difficulties = [for(f in Paths.getFolderContent('songs/${songNameLower}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
|
||||
data.difficulties = [for(f in Paths.getFolderContent('songs/${songName}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
|
||||
var tempDiffs = [];
|
||||
if (data.difficulties.length == 3) {
|
||||
var hasHard = false, hasNormal = false, hasEasy = false;
|
||||
for(d in data.difficulties) {
|
||||
switch(d) {
|
||||
case "EASY": hasEasy = true;
|
||||
case "NORMAL": hasNormal = true;
|
||||
case "HARD": hasHard = true;
|
||||
switch(d.toLowerCase()) {
|
||||
case "easy": tempDiffs.insert(0, d);
|
||||
case "normal": tempDiffs.insert(1, d);
|
||||
case "hard": tempDiffs.insert(2, d);
|
||||
}
|
||||
}
|
||||
if (hasHard && hasNormal && hasEasy) {
|
||||
data.difficulties[0] = "EASY";
|
||||
data.difficulties[1] = "NORMAL";
|
||||
data.difficulties[2] = "HARD";
|
||||
}
|
||||
data.difficulties = tempDiffs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,11 +158,9 @@ class Chart {
|
||||
valid = false;
|
||||
}
|
||||
var data:Dynamic = null;
|
||||
try {
|
||||
if (valid)
|
||||
data = Json.parse(Assets.getText(chartPath));
|
||||
} catch(e) {
|
||||
Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED);
|
||||
if (valid) {
|
||||
try data = Json.parse(Assets.getText(chartPath))
|
||||
catch(e) Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +169,7 @@ class Chart {
|
||||
#if REGION
|
||||
if (data != null) switch (detectChartFormat(data)) {
|
||||
case CODENAME:
|
||||
// backward compat on events since its caused problems
|
||||
// backward compat on events since it caused problems
|
||||
var eventTypesToString:Map<Int, String> = [
|
||||
-1 => "HScript Call",
|
||||
0 => "Unknown",
|
||||
@@ -196,7 +187,7 @@ class Chart {
|
||||
|
||||
base = data;
|
||||
case PSYCH_NEW: PsychParser.parse(data, base);
|
||||
case VSLICE: // TODO
|
||||
case VSLICE: Logs.trace("Couldn't parse V-Slice chart because it's not supported on runtime, it MUST be imported and converted in the new song editor window.", ERROR, RED);
|
||||
case LEGACY: FNFLegacyParser.parse(data, base);
|
||||
}
|
||||
#end
|
||||
@@ -238,8 +229,7 @@ class Chart {
|
||||
return 0;
|
||||
default:
|
||||
var index = chart.noteTypes.indexOf(noteTypeName);
|
||||
if (index > -1)
|
||||
return index+1;
|
||||
if (index > -1) return index + 1;
|
||||
chart.noteTypes.push(noteTypeName);
|
||||
return chart.noteTypes.length;
|
||||
}
|
||||
@@ -275,9 +265,8 @@ class Chart {
|
||||
|
||||
CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
|
||||
|
||||
// idk how null reacts to it so better be sure
|
||||
if (saveSettings.overrideExistingMeta == true || !FileSystem.exists(metaPath))
|
||||
CoolUtil.safeSaveFile(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
|
||||
CoolUtil.safeSaveFile(metaPath, makeMetaSaveable(meta));
|
||||
#end
|
||||
return filteredChart;
|
||||
}
|
||||
@@ -306,13 +295,19 @@ class Chart {
|
||||
data.fromMods = null;
|
||||
|
||||
var sortedData:Dynamic = {};
|
||||
for(f in Reflect.fields(data)) {
|
||||
for (f in Reflect.fields(data)) {
|
||||
var v = Reflect.field(data, f);
|
||||
if (v != null)
|
||||
Reflect.setField(sortedData, f, v);
|
||||
}
|
||||
return sortedData;
|
||||
}
|
||||
|
||||
public static inline function makeMetaSaveable(meta:ChartMetaData, prettyPrint:Bool = true):String {
|
||||
var data:Dynamic = Reflect.copy(meta);
|
||||
if (data.color != null) data.color = new FlxColor(data.color).toWebString(); // dont even ask me - Nex
|
||||
return Json.stringify(data, null, prettyPrint ? Flags.JSON_PRETTY_PRINT : null);
|
||||
}
|
||||
}
|
||||
|
||||
typedef ChartSaveSettings = {
|
||||
|
||||
@@ -22,16 +22,12 @@ typedef ChartMetaData = {
|
||||
public var ?beatsPerMeasure:Float;
|
||||
public var ?stepsPerBeat:Int;
|
||||
public var ?icon:String;
|
||||
public var ?color:Dynamic;
|
||||
public var ?color:FlxColor;
|
||||
public var ?difficulties:Array<String>;
|
||||
public var ?coopAllowed:Bool;
|
||||
public var ?opponentModeAllowed:Bool;
|
||||
public var ?customValues:Dynamic;
|
||||
public var ?needsVoices:Bool;
|
||||
public var ?artist:String;
|
||||
|
||||
// NOT TO BE EXPORTED
|
||||
public var ?parsedColor:FlxColor;
|
||||
}
|
||||
|
||||
typedef ChartStrumLine = {
|
||||
|
||||
@@ -9,18 +9,71 @@ import openfl.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
final class EventsData {
|
||||
public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"];
|
||||
class EventsData {
|
||||
public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Camera Position", "Add Camera Zoom", "Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"];
|
||||
public static var defaultEventsParams:Map<String, Array<EventParamInfo>> = [
|
||||
"HScript Call" => [
|
||||
{name: "Function Name", type: TString, defValue: "myFunc"},
|
||||
{name: "Function Parameters (String split with commas)", type: TString, defValue: ""}
|
||||
],
|
||||
"Camera Movement" => [{name: "Camera Target", type: TStrumLine, defValue: 0}],
|
||||
"Camera Movement" => [
|
||||
{name: "Camera Target", type: TStrumLine, defValue: 0},
|
||||
{name: "Tween Movement?", type: TBool, defValue: true, saveIfDefault: false},
|
||||
{name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4, saveIfDefault: false},
|
||||
{ // since its the most used event even by default, we'll set saveIfDefault false to avoid filling up with unnecessary parameters the files - Nex
|
||||
name: "Tween Ease (ex: circ, quad, cube)",
|
||||
type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']),
|
||||
defValue: "CLASSIC",
|
||||
saveIfDefault: false
|
||||
},
|
||||
{
|
||||
name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In",
|
||||
saveIfDefault: false
|
||||
}
|
||||
],
|
||||
"Camera Position" => [
|
||||
{name: "X", type: TFloat(null, null, 10, 3), defValue: 0},
|
||||
{name: "Y", type: TFloat(null, null, 10, 3), defValue: 0},
|
||||
{name: "Tween Movement?", type: TBool, defValue: true, saveIfDefault: false},
|
||||
{name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4, saveIfDefault: false},
|
||||
{
|
||||
name: "Tween Ease (ex: circ, quad, cube)",
|
||||
type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']),
|
||||
defValue: "CLASSIC",
|
||||
saveIfDefault: false
|
||||
},
|
||||
{
|
||||
name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In",
|
||||
saveIfDefault: false
|
||||
},
|
||||
{name: "Is Offset?", type: TBool, defValue: false, saveIfDefault: false}
|
||||
],
|
||||
"Add Camera Zoom" => [
|
||||
{name: "Amount", type: TFloat(-10, 10, 0.01, 2), defValue: 0.05},
|
||||
{name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"}
|
||||
],
|
||||
"Camera Zoom" => [
|
||||
{name: "Tween Zoom?", type: TBool, defValue: true},
|
||||
{name: "New Zoom", type: TFloat(-10, 10, 0.01, 2), defValue: 1},
|
||||
{name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"},
|
||||
{name: "Tween Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4},
|
||||
{
|
||||
name: "Tween Ease (ex: circ, quad, cube)",
|
||||
type: TDropDown(['linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']),
|
||||
defValue: "linear"
|
||||
},
|
||||
{
|
||||
name: "Tween Type (excluded if linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In"
|
||||
},
|
||||
{name: "Mode", type: TDropDown(['direct', 'stage']), defValue: "direct"},
|
||||
{name: "Multiplicative?", type: TBool, defValue: true}
|
||||
],
|
||||
"Camera Modulo Change" => [
|
||||
{name: "Modulo Interval (Beats)", type: TInt(1, 9999999, 1), defValue: 4},
|
||||
{name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1}
|
||||
@@ -42,13 +95,23 @@ final class EventsData {
|
||||
defValue: "linear"
|
||||
},
|
||||
{
|
||||
name: "Tween Type (ex: InOut)",
|
||||
name: "Tween Type (excluded if linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In"
|
||||
}
|
||||
},
|
||||
{name: "Multiplicative?", type: TBool, defValue: false}
|
||||
],
|
||||
"Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}],
|
||||
"Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}],
|
||||
"Play Animation" => [
|
||||
{name: "Character", type: TStrumLine, defValue: 0},
|
||||
{name: "Animation", type: TString, defValue: "animation"},
|
||||
{name: "Is forced?", type: TBool, defValue: true},
|
||||
{
|
||||
name: "Animation Context",
|
||||
type: TDropDown(["NONE", "SING", "DANCE", "MISS", "LOCK"]),
|
||||
defValue: "NONE"
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
public static var eventsList:Array<String> = defaultEventsList.copy();
|
||||
@@ -136,6 +199,7 @@ typedef EventParamInfo = {
|
||||
var name:String;
|
||||
var type:EventParamType;
|
||||
var defValue:Dynamic;
|
||||
var ?saveIfDefault:Bool;
|
||||
}
|
||||
|
||||
enum EventParamType {
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
package funkin.backend.chart;
|
||||
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
|
||||
typedef ChartDataWithInfo = {
|
||||
var diffName:String;
|
||||
var chart:ChartData;
|
||||
}
|
||||
|
||||
// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex
|
||||
class VSliceParser {
|
||||
public static function parse(metaData:Dynamic, chartData:Dynamic, resultMeta:ChartMetaData, resultCharts:Array<ChartDataWithInfo>, ?songID:String) {
|
||||
var metaData:SwagMetadata = metaData;
|
||||
var chartData:NewSwagSong = chartData;
|
||||
|
||||
parseMeta(metaData, resultMeta, songID);
|
||||
|
||||
for (diff in Reflect.fields(chartData.notes))
|
||||
{
|
||||
var base:ChartData = {
|
||||
strumLines: [],
|
||||
noteTypes: [],
|
||||
events: [],
|
||||
meta: {name: null},
|
||||
scrollSpeed: Reflect.field(chartData.scrollSpeed, diff),
|
||||
stage: Flags.DEFAULT_STAGE,
|
||||
codenameChart: true
|
||||
};
|
||||
parseChart(Reflect.field(chartData.notes, diff), metaData, chartData.events, base);
|
||||
resultCharts.push({diffName: diff, chart: base});
|
||||
}
|
||||
}
|
||||
|
||||
public static function parseChart(data:Dynamic, metaData:Dynamic, events:Dynamic, result:ChartData) {
|
||||
// vslice chart parsing
|
||||
var data:Array<SwagNote> = data;
|
||||
var metadata:SwagMetadata = metaData;
|
||||
var events:Array<SwagEvent> = events;
|
||||
|
||||
result.stage = metadata.playData.stage;
|
||||
|
||||
var p2isGF:Bool = false;
|
||||
result.strumLines.push({
|
||||
characters: [metadata.playData.characters.opponent],
|
||||
type: 0,
|
||||
position: (p2isGF = metadata.playData.characters.opponent.startsWith("gf")) ? "girlfriend" : "dad",
|
||||
notes: [],
|
||||
vocalsSuffix: "-Opponent"
|
||||
});
|
||||
result.strumLines.push({
|
||||
characters: [metadata.playData.characters.player],
|
||||
type: 1,
|
||||
position: "boyfriend",
|
||||
notes: [],
|
||||
vocalsSuffix: "-Player"
|
||||
});
|
||||
var gfName = metadata.playData.characters.girlfriend;
|
||||
if (!p2isGF && gfName != "none") {
|
||||
result.strumLines.push({
|
||||
characters: [gfName],
|
||||
type: 2,
|
||||
position: "girlfriend",
|
||||
notes: [],
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
|
||||
var timeChanges = metadata.timeChanges;
|
||||
result.meta.bpm = timeChanges[0].bpm;
|
||||
result.meta.needsVoices = true;
|
||||
|
||||
for (note in data)
|
||||
{
|
||||
var daNoteType:Null<Int> = null;
|
||||
if (note.k != null)
|
||||
daNoteType = Chart.addNoteType(result, note.k == "alt" ? "Alt Anim Note" : note.k); // they hardcoded "alt" for converting old charts BUT THEN WHY THE HELL WOULD YOU CALL "MOM" THE NOTE KIND IN WEEK5 GRAHHH - Nex
|
||||
|
||||
var daNoteData:Int = Std.int(note.d % 8);
|
||||
var isMustHit:Bool = Math.floor(daNoteData / 4) == 0;
|
||||
|
||||
result.strumLines[isMustHit ? 1 : 0].notes.push({
|
||||
time: note.t,
|
||||
id: daNoteData % 4,
|
||||
type: daNoteType,
|
||||
sLen: note.l
|
||||
});
|
||||
}
|
||||
|
||||
var curBPM = result.meta.bpm;
|
||||
for (i in 1...timeChanges.length) // starting from 1 on purpose - Nex
|
||||
{
|
||||
var curChange = timeChanges[i];
|
||||
if (curBPM != curChange.bpm)
|
||||
{
|
||||
curBPM = curChange.bpm;
|
||||
result.events.push({
|
||||
time: curChange.t,
|
||||
name: "BPM Change",
|
||||
params: [curBPM]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
var values = event.v;
|
||||
switch (event.e)
|
||||
{
|
||||
case "FocusCamera":
|
||||
var isPosOnly = false;
|
||||
var arr:Array<Dynamic> = [switch(values.char) {
|
||||
// more cases here in the future if they add em? (i hope not) - Nex
|
||||
case 0: 1;
|
||||
case 1: 0;
|
||||
case -1:
|
||||
isPosOnly = true;
|
||||
-1;
|
||||
default: 2;
|
||||
}];
|
||||
if (values.ease != "CLASSIC" && values.ease != null) {
|
||||
if (values.ease == "INSTANT") arr = arr.concat([false]);
|
||||
else {
|
||||
var cneEase = parseEase(values.ease);
|
||||
arr = arr.concat([true, values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1]]);
|
||||
}
|
||||
}
|
||||
if (isPosOnly || ((values.x != null && values.x != 0) || (values.y != null && values.y != 0))) {
|
||||
var useNull = arr.length <= 2;
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Position",
|
||||
params: [values.x, values.y, useNull ? null : arr[1], useNull ? null : arr[2], useNull ? null : arr[3], useNull ? null : arr[4], true]
|
||||
});
|
||||
}
|
||||
if (!isPosOnly) result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Movement",
|
||||
params: arr
|
||||
});
|
||||
case "PlayAnimation":
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Play Animation",
|
||||
params: [switch(values.target) {
|
||||
case 'boyfriend' | 'bf' | 'player': 1;
|
||||
case 'dad' | 'opponent': 0;
|
||||
default /*case 'girlfriend' | 'gf'*/: 2; // usually the default should be the stage prop but we dont have that sooo - Nex
|
||||
}, values.anim, values.force == null ? false : values.force]
|
||||
});
|
||||
case "ScrollSpeed":
|
||||
var cneEase = values.ease == null || values.ease == "INSTANT" ? ["linear", null] : parseEase(values.ease);
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Scroll Speed Change", // we dont support the strumline value and also i will put the whole ease name into a single parameter since it works anyways - Nex
|
||||
params: [values.ease != "INSTANT", values.scroll == null ? 1 : values.scroll, values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1], values.absolute != true]
|
||||
});
|
||||
case "SetCameraBop":
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Modulo Change",
|
||||
params: [values.rate == null ? 4 : values.rate, values.intensity == null ? 1 : values.intensity]
|
||||
});
|
||||
case "ZoomCamera":
|
||||
var cneEase = values.ease == null || values.ease == "INSTANT" ? ["linear", null] : parseEase(values.ease);
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Zoom", // we dont support the direct mode since welp, its kind of useless here - Nex
|
||||
params: [values.ease != "INSTANT", values.zoom == null ? 1 : values.zoom, "camGame", values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1], values.mode, false]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function parseMeta(data:Dynamic, result:ChartMetaData, ?songID:String) {
|
||||
var data:SwagMetadata = data;
|
||||
var songName = data.songName;
|
||||
var songID:String = songID == null ? songName.toLowerCase().replace(" ", "-") : songID;
|
||||
var firstTimeChange:SwagTimeChange = data.timeChanges[0];
|
||||
|
||||
result.name = songID;
|
||||
result.bpm = firstTimeChange.bpm.getDefault(Flags.DEFAULT_BPM);
|
||||
result.beatsPerMeasure = firstTimeChange.n.getDefault(Flags.DEFAULT_BEATS_PER_MEASURE);
|
||||
result.stepsPerBeat = firstTimeChange.d.getDefault(Flags.DEFAULT_STEPS_PER_BEAT);
|
||||
result.displayName = songName;
|
||||
result.icon = Flags.DEFAULT_HEALTH_ICON;
|
||||
result.color = 0xFFFFFF;
|
||||
result.opponentModeAllowed = true;
|
||||
result.coopAllowed = true;
|
||||
result.difficulties = data.playData.difficulties.concat(data.playData.songVariations.getDefault([]));
|
||||
|
||||
if (result.customValues == null) result.customValues = {};
|
||||
result.customValues.artist = data.artist;
|
||||
if (data.charter != null) result.customValues.charter = data.charter;
|
||||
for (field in Reflect.fields(data.playData)) if (field != "difficulties" && field != "songVariations" && field != "characters" && field != "stage")
|
||||
Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field));
|
||||
}
|
||||
|
||||
public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata {
|
||||
var addVars:Dynamic = meta.customValues;
|
||||
var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE;
|
||||
var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT, playerVocals: [], opponentVocals: [], instrumental: '', altInstrumentals: []};
|
||||
var defTimeCh:Array<SwagTimeChange>;
|
||||
|
||||
if (addVars.timeChanges != null && addVars.timeChanges.length > 0) {
|
||||
defTimeCh = addVars.timeChanges;
|
||||
defTimeCh[0].bpm = meta.bpm;
|
||||
}
|
||||
else defTimeCh = [{bpm: meta.bpm, t: -1}];
|
||||
|
||||
if (chart != null) {
|
||||
defStage = chart.stage;
|
||||
|
||||
var done:Array<Bool> = [false, false, false];
|
||||
for (strumLine in chart.strumLines) switch (strumLine.type) {
|
||||
case OPPONENT:
|
||||
if (!done[0]) {
|
||||
done[0] = true;
|
||||
defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0];
|
||||
}
|
||||
case PLAYER:
|
||||
if (!done[1]) {
|
||||
done[1] = true;
|
||||
defChars.player = strumLine.characters.getDefault([defChars.player])[0];
|
||||
}
|
||||
case ADDITIONAL:
|
||||
if (!done[3]) {
|
||||
done[3] = true;
|
||||
defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result:SwagMetadata = {
|
||||
songName: meta.name,
|
||||
timeFormat: MILLISECONDS,
|
||||
artist: "",
|
||||
timeChanges: defTimeCh,
|
||||
looped: false,
|
||||
generatedBy: 'V-Slice Chart Importer (Codename Engine)',
|
||||
version: Flags.VSLICE_SONG_METADATA_VERSION,
|
||||
playData: {
|
||||
stage: defStage,
|
||||
characters: defChars,
|
||||
songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [],
|
||||
difficulties: meta.difficulties,
|
||||
noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.VSLICE_DEFAULT_NOTE_STYLE,
|
||||
album: addVars.album != null ? addVars.album : Flags.VSLICE_DEFAULT_ALBUM_ID,
|
||||
previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.VSLICE_DEFAULT_PREVIEW_START,
|
||||
previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.VSLICE_DEFAULT_PREVIEW_END
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function encodeChart(chart:ChartData):NewSwagSong {
|
||||
// TO DO
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function parseEase(vsliceEase:String):Array<String> {
|
||||
for (key in ['InOut', 'In', 'Out']) if (vsliceEase.endsWith(key)) return [vsliceEase.substr(0, vsliceEase.length - key.length), key];
|
||||
return [vsliceEase];
|
||||
}
|
||||
}
|
||||
|
||||
// METADATA STRUCTURES
|
||||
typedef SwagMetadata =
|
||||
{
|
||||
var version:String;
|
||||
var songName:String;
|
||||
var artist:String;
|
||||
var ?charter:String;
|
||||
var ?divisions:Int;
|
||||
var ?looped:Bool;
|
||||
var ?offsets:SwagSongOffsets;
|
||||
var playData:SwagPlayData;
|
||||
var generatedBy:String;
|
||||
var ?timeFormat:SwagTimeFormat;
|
||||
var timeChanges:Array<SwagTimeChange>;
|
||||
}
|
||||
|
||||
enum abstract SwagTimeFormat(String) from String to String
|
||||
{
|
||||
var TICKS = 'ticks';
|
||||
var FLOAT = 'float';
|
||||
var MILLISECONDS = 'ms';
|
||||
}
|
||||
|
||||
typedef SwagTimeChange =
|
||||
{
|
||||
var t:Int; // Time Stamp
|
||||
var ?b:Int; // Beat Time
|
||||
var bpm:Float;
|
||||
var ?n:Int; // Time Signature Num
|
||||
var ?d:Int; // Time Signature Den
|
||||
var ?bt:Array<Int>; // Beat Tuplets
|
||||
}
|
||||
|
||||
typedef SwagSongOffsets =
|
||||
{
|
||||
var ?instrumental:Float;
|
||||
var ?altInstrumentals:Dynamic;
|
||||
var ?vocals:Dynamic;
|
||||
var ?altVocals:Dynamic;
|
||||
}
|
||||
|
||||
typedef SwagPlayData =
|
||||
{
|
||||
var ?songVariations:Array<String>;
|
||||
var difficulties:Array<String>;
|
||||
var characters:SwagCharactersList;
|
||||
var stage:String;
|
||||
var noteStyle:String;
|
||||
var ?ratings:Dynamic;
|
||||
var ?album:String;
|
||||
var ?previewStart:Int;
|
||||
var ?previewEnd:Int;
|
||||
}
|
||||
|
||||
typedef SwagCharactersList =
|
||||
{
|
||||
var ?player:String;
|
||||
var ?girlfriend:String;
|
||||
var ?opponent:String;
|
||||
var ?instrumental:String;
|
||||
var ?altInstrumentals:Array<String>;
|
||||
var ?opponentVocals:Array<String>;
|
||||
var ?playerVocals:Array<String>;
|
||||
}
|
||||
|
||||
// CHART STRUCTURE
|
||||
typedef NewSwagSong =
|
||||
{
|
||||
var version:String;
|
||||
var scrollSpeed:Dynamic; // Map<String, Float>
|
||||
var events:Array<SwagEvent>;
|
||||
var notes:Dynamic; // Map<String, Array<SwagNote>>
|
||||
var generatedBy:String;
|
||||
}
|
||||
|
||||
typedef SwagEvent =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var e:String; // Event Kind
|
||||
var ?v:Dynamic; // Value (Map<String, Dynamic>)
|
||||
}
|
||||
|
||||
typedef SwagNote =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var d:Int; // Data
|
||||
var ?l:Float; // Length
|
||||
var ?k:String; // Kind
|
||||
var ?p:Array<SwagNoteParamsData>; // Params
|
||||
}
|
||||
|
||||
typedef SwagNoteParamsData =
|
||||
{
|
||||
var n:String; // Name
|
||||
var v:Dynamic; // Value
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import flixel.FlxState;
|
||||
import funkin.backend.assets.ModsFolder;
|
||||
import funkin.backend.scripting.events.CancellableEvent;
|
||||
import funkin.backend.system.Conductor;
|
||||
import funkin.options.PlayerSettings;
|
||||
|
||||
#if GLOBAL_SCRIPT
|
||||
/**
|
||||
* Class for THE Global Script, aka script that runs in the background at all times.
|
||||
@@ -12,6 +14,8 @@ class GlobalScript {
|
||||
public static var scripts:ScriptPack;
|
||||
|
||||
private static var initialized:Bool = false;
|
||||
private static var reloading:Bool = false;
|
||||
private static var _lastAllow_Reload:Bool = false;
|
||||
|
||||
public static function init() {
|
||||
if(initialized) return;
|
||||
@@ -46,19 +50,13 @@ class GlobalScript {
|
||||
});
|
||||
FlxG.signals.postUpdate.add(function() {
|
||||
call("postUpdate", [FlxG.elapsed]);
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
if (scripts.scripts.length > 0) {
|
||||
Logs.trace('Reloading global script...', WARNING, YELLOW);
|
||||
scripts.reload();
|
||||
Logs.trace('Global script successfully reloaded.', WARNING, GREEN);
|
||||
} else {
|
||||
Logs.trace('Loading global script...', WARNING, YELLOW);
|
||||
onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end);
|
||||
}
|
||||
|
||||
if (reloading) {
|
||||
reloading = false;
|
||||
MusicBeatState.ALLOW_DEV_RELOAD = _lastAllow_Reload;
|
||||
}
|
||||
#end
|
||||
if (FlxG.keys.justPressed.F2)
|
||||
|
||||
if (PlayerSettings.solo.controls.DEV_CONSOLE)
|
||||
NativeAPI.allocConsole();
|
||||
});
|
||||
FlxG.signals.preDraw.add(function() {
|
||||
@@ -76,13 +74,26 @@ class GlobalScript {
|
||||
FlxG.signals.preStateSwitch.add(function() {
|
||||
call("preStateSwitch", []);
|
||||
});
|
||||
|
||||
FlxG.signals.preUpdate.add(function() {
|
||||
call("preUpdate", [FlxG.elapsed]);
|
||||
call("update", [FlxG.elapsed]);
|
||||
});
|
||||
|
||||
// Commented this out, because it might be the cause why global runs twice - Neo
|
||||
//onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end);
|
||||
if (FlxG.keys.pressed.SHIFT) {
|
||||
// If we want, we could just make reseting GlobalScript it's own keybind, but for now this works.
|
||||
if (PlayerSettings.solo.controls.DEV_RELOAD) {
|
||||
reloading = true;
|
||||
Logs.trace("Reloading Global Scripts...", INFO, YELLOW);
|
||||
|
||||
// yeah its a bit messy, sorry. This just prevents actually reloading the actual state.
|
||||
_lastAllow_Reload = MusicBeatState.ALLOW_DEV_RELOAD;
|
||||
MusicBeatState.ALLOW_DEV_RELOAD = false;
|
||||
|
||||
// Would be better to just re-initalize GlobalScript so there aren't any lose ends.
|
||||
onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function event<T:CancellableEvent>(name:String, event:T):T {
|
||||
@@ -95,6 +106,7 @@ class GlobalScript {
|
||||
if (scripts != null)
|
||||
scripts.call(name, args);
|
||||
}
|
||||
|
||||
public static function onModSwitch(newMod:String) {
|
||||
call("destroy");
|
||||
scripts = FlxDestroyUtil.destroy(scripts);
|
||||
|
||||
@@ -128,6 +128,10 @@ class HScript extends Script {
|
||||
interp.execute(expr);
|
||||
call("new", []);
|
||||
}
|
||||
|
||||
#if GLOBAL_SCRIPT
|
||||
funkin.backend.scripting.GlobalScript.call("onScriptSetup", [this, "hscript"]);
|
||||
#end
|
||||
}
|
||||
|
||||
public override function reload() {
|
||||
|
||||
@@ -83,7 +83,7 @@ class ScriptPack extends Script {
|
||||
**/
|
||||
public override function call(func:String, ?parameters:Array<Dynamic>):Dynamic {
|
||||
for(e in scripts)
|
||||
if(e.active)
|
||||
if (e.active)
|
||||
e.call(func, parameters);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package funkin.backend.scripting.events.gameplay;
|
||||
|
||||
final class RatingUpdateEvent extends CancellableEvent {
|
||||
/**
|
||||
New combo
|
||||
New combo (may be null if no ratings were found)
|
||||
**/
|
||||
public var rating:ComboRating;
|
||||
public var rating:Null<ComboRating>;
|
||||
/**
|
||||
Old combo (may be null)
|
||||
**/
|
||||
public var oldRating:ComboRating;
|
||||
public var oldRating:Null<ComboRating>;
|
||||
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package funkin.backend.scripting.events.healthicon;
|
||||
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
|
||||
final class HealthIconChangeEvent extends CancellableEvent {
|
||||
/**
|
||||
* Amount
|
||||
* Animation State
|
||||
*/
|
||||
public var amount:Int;
|
||||
public var anim:OneOfTwo<String, Int>;
|
||||
|
||||
/**
|
||||
* The health icon
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package funkin.backend.scripting.events.menu.storymenu;
|
||||
|
||||
import funkin.menus.StoryMenuState.WeekData;
|
||||
import funkin.backend.week.WeekData;
|
||||
|
||||
final class WeekSelectEvent extends CancellableEvent {
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package funkin.backend.scripting.events.note;
|
||||
|
||||
import funkin.game.Character;
|
||||
import funkin.game.HealthIcon;
|
||||
import funkin.game.Note;
|
||||
|
||||
final class NoteHitEvent extends CancellableEvent {
|
||||
@@ -113,6 +114,10 @@ final class NoteHitEvent extends CancellableEvent {
|
||||
* Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one).
|
||||
*/
|
||||
public var forceAnim:Null<Bool> = true;
|
||||
/**
|
||||
* The attached healthIcon used distinction for icons amongst others
|
||||
*/
|
||||
public var healthIcon:HealthIcon;
|
||||
|
||||
/**
|
||||
* Prevents the default sing animation from being played.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.backend.scripting.events.splash;
|
||||
|
||||
import funkin.game.Splash;
|
||||
import funkin.game.SplashGroup;
|
||||
import funkin.game.Strum;
|
||||
|
||||
@@ -11,7 +12,7 @@ final class SplashShowEvent extends CancellableEvent {
|
||||
/**
|
||||
Splash that is shown
|
||||
**/
|
||||
public var splash:FunkinSprite;
|
||||
public var splash:Splash;
|
||||
/**
|
||||
Strum that the splash is shown on
|
||||
**/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.backend.shaders;
|
||||
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import haxe.Exception;
|
||||
@@ -555,34 +556,44 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
return val;
|
||||
}
|
||||
|
||||
var field:Dynamic = Reflect.field(data, name);
|
||||
var cl:String = Type.getClassName(Type.getClass(field));
|
||||
var field = Reflect.field(data, name);
|
||||
var cl = Type.getClassName(Type.getClass(field));
|
||||
var isNotNull = val != null;
|
||||
// cant do "field is ShaderInput" for some reason
|
||||
if (cl.startsWith("openfl.display.ShaderParameter")) {
|
||||
if (field.__length <= 1) {
|
||||
if ((val is Array))
|
||||
return field.value = val;
|
||||
// that means we wait for a single number, instead of an array
|
||||
if (field.__isInt && !(val is Int))
|
||||
if (field.__isInt && isNotNull && !(val is Int)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), 'Int');
|
||||
else if (field.__isBool && !(val is Bool))
|
||||
return null;
|
||||
} else
|
||||
if (field.__isBool && isNotNull && !(val is Bool)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), 'Bool');
|
||||
else if (field.__isFloat && !(val is Float))
|
||||
return null;
|
||||
} else
|
||||
if (field.__isFloat && isNotNull && !(val is Float)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), 'Float');
|
||||
|
||||
return field.value = [val];
|
||||
return null;
|
||||
}
|
||||
return field.value = isNotNull ? [val] : null;
|
||||
} else {
|
||||
if (!(val is Array))
|
||||
if (isNotNull && !(val is Array)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), Array);
|
||||
|
||||
return null;
|
||||
}
|
||||
return field.value = val;
|
||||
}
|
||||
} else if (cl.startsWith("openfl.display.ShaderInput")) {
|
||||
// shader input!!
|
||||
if (!(val is BitmapData)) {
|
||||
var bitmap:BitmapData;
|
||||
if (!isNotNull) bitmap = null;
|
||||
else if (val is BitmapData) bitmap = val;
|
||||
else if (val is FlxGraphic) bitmap = val.bitmap;
|
||||
else {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), BitmapData);
|
||||
return null;
|
||||
}
|
||||
field.input = cast val;
|
||||
field.input = bitmap;
|
||||
}
|
||||
|
||||
return val;
|
||||
|
||||
@@ -24,6 +24,11 @@ enum Control
|
||||
PAUSE;
|
||||
//CHEAT;
|
||||
SWITCHMOD;
|
||||
|
||||
// Debugs
|
||||
DEV_ACCESS;
|
||||
DEV_CONSOLE;
|
||||
DEV_RELOAD;
|
||||
}
|
||||
|
||||
enum KeyboardScheme
|
||||
@@ -150,6 +155,24 @@ class Controls extends FlxActionSet
|
||||
@:pressed("switchmod") public var SWITCHMOD_HOLD(get, set): Bool;
|
||||
@:justReleased("switchmod") public var SWITCHMOD_R(get, set): Bool;
|
||||
|
||||
@:devModeOnly
|
||||
@:gamepad([])
|
||||
@:justPressed("dev-access") public var DEV_ACCESS(get, set): Bool;
|
||||
@:pressed("dev-access") public var DEV_ACCESS_HOLD(get, set): Bool;
|
||||
@:justReleased("dev-access") public var DEV_ACCESS_R(get, set): Bool;
|
||||
|
||||
@:devModeOnly
|
||||
@:gamepad([])
|
||||
@:justPressed("dev-console") public var DEV_CONSOLE(get, set): Bool;
|
||||
@:pressed("dev-console") public var DEV_CONSOLE_HOLD(get, set): Bool;
|
||||
@:justReleased("dev-console") public var DEV_CONSOLE_R(get, set): Bool;
|
||||
|
||||
@:devModeOnly
|
||||
@:gamepad([])
|
||||
@:justPressed("dev-reload") public var DEV_RELOAD(get, set): Bool;
|
||||
@:pressed("dev-reload") public var DEV_RELOAD_HOLD(get, set): Bool;
|
||||
@:justReleased("dev-reload") public var DEV_RELOAD_R(get, set): Bool;
|
||||
|
||||
@:allow(funkin.backend.utils.ControlsUtil)
|
||||
var byName:Map<String, FlxActionDigital> = [];
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package funkin.backend.system;
|
||||
|
||||
import lime.utils.AssetLibrary as LimeAssetLibrary;
|
||||
import lime.utils.AssetType;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
import lime.app.Application;
|
||||
import funkin.backend.system.macros.GitCommitMacro;
|
||||
import funkin.backend.assets.IModsAssetLibrary;
|
||||
import funkin.backend.assets.ScriptedAssetLibrary;
|
||||
import funkin.backend.system.macros.GitCommitMacro;
|
||||
import lime.app.Application;
|
||||
import lime.utils.AssetLibrary as LimeAssetLibrary;
|
||||
import lime.utils.AssetType;
|
||||
|
||||
/**
|
||||
* A class that reads the `flags.ini` file, allowing to read settable Flags (customs too).
|
||||
@@ -28,10 +27,6 @@ class Flags {
|
||||
public static var REPO_OWNER:String = "CodenameCrew";
|
||||
public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME';
|
||||
|
||||
// make this empty once you guys are done with the project.
|
||||
// good luck /gen <3 @crowplexus
|
||||
public static var RELEASE_CYCLE:String = "Beta";
|
||||
|
||||
/**
|
||||
* Preferred sound extension for the game's audio files.
|
||||
* Currently is set to `mp3` for web targets, and `ogg` for other targets.
|
||||
@@ -41,7 +36,7 @@ class Flags {
|
||||
public static var IMAGE_EXT:String = "png"; // we also support jpg
|
||||
|
||||
public static var DEFAULT_DISCORD_LOGO_KEY:String = "icon";
|
||||
public static var DEFAULT_DISCORD_CLIENT_ID:String = "1027994136193810442";
|
||||
public static var DEFAULT_DISCORD_CLIENT_ID:String = "1383853614589673472";
|
||||
public static var DEFAULT_DISCORD_LOGO_TEXT:String = "Codename Engine";
|
||||
|
||||
@:also(funkin.game.Character.FALLBACK_CHARACTER)
|
||||
@@ -59,23 +54,25 @@ class Flags {
|
||||
public static var WEEKS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override";
|
||||
|
||||
public static var DEFAULT_BPM:Float = 100.0;
|
||||
public static var DEFAULT_BEATS_PER_MEASURE:Float = 4;
|
||||
public static var DEFAULT_BEATS_PER_MEASURE:Int = 4;
|
||||
public static var DEFAULT_STEPS_PER_BEAT:Int = 4;
|
||||
public static var DEFAULT_LOOP_TIME:Float = 0.0;
|
||||
|
||||
public static var SUPPORTED_CHART_RUNTIME_FORMATS:Array<String> = ["Legacy", "Psych Engine"];
|
||||
public static var SUPPORTED_CHART_FORMATS:Array<String> = ["BaseGame"];
|
||||
|
||||
public static var BASEGAME_SONG_METADATA_VERSION:String = "2.2.2";
|
||||
public static var BASEGAME_SONG_CHART_DATA_VERSION:String = "2.0.0";
|
||||
public static var BASEGAME_DEFAULT_NOTE_STYLE:String = 'funkin';
|
||||
public static var BASEGAME_DEFAULT_ALBUM_ID:String = 'volume1';
|
||||
public static var BASEGAME_DEFAULT_PREVIEW_START:Float = 0;
|
||||
public static var BASEGAME_DEFAULT_PREVIEW_END:Float = 15000;
|
||||
public static var VSLICE_SONG_METADATA_VERSION:String = "2.2.2";
|
||||
public static var VSLICE_SONG_CHART_DATA_VERSION:String = "2.0.0";
|
||||
public static var VSLICE_DEFAULT_NOTE_STYLE:String = 'funkin';
|
||||
public static var VSLICE_DEFAULT_ALBUM_ID:String = 'volume1';
|
||||
public static var VSLICE_DEFAULT_PREVIEW_START:Int = 0;
|
||||
public static var VSLICE_DEFAULT_PREVIEW_END:Int = 15000;
|
||||
|
||||
/**
|
||||
* Default background colors for songs without bg color
|
||||
* Default background colors for songs or more without bg color
|
||||
*/
|
||||
public static var DEFAULT_COLOR:FlxColor = 0xFF9271FD;
|
||||
public static var DEFAULT_WEEK_COLOR:FlxColor = 0xFFF9CF51;
|
||||
public static var DEFAULT_COOP_ALLOWED:Bool = false;
|
||||
public static var DEFAULT_OPPONENT_MODE_ALLOWED:Bool = false;
|
||||
|
||||
@@ -97,7 +94,7 @@ class Flags {
|
||||
public static var DEFAULT_CAM_ZOOM_STRENGTH:Int = 1;
|
||||
public static var DEFAULT_CAM_ZOOM:Float = 1.05; // what zoom level it defaults to
|
||||
public static var DEFAULT_HUD_ZOOM:Float = 1.0;
|
||||
public static var MAX_CAMERA_ZOOM:Float = 1.35;
|
||||
public static var MAX_CAMERA_ZOOM_MULT:Float = 1.35;
|
||||
|
||||
public static var DEFAULT_PAUSE_ITEMS:Array<String> = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"];
|
||||
public static var DEFAULT_CUTSCENE_PAUSE_ITEMS:Array<String> = ['Resume Cutscene', 'Skip Cutscene', 'Restart Cutscene', 'Exit to menu'];
|
||||
@@ -157,7 +154,7 @@ class Flags {
|
||||
// -- End of Codename's Default Flags --
|
||||
|
||||
/**
|
||||
* Flags that Codename couldn't recognize as it's own defaults (they can only be `string`!).
|
||||
* Flags that Codename couldn't recognize as it's own defaults (they can only be `string`! due to them being unparsed).
|
||||
*/
|
||||
@:bypass public static var customFlags:Map<String, String> = [];
|
||||
|
||||
@@ -187,10 +184,9 @@ class Flags {
|
||||
}
|
||||
}
|
||||
|
||||
public static function loadFromDatas(files:Array<String>) {
|
||||
public static function loadFromDatas(datas:Array<String>) {
|
||||
var flags:Map<String, String> = [];
|
||||
for(file in files) {
|
||||
var data:String = Assets.getText(file);
|
||||
for(data in datas) {
|
||||
if(data != null)
|
||||
loadFromData(flags, data);
|
||||
}
|
||||
|
||||
@@ -81,12 +81,12 @@ class Main extends Sprite
|
||||
public static var audioDisconnected:Bool = false;
|
||||
|
||||
public static var changeID:Int = 0;
|
||||
public static var pathBack = #if windows
|
||||
public static var pathBack = #if (windows || linux)
|
||||
"../../../../"
|
||||
#elseif mac
|
||||
"../../../../../../../"
|
||||
#else
|
||||
""
|
||||
"../../../../"
|
||||
#end;
|
||||
public static var startedFromSource:Bool = #if TEST_BUILD true #else false #end;
|
||||
|
||||
@@ -180,6 +180,8 @@ class Main extends Sprite
|
||||
}
|
||||
|
||||
public static function refreshAssets() @:privateAccess {
|
||||
FunkinCache.instance.clearSecondLayer();
|
||||
|
||||
var game = FlxG.game;
|
||||
var daSndTray = Type.createInstance(game._customSoundTray = funkin.menus.ui.FunkinSoundTray, []);
|
||||
var index:Int = game.numChildren - 1;
|
||||
@@ -238,9 +240,9 @@ class Main extends Sprite
|
||||
Sys.setCwd(haxe.io.Path.directory(Sys.programPath()));
|
||||
}
|
||||
#elseif android
|
||||
Sys.setCwd(Path.addTrailingSlash(VERSION.SDK_INT > 30 ? Context.getObbDir() : Context.getExternalFilesDir()));
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(VERSION.SDK_INT > 30 ? Context.getObbDir() : Context.getExternalFilesDir()));
|
||||
#elseif (ios || switch)
|
||||
Sys.setCwd(Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath));
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath));
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,21 @@ import sys.FileSystem;
|
||||
import flixel.FlxState;
|
||||
import funkin.backend.assets.ModsFolder;
|
||||
import funkin.backend.chart.EventsData;
|
||||
import funkin.menus.BetaWarningState;
|
||||
import funkin.menus.TitleState;
|
||||
import funkin.backend.system.framerate.Framerate;
|
||||
import funkin.menus.TitleState;
|
||||
import haxe.io.Path;
|
||||
|
||||
@dox(hide)
|
||||
typedef AddonInfo = {
|
||||
var name:String;
|
||||
var path:String;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple state used for loading the game
|
||||
*/
|
||||
class MainState extends FlxState {
|
||||
public static var initiated:Bool = false;
|
||||
public static var betaWarningShown:Bool = false;
|
||||
public override function create() {
|
||||
super.create();
|
||||
if (!initiated) {
|
||||
@@ -36,25 +41,64 @@ class MainState extends FlxState {
|
||||
Paths.assetsTree.reset();
|
||||
|
||||
#if MOD_SUPPORT
|
||||
var _lowPriorityAddons:Array<String> = [];
|
||||
var _highPriorityAddons:Array<String> = [];
|
||||
var _noPriorityAddons:Array<String> = [];
|
||||
if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)) {
|
||||
for(i=>addon in [for(dir in FileSystem.readDirectory(ModsFolder.addonsPath)) if (FileSystem.isDirectory(ModsFolder.addonsPath + dir)) dir]) {
|
||||
if (addon.startsWith("[LOW]")) _lowPriorityAddons.insert(0, addon);
|
||||
else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, addon);
|
||||
else _noPriorityAddons.insert(0, addon);
|
||||
}
|
||||
for (addon in _lowPriorityAddons)
|
||||
Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.addonsPath + addon, StringTools.ltrim(addon.substr("[LOW]".length))));
|
||||
}
|
||||
if (ModsFolder.currentModFolder != null)
|
||||
Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder));
|
||||
inline function isDirectory(path:String):Bool
|
||||
return FileSystem.exists(path) && FileSystem.isDirectory(path);
|
||||
|
||||
if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)){
|
||||
for (addon in _noPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.addonsPath + addon, addon));
|
||||
for (addon in _highPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.addonsPath + addon, StringTools.ltrim(addon.substr("[HIGH]".length))));
|
||||
inline function ltrim(str:String, prefix:String):String
|
||||
return str.substr(prefix.length).ltrim();
|
||||
|
||||
inline function loadLib(path:String, name:String)
|
||||
Paths.assetsTree.addLibrary(ModsFolder.loadModLib(path, name));
|
||||
|
||||
var _lowPriorityAddons:Array<AddonInfo> = [];
|
||||
var _highPriorityAddons:Array<AddonInfo> = [];
|
||||
var _noPriorityAddons:Array<AddonInfo> = [];
|
||||
|
||||
var addonPaths = [
|
||||
ModsFolder.addonsPath,
|
||||
(
|
||||
ModsFolder.currentModFolder != null ?
|
||||
ModsFolder.modsPath + ModsFolder.currentModFolder + "/addons/" :
|
||||
null
|
||||
)
|
||||
];
|
||||
|
||||
for(path in addonPaths) {
|
||||
if (path == null) continue;
|
||||
if (!isDirectory(path)) continue;
|
||||
|
||||
for(addon in FileSystem.readDirectory(path)) {
|
||||
if(!FileSystem.isDirectory(path + addon)) {
|
||||
switch(Path.extension(addon).toLowerCase()) {
|
||||
case 'zip':
|
||||
addon = Path.withoutExtension(addon);
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var data:AddonInfo = {
|
||||
name: addon,
|
||||
path: path + addon
|
||||
};
|
||||
|
||||
if (addon.startsWith("[LOW]")) _lowPriorityAddons.insert(0, data);
|
||||
else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, data);
|
||||
else _noPriorityAddons.insert(0, data);
|
||||
}
|
||||
}
|
||||
|
||||
for (addon in _lowPriorityAddons)
|
||||
loadLib(addon.path, ltrim(addon.name, "[LOW]"));
|
||||
|
||||
if (ModsFolder.currentModFolder != null)
|
||||
loadLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder);
|
||||
|
||||
for (addon in _noPriorityAddons)
|
||||
loadLib(addon.path, addon.name);
|
||||
|
||||
for (addon in _highPriorityAddons)
|
||||
loadLib(addon.path, ltrim(addon.name, "[HIGH]"));
|
||||
#end
|
||||
|
||||
Flags.load();
|
||||
@@ -67,14 +111,10 @@ class MainState extends FlxState {
|
||||
ControlsUtil.loadCustomControls();
|
||||
TitleState.initialized = false;
|
||||
|
||||
if(Framerate.isLoaded)
|
||||
if (Framerate.isLoaded)
|
||||
Framerate.instance.reload();
|
||||
|
||||
if (Flags.DISABLE_BETA_WARNING_SCREEN || betaWarningShown)
|
||||
FlxG.switchState(new TitleState());
|
||||
else
|
||||
FlxG.switchState(new BetaWarningState());
|
||||
betaWarningShown = true;
|
||||
FlxG.switchState(new TitleState());
|
||||
|
||||
#if sys
|
||||
CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN);
|
||||
|
||||
@@ -13,5 +13,5 @@ class CodenameBuildField extends TextField {
|
||||
}
|
||||
|
||||
public function reload()
|
||||
text = 'Codename Engine ${Flags.RELEASE_CYCLE}\n${Flags.COMMIT_MESSAGE}';
|
||||
text = '${Flags.VERSION_MESSAGE}\n${Flags.COMMIT_MESSAGE}';
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ class FlixelInfo extends FramerateCategory {
|
||||
|
||||
|
||||
@:privateAccess {
|
||||
var c:Int = 0;
|
||||
for(_ in FlxG.bitmap._cache.keys())
|
||||
c++;
|
||||
var c:Int = Lambda.count(FlxG.bitmap._cache);
|
||||
|
||||
if((FlxG.state is ModState)) {
|
||||
var state:ModState = cast FlxG.state;
|
||||
@@ -27,6 +25,7 @@ class FlixelInfo extends FramerateCategory {
|
||||
_text += '\nBitmaps Count: ${c}';
|
||||
_text += '\nSounds Count: ${FlxG.sound.list.length}';
|
||||
_text += '\nFlxG.game Childs Count: ${FlxG.game.numChildren}';
|
||||
if(FlxG.renderBlit) _text += '\nBlitting Render: true';
|
||||
// _text += '\nCached objects count: ${cachedObjects}';
|
||||
#if FLX_POINT_POOL
|
||||
//var points = flixel.math.FlxPoint.FlxBasePoint.pool;
|
||||
|
||||
@@ -20,7 +20,7 @@ class Framerate extends Sprite {
|
||||
public static var codenameBuildField:CodenameBuildField;
|
||||
#end
|
||||
|
||||
public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_sans" #end;
|
||||
public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_typewriter" #end;
|
||||
|
||||
/**
|
||||
* 0: FPS INVISIBLE
|
||||
@@ -46,7 +46,7 @@ class Framerate extends Sprite {
|
||||
super();
|
||||
if (instance != null) throw "Cannot create another instance";
|
||||
instance = this;
|
||||
textFormat = new TextFormat("Consolas", 12, -1);
|
||||
textFormat = new TextFormat(fontName, 12, -1);
|
||||
|
||||
isLoaded = true;
|
||||
|
||||
|
||||
@@ -53,6 +53,25 @@ class SystemInfo extends FramerateCategory {
|
||||
if (osName != "")
|
||||
osInfo = '${osName} ${osVersion}'.trim();
|
||||
}
|
||||
#elseif windows
|
||||
var windowsCurrentVersionPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
|
||||
var buildNumber = Std.parseInt(RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "CurrentBuildNumber"));
|
||||
var edition = RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "ProductName");
|
||||
|
||||
var lcuKey = "WinREVersion"; // Last Cumulative Update Key On Older Windows Versions
|
||||
if (buildNumber >= 22000) { // Windows 11 Initial Release Build Number
|
||||
edition = edition.replace("Windows 10", "Windows 11");
|
||||
lcuKey = "LCUVer"; // Last Cumulative Update Key On Windows 11
|
||||
}
|
||||
|
||||
var lcuVersion = RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, lcuKey);
|
||||
|
||||
osInfo = edition;
|
||||
|
||||
if (lcuVersion != null && lcuVersion != "")
|
||||
osInfo += ' ${lcuVersion}';
|
||||
else if (lime.system.System.platformVersion != null && lime.system.System.platformVersion != "")
|
||||
osInfo += ' ${lime.system.System.platformVersion}';
|
||||
#else
|
||||
if (lime.system.System.platformLabel != null && lime.system.System.platformLabel != "" && lime.system.System.platformVersion != null && lime.system.System.platformVersion != "")
|
||||
osInfo = '${lime.system.System.platformLabel.replace(lime.system.System.platformVersion, "").trim()} ${lime.system.System.platformVersion}';
|
||||
@@ -62,10 +81,7 @@ class SystemInfo extends FramerateCategory {
|
||||
|
||||
try {
|
||||
#if windows
|
||||
var process = new HiddenProcess("wmic", ["cpu", "get", "name"]);
|
||||
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
|
||||
|
||||
cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim();
|
||||
cpuName = RegistryUtil.get(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString");
|
||||
#elseif mac
|
||||
var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isn't able to use the args but it still works
|
||||
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
|
||||
@@ -86,7 +102,7 @@ class SystemInfo extends FramerateCategory {
|
||||
Logs.trace('Unable to grab CPU Name: $e', ERROR, RED);
|
||||
}
|
||||
|
||||
@:privateAccess {
|
||||
@:privateAccess if(FlxG.renderTile) { // Blit doesn't enable the gpu. Idk if we should fix this
|
||||
if (flixel.FlxG.stage.context3D != null && flixel.FlxG.stage.context3D.gl != null) {
|
||||
gpuName = Std.string(flixel.FlxG.stage.context3D.gl.getParameter(flixel.FlxG.stage.context3D.gl.RENDERER)).split("/")[0].trim();
|
||||
#if !flash
|
||||
|
||||
@@ -7,6 +7,8 @@ class BuildInfo {
|
||||
public static function printBuildInfo() {
|
||||
if(haxe.macro.Context.defined("display")) return;
|
||||
var haxeVersion = haxe.macro.Context.definedValue("haxe").trim();
|
||||
var sleepChangeVersion = false;
|
||||
|
||||
Sys.println('[ BUILD INFO ]');
|
||||
Sys.println('Haxe Version: ${haxeVersion}');
|
||||
try {
|
||||
@@ -33,8 +35,10 @@ class BuildInfo {
|
||||
}
|
||||
}
|
||||
|
||||
if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion)
|
||||
if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion) {
|
||||
Sys.println('Last Built With Haxe: ${lastBuiltWith} [!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!]');
|
||||
sleepChangeVersion = true;
|
||||
}
|
||||
} catch(e) {}
|
||||
var targetPlatform = #if html5 "Web (HTML5)"
|
||||
#elseif ios "iOS"
|
||||
@@ -51,6 +55,14 @@ class BuildInfo {
|
||||
Sys.println('Target Platform: ' + targetPlatform);
|
||||
Sys.println('Build Date: ${Date.now().toString()}');
|
||||
Sys.println('');
|
||||
|
||||
if(sleepChangeVersion) {
|
||||
Sys.println('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
||||
Sys.println('!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!');
|
||||
Sys.println('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
||||
Sys.println('');
|
||||
Sys.sleep(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
@@ -14,6 +14,7 @@ class ControlsMacro
|
||||
{
|
||||
static var _allControls: Array<String> = null;
|
||||
static var _allInternalControls: Array<String> = null;
|
||||
static var _allDevModeOnlyControls: Array<String> = null;
|
||||
static var _currentControls: Map<String, Array<Expr>> = null;
|
||||
static var _currentGamepadControls: Map<String, Expr> = null;
|
||||
static var _keySet: Map<String, String> = null;
|
||||
@@ -29,6 +30,7 @@ class ControlsMacro
|
||||
|
||||
_allControls = [];
|
||||
_allInternalControls = [];
|
||||
_allDevModeOnlyControls = [];
|
||||
_currentControls = [];
|
||||
_currentGamepadControls = [];
|
||||
_keySet = [];
|
||||
@@ -154,6 +156,7 @@ class ControlsMacro
|
||||
|
||||
_allControls = null;
|
||||
_allInternalControls = null;
|
||||
_allDevModeOnlyControls = null;
|
||||
_currentControls = null;
|
||||
_currentGamepadControls = null;
|
||||
_keySet = null;
|
||||
@@ -289,6 +292,9 @@ class ControlsMacro
|
||||
case ":justReleased":
|
||||
keyset = extractString(meta.params[0]);
|
||||
expr = macro func($i{internalName}, JUST_RELEASED);
|
||||
case ":devModeOnly":
|
||||
if (!_allDevModeOnlyControls.contains(shortName))
|
||||
_allDevModeOnlyControls.push(shortName);
|
||||
default:
|
||||
shouldRemove = false;
|
||||
}
|
||||
@@ -326,17 +332,16 @@ class ControlsMacro
|
||||
|
||||
// Generated Code:
|
||||
// inline function get_UI_UP(): Bool
|
||||
// return _uiUp.check();
|
||||
// return _uiUp.check(); or return Options.devMode && _uiUp.check(); depending if its dev mode
|
||||
var getField: Field = {
|
||||
name: "get_" + name,
|
||||
access: [APrivate, AInline],
|
||||
kind: FFun({
|
||||
ret: macro : Bool,
|
||||
params: [],
|
||||
expr: macro
|
||||
{
|
||||
return $i{internalName}.check();
|
||||
},
|
||||
expr: _allDevModeOnlyControls.contains(shortName) ?
|
||||
(macro return Options.devMode && $i{internalName}.check()) :
|
||||
(macro return $i{internalName}.check()),
|
||||
args: []
|
||||
}),
|
||||
pos: field.pos,
|
||||
|
||||
@@ -18,7 +18,7 @@ class Socket implements IFlxDestroyable {
|
||||
|
||||
public function read():String {
|
||||
try {
|
||||
return this.socket.input.readUntil(('\n').charCodeAt(0)).replace("\\n", "\n");
|
||||
return this.socket.input.readUntil('\n'.code).replace("\\n", "\n");
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
@@ -21,21 +21,21 @@ class AudioAnalyzer {
|
||||
}
|
||||
|
||||
public function analyze(startPos:Float, endPos:Float):Float {
|
||||
var bitsPerSample = buffer.bitsPerSample;
|
||||
var multi = __timeMulti / 4000 / bitsPerSample;
|
||||
var bytesStartPos:Int = Math.floor(startPos * multi) * bitsPerSample;
|
||||
var bytesEndPos:Int = Math.floor(endPos * multi) * bitsPerSample;
|
||||
var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
|
||||
var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
|
||||
|
||||
bytesStartPos -= bytesStartPos % bitsPerSample;
|
||||
bytesEndPos -= bytesEndPos % bitsPerSample;
|
||||
|
||||
var dataBuffer = buffer.data.buffer;
|
||||
bytesStartPos -= bytesStartPos % buffer.bitsPerSample;
|
||||
bytesEndPos -= bytesEndPos % buffer.bitsPerSample;
|
||||
|
||||
var maxByte:Int = 0;
|
||||
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / bitsPerSample)) {
|
||||
var bytePos = bytesStartPos + (i * bitsPerSample);
|
||||
// What if bitsPerSample is 8? or 32? or 64? -Ne_Eo
|
||||
var byte:Int = dataBuffer.get(bytePos) | (dataBuffer.get(bytePos + 1) << 8);
|
||||
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) {
|
||||
#if js
|
||||
var byte:Int = buffer.data[bytesStartPos + (i * buffer.bitsPerSample)]
|
||||
| (buffer.data[bytesStartPos + (i * buffer.bitsPerSample) + 1] << 8);
|
||||
#else
|
||||
var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample))
|
||||
| (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8);
|
||||
#end
|
||||
if (byte > 256 * 128) byte -= 256 * 256;
|
||||
if (maxByte < byte) maxByte = byte;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ final class CoolUtil
|
||||
if(result.isNothing) Logs.trace('The file where it has been tried to get the attributes from, might be corrupted or inexistent (code: ${result.getValue()})', WARNING);
|
||||
return result;
|
||||
#else
|
||||
return 0;
|
||||
return new FileAttributeWrapper(0);
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ final class CoolUtil
|
||||
addMissingFolders(Path.directory(path));
|
||||
|
||||
var result = NativeAPI.addFileAttributes(path, attrib, useAbsolute);
|
||||
if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
|
||||
if (result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
|
||||
return result;
|
||||
#else
|
||||
return 0;
|
||||
@@ -453,9 +453,13 @@ final class CoolUtil
|
||||
if (Assets.exists(infoPath)) {
|
||||
var musicInfo = IniUtil.parseAsset(infoPath, [
|
||||
"BPM" => null,
|
||||
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT
|
||||
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT,
|
||||
"LoopTime" => '${Flags.DEFAULT_LOOP_TIME}'
|
||||
]);
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
FlxG.sound.music.loopTime = Std.parseFloat(musicInfo["LoopTime"]) * 1000;
|
||||
|
||||
var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)];
|
||||
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
|
||||
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
|
||||
@@ -712,11 +716,11 @@ final class CoolUtil
|
||||
* @param spr Sprite to load the graphic for
|
||||
* @param path Path to the graphic
|
||||
*/
|
||||
public static function loadAnimatedGraphic(spr:FlxSprite, path:String) {
|
||||
public static function loadAnimatedGraphic(spr:FlxSprite, path:String, fps:Float = 24.0) {
|
||||
spr.frames = Paths.getFrames(path, true);
|
||||
|
||||
if (spr.frames != null && spr.frames.frames != null) {
|
||||
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true);
|
||||
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], fps, true);
|
||||
spr.animation.play("idle");
|
||||
}
|
||||
|
||||
@@ -915,8 +919,8 @@ final class CoolUtil
|
||||
* @param mainEase Main ease
|
||||
* @param suffix Suffix (Ignored if `mainEase` is `linear`)
|
||||
*/
|
||||
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
|
||||
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
|
||||
@:noUsing public static inline function flxeaseFromString(mainEase:String, ?suffix:String)
|
||||
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" || suffix == null ? "" : suffix));
|
||||
|
||||
/*
|
||||
* Returns the filename of a path, without the extension.
|
||||
@@ -1323,6 +1327,124 @@ final class CoolUtil
|
||||
public static inline function isMapEmpty<K, V>(map: Map<K, V>): Bool {
|
||||
return !map.keys().hasNext();
|
||||
}
|
||||
|
||||
public inline static function parsePropertyString(fieldPath:String):Array<OneOfTwo<String, Int>> {
|
||||
return FlxTween.parseFieldString(fieldPath);
|
||||
}
|
||||
|
||||
public static function stringifyFieldsPath(fields:Array<OneOfTwo<String, Int>>):String {
|
||||
var str = new StringBuf();
|
||||
var first = true;
|
||||
for (field in fields) {
|
||||
if (Type.typeof(field) == TInt) {
|
||||
str.add('[${field}]');
|
||||
} else {
|
||||
if (!first)
|
||||
str.add('.');
|
||||
str.add(field);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static function parseProperty(target:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>):Dynamic {
|
||||
var fields:Array<OneOfTwo<String, Int>> = {
|
||||
if((fields is String)) CoolUtil.parsePropertyString(fields);
|
||||
else fields;
|
||||
}
|
||||
|
||||
var field = CoolUtil.last(fields);
|
||||
for (i in 0...fields.length - 1) {
|
||||
var component = fields[i];
|
||||
if (Type.typeof(component) == TInt) {
|
||||
if ((target is Array)) {
|
||||
var index:Int = cast component;
|
||||
var arr:Array<Dynamic> = cast target;
|
||||
target = arr[index];
|
||||
}
|
||||
} else { // TClass(String)
|
||||
target = Reflect.getProperty(target, component);
|
||||
}
|
||||
if (!Reflect.isObject(target) && !(target is Array))
|
||||
throw 'The object does not have the property "$component" in "${stringifyFieldsPath(fields)}"';
|
||||
}
|
||||
return new PropertyInfo(target, field);
|
||||
}
|
||||
|
||||
public static function cloneProperty(toTarget:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>, fromTarget:Dynamic):Dynamic {
|
||||
var fields:Array<OneOfTwo<String, Int>> = {
|
||||
if((fields is String)) CoolUtil.parsePropertyString(fields);
|
||||
else fields;
|
||||
}
|
||||
|
||||
var toProperty = CoolUtil.parseProperty(toTarget, fields);
|
||||
var fromProperty = CoolUtil.parseProperty(fromTarget, fields);
|
||||
|
||||
return toProperty.setValue(fromProperty.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyInfo {
|
||||
public var object:Dynamic;
|
||||
public var field:OneOfTwo<String, Int>;
|
||||
public var typeOfField:Type.ValueType;
|
||||
#if hscript_improved
|
||||
public var isCustom:Bool = false;
|
||||
public var custom:hscript.IHScriptCustomBehaviour;
|
||||
#end
|
||||
|
||||
public function new(object:Dynamic, field:OneOfTwo<String, Int>) {
|
||||
this.object = object;
|
||||
this.field = field;
|
||||
#if hscript_improved
|
||||
if (object is hscript.IHScriptCustomBehaviour)
|
||||
{
|
||||
isCustom = true;
|
||||
custom = cast object;
|
||||
}
|
||||
#end
|
||||
|
||||
typeOfField = Type.typeof(field);
|
||||
}
|
||||
|
||||
public function getValue():Dynamic
|
||||
{
|
||||
if (typeOfField == TInt)
|
||||
{
|
||||
var index:Int = cast field;
|
||||
var arr:Array<Dynamic> = cast object;
|
||||
return arr[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hscript_improved
|
||||
if (isCustom)
|
||||
return custom.hget(field);
|
||||
else
|
||||
#end
|
||||
return Reflect.getProperty(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
public function setValue(value:Dynamic):Void
|
||||
{
|
||||
if (typeOfField == TInt)
|
||||
{
|
||||
var index:Int = cast field;
|
||||
var arr:Array<Dynamic> = cast object;
|
||||
arr[index] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hscript_improved
|
||||
if (isCustom)
|
||||
custom.hset(field, value);
|
||||
else
|
||||
#end
|
||||
Reflect.setProperty(object, field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -127,7 +127,7 @@ final class MemoryUtil {
|
||||
public static function getMemType():String {
|
||||
#if windows
|
||||
var memoryMap:Map<Int, String> = [
|
||||
0 => "Unknown",
|
||||
0 => null,
|
||||
1 => "Other",
|
||||
2 => "DRAM",
|
||||
3 => "Synchronous DRAM",
|
||||
@@ -166,9 +166,9 @@ final class MemoryUtil {
|
||||
];
|
||||
var memoryOutput:Int = -1;
|
||||
|
||||
var process = new HiddenProcess("wmic", ["memorychip", "get", "SMBIOSMemoryType"]);
|
||||
var process = new HiddenProcess("powershell", ["-Command", "Get-CimInstance Win32_PhysicalMemory | Select-Object -ExpandProperty SMBIOSMemoryType" ]);
|
||||
if (process.exitCode() == 0) memoryOutput = Std.int(Std.parseFloat(process.stdout.readAll().toString().trim().split("\n")[1]));
|
||||
if (memoryOutput != -1) return memoryMap[memoryOutput];
|
||||
if (memoryOutput != -1) return memoryMap[memoryOutput] == null ? 'Unknown ($memoryOutput)' : memoryMap[memoryOutput];
|
||||
#elseif mac
|
||||
var process = new HiddenProcess("system_profiler", ["SPMemoryDataType"]);
|
||||
var reg = ~/Type: (.+)/;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
enum abstract RegistryHive(Int) {
|
||||
var HKEY_CLASSES_ROOT = 0x80000000;
|
||||
var HKEY_CURRENT_USER = 0x80000001;
|
||||
var HKEY_LOCAL_MACHINE = 0x80000002;
|
||||
var HKEY_USERS = 0x80000003;
|
||||
var HKEY_CURRENT_CONFIG = 0x80000005;
|
||||
}
|
||||
#if windows
|
||||
@:cppFileCode('
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
')
|
||||
#end
|
||||
class RegistryUtil {
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
DWORD dataSize = 0;
|
||||
DWORD dataType = 0;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) return null();
|
||||
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, &dataSize);
|
||||
if (result != ERROR_SUCCESS || dataSize == 0) {
|
||||
RegCloseKey(hKey);
|
||||
return null();
|
||||
}
|
||||
|
||||
std::vector<wchar_t> buffer(dataSize / sizeof(wchar_t));
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, NULL, (LPBYTE)buffer.data(), &dataSize);
|
||||
RegCloseKey(hKey);
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
return ::String(buffer.data());
|
||||
}
|
||||
return null();
|
||||
')
|
||||
#end
|
||||
public static function get(hive:RegistryHive, key:String, string:String):Null<String>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
std::wstring data = std::wstring(value.wchar_str());
|
||||
|
||||
result = RegCreateKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
result = RegSetValueExW(hKey, valname.c_str(), 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(wchar_t)));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function set(hive:RegistryHive, key:String, string:String, value:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
DWORD dataType = 0;
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, NULL);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function exists(hive:RegistryHive, key:String, string:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_SET_VALUE, &hKey);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
result = RegDeleteValueW(hKey, valname.c_str());
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function delete(hive:RegistryHive, key:String, string:String):Bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -114,12 +114,13 @@ final class XMLUtil {
|
||||
* @param parentFolder The parent folder
|
||||
* @param defaultAnimType The default animation type
|
||||
*/
|
||||
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT):FunkinSprite {
|
||||
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, loadGraphic:Bool = true):FunkinSprite {
|
||||
if (parentFolder == null) parentFolder = "";
|
||||
|
||||
spr.name = node.getAtt("name");
|
||||
spr.antialiasing = true;
|
||||
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
|
||||
if (loadGraphic)
|
||||
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
|
||||
|
||||
spr.spriteAnimType = defaultAnimType;
|
||||
if (node.has.type) {
|
||||
@@ -239,10 +240,10 @@ final class XMLUtil {
|
||||
* @param cl The class to create (advanced)
|
||||
* @param args The arguments to pass to the class (advanced)
|
||||
*/
|
||||
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>):FunkinSprite {
|
||||
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>, loadGraphic:Bool = true):FunkinSprite {
|
||||
if(cl == null) cl = FunkinSprite;
|
||||
if(args == null) args = [];
|
||||
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType);
|
||||
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType, loadGraphic);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import haxe.zip.Entry;
|
||||
import haxe.zip.Reader;
|
||||
|
||||
#if sys
|
||||
#if (!macro && sys)
|
||||
import openfl.display.BitmapData;
|
||||
@@ -9,8 +12,6 @@ import haxe.Exception;
|
||||
import haxe.Json;
|
||||
import haxe.crypto.Crc32;
|
||||
import haxe.zip.Compress;
|
||||
import haxe.zip.Entry;
|
||||
import haxe.zip.Reader;
|
||||
import haxe.zip.Tools;
|
||||
import haxe.zip.Uncompress;
|
||||
import haxe.zip.Writer;
|
||||
@@ -135,12 +136,12 @@ final class ZipUtil {
|
||||
if (prog == null) prog = new ZipProgress();
|
||||
|
||||
try {
|
||||
var curPath:Array<String> = ['$path'];
|
||||
var curPath:Array<String> = [path];
|
||||
var destPath:Array<String> = [];
|
||||
if (prefix != "") {
|
||||
prefix = prefix.replace("\\", "/");
|
||||
while(prefix.charAt(0) == "/") prefix = prefix.substr(1);
|
||||
while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1);
|
||||
while(prefix.charCodeAt(0) == "/".code) prefix = prefix.substr(1);
|
||||
while(prefix.charCodeAt(prefix.length-1) == "/".code) prefix = prefix.substr(0, prefix.length-1);
|
||||
destPath.push(prefix);
|
||||
}
|
||||
|
||||
@@ -160,7 +161,7 @@ final class ZipUtil {
|
||||
} else {
|
||||
// is file, put it in the list
|
||||
var zipPath = '$zipPath/$e';
|
||||
while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1);
|
||||
while(zipPath.charCodeAt(0) == "/".code) zipPath = zipPath.substr(1);
|
||||
files.push(new StrNameLabel('$path/$e', zipPath));
|
||||
}
|
||||
}
|
||||
@@ -227,20 +228,6 @@ class ZipProgress {
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
class ZipReader extends Reader {
|
||||
public var files:List<Entry>;
|
||||
|
||||
public override function read() {
|
||||
if (files != null) return files;
|
||||
try {
|
||||
var files = super.read();
|
||||
return this.files = files;
|
||||
} catch(e) {
|
||||
}
|
||||
return new List<Entry>();
|
||||
}
|
||||
}
|
||||
|
||||
class ZipWriter extends Writer {
|
||||
public function flush() {
|
||||
o.flush();
|
||||
@@ -265,4 +252,18 @@ class StrNameLabel {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
class ZipReader extends Reader {
|
||||
public var files:List<Entry>;
|
||||
|
||||
public override function read() {
|
||||
if (files != null) return files;
|
||||
try {
|
||||
var files = super.read();
|
||||
return this.files = files;
|
||||
} catch(e) {
|
||||
}
|
||||
return new List<Entry>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package funkin.backend.week;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.backend.week.WeekData.WeekCharacter;
|
||||
import funkin.backend.week.WeekData;
|
||||
import haxe.xml.Access;
|
||||
|
||||
class Week {
|
||||
public static function loadWeek(weekName:String, loadCharactersData:Bool = true):WeekData {
|
||||
var week:Access = null;
|
||||
try week = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/weeks/$weekName'))).firstElement())
|
||||
catch(e) Logs.trace('Cannot parse week "$weekName.xml": ${Std.string(e)}', ERROR);
|
||||
|
||||
if (week == null) return null;
|
||||
if (!week.has.name) {
|
||||
Logs.trace('Week "${weekName}" has no name attribute', WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
var weekObj:WeekData = {
|
||||
xml: week,
|
||||
name: week.att.name,
|
||||
id: weekName,
|
||||
sprite: week.getAtt('sprite').getDefault(weekName),
|
||||
chars: [null, null, null],
|
||||
songs: [],
|
||||
difficulties: ['easy', 'normal', 'hard'],
|
||||
bgColor: week.has.bgColor ? FlxColor.fromString(week.getAtt("bgColor")) : Flags.DEFAULT_WEEK_COLOR
|
||||
};
|
||||
|
||||
var weekName = weekObj.name;
|
||||
for (k => song in week.nodes.song) {
|
||||
if (song == null) continue;
|
||||
try {
|
||||
var name = song.innerData.trim();
|
||||
if (name == "") {
|
||||
Logs.trace('Song at index ${k} in week $weekName has no name. Skipping...', WARNING);
|
||||
continue;
|
||||
}
|
||||
weekObj.songs.push({
|
||||
name: name,
|
||||
hide: song.getAtt('hide').getDefault('false') == "true",
|
||||
displayName: song.getAtt('displayName')
|
||||
});
|
||||
} catch(e) {
|
||||
Logs.trace('Song at index ${k} in week $weekName cannot contain any other XML nodes in its name.', WARNING);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (weekObj.songs.length <= 0) {
|
||||
Logs.trace('Week $weekName has no songs.', WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
var diffNodes = week.nodes.difficulty;
|
||||
if (diffNodes.length > 0) {
|
||||
var diffs:Array<String> = [for (e in diffNodes) if (e.has.name) e.att.name];
|
||||
if (diffs.length > 0) weekObj.difficulties = diffs;
|
||||
}
|
||||
|
||||
if (week.has.chars) for (k => e in week.att.chars.split(",")) {
|
||||
var trim = e.trim();
|
||||
weekObj.chars[k] = (trim == "" || e == "none" || e == "null") ? null : (loadCharactersData ? loadWeekCharacter(trim) : {name: trim});
|
||||
}
|
||||
|
||||
return weekObj;
|
||||
}
|
||||
|
||||
public static function loadWeekCharacter(charName:String):WeekCharacter {
|
||||
var char:Access = null;
|
||||
try char = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/characters/$charName'))).firstElement())
|
||||
catch(e) Logs.trace('Cannot parse character "$charName.xml": ${Std.string(e)}', ERROR);
|
||||
if (char == null) return null;
|
||||
|
||||
if (!char.has.name) char.x.set("name", charName);
|
||||
if (!char.has.sprite) char.x.set("sprite", 'menus/storymenu/characters/${char.att.name}');
|
||||
if (!char.has.updateHitbox) char.x.set("updateHitbox", "true");
|
||||
|
||||
return {
|
||||
xml: char,
|
||||
name: char.att.name,
|
||||
spritePath: char.att.sprite,
|
||||
scale: Std.parseFloat(char.getAtt('scale')).getDefault(1),
|
||||
offset: FlxPoint.get(
|
||||
Std.parseFloat(char.getAtt('x')).getDefault(0),
|
||||
Std.parseFloat(char.getAtt('y')).getDefault(0)
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package funkin.backend.week;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import haxe.xml.Access;
|
||||
|
||||
typedef WeekData = {
|
||||
var ?xml:Access;
|
||||
var name:String; // name SHOULD NOT be used for loading week highscores, its just the name on the right side of the week, remember that next time!! - Nex
|
||||
var id:String; // id IS instead for saving and loading!! - Nex
|
||||
var sprite:String;
|
||||
var chars:Array<WeekCharacter>;
|
||||
var songs:Array<WeekSong>;
|
||||
var difficulties:Array<String>;
|
||||
var bgColor:FlxColor;
|
||||
}
|
||||
|
||||
typedef WeekCharacter = {
|
||||
var ?xml:Access;
|
||||
var name:String;
|
||||
var ?spritePath:String;
|
||||
var ?scale:Float;
|
||||
var ?offset:FlxPoint;
|
||||
// var frames:FlxFramesCollection;
|
||||
}
|
||||
|
||||
typedef WeekSong = {
|
||||
var name:String;
|
||||
var hide:Bool;
|
||||
var ?displayName:String;
|
||||
}
|
||||
Reference in New Issue
Block a user