mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-25 16:24:40 +00:00
Autosave and save-on-quit for chart editor
This commit is contained in:
parent
b3b7fb49c2
commit
6b3c15348b
|
@ -135,14 +135,6 @@
|
|||
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug" />
|
||||
|
||||
<!--
|
||||
With these options enabled, console popup and beep no longer occur.
|
||||
You can still see the log messages by opening the console (F2).
|
||||
Be sure to remove these during cleanup and bugfix testing!
|
||||
-->
|
||||
<haxedef name="FLX_NO_ERROR_SOUND" />
|
||||
<haxedef name="FLX_NO_ERROR_CONSOLE" />
|
||||
|
||||
<!--Disable the Flixel core focus lost screen-->
|
||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ import flixel.group.FlxSpriteGroup;
|
|||
import flixel.math.FlxMath;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Loosley based on FlxTypeText lolol
|
||||
*/
|
||||
|
|
|
@ -18,8 +18,6 @@ import lime.math.Rectangle;
|
|||
import lime.utils.Assets;
|
||||
import openfl.filters.ShaderFilter;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class CoolUtil
|
||||
{
|
||||
public static var difficultyArray:Array<String> = ['EASY', "NORMAL", "HARD"];
|
||||
|
|
|
@ -4,8 +4,6 @@ import flixel.FlxSprite;
|
|||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class CutsceneCharacter extends FlxTypedGroup<FlxSprite>
|
||||
{
|
||||
public var coolPos:FlxPoint = FlxPoint.get();
|
||||
|
|
|
@ -11,8 +11,6 @@ import flixel.util.FlxColor;
|
|||
import flixel.util.FlxTimer;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class DialogueBox extends FlxSpriteGroup
|
||||
{
|
||||
var box:FlxSprite;
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import Sys.sleep;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if discord_rpc
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
|
|
@ -34,8 +34,6 @@ import funkin.shaderslmfao.StrokeShader;
|
|||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class FreeplayState extends MusicBeatSubstate
|
||||
{
|
||||
var songs:Array<SongMetadata> = [];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.transition.TransitionData;
|
||||
|
@ -15,10 +16,8 @@ import funkin.play.song.SongData.SongDataParser;
|
|||
import funkin.play.stage.StageData;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if colyseus
|
||||
import io.colyseus.Client;
|
||||
import io.colyseus.Room;
|
||||
|
@ -88,8 +87,16 @@ class InitState extends FlxTransitionableState
|
|||
if (FlxG.save.data.mute != null)
|
||||
FlxG.sound.muted = FlxG.save.data.mute;
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
|
||||
// FlxG.save.close();
|
||||
// FlxG.sound.loadSavedPrefs();
|
||||
WindowUtil.initWindowEvents();
|
||||
|
||||
PreferencesMenu.initPrefs();
|
||||
PlayerSettings.init();
|
||||
Highscore.load();
|
||||
|
|
|
@ -188,10 +188,13 @@ class LoadingState extends MusicBeatState
|
|||
{
|
||||
Paths.setCurrentLevel('tutorial');
|
||||
}
|
||||
else if (PlayState.storyWeek == 8) {
|
||||
else if (PlayState.storyWeek == 8)
|
||||
{
|
||||
// TODO: Refactor this code.
|
||||
Paths.setCurrentLevel("weekend1");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Paths.setCurrentLevel("week" + PlayState.storyWeek);
|
||||
}
|
||||
#if NO_PRELOAD_ALL
|
||||
|
@ -251,7 +254,7 @@ class LoadingState extends MusicBeatState
|
|||
}
|
||||
else
|
||||
{
|
||||
if (StringTools.endsWith(path, ".bundle"))
|
||||
if (path.endsWith(".bundle"))
|
||||
{
|
||||
rootPath = path;
|
||||
path += "/library.json";
|
||||
|
|
|
@ -28,9 +28,6 @@ import funkin.util.Constants;
|
|||
import funkin.util.WindowUtil;
|
||||
import lime.app.Application;
|
||||
import openfl.filters.ShaderFilter;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
|
|
@ -15,8 +15,6 @@ import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
|||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
import lime.app.Application;
|
||||
import openfl.display.Stage;
|
||||
|
||||
using StringTools;
|
||||
#end
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,8 +10,6 @@ import funkin.shaderslmfao.ColorSwap;
|
|||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.util.Constants;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Note extends FlxSprite
|
||||
{
|
||||
public var data = new NoteData();
|
||||
|
|
|
@ -6,8 +6,6 @@ import funkin.play.PlayState;
|
|||
import haxe.Json;
|
||||
import lime.utils.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
typedef SwagSong =
|
||||
{
|
||||
var song:String;
|
||||
|
|
|
@ -15,9 +15,6 @@ import funkin.play.PlayState;
|
|||
import funkin.play.song.SongData.SongDataParser;
|
||||
import lime.net.curl.CURLCode;
|
||||
import openfl.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
|
|
@ -22,8 +22,6 @@ import openfl.events.NetStatusEvent;
|
|||
import openfl.media.Video;
|
||||
import openfl.net.NetStream;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if desktop
|
||||
#end
|
||||
class TitleState extends MusicBeatState
|
||||
|
|
|
@ -17,8 +17,6 @@ import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
|||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains any script functions which should be BLOCKED from use by modded scripts.
|
||||
*/
|
||||
|
|
|
@ -17,8 +17,6 @@ import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
|||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains any script functions which should be ALLOWD for use by modded scripts.
|
||||
*/
|
||||
|
|
|
@ -34,7 +34,6 @@ import openfl.events.IOErrorEvent;
|
|||
import openfl.net.FileReference;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
using flixel.util.FlxSpriteUtil; // add in "compiler save" that saves the JSON directly to the debug json using File.write() stuff on windows / sys
|
||||
|
||||
class ChartingState extends MusicBeatState
|
||||
|
|
|
@ -3,8 +3,6 @@ package funkin.freeplayStuff;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
|
||||
{
|
||||
public var scoreShit(default, set):Int = 0;
|
||||
|
|
|
@ -7,6 +7,7 @@ import funkin.play.stage.StageData;
|
|||
import polymod.Polymod;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
import funkin.util.FileUtil;
|
||||
|
||||
class PolymodHandler
|
||||
{
|
||||
|
@ -25,12 +26,7 @@ class PolymodHandler
|
|||
|
||||
public static function createModRoot()
|
||||
{
|
||||
#if sys
|
||||
if (!sys.FileSystem.exists(MOD_FOLDER))
|
||||
{
|
||||
sys.FileSystem.createDirectory(MOD_FOLDER);
|
||||
}
|
||||
#end
|
||||
FileUtil.createDirIfNotExists(MOD_FOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,8 +10,6 @@ import funkin.modding.events.ScriptEvent;
|
|||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Countdown
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -11,8 +11,6 @@ import funkin.play.PlayState;
|
|||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* A substate which renders over the PlayState when the player dies.
|
||||
* Displays the player death animation, plays the music, and handles restarting the song.
|
||||
|
|
|
@ -41,9 +41,6 @@ import funkin.ui.stageBuildShit.StageOffsetSubstate;
|
|||
import funkin.util.Constants;
|
||||
import funkin.util.SortUtil;
|
||||
import lime.ui.Haptic;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
|
|
@ -6,8 +6,6 @@ import funkin.noteStuff.NoteBasic.NoteDir;
|
|||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.stage.Bopper;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* A Character is a stage prop which bops to the music as well as controlled by the strumlines.
|
||||
*
|
||||
|
|
|
@ -16,8 +16,6 @@ import funkin.util.assets.DataAssets;
|
|||
import haxe.Json;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class CharacterDataParser
|
||||
{
|
||||
/**
|
||||
|
@ -258,7 +256,7 @@ class CharacterDataParser
|
|||
static function loadCharacterFile(charPath:String):String
|
||||
{
|
||||
var charFilePath:String = Paths.json('characters/${charPath}');
|
||||
var rawJson = StringTools.trim(Assets.getText(charFilePath));
|
||||
var rawJson = Assets.getText(charFilePath).trim();
|
||||
|
||||
while (!StringTools.endsWith(rawJson, "}"))
|
||||
{
|
||||
|
|
|
@ -8,8 +8,6 @@ import haxe.Json;
|
|||
import openfl.utils.Assets;
|
||||
import thx.semver.Version;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains utilities for loading and parsing stage data.
|
||||
*/
|
||||
|
@ -267,7 +265,8 @@ abstract SongMetadata(RawSongMetadata)
|
|||
};
|
||||
}
|
||||
|
||||
public function clone(?newVariation:String = null):SongMetadata {
|
||||
public function clone(?newVariation:String = null):SongMetadata
|
||||
{
|
||||
var result = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
|
||||
result.version = this.version;
|
||||
result.timeFormat = this.timeFormat;
|
||||
|
@ -276,7 +275,7 @@ abstract SongMetadata(RawSongMetadata)
|
|||
result.loop = this.loop;
|
||||
result.playData = this.playData;
|
||||
result.generatedBy = this.generatedBy;
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ import funkin.util.assets.DataAssets;
|
|||
import haxe.Json;
|
||||
import openfl.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains utilities for loading and parsing stage data.
|
||||
*/
|
||||
|
|
|
@ -6,8 +6,6 @@ import flixel.group.FlxGroup.FlxTypedGroup;
|
|||
import flixel.tweens.FlxTween;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
{
|
||||
override public function new()
|
||||
|
|
|
@ -33,7 +33,6 @@ import openfl.net.URLLoader;
|
|||
import openfl.net.URLRequest;
|
||||
import openfl.utils.ByteArray;
|
||||
|
||||
using StringTools;
|
||||
using flixel.util.FlxSpriteUtil;
|
||||
|
||||
#if web
|
||||
|
|
|
@ -64,6 +64,7 @@ class AddNotesCommand implements ChartEditorCommand
|
|||
|
||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -76,6 +77,7 @@ class AddNotesCommand implements ChartEditorCommand
|
|||
state.currentSelection = [];
|
||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -109,6 +111,7 @@ class RemoveNotesCommand implements ChartEditorCommand
|
|||
state.currentSelection = [];
|
||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -124,6 +127,7 @@ class RemoveNotesCommand implements ChartEditorCommand
|
|||
state.currentSelection = notes;
|
||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -409,6 +413,7 @@ class CutNotesCommand implements ChartEditorCommand
|
|||
// Delete the notes.
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentSelection = [];
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
|
@ -419,6 +424,7 @@ class CutNotesCommand implements ChartEditorCommand
|
|||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
state.currentSelection = notes;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -453,6 +459,7 @@ class FlipNotesCommand implements ChartEditorCommand
|
|||
|
||||
state.currentSelection = flippedNotes;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
|
@ -465,6 +472,7 @@ class FlipNotesCommand implements ChartEditorCommand
|
|||
|
||||
state.currentSelection = notes;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -498,6 +506,7 @@ class PasteNotesCommand implements ChartEditorCommand
|
|||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
||||
state.currentSelection = addedNotes.copy();
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -509,6 +518,7 @@ class PasteNotesCommand implements ChartEditorCommand
|
|||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
||||
state.currentSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -539,6 +549,7 @@ class AddEventsCommand implements ChartEditorCommand
|
|||
// TODO: Allow selecting events.
|
||||
// state.currentSelection = events;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -551,6 +562,7 @@ class AddEventsCommand implements ChartEditorCommand
|
|||
|
||||
state.currentSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -581,6 +593,7 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
|
|||
{
|
||||
note.length = newLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
@ -591,6 +604,7 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
|
|||
{
|
||||
note.length = oldLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import haxe.io.Path;
|
||||
import flixel.addons.display.FlxSliceSprite;
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -31,6 +32,7 @@ import funkin.ui.haxeui.components.CharacterPlayer;
|
|||
import funkin.ui.haxeui.HaxeUIState;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.CheckBox;
|
||||
|
@ -49,11 +51,11 @@ import haxe.ui.events.DragEvent;
|
|||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import lime.media.AudioBuffer;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* A state dedicated to allowing the user to create and edit song charts.
|
||||
|
@ -98,6 +100,11 @@ class ChartEditorState extends HaxeUIState
|
|||
// The height of the menu bar in the layout.
|
||||
static final MENU_BAR_HEIGHT = 32;
|
||||
|
||||
/**
|
||||
* Duration to wait before autosaving the chart.
|
||||
*/
|
||||
static final AUTOSAVE_TIMER_DELAY:Float = 60.0 * 5.0;
|
||||
|
||||
// The amount of padding between the menu bar and the chart grid when fully scrolled up.
|
||||
static final GRID_TOP_PAD:Int = 8;
|
||||
|
||||
|
@ -435,6 +442,38 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
var notePreviewDirty:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether the chart has been modified since it was last saved.
|
||||
* Used to determine whether to auto-save, etc.
|
||||
*/
|
||||
var saveDataDirty(default, set):Bool = false;
|
||||
|
||||
function set_saveDataDirty(value:Bool):Bool
|
||||
{
|
||||
if (value == saveDataDirty)
|
||||
return value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Start the auto-save timer.
|
||||
autoSaveTimer = new FlxTimer().start(AUTOSAVE_TIMER_DELAY, (_) -> autoSave());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop the auto-save timer.
|
||||
autoSaveTimer.cancel();
|
||||
autoSaveTimer.destroy();
|
||||
autoSaveTimer = null;
|
||||
}
|
||||
|
||||
return saveDataDirty = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A timer used to auto-save the chart after a period of inactivity.
|
||||
*/
|
||||
var autoSaveTimer:FlxTimer;
|
||||
|
||||
/**
|
||||
* Whether the difficulty tree view in the toolbox has been modified and needs to be updated.
|
||||
* This happens when we add/remove difficulties.
|
||||
|
@ -847,6 +886,8 @@ class ChartEditorState extends HaxeUIState
|
|||
// Setup the onClick listeners for the UI after it's been created.
|
||||
setupUIListeners();
|
||||
|
||||
setupAutoSave();
|
||||
|
||||
// TODO: We should be loading the music later when the user requests it.
|
||||
// loadDefaultMusic();
|
||||
|
||||
|
@ -1240,6 +1281,48 @@ class ChartEditorState extends HaxeUIState
|
|||
registerContextMenu(null, Paths.ui('chart-editor/context/test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup timers and listerners to handle auto-save.
|
||||
*/
|
||||
function setupAutoSave()
|
||||
{
|
||||
WindowUtil.windowExit.add(onWindowClose);
|
||||
saveDataDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after 5 minutes without saving.
|
||||
*/
|
||||
function autoSave()
|
||||
{
|
||||
saveDataDirty = false;
|
||||
|
||||
// Auto-save the chart.
|
||||
|
||||
#if html5
|
||||
// Auto-save to local storage.
|
||||
#else
|
||||
// Auto-save to temp file.
|
||||
exportAllSongData(true, true);
|
||||
#end
|
||||
}
|
||||
|
||||
function onWindowClose(exitCode:Int)
|
||||
{
|
||||
trace('Window exited with exit code: $exitCode');
|
||||
trace('Should save chart? $saveDataDirty');
|
||||
|
||||
if (saveDataDirty)
|
||||
{
|
||||
exportAllSongData(true);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupAutoSave()
|
||||
{
|
||||
WindowUtil.windowExit.remove(onWindowClose);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
{
|
||||
// dispatchEvent gets called here.
|
||||
|
@ -1268,25 +1351,20 @@ class ChartEditorState extends HaxeUIState
|
|||
handleHelpKeybinds();
|
||||
|
||||
// DEBUG
|
||||
#if debug
|
||||
if (FlxG.keys.justPressed.F)
|
||||
{
|
||||
showNotification('Hi there :)');
|
||||
}
|
||||
// This breaks the layout don't use it.
|
||||
// showNotification('Hi there :)');
|
||||
|
||||
if (FlxG.keys.justPressed.Q)
|
||||
{
|
||||
ChartEditorDialogHandler.openWelcomeDialog(this, true);
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.W)
|
||||
{
|
||||
difficultySelectDirty = true;
|
||||
autoSave();
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.E)
|
||||
{
|
||||
currentSongMetadata.timeChanges[0].timeSignatureNum = (currentSongMetadata.timeChanges[0].timeSignatureNum == 4 ? 3 : 4);
|
||||
}
|
||||
#end
|
||||
|
||||
// Right align the BF health icon.
|
||||
|
||||
|
@ -2959,6 +3037,8 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
super.destroy();
|
||||
|
||||
cleanupAutoSave();
|
||||
|
||||
@:privateAccess
|
||||
ChartEditorNoteSprite.noteFrameCollection = null;
|
||||
}
|
||||
|
@ -2998,7 +3078,11 @@ class ChartEditorState extends HaxeUIState
|
|||
notifBar.hide();
|
||||
}
|
||||
|
||||
public function exportAllSongData():Void
|
||||
/**
|
||||
* @param force Whether to force the export without prompting the user for a file location.
|
||||
* @param tmp If true, save to the temporary directory instead of the local `backup` directory.
|
||||
*/
|
||||
public function exportAllSongData(?force:Bool = false, ?tmp:Bool = false):Void
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
||||
|
@ -3030,6 +3114,16 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
trace('Exporting ${zipEntries.length} files to ZIP...');
|
||||
|
||||
if (force)
|
||||
{
|
||||
var targetPath:String = tmp ? Path.join([FileUtil.getTempDir(), 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']) : Path.join(['./backups/', 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']);
|
||||
|
||||
// We have to force write because the program will die before the save dialog is closed.
|
||||
trace('Force exporting to $targetPath...');
|
||||
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prompt and save.
|
||||
var onSave:Array<String>->Void = (paths:Array<String>) ->
|
||||
{
|
||||
|
@ -3041,6 +3135,6 @@ class ChartEditorState extends HaxeUIState
|
|||
trace('Export cancelled.');
|
||||
};
|
||||
|
||||
FileUtil.saveFilesAsZIP(zipEntries, onSave, onCancel, '$currentSongId-chart.zip');
|
||||
FileUtil.saveMultipleFiles(zipEntries, onSave, onCancel, '$currentSongId-chart.zip');
|
||||
}
|
||||
}
|
||||
|
|
11
source/funkin/util/DateUtil.hx
Normal file
11
source/funkin/util/DateUtil.hx
Normal file
|
@ -0,0 +1,11 @@
|
|||
package funkin.util;
|
||||
|
||||
class DateUtil
|
||||
{
|
||||
public static function generateTimestamp():String
|
||||
{
|
||||
var date = Date.now();
|
||||
return
|
||||
'${date.getFullYear()}-${Std.string(date.getMonth() + 1).lpad('0', 2)}-${Std.string(date.getDate()).lpad('0', 2)}-${Std.string(date.getHours()).lpad('0', 2)}-${Std.string(date.getMinutes()).lpad('0', 2)}-${Std.string(date.getSeconds()).lpad('0', 2)}';
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.util;
|
||||
|
||||
import cpp.abi.Abi;
|
||||
import haxe.zip.Entry;
|
||||
import lime.utils.Bytes;
|
||||
import lime.ui.FileDialog;
|
||||
|
@ -222,7 +223,8 @@ class FileUtil
|
|||
* @param typeFilter TODO What does this do?
|
||||
* @return Whether the file dialog was opened successfully.
|
||||
*/
|
||||
public static function saveMultipleFiles(resources:Array<Entry>, ?onSaveAll:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String, ?force:Bool = false):Bool
|
||||
public static function saveMultipleFiles(resources:Array<Entry>, ?onSaveAll:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String,
|
||||
?force:Bool = false):Bool
|
||||
{
|
||||
#if desktop
|
||||
// Prompt the user for a directory, then write all of the files to there.
|
||||
|
@ -232,17 +234,23 @@ class FileUtil
|
|||
for (resource in resources)
|
||||
{
|
||||
var filePath = haxe.io.Path.join([targetPath, resource.fileName]);
|
||||
try {
|
||||
if (resource.data == null) {
|
||||
try
|
||||
{
|
||||
if (resource.data == null)
|
||||
{
|
||||
trace('WARNING: File $filePath has no data or content. Skipping.');
|
||||
continue;
|
||||
} else {
|
||||
writeBytesToPath(filePath, resource.data, force);
|
||||
}
|
||||
} catch (e:Dynamic) {
|
||||
trace('Failed to write file (probably already exists): $filePath' + filePath);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
writeBytesToPath(filePath, resource.data, force ? Force : Skip);
|
||||
}
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
trace('Failed to write file (probably already exists): $filePath' + filePath);
|
||||
continue;
|
||||
}
|
||||
paths.push(filePath);
|
||||
}
|
||||
onSaveAll(paths);
|
||||
|
@ -264,7 +272,9 @@ class FileUtil
|
|||
/**
|
||||
* Takes an array of file entries and prompts the user to save them as a ZIP file.
|
||||
*/
|
||||
public static function saveFilesAsZIP(resources:Array<Entry>, ?onSave:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String, ?force:Bool = false):Bool {
|
||||
public static function saveFilesAsZIP(resources:Array<Entry>, ?onSave:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String,
|
||||
?force:Bool = false):Bool
|
||||
{
|
||||
// Create a ZIP file.
|
||||
var zipBytes = createZIPFromEntries(resources);
|
||||
|
||||
|
@ -280,46 +290,162 @@ class FileUtil
|
|||
}
|
||||
|
||||
/**
|
||||
* Write string file contents directly to a given path.
|
||||
* Only works on desktop.
|
||||
* Takes an array of file entries and forcibly writes a ZIP to the given path.
|
||||
* Only works on desktop, because HTML5 doesn't allow you to write files to arbitrary paths.
|
||||
* Use `saveFilesAsZIP` instead.
|
||||
* @param force Whether to force overwrite an existing file.
|
||||
*/
|
||||
public static function writeStringToPath(path:String, data:String, force:Bool = false)
|
||||
public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, ?force:Bool = false):Bool
|
||||
{
|
||||
if (force || !sys.FileSystem.exists(path))
|
||||
#if desktop
|
||||
// Create a ZIP file.
|
||||
var zipBytes = createZIPFromEntries(resources);
|
||||
|
||||
// Write the ZIP.
|
||||
writeBytesToPath(path, zipBytes, force ? Force : Skip);
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Write string file contents directly to a given path.
|
||||
* Only works on desktop.
|
||||
*
|
||||
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
||||
*/
|
||||
public static function writeStringToPath(path:String, data:String, mode:FileWriteMode = Skip)
|
||||
{
|
||||
#if sys
|
||||
createDirIfNotExists(Path.directory(path));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
sys.io.File.saveContent(path, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw 'File already exists: $path';
|
||||
case Force:
|
||||
sys.io.File.saveContent(path, data);
|
||||
case Skip:
|
||||
if (!sys.FileSystem.exists(path))
|
||||
{
|
||||
sys.io.File.saveContent(path, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw 'File already exists: $path';
|
||||
}
|
||||
case Ask:
|
||||
if (sys.FileSystem.exists(path))
|
||||
{
|
||||
// TODO: We don't have the technology to use native popups yet.
|
||||
}
|
||||
else
|
||||
{
|
||||
sys.io.File.saveContent(path, data);
|
||||
}
|
||||
}
|
||||
#else
|
||||
throw 'Direct file writing by path not supported on this platform.';
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Write byte file contents directly to a given path.
|
||||
* Only works on desktop.
|
||||
* Only works on desktop.
|
||||
*
|
||||
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
||||
*/
|
||||
public static function writeBytesToPath(path:String, data:Bytes, force:Bool = false)
|
||||
public static function writeBytesToPath(path:String, data:Bytes, mode:FileWriteMode = Skip)
|
||||
{
|
||||
if (force || !sys.FileSystem.exists(path))
|
||||
#if sys
|
||||
createDirIfNotExists(Path.directory(path));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
sys.io.File.saveBytes(path, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw 'File already exists: $path';
|
||||
case Force:
|
||||
sys.io.File.saveBytes(path, data);
|
||||
case Skip:
|
||||
if (!sys.FileSystem.exists(path))
|
||||
{
|
||||
sys.io.File.saveBytes(path, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw 'File already exists: $path';
|
||||
}
|
||||
case Ask:
|
||||
if (sys.FileSystem.exists(path))
|
||||
{
|
||||
// TODO: We don't have the technology to use native popups yet.
|
||||
}
|
||||
else
|
||||
{
|
||||
sys.io.File.saveBytes(path, data);
|
||||
}
|
||||
}
|
||||
#else
|
||||
throw 'Direct file writing by path not supported on this platform.';
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Write string file contents directly to the end of a file at the given path.
|
||||
* Only works on desktop.
|
||||
* Only works on desktop.
|
||||
*/
|
||||
public static function appendStringToPath(path:String, data:String)
|
||||
{
|
||||
sys.io.File.append(path, false).writeString(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory if it doesn't already exist.
|
||||
* Only works on desktop.
|
||||
*/
|
||||
public static function createDirIfNotExists(dir:String)
|
||||
{
|
||||
#if sys
|
||||
if (!sys.FileSystem.exists(dir))
|
||||
{
|
||||
sys.FileSystem.createDirectory(dir);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
static var tempDir:String = null;
|
||||
static final TEMP_ENV_VARS:Array<String> = ['TEMP', 'TMPDIR', 'TEMPDIR', 'TMP'];
|
||||
|
||||
/**
|
||||
* Get the path to a temporary directory we can use for writing files.
|
||||
* Only works on desktop.
|
||||
*/
|
||||
public static function getTempDir():String
|
||||
{
|
||||
if (tempDir != null)
|
||||
return tempDir;
|
||||
|
||||
#if sys
|
||||
#if windows
|
||||
var path:String = null;
|
||||
|
||||
for (envName in TEMP_ENV_VARS)
|
||||
{
|
||||
path = Sys.getEnv(envName);
|
||||
|
||||
if (path == "")
|
||||
path = null;
|
||||
if (path != null)
|
||||
break;
|
||||
}
|
||||
|
||||
return tempDir = Path.join([path, 'funkin/']);
|
||||
#else
|
||||
return tempDir = '/tmp/funkin/';
|
||||
#end
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Bytes object containing a ZIP file, containing the provided entries.
|
||||
*
|
||||
|
@ -329,7 +455,7 @@ class FileUtil
|
|||
public static function createZIPFromEntries(entries:Array<Entry>):Bytes
|
||||
{
|
||||
var o = new haxe.io.BytesOutput();
|
||||
|
||||
|
||||
var zipWriter = new haxe.zip.Writer(o);
|
||||
zipWriter.write(entries.list());
|
||||
|
||||
|
@ -348,24 +474,23 @@ class FileUtil
|
|||
var data = haxe.io.Bytes.ofString(content, UTF8);
|
||||
|
||||
return {
|
||||
fileName : name,
|
||||
fileSize : data.length,
|
||||
|
||||
data : data,
|
||||
dataSize : data.length,
|
||||
fileName: name,
|
||||
fileSize: data.length,
|
||||
|
||||
compressed : false,
|
||||
|
||||
fileTime : Date.now(),
|
||||
crc32 : null,
|
||||
extraFields : null,
|
||||
data: data,
|
||||
dataSize: data.length,
|
||||
|
||||
compressed: false,
|
||||
|
||||
fileTime: Date.now(),
|
||||
crc32: null,
|
||||
extraFields: null,
|
||||
};
|
||||
}
|
||||
|
||||
static function convertTypeFilter(typeFilter:Array<FileFilter>):String
|
||||
{
|
||||
var filter = null;
|
||||
|
||||
if (typeFilter != null)
|
||||
{
|
||||
var filters = [];
|
||||
|
@ -379,3 +504,21 @@ class FileUtil
|
|||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
enum FileWriteMode
|
||||
{
|
||||
/**
|
||||
* Forcibly overwrite the file if it already exists.
|
||||
*/
|
||||
Force;
|
||||
|
||||
/**
|
||||
* Ask the user if they want to overwrite the file if it already exists.
|
||||
*/
|
||||
Ask;
|
||||
|
||||
/**
|
||||
* Skip the file if it already exists.
|
||||
*/
|
||||
Skip;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
|
||||
class WindowUtil
|
||||
{
|
||||
public static function openURL(targetUrl:String)
|
||||
|
@ -12,7 +14,23 @@ class WindowUtil
|
|||
FlxG.openURL(targetUrl);
|
||||
#end
|
||||
#else
|
||||
trace('Cannot open')
|
||||
trace('Cannot open');
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatched when the game window is closed.
|
||||
*/
|
||||
public static final windowExit:FlxTypedSignal<Int->Void> = new FlxTypedSignal<Int->Void>();
|
||||
|
||||
public static function initWindowEvents()
|
||||
{
|
||||
// onUpdate is called every frame just before rendering.
|
||||
|
||||
// onExit is called when the game window is closed.
|
||||
openfl.Lib.current.stage.application.onExit.add(function(exitCode:Int)
|
||||
{
|
||||
windowExit.dispatch(exitCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package funkin.util.assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class DataAssets
|
||||
{
|
||||
static function buildDataPath(path:String):String
|
||||
|
|
Loading…
Reference in a new issue