Skip to content

Commit eada310

Browse files
committed
psych 1.0 format + some charts improvements
Fixes #448
1 parent 85617b7 commit eada310

File tree

6 files changed

+111
-47
lines changed

6 files changed

+111
-47
lines changed

source/funkin/backend/FunkinSprite.hx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ enum abstract XMLAnimType(Int)
2222
var BEAT = 1;
2323
var LOOP = 2;
2424

25-
public static function fromString(str:String, def:XMLAnimType = NONE)
25+
public static function fromString(str:String, def:XMLAnimType = XMLAnimType.NONE)
2626
{
27-
return switch (str.trim().toLowerCase())
27+
return switch (StringTools.trim(str).toLowerCase())
2828
{
2929
case "none": NONE;
3030
case "beat" | "onbeat": BEAT;

source/funkin/backend/chart/Chart.hx

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package funkin.backend.chart;
22

3-
import funkin.backend.system.Conductor;
43
import funkin.backend.chart.ChartData;
54
import flixel.util.FlxColor;
65
import haxe.io.Path;
@@ -13,12 +12,68 @@ import sys.FileSystem;
1312

1413
using StringTools;
1514

15+
enum abstract ChartFormat(Int) {
16+
var CODENAME = 0;
17+
var LEGACY = 1; // also used by many other engines (old Psych, Kade and more) - Nex
18+
var VSLICE = 2;
19+
var PSYCH_NEW = 3;
20+
21+
@:to public function toString():String {
22+
return switch(cast (this, ChartFormat)) {
23+
case CODENAME: "CODENAME";
24+
case LEGACY: "LEGACY";
25+
case VSLICE: "VSLICE";
26+
case PSYCH_NEW: "PSYCH_NEW";
27+
}
28+
}
29+
30+
public static function fromString(str:String, def:ChartFormat = ChartFormat.LEGACY) {
31+
str = str.toLowerCase();
32+
str = StringTools.replace(str, " ", "");
33+
str = StringTools.replace(str, "_", "");
34+
str = StringTools.replace(str, ".", "");
35+
36+
if(StringTools.startsWith(str, "psychv1") || StringTools.startsWith(str, "psych1"))
37+
return PSYCH_NEW;
38+
39+
return switch(str) {
40+
case "codename" | "codenameengine": CODENAME;
41+
case "newpsych" | "psychnew": PSYCH_NEW;
42+
default: def;
43+
}
44+
}
45+
}
46+
1647
class Chart {
1748
/**
1849
* Default background colors for songs without bg color
1950
*/
2051
public inline static var defaultColor:FlxColor = 0xFF9271FD;
2152

53+
public static function cleanSongData(data:Dynamic):Dynamic {
54+
if (Reflect.hasField(data, "song")) {
55+
var field:Dynamic = Reflect.field(data, "song");
56+
if (field != null && Type.typeof(field) == TObject) // Cant use Reflect.isObject, because it detects strings for some reason
57+
return field;
58+
}
59+
return data;
60+
}
61+
62+
public static function detectChartFormat(data:Dynamic):ChartFormat {
63+
var __temp:Dynamic; // imma reuse this var so the program doesnt have to get values multiple times - Nex
64+
65+
if ((__temp = data.codenameChart) == true || __temp == "true")
66+
return CODENAME;
67+
68+
if (Reflect.hasField(data, "version") && Reflect.hasField(data, "scrollSpeed"))
69+
return VSLICE;
70+
71+
if ((__temp = cleanSongData(data).format) != null && __temp is String && StringTools.startsWith(__temp, "psych_v1"))
72+
return PSYCH_NEW;
73+
74+
return LEGACY;
75+
}
76+
2277
public static function loadEventsJson(songName:String) {
2378
var path = Paths.file('songs/${songName.toLowerCase()}/events.json');
2479
var data:Array<ChartEvent> = null;
@@ -33,8 +88,9 @@ class Chart {
3388
}
3489

3590
public static function loadChartMeta(songName:String, difficulty:String = "normal", fromMods:Bool = true) {
36-
var metaPath = Paths.file('songs/${songName.toLowerCase()}/meta.json');
37-
var metaDiffPath = Paths.file('songs/${songName.toLowerCase()}/meta-${difficulty.toLowerCase()}.json');
91+
var songNameLower = songName.toLowerCase();
92+
var metaPath = Paths.file('songs/${songNameLower}/meta.json');
93+
var metaDiffPath = Paths.file('songs/${songNameLower}/meta-${difficulty.toLowerCase()}.json');
3894

3995
var data:ChartMetaData = null;
4096
var fromMods:Bool = fromMods;
@@ -67,7 +123,7 @@ class Chart {
67123
data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(defaultColor));
68124

69125
if (data.difficulties.length <= 0) {
70-
data.difficulties = [for(f in Paths.getFolderContent('songs/${songName.toLowerCase()}/charts/', false, !fromMods)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
126+
data.difficulties = [for(f in Paths.getFolderContent('songs/${songNameLower}/charts/', false, !fromMods)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
71127
if (data.difficulties.length == 3) {
72128
var hasHard = false, hasNormal = false, hasEasy = false;
73129
for(d in data.difficulties) {
@@ -116,12 +172,12 @@ class Chart {
116172
Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED);
117173
}
118174

119-
if (data != null) {
120-
/**
121-
* CHART CONVERSION
122-
*/
123-
#if REGION
124-
if (Reflect.hasField(data, "codenameChart") && Reflect.field(data, "codenameChart") == true) {
175+
/**
176+
* CHART CONVERSION
177+
*/
178+
#if REGION
179+
if (data != null) switch (detectChartFormat(data)) {
180+
case CODENAME:
125181
// backward compat on events since its caused problems
126182
var eventTypesToString:Map<Int, String> = [
127183
-1 => "HScript Call",
@@ -132,28 +188,23 @@ class Chart {
132188
];
133189

134190
if (data.events == null) data.events = [];
135-
for (event in cast(data.events, Array<Dynamic>)) {
136-
if (Reflect.hasField(event, "type")) {
137-
if(event.type != null)
138-
event.name = eventTypesToString[event.type];
139-
Reflect.deleteField(event, "type");
140-
}
191+
for (event in cast(data.events, Array<Dynamic>)) if (Reflect.hasField(event, "type")) {
192+
if (event.type != null)
193+
event.name = eventTypesToString[event.type];
194+
Reflect.deleteField(event, "type");
141195
}
142196

143-
// codename chart
144197
base = data;
145-
} else {
146-
// base game chart
147-
FNFLegacyParser.parse(data, base);
148-
}
149-
#end
198+
case PSYCH_NEW: PsychParser.parse(data, base);
199+
case VSLICE: // TODO
200+
case LEGACY: FNFLegacyParser.parse(data, base);
150201
}
202+
#end
151203

152-
if (base.meta == null)
153-
base.meta = loadChartMeta(songName, difficulty, base.fromMods);
204+
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
205+
if (base.meta == null) base.meta = loadedMeta;
154206
else {
155-
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
156-
for(field in Reflect.fields(base.meta)) {
207+
for (field in Reflect.fields(base.meta)) {
157208
var f = Reflect.field(base.meta, field);
158209
if (f != null)
159210
Reflect.setField(loadedMeta, field, f);
@@ -249,4 +300,4 @@ typedef ChartSaveSettings = {
249300
var ?saveEventsInChart:Bool;
250301
var ?prettyPrint:Bool;
251302
var ?folder:String;
252-
}
303+
}

source/funkin/backend/chart/FNFLegacyParser.hx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import funkin.backend.system.Conductor;
66
class FNFLegacyParser {
77
public static function parse(data:Dynamic, result:ChartData) {
88
// base fnf chart parsing
9-
var data:SwagSong = data;
10-
if (Reflect.hasField(data, "song")) {
11-
var field:Dynamic = Reflect.field(data, "song");
12-
if (!(field is String))
13-
data = field;
14-
}
9+
var data:SwagSong = Chart.cleanSongData(data);
1510

1611
result.scrollSpeed = data.speed;
1712
result.stage = data.stage;
@@ -29,7 +24,7 @@ class FNFLegacyParser {
2924
position: "boyfriend",
3025
notes: []
3126
});
32-
var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : "gf");
27+
var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : (data.player3 != null ? data.player3 : "gf"));
3328
if (!p2isGF && gfName != "none") {
3429
result.strumLines.push({
3530
characters: [gfName],
@@ -132,10 +127,10 @@ class FNFLegacyParser {
132127
if ((swagSection.mustHitSection && strumLine.type == OPPONENT) ||
133128
(!swagSection.mustHitSection && strumLine.type == PLAYER))
134129
sectionNote[1] += 4;
135-
swagSection.sectionNotes.push(sectionNote);
130+
swagSection.sectionNotes.push(sectionNote);
136131
}
137132
}
138-
133+
139134
return {song: base};
140135
}
141136

@@ -214,6 +209,7 @@ typedef SwagSong =
214209

215210
var player1:String;
216211
var player2:String;
212+
var ?player3:String; // alt for gf but idr if it was just used in psych or what but eh whatever maybe its better here anyways - Nex
217213
var ?gf:String;
218214
var ?gfVersion:String;
219215
var validScore:Bool;

source/funkin/backend/chart/PsychParser.hx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,33 @@ import funkin.backend.chart.FNFLegacyParser.SwagSong;
66
import funkin.backend.system.Conductor;
77

88
class PsychParser {
9-
// Alreadly parsed in sections
9+
// Already parsed in sections
1010
public static var ignoreEvents:Array<String> = [
1111
"Camera Movement",
1212
"Alt Animation Toggle",
1313
"BPM Change"
1414
];
1515

16-
public static function parse(data:Dynamic, result:ChartData)
16+
/**
17+
* Converts 1.0 Psych charts to older ones
18+
*/
19+
public static function standardize(data:Dynamic, result:ChartData) {
20+
var sectionsData:Array<Dynamic> = Chart.cleanSongData(data).notes;
21+
if (sectionsData == null) return;
22+
23+
for (section in sectionsData) for (note in cast(section.sectionNotes, Array<Dynamic>)) {
24+
var gottaHitNote:Bool = section.mustHitSection == (note[1] >= 4);
25+
note[1] = (note[1] % 4) + (gottaHitNote ? 4 : 0);
26+
27+
if (note[3] != null && Std.isOfType(note[3], String))
28+
note[3] = Chart.addNoteType(result, note[3]);
29+
}
30+
}
31+
32+
public static function parse(data:Dynamic, result:ChartData) {
33+
if (Chart.detectChartFormat(data) == PSYCH_NEW) standardize(data, result);
1734
FNFLegacyParser.parse(data, result);
35+
}
1836

1937
public static function encode(chart:ChartData):Dynamic {
2038
var base:SwagSong = FNFLegacyParser.__convertToSwagSong(chart);
@@ -32,7 +50,7 @@ class PsychParser {
3250
for (note in strumLine.notes) {
3351
var section:Int = Math.floor(Conductor.getStepForTime(note.time) / Conductor.getMeasureLength());
3452
var swagSection:SwagSection = base.notes[section];
35-
53+
3654
if (section >= 0 && section < base.notes.length) {
3755
var sectionNote:Array<Dynamic> = [
3856
note.time, // TIME
@@ -44,10 +62,10 @@ class PsychParser {
4462
if ((swagSection.mustHitSection && strumLine.type == OPPONENT) ||
4563
(!swagSection.mustHitSection && strumLine.type == PLAYER))
4664
sectionNote[1] += 4;
47-
swagSection.sectionNotes.push(sectionNote);
65+
swagSection.sectionNotes.push(sectionNote);
4866
}
4967
}
50-
68+
5169
var groupedEvents:Array<Array<ChartEvent>> = [];
5270
var __last:Array<ChartEvent> = null;
5371
var __lastTime:Float = Math.NaN;
@@ -91,7 +109,7 @@ class PsychParser {
91109
FlxMath.roundDecimal(event.params[1]/chart.scrollSpeed, 2), // SCROLL SPEED MULTIPLER
92110
FlxMath.roundDecimal( // TIME
93111
event.params[0] ? // IS TWEENED?
94-
(Conductor.getTimeForStep(eventStep+event.params[2]) - Conductor.getTimeForStep(eventStep))/1000
112+
(Conductor.getTimeForStep(eventStep+event.params[2]) - Conductor.getTimeForStep(eventStep))/1000
95113
: 0, 2)
96114
]);
97115
default:
@@ -106,7 +124,7 @@ class PsychParser {
106124

107125
for (psychEvent in psychEvents)
108126
for (i in 1...3) // Turn both vals into strings
109-
if (!(psychEvent[i] is String))
127+
if (!(psychEvent[i] is String))
110128
psychEvent[i] = Std.string(psychEvent[i]);
111129

112130
base.events.push([events[0].time, psychEvents]);

source/funkin/editors/charter/Charter.hx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,7 @@ class Charter extends UIState {
13561356
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json',
13571357
}));
13581358
}
1359-
1359+
13601360
function _file_saveas_psych(_) {
13611361
openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? "\t" : null), {
13621362
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json',

source/hscript/Config.hx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class Config {
2929
];
3030

3131
public static final DISALLOW_ABSTRACT_AND_ENUM = [
32-
"funkin.backend.FunkinSprite", // Error: String has no field trim, Due to Func
3332
"funkin.backend.scripting.events.PlayAnimEvent", // Error: expected member name or ';' after declaration specifiers, Due to Func
3433
];
3534
}

0 commit comments

Comments
 (0)