Options Rewrite WIP All Editor Menus Done

This commit is contained in:
Ralty
2025-07-27 08:12:11 +07:00
parent d629b6afe4
commit b15e3bb5aa
20 changed files with 505 additions and 513 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+13
View File
@@ -46,7 +46,9 @@
<str id="acceptDifficulty">Press ACCEPT to edit the chart for the selected difficulty.</str>
<str id="selectDifficulty">Select a difficulty to continue.</str>
<str id="newDifficulty">New Difficulty</str>
<str id="newDifficultyDesc">Press ACCEPT to create a new chart difficulty.</str>
<str id="newSong">New Song</str>
<str id="newSongDesc">Press ACCEPT to create a new song.</str>
</group>
<group name="ChartCreationScreen" prefix="chartCreation.">
@@ -435,6 +437,11 @@
<str id="win-title">Creating New Character</str>
<str id="charName">Character Name $* Required$</str>
<str id="charImgName">Character Image File $* Required$</str>
<group name="Warnings" prefix="warnings.">
<str id="char-exists-title">Creating Character: Error!</str>
<str id="char-exists-body">The character you are trying to create already exists, if you would like to override it delete the character first!</str>
</group>
</group>
</group>
@@ -442,6 +449,7 @@
<group name="AlphabetSelection" prefix="selection.">
<str id="acceptTypeface">Press ACCEPT to edit this typeface.</str>
<str id="newTypeface">New Typeface</str>
<str id="newTypefaceDesc">Press ACCEPT to create a new typeface.</str>
<str id="desc">Select a typeface to edit</str>
<group name="Warnings" prefix="warnings.">
@@ -554,6 +562,11 @@
<str id="title">Stage Info</str>
<str id="stageName">Stage Name</str>
<str id="stagePath">Stage Path</str>
<group name="Warnings" prefix="warnings.">
<str id="stage-exists-title">Creating Stage: Error!</str>
<str id="stage-exists-body">The stage you are trying to create alreadly exists, if you would like to override it delete the stage first!</str>
</group>
</group>
<group name="StageInfoScreen" prefix="stageInfoScreen">
+42 -3
View File
@@ -1,18 +1,57 @@
package funkin.editors;
import funkin.backend.utils.NativeAPI;
import funkin.options.OptionsScreen;
import funkin.options.TreeMenu;
import funkin.options.TreeMenuScreen;
import funkin.options.type.*;
class DebugOptions extends TreeMenu {
public override function create() {
var bg:FlxSprite;
override function create() {
super.create();
UIState.setResolutionAware();
add(bg = new FlxSprite().loadAnimatedGraphic(Paths.image('menus/menuBGBlue')));
bg.antialiasing = true;
bg.scrollFactor.set();
updateBG();
addMenu(new TreeMenuScreen('DebugOptions.title', 'DebugOptions.desc'));
FlxG.camera.fade(0xFF000000, 0.5, true);
}
public function updateBG() {
var scaleX:Float = FlxG.width / bg.width;
var scaleY:Float = FlxG.height / bg.height;
bg.scale.x = bg.scale.y = Math.max(scaleX, scaleY) * 1.15;
bg.screenCenter();
}
override function onResize(width:Int, height:Int) {
super.onResize(width, height);
if (!UIState.resolutionAware) return;
updateBG();
}
}
class DebugOptionsScreen extends OptionsScreen {
class DebugOptionsScreen extends TreeMenuScreen {
public function new() {
super('DebugOptions.title', 'DebugOptions.desc', 'DebugOptions.');
#if windows
add(new TextOption(getNameID("showConsole"), getDescID("showConsole"), () -> NativeAPI.allocConsole()));
#end
add(new Checkbox(getNameID("editorsResizable"), getDescID("editorsResizable"), "editorsResizable"));
add(new Checkbox(getNameID("bypassEditorsResize"), getDescID("bypassEditorsResize"), "bypassEditorsResize"));
add(new Checkbox(getNameID("editorSFX"), getDescID("editorSFX"), "editorSFX"));
add(new Checkbox(getNameID("editorPrettyPrint"), getDescID("editorPrettyPrint"), "editorPrettyPrint"));
add(new Checkbox(getNameID("intensiveBlur"), getDescID("intensiveBlur"), "intensiveBlur"));
add(new Checkbox(getNameID("charterAutoSaves"), getDescID("charterAutoSaves"), "charterAutoSaves"));
add(new NumOption(getNameID("charterAutoSaveTime"), getDescID("charterAutoSaveTime"), 60, 60*10, 1, "charterAutoSaveTime"));
add(new NumOption(getNameID("charterAutoSaveWarningTime"), getDescID("charterAutoSaveWarningTime"), 0, 15, 1, "charterAutoSaveWarningTime"));
add(new Checkbox(getNameID("charterAutoSavesSeparateFolder"), getDescID("charterAutoSavesSeparateFolder"), "charterAutoSavesSeparateFolder"));
}
}
+47 -14
View File
@@ -1,28 +1,25 @@
package funkin.editors;
import flixel.addons.display.FlxBackdrop;
import funkin.menus.MainMenuState;
import funkin.options.*;
import funkin.options.type.OptionType;
class EditorTreeMenu extends TreeMenu {
class EditorTreeMenu extends funkin.options.TreeMenu {
public var bg:FlxBackdrop;
public var bgType:String = "default";
public var bgMovement:FlxPoint = new FlxPoint();
public var main:OptionsScreen;
public override function create() {
override function create() {
super.create();
UIState.setResolutionAware();
FlxG.camera.fade(0xFF000000, 0.5, true);
}
bg = new FlxBackdrop();
override function createPost() {
super.createPost();
add(bg = new FlxBackdrop());
bg.loadGraphic(Paths.image('editors/bgs/${bgType}'));
bg.antialiasing = true;
setBackgroundRotation(-5);
add(bg);
}
public inline function setBackgroundRotation(rotation:Float) {
@@ -30,11 +27,47 @@ class EditorTreeMenu extends TreeMenu {
bg.velocity.set(85, 0).degrees = bg.rotation;
}
public override function exit() {
FlxG.switchState(new MainMenuState());
override function exit() {
FlxG.switchState(new funkin.menus.MainMenuState());
}
public function onMenuChange() {
override function update(elapsed:Float) {
super.update(elapsed);
bg.colorTransform.redOffset = lerp(bg.colorTransform.redOffset, 0, 0.0625);
bg.colorTransform.greenOffset = lerp(bg.colorTransform.greenOffset, 0, 0.0625);
bg.colorTransform.blueOffset = lerp(bg.colorTransform.blueOffset, 0, 0.0625);
bg.colorTransform.redMultiplier = lerp(bg.colorTransform.redMultiplier, 1, 0.0625);
bg.colorTransform.greenMultiplier = lerp(bg.colorTransform.greenMultiplier, 1, 0.0625);
bg.colorTransform.blueMultiplier = lerp(bg.colorTransform.blueMultiplier, 1, 0.0625);
}
override function menuChanged() @:privateAccess {
super.menuChanged();
if (previousMenus.length > 0) return; // selected a sub-menu
var prev = tree[tree.length - 2];
if (prev == null || prev.curOption == null || !(prev.curOption is OptionType)) return;
// small flashbang
var color = cast(prev.curOption, OptionType).editorFlashColor;
bg.colorTransform.redOffset = 0.25 * color.red;
bg.colorTransform.greenOffset = 0.25 * color.green;
bg.colorTransform.blueOffset = 0.25 * color.blue;
bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25);
bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
}
}
class EditorTreeMenuScreen extends funkin.options.TreeMenuScreen {
public function new(name:String, desc:String, ?prefix:String, ?objects:Array<FlxSprite>,
?newButton:String, ?newButtonDesc:String, ?newCallback:Void->Void)
{
super(name, desc, prefix, objects);
if (newCallback != null) {
insert(0, new funkin.options.type.NewOption(getID(newButton), getID(newButtonDesc), newCallback));
curSelected = 1;
}
}
}
@@ -1,51 +1,35 @@
package funkin.editors.alphabet;
import haxe.io.Path;
import haxe.xml.Access;
import funkin.game.Character;
import funkin.options.OptionsScreen;
import funkin.options.type.IconOption;
import funkin.editors.EditorTreeMenu;
import funkin.options.type.NewOption;
import funkin.options.type.TextOption;
import funkin.options.type.OptionType;
import haxe.io.Path;
class AlphabetSelection extends EditorTreeMenu
{
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("editor.alphabet.selection." + id, args);
public override function create()
{
bgType = "charter";
class AlphabetSelection extends EditorTreeMenu {
override function create() {
super.create();
DiscordUtil.call("onEditorTreeLoaded", ["Alphabet Editor"]);
addMenu(new AlphabetSelectionScreen());
}
}
class AlphabetSelectionScreen extends EditorTreeMenuScreen {
public function new() {
super('editor.alphabet.name', 'editor.alphabet.selection.desc', 'editor.alphabet.selection', 'newTypeface', 'newTypefaceDesc', () -> {
parent.openSubState(new UIWarningSubstate(translate('warnings.notImplemented-title'), translate('warnings.notImplemented-body'), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}}
]));
});
var modsList:Array<String> = [];
for(file in Paths.getFolderContent('data/alphabet/', true, BOTH)) // mods ? MODS : BOTH
for (file in Paths.getFolderContent('data/alphabet/', true, BOTH)) // mods ? MODS : BOTH
if (Path.extension(file) == "xml") modsList.push(CoolUtil.getFilename(file));
var list:Array<OptionType> = [
for (typeface in modsList)
new AlphabetIconOption(typeface, translate("acceptTypeface"), typeface, function() {
FlxG.switchState(new AlphabetEditor(typeface));
})
];
var newChar = translate("newTypeface");
list.insert(0, new NewOption(newChar, newChar, function() {
openSubState(new UIWarningSubstate(translate("warnings.notImplemented-title"), translate("warnings.notImplemented-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}}
]));
}));
main = new OptionsScreen(TU.translate("editor.alphabet.name"), translate("desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Alphabet Editor"]);
}
override function createPost() {
super.createPost();
main.changeSelection(1);
for (typeface in modsList)
add(new AlphabetIconOption(typeface, getID('acceptTypeface'), typeface, () -> FlxG.switchState(new AlphabetEditor(typeface))));
}
}
@@ -57,7 +41,7 @@ class AlphabetIconOption extends TextOption {
var xml = Xml.parse(Assets.getText(Paths.xml('alphabet/$typeface'))).firstElement();
var spritesheet = null;
for(node in xml.elements()) {
for (node in xml.elements()) {
if (node.nodeName == "spritesheet") {
spritesheet = node.firstChild().nodeValue;
break;
@@ -73,14 +57,14 @@ class AlphabetIconOption extends TextOption {
iconSpr.frames = Paths.getFrames(spritesheet);
iconSpr.antialiasing = true;
var frameToUse = iconSpr.frames.frames[0];
for(frame in iconSpr.frames.frames) {
for (frame in iconSpr.frames.frames) {
if (frame.name.toUpperCase().startsWith("A")) {
frameToUse = frame;
break;
}
}
iconSpr.frame = frameToUse;
if(useColorOffsets) {
if (useColorOffsets) {
iconSpr.colorTransform.color = -1;
}
iconSpr.setPosition(90 - iconSpr.width - 20, (__text.height - iconSpr.height) / 2);
@@ -1,81 +1,69 @@
package funkin.editors.character;
import haxe.xml.Printer;
import funkin.editors.ui.UIImageExplorer.ImageSaveData;
import funkin.game.Character;
import funkin.options.OptionsScreen;
import funkin.editors.ui.UIImageExplorer.ImageSaveData;
import funkin.editors.EditorTreeMenu;
import funkin.options.type.IconOption;
import funkin.options.type.NewOption;
import funkin.options.type.TextOption;
import funkin.options.type.OptionType;
import funkin.backend.assets.ModsFolder;
class CharacterSelection extends EditorTreeMenu
{
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterSelection." + id, args);
class CharacterSelection extends EditorTreeMenu {
override function create() {
super.create();
DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]);
addMenu(new CharacterSelectionScreen());
}
}
class CharacterSelectionScreen extends EditorTreeMenuScreen {
public var modsList:Array<String> = [];
public override function create()
{
bgType = "charter";
super.create();
public function new() {
super('editor.character.name', 'characterSelection.desc', 'characterSelection.', 'newCharacter', 'newCharacterDesc', () -> {
parent.openSubState(new CharacterCreationScreen(createCharacter));
});
var isMods:Bool = true;
modsList = Character.getList(true, true);
if(modsList.length == 0) {
if (modsList.length == 0) {
modsList = Character.getList(false, true);
isMods = false;
}
function generateList(modsList:Array<String>, isMods:Bool, folderPath:String = ""):Array<OptionType> {
var list:Array<OptionType> = [];
function generateList(modsList:Array<String>, isMods:Bool, folderPath:String = ""):Array<FlxSprite> {
var list:Array<FlxSprite> = [];
for(char in modsList) {
if(char.endsWith("/")) {
for (char in modsList) {
if (char.endsWith("/")) {
var folderName = CoolUtil.getFilename(char.substr(0, char.length-1));
list.push(new TextOption(folderName + " >", translate("acceptFolder"), function() {
list.push(new TextOption(folderName, getID('acceptFolder'), ' >', () -> {
var newModsList = Character.getList(isMods, true, char);
var newList:Array<OptionType> = generateList(newModsList, isMods, folderPath + folderName + "/");
optionsTree.add(new OptionsScreen(folderPath + folderName, translate("desc-folder", [folderPath + folderName + "/"]), newList));
var newList:Array<FlxSprite> = generateList(newModsList, isMods, folderPath + folderName + "/");
parent.addMenu(new EditorTreeMenuScreen(folderPath + folderName, translate('desc-folder', [folderPath + folderName + "/"]), newList));
}));
}
else {
list.push(new IconOption(char, getID('acceptCharacter'), Character.getIconFromCharName(folderPath + char, char), () -> {
FlxG.switchState(new CharacterEditor(folderPath + char));
}));
} else {
list.push(new IconOption(char, translate("acceptCharacter"), Character.getIconFromCharName(folderPath + char, char),
function() {
FlxG.switchState(new CharacterEditor(folderPath + char));
})
);
}
}
list.insert(0, new NewOption(translate("newCharacter"), translate("newCharacterDesc"), function() {
openSubState(new CharacterCreationScreen(createCharacter));
}));
return list;
}
var list = generateList(modsList, isMods);
main = new OptionsScreen(TU.translate("editor.character.name"), translate("desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]);
}
override function createPost() {
super.createPost();
main.changeSelection(1);
for (o in generateList(modsList, isMods)) add(o);
}
public function createCharacter(name:String, imageSaveData:ImageSaveData, xml:Xml) {
var characterAlreadyExists:Bool = modsList.contains(name);
if (characterAlreadyExists) {
openSubState(new UIWarningSubstate("Creating Character: Error!", "The character you are trying to create already exists, if you would like to override it delete the character first!", [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
parent.openSubState(new UIWarningSubstate(TU.translate('characterCreationScreen.warning.char-exists-title'), TU.translate('characterCreationScreen.warning.char-exists-body'), [
{label: TU.translate('editor.ok'), color: 0xFFFF0000, onClick: (t) -> {}}
]));
return;
}
@@ -88,11 +76,10 @@ class CharacterSelection extends EditorTreeMenu
UIImageExplorer.saveFilesGlobal(imageSaveData, '${Paths.getAssetsRoot()}/images/characters');
// Add to Menu >:D
var option:IconOption = new IconOption(name, "Press ACCEPT to edit this character.", Character.getIconFromCharName(name),
function() {
FlxG.switchState(new CharacterEditor(name));
}
);
optionsTree.members[optionsTree.members.length-1].insert(1, option);
var option:IconOption = new IconOption(name, getID('acceptCharacter'), Character.getIconFromCharName(name), () -> {
FlxG.switchState(new CharacterEditor(name));
});
insert(1, option);
}
}
+56 -113
View File
@@ -1,118 +1,83 @@
package funkin.editors.charter;
import flixel.util.FlxColor;
import funkin.backend.chart.Chart;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.backend.chart.ChartData;
import funkin.backend.system.framerate.Framerate;
import funkin.editors.EditorTreeMenu;
import funkin.editors.charter.SongCreationScreen.SongCreationData;
import funkin.editors.EditorTreeMenu;
import funkin.menus.FreeplayState.FreeplaySonglist;
import funkin.options.*;
import funkin.options.type.*;
import funkin.options.type.NewOption;
import haxe.Json;
using StringTools;
class CharterSelection extends EditorTreeMenu {
public var freeplayList:FreeplaySonglist;
public var curSong:ChartMetaData;
public override function create() {
bgType = "charter";
override function create() {
super.create();
DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]);
addMenu(new CharterSelectionScreen());
bgType = 'charter';
}
}
Framerate.offset.y = 60;
class CharterSelectionScreen extends EditorTreeMenuScreen {
public var freeplayList:FreeplaySonglist;
public var songList:Array<String> = [];
public var curSong:ChartMetaData;
inline public function makeChartOption(d:String, name:String):TextOption {
return new TextOption(d, getID('acceptDifficulty'), () -> FlxG.switchState(new Charter(name, d)));
}
public function makeSongOption(s:ChartMetaData):IconOption {
songList.push(s.name.toLowerCase());
var opt = new IconOption(s.name, getID('acceptSong'), s.icon, () -> {
curSong = s;
var screen = new EditorTreeMenuScreen(s.name, getID('selectDifficulty'), [
for (d in s.difficulties) if (d != '') makeChartOption(d, s.name)
]);
#if sys
screen.insert(0, new NewOption(getID('newDifficulty'), getID('newDifficultyDesc'), () -> {
parent.openSubState(new ChartCreationScreen(saveChart));
}));
screen.curSelected = 1;
#end
parent.addMenu(screen);
});
opt.suffix = ' >';
opt.color = s.color.getDefault(FlxColor.WHITE);
return opt;
}
public function new() {
super('editor.chart.name', 'charterSelection.desc', 'charterSelection.', 'newSong', 'newSongDesc', () -> {
parent.openSubState(new SongCreationScreen(saveSong));
});
freeplayList = FreeplaySonglist.get(false);
var list:Array<OptionType> = [
for(s in freeplayList.songs) new EditorIconOption(s.name, TU.translate("charterSelection.acceptSong"), s.icon, function() {
curSong = s;
var list:Array<OptionType> = [
for(d in s.difficulties) if (d != "")
new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() {
FlxG.switchState(new Charter(s.name, d));
})
];
#if sys
var newDiff = TU.translate("charterSelection.newDifficulty");
list.push(new NewOption(newDiff, newDiff, function() {
FlxG.state.openSubState(new ChartCreationScreen(saveChart));
}));
#end
optionsTree.add(new OptionsScreen(s.name, TU.translate("charterSelection.selectDifficulty"), list));
}, s.color.getDefault(0xFFFFFFFF))
];
#if sys
var newSong = TU.translate("charterSelection.newSong");
list.insert(0, new NewOption(newSong, newSong, function() {
FlxG.state.openSubState(new SongCreationScreen(saveSong));
}));
#end
main = new OptionsScreen(TU.translate("editor.chart.name"), TU.translate("charterSelection.desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]);
}
override function createPost() {
super.createPost();
main.changeSelection(1);
}
public override function update(elapsed:Float) {
super.update(elapsed);
bg.colorTransform.redOffset = lerp(bg.colorTransform.redOffset, 0, 0.0625);
bg.colorTransform.greenOffset = lerp(bg.colorTransform.greenOffset, 0, 0.0625);
bg.colorTransform.blueOffset = lerp(bg.colorTransform.blueOffset, 0, 0.0625);
bg.colorTransform.redMultiplier = lerp(bg.colorTransform.redMultiplier, 1, 0.0625);
bg.colorTransform.greenMultiplier = lerp(bg.colorTransform.greenMultiplier, 1, 0.0625);
bg.colorTransform.blueMultiplier = lerp(bg.colorTransform.blueMultiplier, 1, 0.0625);
}
public override function onMenuChange() {
super.onMenuChange();
if (optionsTree.members.length > 1) { // selected a song
if(main != null) {
var opt = main.members[main.curSelected];
if(opt is EditorIconOption) {
var opt:EditorIconOption = cast opt;
// small flashbang
var color = opt.flashColor;
bg.colorTransform.redOffset = 0.25 * color.red;
bg.colorTransform.greenOffset = 0.25 * color.green;
bg.colorTransform.blueOffset = 0.25 * color.blue;
bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25);
bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
}
}
}
for (i => s in freeplayList.songs) add(makeSongOption(s));
}
#if sys
public function saveSong(creation:SongCreationData, ?callback:String -> SongCreationData -> Void) {
var songAlreadyExists:Bool = [for (s in freeplayList.songs) s.name.toLowerCase()].contains(creation.meta.name.toLowerCase());
var songAlreadyExists:Bool = songList.contains(creation.meta.name.toLowerCase());
if (songAlreadyExists) {
openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}},
parent.openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
// Paths
var songsDir:String = '${Paths.getAssetsRoot()}/songs/';
var songFolder:String = '$songsDir${creation.meta.name}';
var songFolder:String = '${Paths.getAssetsRoot()}/songs/${creation.meta.name}';
#if sys
// Make Directories
sys.FileSystem.createDirectory(songFolder);
CoolUtil.addMissingFolders(songFolder);
sys.FileSystem.createDirectory('$songFolder/song');
sys.FileSystem.createDirectory('$songFolder/charts');
@@ -125,52 +90,30 @@ class CharterSelection extends EditorTreeMenu {
if (creation.oppVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Opponent.${Flags.SOUND_EXT}', creation.oppVocals);
#end
if (callback != null)
callback(songFolder, creation);
var option = new EditorIconOption(creation.meta.name, TU.translate("charterSelection.acceptSong"), creation.meta.icon, function() {
curSong = creation.meta;
var list:Array<OptionType> = [
for(d in creation.meta.difficulties)
if (d != "") new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() {
FlxG.switchState(new Charter(creation.meta.name, d));
})
];
var newDiff = TU.translate("charterSelection.newDifficulty");
list.push(new NewOption(newDiff, newDiff, function() {
FlxG.state.openSubState(new ChartCreationScreen(saveChart));
}));
optionsTree.insert(1, new OptionsScreen(creation.meta.name, TU.translate("charterSelection.selectDifficulty"), list));
}, creation.meta.color.getDefault(0xFFFFFFFF));
if (callback != null) callback(songFolder, creation);
// Add to List
freeplayList.songs.insert(0, creation.meta);
main.insert(1, option);
insert(1, makeSongOption(creation.meta));
}
public function saveChart(name:String, data:ChartData) {
var difficultyAlreadyExists:Bool = curSong.difficulties.contains(name);
if (difficultyAlreadyExists) {
openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}},
parent.openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
// Paths
var songFolder:String = '${Paths.getAssetsRoot()}/songs/${curSong.name}';
// Save Files
CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, Flags.JSON_PRETTY_PRINT));
// Add to List
// duplicated code, todo: fix this
curSong.difficulties.push(name);
var option = new TextOption(name, TU.translate("charterSelection.acceptDifficulty"), function() {
FlxG.switchState(new Charter(curSong.name, name));
});
optionsTree.members[optionsTree.members.length-1].insert(optionsTree.members[optionsTree.members.length-1].length-1, option);
parent.tree.last().insert(parent.tree.last().length - 1, makeChartOption(name, curSong.name));
// Add to Meta
var meta = Json.parse(sys.io.File.getContent('$songFolder/meta.json'));
+31 -38
View File
@@ -1,46 +1,47 @@
package funkin.editors.stage;
import funkin.editors.stage.StageCreationScreen.StageCreationData;
import funkin.editors.EditorTreeMenu;
import funkin.game.Stage;
import funkin.options.OptionsScreen;
import funkin.options.type.NewOption;
import funkin.options.type.OptionType;
import funkin.options.type.TextOption;
class StageSelection extends EditorTreeMenu
{
using StringTools;
class StageSelection extends EditorTreeMenu {
override function create() {
super.create();
DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
addMenu(new StageSelectionScreen());
}
}
class StageSelectionScreen extends EditorTreeMenuScreen {
public var stages:Array<String> = [];
public override function create()
{
bgType = "charter";
super.create();
public function makeStageOption(stage:String):TextOption {
return new TextOption(stage, getID('acceptStage'), () -> {
FlxG.switchState(new StageEditor(stage));
});
}
public function new() {
super('editor.stage.name', 'stageSelection.desc', 'stageSelection.', 'newStage', 'acceptNewStage', () -> {
parent.openSubState(new StageCreationScreen(saveStage));
});
var modsList:Array<String> = Stage.getList(true, true);
var list:Array<OptionType> = [
for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) {
stages.push(stage);
new TextOption(stage, TU.translate("stageSelection.acceptStage"),
function() {
FlxG.switchState(new StageEditor(stage));
});
}
];
list.insert(0, new NewOption(TU.translate("stageSelection.newStage"), TU.translate("stageSelection.acceptNewStage"), function() {
FlxG.state.openSubState(new StageCreationScreen(saveStage));
}));
main = new OptionsScreen(TU.translate("editor.stage.name"), TU.translate("stageSelection.desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) {
stages.push(stage.toLowerCase());
add(makeStageOption(stage));
}
}
public function saveStage(creation:StageCreationData) {
if ([for (s in stages) s.toLowerCase()].contains(creation.name.toLowerCase())) {
openSubState(new UIWarningSubstate("Creating Stage: Error!", "The stage you are trying to create alreadly exists, if you would like to override it delete the stage first!", [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
if (stages.contains(creation.name.toLowerCase())) {
parent.openSubState(new UIWarningSubstate(TU.translate("stageCreationScreen.warnings.stage-exists-title"), TU.translate("stageCreationScreen.warnings.stage-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
@@ -51,15 +52,7 @@ class StageSelection extends EditorTreeMenu
#end
// Add to List
var option = new TextOption(creation.name, TU.translate("stageSelection.acceptStage"), function() {
FlxG.switchState(new StageEditor(creation.name));
});
optionsTree.members[optionsTree.members.length-1].insert(1, option);
}
override function createPost() {
super.createPost();
main.changeSelection(1);
stages.push(creation.name.toLowerCase());
parent.tree.last().insert(parent.tree.last().length - 1, makeStageOption(creation.name));
}
}
+2 -2
View File
@@ -6,7 +6,7 @@ import flixel.tweens.FlxTween;
import funkin.backend.FunkinText;
import funkin.backend.scripting.events.menu.MenuChangeEvent;
import funkin.backend.scripting.events.NameEvent;
//import funkin.menus.credits.CreditsMain;
import funkin.menus.credits.CreditsMain;
import funkin.options.OptionsMenu;
import lime.app.Application;
@@ -168,7 +168,7 @@ class MainMenuState extends MusicBeatState
{
case 'story mode': FlxG.switchState(new StoryMenuState());
case 'freeplay': FlxG.switchState(new FreeplayState());
//case 'donate', 'credits': FlxG.switchState(new CreditsMain()); // kept donate for not breaking scripts, if you don't want donate to bring you to the credits menu, thats easy softcodable - Nex
case 'donate', 'credits': FlxG.switchState(new CreditsMain()); // kept donate for not breaking scripts, if you don't want donate to bring you to the credits menu, thats easy softcodable - Nex
case 'options': FlxG.switchState(new OptionsMenu());
}
});
@@ -0,0 +1,135 @@
package funkin.menus.credits;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.backend.system.github.GitHub;
import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor;
import funkin.options.PlayerSettings;
import funkin.options.type.GithubIconOption;
using StringTools;
class CreditsCodename extends funkin.options.TreeMenuScreen {
public var error:Bool = false;
public var totalContributions:Int = 0;
public var contribFormats:Array<FlxTextFormatMarkerPair> = [];
public function new() {
super("Codename Engine", "credits.allContributors");
tryUpdating(true);
}
// blame the secondary threads if the code has to look this bad - Nex
private var _canReset:Bool = true;
private var _downloadingSteps:Int = 0;
override function update(elapsed:Float) {
super.update(elapsed);
if (_downloadingSteps == 2) {
_downloadingSteps = 0;
_canReset = true;
displayList();
} else if (_downloadingSteps == 1) {
_downloadingSteps = 0;
_canReset = true;
updateMenuDesc();
}
else if (_canReset && PlayerSettings.solo.controls.RESET) tryUpdating();
}
public function tryUpdating(forceDisplaying:Bool = false) {
updateMenuDesc(TU.translate("credits.downloadingList"));
_canReset = false;
Main.execAsync(() -> {
if (checkUpdate() || forceDisplaying) _downloadingSteps = 2;
else _downloadingSteps = 1;
});
}
public override function updateMenuDesc(?txt:String) {
if (!_canReset) return;
super.updateMenuDesc(txt);
updateMarkup();
}
public function updateMarkup() {
if (parent == null) return;
var text:String = parent.descLabel.text;
parent.descLabel.text = "";
parent.descLabel.applyMarkup(text, contribFormats = [
new FlxTextFormatMarkerPair(new FlxTextFormat(Flags.MAIN_DEVS_COLOR), '*'),
new FlxTextFormatMarkerPair(new FlxTextFormat(FlxColor.interpolate(Flags.MIN_CONTRIBUTIONS_COLOR, Flags.MAIN_DEVS_COLOR, Options.contributors[curSelected].contributions / totalContributions)), '~')
]);
}
override function destroy() {
super.destroy();
for (frmt in contribFormats) parent.descLabel.removeFormat(frmt.format);
}
public function checkUpdate():Bool {
var curTime:Float = Date.now().getTime();
if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits - Nex
Options.lastUpdated = curTime;
error = false;
var idk = GitHub.getContributors(Flags.REPO_OWNER, Flags.REPO_NAME, function(e) {
error = true;
var errMsg:String = 'Error while trying to download contributors list:\n${CoolUtil.removeIP(e.message)}';
Logs.error(errMsg.replace('\n', ' '));
funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING);
});
if(error) return false;
if((idk is Array)) {
var contributors:Array<CreditsGitHubContributor> = [];
for(e in idk) contributors.push({
login: e.login,
avatar_url: e.avatar_url,
html_url: e.html_url,
id: e.id,
contributions: e.contributions
});
Options.contributors = contributors;
}
Logs.verbose('[CreditsCodename] Contributors list Updated!');
var errorOnMain:Bool = false;
var idk2 = GitHub.getOrganizationMembers(Flags.REPO_OWNER, function(e) {
errorOnMain = true;
var errMsg:String = 'Error while trying to download ${Flags.REPO_OWNER} members list:\n${CoolUtil.removeIP(e.message)}';
Logs.error(errMsg.replace('\n', ' '));
funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING);
});
if(!errorOnMain) {
Options.mainDevs = [for(m in idk2) m.id];
Logs.verbose('[CreditsCodename] Main Devs list Updated!');
}
return true;
}
public function displayList() {
//if (curSelected > Options.contributors.length - 1) changeSelection(-(curSelected - (Options.contributors.length - 1)));
if (curSelected > Options.contributors.length - 1) curSelected = Options.contributors.length - 1;
changeSelection(0, true);
while (members.length > 0) {
members[0].destroy();
remove(members[0], true);
}
totalContributions = 0;
for(c in Options.contributors) totalContributions += c.contributions;
for(c in Options.contributors) {
var text = TU.translate("credits.totalContributions", [c.contributions, totalContributions, FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)]);
var opt:GithubIconOption = new GithubIconOption(c, text);
if(Options.mainDevs.contains(c.id)) {
opt.desc += TU.translate("credits.mainDev");
@:privateAccess opt.__text.color = Flags.MAIN_DEVS_COLOR;
}
add(opt);
}
updateMenuDesc();
}
}
+103
View File
@@ -0,0 +1,103 @@
package funkin.menus.credits;
import flixel.util.FlxColor;
import funkin.backend.assets.AssetSource;
import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor;
import funkin.options.TreeMenu;
import funkin.options.TreeMenuScreen;
import funkin.options.type.*;
import haxe.xml.Access;
class CreditsMain extends TreeMenu {
var bg:FlxSprite;
var items:Array<OptionType> = [];
override function create() {
super.create();
DiscordUtil.call("onMenuLoaded", ["Credits Menu"]);
add(bg = new FlxSprite().loadAnimatedGraphic(Paths.image('menus/menuBGBlue')));
bg.antialiasing = true;
bg.scrollFactor.set();
updateBG();
var first = new TreeMenuScreen('credits.name', 'credits.madePossible');
addMenu(first);
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
var xmlPath = Paths.xml('config/credits/LIB_$i');
if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) {
var access:Access = null;
try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT")))
catch(e) Logs.trace('[CreditsMain] Error while parsing credits.xml: ${Std.string(e)}', ERROR);
if (access != null) for (c in parseCreditsFromXML(access)) first.add(c);
}
}
first.add(new TextOption('Codename Engine', 'credits.selectCodename', ' >', () -> addMenu(new CreditsCodename())));
first.add(new TextOption('Friday Night Funkin\'', 'credits.selectBase', ' >', () -> CoolUtil.openURL(Flags.URL_FNF_ITCH)));
}
public function updateBG() {
var scaleX:Float = FlxG.width / bg.width;
var scaleY:Float = FlxG.height / bg.height;
bg.scale.x = bg.scale.y = Math.max(scaleX, scaleY) * 1.15;
bg.screenCenter();
}
// XML STUFF
public function parseCreditsFromXML(xml:Access, source:AssetSource = BOTH):Array<FlxSprite> {
var credsMenus:Array<FlxSprite> = [];
for(node in xml.elements) {
var desc = node.getAtt("desc").getDefault("No Description");
if (node.name == "github") {
if (!node.has.user) {
Logs.warn("A github node requires a user attribute.", "CreditsMain");
continue;
}
var username = node.getAtt("user");
var user:CreditsGitHubContributor = { // Kind of forcing
login: username,
html_url: 'https://github.com/$username',
avatar_url: 'https://github.com/$username.png'
};
var opt:GithubIconOption = new GithubIconOption(user, desc, null,
node.has.customName ? node.att.customName : null,
node.has.size ? Std.parseInt(node.att.size) : 96,
node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true
);
if (node.has.color)
@:privateAccess opt.__text.color = FlxColor.fromString(node.att.color);
credsMenus.push(opt);
} else {
if (!node.has.name) {
Logs.warn("A credit node requires a name attribute.", "CreditsMain");
continue;
}
var name = node.getAtt("name");
switch(node.name) {
case "credit":
var opt:PortraitOption = new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url),
node.has.icon && Paths.assetsTree.existsSpecific(Paths.image('credits/${node.att.icon}'), "IMAGE", source) ?
FlxG.bitmap.add(Paths.image('credits/${node.att.icon}')) : null, node.has.size ? Std.parseInt(node.att.size) : 96,
node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true
);
if (node.has.color)
@:privateAccess opt.__text.color = FlxColor.fromString(node.att.color);
credsMenus.push(opt);
case "menu":
credsMenus.push(new TextOption(name, desc, ' >', () -> addMenu(new TreeMenuScreen(name, desc, parseCreditsFromXML(node, source)))));
}
}
}
return credsMenus;
}
}
+4 -8
View File
@@ -94,7 +94,7 @@ class OptionsMenu extends TreeMenu {
var access:Access = null;
try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT")))
catch(e) Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR);
//if (access != null) for (o in parseOptionsFromXML(access)) main.add(o);
if (access != null) for (o in parseOptionsFromXML(access)) tree.first().add(o);
}
}
}
@@ -120,9 +120,8 @@ class OptionsMenu extends TreeMenu {
}
// XML STUFF
/*
public function parseOptionsFromXML(xml:Access):Array<OptionType> {
var options:Array<OptionType> = [];
public function parseOptionsFromXML(xml:Access):Array<FlxSprite> {
var options:Array<FlxSprite> = [];
for(node in xml.elements) {
if (!node.has.name) {
@@ -164,13 +163,10 @@ class OptionsMenu extends TreeMenu {
options.push(new ArrayOption(name, desc, optionOptions, optionDisplayOptions, node.att.id, null, FlxG.save.data));
case "menu":
options.push(new TextOption(name + " >", desc, function() {
optionsTree.add(new TreeMenuScreen(name, desc, parseOptionsFromXML(node)));
}));
options.push(new TextOption(name, desc, ' >', () -> addMenu(new TreeMenuScreen(name, desc, parseOptionsFromXML(node)))));
}
}
return options;
}
*/
}
-141
View File
@@ -1,141 +0,0 @@
// REMOVE THIS WHEN COMPLETE
package funkin.options;
import funkin.options.type.OptionType;
class OptionsScreen extends FlxTypedSpriteGroup<OptionType> {
public static var optionHeight:Float = 120;
public var parent:OptionsTree;
public var curSelected:Int = 0;
public var id:Int = 0;
private var __firstFrame:Bool = true;
public var name:String;
public var desc:String;
/**
* The prefix to add to the translations ids.
**/
public var prefix:String = "";
private var rawName(default, set):String;
private var rawDesc(default, set):String;
public inline function getNameID(name):String {
return prefix + name + "-name";
}
public inline function getDescID(name):String {
return prefix + name + "-desc";
}
public function getName(name:String, ?args:Array<Dynamic>):String {
return TU.translate(getNameID(name), args);
}
public function getDesc(name:String, ?args:Array<Dynamic>):String {
return TU.translate(getDescID(name), args);
}
public function translate(name:String, ?args:Array<Dynamic>):String {
return TU.translate(prefix + name, args);
}
public function new(name:String, desc:String, ?prefix:String, ?options:Array<OptionType>) {
super();
rawName = name;
rawDesc = desc;
this.prefix = prefix != null ? prefix : "";
if (options != null) for(o in options) add(o);
}
function set_rawName(v:String) {
rawName = v;
this.name = TU.exists(rawName) ? TU.translate(rawName) : rawName;
return v;
}
function set_rawDesc(v:String) {
rawDesc = v;
this.desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc;
return v;
}
public function reloadStrings() {
this.rawName = rawName;
this.rawDesc = rawDesc;
for(o in members) {
o.reloadStrings();
}
}
public override function update(elapsed:Float) {
super.update(elapsed);
var controls = PlayerSettings.solo.controls;
changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel);
x = id * FlxG.width;
for(k=>option in members) {
if(option == null) continue;
var y:Float = ((FlxG.height - optionHeight) / 2) + ((k - curSelected) * optionHeight);
option.selected = false;
option.y = __firstFrame ? y : CoolUtil.fpsLerp(option.y, y, 0.25);
option.x = x + (-50 + (Math.abs(Math.cos((option.y + (optionHeight / 2) - (FlxG.camera.scroll.y + (FlxG.camera.height / 2))) / (FlxG.camera.height * 1.25) * Math.PI)) * 150));
}
if (__firstFrame) {
__firstFrame = false;
return;
}
if (members.length > 0) {
members[curSelected].selected = true;
if (controls.ACCEPT || (FlxG.mouse.justReleased && Main.timeSinceFocus > 0.25)) {
members[curSelected].select();
onSelect(members[curSelected]);
}
if (controls.LEFT_P)
members[curSelected].changeSelection(-1);
if (controls.RIGHT_P)
members[curSelected].changeSelection(1);
}
if (controls.BACK || FlxG.mouse.justReleasedRight)
close();
}
public function close() {
onClose(this);
}
public function changeSelection(sel:Int, force:Bool = false) {
if (members.length <= 0 || (sel == 0 && !force)) return;
CoolUtil.playMenuSFX(SCROLL);
curSelected = FlxMath.wrap(curSelected + sel, 0, members.length-1);
members[curSelected].selected = true;
updateMenuDesc();
}
public function updateMenuDesc(?customTxt:String) {
if (parent == null || parent.treeParent == null) return;
parent.treeParent.updateDesc(customTxt != null ? customTxt : members[curSelected].desc);
}
public dynamic function onClose(o:OptionsScreen) {}
public dynamic function onSelect(o:OptionType) {
if(o is funkin.options.type.RadioButton) {
var orb:funkin.options.type.RadioButton = cast o;
for(e in members) {
if(e is funkin.options.type.RadioButton) {
var rb:funkin.options.type.RadioButton = cast e;
if(rb.forId == orb.forId && rb != o) {
rb.checked = false;
}
}
}
}
}
}
-91
View File
@@ -1,91 +0,0 @@
// REMOVE THIS WHEN COMPLETE
package funkin.options;
class OptionsTree extends FlxTypedGroup<OptionsScreen> {
public var lastMenu:OptionsScreen;
public var treeParent:TreeMenu;
public var wasClosing:Bool = false;
//public override function new() {
// super();
//}
public override function update(elapsed:Float) {
var last = members.last();
if (last != null)
last.update(elapsed);
}
public function updateAll(elapsed:Float) {
for (member in members) {
if(member == null) continue;
if(member.active && member.exists) {
member.update(elapsed);
}
}
}
public override function draw() {
super.draw();
if (lastMenu != null) { // manually draw lastMenu since it got removed from the members list
lastMenu.draw();
}
}
public function reloadStrings() {
for(o in members) {
o.reloadStrings();
}
}
public override function add(m:OptionsScreen) {
super.add(m);
setup(m);
clearLastMenu();
wasClosing = false;
onMenuChange();
return m;
}
public override function insert(pos:Int, m:OptionsScreen) {
var last = members.last();
super.insert(pos, m);
setup(m);
if (last != members.last()) {
wasClosing = false;
onMenuChange();
}
return m;
}
public function setup(m:OptionsScreen) {
m.onClose = __subMenuClose;
m.id = members.indexOf(m);
m.parent = this;
m.update(0);
}
function __subMenuClose(m:OptionsScreen) {
wasClosing = true;
clearLastMenu();
lastMenu = m;
remove(m, true);
onMenuChange();
onMenuClose(m);
}
public function clearLastMenu() {
lastMenu = FlxDestroyUtil.destroy(lastMenu);
}
public override function destroy() {
super.destroy();
lastMenu = FlxDestroyUtil.destroy(lastMenu);
}
public dynamic function onMenuChange() {
}
public dynamic function onMenuClose(m:OptionsScreen) {
}
}
+3 -3
View File
@@ -32,8 +32,6 @@ class TreeMenu extends UIState {
public var descLabel:FunkinText;
public var bgLabel:FlxSprite;
public var optionsTree:OptionsTree;
var menuChangeTween:FlxTween;
var __drawer:TreeMenuDrawer;
var __treeCreated:Bool = false;
@@ -74,7 +72,9 @@ class TreeMenu extends UIState {
add(bgLabel);
add(titleLabel);
add(descLabel);
updateLabels();
FlxG.camera.scroll.x = -FlxG.width;
menuChanged();
__treeCreated = true;
}
@@ -1,13 +0,0 @@
package funkin.options.type;
import flixel.util.FlxColor;
class EditorIconOption extends IconOption {
public var flashColor:FlxColor;
public function new(name:String, desc:String, icon:String, callback:Void->Void, flashColor:FlxColor = 0) {
super(name, desc, icon, callback);
this.flashColor = flashColor;
}
}
@@ -26,6 +26,7 @@ class GithubIconOption extends TextOption
this.icon = new GithubUserIcon(user, size, waitUntilLoad);
this.usePortrait = usePortrait;
add(icon);
__text.x = 100;
}
}
+1
View File
@@ -10,6 +10,7 @@ class NewOption extends TextOption {
public function new(name:String, desc:String, callback:Void->Void) {
super(name, desc, callback);
itemHeight = 160;
__text.color = FlxColor.LIME;
__text.x = 100;
+3
View File
@@ -1,6 +1,7 @@
package funkin.options.type;
import flixel.group.FlxSpriteGroup;
import flixel.util.FlxColor;
import funkin.backend.system.Controls;
import funkin.options.TreeMenu.ITreeOption;
@@ -19,6 +20,8 @@ class OptionType extends FlxSpriteGroup implements ITreeOption {
public var itemHeight:Float = 120;
public var editorFlashColor:FlxColor = FlxColor.WHITE;
public function new(text:String, desc:String) {
super();
rawText = text;
+7 -1
View File
@@ -6,7 +6,7 @@ import flixel.effects.FlxFlicker;
* Option type that has text.
**/
class TextOption extends OptionType {
public var suffix:String;
public var suffix(default, set):String;
public var selectCallback:Void->Void;
var __text:Alphabet;
@@ -15,6 +15,12 @@ class TextOption extends OptionType {
return text = v;
}
function set_suffix(v:String) {
if (suffix == (suffix = v)) return v;
__text.text = text + suffix;
return v;
}
public function new(text:String, desc:String, ?suffix:String = "", ?selectCallback:Void->Void = null) {
this.suffix = suffix;
this.selectCallback = selectCallback;