fix config loading, add arraylist module info

- fullbright no longer crashes during config load (mc.options is null
  while the game is still starting, which aborted loading every module
  registered after fullbright - including killaura's keybind)
- one broken module no longer aborts loading the rest
- fix "is a directory" IOException on every startup (commandmanager
  had no file name, so it resolved to the config folder itself)
- arraylist: alignment/sort options, module info shown on the opposite
  side of the row, hides hud/disabled modules behind settings
- killaura: show target name as module info
This commit is contained in:
froslanz
2026-10-22 08:57:57 -04:00
parent 1c3e2ff39f
commit 4ff4902981
6 changed files with 169 additions and 9 deletions
@@ -19,21 +19,25 @@ public class KillAuraModule extends Module {
private final Setting<Boolean> onlyWeapon = bool("OnlyWeapon", false);
private final Setting<Boolean> 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;
}
}
@@ -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<Align> alignment = mode("Alignment", Align.RIGHT);
public final Setting<Sort> sort = mode("Sort", Sort.LENGTH);
public final Setting<Boolean> disabled = bool("Disabled", false);
public final Setting<Boolean> 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<Module> 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<Module> 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
}
}
@@ -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();
}
@@ -111,4 +111,9 @@ public class CommandManager extends Feature implements Jsonable {
setCommandPrefix(object.get("prefix").getAsString());
}
}
@Override
public String getFileName() {
return "commands.json";
}
}
@@ -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);
@@ -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<Module> 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);
}
}
}