mirror of
https://github.com/TeamMidnightDust/MidnightLib.git
synced 2025-12-17 09:45:10 +01:00
dev: switch to stonecutter build system
- This will allow us to build the library for different Minecraft versions at the same time - Right now, only Fabric and Neoforge 1.21.10 are fully working - As a bonus, the jar is now even smaller!
This commit is contained in:
125
src/main/java/eu/midnightdust/core/MidnightLib.java
Normal file
125
src/main/java/eu/midnightdust/core/MidnightLib.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package eu.midnightdust.core;
|
||||
|
||||
import eu.midnightdust.core.config.MidnightLibConfig;
|
||||
import eu.midnightdust.lib.config.AutoCommand;
|
||||
import eu.midnightdust.lib.config.MidnightConfig;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.UIManager;
|
||||
import net.minecraft.Util;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//? if fabric {
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class MidnightLib implements DedicatedServerModInitializer, ClientModInitializer, ModMenuApi {
|
||||
//?} else if neoforge {
|
||||
/*import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import eu.midnightdust.lib.util.PlatformFunctions;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
|
||||
@Mod("midnightlib")
|
||||
public class MidnightLib {
|
||||
*///?}
|
||||
public static List<String> hiddenMods = new ArrayList<>();
|
||||
public static final String MOD_ID = "midnightlib";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
public void onInitializeClient() {
|
||||
try {
|
||||
if (Util.getPlatform() != Util.OS.OSX) {
|
||||
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.configInstances.forEach((modid, config) -> {
|
||||
for (Field field : config.configClass.getFields()) {
|
||||
if (field.isAnnotationPresent(MidnightConfig.Entry.class) && !field.isAnnotationPresent(MidnightConfig.Client.class) && !field.isAnnotationPresent(MidnightConfig.Hidden.class))
|
||||
new AutoCommand(field, modid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//? if fabric {
|
||||
public void onInitializeServer() {
|
||||
registerAutoCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||
return parent -> MidnightLibConfig.getScreen(parent,"midnightlib");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
|
||||
HashMap<String, ConfigScreenFactory<?>> map = new HashMap<>();
|
||||
MidnightConfig.configInstances.forEach((modid, cClass) -> {
|
||||
if (!MidnightLib.hiddenMods.contains(modid))
|
||||
map.put(modid, parent -> MidnightConfig.getScreen(parent, modid));
|
||||
});
|
||||
return map;
|
||||
}
|
||||
//?}
|
||||
|
||||
|
||||
|
||||
/*? if neoforge {*/
|
||||
|
||||
/*public static List<LiteralArgumentBuilder<CommandSourceStack>> commands = new ArrayList<>();
|
||||
|
||||
public MidnightLib() {
|
||||
if (PlatformFunctions.isClientEnv()) this.onInitializeClient();
|
||||
}
|
||||
|
||||
@EventBusSubscriber(modid = "midnightlib", value = Dist.CLIENT)
|
||||
public static class MidnightLibBusEvents {
|
||||
@SubscribeEvent
|
||||
public static void onPostInit(FMLClientSetupEvent event) {
|
||||
ModList.get().forEachModContainer((modid, modContainer) -> {
|
||||
if (MidnightConfig.configInstances.containsKey(modid) && !MidnightLib.hiddenMods.contains(modid)) {
|
||||
modContainer.registerExtensionPoint(IConfigScreenFactory.class, (minecraftClient, screen) -> MidnightConfig.getScreen(screen, modid));
|
||||
}
|
||||
});
|
||||
MidnightLib.registerAutoCommand();
|
||||
}
|
||||
}
|
||||
|
||||
@EventBusSubscriber(modid = "midnightlib")
|
||||
public static class MidnightLibEvents {
|
||||
@SubscribeEvent
|
||||
public static void registerCommands(RegisterCommandsEvent event) {
|
||||
try {
|
||||
commands.forEach(command -> event.getDispatcher().register(command));
|
||||
}
|
||||
catch (ConcurrentModificationException ignored) {}
|
||||
}
|
||||
}
|
||||
*///?}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
public static boolean shouldShowButton() {
|
||||
return config_screen_list.equals(ConfigButton.TRUE) || (config_screen_list.equals(ConfigButton.MODMENU) && !HAS_MODMENU);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package eu.midnightdust.core.mixin;
|
||||
|
||||
import eu.midnightdust.core.screen.MidnightConfigOverviewScreen;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
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;
|
||||
|
||||
import java.util.Objects;
|
||||
import net.minecraft.client.gui.components.SpriteIconButton;
|
||||
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.options.OptionsScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import static eu.midnightdust.core.MidnightLib.MOD_ID;
|
||||
import static eu.midnightdust.core.config.MidnightLibConfig.shouldShowButton;
|
||||
|
||||
@Mixin(OptionsScreen.class)
|
||||
public abstract class MixinOptionsScreen extends Screen {
|
||||
@Shadow @Final private HeaderAndFooterLayout layout;
|
||||
@Unique SpriteIconButton midnightlib$button = SpriteIconButton.builder(Component.translatable("midnightlib.overview.title"), (
|
||||
buttonWidget) -> Objects.requireNonNull(minecraft).setScreen(new MidnightConfigOverviewScreen(this)), true)
|
||||
.sprite(ResourceLocation.fromNamespaceAndPath(MOD_ID,"icon/"+MOD_ID), 16, 16).size(20, 20).build();
|
||||
|
||||
private MixinOptionsScreen(Component title) {super(title);}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "init")
|
||||
public void midnightlib$onInit(CallbackInfo ci) {
|
||||
if (shouldShowButton()) {
|
||||
this.midnightlib$setButtonPos();
|
||||
this.addRenderableWidget(midnightlib$button);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("TAIL"), method = "repositionElements")
|
||||
public void midnightlib$onResize(CallbackInfo ci) {
|
||||
if (shouldShowButton()) this.midnightlib$setButtonPos();
|
||||
}
|
||||
|
||||
@Unique
|
||||
public void midnightlib$setButtonPos() {
|
||||
midnightlib$button.setPosition(layout.getWidth() / 2 + 158, layout.getY() + layout.getFooterHeight() - 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package eu.midnightdust.core.screen;
|
||||
|
||||
import eu.midnightdust.core.MidnightLib;
|
||||
import eu.midnightdust.lib.config.MidnightConfig;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import eu.midnightdust.lib.config.MidnightConfigListWidget;
|
||||
|
||||
public class MidnightConfigOverviewScreen extends Screen {
|
||||
|
||||
public MidnightConfigOverviewScreen(Screen parent) {
|
||||
super(Component.translatable( "midnightlib.overview.title"));
|
||||
this.parent = parent;
|
||||
}
|
||||
private final Screen parent;
|
||||
private MidnightConfigListWidget list;
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
this.addRenderableWidget(Button.builder(CommonComponents.GUI_DONE, (button) -> Objects.requireNonNull(minecraft).setScreen(parent)).bounds(this.width / 2 - 100, this.height - 26, 200, 20).build());
|
||||
|
||||
this.addWidget(this.list = new MidnightConfigListWidget(this.minecraft, this.width, this.height - 57, 24, 25));
|
||||
List<String> sortedMods = new ArrayList<>(MidnightConfig.configInstances.keySet());
|
||||
Collections.sort(sortedMods);
|
||||
sortedMods.forEach((modid) -> {
|
||||
if (!MidnightLib.hiddenMods.contains(modid)) {
|
||||
list.addButton(List.of(Button.builder(Component.translatable(modid +".midnightconfig.title"), (button) ->
|
||||
Objects.requireNonNull(minecraft).setScreen(MidnightConfig.getScreen(this, modid))).bounds(this.width / 2 - 125, this.height - 28, 250, 20).build()), null, null);
|
||||
}});
|
||||
super.init();
|
||||
}
|
||||
@Override
|
||||
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
this.list.render(context, mouseX, mouseY, delta);
|
||||
context.drawCenteredString(font, title, width / 2, 10, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
84
src/main/java/eu/midnightdust/lib/config/AutoCommand.java
Normal file
84
src/main/java/eu/midnightdust/lib/config/AutoCommand.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.mojang.brigadier.arguments.*;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import eu.midnightdust.lib.config.MidnightConfig.Entry;
|
||||
import eu.midnightdust.lib.util.PlatformFunctions;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class AutoCommand {
|
||||
final static String VALUE = "value";
|
||||
final Field field;
|
||||
final Class<?> type;
|
||||
final String modid;
|
||||
final boolean isList;
|
||||
|
||||
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 = Commands.literal(field.getName()).executes(this::getValue);
|
||||
|
||||
if (type.isEnum()) {
|
||||
for (Object enumValue : field.getType().getEnumConstants())
|
||||
command = command.then(Commands.literal(enumValue.toString())
|
||||
.executes(ctx -> this.setValue(ctx.getSource(), enumValue, "")));
|
||||
} else if (isList) {
|
||||
for (String action : new String[]{"add", "remove"})
|
||||
command = command.then(Commands.literal(action)
|
||||
.then(Commands.argument(VALUE, getArgType()).executes(ctx -> setValueFromArg(ctx, action))));
|
||||
} else command = command.then(Commands.argument(VALUE, getArgType()).executes(ctx -> setValueFromArg(ctx, "")));
|
||||
|
||||
PlatformFunctions.registerCommand(Commands.literal("midnightconfig").requires(source -> source.hasPermission(2)).then(Commands.literal(modid).then(command)));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public int setValueFromArg(CommandContext<CommandSourceStack> 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(CommandSourceStack source, Object value, String action) {
|
||||
boolean add = Objects.equals(action, "add");
|
||||
try {
|
||||
if (!isList) field.set(null, value);
|
||||
else {
|
||||
@SuppressWarnings("unchecked") var list = (List<Object>) 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.sendFailure(Component.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.sendSuccess(() -> Component.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(CommandContext<CommandSourceStack> context) {
|
||||
context.getSource().sendSuccess(() -> {
|
||||
try { return Component.literal("The value of %s is %s".formatted(field.getName(), field.get(null)));
|
||||
} catch (IllegalAccessException e) {throw new RuntimeException(e);}
|
||||
}, true);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
77
src/main/java/eu/midnightdust/lib/config/ButtonEntry.java
Normal file
77
src/main/java/eu/midnightdust/lib/config/ButtonEntry.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.List;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.ContainerObjectSelectionList;
|
||||
import net.minecraft.client.gui.components.MultiLineTextWidget;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.narration.NarratableEntry;
|
||||
import net.minecraft.client.gui.screens.ConfirmLinkScreen;
|
||||
import net.minecraft.client.input.MouseButtonEvent;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class ButtonEntry extends ContainerObjectSelectionList.Entry<ButtonEntry> {
|
||||
private static final Font textRenderer = Minecraft.getInstance().font;
|
||||
public final Component text;
|
||||
public final List<AbstractWidget> buttons;
|
||||
public final EntryInfo info;
|
||||
public boolean centered = false;
|
||||
public MultiLineTextWidget title;
|
||||
|
||||
public ButtonEntry(List<AbstractWidget> buttons, Component 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 = Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
|
||||
if (text != null && (!text.getString().contains("spacer") || !buttons.isEmpty())) {
|
||||
title = new MultiLineTextWidget(12, 0, Component.translationArg(text), textRenderer).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);
|
||||
if (centered) title.setX(scaledWidth / 2 - (title.getWidth() / 2));
|
||||
}
|
||||
}
|
||||
|
||||
public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float tickDelta) {
|
||||
buttons.forEach(b -> {
|
||||
b.setY(this.getY());
|
||||
b.render(context, mouseX, mouseY, tickDelta);
|
||||
});
|
||||
if (title != null) {
|
||||
title.setY(this.getY() + 5);
|
||||
title.render(context, mouseX, mouseY, tickDelta);
|
||||
|
||||
if (info.entry != null && !this.buttons.isEmpty() && this.buttons.getFirst() instanceof AbstractWidget widget) {
|
||||
int idMode = this.info.entry.idMode();
|
||||
if (idMode != -1) context.renderItem(idMode == 0 ?
|
||||
BuiltInRegistries.ITEM.getValue(ResourceLocation.tryParse(this.info.tempValue)).getDefaultInstance()
|
||||
: BuiltInRegistries.BLOCK.getValue(ResourceLocation.tryParse(this.info.tempValue)).asItem().getDefaultInstance(),
|
||||
widget.getX() + widget.getWidth() - 18, this.getY() + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
|
||||
if (this.info != null && this.info.comment != null && !this.info.comment.url().isBlank())
|
||||
ConfirmLinkScreen.confirmLinkNow(Minecraft.getInstance().screen, this.info.comment.url(), true);
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
public List<? extends GuiEventListener> children() {
|
||||
return Lists.newArrayList(buttons);
|
||||
}
|
||||
|
||||
public List<? extends NarratableEntry> narratables() {
|
||||
return Lists.newArrayList(buttons);
|
||||
}
|
||||
}
|
||||
103
src/main/java/eu/midnightdust/lib/config/EntryInfo.java
Normal file
103
src/main/java/eu/midnightdust/lib/config/EntryInfo.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import eu.midnightdust.lib.util.PlatformFunctions;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.gui.components.tabs.Tab;
|
||||
import net.minecraft.client.resources.language.I18n;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class EntryInfo {
|
||||
public MidnightConfig.Entry entry;
|
||||
public MidnightConfig.Comment comment;
|
||||
public MidnightConfig.Condition[] conditions;
|
||||
public final Field field;
|
||||
public final Class<?> dataType;
|
||||
public final String modid, fieldName, translationKey;
|
||||
int listIndex;
|
||||
Object defaultValue, value, function;
|
||||
String tempValue; // The value visible in the config screen
|
||||
boolean inLimits = true;
|
||||
Component error;
|
||||
AbstractWidget 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 = MidnightConfig.getUnderlyingType(field);
|
||||
this.entry = field.getAnnotation(MidnightConfig.Entry.class);
|
||||
this.comment = field.getAnnotation(MidnightConfig.Comment.class);
|
||||
this.conditions = field.getAnnotationsByType(MidnightConfig.Condition.class);
|
||||
} else {
|
||||
this.fieldName = "";
|
||||
this.dataType = null;
|
||||
}
|
||||
|
||||
if (entry != null && !entry.name().isEmpty())
|
||||
this.translationKey = entry.name();
|
||||
else if (comment != null && !comment.name().isEmpty())
|
||||
this.translationKey = comment.name();
|
||||
else this.translationKey = modid + ".midnightconfig." + fieldName;
|
||||
}
|
||||
|
||||
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) MidnightConfig.entries.values().forEach(EntryInfo::updateConditions);
|
||||
this.field.set(null, this.value);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateConditions() {
|
||||
boolean prevConditionState = this.conditionsMet;
|
||||
if (this.conditions.length > 0) this.conditionsMet = true; // reset conditions
|
||||
for (MidnightConfig.Condition condition : this.conditions) {
|
||||
//noinspection ConstantValue
|
||||
if (!condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(condition.requiredModId()))
|
||||
this.conditionsMet = false;
|
||||
String requiredOption = condition.requiredOption().contains(":") ? condition.requiredOption() : (this.modid + ":" + condition.requiredOption());
|
||||
if (MidnightConfig.entries.get(requiredOption) instanceof EntryInfo info)
|
||||
this.conditionsMet &= List.of(condition.requiredValue()).contains(info.tempValue);
|
||||
if (!this.conditionsMet) break;
|
||||
}
|
||||
if (prevConditionState != this.conditionsMet) MidnightConfig.configInstances.get(modid).reloadScreen = true;
|
||||
}
|
||||
|
||||
public <T> void writeList(int index, T value) {
|
||||
//noinspection unchecked
|
||||
var list = (List<T>) this.value;
|
||||
if (index >= list.size())
|
||||
list.add(value);
|
||||
else list.set(index, value);
|
||||
}
|
||||
|
||||
public Tooltip getTooltip(boolean isButton) {
|
||||
String key = translationKey + (!isButton ? ".label" : "") + ".tooltip";
|
||||
return Tooltip.create(isButton && this.error != null ? this.error : I18n.exists(key) ? Component.translatable(key) : Component.empty());
|
||||
}
|
||||
}
|
||||
324
src/main/java/eu/midnightdust/lib/config/MidnightConfig.java
Normal file
324
src/main/java/eu/midnightdust/lib/config/MidnightConfig.java
Normal file
@@ -0,0 +1,324 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.stream.*;
|
||||
import eu.midnightdust.lib.util.PlatformFunctions;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.resources.language.I18n;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
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.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** MidnightConfig by Martin "Motschen" Prokoph
|
||||
* Minimalist config library - feel free to copy!
|
||||
* Originally based on <a href="https://github.com/Minenash/TinyConfig">...</a>
|
||||
* Credits to Minenash */
|
||||
|
||||
@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 HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)");
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
.excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE).excludeFieldsWithModifiers(Modifier.FINAL)
|
||||
.addSerializationExclusionStrategy(new ExclusionStrategy() {
|
||||
public boolean shouldSkipClass(Class<?> clazz) { return false; }
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) { return fieldAttributes.getAnnotation(Entry.class) == null; }
|
||||
})
|
||||
.registerTypeAdapter(ResourceLocation.class, new TypeAdapter<ResourceLocation>() {
|
||||
public void write(JsonWriter out, ResourceLocation id) throws IOException { out.value(id.toString()); }
|
||||
public ResourceLocation read(JsonReader in) throws IOException { return ResourceLocation.parse(in.nextString()); }
|
||||
}).setPrettyPrinting().create();
|
||||
|
||||
protected static final LinkedHashMap<String, EntryInfo> entries = new LinkedHashMap<>(); // modid:fieldName -> EntryInfo
|
||||
|
||||
public static final Map<String, MidnightConfig> configInstances = new HashMap<>();
|
||||
|
||||
protected String modid;
|
||||
protected boolean reloadScreen = false;
|
||||
public Class<? extends MidnightConfig> configClass;
|
||||
|
||||
public static <T extends MidnightConfig> T createInstance(String modid, Class<? extends MidnightConfig> configClass) { // This is basically an argumented constructor without the requirement of having one in each config class
|
||||
try {
|
||||
T instance = (T) configClass.getDeclaredConstructor().newInstance();
|
||||
instance.modid = modid;
|
||||
instance.configClass = configClass;
|
||||
configInstances.put(modid, instance);
|
||||
return instance;
|
||||
}
|
||||
catch (Exception e) { throw new RuntimeException(e); }
|
||||
}
|
||||
|
||||
public static void init(String modid, Class<? extends MidnightConfig> config) {
|
||||
MidnightConfig instance = createInstance(modid, config);
|
||||
|
||||
for (Field field : config.getFields()) {
|
||||
//noinspection ConstantValue
|
||||
if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class))
|
||||
&& !field.isAnnotationPresent(Server.class)
|
||||
&& !field.isAnnotationPresent(Hidden.class)
|
||||
&& PlatformFunctions.isClientEnv())
|
||||
instance.addClientEntry(field, new EntryInfo(field, modid));
|
||||
}
|
||||
instance.loadValuesFromJson();
|
||||
}
|
||||
|
||||
public void addClientEntry(Field field, EntryInfo info) {
|
||||
Entry e = info.entry;
|
||||
if (e != null && info.dataType != null) {
|
||||
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 == ResourceLocation.class) textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true);
|
||||
else if (info.dataType == boolean.class) {
|
||||
Function<Object, Component> func = value -> Component.translatable((Boolean) value ? "gui.yes" : "gui.no").withStyle((Boolean) value ? ChatFormatting.GREEN : ChatFormatting.RED);
|
||||
info.function = new AbstractMap.SimpleEntry<Button.OnPress, Function<Object, Component>>(button -> {
|
||||
info.setValue(!(Boolean) info.value); button.setMessage(func.apply(info.value));
|
||||
}, func);
|
||||
} else if (info.dataType.isEnum()) {
|
||||
List<?> values = Arrays.asList(field.getType().getEnumConstants());
|
||||
Function<Object, Component> func = value -> getEnumTranslatableText(value, info);
|
||||
info.function = new AbstractMap.SimpleEntry<Button.OnPress, Function<Object, Component>>(button -> {
|
||||
int index = values.indexOf(info.value) + 1;
|
||||
info.setValue(values.get(index >= values.size() ? 0 : index));
|
||||
button.setMessage(func.apply(info.value));
|
||||
}, func);
|
||||
}
|
||||
|
||||
try { info.defaultValue = field.get(null);
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
entries.put(modid + ":" + field.getName(), info);
|
||||
}
|
||||
|
||||
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 void textField(EntryInfo info, Function<String,Number> f, Pattern pattern, double min, double max, boolean cast) {
|
||||
boolean isNumber = pattern != null;
|
||||
info.function = (BiFunction<EditBox, Button, Predicate<String>>) (t, b) -> s -> {
|
||||
s = s.trim();
|
||||
if (!(s.isEmpty() || !isNumber || pattern.matcher(s).matches()) ||
|
||||
(info.dataType == ResourceLocation.class && ResourceLocation.read(s).isError())) return false;
|
||||
|
||||
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 : Component.literal(value.doubleValue() < min ?
|
||||
"§cMinimum " + (isNumber? "value" : "length") + (cast? " is " + (int)min : " is " + min) :
|
||||
"§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max)).withStyle(ChatFormatting.RED);
|
||||
t.setTooltip(info.getTooltip(true));
|
||||
}
|
||||
|
||||
info.tempValue = s;
|
||||
t.setTextColor(inLimits? 0xFFFFFFFF : 0xFFFF7777);
|
||||
info.inLimits = inLimits;
|
||||
b.active = entries.values().stream().allMatch(e -> e.inLimits);
|
||||
|
||||
if (inLimits) {
|
||||
if (info.dataType == ResourceLocation.class)
|
||||
info.setValue(ResourceLocation.tryParse(s));
|
||||
else info.setValue(isNumber ? value : s);
|
||||
}
|
||||
|
||||
if (info.entry.isColor()) {
|
||||
if (!s.contains("#")) s = '#' + s;
|
||||
if (!HEXADECIMAL_ONLY.matcher(s).matches()) return false;
|
||||
try { info.actionButton.setMessage(Component.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB())));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
protected Component getEnumTranslatableText(Object value, EntryInfo info) {
|
||||
if (value instanceof OptionEnum translatableOption) return translatableOption.getCaption();
|
||||
|
||||
String translationKey = "%s.midnightconfig.enum.%s.%s".formatted(modid, info.dataType.getSimpleName(), info.toTemporaryValue());
|
||||
return I18n.exists(translationKey) ? Component.translatable(translationKey) : Component.literal(info.toTemporaryValue());
|
||||
}
|
||||
|
||||
public void loadValuesFromJson() {
|
||||
try {
|
||||
gson.fromJson(Files.newBufferedReader(getJsonFilePath()), configClass);
|
||||
} catch (Exception e) {
|
||||
write(modid);
|
||||
}
|
||||
|
||||
entries.values().forEach(info -> {
|
||||
if (info.field != null && info.entry != null) {
|
||||
try {
|
||||
info.value = info.field.get(null) == null ? info.defaultValue : info.field.get(null);
|
||||
info.tempValue = info.toTemporaryValue();
|
||||
info.updateConditions();
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void write(String modid) {
|
||||
configInstances.get(modid).writeChanges(modid);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void writeChanges(String modid) {
|
||||
this.writeChanges();
|
||||
}
|
||||
|
||||
public void writeChanges() {
|
||||
try {
|
||||
Path path;
|
||||
if (!Files.exists(path = getJsonFilePath()))
|
||||
Files.createFile(path);
|
||||
Files.write(path, gson.toJson(this).getBytes());
|
||||
} catch (Exception e) { e.fillInStackTrace(); }
|
||||
}
|
||||
|
||||
public Path getJsonFilePath() {
|
||||
return PlatformFunctions.getConfigDirectory().resolve(modid + ".json");
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
// Overridable method
|
||||
public void onTabInit(String tabName, MidnightConfigListWidget list, MidnightConfigScreen screen) {
|
||||
}
|
||||
|
||||
public static MidnightConfigScreen getScreen(Screen parent, String modid) {
|
||||
return configInstances.get(modid).getScreen(parent);
|
||||
}
|
||||
public MidnightConfigScreen getScreen(Screen parent) {
|
||||
return new MidnightConfigScreen(parent, modid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry Annotation<br>
|
||||
* - <b>width</b>: The maximum character length of the {@link String}, {@link ResourceLocation} or String/Identifier {@link List<>} field<br>
|
||||
* - <b>min</b>: The minimum value of the <code>int</code>, <code>float</code> or <code>double</code> field<br>
|
||||
* - <b>max</b>: The maximum value of the <code>int</code>, <code>float</code> or <code>double</code> field<br>
|
||||
* - <b>name</b>: Will be used instead of the default translation key, if not empty<br>
|
||||
* - <b>selectionMode</b>: 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
|
||||
* <code>[modid].midnightconfig.[fieldName].fileChooser.title</code> for the file picker dialog title<br>
|
||||
* - <b>fileChooserType</b>: 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 <code>[modid].midnightconfig.[fieldName].fileFilter.description</code> for the file filter description
|
||||
* if <code>"*"</code> is not used as file extension<br>
|
||||
* - <b>fileExtensions</b>: The file extensions for the file picker button for {@link String} fields (default: <code>{"*"}</code>),
|
||||
* only works if selectionMode is {@link JFileChooser#FILES_ONLY} or {@link JFileChooser#FILES_AND_DIRECTORIES}<br>
|
||||
* - <b>isColor</b>: If the field is a hexadecimal color code (default: false)<br>
|
||||
* - <b>isSlider</b>: If the field is a slider (default: false)<br>
|
||||
* - <b>precision</b>: The precision of the <code>float</code> or <code>double</code> field (default: 100)<br>
|
||||
* - <b>category</b>: The category of the field in the config screen (default: "default")<br>
|
||||
* */
|
||||
@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<br>
|
||||
* - <b>{@link Comment#centered()}</b>: If the comment should be centered<br>
|
||||
* - <b>{@link Comment#category()}</b>: The category of the comment in the config screen<br>
|
||||
* - <b>{@link Comment#name()}</b>: Will be used instead of the default translation key, if not empty<br>
|
||||
* - <b>{@link Comment#url()}</b>: The url of the comment should link to in the config screen (none if left empty)<br>
|
||||
* */
|
||||
@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<br>
|
||||
* - <b>{@link Condition#requiredModId()}</b>: The id of a mod that is required to be loaded.<br>
|
||||
* - <b>{@link Condition#requiredOption()}</b>: The {@link Field} which will be used to check the condition. Can also access options of other MidnightLib mods ("modid:optionName").<br>
|
||||
* - <b>{@link Condition#requiredValue()}</b>: The value that {@link Condition#requiredOption()} should be set to for the condition to be met.<br>
|
||||
* - <b>{@link Condition#visibleButLocked()}</b>: The behaviour to take when {@link Condition#requiredModId} is not loaded
|
||||
* or {@link Condition#requiredOption()} returns a value that is not {@link Condition#requiredValue()}.<br>
|
||||
* <code>true</code> – Option is visible, but not editable<br>
|
||||
* <code>false</code> – 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;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Conditions {
|
||||
Condition[] value();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import java.util.List;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.ContainerObjectSelectionList;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class MidnightConfigListWidget extends ContainerObjectSelectionList<ButtonEntry> {
|
||||
public boolean renderHeaderSeparator = true;
|
||||
|
||||
public MidnightConfigListWidget(Minecraft client, int width, int height, int y, int itemHeight) {
|
||||
super(client, width, height, y, itemHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollBarX() {
|
||||
return this.width - 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderListSeparators(GuiGraphics context) {
|
||||
if (renderHeaderSeparator)
|
||||
super.renderListSeparators(context);
|
||||
else
|
||||
context.blit(RenderPipelines.GUI_TEXTURED, this.minecraft.level == null ? Screen.FOOTER_SEPARATOR : Screen.INWORLD_FOOTER_SEPARATOR, this.getX(), this.getBottom(), 0, 0, this.getWidth(), 2, 32, 2);
|
||||
}
|
||||
|
||||
public void addButton(List<AbstractWidget> buttons, Component text, EntryInfo info) {
|
||||
this.addEntry(new ButtonEntry(buttons, text, info));
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.clearEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowWidth() {
|
||||
return 10000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.components.SpriteIconButton;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.gui.components.tabs.GridLayoutTab;
|
||||
import net.minecraft.client.gui.components.tabs.Tab;
|
||||
import net.minecraft.client.gui.components.tabs.TabManager;
|
||||
import net.minecraft.client.gui.components.tabs.TabNavigationBar;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.client.resources.language.I18n;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class MidnightConfigScreen extends Screen {
|
||||
public MidnightConfig instance;
|
||||
public final String translationPrefix, modid;
|
||||
public final Screen parent;
|
||||
public MidnightConfigListWidget list;
|
||||
public TabManager tabManager = new TabManager(a -> {}, a -> {});
|
||||
public Map<String, Tab> tabs = new LinkedHashMap<>();
|
||||
public Tab prevTab;
|
||||
public TabNavigationBar tabNavigation;
|
||||
public Button done;
|
||||
public double scrollProgress = 0d;
|
||||
|
||||
public MidnightConfigScreen(Screen parent, String modid) {
|
||||
super(Component.translatable(modid + ".midnightconfig.title"));
|
||||
this.parent = parent;
|
||||
this.modid = modid;
|
||||
this.translationPrefix = modid + ".midnightconfig.";
|
||||
this.instance = MidnightConfig.configInstances.get(modid);
|
||||
instance.loadValuesFromJson();
|
||||
MidnightConfig.entries.values().forEach(info -> {
|
||||
if (Objects.equals(info.modid, modid)) {
|
||||
String tabId = info.entry != null ? info.entry.category() : info.comment.category();
|
||||
String name = translationPrefix + "category." + tabId;
|
||||
if (!I18n.exists(name) && tabId.equals("default"))
|
||||
name = translationPrefix + "title";
|
||||
if (!tabs.containsKey(name)) {
|
||||
info.tab = new GridLayoutTab(Component.translatable(name));
|
||||
tabs.put(name, info.tab);
|
||||
} else info.tab = tabs.get(name);
|
||||
}
|
||||
});
|
||||
tabNavigation = TabNavigationBar.builder(tabManager, this.width).addTabs(tabs.values().toArray(new Tab[0])).build();
|
||||
tabNavigation.selectTab(0, false);
|
||||
tabNavigation.arrangeElements();
|
||||
prevTab = tabManager.getCurrentTab();
|
||||
}
|
||||
|
||||
// Real Time config update //
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (prevTab != null && prevTab != tabManager.getCurrentTab()) {
|
||||
prevTab = tabManager.getCurrentTab();
|
||||
updateList();
|
||||
list.setScrollAmount(0);
|
||||
}
|
||||
scrollProgress = list.scrollAmount();
|
||||
for (EntryInfo info : MidnightConfig.entries.values())
|
||||
if (Objects.equals(modid, info.modid)) info.updateFieldValue();
|
||||
updateButtons();
|
||||
if (instance.reloadScreen) {
|
||||
updateList();
|
||||
instance.reloadScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateButtons() {
|
||||
if (this.list == null) return;
|
||||
|
||||
for (ButtonEntry entry : this.list.children()) {
|
||||
if (entry.buttons != null && entry.buttons.size() > 1 && entry.info.field != null) {
|
||||
if (entry.buttons.get(0) instanceof AbstractWidget widget)
|
||||
if (widget.isFocused() || widget.isHovered())
|
||||
widget.setTooltip(entry.info.getTooltip(true));
|
||||
if (entry.buttons.get(1) instanceof Button button)
|
||||
button.active = !Objects.equals(String.valueOf(entry.info.value), String.valueOf(entry.info.defaultValue)) && entry.info.conditionsMet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(KeyEvent input) {
|
||||
return this.tabNavigation.keyPressed(input) || super.keyPressed(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
instance.loadValuesFromJson();
|
||||
MidnightConfig.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;
|
||||
});
|
||||
Objects.requireNonNull(minecraft).setScreen(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
tabNavigation.setWidth(this.width);
|
||||
tabNavigation.arrangeElements();
|
||||
if (tabs.size() > 1)
|
||||
this.addRenderableWidget(tabNavigation);
|
||||
|
||||
this.addRenderableWidget(Button.builder(CommonComponents.GUI_CANCEL, button -> this.onClose()).bounds(this.width / 2 - 154, this.height - 26, 150, 20).build());
|
||||
done = this.addRenderableWidget(Button.builder(CommonComponents.GUI_DONE, (button) -> {
|
||||
for (EntryInfo info : MidnightConfig.entries.values())
|
||||
if (info.modid.equals(modid))
|
||||
info.updateFieldValue();
|
||||
MidnightConfig.write(modid);
|
||||
onClose();
|
||||
}).bounds(this.width / 2 + 4, this.height - 26, 150, 20).build());
|
||||
|
||||
this.list = new MidnightConfigListWidget(this.minecraft, this.width, this.height - 57, 24, 25);
|
||||
this.addWidget(this.list);
|
||||
updateList();
|
||||
if (tabs.size() > 1)
|
||||
list.renderHeaderSeparator = false;
|
||||
}
|
||||
|
||||
public void updateList() {
|
||||
this.list.clear();
|
||||
instance.onTabInit(prevTab.getTabTitle().getContents() instanceof TranslatableContents translatable ?
|
||||
translatable.getKey().substring(translatable.getKey().lastIndexOf('.') + 1) : prevTab.getTabTitle().toString(), list, this);
|
||||
for (EntryInfo info : MidnightConfig.entries.values()) {
|
||||
info.updateConditions();
|
||||
if (!info.conditionsMet) {
|
||||
boolean visibleButLocked = false;
|
||||
for (MidnightConfig.Condition condition : info.conditions)
|
||||
visibleButLocked |= condition.visibleButLocked();
|
||||
if (!visibleButLocked) continue;
|
||||
}
|
||||
if (info.modid.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) {
|
||||
SpriteIconButton resetButton = SpriteIconButton.builder(Component.translatable("controls.reset"), (button -> {
|
||||
info.value = info.defaultValue;
|
||||
info.listIndex = 0;
|
||||
info.tempValue = info.toTemporaryValue();
|
||||
updateList();
|
||||
}), true).sprite(ResourceLocation.fromNamespaceAndPath("midnightlib", "icon/reset"), 12, 12).size(20, 20).build();
|
||||
resetButton.setPosition(width - 205 + 150 + 25, 0);
|
||||
|
||||
if (info.function != null) {
|
||||
AbstractWidget widget;
|
||||
MidnightConfig.Entry e = info.entry;
|
||||
if (info.function instanceof Map.Entry) { // Enums & booleans
|
||||
var values = (Map.Entry<Button.OnPress, Function<Object, Component>>) info.function;
|
||||
if (info.dataType.isEnum()) {
|
||||
values.setValue(value -> instance.getEnumTranslatableText(value, info));
|
||||
}
|
||||
widget = Button.builder(values.getValue().apply(info.value), values.getKey()).bounds(width - 185, 0, 150, 20).tooltip(info.getTooltip(true)).build();
|
||||
} else if (e.isSlider())
|
||||
widget = new MidnightSliderWidget(width - 185, 0, 150, 20, Component.nullToEmpty(info.tempValue), (Double.parseDouble(info.tempValue) - e.min()) / (e.max() - e.min()), info);
|
||||
else
|
||||
widget = new EditBox(font, width - 185, 0, 150, 20, Component.empty());
|
||||
|
||||
if (widget instanceof EditBox textField) {
|
||||
textField.setMaxLength(e.width());
|
||||
textField.setValue(info.tempValue);
|
||||
Predicate<String> processor = ((BiFunction<EditBox, Button, Predicate<String>>) info.function).apply(textField, done);
|
||||
textField.setFilter(processor);
|
||||
}
|
||||
widget.setTooltip(info.getTooltip(true));
|
||||
|
||||
Button cycleButton = null;
|
||||
if (info.field.getType() == List.class) {
|
||||
cycleButton = Button.builder(Component.literal(String.valueOf(info.listIndex)).withStyle(ChatFormatting.GOLD), (button -> {
|
||||
var values = (List<?>) info.value;
|
||||
values.remove("");
|
||||
info.listIndex = info.listIndex != values.size() ? info.listIndex + 1 : 0;
|
||||
info.tempValue = info.listIndex != values.size() ? info.toTemporaryValue() : "";
|
||||
updateList();
|
||||
})).bounds(width - 185, 0, 20, 20).tooltip(Tooltip.create(Component.translatable("midnightconfig.action.list_index", info.listIndex))).build();
|
||||
}
|
||||
if (e.isColor()) {
|
||||
Button colorButton = Button.builder(Component.literal("⬛"),
|
||||
button -> new Thread(() -> {
|
||||
Color newColor = JColorChooser.showDialog(null, Component.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()
|
||||
).bounds(width - 185, 0, 20, 20).tooltip(Tooltip.create(Component.translatable("midnightconfig.action.color_chooser"))).build();
|
||||
try {
|
||||
colorButton.setMessage(Component.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB())));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
info.actionButton = colorButton;
|
||||
} else if (e.selectionMode() > -1) {
|
||||
Button explorerButton = SpriteIconButton.builder(Component.empty(),
|
||||
button -> new Thread(() -> {
|
||||
JFileChooser fileChooser = new JFileChooser(info.tempValue);
|
||||
fileChooser.setFileSelectionMode(e.selectionMode());
|
||||
fileChooser.setDialogType(e.fileChooserType());
|
||||
fileChooser.setDialogTitle(Component.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(
|
||||
Component.translatable(translationPrefix + info.fieldName + ".fileFilter").getString(), e.fileExtensions()));
|
||||
if (fileChooser.showDialog(null, null) == JFileChooser.APPROVE_OPTION) {
|
||||
info.setValue(fileChooser.getSelectedFile().getAbsolutePath());
|
||||
updateList();
|
||||
}
|
||||
}).start(), true
|
||||
).sprite(ResourceLocation.fromNamespaceAndPath("midnightlib", "icon/explorer"), 12, 12).size(20, 20).build();
|
||||
explorerButton.setTooltip(Tooltip.create(Component.translatable("midnightconfig.action.file_chooser")));
|
||||
explorerButton.setPosition(width - 185, 0);
|
||||
info.actionButton = explorerButton;
|
||||
}
|
||||
List<AbstractWidget> widgets = Lists.newArrayList(widget, resetButton);
|
||||
|
||||
if (info.actionButton != null) {
|
||||
if (Util.getPlatform() == Util.OS.OSX) 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, Component.translatable(info.translationKey), info);
|
||||
} else this.list.addButton(List.of(), Component.translatable(info.translationKey), info);
|
||||
}
|
||||
list.setScrollAmount(scrollProgress);
|
||||
updateButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
this.list.render(context, mouseX, mouseY, delta);
|
||||
if (tabs.size() < 2) context.drawCenteredString(font, title, width / 2, 10, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class MidnightSliderWidget extends AbstractSliderButton {
|
||||
private final EntryInfo info;
|
||||
private final MidnightConfig.Entry e;
|
||||
|
||||
public MidnightSliderWidget(int x, int y, int width, int height, Component text, double value, EntryInfo info) {
|
||||
super(x, y, width, height, text, value);
|
||||
this.e = info.entry;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessage() {
|
||||
this.setMessage(Component.nullToEmpty(info.tempValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package eu.midnightdust.lib.util;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class MidnightColorUtil {
|
||||
/**
|
||||
* @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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package eu.midnightdust.lib.util;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
//? if fabric {
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
//?}
|
||||
//? if neoforge {
|
||||
/*import eu.midnightdust.core.MidnightLib;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.loading.FMLEnvironment;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
*///?}
|
||||
|
||||
public class PlatformFunctions {
|
||||
//? if fabric {
|
||||
public static String getPlatformName() {
|
||||
return "fabric";
|
||||
}
|
||||
public static Path getConfigDirectory() {
|
||||
return FabricLoader.getInstance().getConfigDir();
|
||||
}
|
||||
public static boolean isClientEnv() {
|
||||
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
|
||||
}
|
||||
public static boolean isModLoaded(String modid) {
|
||||
return FabricLoader.getInstance().isModLoaded(modid);
|
||||
}
|
||||
public static void registerCommand(LiteralArgumentBuilder<CommandSourceStack> command) {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated, registrationEnvironment) -> dispatcher.register(command));
|
||||
}
|
||||
//?} else if neoforge {
|
||||
/*public static String getPlatformName() {
|
||||
return "neoforge";
|
||||
}
|
||||
public static Path getConfigDirectory() {
|
||||
return FMLPaths.CONFIGDIR.get();
|
||||
}
|
||||
public static boolean isClientEnv() {
|
||||
//? if >= 1.21.9 {
|
||||
return FMLEnvironment.getDist().isClient();
|
||||
//?} else {
|
||||
// return FMLEnvironment.dist.isClient();
|
||||
//?}
|
||||
}
|
||||
public static boolean isModLoaded(String modid) {
|
||||
return ModList.get().isLoaded(modid);
|
||||
}
|
||||
public static void registerCommand(LiteralArgumentBuilder<CommandSourceStack> command) {
|
||||
MidnightLib.commands.add(command);
|
||||
}
|
||||
*///?} else if forge {
|
||||
//
|
||||
//?}
|
||||
}
|
||||
31
src/main/resources/META-INF/neoforge.mods.toml
Normal file
31
src/main/resources/META-INF/neoforge.mods.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[2,)"
|
||||
#issueTrackerURL = ""
|
||||
license = "MIT License"
|
||||
|
||||
[[mods]]
|
||||
modId = "midnightlib"
|
||||
version = "${version}"
|
||||
displayName = "${name}"
|
||||
logoFile = "midnightlib.png"
|
||||
authors = "TeamMidnightDust, Motschen"
|
||||
description = '''
|
||||
Common Library for Team MidnightDust's mods.
|
||||
'''
|
||||
|
||||
[[mixins]]
|
||||
config = "midnightlib.mixins.json"
|
||||
|
||||
[[dependencies.midnightlib]]
|
||||
modId = "neoforge"
|
||||
mandatory = true
|
||||
versionRange = "[20.5,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
||||
[[dependencies.midnightlib]]
|
||||
modId = "minecraft"
|
||||
mandatory = true
|
||||
versionRange = "[1.20.5,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
1
src/main/resources/architectury.common.json
Normal file
1
src/main/resources/architectury.common.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
src/main/resources/assets/midnightlib/icon.png
Normal file
BIN
src/main/resources/assets/midnightlib/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
10
src/main/resources/assets/midnightlib/lang/de_de.json
Executable file
10
src/main/resources/assets/midnightlib/lang/de_de.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"midnightlib.overview.title":"MidnightConfig Übersicht",
|
||||
"midnightlib.midnightconfig.title":"MidnightLib Konfiguration",
|
||||
"midnightlib.midnightconfig.config_screen_list":"Konfigurationsübersicht",
|
||||
"modmenu.summaryTranslation.midnightlib": "Code-Bibliothek für einfache Konfiguration.",
|
||||
"midnightconfig.colorChooser.title": "Wähle eine Farbe",
|
||||
"midnightconfig.action.list_index": "Bearbeite Liste an Index %s",
|
||||
"midnightconfig.action.color_chooser": "Öffne Farbauswahl",
|
||||
"midnightconfig.action.file_chooser": "Öffne Dateiauswahl"
|
||||
}
|
||||
15
src/main/resources/assets/midnightlib/lang/en_us.json
Executable file
15
src/main/resources/assets/midnightlib/lang/en_us.json
Executable file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"midnightlib.overview.title":"MidnightConfig Overview",
|
||||
"midnightlib.midnightconfig.title":"MidnightLib Config",
|
||||
"midnightlib.midnightconfig.config_screen_list":"Enable Config Screen List",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§aYes",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cNo",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.MODMENU":"§bModMenu",
|
||||
"midnightlib.modrinth":"Modrinth",
|
||||
"midnightlib.curseforge":"CurseForge",
|
||||
"midnightlib.wiki":"Wiki",
|
||||
"midnightconfig.colorChooser.title": "Choose a color",
|
||||
"midnightconfig.action.list_index": "Editing list at index %s",
|
||||
"midnightconfig.action.color_chooser": "Open color chooser",
|
||||
"midnightconfig.action.file_chooser": "Open file chooser"
|
||||
}
|
||||
13
src/main/resources/assets/midnightlib/lang/es_ar.json
Normal file
13
src/main/resources/assets/midnightlib/lang/es_ar.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"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í",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cNo",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.MODMENU":"§bMenú del Mod",
|
||||
"midnightlib.modrinth":"Modrinth",
|
||||
"midnightlib.curseforge":"CurseForge",
|
||||
"midnightlib.wiki":"Wiki",
|
||||
"modmenu.summaryTranslation.midnightlib": "Librería común para facilitar la configuración.",
|
||||
"midnightconfig.colorChooser.title": "Elegí un color"
|
||||
}
|
||||
8
src/main/resources/assets/midnightlib/lang/fr_fr.json
Normal file
8
src/main/resources/assets/midnightlib/lang/fr_fr.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"midnightlib.overview.title":"Vue d'ensemble de MidnightConfig",
|
||||
"midnightlib.midnightconfig.title":"Configuration de MidnightLib",
|
||||
"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",
|
||||
"modmenu.summaryTranslation.midnightlib": "Bibliothèque commune pour les mods de la Team MidnightDust."
|
||||
}
|
||||
8
src/main/resources/assets/midnightlib/lang/ms_my.json
Normal file
8
src/main/resources/assets/midnightlib/lang/ms_my.json
Normal file
@@ -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."
|
||||
}
|
||||
8
src/main/resources/assets/midnightlib/lang/pt_br.json
Normal file
8
src/main/resources/assets/midnightlib/lang/pt_br.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"midnightlib.overview.title":"Visão geral do MidnightConfig",
|
||||
"midnightlib.midnightconfig.title":"Configuração MidnightLib",
|
||||
"midnightlib.midnightconfig.config_screen_list":"Ativar lista de telas de configuração",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.TRUE":"§aVerdadeiro",
|
||||
"midnightlib.midnightconfig.enum.ConfigButton.FALSE":"§cFalso",
|
||||
"modmenu.summaryTranslation.midnightlib": "Biblioteca comum para mods do Team MidnightDust."
|
||||
}
|
||||
11
src/main/resources/assets/midnightlib/lang/ru_ru.json
Normal file
11
src/main/resources/assets/midnightlib/lang/ru_ru.json
Normal file
@@ -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": "Выберите цвет"
|
||||
}
|
||||
9
src/main/resources/assets/midnightlib/lang/tt_ru.json
Normal file
9
src/main/resources/assets/midnightlib/lang/tt_ru.json
Normal file
@@ -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": "MidnightDust төркеменең модлары өчен гомуми китапханә."
|
||||
}
|
||||
7
src/main/resources/assets/midnightlib/lang/uk_ua.json
Normal file
7
src/main/resources/assets/midnightlib/lang/uk_ua.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"midnightlib.overview.title":"Огляд MidnightConfig",
|
||||
"midnightlib.midnightconfig.title":"Конфігурація MidnightLib",
|
||||
"midnightlib.midnightconfig.config_screen_list":"Увімкнути список екрана конфігурації",
|
||||
"midnightlib.wiki":"Вікі",
|
||||
"modmenu.summaryTranslation.midnightlib": "Загальна бібліотека для модів команди MidnightDust."
|
||||
}
|
||||
10
src/main/resources/assets/midnightlib/lang/zh_cn.json
Normal file
10
src/main/resources/assets/midnightlib/lang/zh_cn.json
Normal file
@@ -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": "选择一种颜色"
|
||||
}
|
||||
10
src/main/resources/assets/midnightlib/lang/zh_tw.json
Normal file
10
src/main/resources/assets/midnightlib/lang/zh_tw.json
Normal file
@@ -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模組選單",
|
||||
"midnightlib.wiki":"維基",
|
||||
"modmenu.summaryTranslation.midnightlib": "MidnightDust 團隊的常用程式庫模組。"
|
||||
}
|
||||
9
src/main/resources/assets/midnightlib/lang/zlm_arab.json
Normal file
9
src/main/resources/assets/midnightlib/lang/zlm_arab.json
Normal file
@@ -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": "ڤوستاک بياسا اونتوق کونفيݢوراسي موده."
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 136 B |
Binary file not shown.
|
After Width: | Height: | Size: 136 B |
Binary file not shown.
|
After Width: | Height: | Size: 114 B |
54
src/main/resources/fabric.mod.json
Normal file
54
src/main/resources/fabric.mod.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "${id}",
|
||||
"version": "${version}",
|
||||
"name": "${name}",
|
||||
"description": "Lightweight config library with config screens and commands.",
|
||||
"authors": [
|
||||
"Motschen"
|
||||
],
|
||||
"contributors": [
|
||||
"maloryware",
|
||||
"Jaffe2718"
|
||||
],
|
||||
"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": {
|
||||
"server": [
|
||||
"eu.midnightdust.core.MidnightLib"
|
||||
],
|
||||
"client": [
|
||||
"eu.midnightdust.core.MidnightLib"
|
||||
],
|
||||
"modmenu": [
|
||||
"eu.midnightdust.core.MidnightLib"
|
||||
]
|
||||
},
|
||||
"depends": {
|
||||
"fabric-resource-loader-v0": "*",
|
||||
"minecraft": ">=1.21"
|
||||
},
|
||||
|
||||
"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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/main/resources/midnightlib.mixins.json
Normal file
1
src/main/resources/midnightlib.mixins.json
Normal file
@@ -0,0 +1 @@
|
||||
{"required": true,"minVersion": "0.8","package": "eu.midnightdust.core.mixin","compatibilityLevel": "JAVA_17","client": ["MixinOptionsScreen"],"injectors": {"defaultRequire": 1}}
|
||||
Reference in New Issue
Block a user