finished global/local events + HUGE SAVES FIX

HOW DID WE NEVER NOTICE??
anyways im really happy on how everything came out :D
more than a fix it also ended up like a rewrite!
This commit is contained in:
⍚~Nex
2025-03-09 06:11:36 +01:00
parent 6c836eae71
commit 39aadbe8a0
7 changed files with 167 additions and 128 deletions
+1
View File
@@ -134,6 +134,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
{
frames = Paths.getFrames(path, true);
}
return this;
}
public function onPropertySet(property:String, value:Dynamic) {
+2 -2
View File
@@ -8,6 +8,7 @@ import funkin.backend.scripting.events.ResizeEvent;
import funkin.backend.scripting.events.TransitionCreationEvent;
import funkin.backend.utils.FunkinParentDisabler;
import funkin.editors.ui.UIState;
class MusicBeatTransition extends MusicBeatSubstate {
public static var script:String = Flags.DEFAULT_TRANSITION_SCRIPT;
public var transitionScript:Script;
@@ -55,8 +56,7 @@ class MusicBeatTransition extends MusicBeatSubstate {
add(blackSpr = new FlxSprite(0, transOut ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1));
blackSpr.color = 0xFF000000;
add(transitionSprite = new FunkinSprite());
transitionSprite.loadSprite(Paths.image('menus/transitionSpr'));
add(transitionSprite = new FunkinSprite().loadSprite(Paths.image('menus/transitionSpr')));
resizeDefaultSprites();
transitionCamera.flipY = !transOut;
+19 -8
View File
@@ -77,9 +77,8 @@ class Chart {
if (Assets.exists(path)) {
try {
data = Json.parse(Assets.getText(path)).events;
} catch(e) {
Logs.trace('Failed to load song event data for ${songName} ($path): ${Std.string(e)}', ERROR);
}
for (event in data) event.global = true;
} catch(e) Logs.trace('Failed to load song event data for ${songName} ($path): ${Std.string(e)}', ERROR);
}
return data;
}
@@ -259,9 +258,10 @@ class Chart {
if (saveSettings == null) saveSettings = {};
if (saveSettings.saveMetaInChart == null) saveSettings.saveMetaInChart = true;
if (saveSettings.saveEventsInChart == null) saveSettings.saveEventsInChart = true;
if (saveSettings.saveLocalEvents == null) saveSettings.saveLocalEvents = true;
if (saveSettings.saveGlobalEvents == null) saveSettings.saveGlobalEvents = false;
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveEventsInChart);
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveLocalEvents, saveSettings.saveGlobalEvents);
var meta = filteredChart.meta;
#if sys
@@ -282,7 +282,7 @@ class Chart {
return filteredChart;
}
public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Null<Bool>, ?saveEventsInChart:Null<Bool>):ChartData {
public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Bool, ?saveLocalEvents:Bool, ?saveGlobalEvents:Bool):ChartData {
var data = Reflect.copy(chart); // make a copy of the chart to leave the OG intact
if (saveMetaInChart != true) {
data.meta = null;
@@ -291,7 +291,17 @@ class Chart {
if (data.meta != null && Reflect.hasField(data.meta, "parsedColor")) Reflect.deleteField(data.meta, "parsedColor");
}
data.events = saveEventsInChart != true ? null : Reflect.copy(chart.events); // same here once again
if (saveLocalEvents != true && saveGlobalEvents != true) data.events = null;
else {
data.events = [];
for (event in chart.events) if ((saveLocalEvents == true && event.global != true) || (saveGlobalEvents == true && event.global == true)) {
var copy = Reflect.copy(event);
if (saveLocalEvents == true ? event.global != true : event.global == true) Reflect.deleteField(copy, "global"); // should NOT delete the field when saving with the local events and the event should have been global - Nex
data.events.push(copy);
}
if (data.events.length == 0) data.events = null;
}
data.fromMods = null;
var sortedData:Dynamic = {};
@@ -307,7 +317,8 @@ class Chart {
typedef ChartSaveSettings = {
var ?overrideExistingMeta:Bool;
var ?saveMetaInChart:Bool;
var ?saveEventsInChart:Bool;
var ?saveLocalEvents:Bool;
var ?saveGlobalEvents:Bool;
var ?prettyPrint:Bool;
var ?folder:String;
}
+1
View File
@@ -61,6 +61,7 @@ typedef ChartEvent = {
var name:String;
var time:Float;
var params:Array<Dynamic>;
var ?global:Bool; // If its from a global event file like events.json; this field might be saved rarely in the chart json directly but modders can mess with this for messing with the saving funcs too - Nex
}
enum abstract ChartStrumLineType(Int) from Int to Int {
+116 -95
View File
@@ -2,22 +2,26 @@ package funkin.editors.charter;
// ! FUCK YOU CHUF (your biggest fan -lunar) <3
// import flixel.FlxLayer;
import funkin.editors.charter.CharterEvent;
import funkin.editors.extra.CameraHoverDummy;
import flixel.input.keyboard.FlxKey;
import flixel.math.FlxPoint;
import flixel.sound.FlxSound;
import flixel.util.FlxSort;
import funkin.backend.chart.*;
import funkin.backend.chart.ChartData;
import funkin.backend.system.Conductor;
import funkin.backend.system.framerate.Framerate;
import funkin.editors.charter.CharterBackdropGroup.EventBackdrop;
import funkin.editors.charter.CharterEvent;
import funkin.editors.charter.CharterStrumline;
import funkin.editors.extra.CameraHoverDummy;
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
import funkin.editors.ui.UIContextMenu.UIContextMenuOptionSpr;
import funkin.editors.ui.UIState;
import funkin.editors.ui.UITopMenu.UITopMenuButton;
import haxe.Json;
#if sys
import sys.FileSystem;
#end
class Charter extends UIState {
public static var __song:String;
@@ -110,7 +114,7 @@ class Charter extends UIState {
WindowUtils.suffix = " (Chart Editor)";
SaveWarning.selectionClass = CharterSelection;
SaveWarning.saveFunc = () -> {_file_save(null);};
SaveWarning.saveFunc = () -> _file_save_all(null);
topMenu = [
{
@@ -121,55 +125,57 @@ class Charter extends UIState {
},
null,*/
{
label: "Save",
label: "Save everything",
keybind: [CONTROL, S],
onSelect: _file_save,
onSelect: _file_save_all,
},
null,
{
label: "Save As...",
label: "Save chart",
keybind: [CONTROL, SHIFT, S],
onSelect: _file_saveas,
onSelect: (_) -> _file_save(),
},
{
label: "Save chart as...",
onSelect: (_) -> _file_saveas(),
},
null,
{
label: "Save Without Events",
keybind: [CONTROL, ALT, TAB, S],
onSelect: _file_save_no_events,
label: "Save global events",
keybind: [CONTROL, B, S],
onSelect: (_) -> _file_events_save(),
},
{
label: "Save Without Events As...",
keybind: [CONTROL, SHIFT, ALT, TAB, S],
onSelect: _file_saveas_no_events,
label: "Save global events as...",
onSelect: (_) -> _file_events_saveas(),
},
{
label: "Save Events Separately",
keybind: [CONTROL, TAB, S],
onSelect: _file_events_save,
},
{
label: "Save Events Separately As...",
keybind: [CONTROL, SHIFT, TAB, S],
onSelect: _file_events_saveas,
},
null,
{
label: "Save Meta",
label: "Save chart without local events",
keybind: [CONTROL, ALT, S],
onSelect: _file_meta_save,
onSelect: (_) -> _file_save_no_events(),
},
{
label: "Save Meta As...",
keybind: [CONTROL, ALT ,SHIFT, S],
onSelect: _file_meta_saveas,
label: "Save chart without local events as...",
onSelect: (_) -> _file_saveas_no_events(),
},
null,
{
label: "Export For FNF Legacy...",
onSelect: _file_saveas_fnflegacy,
label: "Save meta",
keybind: [CONTROL, M, S],
onSelect: (_) -> _file_meta_save(),
},
{
label: "Export For Psych Engine...",
onSelect: _file_saveas_psych,
label: "Save meta as...",
onSelect: (_) -> _file_meta_saveas(),
},
null,
{
label: "Export for FNF Legacy as...",
onSelect: (_) -> _file_saveas_fnflegacy(),
},
{
label: "Export for Psych Engine as...",
onSelect: (_) -> _file_saveas_psych(),
},
null,
{
@@ -616,7 +622,7 @@ class Charter extends UIState {
notesGroup.autoSort = true;
// create events
localEventsGroup.autoSort = false;
globalEventsGroup.autoSort = localEventsGroup.autoSort = false;
var __last:CharterEvent = null;
var __lastTime:Float = Math.NaN;
for (e in PlayState.SONG.events) {
@@ -626,15 +632,15 @@ class Charter extends UIState {
} else {
__last = new CharterEvent(Conductor.getStepForTime(e.time), [e]);
__lastTime = e.time;
localEventsGroup.add(__last);
(__last.global ? globalEventsGroup : localEventsGroup).add(__last);
}
}
localEventsGroup.sortEvents();
localEventsGroup.autoSort = true;
for(e in localEventsGroup.members)
e.refreshEventIcons();
for (grp in [localEventsGroup, globalEventsGroup]) {
grp.sortEvents();
grp.autoSort = true;
for (e in grp.members) e.refreshEventIcons();
}
buildNoteTypesUI();
refreshBPMSensitive();
@@ -771,9 +777,9 @@ class Charter extends UIState {
var songPath:String = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}';
if (Options.charterAutoSavesSeparateFolder)
Chart.save(songPath, PlayState.SONG, __autoSaveLocation, {saveMetaInChart: false, folder: "autosaves", prettyPrint: Options.editorPrettyPrint});
else
Chart.save(songPath, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, prettyPrint: Options.editorPrettyPrint});
Chart.save(songPath, PlayState.SONG, __autoSaveLocation, {saveMetaInChart: true, saveLocalEvents: true, saveGlobalEvents: true, folder: "autosaves", prettyPrint: Options.editorPrettyPrint});
else // These two chart saves are particular, to avoid any kind of loss, stuff like meta, global and local events will be save all together - Nex
Chart.save(songPath, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: true, saveLocalEvents: true, saveGlobalEvents: true, prettyPrint: Options.editorPrettyPrint});
undos.save();
}
autoSaveNotif.cancelled = false;
@@ -1422,90 +1428,106 @@ class Charter extends UIState {
else {undos = null; FlxG.switchState(new CharterSelection()); PlayState.resetSongInfos(); Charter.instance.__clearStatics();}
}
function _file_save(_) {
function _file_save_all(_) {
buildChart();
_file_save(false);
_file_events_save(false);
_file_meta_save(false);
}
function _file_save(shouldBuild:Bool = true) {
#if sys
saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}');
saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', false, shouldBuild);
undos.save();
return;
#else
_file_saveas(shouldBuild);
#end
_file_saveas(_);
}
function _file_saveas(_) {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
function _file_saveas(shouldBuild:Bool = true) {
saveAs(Chart.filterChartForSaving(PlayState.SONG, false, true, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, {
defaultSaveFile: '${__diff.toLowerCase()}.json'
}));
}, null, shouldBuild);
undos.save();
}
function _file_save_no_events(_) {
function _file_events_save(shouldBuild:Bool = true) {
#if sys
saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', true);
undos.save();
return;
if (shouldBuild) buildChart();
var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events};
var path = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json';
if (data == null || data.events.length == 0) FileSystem.deleteFile(path); // Instead of replacing with a useless empty file, deletes the file directly - Nex
else CoolUtil.safeSaveFile(path, Json.stringify(data, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null));
#else
_file_events_saveas(shouldBuild);
#end
_file_saveas(_);
}
function _file_saveas_no_events(_) {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
function _file_events_saveas(shouldBuild:Bool = true) {
if (shouldBuild) buildChart();
var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events};
saveAs(data, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, {
defaultSaveFile: 'events.json'
}, null, false);
}
function _file_save_no_events(shouldBuild:Bool = true) {
#if sys
saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', true, shouldBuild);
undos.save();
#else
_file_saveas(shouldBuild);
#end
}
function _file_saveas_no_events(shouldBuild:Bool = true) {
saveAs(Chart.filterChartForSaving(PlayState.SONG, false, false, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, {
defaultSaveFile: '${__diff.toLowerCase()}.json'
}));
}, null, shouldBuild);
undos.save();
}
function _file_meta_save(_) {
function _file_meta_save(shouldBuild:Bool = true) {
#if sys
if (shouldBuild) buildChart();
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json',
Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT)
Json.stringify(PlayState.SONG.meta == null ? {} : Chart.filterChartForSaving(PlayState.SONG, true, false, false).meta, null, Flags.JSON_PRETTY_PRINT)
);
#else
_file_meta_saveas(_);
_file_meta_saveas(shouldBuild);
#end
}
function _file_meta_saveas(_) {
openSubState(new SaveSubstate(Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT), { // always pretty print meta
function _file_meta_saveas(shouldBuild:Bool = true) {
saveAs(PlayState.SONG.meta == null ? {} : Chart.filterChartForSaving(PlayState.SONG, true, false, false).meta, null, Flags.JSON_PRETTY_PRINT, { // always pretty print meta
defaultSaveFile: 'meta.json'
}));
}, null, shouldBuild);
}
function _file_saveas_fnflegacy(_) {
openSubState(new SaveSubstate(Json.stringify(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
function _file_saveas_fnflegacy(shouldBuild:Bool = true) {
saveAs(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json',
}));
}, null, shouldBuild);
}
function _file_saveas_psych(_) {
openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
function _file_saveas_psych(shouldBuild:Bool = true) {
saveAs(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json',
}));
}, null, shouldBuild);
}
function _file_events_save(_) {
#if sys
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json',
Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null)
);
#else
_file_events_saveas(_);
#end
}
function _file_events_saveas(_) {
#if sys
openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
defaultSaveFile: 'events.json'
}));
#end
function saveAs(data:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String, ?options:SaveSubstate.SaveSubstateData, ?saveOptions:Map<String, Bool>, shouldBuild:Bool = true) {
if (shouldBuild) buildChart();
openSubState(new SaveSubstate(Json.stringify(data, replacer, space), options, saveOptions));
}
#if sys
function saveTo(path:String, separateEvents:Bool = false) {
buildChart();
Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: !separateEvents, prettyPrint: Options.editorPrettyPrint});
function saveTo(path:String, separateEvents:Bool = false, shouldBuild:Bool = true) {
if (shouldBuild) buildChart();
Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveLocalEvents: !separateEvents, prettyPrint: Options.editorPrettyPrint});
}
#end
@@ -1976,15 +1998,14 @@ class Charter extends UIState {
public function buildEvents() {
PlayState.SONG.events = [];
// TODO!!!!
localEventsGroup.sortEvents();
for(e in localEventsGroup.members) {
for(event in e.events) {
var events:Array<CharterEvent> = localEventsGroup.members.concat(globalEventsGroup.members);
events.sort(globalEventsGroup.sortEventsFilter.bind(FlxSort.ASCENDING));
for (e in events) for (event in e.events) {
event.time = Conductor.getTimeForStep(e.step);
PlayState.SONG.events.push(event);
}
}
}
public function updateBPMEvents() {
buildEvents();
@@ -19,15 +19,19 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
public var eventsBackdrop:EventBackdrop;
public var snappedToGrid:Bool = true;
public var global:Bool = false;
public var displayGlobal:Bool = false;
public var global(default, set):Bool = false;
private function set_global(val:Bool) {
for (event in events) event.global = val;
return global = val;
}
public function new(step:Float, ?events:Array<ChartEvent>, ?global:Bool = false) {
public function new(step:Float, ?events:Array<ChartEvent>, ?global:Bool) {
super(-100, (step * 40) - 17, 100, 34, 'editors/charter/event-spr');
this.step = step;
this.events = events.getDefault([]);
this.global = displayGlobal = global;
this.global = displayGlobal = (global == null ? events[0] != null && events[0].global == true : global);
__redOffset = global ? 20 : 0;
cursor = CLICK;
@@ -33,9 +33,7 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> {
public inline function sortEvents() {
__lastSort = members.length;
this.sort(function(i, e1, e2) {
return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step);
});
this.sort(sortEventsFilter);
updateEventsIDs();
updateEventsBackdrop();
}
@@ -54,4 +52,7 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> {
event.kill();
}
}
public dynamic function sortEventsFilter(i:Int, e1:CharterEvent, e2:CharterEvent)
return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step);
}