diff --git a/MidnightConfigExample.java b/MidnightConfigExample.java deleted file mode 100644 index 2975aea..0000000 --- a/MidnightConfigExample.java +++ /dev/null @@ -1,67 +0,0 @@ -package eu.midnightdust.core.config; - -import com.google.common.collect.Lists; -import eu.midnightdust.lib.config.MidnightConfig; - -import java.util.List; - -/** MidnightConfig documentation & examples: - * Thanks for choosing MidnightConfig - the fancy, tiny and lightweight config library. - * If you want to use the lib in your mod, here are some examples and hints: - * Every option in a MidnightConfig class has to be public and static, so we can access it from other classes. - * The config class also has to extend MidnightConfig*/ - -public class MidnightConfigExample extends MidnightConfig { - - @Comment(category = "text") public static Comment text1; // Comments are rendered like an option without a button and are excluded from the config file - @Comment(category = "text", centered = true) public static Comment text2; // Centered comments are the same as normal ones - just centered! - @Comment(category = "text") public static Comment spacer1; // Comments containing the word "spacer" will just appear as a blank line - @Entry(category = "text") public static boolean showInfo = true; // Example for a boolean option - @Entry(category = "text") public static String name = "Hello World!"; // Example for a string option, which is in a category! - @Entry(category = "text") public static TestEnum testEnum = TestEnum.FABRIC; // Example for an enum option - public enum TestEnum { // Enums allow the user to cycle through predefined options - QUILT, FABRIC, FORGE - } - @Entry(category = "numbers") public static int fabric = 16777215; // Example for an int option - @Entry(category = "numbers") public static double world = 1.4D; // Example for a double option - @Entry(category = "numbers", min=69,max=420) public static int hello = 420; // - The entered number has to be larger than 69 and smaller than 420 - @Entry(category = "text", width = 7, min = 7, isColor = true, name = "I am a color!") public static String titleColor = "#ffffff"; // The isColor property adds a preview box for a hexadecimal color - @Entry(category = "text", name = "I am an array list!") public static List arrayList = Lists.newArrayList("String1", "String2"); // Array String Lists are also supported - @Entry(category = "sliders", name = "I am an int slider.",isSlider = true, min = 0, max = 100) public static int intSlider = 35; // Int fields can also be displayed as a Slider - @Entry(category = "sliders", name = "I am a float slider!", isSlider = true, min = 0f, max = 1f, precision = 1000) public static float floatSlider = 0.24f; // And so can floats! Precision defines the amount of decimal places - // The name field can be used to specify a custom translation string or plain text - - public static int imposter = 16777215; // - Entries without an @Entry or @Comment annotation are ignored - - /* - The .json language file for your config class could look similar to this: - { - "modid.midnightconfig.title":"I am a title", // "*.midnightconfig.title" defines the title of the screen - "modid.midnightconfig.text1":"I am a comment *u*", // Translation for the comment "text1" defined in the example config - "modid.midnightconfig.text2":"I am a centered comment (╯°□°)╯︵ ┻━┻", - "modid.midnightconfig.name":"I am a string!", // Translation for the field "name" defined in the example config - "modid.midnightconfig.name.tooltip":"I am a tooltip uwu \nI am a new line", - // When hovering over the option "showInfo", - // this text will appear as a tooltip. - // "\n" inserts a line break. - "modid.midnightconfig.fabric":"I am an int", - "modid.midnightconfig.world":"I am a double", - "modid.midnightconfig.showInfo":"I am a boolean", - "modid.midnightconfig.hello":"I am a limited int!", - "modid.midnightconfig.testEnum":"I am an enum!", - "modid.midnightconfig.enum.TestEnum.FORGE":"Slow", - "modid.midnightconfig.enum.TestEnum.FABRIC":"Fancy", - "modid.midnightconfig.enum.TestEnum.QUILT":"Fabulous", - "modid.midnightconfig.category.numbers": "Numbers", - "modid.midnightconfig.category.text": "Text", - "modid.midnightconfig.category.sliders": "Sliders" - } - To initialize the config you have to call "MidnightConfig.init("modid", MidnightConfigExample.class)" in your ModInitializer - To get an instance of the config screen you have to call "MidnightConfig.getScreen(parent, "modid");" - If you don't use the whole library and therefore not the automatic ModMenu integration, the code in your ModMenu integration class would look something like this: - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - return parent -> MidnightConfig.getScreen(parent, "modid"); - } - */ -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index a2da9cc..38eed5b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,11 @@ +import groovy.json.JsonSlurper +import groovy.json.JsonOutput + plugins { id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "1.1-SNAPSHOT" apply false + id "dev.architectury.loom" version "1.10-SNAPSHOT" apply false + id "me.shedaniel.unified-publishing" version "0.1.+" apply false + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false } architectury { @@ -12,10 +17,11 @@ subprojects { dependencies { minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" - // The following line declares the mojmap mappings, you may use other mappings as well - //mappings loom.officialMojangMappings() // The following line declares the yarn mappings you may select this one as well. - mappings "net.fabricmc:yarn:${rootProject.yarn_mappings}:v2" + mappings loom.layered { + it.mappings("net.fabricmc:yarn:$rootProject.yarn_mappings:v2") + // it.mappings("dev.architectury:yarn-mappings-patch-neoforge:$rootProject.yarn_mappings_patch_neoforge_version") + } } } @@ -36,10 +42,33 @@ allprojects { // for more information about repositories. } - tasks.withType(JavaCompile) { + tasks.withType(JavaCompile).configureEach { options.encoding = "UTF-8" options.release = 17 } + ext { + releaseChangelog = { + def changes = new StringBuilder() + changes << "## MidnightLib v$project.version for $project.minecraft_version\n[View the changelog](https://www.github.com/TeamMidnightDust/MidnightLib/commits/)" + def proc = "git log --max-count=1 --pretty=format:%s".execute() + proc.in.eachLine { line -> + def processedLine = line.toString() + if (!processedLine.contains("New translations") && !processedLine.contains("Merge") && !processedLine.contains("branch")) { + changes << "\n- ${processedLine.capitalize()}" + } + } + proc.waitFor() + return changes.toString() + } + } + processResources { + // Minify json resources + doLast { + fileTree(dir: outputs.files.asPath, include: "**/*.json").each { + File file -> file.text = JsonOutput.toJson(new JsonSlurper().parse(file)) + } + } + } java { withSourcesJar() diff --git a/common/build.gradle b/common/build.gradle index 4b57e84..d1f0ac2 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -2,13 +2,11 @@ architectury { common(rootProject.enabled_platforms.split(",")) } -loom { -} - dependencies { // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies // Do NOT use other classes from fabric loader - modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" +// implementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + modImplementation "net.fabricmc:fabric-loader:0.16.13" //FIXME: Using the same floader version doesn't let you access it..? architectury moment } publishing { diff --git a/common/src/main/java/eu/midnightdust/core/MidnightLib.java b/common/src/main/java/eu/midnightdust/core/MidnightLib.java new file mode 100755 index 0000000..1343567 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/core/MidnightLib.java @@ -0,0 +1,39 @@ +package eu.midnightdust.core; + +import eu.midnightdust.core.config.MidnightLibConfig; +import eu.midnightdust.lib.config.AutoCommand; +import eu.midnightdust.lib.config.MidnightConfig; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.UIManager; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import static net.minecraft.client.MinecraftClient.IS_SYSTEM_MAC; + +public class MidnightLib { + public static List hiddenMods = new ArrayList<>(); + public static final String MOD_ID = "midnightlib"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + @Environment(EnvType.CLIENT) + public static void onInitializeClient() { + try { if (!IS_SYSTEM_MAC) { + System.setProperty("java.awt.headless", "false"); + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + }} catch (Exception | Error e) { LOGGER.error("Error setting system look and feel", e); } + MidnightLibConfig.init(MOD_ID, MidnightLibConfig.class); + } + public static void registerAutoCommand() { + MidnightConfig.configClass.forEach((modid, config) -> { + for (Field field : config.getFields()) { + if (field.isAnnotationPresent(MidnightConfig.Entry.class) && !field.isAnnotationPresent(MidnightConfig.Client.class) && !field.isAnnotationPresent(MidnightConfig.Hidden.class)) + new AutoCommand(field, modid); + } + }); + } +} diff --git a/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java b/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java deleted file mode 100755 index 8d6df02..0000000 --- a/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java +++ /dev/null @@ -1,15 +0,0 @@ -package eu.midnightdust.core; - -import eu.midnightdust.core.config.MidnightLibConfig; -import eu.midnightdust.lib.config.MidnightConfig; - -import java.util.ArrayList; -import java.util.List; - -public class MidnightLibClient { - public static List hiddenMods = new ArrayList<>(); - - public static void onInitializeClient() { - MidnightConfig.init("midnightlib", MidnightLibConfig.class); - } -} diff --git a/common/src/main/java/eu/midnightdust/core/MidnightLibServer.java b/common/src/main/java/eu/midnightdust/core/MidnightLibServer.java deleted file mode 100755 index a092123..0000000 --- a/common/src/main/java/eu/midnightdust/core/MidnightLibServer.java +++ /dev/null @@ -1,18 +0,0 @@ -package eu.midnightdust.core; - -import eu.midnightdust.lib.config.AutoCommand; -import eu.midnightdust.lib.config.MidnightConfig; - -import java.lang.reflect.Field; - -public class MidnightLibServer { - - public static void onInitializeServer() { - MidnightConfig.configClass.forEach((modid, config) -> { - for (Field field : config.getFields()) { - if (field.isAnnotationPresent(MidnightConfig.Entry.class) && !field.isAnnotationPresent(MidnightConfig.Client.class) && !field.isAnnotationPresent(MidnightConfig.Hidden.class)) - new AutoCommand(field, modid).register(); - } - }); - } -} diff --git a/common/src/main/java/eu/midnightdust/core/config/MidnightLibConfig.java b/common/src/main/java/eu/midnightdust/core/config/MidnightLibConfig.java index 41ac616..8be89a8 100644 --- a/common/src/main/java/eu/midnightdust/core/config/MidnightLibConfig.java +++ b/common/src/main/java/eu/midnightdust/core/config/MidnightLibConfig.java @@ -3,11 +3,18 @@ package eu.midnightdust.core.config; import eu.midnightdust.lib.config.MidnightConfig; import eu.midnightdust.lib.util.PlatformFunctions; +import java.util.Objects; + public class MidnightLibConfig extends MidnightConfig { - @Entry // Enable or disable the MidnightConfig overview screen button - public static ConfigButton config_screen_list = PlatformFunctions.isModLoaded("modmenu") ? ConfigButton.MODMENU : ConfigButton.TRUE; + public static final boolean HAS_MODMENU = PlatformFunctions.isModLoaded("modmenu") || Objects.equals(PlatformFunctions.getPlatformName(), "neoforge"); + + @Entry public static ConfigButton config_screen_list = HAS_MODMENU ? ConfigButton.MODMENU : ConfigButton.TRUE; public enum ConfigButton { - TRUE,FALSE,MODMENU + TRUE, FALSE, MODMENU + } + + public static boolean shouldShowButton() { + return config_screen_list.equals(ConfigButton.TRUE) || (config_screen_list.equals(ConfigButton.MODMENU) && !HAS_MODMENU); } } diff --git a/common/src/main/java/eu/midnightdust/core/mixin/MixinOptionsScreen.java b/common/src/main/java/eu/midnightdust/core/mixin/MixinOptionsScreen.java old mode 100755 new mode 100644 index 1f3da97..29746e7 --- a/common/src/main/java/eu/midnightdust/core/mixin/MixinOptionsScreen.java +++ b/common/src/main/java/eu/midnightdust/core/mixin/MixinOptionsScreen.java @@ -9,6 +9,7 @@ import net.minecraft.client.gui.screen.option.OptionsScreen; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -17,7 +18,9 @@ import java.util.Objects; @Mixin(OptionsScreen.class) public class MixinOptionsScreen extends Screen { + @Unique private static final Identifier MIDNIGHTLIB_ICON_TEXTURE = new Identifier("midnightlib","textures/gui/midnightlib_button.png"); + protected MixinOptionsScreen(Text title) { super(title); } diff --git a/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java b/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java index c115063..d08d956 100755 --- a/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java +++ b/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java @@ -1,6 +1,6 @@ package eu.midnightdust.core.screen; -import eu.midnightdust.core.MidnightLibClient; +import eu.midnightdust.core.MidnightLib; import eu.midnightdust.lib.config.MidnightConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -11,7 +11,6 @@ import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.*; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.screen.ScreenTexts; import net.minecraft.text.*; import java.util.*; @@ -36,7 +35,7 @@ public class MidnightConfigOverviewScreen extends Screen { List sortedMods = new ArrayList<>(MidnightConfig.configClass.keySet()); Collections.sort(sortedMods); sortedMods.forEach((modid) -> { - if (!MidnightLibClient.hiddenMods.contains(modid)) { + if (!MidnightLib.hiddenMods.contains(modid)) { list.addButton(ButtonWidget.builder(Text.translatable(modid +".midnightconfig.title"), (button) -> Objects.requireNonNull(client).setScreen(MidnightConfig.getScreen(this,modid))).dimensions(this.width / 2 - 125, this.height - 28, 250, 20).build()); } diff --git a/common/src/main/java/eu/midnightdust/lib/config/AutoCommand.java b/common/src/main/java/eu/midnightdust/lib/config/AutoCommand.java index c948a69..e41f920 100644 --- a/common/src/main/java/eu/midnightdust/lib/config/AutoCommand.java +++ b/common/src/main/java/eu/midnightdust/lib/config/AutoCommand.java @@ -1,96 +1,86 @@ package eu.midnightdust.lib.config; -import com.mojang.brigadier.arguments.DoubleArgumentType; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.arguments.*; +import com.mojang.brigadier.context.CommandContext; import eu.midnightdust.lib.util.PlatformFunctions; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.Text; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.Objects; + +import static eu.midnightdust.lib.config.MidnightConfig.Entry; public class AutoCommand { - public static List> commands = new ArrayList<>(); - private LiteralArgumentBuilder command; - final Field entry; + final static String VALUE = "value"; + final Field field; + final Class type; final String modid; + final boolean isList; - public AutoCommand(Field entry, String modid) { - this.entry = entry; - this.modid = modid; + public AutoCommand(Field field, String modid) { + this.field = field; this.modid = modid; + this.type = MidnightConfig.getUnderlyingType(field); + this.isList = field.getType() == List.class; + + var command = CommandManager.literal(field.getName()).executes(this::getValue); + + if (type.isEnum()) { + for (Object enumValue : field.getType().getEnumConstants()) + command = command.then(CommandManager.literal(enumValue.toString()) + .executes(ctx -> this.setValue(ctx.getSource(), enumValue, ""))); + } else if (isList) { + for (String action : new String[]{"add", "remove"}) + command = command.then(CommandManager.literal(action) + .then(CommandManager.argument(VALUE, getArgType()).executes(ctx -> setValueFromArg(ctx, action)))); + } else command = command.then(CommandManager.argument(VALUE, getArgType()).executes(ctx -> setValueFromArg(ctx, ""))); + + PlatformFunctions.registerCommand(CommandManager.literal("midnightconfig").requires(source -> source.hasPermissionLevel(2)).then(CommandManager.literal(modid).then(command))); } - public void register() { - command = CommandManager.literal(modid); - command(); - LiteralArgumentBuilder finalized = CommandManager.literal("midnightconfig").requires(source -> source.hasPermissionLevel(2)).then(command); - - PlatformFunctions.registerCommand(finalized); commands.add(finalized); + public ArgumentType getArgType() { + Entry entry = field.getAnnotation(Entry.class); + if (type == int.class) return IntegerArgumentType.integer((int) entry.min(), (int) entry.max()); + else if (type == double.class) return DoubleArgumentType.doubleArg(entry.min(), entry.max()); + else if (type == float.class) return FloatArgumentType.floatArg((float) entry.min(), (float) entry.max()); + else if (type == boolean.class) return BoolArgumentType.bool(); + return StringArgumentType.string(); } - private void command() { - if (entry.getType() == int.class) - command = command.then(CommandManager.literal(this.entry.getName()).executes(ctx -> getValue(ctx.getSource())).then( - CommandManager.argument("value", IntegerArgumentType.integer((int) entry.getAnnotation(MidnightConfig.Entry.class).min(),(int) entry.getAnnotation(MidnightConfig.Entry.class).max())) - .executes(ctx -> this.setValue(ctx.getSource(), IntegerArgumentType.getInteger(ctx, "value"))) - )); - else if (entry.getType() == double.class) - command = command.then(CommandManager.literal(this.entry.getName()).executes(ctx -> getValue(ctx.getSource())).then( - CommandManager.argument("value", DoubleArgumentType.doubleArg(entry.getAnnotation(MidnightConfig.Entry.class).min(),entry.getAnnotation(MidnightConfig.Entry.class).max())) - .executes(ctx -> this.setValue(ctx.getSource(), DoubleArgumentType.getDouble(ctx, "value"))) - )); - else if (entry.getType() == boolean.class) { - command = command.then(CommandManager.literal(this.entry.getName()).executes(ctx -> getValue(ctx.getSource())).then( - CommandManager.literal("true") - .executes(ctx -> this.setValue(ctx.getSource(), true)) - )); - command = command.then(CommandManager.literal(this.entry.getName()).executes(ctx -> getValue(ctx.getSource())).then( - CommandManager.literal("false") - .executes(ctx -> this.setValue(ctx.getSource(), false)) - )); - } - else if (entry.getType().isEnum()) { - for (int i = 0; i < entry.getType().getEnumConstants().length; ++i) { - Object enumValue = Arrays.stream(entry.getType().getEnumConstants()).toList().get(i); - command = command.then(CommandManager.literal(this.entry.getName()).executes(ctx -> getValue(ctx.getSource())).then( - CommandManager.literal(enumValue.toString()) - .executes(ctx -> this.setValue(ctx.getSource(), enumValue)) - )); - } - } - else - command = command.then(CommandManager.literal(this.entry.getName()).executes(ctx -> getValue(ctx.getSource())).then( - CommandManager.argument("value", StringArgumentType.string()) - .executes(ctx -> this.setValue(ctx.getSource(), StringArgumentType.getString(ctx, "value"))) - )); + public int setValueFromArg(CommandContext context, String action) { + if (type == int.class) return setValue(context.getSource(), IntegerArgumentType.getInteger(context, VALUE), action); + else if (type == double.class) return setValue(context.getSource(), DoubleArgumentType.getDouble(context, VALUE), action); + else if (type == float.class) return setValue(context.getSource(), FloatArgumentType.getFloat(context, VALUE), action); + else if (type == boolean.class) return setValue(context.getSource(), BoolArgumentType.getBool(context, VALUE), action); + return setValue(context.getSource(), StringArgumentType.getString(context, VALUE), action); } - - private int setValue(ServerCommandSource source, Object value) { + private int setValue(ServerCommandSource source, Object value, String action) { + boolean add = Objects.equals(action, "add"); try { - entry.set(null,value); + if (!isList) field.set(null, value); + else { + @SuppressWarnings("unchecked") var list = (List) field.get(null); + if (add) list.add(value); + else if (!list.contains(value)) throw new IllegalArgumentException("List does not contain this string!"); + else list.remove(value); + } MidnightConfig.write(modid); } catch (Exception e) { - source.sendError(Text.literal("Could not set "+entry.getName()+" to value "+value+": " + e)); + source.sendError(Text.literal(isList ? "Could not %s %s %s %s: %s".formatted(add ? "add" : "remove", value, add ? "to" : "from", field.getName(), e) : "Could not set %s to value %s: %s".formatted(field.getName(), value, e))); return 0; } - - source.sendFeedback(() -> Text.literal("Successfully set " + entry.getName()+" to "+value), true); + source.sendFeedback(() -> Text.literal(isList ? "Successfully %s %s %s %s".formatted(add ? "added" : "removed", value, add ? "to" : "from", field.getName()) : + "Successfully set %s to %s".formatted(field.getName(), value)), true); return 1; } - private int getValue(ServerCommandSource source) { - source.sendFeedback(() -> { - try { - return Text.literal("The value of "+entry.getName()+" is "+entry.get(null)); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - }, false); + private int getValue(CommandContext context) { + context.getSource().sendFeedback(() -> { + try { return Text.literal("The value of %s is %s".formatted(field.getName(), field.get(null))); + } catch (IllegalAccessException e) {throw new RuntimeException(e);} + }, true); return 0; } -} +} \ No newline at end of file diff --git a/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java b/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java index e62e529..eaf0796 100755 --- a/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java +++ b/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java @@ -1,47 +1,58 @@ package eu.midnightdust.lib.config; -import com.google.gson.ExclusionStrategy; -import com.google.gson.FieldAttributes; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.google.common.collect.Lists; +import com.google.gson.*; import com.google.gson.stream.*; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.serialization.DataResult; import eu.midnightdust.lib.util.PlatformFunctions; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; +import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.Selectable; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.tab.GridScreenTab; -import net.minecraft.client.gui.tab.Tab; -import net.minecraft.client.gui.tab.TabManager; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.*; +import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.ConfirmLinkScreen; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tab.GridScreenTab; import net.minecraft.client.gui.tab.Tab; import net.minecraft.client.gui.tab.TabManager; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.*; import net.minecraft.client.resource.language.I18n; +import net.minecraft.registry.Registries; import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.OrderedText; -import net.minecraft.text.Style; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; +import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import net.minecraft.util.TranslatableOption; +import org.jetbrains.annotations.Nullable; +import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.Color; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.nio.file.Files; -import java.nio.file.Path; +import java.lang.annotation.*; +import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; +import java.nio.file.Files; import java.nio.file.Path; import java.util.*; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; -/** MidnightConfig v2.4.1 by TeamMidnightDust & Motschen +import static net.minecraft.client.MinecraftClient.IS_SYSTEM_MAC; + + +/* FIXME: + * hi martin :wave: + * before anythinge else: + * DON'T ANNOY YOURSELF WITH THIS + * BACKPORT UNLESS YOU REALLY DON'T HAVE + * ANYTHING BETTER TO DO!!!!! + * i don't wish to waste your time in any capacity, + * and this backport is going to be a *huge* mess, + * as you surely know + * so please!! take it easy! + * much love <3 + * . + * this being your codebase, i'm guessing + * (moreso hoping) you'll know what's in need + * of fixing given the rendering changes between + * 1.20 & 1.21 + * gave it my best shot but... got completely lost. + */ + +/** MidnightConfig by Martin "Motschen" Prokoph * Single class config library - feel free to copy! * Based on ... * Credits to Minenash */ @@ -49,144 +60,213 @@ import java.util.regex.Pattern; @SuppressWarnings("unchecked") public abstract class MidnightConfig { private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)"); - private static final Pattern DECIMAL_ONLY = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)"); + private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)"); private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)"); - private static final List entries = new ArrayList<>(); + private static final LinkedHashMap entries = new LinkedHashMap<>(); // modid:fieldName -> EntryInfo + private static boolean reloadScreen = false; - protected static class EntryInfo { - Field field; - Object widget; - int width; - boolean centered; - Text error; - Object defaultValue; - Object value; - String tempValue; + public static class EntryInfo { + public Entry entry; + public Comment comment; + public Condition[] conditions; + public final Field field; + public final Class dataType; + public final String modid, fieldName; + int listIndex; + Object defaultValue, value, function; + String tempValue; // The value visible in the config screen boolean inLimits = true; - String id; - Text name; - int index; - ClickableWidget colorButton; + Text name, error; + ClickableWidget actionButton; // color picker button / explorer button Tab tab; + boolean conditionsMet = true; + + public EntryInfo(Field field, String modid) { + this.field = field; this.modid = modid; + if (field != null) { + this.fieldName = field.getName(); + this.dataType = getUnderlyingType(field); + this.entry = field.getAnnotation(Entry.class); + this.comment = field.getAnnotation(Comment.class); + this.conditions = field.getAnnotationsByType(Condition.class); + } else { this.fieldName = ""; this.dataType = null; } + if (entry != null && !entry.name().isEmpty()) this.name = Text.translatable(entry.name()); + else if (comment != null && !comment.name().isEmpty()) this.name = Text.translatable(comment.name()); + } + public void setValue(Object value) { + if (this.field.getType() != List.class) { this.value = value; + this.tempValue = value.toString(); + } else { writeList(this.listIndex, value); + this.tempValue = toTemporaryValue(); } + } + public String toTemporaryValue() { + if (this.field.getType() != List.class) return this.value.toString(); + else try { return ((List) this.value).get(this.listIndex).toString(); } catch (Exception ignored) {return "";} + } + public void updateFieldValue() { + try { + if (this.field.get(null) != value) entries.values().forEach(EntryInfo::updateConditions); + this.field.set(null, this.value); + } catch (IllegalAccessException ignored) {} + } + @SuppressWarnings("ConstantValue") //pertains to requiredModLoaded + public void updateConditions() { + boolean prevConditionState = this.conditionsMet; + if (this.conditions.length > 0) this.conditionsMet = true; // reset conditions + for (Condition condition : this.conditions) { + if (!condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(condition.requiredModId())) + this.conditionsMet = false; + String requiredOption = condition.requiredOption().contains(":") ? condition.requiredOption() : (this.modid + ":" + condition.requiredOption()); + if (entries.get(requiredOption) != null) { + EntryInfo info = entries.get(requiredOption); + this.conditionsMet &= List.of(condition.requiredValue()).contains(info.tempValue); + } + if (!this.conditionsMet) break; + } + if (prevConditionState != this.conditionsMet) reloadScreen = true; + } + public void writeList(int index, T value) { + var list = (List) this.value; + if (index >= list.size()) list.add(value); + else list.set(index, value); + } + public Tooltip getTooltip(boolean isButton) { + String key = this.modid + ".midnightconfig."+this.fieldName+(!isButton ? ".label" : "" )+".tooltip"; + return Tooltip.of(isButton && this.error != null ? this.error : I18n.hasTranslation(key) ? Text.translatable(key) : Text.empty()); + } } - public static final Map> configClass = new HashMap<>(); + public static final Map> configClass = new HashMap<>(); private static Path path; - private static final Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE).addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy()).setPrettyPrinting().create(); + private static final Gson gson = new GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE) + .addSerializationExclusionStrategy(new NonEntryExclusionStrategy()) + .registerTypeAdapter(Identifier.class, new Identifier.Serializer()).setPrettyPrinting().create(); - public static void init(String modid, Class config) { + public static void loadValuesFromJson(String modid) { + try { gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); } + catch (Exception e) { write(modid); } + entries.values().forEach(info -> { + if (info.field != null && info.entry != null) { + try { + info.value = info.field.get(null); + info.tempValue = info.toTemporaryValue(); + info.updateConditions(); + } catch (IllegalAccessException ignored) {} + } + }); + } + public static void init(String modid, Class config) { path = PlatformFunctions.getConfigDirectory().resolve(modid + ".json"); configClass.put(modid, config); for (Field field : config.getFields()) { - EntryInfo info = new EntryInfo(); + EntryInfo info = new EntryInfo(field, modid); if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class)) && !field.isAnnotationPresent(Server.class) && !field.isAnnotationPresent(Hidden.class) && PlatformFunctions.isClientEnv()) initClient(modid, field, info); - if (field.isAnnotationPresent(Comment.class)) info.centered = field.getAnnotation(Comment.class).centered(); if (field.isAnnotationPresent(Entry.class)) - try { - info.defaultValue = field.get(null); - } catch (IllegalAccessException ignored) {} - } - try { gson.fromJson(Files.newBufferedReader(path), config); } - catch (Exception e) { write(modid); } - - for (EntryInfo info : entries) { - if (info.field.isAnnotationPresent(Entry.class)) - try { - info.value = info.field.get(null); - info.tempValue = info.value.toString(); + try { info.defaultValue = field.get(null); } catch (IllegalAccessException ignored) {} } + loadValuesFromJson(modid); } @Environment(EnvType.CLIENT) private static void initClient(String modid, Field field, EntryInfo info) { - Class type = field.getType(); - Entry e = field.getAnnotation(Entry.class); - info.width = e != null ? e.width() : 0; - info.field = field; - info.id = modid; - + Entry e = info.entry; + String key = modid + ":" + field.getName(); if (e != null) { - if (!e.name().equals("")) info.name = Text.translatable(e.name()); - if (type == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true); - else if (type == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); - else if (type == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false); - else if (type == String.class || type == List.class) textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true); - else if (type == boolean.class) { + if (info.dataType == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true); + else if (info.dataType == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); + else if (info.dataType == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false); + else if (info.dataType == String.class || info.dataType == Identifier.class) textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true); + else if (info.dataType == boolean.class) { Function func = value -> Text.translatable((Boolean) value ? "gui.yes" : "gui.no").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED); - info.widget = new AbstractMap.SimpleEntry>(button -> { - info.value = !(Boolean) info.value; - button.setMessage(func.apply(info.value)); + info.function = new AbstractMap.SimpleEntry>(button -> { + info.setValue(!(Boolean) info.value); button.setMessage(func.apply(info.value)); }, func); - } else if (type.isEnum()) { + } else if (info.dataType.isEnum()) { List values = Arrays.asList(field.getType().getEnumConstants()); - Function func = value -> Text.translatable(modid + ".midnightconfig." + "enum." + type.getSimpleName() + "." + info.value.toString()); - info.widget = new AbstractMap.SimpleEntry>(button -> { + Function func = value -> getEnumTranslatableText(value, modid, info); + info.function = new AbstractMap.SimpleEntry>(button -> { int index = values.indexOf(info.value) + 1; - info.value = values.get(index >= values.size() ? 0 : index); + info.setValue(values.get(index >= values.size() ? 0 : index)); button.setMessage(func.apply(info.value)); }, func); } } - entries.add(info); + entries.put(key, info); } - public static Tooltip getTooltip(EntryInfo info) { - return Tooltip.of(info.error != null ? info.error : I18n.hasTranslation(info.id + ".midnightconfig."+info.field.getName()+".tooltip") ? Text.translatable(info.id + ".midnightconfig."+info.field.getName()+".tooltip") : Text.empty()); + public static Class getUnderlyingType(Field field) { + Class rawType = field.getType(); + if (field.getType() == List.class) + rawType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + try { return (Class) rawType.getField("TYPE").get(null); // Tries to get primitive types from non-primitives (e.g. Boolean -> boolean) + } catch (NoSuchFieldException | IllegalAccessException ignored) { return rawType; } + } + + private static Text getEnumTranslatableText(Object value, String modid, EntryInfo info) { + if (value instanceof TranslatableOption translatableOption) return translatableOption.getText(); + + String translationKey = "%s.midnightconfig.enum.%s.%s".formatted(modid, info.dataType.getSimpleName(), info.toTemporaryValue()); + return I18n.hasTranslation(translationKey) ? Text.translatable(translationKey) : Text.literal(info.toTemporaryValue()); } private static void textField(EntryInfo info, Function f, Pattern pattern, double min, double max, boolean cast) { boolean isNumber = pattern != null; - info.widget = (BiFunction>) (t, b) -> s -> { + info.function = (BiFunction>) (t, b) -> s -> { s = s.trim(); - if (!(s.isEmpty() || !isNumber || pattern.matcher(s).matches())) return false; - - Number value = 0; - boolean inLimits = false; - info.error = null; + if (!(s.isEmpty() || !isNumber || pattern.matcher(s).matches()) || + (info.dataType == Identifier.class && Identifier.validate(s).equals(DataResult.success(new Identifier(s))))) return false; // inline substitution for "isError" + Number value = 0; boolean inLimits = false; info.error = null; if (!(isNumber && s.isEmpty()) && !s.equals("-") && !s.equals(".")) { try { value = f.apply(s); } catch(NumberFormatException e){ return false; } inLimits = value.doubleValue() >= min && value.doubleValue() <= max; info.error = inLimits? null : Text.literal(value.doubleValue() < min ? "§cMinimum " + (isNumber? "value" : "length") + (cast? " is " + (int)min : " is " + min) : "§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max)).formatted(Formatting.RED); - t.setTooltip(getTooltip(info)); + t.setTooltip(info.getTooltip(true)); } info.tempValue = s; t.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777); info.inLimits = inLimits; - b.active = entries.stream().allMatch(e -> e.inLimits); + b.active = entries.values().stream().allMatch(e -> e.inLimits); - if (inLimits && info.field.getType() != List.class) - info.value = isNumber? value : s; - else if (inLimits) { - if (((List) info.value).size() == info.index) ((List) info.value).add(""); - ((List) info.value).set(info.index, Arrays.stream(info.tempValue.replace("[", "").replace("]", "").split(", ")).toList().get(0)); + if (inLimits) { + if (info.dataType == Identifier.class) info.setValue(Identifier.tryParse(s)); + else info.setValue(isNumber ? value : s); } - if (info.field.getAnnotation(Entry.class).isColor()) { + if (info.entry.isColor()) { if (!s.contains("#")) s = '#' + s; if (!HEXADECIMAL_ONLY.matcher(s).matches()) return false; - try { - info.colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); + try { info.actionButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); } catch (Exception ignored) {} } return true; }; } - - public static void write(String modid) { - path = PlatformFunctions.getConfigDirectory().resolve(modid + ".json"); - try { - if (!Files.exists(path)) Files.createFile(path); - Files.write(path, gson.toJson(configClass.get(modid).getDeclaredConstructor().newInstance()).getBytes()); - } catch (Exception e) { - e.printStackTrace(); - } + public static MidnightConfig getClass(String modid) { + try { return configClass.get(modid).getDeclaredConstructor().newInstance(); } catch (Exception e) {throw new RuntimeException(e);} } + public static void write(String modid) { getClass(modid).writeChanges(modid); } + + public void writeChanges(String modid) { + try { if (!Files.exists(path = PlatformFunctions.getConfigDirectory().resolve(modid + ".json"))) Files.createFile(path); + Files.write(path, gson.toJson(getClass(modid)).getBytes()); + } catch (Exception e) { e.fillInStackTrace(); } + } + + @SuppressWarnings("unused") // Utility for mod authors + public static @Nullable Object getDefaultValue(String modid, String entry) { + String key = modid + ":" + entry; + return entries.containsKey(key) ? entries.get(key).defaultValue : null; + } + + public void onTabInit(String tabName, MidnightConfigListWidget list, MidnightConfigScreen screen) {} + @Environment(EnvType.CLIENT) public static Screen getScreen(Screen parent, String modid) { return new MidnightConfigScreen(parent, modid); @@ -194,37 +274,32 @@ public abstract class MidnightConfig { @Environment(EnvType.CLIENT) public static class MidnightConfigScreen extends Screen { protected MidnightConfigScreen(Screen parent, String modid) { - super(Text.translatable(modid + ".midnightconfig." + "title")); - this.parent = parent; - this.modid = modid; + super(Text.translatable(modid + ".midnightconfig.title")); + this.parent = parent; this.modid = modid; this.translationPrefix = modid + ".midnightconfig."; - loadValues(); - - for (EntryInfo e : entries) { - if (e.id.equals(modid)) { - String tabId = e.field.isAnnotationPresent(Entry.class) ? e.field.getAnnotation(Entry.class).category() : e.field.getAnnotation(Comment.class).category(); + loadValuesFromJson(modid); + entries.values().forEach(info -> { + if (info.modid.equals(modid)) { + String tabId = info.entry != null ? info.entry.category() : info.comment.category(); String name = translationPrefix + "category." + tabId; if (!I18n.hasTranslation(name) && tabId.equals("default")) name = translationPrefix + "title"; if (!tabs.containsKey(name)) { Tab tab = new GridScreenTab(Text.translatable(name)); - e.tab = tab; - tabs.put(name, tab); - } else e.tab = tabs.get(name); + info.tab = tab; tabs.put(name, tab); + } else info.tab = tabs.get(name); } - } + }); tabNavigation = TabNavigationWidget.builder(tabManager, this.width).tabs(tabs.values().toArray(new Tab[0])).build(); tabNavigation.selectTab(0, false); tabNavigation.init(); prevTab = tabManager.getCurrentTab(); } - public final String translationPrefix; + public final String translationPrefix, modid; public final Screen parent; - public final String modid; public MidnightConfigListWidget list; - public boolean reload = false; public TabManager tabManager = new TabManager(a -> {}, a -> {}); - public Map tabs = new HashMap<>(); + public Map tabs = new LinkedHashMap<>(); public Tab prevTab; public TabNavigationWidget tabNavigation; public ButtonWidget done; @@ -236,246 +311,343 @@ public abstract class MidnightConfig { super.tick(); if (prevTab != null && prevTab != tabManager.getCurrentTab()) { prevTab = tabManager.getCurrentTab(); - this.list.clear(); - fillList(); - list.setScrollAmount(0); + updateList(); list.setScrollAmount(0); } scrollProgress = list.getScrollAmount(); - for (EntryInfo info : entries) { - try {info.field.set(null, info.value);} catch (IllegalAccessException ignored) {} - } - updateResetButtons(); + for (EntryInfo info : entries.values()) info.updateFieldValue(); + updateButtons(); + if (reloadScreen) { updateList(); reloadScreen = false; } } - public void updateResetButtons() { + public void updateButtons() { if (this.list != null) { for (ButtonEntry entry : this.list.children()) { - if (entry.buttons != null && entry.buttons.size() > 1 && entry.buttons.get(1) instanceof ButtonWidget button) { - button.active = !Objects.equals(entry.info.value.toString(), entry.info.defaultValue.toString()); - } - } - } - } - public void loadValues() { - try { gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); } - catch (Exception e) { write(modid); } - - for (EntryInfo info : entries) { - if (info.field.isAnnotationPresent(Entry.class)) - try { - info.value = info.field.get(null); - info.tempValue = info.value.toString(); - } catch (IllegalAccessException ignored) {} - } - } + if (entry.buttons != null && entry.buttons.size() > 1 && entry.info.field != null) { + if (entry.buttons.get(0) != null) { + ClickableWidget widget = entry.buttons.get(0); + if (widget.isFocused() || widget.isHovered()) widget.setTooltip(entry.info.getTooltip(true)); + } + if (entry.buttons.get(1) instanceof ButtonWidget button) + button.active = !Objects.equals(String.valueOf(entry.info.value), String.valueOf(entry.info.defaultValue)) && entry.info.conditionsMet; + }}}} @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (this.tabNavigation.trySwitchTabsWithKey(keyCode)) return true; return super.keyPressed(keyCode, scanCode, modifiers); } @Override + public void close() { + loadValuesFromJson(modid); cleanup(); + Objects.requireNonNull(client).setScreen(parent); + } + private void cleanup() { + entries.values().forEach(info -> { + info.error = null; info.value = null; info.tempValue = null; info.actionButton = null; info.listIndex = 0; info.tab = null; info.inLimits = true; + }); + } + @Override public void init() { super.init(); - tabNavigation.setWidth(this.width); - tabNavigation.init(); + tabNavigation.setWidth(this.width); tabNavigation.init(); if (tabs.size() > 1) this.addDrawableChild(tabNavigation); - this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { - loadValues(); - Objects.requireNonNull(client).setScreen(parent); - }).dimensions(this.width / 2 - 154, this.height - 28, 150, 20).build()); + this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> this.close()).dimensions(this.width / 2 - 154, this.height - 26, 150, 20).build()); done = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, (button) -> { - for (EntryInfo info : entries) - if (info.id.equals(modid)) { - try { - info.field.set(null, info.value); - } catch (IllegalAccessException ignored) {} - } - write(modid); + for (EntryInfo info : entries.values()) if (info.modid.equals(modid)) info.updateFieldValue(); + write(modid); cleanup(); Objects.requireNonNull(client).setScreen(parent); - }).dimensions(this.width / 2 + 4, this.height - 28, 150, 20).build()); + }).dimensions(this.width / 2 + 4, this.height - 26, 150, 20).build()); - this.list = new MidnightConfigListWidget(this.client, this.width, this.height, 32, this.height - 32, 25); - if (this.client != null && this.client.world != null) this.list.setRenderBackground(false); - this.addSelectableChild(this.list); - - fillList(); - reload = true; + this.list = new MidnightConfigListWidget(this.client, this.width, this.height, this.height - 57, 24, 25); + this.addSelectableChild(this.list); fillList(); + if (tabs.size() > 1) list.renderHeaderSeparator = false; + } + public void updateList() { + this.list.clear(); fillList(); } public void fillList() { - for (EntryInfo info : entries) { - if (info.id.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) { - Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.field.getName())); - ButtonWidget resetButton = ButtonWidget.builder(Text.literal("Reset").formatted(Formatting.RED), (button -> { - info.value = info.defaultValue; - info.tempValue = info.defaultValue.toString(); - info.index = 0; - list.clear(); - fillList(); - })).dimensions(width - 205, 0, 40, 20).build(); + MidnightConfig.getClass(modid).onTabInit(prevTab.getTitle().getContent() instanceof TranslatableTextContent translatable ? translatable.getKey().replace("%s.midnightconfig.category.".formatted(modid), "") : prevTab.getTitle().toString(), list, this); + for (EntryInfo info : entries.values()) { + info.updateConditions(); + if (!info.conditionsMet) { + boolean visibleButLocked = false; + for (Condition condition : info.conditions) { + visibleButLocked |= condition.visibleButLocked(); + } + if (!visibleButLocked) continue; + } + if (info.modid.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) { + Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.fieldName)); + IconButtonWidget resetButton = IconButtonWidget.builder(Text.translatable("controls.reset"), Identifier.of("midnightlib","icon/reset"), (button -> { + info.value = info.defaultValue; info.listIndex = 0; + info.tempValue = info.toTemporaryValue(); + updateList(); + }) ).build(); + resetButton.setPosition(width - 205 + 150 + 25, 0); - if (info.widget instanceof Map.Entry) { - Map.Entry> widget = (Map.Entry>) info.widget; - if (info.field.getType().isEnum()) - widget.setValue(value -> Text.translatable(translationPrefix + "enum." + info.field.getType().getSimpleName() + "." + info.value.toString())); - this.list.addButton(List.of(ButtonWidget.builder(widget.getValue().apply(info.value), widget.getKey()).dimensions(width - 160, 0, 150, 20).tooltip(getTooltip(info)).build(), resetButton), name, info); - } else if (info.field.getType() == List.class) { - if (!reload) info.index = 0; - TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, Text.empty()); - widget.setMaxLength(info.width); - if (info.index < ((List) info.value).size()) - widget.setText((String.valueOf(((List) info.value).get(info.index)))); - Predicate processor = ((BiFunction>) info.widget).apply(widget, done); - widget.setTextPredicate(processor); - resetButton.setWidth(20); - resetButton.setMessage(Text.literal("R").formatted(Formatting.RED)); - ButtonWidget cycleButton = ButtonWidget.builder(Text.literal(String.valueOf(info.index)).formatted(Formatting.GOLD), (button -> { - if (((List) info.value).contains("")) ((List) info.value).remove(""); - info.index = info.index + 1; - if (info.index > ((List) info.value).size()) info.index = 0; - list.clear(); - fillList(); - })).dimensions(width - 185, 0, 20, 20).build(); - widget.setTooltip(getTooltip(info)); - this.list.addButton(List.of(widget, resetButton, cycleButton), name, info); - } else if (info.widget != null) { + if (info.function != null) { ClickableWidget widget; - Entry e = info.field.getAnnotation(Entry.class); - if (e.isSlider()) - widget = new MidnightSliderWidget(width - 160, 0, 150, 20, Text.of(info.tempValue), (Double.parseDouble(info.tempValue) - e.min()) / (e.max() - e.min()), info); - else - widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null, Text.of(info.tempValue)); + Entry e = info.entry; + if (info.function instanceof Map.Entry) { // Enums & booleans + var values = (Map.Entry>) info.function; + if (info.dataType.isEnum()) { + values.setValue(value -> getEnumTranslatableText(value, modid, info)); + } + widget = ButtonWidget.builder(values.getValue().apply(info.value), values.getKey()).dimensions(width - 185, 0, 150, 20).tooltip(info.getTooltip(true)).build(); + } else if (e.isSlider()) + widget = new MidnightSliderWidget(width - 185, 0, 150, 20, Text.of(info.tempValue), (Double.parseDouble(info.tempValue) - e.min()) / (e.max() - e.min()), info); + else widget = new TextFieldWidget(textRenderer, width - 185, 0, 150, 20, Text.empty()); if (widget instanceof TextFieldWidget textField) { - textField.setMaxLength(info.width); - textField.setText(info.tempValue); - Predicate processor = ((BiFunction>) info.widget).apply(textField, done); + textField.setMaxLength(e.width()); textField.setText(info.tempValue); + Predicate processor = ((BiFunction>) info.function).apply(textField, done); textField.setTextPredicate(processor); } - widget.setTooltip(getTooltip(info)); - if (e.isColor()) { - resetButton.setWidth(20); - resetButton.setMessage(Text.literal("R").formatted(Formatting.RED)); - ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"), (button -> { + widget.setTooltip(info.getTooltip(true)); + + ButtonWidget cycleButton = null; + if (info.field.getType() == List.class) { + cycleButton = ButtonWidget.builder(Text.literal(String.valueOf(info.listIndex)).formatted(Formatting.GOLD), (button -> { + var values = (List) info.value; + values.remove(""); + info.listIndex = info.listIndex + 1; + if (info.listIndex > values.size()) info.listIndex = 0; + info.tempValue = info.toTemporaryValue(); + if (info.listIndex == values.size()) info.tempValue = ""; + updateList(); })).dimensions(width - 185, 0, 20, 20).build(); - try { - colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); - } catch (Exception ignored) { - } - info.colorButton = colorButton; - colorButton.active = false; - this.list.addButton(List.of(widget, resetButton, colorButton), name, info); - } else this.list.addButton(List.of(widget, resetButton), name, info); - } else { - this.list.addButton(List.of(), name, info); - } - } - list.setScrollAmount(scrollProgress); - updateResetButtons(); + } + if (e.isColor()) { + ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"), + button -> new Thread(() -> { + Color newColor = JColorChooser.showDialog(null, Text.translatable("midnightconfig.colorChooser.title").getString(), Color.decode(!Objects.equals(info.tempValue, "") ? info.tempValue : "#FFFFFF")); + if (newColor != null) { + info.setValue("#" + Integer.toHexString(newColor.getRGB()).substring(2)); + updateList(); + } + }).start() + ).dimensions(width - 185, 0, 20, 20).build(); + try { colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); + } catch (Exception ignored) {} + info.actionButton = colorButton; + } else if (e.selectionMode() > -1) { + ButtonWidget explorerButton = IconButtonWidget.builder(Text.empty(), Identifier.of("midnightlib", "icon/explorer"), + button -> new Thread(() -> { + JFileChooser fileChooser = new JFileChooser(info.tempValue); + fileChooser.setFileSelectionMode(e.selectionMode()); fileChooser.setDialogType(e.fileChooserType()); + fileChooser.setDialogTitle(Text.translatable(translationPrefix + info.fieldName + ".fileChooser").getString()); + if ((e.selectionMode() == JFileChooser.FILES_ONLY || e.selectionMode() == JFileChooser.FILES_AND_DIRECTORIES) && Arrays.stream(e.fileExtensions()).noneMatch("*"::equals)) + fileChooser.setFileFilter(new FileNameExtensionFilter( + Text.translatable(translationPrefix + info.fieldName + ".fileFilter").getString(), e.fileExtensions())); + if (fileChooser.showDialog(null, null) == JFileChooser.APPROVE_OPTION) { + info.setValue(fileChooser.getSelectedFile().getAbsolutePath()); + updateList(); + } + }).start() + ).build(); + explorerButton.setPosition(width - 185, 0); + info.actionButton = explorerButton; + } + List widgets = Lists.newArrayList(widget, resetButton); + if (info.actionButton != null) { + if (IS_SYSTEM_MAC) info.actionButton.active = false; + widget.setWidth(widget.getWidth() - 22); widget.setX(widget.getX() + 22); + widgets.add(info.actionButton); + } if (cycleButton != null) { + if (info.actionButton != null) info.actionButton.setX(info.actionButton.getX() + 22); + widget.setWidth(widget.getWidth() - 22); widget.setX(widget.getX() + 22); + widgets.add(cycleButton); + } + if (!info.conditionsMet) widgets.forEach(w -> w.active = false); + this.list.addButton(widgets, name, info); + } else this.list.addButton(List.of(), name, info); + } list.setScrollAmount(scrollProgress); + updateButtons(); } } @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - this.renderBackground(context); + super.render(context, mouseX, mouseY, delta); this.list.render(context, mouseX, mouseY, delta); - - if (tabs.size() < 2) context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 15, 0xFFFFFF); - super.render(context,mouseX,mouseY,delta); + if (tabs.size() < 2) context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 10, 0xFFFFFFFF); } } @Environment(EnvType.CLIENT) public static class MidnightConfigListWidget extends ElementListWidget { - TextRenderer textRenderer; + public boolean renderHeaderSeparator = true; + public MidnightConfigListWidget(MinecraftClient client, int width, int height, int yStart, int y, int itemHeight) { super(client, width, height, yStart, y, itemHeight); } + @Override public int getScrollbarPositionX() { return this.width -7; } - public MidnightConfigListWidget(MinecraftClient minecraftClient, int i, int j, int k, int l, int m) { - super(minecraftClient, i, j, k, l, m); - this.centerListVertically = false; - textRenderer = minecraftClient.textRenderer; - } + /* @Override - public int getScrollbarPositionX() { return this.width -7; } - - public void addButton(List buttons, Text text, EntryInfo info) { - this.addEntry(new ButtonEntry(buttons, text, info)); + protected void drawHeaderAndFooterSeparators(DrawContext context) { + if (renderHeaderSeparator) super.drawHeaderAndFooterSeparators(context); + else { RenderSystem.enableBlend(); + context.drawTexture(this.client.world == null ? Screen.FOOTER_SEPARATOR_TEXTURE : Screen.INWORLD_FOOTER_SEPARATOR_TEXTURE, this.getX(), this.getBottom(), 0.0F, 0.0F, this.getWidth(), 2, 32, 2); + RenderSystem.disableBlend(); } } - public void clear() { - this.clearEntries(); - } - @Override - public int getRowWidth() { return 10000; } + */ + public void addButton(List buttons, Text text, EntryInfo info) { this.addEntry(new ButtonEntry(buttons, text, info)); } + public void clear() { this.clearEntries(); } + @Override public int getRowWidth() { return 10000; } } public static class ButtonEntry extends ElementListWidget.Entry { private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + public final Text text; public final List buttons; - private final Text text; public final EntryInfo info; - private final List children = new ArrayList<>(); - public static final Map buttonsWithText = new HashMap<>(); + public boolean centered = false; + public MultilineTextWidget title; - private ButtonEntry(List buttons, Text text, EntryInfo info) { - if (!buttons.isEmpty()) buttonsWithText.put(buttons.get(0),text); - this.buttons = buttons; - this.text = text; - this.info = info; - children.addAll(buttons); + public ButtonEntry(List buttons, Text text, EntryInfo info) { + this.buttons = buttons; this.text = text; this.info = info; + if (info != null && info.comment != null) this.centered = info.comment.centered(); + int scaledWidth = MinecraftClient.getInstance().getWindow().getScaledWidth(); + + if (text != null && (!text.getString().contains("spacer") || !buttons.isEmpty())) { + title = new MultilineTextWidget((centered) ? (scaledWidth / 2 - (textRenderer.getWidth(text) / 2)) : 12, 0, text, textRenderer); + title.setCentered(centered); + if (info != null) title.setTooltip(info.getTooltip(false)); + title.setMaxWidth(!buttons.isEmpty() ? buttons.get(buttons.size() > 2 ? buttons.size()-1 : 0).getX() - 16 : scaledWidth - 24); + } } public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - buttons.forEach(b -> { b.setY(y); b.render(context, mouseX, mouseY, tickDelta); }); - if (text != null && (!text.getString().contains("spacer") || !buttons.isEmpty())) { - if (info.centered) context.drawTextWithShadow(textRenderer, text, MinecraftClient.getInstance().getWindow().getScaledWidth() / 2 - (textRenderer.getWidth(text) / 2), y + 5, 0xFFFFFF); - else { - int wrappedY = y; - for(Iterator iterator = textRenderer.wrapLines(text, (buttons.size() > 1 ? buttons.get(1).getX()-24 : MinecraftClient.getInstance().getWindow().getScaledWidth() - 24)).iterator(); iterator.hasNext(); wrappedY += 9) { - OrderedText orderedText = iterator.next(); - context.drawTextWithShadow(textRenderer, orderedText, 12, wrappedY + 5, 0xFFFFFF); - } + buttons.forEach(b -> { b.setY(y); b.render(context, mouseX, mouseY, tickDelta);}); + if (title != null) { + title.setY(y+5); + title.render(context, mouseX, mouseY, tickDelta); + boolean tooltipVisible = mouseX >= title.getX() && mouseX < title.getWidth() + title.getX() && mouseY >= title.getY() && mouseY < title.getHeight() + title.getY(); + if (tooltipVisible && title.getTooltip() != null) context.drawOrderedTooltip(textRenderer, title.getTooltip().getLines(MinecraftClient.getInstance()), mouseX, mouseY); + + if (info.entry != null && !this.buttons.isEmpty() && this.buttons.get(0) != null) { + ClickableWidget widget = this.buttons.get(0); + int idMode = this.info.entry.idMode(); + if (idMode != -1) context.drawItem(idMode == 0 ? Registries.ITEM.get(Identifier.tryParse(this.info.tempValue)).getDefaultStack() : Registries.BLOCK.get(Identifier.tryParse(this.info.tempValue)).asItem().getDefaultStack(), widget.getX() + widget.getWidth() - 18, y + 2); } } } - public List children() {return children;} - public List selectableChildren() {return children;} + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (this.info != null && this.info.comment != null && !this.info.comment.url().isBlank()) + ConfirmLinkScreen.open(this.info.comment.url(), MinecraftClient.getInstance().currentScreen, true); + return super.mouseClicked(mouseX, mouseY, button); + } + + public List children() {return Lists.newArrayList(buttons);} + public List selectableChildren() {return Lists.newArrayList(buttons);} } public static class MidnightSliderWidget extends SliderWidget { private final EntryInfo info; private final Entry e; public MidnightSliderWidget(int x, int y, int width, int height, Text text, double value, EntryInfo info) { super(x, y, width, height, text, value); - this.e = info.field.getAnnotation(Entry.class); + this.e = info.entry; this.info = info; } @Override - protected void updateMessage() { - this.setMessage(Text.of(info.tempValue)); - } + public void updateMessage() { this.setMessage(Text.of(info.tempValue)); } @Override - protected void applyValue() { - if (info.field.getType() == int.class) info.value = ((Number) (e.min() + value * (e.max() - e.min()))).intValue(); - else if (info.field.getType() == double.class) info.value = Math.round((e.min() + value * (e.max() - e.min())) * (double) e.precision()) / (double) e.precision(); - else if (info.field.getType() == float.class) info.value = Math.round((e.min() + value * (e.max() - e.min())) * (float) e.precision()) / (float) e.precision(); - info.tempValue = String.valueOf(info.value); + public void applyValue() { + if (info.dataType == int.class) info.setValue(((Number) (e.min() + value * (e.max() - e.min()))).intValue()); + else if (info.dataType == double.class) info.setValue(Math.round((e.min() + value * (e.max() - e.min())) * (double) e.precision()) / (double) e.precision()); + else if (info.dataType == float.class) info.setValue(Math.round((e.min() + value * (e.max() - e.min())) * (float) e.precision()) / (float) e.precision()); } } - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Entry { - int width() default 100; + public static class NonEntryExclusionStrategy implements ExclusionStrategy { + public boolean shouldSkipClass(Class clazz) { return false; } + public boolean shouldSkipField(FieldAttributes fieldAttributes) { return fieldAttributes.getAnnotation(Entry.class) == null; } + } + + /** + * Entry Annotation
+ * - width: The maximum character length of the {@link String}, {@link Identifier} or String/Identifier {@link List<>} field
+ * - min: The minimum value of the int, float or double field
+ * - max: The maximum value of the int, float or double field
+ * - name: Will be used instead of the default translation key, if not empty
+ * - selectionMode: The selection mode of the file picker button for {@link String} fields, + * -1 for none, {@link JFileChooser#FILES_ONLY} for files, {@link JFileChooser#DIRECTORIES_ONLY} for directories, + * {@link JFileChooser#FILES_AND_DIRECTORIES} for both (default: -1). Remember to set the translation key + * [modid].midnightconfig.[fieldName].fileChooser.title for the file picker dialog title
+ * - fileChooserType: The type of the file picker button for {@link String} fields, + * can be {@link JFileChooser#OPEN_DIALOG} or {@link JFileChooser#SAVE_DIALOG} (default: {@link JFileChooser#OPEN_DIALOG}). + * Remember to set the translation key [modid].midnightconfig.[fieldName].fileFilter.description for the file filter description + * if "*" is not used as file extension
+ * - fileExtensions: The file extensions for the file picker button for {@link String} fields (default: {"*"}), + * only works if selectionMode is {@link JFileChooser#FILES_ONLY} or {@link JFileChooser#FILES_AND_DIRECTORIES}
+ * - isColor: If the field is a hexadecimal color code (default: false)
+ * - isSlider: If the field is a slider (default: false)
+ * - precision: The precision of the float or double field (default: 100)
+ * - category: The category of the field in the config screen (default: "default")
+ * */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Entry { + int width() default 400; double min() default Double.MIN_NORMAL; double max() default Double.MAX_VALUE; String name() default ""; + int selectionMode() default -1; // -1 for none, 0 for file, 1 for directory, 2 for both + int fileChooserType() default JFileChooser.OPEN_DIALOG; + String[] fileExtensions() default {"*"}; + int idMode() default -1; // -1 for none, 0 for item, 1 for block boolean isColor() default false; boolean isSlider() default false; int precision() default 100; String category() default "default"; + @Deprecated String requiredMod() default ""; } + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Client {} + + /** + * Hides the entry in config screens, but still makes it accessible through the command {@code /midnightconfig MOD_ID ENTRY} and directly editing the config file. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Server {} + + /** + * Hides the entry entirely. + * Accessible only through directly editing the config file. + * Perfect for saving persistent internal data. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Hidden {} + + /** + * Comment Annotation
+ * - {@link Comment#centered()}: If the comment should be centered
+ * - {@link Comment#category()}: The category of the comment in the config screen
+ * - {@link Comment#name()}: Will be used instead of the default translation key, if not empty
+ * - {@link Comment#url()}: The url of the comment should link to in the config screen (none if left empty)
+ * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Comment { boolean centered() default false; String category() default "default"; + String name() default ""; + String url() default ""; + @Deprecated String requiredMod() default ""; + } + /** + * Condition Annotation
+ * - {@link Condition#requiredModId()}: The id of a mod that is required to be loaded.
+ * - {@link Condition#requiredOption()}: The {@link Field} which will be used to check the condition. Can also access options of other MidnightLib mods ("modid:optionName").
+ * - {@link Condition#requiredValue()}: The value that {@link Condition#requiredOption()} should be set to for the condition to be met.
+ * - {@link Condition#visibleButLocked()}: The behaviour to take when {@link Condition#requiredModId} is not loaded + * or {@link Condition#requiredOption()} returns a value that is not {@link Condition#requiredValue()}.
+ * true – Option is visible, but not editable
+ * false – Option is completely hidden + */ + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(Conditions.class) + @Target(ElementType.FIELD) + public @interface Condition { + String requiredModId() default ""; + String requiredOption() default ""; + String[] requiredValue() default {"true"}; + boolean visibleButLocked() default false; } - public static class HiddenAnnotationExclusionStrategy implements ExclusionStrategy { - public boolean shouldSkipClass(Class clazz) { return false; } - public boolean shouldSkipField(FieldAttributes fieldAttributes) { - return fieldAttributes.getAnnotation(Entry.class) == null; - } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Conditions { + Condition[] value(); } -} +} \ No newline at end of file diff --git a/common/src/main/java/eu/midnightdust/lib/util/MidnightColorUtil.java b/common/src/main/java/eu/midnightdust/lib/util/MidnightColorUtil.java index f46f972..210ebc3 100755 --- a/common/src/main/java/eu/midnightdust/lib/util/MidnightColorUtil.java +++ b/common/src/main/java/eu/midnightdust/lib/util/MidnightColorUtil.java @@ -3,24 +3,12 @@ package eu.midnightdust.lib.util; import java.awt.Color; public class MidnightColorUtil { - public static float hue; - public static void tick() { - if (hue > 1) hue = 0f; - hue = hue + 0.01f; - } - /** * @param colorStr e.g. "FFFFFF" or "#FFFFFF" * @return Color as RGB */ public static Color hex2Rgb(String colorStr) { - try { - return Color.decode("#" + colorStr.replace("#", "")); - } catch (Exception ignored) {} - return Color.BLACK; - } - - public static Color radialRainbow(float saturation, float brightness) { - return Color.getHSBColor(hue, saturation, brightness); + try { return Color.decode("#" + colorStr.replace("#", "")); + } catch (Exception ignored) { return Color.BLACK; } } } diff --git a/common/src/main/java/eu/midnightdust/lib/util/PlatformFunctions.java b/common/src/main/java/eu/midnightdust/lib/util/PlatformFunctions.java index 7cc3b5b..aabefb3 100644 --- a/common/src/main/java/eu/midnightdust/lib/util/PlatformFunctions.java +++ b/common/src/main/java/eu/midnightdust/lib/util/PlatformFunctions.java @@ -7,6 +7,11 @@ import net.minecraft.server.command.ServerCommandSource; import java.nio.file.Path; public class PlatformFunctions { + @ExpectPlatform + public static String getPlatformName() { + // Just throw an error, the content should get replaced at runtime. + throw new AssertionError(); + } @ExpectPlatform public static Path getConfigDirectory() { // Just throw an error, the content should get replaced at runtime. diff --git a/common/src/main/resources/assets/midnightlib/lang/de_de.json b/common/src/main/resources/assets/midnightlib/lang/de_de.json index b425e31..2afbf42 100755 --- a/common/src/main/resources/assets/midnightlib/lang/de_de.json +++ b/common/src/main/resources/assets/midnightlib/lang/de_de.json @@ -2,7 +2,6 @@ "midnightlib.overview.title":"MidnightConfig Übersicht", "midnightlib.midnightconfig.title":"MidnightLib Konfiguration", "midnightlib.midnightconfig.config_screen_list":"Konfigurationsübersicht", - "midnightlib.midnightconfig.background_texture":"Textur der Konfigurationsbildschirme", - "modmenu.descriptionTranslation.midnightlib": "Code-Bibliothek für Mods von MidnightDust.\nStellt eine Konfigurationsschnittstelle, automatische Kompatibilität, oft genutzten Code und Hüte für Unterstützer bereit.", - "modmenu.summaryTranslation.midnightlib": "Code-Bibliothek für Mods von MidnightDust." + "modmenu.summaryTranslation.midnightlib": "Code-Bibliothek für einfache Konfiguration.", + "midnightconfig.colorChooser.title": "Wähle eine Farbe" } \ No newline at end of file diff --git a/common/src/main/resources/assets/midnightlib/lang/en_us.json b/common/src/main/resources/assets/midnightlib/lang/en_us.json index 326564a..d81ceab 100755 --- a/common/src/main/resources/assets/midnightlib/lang/en_us.json +++ b/common/src/main/resources/assets/midnightlib/lang/en_us.json @@ -5,10 +5,9 @@ "midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§aYes", "midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cNo", "midnightlib.midnightconfig.enum.ConfigButton.MODMENU":"§bModMenu", - "midnightlib.midnightconfig.background_texture":"Texture of config screen backgrounds", "midnightlib.modrinth":"Modrinth", "midnightlib.curseforge":"CurseForge", "midnightlib.wiki":"Wiki", - "modmenu.descriptionTranslation.midnightlib": "Common Library for Team MidnightDust's mods.\nProvides a config api, automatic integration with other mods, common utils, and cosmetics.", - "modmenu.summaryTranslation.midnightlib": "Common Library for Team MidnightDust's mods." + "modmenu.summaryTranslation.midnightlib": "Common Library for easy configuration.", + "midnightconfig.colorChooser.title": "Choose a color" } \ No newline at end of file diff --git a/common/src/main/resources/assets/midnightlib/lang/es_ar.json b/common/src/main/resources/assets/midnightlib/lang/es_ar.json new file mode 100644 index 0000000..0829d0a --- /dev/null +++ b/common/src/main/resources/assets/midnightlib/lang/es_ar.json @@ -0,0 +1,8 @@ +{ + "midnightlib.overview.title": "Visión general de MidnightConfig", + "midnightlib.midnightconfig.title": "Configuración de MidnightLib", + "midnightlib.midnightconfig.config_screen_list": "Habilitar lista de pantallas de configuración", + "midnightlib.midnightconfig.enum.ConfigButton.TRUE": "§aSí", + "modmenu.summaryTranslation.midnightlib": "Librería común para facilitar la configuración.", + "midnightconfig.colorChooser.title": "Elegí un color" +} diff --git a/common/src/main/resources/assets/midnightlib/lang/fr_fr.json b/common/src/main/resources/assets/midnightlib/lang/fr_fr.json index 143b2eb..5898303 100644 --- a/common/src/main/resources/assets/midnightlib/lang/fr_fr.json +++ b/common/src/main/resources/assets/midnightlib/lang/fr_fr.json @@ -4,7 +4,5 @@ "midnightlib.midnightconfig.config_screen_list":"Activer la liste de l'écran de configuration", "midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§aOui", "midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cNon", - "midnightlib.midnightconfig.background_texture":"Texture d'arrière-plan de l'écran de configuration", - "modmenu.descriptionTranslation.midnightlib": "Bibliothèque commune pour les mods de la Team MidnightDust.\nFournit une API de configuration, une intégration automatique avec d'autres mods, des utilitaires courants et des cosmétiques.", "modmenu.summaryTranslation.midnightlib": "Bibliothèque commune pour les mods de la Team MidnightDust." } diff --git a/common/src/main/resources/assets/midnightlib/lang/ms_my.json b/common/src/main/resources/assets/midnightlib/lang/ms_my.json new file mode 100644 index 0000000..6a5e177 --- /dev/null +++ b/common/src/main/resources/assets/midnightlib/lang/ms_my.json @@ -0,0 +1,8 @@ +{ + "midnightlib.overview.title": "Ikhtisar MidnightConfig", + "midnightlib.midnightconfig.title": "Konfigurasi MidnightLib", + "midnightlib.midnightconfig.config_screen_list": "Dayakan Senarai Skrin Konfigurasi", + "midnightlib.midnightconfig.enum.ConfigButton.TRUE": "§aYa", + "midnightlib.midnightconfig.enum.ConfigButton.FALSE": "§cTidak", + "modmenu.summaryTranslation.midnightlib": "Pustaka Biasa untuk konfigurasi mudah." +} \ No newline at end of file diff --git a/common/src/main/resources/assets/midnightlib/lang/pt_br.json b/common/src/main/resources/assets/midnightlib/lang/pt_br.json index 79cd680..08262fc 100644 --- a/common/src/main/resources/assets/midnightlib/lang/pt_br.json +++ b/common/src/main/resources/assets/midnightlib/lang/pt_br.json @@ -4,7 +4,5 @@ "midnightlib.midnightconfig.config_screen_list":"Ativar lista de telas de configuração", "midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§aVerdadeiro", "midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cFalso", - "midnightlib.midnightconfig.background_texture":"Textura dos fundos da tela de configuração", - "modmenu.descriptionTranslation.midnightlib": "Biblioteca comum para mods do Team MidnightDust.\nFornece uma API de configuração, integração automática com outros mods, utilitários comuns e cosméticos.", "modmenu.summaryTranslation.midnightlib": "Biblioteca comum para mods do Team MidnightDust." } \ No newline at end of file diff --git a/common/src/main/resources/assets/midnightlib/lang/ru_ru.json b/common/src/main/resources/assets/midnightlib/lang/ru_ru.json new file mode 100644 index 0000000..614b28e --- /dev/null +++ b/common/src/main/resources/assets/midnightlib/lang/ru_ru.json @@ -0,0 +1,11 @@ +{ + "midnightlib.overview.title": "Обзор MidnightConfig", + "midnightlib.midnightconfig.title": "Конфигурация MidnightLib", + "midnightlib.midnightconfig.config_screen_list": "Включить список экранов настройки", + "midnightlib.midnightconfig.enum.ConfigButton.TRUE": "§aДа", + "midnightlib.midnightconfig.enum.ConfigButton.FALSE": "§cНет", + "midnightlib.midnightconfig.enum.ConfigButton.MODMENU": "§bModMenu", + "midnightlib.wiki": "Вики", + "modmenu.summaryTranslation.midnightlib": "Общая библиотека для простой настройки.", + "midnightconfig.colorChooser.title": "Выберите цвет" +} \ No newline at end of file diff --git a/common/src/main/resources/assets/midnightlib/lang/tt_ru.json b/common/src/main/resources/assets/midnightlib/lang/tt_ru.json index ce648ee..c06a27d 100644 --- a/common/src/main/resources/assets/midnightlib/lang/tt_ru.json +++ b/common/src/main/resources/assets/midnightlib/lang/tt_ru.json @@ -4,8 +4,6 @@ "midnightlib.midnightconfig.config_screen_list":"Көйләүләр экранының исемлеген кушу", "midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§aӘйе", "midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cЮк", - "midnightlib.midnightconfig.background_texture":"Көйләүләр экранының фонының текстурасы", "midnightlib.wiki":"Вики", - "modmenu.descriptionTranslation.midnightlib": "MidnightDust төркеменең модлары өчен гомуми китапханә.\nКөйләүләр API-ын, башка модлар белән автоматик интеграцияне, гомуми хезмәти программаларны һәм бизәнүләрне тәэмин ителә.", "modmenu.summaryTranslation.midnightlib": "MidnightDust төркеменең модлары өчен гомуми китапханә." } diff --git a/common/src/main/resources/assets/midnightlib/lang/uk_ua.json b/common/src/main/resources/assets/midnightlib/lang/uk_ua.json index 6aee0e9..b785e26 100644 --- a/common/src/main/resources/assets/midnightlib/lang/uk_ua.json +++ b/common/src/main/resources/assets/midnightlib/lang/uk_ua.json @@ -2,8 +2,6 @@ "midnightlib.overview.title":"Огляд MidnightConfig", "midnightlib.midnightconfig.title":"Конфігурація MidnightLib", "midnightlib.midnightconfig.config_screen_list":"Увімкнути список екрана конфігурації", - "midnightlib.midnightconfig.background_texture":"Текстура фону екрана конфігурації", "midnightlib.wiki":"Вікі", - "modmenu.descriptionTranslation.midnightlib": "Загальна бібліотека для модів команди MidnightDust.\nНадає конфігураційний API, автоматичну інтеграцію з іншими модами, загальні утиліти та косметику.", "modmenu.summaryTranslation.midnightlib": "Загальна бібліотека для модів команди MidnightDust." } diff --git a/common/src/main/resources/assets/midnightlib/lang/zh_cn.json b/common/src/main/resources/assets/midnightlib/lang/zh_cn.json new file mode 100644 index 0000000..6dc1df7 --- /dev/null +++ b/common/src/main/resources/assets/midnightlib/lang/zh_cn.json @@ -0,0 +1,10 @@ +{ + "midnightlib.overview.title":"MidnightConfig 概述", + "midnightlib.midnightconfig.title":"MidnightLib 配置", + "midnightlib.midnightconfig.config_screen_list":"启用配置屏幕列表", + "midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§a是", + "midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§c否", + "midnightlib.midnightconfig.enum.ConfigButton.MODMENU":"§b模组菜单", + "modmenu.summaryTranslation.midnightlib": "一个便于模组配置的通用库模组", + "midnightconfig.colorChooser.title": "选择一种颜色" +} diff --git a/common/src/main/resources/assets/midnightlib/lang/zh_tw.json b/common/src/main/resources/assets/midnightlib/lang/zh_tw.json index 4b8207c..e710f86 100644 --- a/common/src/main/resources/assets/midnightlib/lang/zh_tw.json +++ b/common/src/main/resources/assets/midnightlib/lang/zh_tw.json @@ -5,10 +5,6 @@ "midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§a是", "midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§c否", "midnightlib.midnightconfig.enum.ConfigButton.MODMENU":"§b模組選單", - "midnightlib.midnightconfig.background_texture":"設定背景畫面的材質", - "midnightlib.modrinth":"Modrinth", - "midnightlib.curseforge":"CurseForge", "midnightlib.wiki":"維基", - "modmenu.descriptionTranslation.midnightlib": "MidnightDust 團隊的常用程式庫模組。\n提供設定 API、與其他模組自動整合、常用工具和美觀。", "modmenu.summaryTranslation.midnightlib": "MidnightDust 團隊的常用程式庫模組。" } diff --git a/common/src/main/resources/assets/midnightlib/lang/zlm_arab.json b/common/src/main/resources/assets/midnightlib/lang/zlm_arab.json new file mode 100644 index 0000000..ddd1a99 --- /dev/null +++ b/common/src/main/resources/assets/midnightlib/lang/zlm_arab.json @@ -0,0 +1,9 @@ +{ + "midnightlib.overview.title": "اختصار MidnightConfig", + "midnightlib.midnightconfig.title": "کونفيݢوراسي MidnightLib", + "midnightlib.midnightconfig.config_screen_list": "داياکن سناراي سکرين کونفيݢوراسي", + "midnightlib.midnightconfig.enum.ConfigButton.TRUE": "§aيا", + "midnightlib.midnightconfig.enum.ConfigButton.FALSE": "§cتيدق", + "midnightlib.wiki": "ويکي", + "modmenu.summaryTranslation.midnightlib": "ڤوستاک بياسا اونتوق کونفيݢوراسي موده." +} \ No newline at end of file diff --git a/common/src/main/resources/assets/midnightlib/textures/gui/explorer.png b/common/src/main/resources/assets/midnightlib/textures/gui/explorer.png new file mode 100644 index 0000000..d7f4311 Binary files /dev/null and b/common/src/main/resources/assets/midnightlib/textures/gui/explorer.png differ diff --git a/common/src/main/resources/assets/midnightlib/textures/gui/midnightlib_button.png b/common/src/main/resources/assets/midnightlib/textures/gui/midnightlib_button.png index 0828266..81d9a30 100644 Binary files a/common/src/main/resources/assets/midnightlib/textures/gui/midnightlib_button.png and b/common/src/main/resources/assets/midnightlib/textures/gui/midnightlib_button.png differ diff --git a/common/src/main/resources/assets/midnightlib/textures/gui/reset.png b/common/src/main/resources/assets/midnightlib/textures/gui/reset.png new file mode 100644 index 0000000..c0f9aaa Binary files /dev/null and b/common/src/main/resources/assets/midnightlib/textures/gui/reset.png differ diff --git a/fabric-like/build.gradle b/fabric-like/build.gradle deleted file mode 100644 index 1b87612..0000000 --- a/fabric-like/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -architectury { - common(rootProject.enabled_platforms.split(",")) -} -repositories { - maven { url "https://maven.terraformersmc.com/releases" } -} - -loom { -} - -dependencies { - modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" - modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" - modImplementation ("com.terraformersmc:modmenu:${rootProject.mod_menu_version}") - - compileClasspath(project(path: ":common", configuration: "namedElements")) { transitive false } -} diff --git a/fabric/build.gradle b/fabric/build.gradle index 3cc4036..4469524 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,5 +1,9 @@ plugins { - id "com.github.johnrengelman.shadow" version "7.1.2" + id 'com.github.johnrengelman.shadow' + id "me.shedaniel.unified-publishing" +} +repositories { + maven { url "https://maven.terraformersmc.com/releases" } } architectury { @@ -12,28 +16,28 @@ loom { configurations { common - shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. + shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files. compileClasspath.extendsFrom common runtimeClasspath.extendsFrom common developmentFabric.extendsFrom common - archivesBaseName = rootProject.archives_base_name + "-fabric" + archivesBaseName = rootProject.archives_base_name + version = rootProject.mod_version + "-" + project.name + "+" + rootProject.minecraft_version } dependencies { modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" + modCompileOnly ("com.terraformersmc:modmenu:${rootProject.mod_menu_version}") common(project(path: ":common", configuration: "namedElements")) { transitive false } shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } - common(project(path: ":fabric-like", configuration: "namedElements")) { transitive false } - shadowCommon(project(path: ":fabric-like", configuration: "transformProductionFabric")) { transitive false } } processResources { - inputs.property "version", project.version + inputs.property "version", rootProject.version filesMatching("fabric.mod.json") { - expand "version": project.version + expand "version": rootProject.version } } @@ -41,17 +45,12 @@ shadowJar { exclude "architectury.common.json" configurations = [project.configurations.shadowCommon] - classifier "dev-shadow" + archiveClassifier = "dev-shadow" } remapJar { input.set shadowJar.archiveFile dependsOn shadowJar - classifier null -} - -jar { - classifier "dev" } sourcesJar { @@ -66,16 +65,40 @@ components.java { } } -publishing { - publications { - mavenFabric(MavenPublication) { - artifactId = rootProject.archives_base_name + "-" + project.name - from components.java +/*unifiedPublishing { + project { + displayName = "MidnightLib $rootProject.version - Fabric $project.minecraft_version" + releaseType = "$project.release_type" + changelog = releaseChangelog() + gameVersions = [] + gameLoaders = ["fabric","quilt"] + mainPublication remapJar + relations { + depends { + curseforge = "fabric-api" + modrinth = "fabric-api" + } + } + + var CURSEFORGE_TOKEN = project.findProperty("CURSEFORGE_TOKEN") ?: System.getenv("CURSEFORGE_TOKEN") + if (CURSEFORGE_TOKEN != null) { + curseforge { + token = CURSEFORGE_TOKEN + id = rootProject.curseforge_id + gameVersions.addAll "Java 17", project.minecraft_version + if (project.supported_versions != "") gameVersions.addAll project.supported_versions + } + } + + var MODRINTH_TOKEN = project.findProperty("MODRINTH_TOKEN") ?: System.getenv("MODRINTH_TOKEN") + if (MODRINTH_TOKEN != null) { + modrinth { + token = MODRINTH_TOKEN + id = rootProject.modrinth_id + version = rootProject.mod_version + "+" + rootProject.minecraft_version + "-" + project.name + gameVersions.addAll project.minecraft_version + if (project.supported_versions != "") gameVersions.addAll project.supported_versions + } } } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - } -} +}*/ \ No newline at end of file diff --git a/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibClientFabric.java b/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibClientFabric.java deleted file mode 100644 index 9cbb33e..0000000 --- a/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibClientFabric.java +++ /dev/null @@ -1,16 +0,0 @@ -package eu.midnightdust.fabric.core; - -import eu.midnightdust.core.MidnightLibClient; -import eu.midnightdust.lib.util.MidnightColorUtil; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; - -public class MidnightLibClientFabric implements ClientModInitializer { - @Override - public void onInitializeClient() { - MidnightLibClient.onInitializeClient(); - ClientTickEvents.END_CLIENT_TICK.register( - client -> MidnightColorUtil.tick() - ); - } -} diff --git a/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibFabric.java b/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibFabric.java new file mode 100644 index 0000000..2538458 --- /dev/null +++ b/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibFabric.java @@ -0,0 +1,14 @@ +package eu.midnightdust.fabric.core; + +import eu.midnightdust.core.MidnightLib; +import net.fabricmc.api.*; + +public class MidnightLibFabric implements DedicatedServerModInitializer, ClientModInitializer { + @Override @Environment(EnvType.CLIENT) + public void onInitializeClient() { + MidnightLib.onInitializeClient(); + MidnightLib.registerAutoCommand(); + } + @Override + public void onInitializeServer() {MidnightLib.registerAutoCommand();} +} diff --git a/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibServerFabric.java b/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibServerFabric.java deleted file mode 100644 index 90fb7bb..0000000 --- a/fabric/src/main/java/eu/midnightdust/fabric/core/MidnightLibServerFabric.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.midnightdust.fabric.core; - -import eu.midnightdust.core.MidnightLibServer; -import net.fabricmc.api.DedicatedServerModInitializer; - -public class MidnightLibServerFabric implements DedicatedServerModInitializer { - @Override - public void onInitializeServer() { - MidnightLibServer.onInitializeServer(); - } -} diff --git a/fabric-like/src/main/java/eu/midnightdust/lib/config/AutoModMenu.java b/fabric/src/main/java/eu/midnightdust/lib/config/AutoModMenu.java similarity index 83% rename from fabric-like/src/main/java/eu/midnightdust/lib/config/AutoModMenu.java rename to fabric/src/main/java/eu/midnightdust/lib/config/AutoModMenu.java index 4a16c68..8f7ebd0 100755 --- a/fabric-like/src/main/java/eu/midnightdust/lib/config/AutoModMenu.java +++ b/fabric/src/main/java/eu/midnightdust/lib/config/AutoModMenu.java @@ -2,7 +2,7 @@ package eu.midnightdust.lib.config; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; -import eu.midnightdust.core.MidnightLibClient; +import eu.midnightdust.core.MidnightLib; import eu.midnightdust.core.config.MidnightLibConfig; import java.util.HashMap; @@ -19,10 +19,8 @@ public class AutoModMenu implements ModMenuApi { public Map> getProvidedConfigScreenFactories() { HashMap> map = new HashMap<>(); MidnightConfig.configClass.forEach((modid, cClass) -> { - if (!MidnightLibClient.hiddenMods.contains(modid)) + if (!MidnightLib.hiddenMods.contains(modid)) map.put(modid, parent -> MidnightConfig.getScreen(parent, modid)); - } - ); - return map; + }); return map; } } \ No newline at end of file diff --git a/fabric/src/main/java/eu/midnightdust/lib/util/fabric/PlatformFunctionsImpl.java b/fabric/src/main/java/eu/midnightdust/lib/util/fabric/PlatformFunctionsImpl.java index 2ade0c4..5615bd8 100644 --- a/fabric/src/main/java/eu/midnightdust/lib/util/fabric/PlatformFunctionsImpl.java +++ b/fabric/src/main/java/eu/midnightdust/lib/util/fabric/PlatformFunctionsImpl.java @@ -10,6 +10,9 @@ import net.minecraft.server.command.ServerCommandSource; import java.nio.file.Path; public class PlatformFunctionsImpl { + public static String getPlatformName() { + return "fabric"; + } /** * This is our actual method to {@link PlatformFunctions#getConfigDirectory()}. */ diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 6a57513..6cb1865 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -1,54 +1,56 @@ { - "schemaVersion": 1, - "id": "midnightlib", - "version": "${version}", + "schemaVersion": 1, + "id": "midnightlib", + "version": "${version}", - "name": "MidnightLib", - "description": "Common Library for Team MidnightDust's mods.", - "authors": [ - "Motschen", - "TeamMidnightDust" - ], - "contact": { - "homepage": "https://www.midnightdust.eu/", - "sources": "https://github.com/TeamMidnightDust/MidnightLib", - "issues": "https://github.com/TeamMidnightDust/MidnightLib/issues" - }, - - "license": "MIT", - "icon": "assets/midnightlib/icon.png", - - "environment": "*", - "entrypoints": { - "client": [ - "eu.midnightdust.fabric.core.MidnightLibClientFabric" + "name": "MidnightLib", + "description": "Common Library for Team MidnightDust's mods.", + "authors": [ + "Motschen", + "TeamMidnightDust" ], - "server": [ - "eu.midnightdust.fabric.core.MidnightLibServerFabric" + "contributors": [ + "maloryware", + "Jaffe2718" ], - "modmenu": [ - "eu.midnightdust.lib.config.AutoModMenu" - ] - }, - "depends": { - "fabric-resource-loader-v0": "*", - "minecraft": ">=1.20-beta.2" - }, + "contact": { + "homepage": "https://www.midnightdust.eu/", + "sources": "https://github.com/TeamMidnightDust/MidnightLib", + "issues": "https://github.com/TeamMidnightDust/MidnightLib/issues" + }, - "mixins": [ - "midnightlib.mixins.json" - ], + "license": "MIT", + "icon": "assets/midnightlib/icon.png", - "custom": { - "modmenu": { - "links": { - "modmenu.discord": "https://discord.midnightdust.eu/", - "modmenu.website": "https://www.midnightdust.eu/", - "midnightlib.curseforge": "https://www.curseforge.com/minecraft/mc-mods/midnightlib", - "midnightlib.modrinth": "https://modrinth.com/mod/midnightlib", - "midnightlib.wiki": "https://github.com/TeamMidnightDust/MidnightLib/wiki" - }, - "badges": [ "library" ] + "environment": "*", + "entrypoints": { + "server": [ + "eu.midnightdust.fabric.core.MidnightLibFabric" + ], + "client": [ + "eu.midnightdust.fabric.core.MidnightLibFabric" + ], + "modmenu": [ + "eu.midnightdust.lib.config.AutoModMenu" + ] + }, + "depends": { + "fabric-resource-loader-v0": "*", + "minecraft": ">=1.20" + }, + + "mixins": [ + "midnightlib.mixins.json" + ], + + "custom": { + "modmenu": { + "links": { + "modmenu.discord": "http://discord.midnightdust.eu/", + "modmenu.website": "https://midnightdust.eu/midnightlib", + "midnightlib.wiki": "https://midnightdust.eu/wiki/midnightlib" + }, + "badges": [ "library" ] + } } - } } diff --git a/forge/build.gradle b/forge/build.gradle deleted file mode 100644 index f80b84f..0000000 --- a/forge/build.gradle +++ /dev/null @@ -1,82 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "7.1.2" -} - -architectury { - platformSetupLoomIde() - forge() -} - -loom { - forge { - mixinConfig "midnightlib.mixins.json" - } -} - -configurations { - common - shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. - compileClasspath.extendsFrom common - runtimeClasspath.extendsFrom common - developmentForge.extendsFrom common - archivesBaseName = rootProject.archives_base_name + "-forge" -} - -dependencies { - forge "net.minecraftforge:forge:${rootProject.forge_version}" - - common(project(path: ":common", configuration: "namedElements")) { transitive false } - shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false } -} - -processResources { - inputs.property "version", project.version - - filesMatching("META-INF/mods.toml") { - expand "version": project.version - } -} - -shadowJar { - exclude "fabric.mod.json" - exclude "architectury.common.json" - - configurations = [project.configurations.shadowCommon] - classifier "dev-shadow" -} - -remapJar { - input.set shadowJar.archiveFile - dependsOn shadowJar - classifier null -} - -jar { - classifier "dev" -} - -sourcesJar { - def commonSources = project(":common").sourcesJar - dependsOn commonSources - from commonSources.archiveFile.map { zipTree(it) } -} - -components.java { - withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { - skip() - } -} - -publishing { - publications { - mavenForge(MavenPublication) { - artifactId = rootProject.archives_base_name + "-" + project.name - from components.java - } - } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - } -} diff --git a/forge/gradle.properties b/forge/gradle.properties deleted file mode 100644 index 32f842a..0000000 --- a/forge/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -loom.platform=forge \ No newline at end of file diff --git a/forge/src/main/java/eu/midnightdust/forge/MidnightLibClientEvents.java b/forge/src/main/java/eu/midnightdust/forge/MidnightLibClientEvents.java deleted file mode 100644 index c21b2c7..0000000 --- a/forge/src/main/java/eu/midnightdust/forge/MidnightLibClientEvents.java +++ /dev/null @@ -1,30 +0,0 @@ -package eu.midnightdust.forge; - -import eu.midnightdust.lib.config.MidnightConfig; -import eu.midnightdust.lib.util.MidnightColorUtil; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.ConfigScreenHandler; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; - -@Mod.EventBusSubscriber(modid = "midnightlib", bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) -public class MidnightLibClientEvents { - public static void registerClientTick(TickEvent.ClientTickEvent event) { - MidnightColorUtil.tick(); - } - - @SubscribeEvent - public static void onPostInit(FMLClientSetupEvent event) { - MinecraftForge.EVENT_BUS.addListener(MidnightLibClientEvents::registerClientTick); - ModList.get().forEachModContainer((modid, modContainer) -> { - if (MidnightConfig.configClass.containsKey(modid)) { - modContainer.registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, () -> - new ConfigScreenHandler.ConfigScreenFactory((client, parent) -> MidnightConfig.getScreen(parent, modid))); - } - }); - } -} diff --git a/forge/src/main/java/eu/midnightdust/forge/MidnightLibForge.java b/forge/src/main/java/eu/midnightdust/forge/MidnightLibForge.java deleted file mode 100644 index 122d6ec..0000000 --- a/forge/src/main/java/eu/midnightdust/forge/MidnightLibForge.java +++ /dev/null @@ -1,19 +0,0 @@ -package eu.midnightdust.forge; - -import eu.midnightdust.core.MidnightLibClient; -import eu.midnightdust.core.MidnightLibServer; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.IExtensionPoint; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.network.NetworkConstants; - -@Mod("midnightlib") -public class MidnightLibForge { - public MidnightLibForge() { - ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (remote, server) -> true)); - DistExecutor.safeRunWhenOn(Dist.CLIENT, () -> MidnightLibClient::onInitializeClient); - DistExecutor.safeRunWhenOn(Dist.DEDICATED_SERVER, () -> MidnightLibServer::onInitializeServer); - } -} diff --git a/forge/src/main/java/eu/midnightdust/forge/MidnightLibServerEvents.java b/forge/src/main/java/eu/midnightdust/forge/MidnightLibServerEvents.java deleted file mode 100644 index faa63b0..0000000 --- a/forge/src/main/java/eu/midnightdust/forge/MidnightLibServerEvents.java +++ /dev/null @@ -1,19 +0,0 @@ -package eu.midnightdust.forge; - -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import eu.midnightdust.lib.config.AutoCommand; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.event.RegisterCommandsEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -@Mod.EventBusSubscriber(modid = "midnightlib", value = Dist.DEDICATED_SERVER) -public class MidnightLibServerEvents { - @SubscribeEvent - public static void registerCommands(RegisterCommandsEvent event) { - for (LiteralArgumentBuilder command : AutoCommand.commands){ - event.getDispatcher().register(command); - } - } -} diff --git a/forge/src/main/java/eu/midnightdust/lib/util/forge/PlatformFunctionsImpl.java b/forge/src/main/java/eu/midnightdust/lib/util/forge/PlatformFunctionsImpl.java deleted file mode 100644 index 84471c8..0000000 --- a/forge/src/main/java/eu/midnightdust/lib/util/forge/PlatformFunctionsImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -package eu.midnightdust.lib.util.forge; - -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import eu.midnightdust.lib.util.PlatformFunctions; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.loading.FMLEnvironment; -import net.minecraftforge.fml.loading.FMLPaths; - -import java.nio.file.Path; - -public class PlatformFunctionsImpl { - /** - * This is our actual method to {@link PlatformFunctions#getConfigDirectory()}. - */ - public static Path getConfigDirectory() { - return FMLPaths.CONFIGDIR.get(); - } - public static boolean isClientEnv() { - return FMLEnvironment.dist.isClient(); - } - public static boolean isModLoaded(String modid) { - return ModList.get().isLoaded(modid); - } - public static void registerCommand(LiteralArgumentBuilder command) { - // Ignored here, see MidnightLibServerEvents#registerCommands - } -} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml deleted file mode 100644 index 2cde8d9..0000000 --- a/forge/src/main/resources/META-INF/mods.toml +++ /dev/null @@ -1,29 +0,0 @@ -modLoader = "javafml" -loaderVersion = "[43,)" -#issueTrackerURL = "" -license = "MIT License" - -[[mods]] -modId = "midnightlib" -version = "${version}" -displayName = "MidnightLib" -logoFile = "midnightlib.png" -authors = "TeamMidnightDust, Motschen" -description = ''' -Common Library for Team MidnightDust's mods. -''' -#logoFile = "" - -[[dependencies.midnightlib]] -modId = "forge" -mandatory = true -versionRange = "[43,)" -ordering = "NONE" -side = "BOTH" - -[[dependencies.midnightlib]] -modId = "minecraft" -mandatory = true -versionRange = "[1.19.4,)" -ordering = "NONE" -side = "BOTH" \ No newline at end of file diff --git a/forge/src/main/resources/midnightlib.png b/forge/src/main/resources/midnightlib.png deleted file mode 100644 index d31b4c4..0000000 Binary files a/forge/src/main/resources/midnightlib.png and /dev/null differ diff --git a/forge/src/main/resources/pack.mcmeta b/forge/src/main/resources/pack.mcmeta deleted file mode 100644 index 0a839a6..0000000 --- a/forge/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "description": "MidnightLib", - "pack_format": 9 - } -} diff --git a/gradle.properties b/gradle.properties index 3afe1a9..35fa262 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,18 @@ org.gradle.jvmargs=-Xmx4096M -minecraft_version=1.20 -yarn_mappings=1.20+build.1 -enabled_platforms=quilt,fabric,forge +minecraft_version=1.20.1 +supported_versions=1.20 +yarn_mappings=1.20.1+build.10 +enabled_platforms=fabric archives_base_name=midnightlib -mod_version=1.4.1 +mod_version=1.7.5 maven_group=eu.midnightdust +# release_type=release +# curseforge_id=488090 +# modrinth_id=codAaoxh -fabric_loader_version=0.14.21 -fabric_api_version=0.83.0+1.20 +fabric_loader_version=0.16.14 +fabric_api_version=0.92.6+1.20.1 -forge_version=1.20-46.0.2 - -quilt_loader_version=0.19.0-beta.18 -quilt_fabric_api_version=7.0.1+0.83.0-1.20 -mod_menu_version = 7.0.1 \ No newline at end of file +mod_menu_version=7.2.2 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..2733ed5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/quilt/build.gradle b/quilt/build.gradle index ff7a780..ddcf2e3 100644 --- a/quilt/build.gradle +++ b/quilt/build.gradle @@ -1,3 +1,4 @@ +/* plugins { id "com.github.johnrengelman.shadow" version "7.1.2" } @@ -86,3 +87,4 @@ publishing { // Add repositories to publish to here. } } +*/ diff --git a/quilt/src/main/java/eu/midnightdust/quilt/core/MidnightLibClientQuilt.java b/quilt/src/main/java/eu/midnightdust/quilt/core/MidnightLibClientQuilt.java index fe7491a..6899896 100644 --- a/quilt/src/main/java/eu/midnightdust/quilt/core/MidnightLibClientQuilt.java +++ b/quilt/src/main/java/eu/midnightdust/quilt/core/MidnightLibClientQuilt.java @@ -10,8 +10,5 @@ public class MidnightLibClientQuilt implements ClientModInitializer { @Override public void onInitializeClient(ModContainer mod) { MidnightLibClient.onInitializeClient(); - ClientTickEvents.END.register( - client -> MidnightColorUtil.tick() - ); } } diff --git a/settings.gradle b/settings.gradle index c86c020..1923757 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,15 +2,16 @@ pluginManagement { repositories { maven { url "https://maven.fabricmc.net/" } maven { url "https://maven.architectury.dev/" } - maven { url "https://maven.minecraftforge.net/" } + maven { url "https://maven.neoforged.net/releases" } gradlePluginPortal() } } include("common") -include("fabric-like") include("fabric") -include("quilt") -include("forge") +include("test-fabric") +// include("neoforge") +// include("test-neoforge") +//include("quilt") rootProject.name = "midnightlib" diff --git a/test-fabric/build.gradle b/test-fabric/build.gradle new file mode 100644 index 0000000..1d35a52 --- /dev/null +++ b/test-fabric/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'com.github.johnrengelman.shadow' + id "me.shedaniel.unified-publishing" +} +repositories { + maven { url "https://maven.terraformersmc.com/releases" } +} + +architectury { + platformSetupLoomIde() + fabric() +} + +loom { +} + +configurations { + common + shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files. + compileClasspath.extendsFrom common + runtimeClasspath.extendsFrom common + developmentFabric.extendsFrom common + archivesBaseName = rootProject.archives_base_name + "-fabric" +} + +dependencies { + modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" + + implementation project(path: ":fabric", configuration: "namedElements") + common(project(path: ":common", configuration: "namedElements")) { transitive false } +} \ No newline at end of file diff --git a/test-fabric/src/main/java/eu/midnightdust/fabric/example/MLExampleFabric.java b/test-fabric/src/main/java/eu/midnightdust/fabric/example/MLExampleFabric.java new file mode 100644 index 0000000..e873330 --- /dev/null +++ b/test-fabric/src/main/java/eu/midnightdust/fabric/example/MLExampleFabric.java @@ -0,0 +1,11 @@ +package eu.midnightdust.fabric.example; + +import eu.midnightdust.fabric.example.config.MidnightConfigExample; +import net.fabricmc.api.ModInitializer; + +public class MLExampleFabric implements ModInitializer { + @Override + public void onInitialize() { + MidnightConfigExample.init("modid", MidnightConfigExample.class); + } +} diff --git a/test-fabric/src/main/java/eu/midnightdust/fabric/example/MidnightLibExtras.java b/test-fabric/src/main/java/eu/midnightdust/fabric/example/MidnightLibExtras.java new file mode 100644 index 0000000..89b9dcd --- /dev/null +++ b/test-fabric/src/main/java/eu/midnightdust/fabric/example/MidnightLibExtras.java @@ -0,0 +1,102 @@ +package eu.midnightdust.fabric.example; + +import com.google.common.collect.Lists; +import eu.midnightdust.lib.config.MidnightConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.gui.widget.IconButtonWidget; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +/* + Pre-made additional (niche) functionality that is not included in MidnightLib to keep the file size small. + Feel free to copy the parts you need :) +*/ +public class MidnightLibExtras { + public static class KeybindButton extends ButtonWidget { + public static ButtonWidget focusedButton; + + public static void add(KeyBinding binding, MidnightConfig.MidnightConfigListWidget list, MidnightConfig.MidnightConfigScreen screen) { + KeybindButton editButton = new KeybindButton(screen.width - 185, 0, 150, 20, binding); + IconButtonWidget resetButton = IconButtonWidget.builder( + Text.translatable("controls.reset"), + Identifier.of("midnightlib", "icon/reset"), + (button -> { + binding.setBoundKey(binding.getDefaultKey()); + screen.updateList(); + })).textureSize(12, 12).iconSize(20, 20).build(); + resetButton.setPosition(screen.width - 205 + 150 + 25, 0); + editButton.resetButton = resetButton; + editButton.updateMessage(false); + MidnightConfig.EntryInfo info = new MidnightConfig.EntryInfo(null, screen.modid); + + list.addButton(Lists.newArrayList(editButton, resetButton), Text.translatable(binding.getTranslationKey()), info); + } + + private final KeyBinding binding; + private @Nullable ClickableWidget resetButton; + + public KeybindButton(int x, int y, int width, int height, KeyBinding binding) { + super(x, y, width, height, binding.getBoundKeyLocalizedText(), (button) -> { + ((KeybindButton) button).updateMessage(true); + focusedButton = button; + }, (textSupplier) -> binding.isUnbound() ? Text.translatable("narrator.controls.unbound", binding.getTranslationKey()) : Text.translatable("narrator.controls.bound", binding.getTranslationKey(), textSupplier.get())); + this.binding = binding; + updateMessage(false); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (focusedButton == this) { + if (keyCode == GLFW.GLFW_KEY_ESCAPE) { + this.binding.setBoundKey(InputUtil.UNKNOWN_KEY); + } else { + this.binding.setBoundKey(InputUtil.fromKeyCode(keyCode, scanCode)); + } + updateMessage(false); + + focusedButton = null; + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + public void updateMessage(boolean focused) { + boolean hasConflicts = false; + MutableText conflictingBindings = Text.empty(); + if (focused) + this.setMessage(Text.literal("> ").append(this.binding.getBoundKeyLocalizedText().copy().formatted(Formatting.WHITE, Formatting.UNDERLINE)).append(" <").formatted(Formatting.YELLOW)); + else { + this.setMessage(this.binding.getBoundKeyLocalizedText()); + + if (!this.binding.isUnbound()) { + for (KeyBinding keyBinding : MinecraftClient.getInstance().options.allKeys) { + if (keyBinding != this.binding && this.binding.equals(keyBinding)) { + if (hasConflicts) conflictingBindings.append(", "); + + hasConflicts = true; + conflictingBindings.append(Text.translatable(keyBinding.getTranslationKey())); + } + } + } + } + + if (this.resetButton != null) this.resetButton.active = !this.binding.isDefault(); + + if (hasConflicts) { + this.setMessage(Text.literal("[ ").append(this.getMessage().copy().formatted(Formatting.WHITE)).append(" ]").formatted(Formatting.RED)); + this.setTooltip(Tooltip.of(Text.translatable("controls.keybinds.duplicateKeybinds", conflictingBindings))); + } else { + this.setTooltip(null); + } + } + } +} diff --git a/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java b/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java new file mode 100644 index 0000000..68e80bd --- /dev/null +++ b/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java @@ -0,0 +1,170 @@ +package eu.midnightdust.fabric.example.config; + +import com.google.common.collect.Lists; +import eu.midnightdust.fabric.example.MidnightLibExtras; +import eu.midnightdust.lib.config.MidnightConfig; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Identifier; +import net.minecraft.util.TranslatableOption; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** Every option in a MidnightConfig class has to be public and static, so we can access it from other classes. + * The config class also has to extend MidnightConfig*/ +@SuppressWarnings({"unused", "DefaultAnnotationParam"}) +public class MidnightConfigExample extends MidnightConfig { + public static final String TEXT = "text"; + public static final String NUMBERS = "numbers"; + public static final String SLIDERS = "sliders"; + public static final String LISTS = "lists"; + public static final String FILES = "files"; + public static final String CONDITIONS = "conditions"; + public static final String EXTRAS = "extras"; + + @Comment(category = TEXT) public static Comment text1; // Comments are rendered like an option without a button and are excluded from the config file + @Comment(category = TEXT, centered = true) public static Comment text2; // Centered comments are the same as normal ones - just centered! + @Comment(category = TEXT) public static Comment spacer1; // Comments containing the word "spacer" will just appear as a blank line + @Entry(category = TEXT) public static boolean showInfo = true; // Example for a boolean option + @Entry(category = TEXT, name="I am a (non-primitive) Boolean") public static Boolean nonPrimitive = true; // Example for a non-primative boolean option + @Entry(category = TEXT) public static String name = "Hello World!"; // Example for a string option, which is in a category! + @Entry(category = TEXT, width = 7, min = 7, isColor = true, name = "I am a color!") public static String titleColor = "#ffffff"; // The isColor property adds a color chooser for a hexadecimal color + @Entry(category = TEXT, idMode = 0) public static Identifier id = new Identifier("diamond"); // Example for an identifier with matching items displayed next to it! + @Entry(category = TEXT) public static ModPlatform modPlatform = ModPlatform.FABRIC; // Example for an enum option + public enum ModPlatform { // Enums allow the user to cycle through predefined options + QUILT, FABRIC, FORGE, NEOFORGE, VANILLA + } + @Entry(category = TEXT) public static GraphicsSteps graphicsSteps = GraphicsSteps.FABULOUS; // Example for an enum option with TranslatableOption + + @Comment(category = TEXT, name = "§nMidnightLib Wiki", centered = true, url = "https://www.midnightdust.eu/wiki/midnightlib/") public static Comment wiki; // Example for a comment with a url + + @Entry(category = NUMBERS) public static int fabric = 16777215; // Example for an int option + @Entry(category = NUMBERS) public static double world = 1.4D; // Example for a double option + @Entry(category = NUMBERS, min=69,max=420) public static int hello = 420; // - The entered number has to be larger than 69 and smaller than 420 + @Entry(category = SLIDERS, name = "I am an int slider.",isSlider = true, min = 0, max = 100) public static int intSlider = 35; // Int fields can also be displayed as a Slider + @Entry(category = SLIDERS, name = "I am a float slider!", isSlider = true, min = 0f, max = 1f, precision = 1000) public static float floatSlider = 0.24f; // And so can floats! Precision defines the amount of decimal places + @Entry(category = SLIDERS, name = "I am a non-primitive double slider!", isSlider = true, min = 0d, max = 4d, precision = 10000) public static Double nonPrimitiveDoubleSlider = 3.76d; // Even works for non-primitive fields + // The name field can be used to specify a custom translation string or plain text + @Entry(category = LISTS, name = "I am a string list!") public static List stringList = Lists.newArrayList("String1", "String2"); // Array String Lists are also supported + @Entry(category = LISTS, isColor = true, name = "I am a color list!") public static List colorList = Lists.newArrayList("#ac5f99", "#11aa44"); // Lists also support colors + @Entry(category = LISTS, name = "I am an identifier list!", idMode = 1) public static List idList = Lists.newArrayList(new Identifier("dirt")); // A list of block identifiers + @Entry(category = LISTS, name = "I am an integer list!") public static List intList = Lists.newArrayList(69, 420); + @Entry(category = LISTS, name = "I am a float list!") public static List floatList = Lists.newArrayList(4.1f, -1.3f, -1f); + + @Entry(category = FILES, + selectionMode = JFileChooser.FILES_ONLY, + fileExtensions = {"json", "txt", "log"}, // Define valid file extensions + fileChooserType = JFileChooser.SAVE_DIALOG, + name = "I am a file!") + public static String myFile = ""; // The isFile property adds a file picker button + + @Entry(category = FILES, + selectionMode = JFileChooser.DIRECTORIES_ONLY, + fileChooserType = JFileChooser.OPEN_DIALOG, + name = "I am a directory!") + public static String myDirectory = ""; // The isDirectory property adds a directory picker button + + @Entry(category = FILES, + selectionMode = JFileChooser.FILES_AND_DIRECTORIES, + fileExtensions = {"png", "jpg", "jpeg"}, + fileChooserType = JFileChooser.OPEN_DIALOG, + name = "I can choose both files & directories!") + public static String myFileOrDirectory = ""; // The isFileOrDirectory property adds a file or directory picker button + @Entry(category = FILES, + selectionMode = JFileChooser.FILES_AND_DIRECTORIES, + fileExtensions = {"png", "jpg", "jpeg"}, + fileChooserType = JFileChooser.OPEN_DIALOG, + name = "I am a mf file/directory list!") + public static List fileOrDirectoryList = new ArrayList<>(); // Yes, that's right – you can even have lists of files/directories + + @Condition(requiredModId = "midnightlib") // Conditional options are here! + @Entry(category = CONDITIONS, name="Turn me on!") + public static boolean turnMeOn = false; + @Condition(requiredOption = "modid:turnMeOn", visibleButLocked = true) + @Entry(category = CONDITIONS, name="Turn me off (locked)!") + public static Boolean turnMeOff = true; + @Condition(requiredOption = "turnMeOn") // You can also use multiple conditions for the same entry + @Condition(requiredOption = "modid:turnMeOff", requiredValue = "false") + @Entry(category = CONDITIONS, name="Which is the best modloader?") + public static String bestModloader = ""; + @Condition(requiredOption = "turnMeOn") + @Condition(requiredOption = "turnMeOff", requiredValue = "false") + @Condition(requiredOption = "bestModloader", requiredValue = "Forge") + @Comment(category = CONDITIONS, name="❌ You have bad taste :(", centered = true) // Don't take this too seriously btw :) + public static Comment answerForge; // Comments can also be conditional! + @Condition(requiredOption = "turnMeOn") + @Condition(requiredOption = "turnMeOff", requiredValue = "false") + @Condition(requiredOption = "bestModloader", requiredValue = "NeoForge") + @Comment(category = CONDITIONS, name="⛏ Not quite, but it's alright!", centered = true) + public static Comment answerNeoforge; + @Condition(requiredOption = "turnMeOn") + @Condition(requiredOption = "turnMeOff", requiredValue = "false") + @Condition(requiredOption = "bestModloader", requiredValue = "Fabric") + @Comment(category = CONDITIONS, name="⭐ Correct! Fabric (and Quilt) are the best!", centered = true) + public static Comment answerFabric; + @Condition(requiredOption = "turnMeOn") + @Condition(requiredOption = "turnMeOff", requiredValue = "false") + @Condition(requiredOption = "bestModloader", requiredValue = "Quilt") + @Comment(category = CONDITIONS, name="⭐ Correct! Quilt (and Fabric) are the best!", centered = true) + public static Comment answerQuilt; + + @Entry(category = CONDITIONS, name="Enter any prime number below 10") + public static int primeNumber = 0; + @Comment(category = CONDITIONS, name="Correct!") + @Condition(requiredOption = "primeNumber", requiredValue = {"2", "3", "5", "7"}) + public static Comment answerPrime; + + @Condition(requiredOption = "midnightlib:config_screen_list", requiredValue = "FALSE") // Access options of other mods that are also using MidnightLib + @Comment(category = CONDITIONS) public static Comment spaceracer; + @Condition(requiredOption = "midnightlib:config_screen_list", requiredValue = "FALSE") + @Comment(category = CONDITIONS, name="You disabled MidnightLib's config screen list. Why? :(", centered = true) public static Comment why; + + public static int imposter = 16777215; // - Entries without an @Entry or @Comment annotation are ignored + + public enum GraphicsSteps implements TranslatableOption { + FAST(0, "options.graphics.fast"), + FANCY(1, "options.graphics.fancy"), + FABULOUS(2, "options.graphics.fabulous"); + + private final int id; + private final String translationKey; + + GraphicsSteps(int id, String translationKey) { + this.id = id; + this.translationKey = translationKey; + } + + @Override + public Text getText() { + MutableText mutableText = Text.translatable(this.getTranslationKey()); + return this == GraphicsSteps.FABULOUS ? mutableText.formatted(Formatting.ITALIC).formatted(Formatting.AQUA) : mutableText; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public String getTranslationKey() { + return this.translationKey; + } + } + + @Condition(requiredModId = "thismoddoesnotexist") + @Comment(category = EXTRAS) public static Comment iAmJustADummy; // We only have this to initialize an empty tab for the keybinds below + + @Override + public void onTabInit(String tabName, MidnightConfigListWidget list, MidnightConfigScreen screen) { + if (Objects.equals(tabName, EXTRAS)) { + MidnightLibExtras.KeybindButton.add(MinecraftClient.getInstance().options.advancementsKey, list, screen); + MidnightLibExtras.KeybindButton.add(MinecraftClient.getInstance().options.dropKey, list, screen); + } + } + +} \ No newline at end of file diff --git a/test-fabric/src/main/resources/assets/modid/lang/en_us.json b/test-fabric/src/main/resources/assets/modid/lang/en_us.json new file mode 100644 index 0000000..a006c75 --- /dev/null +++ b/test-fabric/src/main/resources/assets/modid/lang/en_us.json @@ -0,0 +1,30 @@ +{ + "modid.midnightconfig.title":"I am a title", + "modid.midnightconfig.text1":"I am a comment *u*", + "modid.midnightconfig.text2":"I am a centered comment (╯°□°)╯︵ ┻━┻", + "modid.midnightconfig.name":"I am a string!", + "modid.midnightconfig.name.label.tooltip":"I am a label tooltip \nWohoo!", + "modid.midnightconfig.name.tooltip":"I am a tooltip uwu \nI am a new line", + "modid.midnightconfig.fabric":"I am an int", + "modid.midnightconfig.world":"I am a double", + "modid.midnightconfig.showInfo":"I am a boolean", + "modid.midnightconfig.hello":"I am a limited int!", + "modid.midnightconfig.id":"I am an Item Identifier!", + "modid.midnightconfig.modPlatform":"I am an enum!", + "modid.midnightconfig.enum.ModPlatform.FORGE":"Forge", + "modid.midnightconfig.enum.ModPlatform.FABRIC":"Fabric", + "modid.midnightconfig.enum.ModPlatform.QUILT":"Quilt", + "modid.midnightconfig.enum.ModPlatform.NEOFORGE":"NeoForge", + "modid.midnightconfig.enum.ModPlatform.VANILLA":"Vanilla", + "modid.midnightconfig.graphicsSteps":"I am an enum with TranslatableOption!", + "modid.midnightconfig.myFileOrDirectory.fileChooser": "Select an image or directory", + "modid.midnightconfig.myFileOrDirectory.fileFilter": "Supported Images (.png, .jpg, .jpeg)", + "modid.midnightconfig.category.numbers": "Numbers", + "modid.midnightconfig.category.text": "Text", + "modid.midnightconfig.category.sliders": "Sliders", + "modid.midnightconfig.category.lists": "Lists", + "modid.midnightconfig.category.files": "Files", + "modid.midnightconfig.category.conditions": "Quiz", + "modid.midnightconfig.category.extras": "Extras", + "modid.midnightconfig.category.multiConditions": "Multi-Conditions" +} \ No newline at end of file diff --git a/test-fabric/src/main/resources/assets/modid/lang/es_ar.json b/test-fabric/src/main/resources/assets/modid/lang/es_ar.json new file mode 100644 index 0000000..d87e7a8 --- /dev/null +++ b/test-fabric/src/main/resources/assets/modid/lang/es_ar.json @@ -0,0 +1,28 @@ +{ + "modid.midnightconfig.title": "Soy un título", + "modid.midnightconfig.text1": "Soy un comentario *u*", + "modid.midnightconfig.text2": "Soy un comentario centrado (╯°□°)╯︵ ┻━┻", + "modid.midnightconfig.name": "¡Soy una cadena de texto!", + "modid.midnightconfig.name.label.tooltip": "Soy el tooltip de una etiqueta \n¡Wujuu!", + "modid.midnightconfig.name.tooltip": "Soy un tooltip uwu \nY una nueva línea", + "modid.midnightconfig.fabric": "Soy un entero", + "modid.midnightconfig.world": "Soy un número decimal", + "modid.midnightconfig.showInfo": "Soy un booleano", + "modid.midnightconfig.hello": "¡Soy un entero limitado!", + "modid.midnightconfig.id": "¡Soy un identificador de ítem!", + "modid.midnightconfig.modPlatform": "¡Soy un enumerador!", + "modid.midnightconfig.enum.ModPlatform.FORGE": "Forge", + "modid.midnightconfig.enum.ModPlatform.FABRIC": "Fabric", + "modid.midnightconfig.enum.ModPlatform.QUILT": "Quilt", + "modid.midnightconfig.enum.ModPlatform.NEOFORGE": "NeoForge", + "modid.midnightconfig.enum.ModPlatform.VANILLA": "Vanilla", + "modid.midnightconfig.myFileOrDirectory.fileChooser": "Seleccioná una imagen o carpeta", + "modid.midnightconfig.myFileOrDirectory.fileFilter": "Imágenes compatibles (.png, .jpg, .jpeg)", + "modid.midnightconfig.category.numbers": "Números", + "modid.midnightconfig.category.text": "Texto", + "modid.midnightconfig.category.sliders": "Deslizadores", + "modid.midnightconfig.category.lists": "Listas", + "modid.midnightconfig.category.files": "Archivos", + "modid.midnightconfig.category.conditions": "Cuestionario", + "modid.midnightconfig.category.multiConditions": "Condiciones múltiples" +} diff --git a/test-fabric/src/main/resources/fabric.mod.json b/test-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..f82ee29 --- /dev/null +++ b/test-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,23 @@ +{ + "schemaVersion": 1, + "id": "midnightlib-example", + "version": "${version}", + + "name": "MidnightLib Example", + "description": "Wow, you can do so much.", + "authors": [ "MidnightDust" ], + + "license": "CC0", + "icon": "assets/midnightlib/icon.png", + + "environment": "*", + "entrypoints": { + "main": [ + "eu.midnightdust.fabric.example.MLExampleFabric" + ] + }, + "depends": { + "fabric-resource-loader-v0": "*", + "midnightlib": ">=1.6.0" + } +}