Skip to content

Commit

Permalink
psych 1.0 format + some charts improvements
Browse files Browse the repository at this point in the history
Fixes #448
  • Loading branch information
NexIsDumb committed Nov 30, 2024
1 parent 85617b7 commit eada310
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 47 deletions.
4 changes: 2 additions & 2 deletions source/funkin/backend/FunkinSprite.hx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ enum abstract XMLAnimType(Int)
var BEAT = 1;
var LOOP = 2;

public static function fromString(str:String, def:XMLAnimType = NONE)
public static function fromString(str:String, def:XMLAnimType = XMLAnimType.NONE)
{
return switch (str.trim().toLowerCase())
return switch (StringTools.trim(str).toLowerCase())
{
case "none": NONE;
case "beat" | "onbeat": BEAT;
Expand Down
105 changes: 78 additions & 27 deletions source/funkin/backend/chart/Chart.hx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package funkin.backend.chart;

import funkin.backend.system.Conductor;
import funkin.backend.chart.ChartData;
import flixel.util.FlxColor;
import haxe.io.Path;
Expand All @@ -13,12 +12,68 @@ import sys.FileSystem;

using StringTools;

enum abstract ChartFormat(Int) {
var CODENAME = 0;
var LEGACY = 1; // also used by many other engines (old Psych, Kade and more) - Nex
var VSLICE = 2;
var PSYCH_NEW = 3;

@:to public function toString():String {
return switch(cast (this, ChartFormat)) {
case CODENAME: "CODENAME";
case LEGACY: "LEGACY";
case VSLICE: "VSLICE";
case PSYCH_NEW: "PSYCH_NEW";
}
}

public static function fromString(str:String, def:ChartFormat = ChartFormat.LEGACY) {
str = str.toLowerCase();
str = StringTools.replace(str, " ", "");
str = StringTools.replace(str, "_", "");
str = StringTools.replace(str, ".", "");

if(StringTools.startsWith(str, "psychv1") || StringTools.startsWith(str, "psych1"))
return PSYCH_NEW;

return switch(str) {
case "codename" | "codenameengine": CODENAME;
case "newpsych" | "psychnew": PSYCH_NEW;
default: def;
}
}
}

class Chart {
/**
* Default background colors for songs without bg color
*/
public inline static var defaultColor:FlxColor = 0xFF9271FD;

public static function cleanSongData(data:Dynamic):Dynamic {
if (Reflect.hasField(data, "song")) {
var field:Dynamic = Reflect.field(data, "song");
if (field != null && Type.typeof(field) == TObject) // Cant use Reflect.isObject, because it detects strings for some reason
return field;
}
return data;
}

public static function detectChartFormat(data:Dynamic):ChartFormat {
var __temp:Dynamic; // imma reuse this var so the program doesnt have to get values multiple times - Nex

if ((__temp = data.codenameChart) == true || __temp == "true")
return CODENAME;

if (Reflect.hasField(data, "version") && Reflect.hasField(data, "scrollSpeed"))
return VSLICE;

if ((__temp = cleanSongData(data).format) != null && __temp is String && StringTools.startsWith(__temp, "psych_v1"))
return PSYCH_NEW;

return LEGACY;
}

public static function loadEventsJson(songName:String) {
var path = Paths.file('songs/${songName.toLowerCase()}/events.json');
var data:Array<ChartEvent> = null;
Expand All @@ -33,8 +88,9 @@ class Chart {
}

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

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

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

if (data != null) {
/**
* CHART CONVERSION
*/
#if REGION
if (Reflect.hasField(data, "codenameChart") && Reflect.field(data, "codenameChart") == true) {
/**
* CHART CONVERSION
*/
#if REGION
if (data != null) switch (detectChartFormat(data)) {
case CODENAME:
// backward compat on events since its caused problems
var eventTypesToString:Map<Int, String> = [
-1 => "HScript Call",
Expand All @@ -132,28 +188,23 @@ class Chart {
];

if (data.events == null) data.events = [];
for (event in cast(data.events, Array<Dynamic>)) {
if (Reflect.hasField(event, "type")) {
if(event.type != null)
event.name = eventTypesToString[event.type];
Reflect.deleteField(event, "type");
}
for (event in cast(data.events, Array<Dynamic>)) if (Reflect.hasField(event, "type")) {
if (event.type != null)
event.name = eventTypesToString[event.type];
Reflect.deleteField(event, "type");
}

// codename chart
base = data;
} else {
// base game chart
FNFLegacyParser.parse(data, base);
}
#end
case PSYCH_NEW: PsychParser.parse(data, base);
case VSLICE: // TODO
case LEGACY: FNFLegacyParser.parse(data, base);
}
#end

if (base.meta == null)
base.meta = loadChartMeta(songName, difficulty, base.fromMods);
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
if (base.meta == null) base.meta = loadedMeta;
else {
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
for(field in Reflect.fields(base.meta)) {
for (field in Reflect.fields(base.meta)) {
var f = Reflect.field(base.meta, field);
if (f != null)
Reflect.setField(loadedMeta, field, f);
Expand Down Expand Up @@ -249,4 +300,4 @@ typedef ChartSaveSettings = {
var ?saveEventsInChart:Bool;
var ?prettyPrint:Bool;
var ?folder:String;
}
}
14 changes: 5 additions & 9 deletions source/funkin/backend/chart/FNFLegacyParser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import funkin.backend.system.Conductor;
class FNFLegacyParser {
public static function parse(data:Dynamic, result:ChartData) {
// base fnf chart parsing
var data:SwagSong = data;
if (Reflect.hasField(data, "song")) {
var field:Dynamic = Reflect.field(data, "song");
if (!(field is String))
data = field;
}
var data:SwagSong = Chart.cleanSongData(data);

result.scrollSpeed = data.speed;
result.stage = data.stage;
Expand All @@ -29,7 +24,7 @@ class FNFLegacyParser {
position: "boyfriend",
notes: []
});
var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : "gf");
var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : (data.player3 != null ? data.player3 : "gf"));
if (!p2isGF && gfName != "none") {
result.strumLines.push({
characters: [gfName],
Expand Down Expand Up @@ -132,10 +127,10 @@ class FNFLegacyParser {
if ((swagSection.mustHitSection && strumLine.type == OPPONENT) ||
(!swagSection.mustHitSection && strumLine.type == PLAYER))
sectionNote[1] += 4;
swagSection.sectionNotes.push(sectionNote);
swagSection.sectionNotes.push(sectionNote);
}
}

return {song: base};
}

Expand Down Expand Up @@ -214,6 +209,7 @@ typedef SwagSong =

var player1:String;
var player2:String;
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
var ?gf:String;
var ?gfVersion:String;
var validScore:Bool;
Expand Down
32 changes: 25 additions & 7 deletions source/funkin/backend/chart/PsychParser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,33 @@ import funkin.backend.chart.FNFLegacyParser.SwagSong;
import funkin.backend.system.Conductor;

class PsychParser {
// Alreadly parsed in sections
// Already parsed in sections
public static var ignoreEvents:Array<String> = [
"Camera Movement",
"Alt Animation Toggle",
"BPM Change"
];

public static function parse(data:Dynamic, result:ChartData)
/**
* Converts 1.0 Psych charts to older ones
*/
public static function standardize(data:Dynamic, result:ChartData) {
var sectionsData:Array<Dynamic> = Chart.cleanSongData(data).notes;
if (sectionsData == null) return;

for (section in sectionsData) for (note in cast(section.sectionNotes, Array<Dynamic>)) {
var gottaHitNote:Bool = section.mustHitSection == (note[1] >= 4);
note[1] = (note[1] % 4) + (gottaHitNote ? 4 : 0);

if (note[3] != null && Std.isOfType(note[3], String))
note[3] = Chart.addNoteType(result, note[3]);
}
}

public static function parse(data:Dynamic, result:ChartData) {
if (Chart.detectChartFormat(data) == PSYCH_NEW) standardize(data, result);
FNFLegacyParser.parse(data, result);
}

public static function encode(chart:ChartData):Dynamic {
var base:SwagSong = FNFLegacyParser.__convertToSwagSong(chart);
Expand All @@ -32,7 +50,7 @@ class PsychParser {
for (note in strumLine.notes) {
var section:Int = Math.floor(Conductor.getStepForTime(note.time) / Conductor.getMeasureLength());
var swagSection:SwagSection = base.notes[section];

if (section >= 0 && section < base.notes.length) {
var sectionNote:Array<Dynamic> = [
note.time, // TIME
Expand All @@ -44,10 +62,10 @@ class PsychParser {
if ((swagSection.mustHitSection && strumLine.type == OPPONENT) ||
(!swagSection.mustHitSection && strumLine.type == PLAYER))
sectionNote[1] += 4;
swagSection.sectionNotes.push(sectionNote);
swagSection.sectionNotes.push(sectionNote);
}
}

var groupedEvents:Array<Array<ChartEvent>> = [];
var __last:Array<ChartEvent> = null;
var __lastTime:Float = Math.NaN;
Expand Down Expand Up @@ -91,7 +109,7 @@ class PsychParser {
FlxMath.roundDecimal(event.params[1]/chart.scrollSpeed, 2), // SCROLL SPEED MULTIPLER
FlxMath.roundDecimal( // TIME
event.params[0] ? // IS TWEENED?
(Conductor.getTimeForStep(eventStep+event.params[2]) - Conductor.getTimeForStep(eventStep))/1000
(Conductor.getTimeForStep(eventStep+event.params[2]) - Conductor.getTimeForStep(eventStep))/1000
: 0, 2)
]);
default:
Expand All @@ -106,7 +124,7 @@ class PsychParser {

for (psychEvent in psychEvents)
for (i in 1...3) // Turn both vals into strings
if (!(psychEvent[i] is String))
if (!(psychEvent[i] is String))
psychEvent[i] = Std.string(psychEvent[i]);

base.events.push([events[0].time, psychEvents]);
Expand Down
2 changes: 1 addition & 1 deletion source/funkin/editors/charter/Charter.hx
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,7 @@ class Charter extends UIState {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json',
}));
}

function _file_saveas_psych(_) {
openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? "\t" : null), {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json',
Expand Down
1 change: 0 additions & 1 deletion source/hscript/Config.hx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class Config {
];

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

0 comments on commit eada310

Please sign in to comment.