diff --git a/README.md b/README.md index 4fa98a4..73a10c9 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,9 @@ Requires [Cloth Config](https://modrinth.com/mod/cloth-config), [Mod Menu](https ## Configuration (requires Mod Menu) ![General Settings](https://cdn.modrinth.com/data/cached_images/9c3ed350ef0c2206a7439d1e1c15b708aa22a332.png) ![Mod Settings](https://cdn.modrinth.com/data/cached_images/f89e15c258a5a24cfda8fe9398406a7ce16edbcc.png) + ## Installation 1. Ensure you have Fabric Mod Loader installed. 2. Download the BetterHUD mod file. -3. Place the downloaded file in your Minecraft `mods` folder. +3. Place the downloaded file in your Minecraft `mods` folder. \ No newline at end of file diff --git a/src/main/java/dsns/betterhud/ModMenu.java b/src/main/java/dsns/betterhud/ModMenu.java index 70400af..787e4ec 100644 --- a/src/main/java/dsns/betterhud/ModMenu.java +++ b/src/main/java/dsns/betterhud/ModMenu.java @@ -3,17 +3,44 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; import dsns.betterhud.util.BaseMod; +import dsns.betterhud.util.ModSettings; import dsns.betterhud.util.Setting; import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import me.shedaniel.clothconfig2.api.AbstractConfigListEntry; import me.shedaniel.clothconfig2.api.ConfigBuilder; import me.shedaniel.clothconfig2.api.ConfigCategory; import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; +import me.shedaniel.clothconfig2.api.Requirement; +import me.shedaniel.clothconfig2.gui.entries.BooleanListEntry; +import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.network.chat.Component; public class ModMenu implements ModMenuApi { + private static final Map ORIENTATION_LABELS; + private static final List STANDARD_KEYS = Arrays.asList( + "Enabled", + "Orientation", + "Custom Position", + "Custom X", + "Custom Y", + "Scale", + "Text Color", + "Background Color" + ); + + static { + ORIENTATION_LABELS = new LinkedHashMap<>(); + ORIENTATION_LABELS.put("top-left", "Top Left"); + ORIENTATION_LABELS.put("top-right", "Top Right"); + ORIENTATION_LABELS.put("bottom-left", "Bottom Left"); + ORIENTATION_LABELS.put("bottom-right", "Bottom Right"); + } + @Override public ConfigScreenFactory getModConfigScreenFactory() { if (!FabricLoader.getInstance().isModLoaded("cloth-config2")) { @@ -24,7 +51,6 @@ public ConfigScreenFactory getModConfigScreenFactory() { .setParentScreen(parent) .setTitle(Component.literal("BetterHUD Settings")); - // same as builder.setSavingRunnable(() -> Config.serialize()); builder.setSavingRunnable(Config::serialize); ConfigEntryBuilder entryBuilder = builder.entryBuilder(); @@ -33,97 +59,341 @@ public ConfigScreenFactory getModConfigScreenFactory() { ConfigCategory category = builder.getOrCreateCategory( Component.literal(mod.getModID()) ); - - for (Map.Entry entry : mod - .getModSettings() - .getSettings() - .entrySet()) { - String key = entry.getKey(); - Setting setting = entry.getValue(); - - if (setting.getType().equals("boolean")) { - category.addEntry( - entryBuilder - .startBooleanToggle( - Component.literal(key), - setting.getBooleanValue() - ) - .setDefaultValue( - Boolean.valueOf(setting.getDefaultValue()) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } else if (setting.getType().equals("string")) { - category.addEntry( - entryBuilder - .startStringDropdownMenu( - Component.literal(key), - setting.getStringValue() - ) - .setSelections( - Arrays.asList(setting.getPossibleValues()) - ) - .setDefaultValue(setting.getDefaultValue()) - .setSaveConsumer(value -> - setting.setValue(value) - ) - .build() - ); - } else if (setting.getType().equals("integer")) { - category.addEntry( - entryBuilder - .startIntField( - Component.literal(key), - setting.getIntValue() - ) - .setDefaultValue( - Integer.parseInt(setting.getDefaultValue()) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } else if (setting.getType().equals("double")) { - category.addEntry( - entryBuilder - .startDoubleField( - Component.literal(key), - setting.getDoubleValue() - ) - .setDefaultValue( - Double.parseDouble( - setting.getDefaultValue() - ) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } else if (setting.getType().equals("color")) { - category.addEntry( - entryBuilder - .startAlphaColorField( - Component.literal(key), - setting.getColorValue() - ) - .setDefaultValue( - Integer.parseInt(setting.getDefaultValue()) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } - } + buildModCategory(category, entryBuilder, mod); } return builder.build(); }; } + + private void buildModCategory( + ConfigCategory category, + ConfigEntryBuilder entryBuilder, + BaseMod mod + ) { + ModSettings settings = mod.getModSettings(); + String name = mod.getModID(); + + Setting enabled = settings.getSetting("Enabled"); + BooleanListEntry enabledEntry = entryBuilder + .startBooleanToggle( + Component.literal("Enable " + name), + enabled.getBooleanValue() + ) + .setDefaultValue(Boolean.parseBoolean(enabled.getDefaultValue())) + .setTooltip( + Component.literal( + "Show the " + name + " module on the HUD." + ) + ) + .setYesNoTextSupplier(value -> + Component.literal(value ? "Enabled" : "Disabled") + ) + .setSaveConsumer(value -> + enabled.setValue(String.valueOf(value)) + ) + .build(); + category.addEntry(enabledEntry); + + SubCategoryBuilder optionsGroup = null; + for (Map.Entry entry : settings + .getSettings() + .entrySet()) { + if (STANDARD_KEYS.contains(entry.getKey())) { + continue; + } + if (optionsGroup == null) { + optionsGroup = entryBuilder + .startSubCategory(Component.literal("Options")) + .setExpanded(true) + .setTooltip( + Component.literal( + "Settings specific to the " + name + " module." + ) + ); + } + AbstractConfigListEntry optionEntry = buildGenericEntry( + entryBuilder, + entry.getKey(), + entry.getValue() + ); + if (optionEntry != null) { + optionsGroup.add(optionEntry); + } + } + if (optionsGroup != null) { + category.addEntry(optionsGroup.build()); + } + + Setting customPosition = settings.getSetting("Custom Position"); + BooleanListEntry customPositionEntry = entryBuilder + .startBooleanToggle( + Component.literal("Use Custom Position"), + customPosition.getBooleanValue() + ) + .setDefaultValue( + Boolean.parseBoolean(customPosition.getDefaultValue()) + ) + .setTooltip( + Component.literal( + "Override the screen anchor and place this module at an exact location." + ) + ) + .setSaveConsumer(value -> + customPosition.setValue(String.valueOf(value)) + ) + .build(); + + SubCategoryBuilder positionGroup = entryBuilder + .startSubCategory(Component.literal("Position")) + .setExpanded(true) + .setTooltip( + Component.literal( + "Where the module is drawn on the screen." + ) + ); + + Setting orientation = settings.getSetting("Orientation"); + positionGroup.add( + entryBuilder + .startSelector( + Component.literal("Anchor"), + orientation.getPossibleValues(), + orientation.getStringValue() + ) + .setDefaultValue(orientation.getDefaultValue()) + .setNameProvider(value -> + Component.literal(prettyOrientation(value)) + ) + .setTooltip( + Component.literal( + "Which corner of the screen the module is anchored to." + ) + ) + .setRequirement(Requirement.isFalse(customPositionEntry)) + .setSaveConsumer(value -> orientation.setValue(value)) + .build() + ); + + positionGroup.add(customPositionEntry); + + Setting customX = settings.getSetting("Custom X"); + positionGroup.add( + entryBuilder + .startIntSlider( + Component.literal("Custom X"), + customX.getIntValue(), + parseBound(customX, 0, 0), + parseBound(customX, 1, 100) + ) + .setDefaultValue(Integer.parseInt(customX.getDefaultValue())) + .setTextGetter(value -> Component.literal(value + "%")) + .setTooltip( + Component.literal( + "Horizontal position as a percentage of screen width (0% = left edge, 100% = right edge)." + ) + ) + .setRequirement(Requirement.isTrue(customPositionEntry)) + .setSaveConsumer(value -> + customX.setValue(String.valueOf(value)) + ) + .build() + ); + + Setting customY = settings.getSetting("Custom Y"); + positionGroup.add( + entryBuilder + .startIntSlider( + Component.literal("Custom Y"), + customY.getIntValue(), + parseBound(customY, 0, 0), + parseBound(customY, 1, 100) + ) + .setDefaultValue(Integer.parseInt(customY.getDefaultValue())) + .setTextGetter(value -> Component.literal(value + "%")) + .setTooltip( + Component.literal( + "Vertical position as a percentage of screen height (0% = top edge, 100% = bottom edge)." + ) + ) + .setRequirement(Requirement.isTrue(customPositionEntry)) + .setSaveConsumer(value -> + customY.setValue(String.valueOf(value)) + ) + .build() + ); + + category.addEntry(positionGroup.build()); + + SubCategoryBuilder appearanceGroup = entryBuilder + .startSubCategory(Component.literal("Appearance")) + .setExpanded(false) + .setTooltip( + Component.literal( + "Size and colors used to draw the module." + ) + ); + + Setting scale = settings.getSetting("Scale"); + appearanceGroup.add( + entryBuilder + .startDoubleField( + Component.literal("Scale"), + scale.getDoubleValue() + ) + .setDefaultValue(Double.parseDouble(scale.getDefaultValue())) + .setMin(parseDoubleBound(scale, 0, 0.1)) + .setMax(parseDoubleBound(scale, 1, 10.0)) + .setTooltip( + Component.literal( + "Size multiplier applied to the module (1.0 = default size)." + ) + ) + .setSaveConsumer(value -> + scale.setValue(String.valueOf(value)) + ) + .build() + ); + + Setting textColor = settings.getSetting("Text Color"); + appearanceGroup.add( + entryBuilder + .startAlphaColorField( + Component.literal("Text Color"), + textColor.getColorValue() + ) + .setDefaultValue( + Integer.parseInt(textColor.getDefaultValue()) + ) + .setTooltip( + Component.literal( + "Color of the displayed text (with alpha)." + ) + ) + .setSaveConsumer(value -> + textColor.setValue(String.valueOf(value)) + ) + .build() + ); + + Setting backgroundColor = settings.getSetting("Background Color"); + appearanceGroup.add( + entryBuilder + .startAlphaColorField( + Component.literal("Background Color"), + backgroundColor.getColorValue() + ) + .setDefaultValue( + Integer.parseInt(backgroundColor.getDefaultValue()) + ) + .setTooltip( + Component.literal( + "Color of the rectangle drawn behind the text. Set alpha to 0 for no background." + ) + ) + .setSaveConsumer(value -> + backgroundColor.setValue(String.valueOf(value)) + ) + .build() + ); + + category.addEntry(appearanceGroup.build()); + } + + private AbstractConfigListEntry buildGenericEntry( + ConfigEntryBuilder entryBuilder, + String key, + Setting setting + ) { + Component label = Component.literal(key); + switch (setting.getType()) { + case "boolean": + return entryBuilder + .startBooleanToggle(label, setting.getBooleanValue()) + .setDefaultValue( + Boolean.parseBoolean(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build(); + case "string": + return entryBuilder + .startSelector( + label, + setting.getPossibleValues(), + setting.getStringValue() + ) + .setDefaultValue(setting.getDefaultValue()) + .setSaveConsumer(value -> setting.setValue(value)) + .build(); + case "integer": + return entryBuilder + .startIntField(label, setting.getIntValue()) + .setMin(parseBound(setting, 0, Integer.MIN_VALUE)) + .setMax(parseBound(setting, 1, Integer.MAX_VALUE)) + .setDefaultValue( + Integer.parseInt(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build(); + case "double": + return entryBuilder + .startDoubleField(label, setting.getDoubleValue()) + .setDefaultValue( + Double.parseDouble(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build(); + case "color": + return entryBuilder + .startAlphaColorField(label, setting.getColorValue()) + .setDefaultValue( + Integer.parseInt(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build(); + default: + return null; + } + } + + private static String prettyOrientation(String raw) { + String pretty = ORIENTATION_LABELS.get(raw); + return pretty != null ? pretty : raw; + } + + private static int parseBound(Setting setting, int index, int fallback) { + String[] possible = setting.getPossibleValues(); + if (possible == null || possible.length <= index) { + return fallback; + } + try { + return Integer.parseInt(possible[index]); + } catch (NumberFormatException e) { + return fallback; + } + } + + private static double parseDoubleBound( + Setting setting, + int index, + double fallback + ) { + String[] possible = setting.getPossibleValues(); + if (possible == null || possible.length <= index) { + return fallback; + } + try { + return Double.parseDouble(possible[index]); + } catch (NumberFormatException e) { + return fallback; + } + } }