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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user