diff --git a/src/main/java/top/fpsmaster/features/impl/InterfaceModule.java b/src/main/java/top/fpsmaster/features/impl/InterfaceModule.java index 3e55771c..ef99eb25 100644 --- a/src/main/java/top/fpsmaster/features/impl/InterfaceModule.java +++ b/src/main/java/top/fpsmaster/features/impl/InterfaceModule.java @@ -7,8 +7,33 @@ import top.fpsmaster.features.settings.impl.NumberSetting; import java.awt.*; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Set; +/** + * Base for HUD modules. + * + *

The shared appearance settings below are consumed by {@code Component.drawRect} and + * {@code Component.drawString} — by the base class, never by the subclass that inherits them. + * Registration therefore belongs here too. Subclasses used to call {@code addSettings(...)} for them + * by hand, which drifted badly: some registered none (the settings still took effect but were + * invisible in the ClickGUI and never persisted), several registered the same setting twice + * (duplicate rows bound to one value), and none of it changed behaviour either way. + * + *

Subclasses now declare their {@link Trait}s and register only their own settings; + * {@link #registerCommonSettings()} is applied centrally once every module is constructed. + */ public class InterfaceModule extends Module { + /** What a HUD module draws, which decides the shared settings that apply to it. */ + public enum Trait { + /** Draws a background panel: {@code bg}, {@code backgroundColor}, {@code rounded}, {@code roundRadius}. */ + BACKGROUND, + /** Draws text: {@code betterFont}, {@code fontShadow}. */ + TEXT, + /** Lays out repeated items and honours {@code spacing}. */ + SPACING + } public BooleanSetting rounded = new BooleanSetting("Round", true); public NumberSetting roundRadius = new NumberSetting("RoundRadius", 3, 0, 30, 1, () -> rounded.getValue()); @@ -16,13 +41,45 @@ public class InterfaceModule extends Module { public BooleanSetting fontShadow = new BooleanSetting("FontShadow", true); public BooleanSetting bg = new BooleanSetting("Background", true); public ColorSetting backgroundColor = new ColorSetting("BackgroundColor", new Color(0, 0, 0, 0), () -> bg.getValue()); - public NumberSetting spacing = new NumberSetting("Spacing",0,0,3,1); + public NumberSetting spacing = new NumberSetting("Spacing", 0, 0, 3, 1); + + /** For modules that draw neither a panel nor text — pass this rather than an empty arg list, + * which would resolve to the two-arg constructor and silently pick up the defaults. */ + public static final Trait[] NONE = new Trait[0]; + private final Set traits; + /** Most HUD modules draw a background panel with text on it. */ public InterfaceModule(String name, Category category) { - super(name, category); + this(name, category, Trait.BACKGROUND, Trait.TEXT); } -} + public InterfaceModule(String name, Category category, Trait... traits) { + super(name, category); + this.traits = traits.length == 0 + ? EnumSet.noneOf(Trait.class) + : EnumSet.copyOf(Arrays.asList(traits)); + } + public boolean has(Trait trait) { + return traits.contains(trait); + } + /** + * Appends the shared settings this module's traits call for. Called once from + * {@code ModuleManager.init()} after every module is constructed, so a module's own settings stay + * at the top of its ClickGUI panel and the shared appearance ones follow. Idempotent — + * {@code addSettings} ignores anything already registered. + */ + public void registerCommonSettings() { + if (has(Trait.BACKGROUND)) { + addSettings(bg, backgroundColor, rounded, roundRadius); + } + if (has(Trait.TEXT)) { + addSettings(betterFont, fontShadow); + } + if (has(Trait.SPACING)) { + addSettings(spacing); + } + } +} diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ArmorDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ArmorDisplay.java index f1c298ba..9568abd4 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ArmorDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ArmorDisplay.java @@ -8,9 +8,8 @@ public class ArmorDisplay extends InterfaceModule { public static ModeSetting mode = new ModeSetting("Mode", 0, "SimpleHoriz", "SimpleVertical", "Vertical"); public ArmorDisplay() { - super("ArmorDisplay", Category.Interface); - addSettings(rounded, backgroundColor, fontShadow, betterFont, spacing, mode, bg, rounded, roundRadius); + super("ArmorDisplay", Category.Interface, Trait.BACKGROUND, Trait.TEXT, Trait.SPACING); + addSettings(mode); } } - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/BetterChat.java b/src/main/java/top/fpsmaster/features/impl/interfaces/BetterChat.java index 540b2dd2..f8237ca3 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/BetterChat.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/BetterChat.java @@ -25,10 +25,9 @@ public class BetterChat extends InterfaceModule { private static java.lang.reflect.Field chatLinesField; private static java.lang.reflect.Field drawnChatLinesField; - public BetterChat() { super("BetterChat", Category.Interface); - addSettings(foldMessage, copyMessage, backgroundColor, fontShadow, betterFont, bg); + addSettings(foldMessage, copyMessage); } @Override @@ -109,5 +108,3 @@ private static java.util.List getDrawnChatLines(GuiNewChat chat) { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/BlockIndicator.java b/src/main/java/top/fpsmaster/features/impl/interfaces/BlockIndicator.java index 1c8dc3f0..bff56012 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/BlockIndicator.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/BlockIndicator.java @@ -18,6 +18,6 @@ public class BlockIndicator extends InterfaceModule { public BlockIndicator() { super("BlockIndicator", Category.Interface); backgroundColor.setColor(new Color(18, 20, 26, 190)); - addSettings(showId, showCoords, yOffset, bg, rounded, roundRadius, backgroundColor, panelColor, accentColor); + addSettings(showId, showCoords, yOffset, panelColor, accentColor); } } diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/CPSDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/CPSDisplay.java index d8e26f00..f972db16 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/CPSDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/CPSDisplay.java @@ -12,7 +12,6 @@ import java.util.LinkedList; public class CPSDisplay extends InterfaceModule { - private static final LinkedList KEYS = new LinkedList<>(); private static final CPSCounterListener COUNTER_LISTENER = new CPSCounterListener(); private static boolean trackingRegistered = false; @@ -21,7 +20,6 @@ public CPSDisplay() { super("CPSDisplay", Category.Interface); ensureTracking(); addSettings(textColor); - addSettings(backgroundColor, fontShadow, betterFont, bg, rounded, roundRadius); } public static void ensureTracking() { @@ -69,5 +67,3 @@ private static class Key { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ClockDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ClockDisplay.java index 0a86240e..fad25134 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ClockDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ClockDisplay.java @@ -17,6 +17,6 @@ public class ClockDisplay extends InterfaceModule { public ClockDisplay() { super("ClockDisplay", Category.Interface); backgroundColor.setColor(new Color(18, 20, 26, 160)); - addSettings(showSeconds, hour24Mode, label, textColor, betterFont, fontShadow, bg, rounded, roundRadius, backgroundColor); + addSettings(showSeconds, hour24Mode, label, textColor); } } diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ComboDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ComboDisplay.java index 5ce48888..daed2fa6 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ComboDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ComboDisplay.java @@ -11,7 +11,6 @@ import java.awt.*; public class ComboDisplay extends InterfaceModule { - private Entity target = null; public static int combo = 0; @@ -19,7 +18,7 @@ public class ComboDisplay extends InterfaceModule { public ComboDisplay() { super("ComboDisplay", Category.Interface); - addSettings(textColor, backgroundColor, betterFont, fontShadow, rounded, bg, rounded, roundRadius); + addSettings(textColor); } @Subscribe @@ -27,7 +26,7 @@ public void onTick(EventTick e) { if (net.minecraft.client.Minecraft.getMinecraft().thePlayer == null) return; net.minecraft.entity.player.EntityPlayer rawPlayer = net.minecraft.client.Minecraft.getMinecraft().thePlayer; - + if (rawPlayer.hurtTime == 1 || (target != null && rawPlayer.getDistanceToEntity(target) > 7)) { combo = 0; } @@ -42,5 +41,3 @@ public void attack(EventAttack e) { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/CoordsDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/CoordsDisplay.java index 33f916e3..b8ceebdd 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/CoordsDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/CoordsDisplay.java @@ -6,16 +6,13 @@ import top.fpsmaster.features.settings.impl.NumberSetting; public class CoordsDisplay extends InterfaceModule { - public BooleanSetting limitDisplay = new BooleanSetting("LimitDisplay", false); public NumberSetting limitDisplayY = new NumberSetting("LimitDisplayY", 92, 0, 255, 1); public CoordsDisplay() { super("CoordsDisplay", Category.Interface); - addSettings(rounded, backgroundColor, fontShadow, betterFont, bg, rounded, roundRadius); + addSettings(limitDisplay, limitDisplayY); } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/FPSDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/FPSDisplay.java index e0ee2656..b4c1aa02 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/FPSDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/FPSDisplay.java @@ -7,15 +7,11 @@ import java.awt.*; public class FPSDisplay extends InterfaceModule { - public static ColorSetting textColor = new ColorSetting("TextColor", new Color(255, 255, 255)); public FPSDisplay() { super("FPSDisplay", Category.Interface); addSettings(textColor); - addSettings(rounded, backgroundColor, fontShadow, betterFont, bg, rounded, roundRadius); } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/InventoryDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/InventoryDisplay.java index 3814b843..085ebf77 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/InventoryDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/InventoryDisplay.java @@ -6,9 +6,6 @@ public class InventoryDisplay extends InterfaceModule { public InventoryDisplay() { super("InventoryDisplay", Category.Interface); - addSettings(backgroundColor, bg, rounded, roundRadius); } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ItemCountDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ItemCountDisplay.java index 66fae6a9..662f5d4a 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ItemCountDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ItemCountDisplay.java @@ -6,18 +6,14 @@ import top.fpsmaster.features.settings.impl.MultipleItemSetting; public class ItemCountDisplay extends InterfaceModule { - //TODO: Custom 自定义物品数量,建议添加MultipleSetting,便于让用户自动添加,暂未实现 public ModeSetting modes = new ModeSetting("mode", 0, "potpvp", "uhc", "custom"); public MultipleItemSetting itemsSetting = new MultipleItemSetting("items",() -> modes.getValue() == 2); public ItemCountDisplay() { - super("ItemCountDisplay", Category.Interface); - addSettings(bg, backgroundColor ,rounded, roundRadius, betterFont, fontShadow, spacing, modes, itemsSetting); + super("ItemCountDisplay", Category.Interface, Trait.BACKGROUND, Trait.TEXT, Trait.SPACING); + addSettings(modes, itemsSetting); } - } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/Keystrokes.java b/src/main/java/top/fpsmaster/features/impl/interfaces/Keystrokes.java index 5639ad0e..5b5d8469 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/Keystrokes.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/Keystrokes.java @@ -38,17 +38,10 @@ public class Keystrokes extends InterfaceModule { public static ModeSetting spaceStyle = new ModeSetting("SpaceStyle", 0, () -> showSpace.getValue(), "Text", "Bar"); public Keystrokes() { - super("Keystrokes", Category.Interface); + super("Keystrokes", Category.Interface, Trait.BACKGROUND, Trait.TEXT, Trait.SPACING); CPSDisplay.ensureTracking(); roundRadius.setValue(2); - addSettings( - fontShadow, betterFont, - pressedColor, fontColor, pressedFontColor, - borderColor, borderWidth, - pressAnimMode, pressAnimColor, pressAnimDuration, - showSpace, cpsMode, wasdStyle, spaceStyle, spacing, bg, backgroundColor, rounded, roundRadius - ); + addSettings(pressedColor, fontColor, pressedFontColor, borderColor, borderWidth, pressAnimMode, pressAnimColor, pressAnimDuration, showSpace, cpsMode, wasdStyle, spaceStyle); } } - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/MiniMap.java b/src/main/java/top/fpsmaster/features/impl/interfaces/MiniMap.java index cef066f7..22548e0e 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/MiniMap.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/MiniMap.java @@ -8,7 +8,7 @@ public class MiniMap extends InterfaceModule { public MiniMap() { - super("MiniMap", Category.Interface); + super("MiniMap", Category.Interface, InterfaceModule.NONE); } public static boolean using = false; @@ -34,5 +34,3 @@ public void onDisable() { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ModsList.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ModsList.java index 3093e36c..240e1f17 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ModsList.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ModsList.java @@ -9,7 +9,6 @@ import java.awt.*; public class ModsList extends InterfaceModule { - public BooleanSetting showLogo = new BooleanSetting("ShowText", true); public BooleanSetting english = new BooleanSetting("English", true); public BooleanSetting rainbow = new BooleanSetting("Rainbow", true); @@ -18,9 +17,7 @@ public class ModsList extends InterfaceModule { public ModsList() { super("ModsList", Category.Interface); - addSettings(showLogo, text, english, color, rainbow, betterFont, spacing, backgroundColor, bg); + addSettings(showLogo, text, english, color, rainbow); } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/PingDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/PingDisplay.java index d5a7fc12..0d7fcec0 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/PingDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/PingDisplay.java @@ -10,11 +10,8 @@ public class PingDisplay extends InterfaceModule { public PingDisplay() { super("PingDisplay", Category.Interface); addSettings(textColor); - addSettings(rounded, backgroundColor, fontShadow, betterFont, bg, rounded, roundRadius); } public static ColorSetting textColor = new ColorSetting("TextColor", new Color(255, 255, 255)); } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/PlayTime.java b/src/main/java/top/fpsmaster/features/impl/interfaces/PlayTime.java index f52cf828..168dc51c 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/PlayTime.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/PlayTime.java @@ -16,6 +16,6 @@ public class PlayTime extends InterfaceModule { public PlayTime() { super("PlayTime", Category.Interface); backgroundColor.setColor(new Color(18, 20, 26, 160)); - addSettings(displayMode, label, textColor, betterFont, fontShadow, bg, rounded, roundRadius, backgroundColor); + addSettings(displayMode, label, textColor); } } diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/PlayerDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/PlayerDisplay.java index 33a997c4..abb6d83b 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/PlayerDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/PlayerDisplay.java @@ -2,13 +2,17 @@ import top.fpsmaster.features.impl.InterfaceModule; import top.fpsmaster.features.manager.Category; +import top.fpsmaster.features.settings.impl.ColorSetting; + +import java.awt.*; public class PlayerDisplay extends InterfaceModule { public PlayerDisplay() { super("PlayerDisplay", Category.Interface); - addSettings(betterFont, rounded, fontShadow, backgroundColor, bg, rounded, roundRadius); + // This HUD used to paint a hard-coded translucent black panel instead of going through + // drawRect, so its background ignored every appearance setting. Now that it uses drawRect, + // seed the shared setting with that same colour so the look is unchanged but adjustable. + backgroundColor = new ColorSetting("BackgroundColor", new Color(0, 0, 0, 60), () -> bg.getValue()); } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/PotionDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/PotionDisplay.java index 17c27554..ff3afef4 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/PotionDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/PotionDisplay.java @@ -12,8 +12,8 @@ public class PotionDisplay extends InterfaceModule { public static NumberSetting reminderTime = new NumberSetting("ReminderTime", 20, 1, 120, 1, () -> noticeableReminder.getValue()); public PotionDisplay() { - super("PotionDisplay", Category.Interface); - addSettings(backgroundColor, fontShadow, betterFont, betterAnimation, noticeableReminder, reminderTime, spacing, bg, rounded, roundRadius); + super("PotionDisplay", Category.Interface, Trait.BACKGROUND, Trait.TEXT, Trait.SPACING); + addSettings(betterAnimation, noticeableReminder, reminderTime); } @Override @@ -29,5 +29,3 @@ public void onDisable() { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ReachDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ReachDisplay.java index a1731abf..baddca77 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ReachDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ReachDisplay.java @@ -19,7 +19,7 @@ public class ReachDisplay extends InterfaceModule { public ReachDisplay() { super("ReachDisplay", Category.Interface); - addSettings(rounded, backgroundColor, fontShadow, betterFont, textColor, bg, rounded, roundRadius); + addSettings(textColor); } @Subscribe @@ -33,5 +33,3 @@ public void onAttack(EventAttack e) { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/Scoreboard.java b/src/main/java/top/fpsmaster/features/impl/interfaces/Scoreboard.java index 0c811e7a..18ac4543 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/Scoreboard.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/Scoreboard.java @@ -10,7 +10,7 @@ public class Scoreboard extends InterfaceModule { public Scoreboard() { super("Scoreboard", Category.Interface); - addSettings(rounded, backgroundColor, fontShadow, betterFont, score, bg, rounded, roundRadius); + addSettings(score); } @Override @@ -26,5 +26,3 @@ public void onDisable() { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/ServerAddressDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/ServerAddressDisplay.java index 3b7d52a8..578ac1d3 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/ServerAddressDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/ServerAddressDisplay.java @@ -14,6 +14,6 @@ public class ServerAddressDisplay extends InterfaceModule { public ServerAddressDisplay() { super("ServerAddressDisplay", Category.Interface); backgroundColor.setColor(new Color(18, 20, 26, 160)); - addSettings(label, textColor, betterFont, fontShadow, bg, rounded, roundRadius, backgroundColor); + addSettings(label, textColor); } } diff --git a/src/main/java/top/fpsmaster/features/impl/utility/Sprint.java b/src/main/java/top/fpsmaster/features/impl/utility/Sprint.java index eb418721..b924da19 100644 --- a/src/main/java/top/fpsmaster/features/impl/utility/Sprint.java +++ b/src/main/java/top/fpsmaster/features/impl/utility/Sprint.java @@ -15,11 +15,10 @@ public class Sprint extends InterfaceModule { public static boolean sprint = true; public Sprint() { - super("Sprint", Category.Utility); - addSettings(toggleSprint, betterFont); + super("Sprint", Category.Utility, Trait.TEXT); + addSettings(toggleSprint); } - @Override public void onEnable() { super.onEnable(); @@ -56,5 +55,3 @@ public void onDisable() { } } - - diff --git a/src/main/java/top/fpsmaster/features/impl/utility/ToggleSneak.java b/src/main/java/top/fpsmaster/features/impl/utility/ToggleSneak.java index 03220e47..2d29aca3 100644 --- a/src/main/java/top/fpsmaster/features/impl/utility/ToggleSneak.java +++ b/src/main/java/top/fpsmaster/features/impl/utility/ToggleSneak.java @@ -17,8 +17,8 @@ public class ToggleSneak extends InterfaceModule { public static boolean sneak = false; public ToggleSneak() { - super("ToggleSneak", Category.Utility); - addSettings(toggleSneak, toggleKey, betterFont); + super("ToggleSneak", Category.Utility, Trait.TEXT); + addSettings(toggleSneak, toggleKey); } @Override diff --git a/src/main/java/top/fpsmaster/features/manager/Module.java b/src/main/java/top/fpsmaster/features/manager/Module.java index f187438f..5840c081 100644 --- a/src/main/java/top/fpsmaster/features/manager/Module.java +++ b/src/main/java/top/fpsmaster/features/manager/Module.java @@ -31,14 +31,27 @@ public Module(String name, Category category) { this.category = category; } + /** + * Registration is idempotent by identity. Several modules used to pass the same setting twice, + * which rendered a duplicate row in the ClickGUI bound to the same underlying value. + */ public void addSettings(Setting... settings) { for (Setting setting : settings) { - if (setting != null) { + if (setting != null && !containsSetting(setting)) { this.settings.add(setting); } } } + private boolean containsSetting(Setting setting) { + for (Setting existing : this.settings) { + if (existing == setting) { + return true; + } + } + return false; + } + public void toggle() { set(!isEnabled); } diff --git a/src/main/java/top/fpsmaster/features/manager/ModuleManager.java b/src/main/java/top/fpsmaster/features/manager/ModuleManager.java index 11ae43b6..ede51dc8 100644 --- a/src/main/java/top/fpsmaster/features/manager/ModuleManager.java +++ b/src/main/java/top/fpsmaster/features/manager/ModuleManager.java @@ -6,6 +6,7 @@ import top.fpsmaster.event.EventDispatcher; import top.fpsmaster.event.Subscribe; import top.fpsmaster.event.events.EventKey; +import top.fpsmaster.features.impl.InterfaceModule; import top.fpsmaster.features.impl.interfaces.*; import top.fpsmaster.features.impl.optimizes.*; import top.fpsmaster.features.impl.render.*; @@ -141,6 +142,14 @@ public void init() { modules.add(new HideIndicator()); + // Shared HUD appearance settings are appended centrally, after each module registered its own, + // so every InterfaceModule ends up with a consistent panel regardless of what its constructor did. + for (Module m : FPSMaster.moduleManager.modules) { + if (m instanceof InterfaceModule) { + ((InterfaceModule) m).registerCommonSettings(); + } + } + for (Module m : FPSMaster.moduleManager.modules) { mainPanel.mods.add(new ModuleRenderer(m)); } diff --git a/src/main/java/top/fpsmaster/modules/logger/ClientLogger.java b/src/main/java/top/fpsmaster/modules/logger/ClientLogger.java index b3c3b5a8..460fa9c8 100644 --- a/src/main/java/top/fpsmaster/modules/logger/ClientLogger.java +++ b/src/main/java/top/fpsmaster/modules/logger/ClientLogger.java @@ -37,6 +37,16 @@ public static void info(String from, String s) { public static void error(String from, String s) { logger.error("{} -> {}", from, s); } + + // Throwable overloads: without these, callers that catch an exception have no way to record it + // and the stack trace is silently dropped. + public static void error(String s, Throwable throwable) { + logger.error(s, throwable); + } + + public static void warn(String s, Throwable throwable) { + logger.warn(s, throwable); + } } diff --git a/src/main/java/top/fpsmaster/ui/custom/Component.java b/src/main/java/top/fpsmaster/ui/custom/Component.java index acdd0548..d7b0aa79 100644 --- a/src/main/java/top/fpsmaster/ui/custom/Component.java +++ b/src/main/java/top/fpsmaster/ui/custom/Component.java @@ -42,6 +42,9 @@ public class Component { public boolean allowScale = false; + /** Consecutive render failures; reset on any successful frame. See ComponentsManager. */ + public int renderFailures = 0; + public Position position = Position.LT; @SuppressWarnings("unchecked") @@ -91,25 +94,32 @@ public float[] getRealPosition(ScaledResolution sr) { float guiWidth = sr.getScaledWidth() / 2f * scaleFactor; float guiHeight = sr.getScaledHeight() / 2f * scaleFactor; + // Anchors offset by the component's *rendered* size. width/height are logical units; everything + // that touches the drawn box (drawRect, hover, drag clamping, blur mask) multiplies by scale, + // so these must too — otherwise a scaled-up right-anchored component overflows the screen edge + // by width * (scale - 1). + float scaledWidth = width * scale; + float scaledHeight = height * scale; + switch (position) { case LT: rX = x * guiWidth / 2f; rY = y * guiHeight / 2f; break; case RT: - rX = guiWidth - (x * guiWidth / 2f + width); + rX = guiWidth - (x * guiWidth / 2f + scaledWidth); rY = y * guiHeight / 2f; break; case LB: rX = x * guiWidth / 2f; - rY = guiHeight - (y * guiHeight / 2f + height); + rY = guiHeight - (y * guiHeight / 2f + scaledHeight); break; case RB: - rX = guiWidth - (x * guiWidth / 2f + width); - rY = guiHeight - (y * guiHeight / 2f + height); + rX = guiWidth - (x * guiWidth / 2f + scaledWidth); + rY = guiHeight - (y * guiHeight / 2f + scaledHeight); break; case CT: - rX = guiWidth / 2f - width * scale / 2f; + rX = guiWidth / 2f - scaledWidth / 2f; rY = y * guiHeight / 2f; break; } @@ -117,7 +127,7 @@ public float[] getRealPosition(ScaledResolution sr) { } public void drawBlurMask(ScaledResolution sr) { - if (!mod.bg.getValue() || width <= 0f || height <= 0f) { + if (!hasBackground() || width <= 0f || height <= 0f) { return; } float[] pos = getRealPosition(sr); @@ -246,11 +256,20 @@ else if (y < guiHeight / 2f) this.y = changeY / guiHeight * 2f; } + /** + * A module only has a background if its traits say so. Checking {@code bg} alone is not enough: + * the field exists on every InterfaceModule and defaults to {@code true}, so a text-only module + * such as Sprint would otherwise have a blur mask stamped for a panel it never draws. + */ + public boolean hasBackground() { + return mod.has(InterfaceModule.Trait.BACKGROUND) && mod.bg.getValue(); + } + public void drawRect(float x, float y, float width, float height, Color color) { float scaledWidth = width * scale; float scaledHeight = height * scale; - if (mod.bg.getValue()) { + if (hasBackground()) { if (mod.rounded.getValue()) { Rects.roundedImage(Math.round(x), Math.round(y), Math.round(scaledWidth), Math.round(scaledHeight), mod.roundRadius.getValue().intValue(), color); } else { diff --git a/src/main/java/top/fpsmaster/ui/custom/ComponentsManager.java b/src/main/java/top/fpsmaster/ui/custom/ComponentsManager.java index 1a9f6152..76072c84 100644 --- a/src/main/java/top/fpsmaster/ui/custom/ComponentsManager.java +++ b/src/main/java/top/fpsmaster/ui/custom/ComponentsManager.java @@ -1,5 +1,6 @@ package top.fpsmaster.ui.custom; +import net.minecraft.client.renderer.GlStateManager; import org.lwjgl.opengl.GL11; import top.fpsmaster.features.impl.InterfaceModule; import top.fpsmaster.ui.custom.impl.*; @@ -59,6 +60,40 @@ public Component getComponent(Class clazz) { .orElse(null); } + /** A component failing this many frames in a row is switched off rather than left to spin. */ + private static final int DISABLE_AFTER_FAILURES = 60; + + /** + * Per-frame failures are swallowed on purpose: {@code mc.thePlayer}/{@code theWorld} go null while + * changing worlds and several components dereference them unguarded, so without this one NPE would + * take down the whole EventRender2D dispatch. What must not happen is logging the same line 60 + * times a second, so output is rate-limited and the exception itself is finally recorded. + */ + private void onComponentFailure(Component component, String phase, Throwable throwable) { + int failures = ++component.renderFailures; + // Full detail for the first few, then only on powers of two. + if (failures <= 3 || Integer.bitCount(failures) == 1) { + ClientLogger.error("Failed to " + phase + " component " + component.mod.name + + " (failure #" + failures + ")", throwable); + } + if (failures == DISABLE_AFTER_FAILURES) { + ClientLogger.error("Disabling component " + component.mod.name + + " after " + failures + " consecutive failures"); + component.mod.set(false); + } + // An exception can leave scissor/stencil/blend enabled and bleed into the rest of the frame. + resetRenderState(); + } + + private void resetRenderState() { + GL11.glDisable(GL11.GL_SCISSOR_TEST); + GL11.glDisable(GL11.GL_STENCIL_TEST); + GlStateManager.enableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + GlStateManager.color(1f, 1f, 1f, 1f); + } + public void drawBackgroundMasks() { GL11.glPushMatrix(); net.minecraft.client.gui.ScaledResolution sr = new net.minecraft.client.gui.ScaledResolution(Utility.mc); @@ -67,8 +102,8 @@ public void drawBackgroundMasks() { if (component.shouldDisplay()) { try { component.drawBlurMask(sr); - } catch (Exception e) { - ClientLogger.error("Failed to render component blur mask: " + component.mod.name); + } catch (Throwable throwable) { + onComponentFailure(component, "mask", throwable); } } }); @@ -97,8 +132,9 @@ public void draw(int mouseX, int mouseY) { if (component.shouldDisplay()) { try { component.display(sr, finalMouseX, finalMouseY); - } catch (Exception e) { - ClientLogger.error("Failed to render component: " + component.mod.name); + component.renderFailures = 0; + } catch (Throwable throwable) { + onComponentFailure(component, "render", throwable); } } }); diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/KeystrokesComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/KeystrokesComponent.java index 1babb09c..40cc1e1a 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/KeystrokesComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/KeystrokesComponent.java @@ -257,16 +257,22 @@ private void drawBorderRect(float x, float y, float width, float height, float b int radius = resolveKeyRadius(); Color border = resolveBorderColor(x, y); + // try/finally: bailing out mid-stencil would leave GL_STENCIL_TEST enabled and colorMask + // partially closed, which silently discards everything drawn after this component. StencilUtil.initStencilToWrite(); - Rects.rounded(outerX, outerY, outerW, outerH, radius, new Color(0, 0, 0, 255).getRGB()); - org.lwjgl.opengl.GL11.glStencilFunc(org.lwjgl.opengl.GL11.GL_ALWAYS, 0, 0xFF); - org.lwjgl.opengl.GL11.glStencilOp(org.lwjgl.opengl.GL11.GL_REPLACE, org.lwjgl.opengl.GL11.GL_REPLACE, org.lwjgl.opengl.GL11.GL_REPLACE); - Rects.rounded(x, y, scaledW, scaledH, radius, new Color(0, 0, 0, 255).getRGB()); - org.lwjgl.opengl.GL11.glColorMask(true, true, true, true); - org.lwjgl.opengl.GL11.glStencilFunc(org.lwjgl.opengl.GL11.GL_EQUAL, 1, 0xFF); - org.lwjgl.opengl.GL11.glStencilOp(org.lwjgl.opengl.GL11.GL_KEEP, org.lwjgl.opengl.GL11.GL_KEEP, org.lwjgl.opengl.GL11.GL_KEEP); - Rects.rounded(outerX, outerY, outerW, outerH, radius, border.getRGB()); - StencilUtil.uninitStencilBuffer(); + try { + Rects.rounded(outerX, outerY, outerW, outerH, radius, new Color(0, 0, 0, 255).getRGB()); + org.lwjgl.opengl.GL11.glStencilFunc(org.lwjgl.opengl.GL11.GL_ALWAYS, 0, 0xFF); + org.lwjgl.opengl.GL11.glStencilOp(org.lwjgl.opengl.GL11.GL_REPLACE, org.lwjgl.opengl.GL11.GL_REPLACE, org.lwjgl.opengl.GL11.GL_REPLACE); + Rects.rounded(x, y, scaledW, scaledH, radius, new Color(0, 0, 0, 255).getRGB()); + org.lwjgl.opengl.GL11.glColorMask(true, true, true, true); + org.lwjgl.opengl.GL11.glStencilFunc(org.lwjgl.opengl.GL11.GL_EQUAL, 1, 0xFF); + org.lwjgl.opengl.GL11.glStencilOp(org.lwjgl.opengl.GL11.GL_KEEP, org.lwjgl.opengl.GL11.GL_KEEP, org.lwjgl.opengl.GL11.GL_KEEP); + Rects.rounded(outerX, outerY, outerW, outerH, radius, border.getRGB()); + } finally { + org.lwjgl.opengl.GL11.glColorMask(true, true, true, true); + StencilUtil.uninitStencilBuffer(); + } } private void updatePressAnims(float dt, float width, float height, boolean pressed) { diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/MiniMapComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/MiniMapComponent.java index 48bafb93..6e4c8bbb 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/MiniMapComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/MiniMapComponent.java @@ -32,12 +32,14 @@ public MiniMapComponent() { public void draw(float x, float y) { super.draw(x, y); + // Drawn raw rather than through drawRect (it is a texture, not a panel), so scale is applied + // to both the offset and the size here. Images.draw( new ResourceLocation("client/gui/minimapbg.png"), - x + width / 2 - 179 / 4f, - y + width / 2 - 179 / 4f, - 179f / 2f, - 178f / 2f, + x + (width / 2 - 179 / 4f) * scale, + y + (width / 2 - 179 / 4f) * scale, + 179f / 2f * scale, + 178f / 2f * scale, -1 ); @@ -53,7 +55,7 @@ public void draw(float x, float y) { GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); Minecraft.getMinecraft().entityRenderer.setupOverlayRendering(); float partialTicks = ((IMinecraft) Minecraft.getMinecraft()).arch$getTimer().renderPartialTicks; - InterfaceHandler.drawInterfaces(width, height, partialTicks); + InterfaceHandler.drawInterfaces(width * scale, height * scale, partialTicks); MinimapAnimation.tick(); GL11.glPopMatrix(); GuiScale.fixScale(); diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/PlayerDisplayComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/PlayerDisplayComponent.java index b4782e03..8dfe0991 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/PlayerDisplayComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/PlayerDisplayComponent.java @@ -29,32 +29,32 @@ public void draw(float x, float y) { for (Entity entity : mc.theWorld.loadedEntityList) { if (entity instanceof EntityPlayer && !entity.isInvisible()) { if (i > 10 || entity == mc.thePlayer) continue; - UFontRenderer s16 = FPSMaster.fontManager.s16; - String healthText = (int) (((EntityPlayer) entity).getHealth() * 10 / 10) + " hp"; - float hX = s16.getStringWidth(healthText); - float nX = s16.getStringWidth(entity.getDisplayName().getFormattedText()); - - Rects.rounded(Math.round(x), Math.round(y + i * 16), Math.round(10 + hX + nX), 14, new Color(0, 0, 0, 60)); - Rects.rounded( - Math.round(x), - Math.round(y + i * 16), - Math.round((10 + hX + nX) * ((EntityPlayer) entity).getHealth() / ((EntityPlayer) entity).getMaxHealth()), - 14, - new Color(0, 0, 0, 60) - ); - - if (width < 10 + hX + nX) { - width = 10 + hX + nX; + EntityPlayer player = (EntityPlayer) entity; + String name = entity.getDisplayName().getFormattedText(); + String healthText = (int) (player.getHealth() * 10 / 10) + " hp"; + // Logical (unscaled) widths — drawRect/drawString apply scale themselves. + float hX = getStringWidth(16, healthText); + float nX = getStringWidth(16, name); + float rowWidth = 10 + hX + nX; + // Row offsets are positions, so they scale here; sizes scale inside drawRect. + float rowY = y + i * 16 * scale; + + drawRect(x, rowY, rowWidth, 14, mod.backgroundColor.getColor()); + drawRect(x, rowY, rowWidth * player.getHealth() / player.getMaxHealth(), 14, + mod.backgroundColor.getColor()); + + if (width < rowWidth) { + width = rowWidth; } - float health = ((EntityPlayer) entity).getHealth(); - float maxHealth = ((EntityPlayer) entity).getMaxHealth(); + float health = player.getHealth(); + float maxHealth = player.getMaxHealth(); Color color = health >= maxHealth * 0.8f ? new Color(50, 255, 55) : health > maxHealth * 0.5f ? new Color(255, 255, 55) : new Color(255, 55, 55); - s16.drawString(entity.getDisplayName().getFormattedText(), x + 2, y + i * 16 + 2, -1); - s16.drawString(healthText, x + 8 + nX, y + i * 16 + 2, color.getRGB()); + drawString(16, name, x + 2 * scale, rowY + 2 * scale, -1); + drawString(16, healthText, x + (8 + nX) * scale, rowY + 2 * scale, color.getRGB()); i++; } diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/PotionDisplayComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/PotionDisplayComponent.java index 2b7eaa1d..c21f8915 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/PotionDisplayComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/PotionDisplayComponent.java @@ -75,13 +75,18 @@ private void drawAnimatedPotion(PotionEffect effect, String title, String durati if (visible <= 0.01f) { return; } + // try/finally: if drawPotion throws, an un-popped scissor would clip the entire rest of the + // frame — including the whole game view — to this potion row's rectangle. beginScissor(x, y, scaledWidth * visible, scaledHeight); GlStateManager.pushMatrix(); - GlStateManager.translate((1f - visible) * -6f * scale, 0f, 0f); - drawPotion(effect, title, duration, x, y, width); - drawAccent(effect, x, y); - GlStateManager.popMatrix(); - endScissor(); + try { + GlStateManager.translate((1f - visible) * -6f * scale, 0f, 0f); + drawPotion(effect, title, duration, x, y, width); + drawAccent(effect, x, y); + } finally { + GlStateManager.popMatrix(); + endScissor(); + } } private void drawPotion(PotionEffect effect, String title, String duration, float x, float y, float width) { diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java index a53a0f5d..6ffe5df1 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java @@ -1,7 +1,5 @@ package top.fpsmaster.ui.custom.impl; -import top.fpsmaster.utils.render.draw.Rects; - import top.fpsmaster.features.impl.interfaces.Scoreboard; import top.fpsmaster.ui.custom.Component; import net.minecraft.client.Minecraft; @@ -15,6 +13,9 @@ public class ScoreboardComponent extends Component { + /** Matches the vanilla font metrics this HUD was originally laid out against. */ + private static final int FONT_SIZE = 16; + public ScoreboardComponent() { super(Scoreboard.class); allowScale = true; @@ -48,25 +49,29 @@ public void draw(float x, float y) { filtered = filtered.subList(filtered.size() - 15, filtered.size()); } - int maxWidth = mc.fontRendererObj.getStringWidth(objective.getDisplayName()); + // Measure through the base class so BetterFont is honoured; these are logical widths, and + // drawRect/drawString apply scale themselves. + String title = objective.getDisplayName(); + float maxWidth = getStringWidth(FONT_SIZE, title); boolean showScore = Scoreboard.score.getValue(); List lines = new ArrayList<>(); for (Score score : filtered) { String name = ScorePlayerTeam.formatPlayerName(mcScoreboard.getPlayersTeam(score.getPlayerName()), score.getPlayerName()); String line = showScore ? name + ": " + score.getScorePoints() : name; lines.add(line); - maxWidth = Math.max(maxWidth, mc.fontRendererObj.getStringWidth(line)); + maxWidth = Math.max(maxWidth, getStringWidth(FONT_SIZE, line)); } - int lineHeight = mc.fontRendererObj.FONT_HEIGHT + 1; + float lineHeight = mc.fontRendererObj.FONT_HEIGHT + 1; width = maxWidth + 6; height = (lines.size() + 1) * lineHeight + 4; - Rects.fill(x, y, width, height, mod.backgroundColor.getColor()); - mc.fontRendererObj.drawStringWithShadow(objective.getDisplayName(), x + 3, y + 2, 0xFFFFFF); - float offsetY = y + 2 + lineHeight; + drawRect(x, y, width, height, mod.backgroundColor.getColor()); + drawString(FONT_SIZE, title, x + 3 * scale, y + 2 * scale, 0xFFFFFF); + // Row offsets are positions, so they scale here. + float offsetY = y + (2 + lineHeight) * scale; for (String line : lines) { - mc.fontRendererObj.drawStringWithShadow(line, x + 3, offsetY, 0xFFFFFF); - offsetY += lineHeight; + drawString(FONT_SIZE, line, x + 3 * scale, offsetY, 0xFFFFFF); + offsetY += lineHeight * scale; } } } diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/TargetHUDComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/TargetHUDComponent.java index 8a1c1e0f..33360b9d 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/TargetHUDComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/TargetHUDComponent.java @@ -52,7 +52,7 @@ public void draw(float x, float y) { healthPer = (float) AnimMath.base(healthPer, (health / maxHealth), 0.1); if (TargetDisplay.targetHUD.getMode() == 0) { - width = (30 + FPSMaster.fontManager.s16.getStringWidth(name)); + width = (30 + getStringWidth(16, name)); height = 30f; // Set color based on health percentage @@ -66,13 +66,16 @@ public void draw(float x, float y) { // Draw elements if animation is greater than 1 if (animation > 0.05) { - Rects.rounded(Math.round(x), Math.round(y), Math.round(width), Math.round(height), new Color(0, 0, 0, (int) animation * 80)); - Rects.rounded(Math.round(x), Math.round(y), Math.round(healthPer * width), Math.round(height), colorAnimation.getColor()); - FPSMaster.fontManager.s16.drawStringWithShadow(name, x + 27, y + 5, -1); - Images.playerHead((AbstractClientPlayer) target1, x + 5, y + 5, 20, 20); + // Panel goes through drawRect so it honours the Background toggle and scale; the health + // bar and head stay raw because they are content, not decoration, and must not vanish + // when the user turns the background off. Their sizes are scaled explicitly. + drawRect(x, y, width, height, new Color(0, 0, 0, (int) animation * 80)); + Rects.rounded(Math.round(x), Math.round(y), Math.round(healthPer * width * scale), Math.round(height * scale), colorAnimation.getColor()); + drawString(16, name, x + 27 * scale, y + 5 * scale, -1); + Images.playerHead((AbstractClientPlayer) target1, x + 5 * scale, y + 5 * scale, Math.round(20 * scale), Math.round(20 * scale)); } } else if (TargetDisplay.targetHUD.getValue() == 1) { - width = (50 + FPSMaster.fontManager.s16.getStringWidth(name)); + width = (50 + getStringWidth(16, name)); height = 40f; // Set color based on health percentage @@ -86,10 +89,10 @@ public void draw(float x, float y) { // Draw elements if animation is greater than 1 if (animation > 0.05) { - Rects.roundedImage(Math.round(x), Math.round(y), Math.round(width), Math.round(height), 8, new Color(0, 0, 0, (int) (animation * 120))); - Rects.roundedImage(Math.round(x + 10), Math.round(y + 30), Math.round(healthPer * (width - 20)), 4, 2, colorAnimation.getColor()); - FPSMaster.fontManager.s18.drawStringWithShadow(name, x + 24, y + 8, new Color(255, 255, 255, (int) (animation * 255)).getRGB()); - Images.playerHead((AbstractClientPlayer) target1, x + 10, y + 8, 12, 12); + drawRect(x, y, width, height, new Color(0, 0, 0, (int) (animation * 120))); + Rects.roundedImage(Math.round(x + 10 * scale), Math.round(y + 30 * scale), Math.round(healthPer * (width - 20) * scale), Math.round(4 * scale), 2, colorAnimation.getColor()); + drawString(18, name, x + 24 * scale, y + 8 * scale, new Color(255, 255, 255, (int) (animation * 255)).getRGB()); + Images.playerHead((AbstractClientPlayer) target1, x + 10 * scale, y + 8 * scale, Math.round(12 * scale), Math.round(12 * scale)); } } } diff --git a/src/main/java/top/fpsmaster/ui/minimap/interfaces/InterfaceHandler.java b/src/main/java/top/fpsmaster/ui/minimap/interfaces/InterfaceHandler.java index 1184405b..f99c6519 100644 --- a/src/main/java/top/fpsmaster/ui/minimap/interfaces/InterfaceHandler.java +++ b/src/main/java/top/fpsmaster/ui/minimap/interfaces/InterfaceHandler.java @@ -128,6 +128,11 @@ public void drawInterface(final int width, final int height, final float partial GlStateManager.blendFunc(770, 771); Component component = FPSMaster.componentsManager.getComponent(MiniMap.class); + if (component == null) { + // getComponent matches on the module's exact class; when the MiniMap module is not + // registered the component falls back to a plain InterfaceModule and never matches. + return; + } float[] pos = component.getRealPosition(); mc.ingameGUI.drawTexturedModalRect(((int) pos[0]), ((int) pos[1]), 0, 0, (int) ((minimapWidth / 2f + 1) / sizeFix), (int) ((minimapWidth / 2f + 1) / sizeFix)); super.drawInterface(width, height, partial);