diff --git a/src/main/java/cc/fros/features/modules/combat/KillAuraModule.java b/src/main/java/cc/fros/features/modules/combat/KillAuraModule.java index e0adce4..cd89a95 100644 --- a/src/main/java/cc/fros/features/modules/combat/KillAuraModule.java +++ b/src/main/java/cc/fros/features/modules/combat/KillAuraModule.java @@ -19,21 +19,25 @@ public class KillAuraModule extends Module { private final Setting onlyWeapon = bool("OnlyWeapon", false); private final Setting sync = bool("MovementSync", false); + private String targetName; + public KillAuraModule() { super("KillAura", "Attacks nearby players.", Category.COMBAT); } @Subscribe public void onPreTick(TickEvent.Pre event) { + Player target = findPlayerTarget(); + this.targetName = target == null ? null : target.getScoreboardName(); + + if (target == null) + return; + if (isOnCooldown()) return; if (onlyWeapon.getValue() && !isHoldingWeapon()) return; - Player target = findPlayerTarget(); - if (target == null) - return; - Angles angles = Angles.entity(target); int priority = 0; @@ -74,4 +78,14 @@ public class KillAuraModule extends Module { } return target; } + + @Override + public void onDisable() { + targetName = null; + } + + @Override + public String getDisplayInfo() { + return targetName; + } } diff --git a/src/main/java/cc/fros/features/modules/hud/ArrayListHudModule.java b/src/main/java/cc/fros/features/modules/hud/ArrayListHudModule.java new file mode 100644 index 0000000..702329e --- /dev/null +++ b/src/main/java/cc/fros/features/modules/hud/ArrayListHudModule.java @@ -0,0 +1,115 @@ +package cc.fros.features.modules.hud; + +import cc.fros.Fros; +import cc.fros.event.impl.render.Render2DEvent; +import cc.fros.features.modules.client.HudModule; +import cc.fros.features.modules.Module; +import cc.fros.features.settings.Setting; + +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +public class ArrayListHudModule extends HudModule { + private static final int DISABLED_COLOR = 0xFF555555; + private static final int BRACKET_COLOR = 0xFFAAAAAA; + private static final int GAP = 4; + + public final Setting alignment = mode("Alignment", Align.RIGHT); + public final Setting sort = mode("Sort", Sort.LENGTH); + public final Setting disabled = bool("Disabled", false); + public final Setting hudModules = bool("HudModules", false); + + public ArrayListHudModule() { + super("ArrayList", "Display modules and their state", 100, 10); + } + + @Override + protected void render(Render2DEvent e) { + super.render(e); + + List shown = Fros.moduleManager.getModules().stream() + .filter(module -> !module.hidden) + .filter(module -> module.getCategory() != Module.Category.HUD || hudModules.getValue()) + .filter(module -> module.isEnabled() || disabled.getValue()) + .sorted(comparator()) + .toList(); + + if (shown.isEmpty()) return; + + float x = getX(); + float y = getY(); + int lineHeight = mc.font.lineHeight; + int color = Fros.colorManager.getColorAsInt(); + boolean right = alignment.getValue() == Align.RIGHT; + int widest = 0; + + for (Module module : shown) { + widest = Math.max(widest, width(module)); + } + + for (int i = 0; i < shown.size(); i++) { + Module module = shown.get(i); + String name = module.getDisplayName(); + String info = module.getDisplayInfo(); + boolean on = module.isEnabled(); + + int rowY = (int) (y + i * lineHeight); + int baseColor = on ? color : DISABLED_COLOR; + + // name sits on the alignment side + int nameX = (int) (right ? x + widest - mc.font.width(name) : x); + e.getContext().drawString(mc.font, name, nameX, rowY, baseColor); + + if (info != null) { + // info sits on the opposite side of the row + int blockWidth = infoWidth(info); + int infoX = (int) (right ? x : x + widest - blockWidth); + int bracketColor = on ? BRACKET_COLOR : DISABLED_COLOR; + + e.getContext().drawString(mc.font, " [", infoX, rowY, bracketColor); + infoX += mc.font.width(" ["); + e.getContext().drawString(mc.font, info, infoX, rowY, on ? -1 : DISABLED_COLOR); + infoX += mc.font.width(info); + e.getContext().drawString(mc.font, "]", infoX, rowY, bracketColor); + } + } + + setWidth(widest); + setHeight(lineHeight * shown.size()); + } + + private int width(Module module) { + int width = mc.font.width(module.getDisplayName()); + String info = module.getDisplayInfo(); + if (info != null) { + width += GAP + infoWidth(info); + } + return width; + } + + private int infoWidth(String info) { + return mc.font.width(" [") + mc.font.width(info) + mc.font.width("]"); + } + + private Comparator comparator() { + return switch (sort.getValue()) { + case LENGTH -> Comparator.comparingInt((Module module) -> + module.getDisplayName().length()).reversed(); + case ALPHABETICAL -> Comparator.comparing(module -> + module.getDisplayName().toLowerCase(Locale.ROOT)); + case NONE -> (first, second) -> 0; + }; + } + + public enum Align { + LEFT, + RIGHT + } + + public enum Sort { + LENGTH, + ALPHABETICAL, + NONE + } +} diff --git a/src/main/java/cc/fros/features/modules/render/FullbrightModule.java b/src/main/java/cc/fros/features/modules/render/FullbrightModule.java index 63dbbc7..61703fc 100644 --- a/src/main/java/cc/fros/features/modules/render/FullbrightModule.java +++ b/src/main/java/cc/fros/features/modules/render/FullbrightModule.java @@ -14,6 +14,7 @@ public class FullbrightModule extends Module { private Mode activeMode = Mode.GAMMA; private double previousGamma; + private boolean gammaCaptured; private boolean hadNightVision; public FullbrightModule() { @@ -52,16 +53,28 @@ public class FullbrightModule extends Module { private void prepare(Mode newMode) { if (newMode == Mode.GAMMA) { - previousGamma = mc.options.gamma().get(); + gammaCaptured = false; + // during config load mc.options is still null, so this can be a no-op; + // applyGamma() captures lazily on the first real tick instead + captureGamma(); return; } hadNightVision = !nullCheck() && mc.player.hasEffect(MobEffects.NIGHT_VISION); } + private void captureGamma() { + if (gammaCaptured || mc.options == null) return; + previousGamma = mc.options.gamma().get(); + gammaCaptured = true; + } + private void restore(Mode oldMode) { if (oldMode == Mode.GAMMA) { - // bypass OptionInstance#set, which rejects values outside [0.0, 1.0] - mc.options.gamma().value = previousGamma; + if (gammaCaptured && mc.options != null) { + // bypass OptionInstance#set, which rejects values outside [0.0, 1.0] + mc.options.gamma().value = previousGamma; + } + gammaCaptured = false; return; } if (!nullCheck() && !hadNightVision) { @@ -70,6 +83,8 @@ public class FullbrightModule extends Module { } private void applyGamma() { + if (mc.options == null) return; + captureGamma(); mc.options.gamma().value = gamma.getValue(); } diff --git a/src/main/java/cc/fros/manager/CommandManager.java b/src/main/java/cc/fros/manager/CommandManager.java index f376738..c265d08 100644 --- a/src/main/java/cc/fros/manager/CommandManager.java +++ b/src/main/java/cc/fros/manager/CommandManager.java @@ -111,4 +111,9 @@ public class CommandManager extends Feature implements Jsonable { setCommandPrefix(object.get("prefix").getAsString()); } } + + @Override + public String getFileName() { + return "commands.json"; + } } diff --git a/src/main/java/cc/fros/manager/ConfigManager.java b/src/main/java/cc/fros/manager/ConfigManager.java index 5edf3fb..109e19b 100644 --- a/src/main/java/cc/fros/manager/ConfigManager.java +++ b/src/main/java/cc/fros/manager/ConfigManager.java @@ -35,7 +35,9 @@ public class ConfigManager { mkdirs(); for (Jsonable jsonable : jsonables) { Path path = FROS_PATH.resolve(jsonable.getFileName()); - if (!Files.exists(path)) continue; + // isRegularFile, not exists: a Jsonable with no file name resolves to the + // config directory itself, and readString on a directory throws IOException + if (!Files.isRegularFile(path)) continue; try { String read = Files.readString(path); jsonable.fromJson(JsonParser.parseString(read)); @@ -51,6 +53,7 @@ public class ConfigManager { try { JsonElement json = jsonable.toJson(); Path path = FROS_PATH.resolve(jsonable.getFileName()); + if (Files.isDirectory(path)) continue; Files.writeString(path, GSON.toJson(json)); } catch (Throwable e) { LOGGER.error("Failed to write to file", e); diff --git a/src/main/java/cc/fros/manager/ModuleManager.java b/src/main/java/cc/fros/manager/ModuleManager.java index 5612e07..41d0209 100644 --- a/src/main/java/cc/fros/manager/ModuleManager.java +++ b/src/main/java/cc/fros/manager/ModuleManager.java @@ -17,6 +17,7 @@ import cc.fros.features.modules.client.NotificationsModule; import cc.fros.features.modules.combat.KillAuraModule; import cc.fros.features.modules.combat.CriticalsModule; import cc.fros.features.modules.combat.KeyPearlModule; +import cc.fros.features.modules.hud.ArrayListHudModule; import cc.fros.features.modules.hud.CoordinatesHudModule; import cc.fros.features.modules.hud.FpsHudModule; import cc.fros.features.modules.hud.PingHudModule; @@ -48,6 +49,7 @@ public class ModuleManager implements Jsonable, Util { private final List modules = new ArrayList<>(); public void init() { + register(new ArrayListHudModule()); register(new WatermarkHudModule()); register(new CoordinatesHudModule()); register(new FpsHudModule()); @@ -165,8 +167,14 @@ public class ModuleManager implements Jsonable, Util { @Override public void fromJson(JsonElement element) { + if (element == null || !element.isJsonObject()) return; for (Module module : getModules()) { - module.fromJson(element.getAsJsonObject().get(module.getName())); + // one broken module must never abort loading the rest + try { + module.fromJson(element.getAsJsonObject().get(module.getName())); + } catch (Throwable throwable) { + LOGGER.error("Failed to load module {}", module.getName(), throwable); + } } }