Refact module to feature

This commit is contained in:
2026-03-08 12:50:17 +01:00
parent 709ad4fbf0
commit da6576e4ed
42 changed files with 361 additions and 356 deletions
@@ -0,0 +1,59 @@
package org.saturnclient.feature;
import org.saturnclient.common.ref.asset.IdentifierRef;
import org.saturnclient.config.manager.ConfigManager;
import org.saturnclient.config.manager.Property.NamedProperty;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Textures;
public abstract class Feature {
private final FeatureDetails details;
private final ConfigManager configManager;
public Feature(FeatureDetails details, NamedProperty<?>... props) {
this.details = details;
configManager = new ConfigManager(details.name);
for (NamedProperty<?> prop : props) {
configManager.property(prop.name, prop.prop);
}
}
public void render(RenderScope scope) {
}
public void tick() {
}
public abstract boolean isEnabled();
public final void setEnabled(boolean e) {
onEnabled(e);
FeatureManager.updateEnabledModules();
}
public abstract void onEnabled(boolean e);
public final String getName() {
return details.name;
}
public IdentifierRef getIconTexture() {
return Textures.getModIcon(details.namespace);
}
public ConfigManager getConfig() {
return configManager;
}
public String getDescription() {
return details.description;
}
public String[] getTags() {
return details.tags;
}
public String getVersion() {
return details.version;
}
}
@@ -0,0 +1,39 @@
package org.saturnclient.feature;
public class FeatureDetails {
public String name;
public String namespace;
public String description;
public String version;
public String[] tags = {};
public FeatureDetails(String name, String namespace) {
this.name = name;
this.namespace = namespace;
}
public FeatureDetails name(String name) {
this.name = name;
return this;
}
public FeatureDetails namespace(String namespace) {
this.namespace = namespace;
return this;
}
public FeatureDetails description(String description) {
this.description = description;
return this;
}
public FeatureDetails version(String version) {
this.version = version;
return this;
}
public FeatureDetails tags(String... tags) {
this.tags = tags;
return this;
}
}
@@ -0,0 +1,54 @@
package org.saturnclient.feature;
import java.util.Map;
import org.saturnclient.config.manager.Property;
import org.saturnclient.config.manager.Property.NamedProperty;
public class FeatureLayout {
public Property<Integer> x = Property.integer(0);
public Property<Integer> y = Property.integer(0);
public Property<Float> scale = Property.floatProp(1.0f);
public Property<Integer> bgColor = Property.color(0x00000000);
public Property<Integer> fgColor = Property.color(0xFFffffff);
public Property<Integer> radius = Property.integer(0);
public Property<Integer> font = Property.font(0);
public boolean renderBackground = true;
public int width = 0;
public int height = 0;
public NamedProperty<Map<String, Property<?>>> prop() {
return Property.namespace(Map.of(
"X", x,
"Y", y,
"Scale", scale,
"Background Color", bgColor,
"Foreground Color", fgColor,
"Corner Radius", radius,
"Font", font)).named("In-Game Display");
}
public FeatureLayout() {
}
public FeatureLayout(int width, int height) {
this.width = width;
this.height = height;
}
public FeatureLayout(int x, int y, int width, int height) {
this.x.value = x;
this.y.value = y;
this.width = width;
this.height = height;
}
public FeatureLayout(int x, int y, int width, int height, float scale) {
this.x.value = x;
this.y.value = y;
this.scale.value = scale;
this.width = width;
this.height = height;
}
}
@@ -0,0 +1,31 @@
package org.saturnclient.feature;
import java.util.ArrayList;
import java.util.List;
public class FeatureManager {
public static List<Feature> ENABLED_MODS = new ArrayList<>();
public static Feature[] MODS = {};
public static void init(ModuleProvider provider) {
MODS = provider.getMods();
updateEnabledModules();
}
public static synchronized void updateEnabledModules() {
ENABLED_MODS.clear();
for (Feature m : MODS) {
if (m.isEnabled()) {
ENABLED_MODS.add(m);
}
}
}
public static Feature[] getAllMods() {
return MODS;
}
public interface ModuleProvider {
Feature[] getMods();
}
}
@@ -0,0 +1,11 @@
package org.saturnclient.feature;
import org.saturnclient.ui.RenderScope;
public interface HudFeature {
public FeatureLayout getDimensions();
public void renderDummy(RenderScope scope);
public void renderHud(RenderScope scope);
}
@@ -0,0 +1,118 @@
package org.saturnclient.feature.features;
import org.saturnclient.common.ref.game.ItemStackRef;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.ArmorDisplayInterface;
import org.saturnclient.ui.RenderScope;
public class ArmorDisplay extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(40, 75);
private static Property<Boolean> useMainHand = Property.bool(true);
private final ArmorDisplayInterface minecraft;
public ArmorDisplay(ArmorDisplayInterface minecraft) {
super(new FeatureDetails("Armor Display", "armor")
.description("Displays armor durability")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
useMainHand.named("Use Main Hand"),
dimensions.prop());
this.minecraft = minecraft;
}
public void renderArmor(RenderScope scope,
ItemStackRef mainHand,
ItemStackRef helmet,
ItemStackRef chestplate,
ItemStackRef leggings,
ItemStackRef boots) {
int row = 0;
if (useMainHand.value && !mainHand.isEmpty()) {
scope.drawItem(mainHand, 0, 15 * row);
renderHealth(scope, row, mainHand.getMaxDamage(), mainHand.getDamage());
row++;
}
if (!helmet.isEmpty()) {
scope.drawItem(helmet, 0, 15 * row);
renderHealth(scope, row, helmet.getMaxDamage(), helmet.getDamage());
row++;
}
if (!chestplate.isEmpty()) {
scope.drawItem(chestplate, 0, 15 * row);
renderHealth(scope, row, chestplate.getMaxDamage(), chestplate.getDamage());
row++;
}
if (!leggings.isEmpty()) {
scope.drawItem(leggings, 0, 15 * row);
renderHealth(scope, row, leggings.getMaxDamage(), leggings.getDamage());
row++;
}
if (!boots.isEmpty()) {
scope.drawItem(boots, 0, 15 * row);
renderHealth(scope, row, boots.getMaxDamage(), boots.getDamage());
row++;
}
}
public void renderHealth(RenderScope scope, int i, int maxDamage, int damage) {
if (maxDamage > 0) {
scope.drawText(
0.5f,
"" + (maxDamage - damage),
17,
(15 * i) + 3,
dimensions.font.value,
dimensions.fgColor.value);
}
}
@Override
public void renderHud(RenderScope scope) {
renderArmor(scope,
minecraft.getMainHand(),
minecraft.getHelmet(),
minecraft.getChestplate(),
minecraft.getLeggings(),
minecraft.getBoots());
}
@Override
public void renderDummy(RenderScope scope) {
renderArmor(scope,
minecraft.getDummyMainHand(),
minecraft.getDummyHelmet(),
minecraft.getDummyChestplate(),
minecraft.getDummyLeggings(),
minecraft.getDummyBoots());
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,50 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.AutoSprintInterface;
public class AutoSprint extends Feature {
private static Property<Boolean> enabled = Property.bool(false);
private final AutoSprintInterface minecraft;
public AutoSprint(AutoSprintInterface minecraft) {
super(
new FeatureDetails("Auto Sprint", "sprint")
.description("Makes the player always sprint")
.tags("Movement")
.version("v0.1.0"),
enabled.named("Enabled"));
this.minecraft = minecraft;
}
@Override
public void tick() {
if (!minecraft.hasPlayer() || !minecraft.hasNetwork()) {
return;
}
if (minecraft.isForwardPressed()
&& !minecraft.isBackPressed()
&& !minecraft.isSneaking()
&& !minecraft.hasHorizontalCollision()
&& !minecraft.isUsingItem()) {
minecraft.setSprinting(true);
}
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,82 @@
package org.saturnclient.feature.features;
import java.time.format.DateTimeFormatter;
import java.time.LocalTime;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class Clock extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> format = Property.select(0, "24 hour", "12 hour");
private static Property<Boolean> seconds = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(60, Fonts.getHeight());
public Clock() {
super(new FeatureDetails("Clock", "clock")
.description("Displays the current real-world time")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
format.named("Format"),
seconds.named("Show seconds"),
dimensions.prop());
}
@Override
public void renderDummy(RenderScope scope) {
renderClock(scope);
}
@Override
public void renderHud(RenderScope scope) {
renderClock(scope);
}
public void renderClock(RenderScope scope) {
String text = "";
LocalTime currentTime = LocalTime.now();
switch (format.value) {
case 0:
if (seconds.value) {
text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
} else {
text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm"));
}
break;
case 1:
if (seconds.value) {
text = currentTime.format(DateTimeFormatter.ofPattern("hh:mm:ss a"));
} else {
text = currentTime.format(DateTimeFormatter.ofPattern("hh:mm a"));
}
break;
}
scope.drawText(text,
0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,98 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.CoordinatesInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class Coordinates extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> displayMethod = Property.select(
0,
"Flat",
"Flat annotated",
"Newline",
"Newline annotated");
private static FeatureLayout dimensions = new FeatureLayout(40, 18);
private final CoordinatesInterface minecraft;
public Coordinates(CoordinatesInterface minecraft) {
super(new FeatureDetails("Coordinates", "coords")
.description("Displays your current coordinates")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
displayMethod.named("Display Method"),
dimensions.prop());
this.minecraft = minecraft;
}
@Override
public void renderDummy(RenderScope scope) {
renderCoords(532, 69, 253, scope);
}
@Override
public void renderHud(RenderScope scope) {
int x = minecraft.getPlayerX();
int y = minecraft.getPlayerY();
int z = minecraft.getPlayerZ();
renderCoords(x, y, z, scope);
}
public void renderCoords(int x, int y, int z, RenderScope scope) {
String f = "%d %d %d";
switch (displayMethod.value) {
case 1:
f = "X: %d Y: %d Z: %d";
break;
case 2:
f = "%d\n%d\n%d";
break;
case 3:
f = "X: %d\nY: %d\nZ: %d";
break;
}
String text = String.format(f, x, y, z);
scope.drawText(
text,
0,
0,
dimensions.font.value,
dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,55 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.CrosshairInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Textures;
public class Crosshair extends Feature {
public static Property<Boolean> enabled = Property.bool(false);
private final CrosshairInterface minecraft;
public Crosshair(CrosshairInterface minecraft) {
super(
new FeatureDetails("Crosshair", "crosshair")
.description("Changes the crosshair if you're aiming at a entity")
.tags("Visuals", "Utility")
.version("v0.1.0"),
enabled.named("Enabled"));
this.minecraft = minecraft;
}
@Override
public void render(RenderScope scope) {
if (enabled.value && minecraft.isTargetingLivingEntity()) {
int scaledWidth = 15;
int scaledHeight = 15;
scope.drawTexture(
Textures.CROSSHAIR_RANGE,
(scope.getScaledWindowWidth() - scaledWidth) / 2,
(scope.getScaledWindowHeight() - scaledHeight) / 2,
0.0F,
0.0F,
scaledWidth,
scaledHeight);
}
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,71 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.DayCounterInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class DayCounter extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(40, 18);
private final DayCounterInterface minecraft;
long day = 0;
public DayCounter(DayCounterInterface minecraft) {
super(new FeatureDetails("DayCounter", "day")
.description("Displays the number of days that have passed")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
this.minecraft = minecraft;
}
@Override
public void renderDummy(RenderScope scope) {
renderDay(271, scope);
}
@Override
public void renderHud(RenderScope scope) {
day = minecraft.getDay();
renderDay(day, scope);
}
public void renderDay(long day, RenderScope scope) {
String text = "Day: " + day;
scope.drawText(
text,
0,
0,
dimensions.font.value,
dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,64 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.FpsInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class Fps extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(60, Fonts.getHeight());
private final FpsInterface minecraft;
public Fps(FpsInterface minecraft) {
super(new FeatureDetails("FPS Display", "fps")
.description("Displays current FPS")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
this.minecraft = minecraft;
}
@Override
public void renderHud(RenderScope scope) {
int fps = minecraft.getFps();
String text = fps + " FPS";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public void renderDummy(RenderScope scope) {
String text = "369 FPS";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
}
@@ -0,0 +1,88 @@
package org.saturnclient.feature.features;
import org.saturnclient.common.provider.GLFWProvider;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.FreelookInterface;
public class Freelook extends Feature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> toggle = Property.bool(true);
public static Property<Integer> freelookKey = Property.keybinding(GLFWProvider.GLFW_KEY_H);
public static boolean isFreeLooking = false;
private static boolean was1stPerson = false;
private final FreelookInterface minecraft;
public Freelook(FreelookInterface minecraft) {
super(
new FeatureDetails("Freelook", "freelook")
.description("Allows you to look around freely without moving your character")
.tags("Movement", "Camera")
.version("0.1.0"),
enabled.named("Enabled"),
toggle.named("Toggle freelook"),
freelookKey.named("Freelook Keybinding"));
this.minecraft = minecraft;
}
@Override
public void tick() {
if (toggle.value) {
if (freelookKey.wasKeyPressed()) {
if (isFreeLooking) {
stopFreelook();
} else {
startFreelook();
}
}
} else if (freelookKey.isKeyPressed()) {
if (!isFreeLooking) {
startFreelook();
}
} else if (isFreeLooking) {
stopFreelook();
}
}
private void startFreelook() {
isFreeLooking = true;
was1stPerson = minecraft.isFirstPerson();
minecraft.setThirdPersonBack();
}
private void stopFreelook() {
if (was1stPerson) {
minecraft.setFirstPerson();
}
isFreeLooking = false;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
}
@@ -0,0 +1,39 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
public class Fullbright extends Feature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> brightness = Property.integer(100);
public Fullbright() {
super(
new FeatureDetails("Fullbright", "fullbright")
.description("Allows you to see in the dark")
.tags("Camera")
.version("v0.1.0"),
enabled.named("Enabled"),
brightness.named("Brightness %"));
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
public static boolean shouldOverrideBrightness() {
return enabled.value;
}
public static float getBrightnessValue() {
return brightness.value / 10;
}
}
@@ -0,0 +1,96 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.HealthDisplayInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class HealthDisplay extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> displayMode = Property.select(0, "Health", "Hearts");
private static Property<Integer> decimals = Property.select(0, "0", "1", "2");
private static FeatureLayout dimensions = new FeatureLayout(40, 18);
private final HealthDisplayInterface minecraft;
public HealthDisplay(HealthDisplayInterface minecraft) {
super(new FeatureDetails("Health display", "health")
.description("Displays your current health")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
displayMode.named("Display mode"),
decimals.named("Decimals"),
dimensions.prop());
this.minecraft = minecraft;
}
@Override
public void renderDummy(RenderScope scope) {
renderHealth(10, scope);
}
@Override
public void renderHud(RenderScope scope) {
float health = minecraft.getPlayerHealth();
switch (displayMode.value) {
case 1:
health = health / 2;
break;
}
renderHealth(health, scope);
}
public void renderHealth(float health, RenderScope scope) {
String text = "";
switch (decimals.value) {
case 0:
text = String.format("%.0f ", health);
break;
case 1:
text = String.format("%.1f ", health);
break;
case 2:
text = String.format("%.2f ", health);
break;
}
scope.drawText(
text,
0,
0,
dimensions.font.value,
dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,156 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.KeystrokesInterface;
import org.saturnclient.ui.RenderScope;
public class Keystrokes extends Feature implements HudFeature {
private static final Property<Boolean> enabled = Property.bool(false);
private static final Property<Boolean> showMouse = Property.bool(true);
private static final Property<Boolean> showSpace = Property.bool(false);
private static final Property<Integer> clickBg = Property.color(0xFFCCCCCC);
private static final Property<Integer> clickFg = Property.color(0xFFFFFFFF);
private static final FeatureLayout dimensions = new FeatureLayout(78, 54);
private final KeystrokesInterface minecraft;
// Key states
private boolean w, a, s, d, lmb, rmb, space;
// Layout constants
private static final int KEY_SIZE = 24;
private static final int KEY_SPACING = 3;
private static final int MOUSE_WIDTH = 38;
private static final int MOUSE_HEIGHT = 24;
private static final int SPACE_HEIGHT = 19;
public Keystrokes(KeystrokesInterface minecraft) {
super(new FeatureDetails("Keystrokes", "keystrokes")
.description("Displays movement keystrokes")
.version("v0.2.0")
.tags("Utility"),
enabled.named("Enabled"),
showMouse.named("Show mouse clicks"),
showSpace.named("Show space"),
dimensions.prop(),
clickFg.named("Clicked fg"),
clickBg.named("Clicked bg"));
this.minecraft = minecraft;
dimensions.renderBackground = false;
}
@Override
public void tick() {
w = minecraft.isForwardPressed();
a = minecraft.isLeftPressed();
s = minecraft.isBackPressed();
d = minecraft.isRightPressed();
lmb = minecraft.isAttackPressed();
rmb = minecraft.isUsePressed();
space = minecraft.isJumpPressed();
updateHeight();
}
private void updateHeight() {
int height = KEY_SIZE * 2 + KEY_SPACING;
if (showSpace.value)
height += SPACE_HEIGHT + KEY_SPACING;
if (showMouse.value)
height += MOUSE_HEIGHT + KEY_SPACING;
dimensions.height = height;
}
@Override
public void renderDummy(RenderScope scope) {
render(scope, false);
}
@Override
public void renderHud(RenderScope scope) {
render(scope, true);
}
private void render(RenderScope scope, boolean live) {
int yOffset = 0;
// --- W ---
renderKey(scope, live && w, 'W', KEY_SIZE + KEY_SPACING, yOffset);
// --- A S D ---
yOffset += KEY_SIZE + KEY_SPACING;
renderKey(scope, live && a, 'A', 0, yOffset);
renderKey(scope, live && s, 'S', KEY_SIZE + KEY_SPACING, yOffset);
renderKey(scope, live && d, 'D', (KEY_SIZE + KEY_SPACING) * 2, yOffset);
yOffset += KEY_SIZE + KEY_SPACING;
// --- SPACE ---
if (showSpace.value) {
renderKeySpace(scope, live && space, 0, yOffset);
yOffset += SPACE_HEIGHT + KEY_SPACING;
}
// --- MOUSE ---
if (showMouse.value) {
renderKeyMouse(scope, live && lmb, "LMB", 0, yOffset);
renderKeyMouse(scope, live && rmb, "RMB", MOUSE_WIDTH + KEY_SPACING, yOffset);
}
}
private void renderKey(RenderScope scope, boolean pressed, char c, int x, int y) {
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, dimensions.radius.value, bg);
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, fg);
}
private void renderKeyMouse(RenderScope scope, boolean pressed, String text, int x, int y) {
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_HEIGHT, dimensions.radius.value, bg);
scope.drawText(0.6f, text, x + 9, y + 7, dimensions.font.value, fg);
}
private void renderKeySpace(RenderScope scope, boolean pressed, int x, int y) {
int bg = pressed ? clickBg.value : dimensions.bgColor.value;
int fg = pressed ? clickFg.value : dimensions.fgColor.value;
scope.drawRoundedRectangle(x, y, dimensions.width, SPACE_HEIGHT, dimensions.radius.value, bg);
int lineY = y + SPACE_HEIGHT / 2;
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, fg);
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,121 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.NametagsInterface;
public class Nametags extends Feature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> healthDisplay = Property.bool(false);
private static Property<Boolean> players = Property.bool(true);
private static Property<Boolean> hostile = Property.bool(false);
private static Property<Boolean> passive = Property.bool(false);
private static Property<Boolean> heartEmoji = Property.bool(true);
private static Property<Boolean> obfuscate = Property.bool(false);
private static Property<Integer> unit = Property.select(0, "Health", "Hearts");
private static Property<Integer> format = Property.select(0, "Value", "Value / Total", "%");
private static Property<Integer> nameColor = Property.select(15,
"Black", "Dark Blue", "Dark Green", "Dark Aqua", "Dark Red", "Dark Purple", "Gold", "Gray",
"Dark Gray", "Blue", "Green", "Aqua", "Red", "Light Purple", "Yellow", "White");
private static Property<Integer> healthColor = Property.select(12,
"Black", "Dark Blue", "Dark Green", "Dark Aqua", "Dark Red", "Dark Purple", "Gold", "Gray",
"Dark Gray", "Blue", "Green", "Aqua", "Red", "Light Purple", "Yellow", "White");
private static final String[] COLOR_CODES = {
"§0", "§1", "§2", "§3", "§4", "§5", "§6", "§7",
"§8", "§9", "§a", "§b", "§c", "§d", "§e", "§f"
};
public Nametags(NametagsInterface minecraft) {
super(new FeatureDetails("Nametags", "nametags")
.description("Allows you to modify entity nametags")
.tags("Camera", "Utility")
.version("0.1.0"),
enabled.named("Enabled"),
healthDisplay.named("Display health"),
players.named("Players"),
hostile.named("Hostile"),
passive.named("Passive"),
heartEmoji.named("Heart emoji"),
obfuscate.named("Obfuscate enemy names"),
unit.named("Unit"),
format.named("Format"),
nameColor.named("Name color"),
healthColor.named("Health color"));
}
@Override
public void tick() {
// Currently unused
}
public static String getHealthString(NametagsInterface.EntityState state, float health, float maxHealth) {
String ret = "";
switch (format.value) {
case 0: // value
ret = String.format("%.1f", health / (unit.value + 1));
break;
case 1: // value / total
ret = String.format("%.1f", health / (unit.value + 1)) + " / "
+ String.format("%.1f", maxHealth / (unit.value + 1));
break;
case 2: // %
ret = String.format("%.0f%%", (health / maxHealth) * 100);
break;
}
return ret;
}
public static String getNametagString(NametagsInterface.EntityState state) {
if (state.getCustomName() == null)
return null;
if (!healthDisplay.value)
return state.getCustomName(); // name only
float health = state.getHealth();
float maxHealth = state.getMaxHealth();
NametagsInterface.EntityType type = state.getEntityType();
if (type == NametagsInterface.EntityType.PASSIVE && !passive.value)
return null;
if (type == NametagsInterface.EntityType.HOSTILE && !hostile.value)
return null;
if (type == NametagsInterface.EntityType.PLAYER && !players.value)
return null;
if (type == NametagsInterface.EntityType.OTHER)
return null;
if (health < 0f || maxHealth < 0f || health > maxHealth)
return null;
String hpColor = COLOR_CODES[healthColor.value];
String color = COLOR_CODES[nameColor.value];
String heart = heartEmoji.value ? " ❤" : "";
String obf = obfuscate.value ? "§k" : "";
String reset = "§r";
return color + obf + state.getCustomName() + reset + " " + hpColor
+ getHealthString(state, health, maxHealth) + heart;
}
public static boolean shouldReplaceName() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
}
@@ -0,0 +1,39 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
public class NoFog extends Feature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> liquids = Property.bool(false);
public NoFog() {
super(
new FeatureDetails("No Fog", "nofog")
.description("Stops fog from rendering")
.tags("Camera")
.version("v0.1.0"),
enabled.named("Enabled"),
liquids.named("See through liquids"));
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
// static for the mixin
public static boolean isActive() {
return enabled.value;
}
public static boolean liquids() {
return liquids.value;
}
}
@@ -0,0 +1,59 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.PingInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class Ping extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(60, Fonts.getHeight());
private final PingInterface pingProvider;
public Ping(PingInterface pingProvider) {
super(new FeatureDetails("Ping Display", "ping")
.description("Displays ping")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
this.pingProvider = pingProvider;
}
@Override
public void renderHud(RenderScope scope) {
int ping = pingProvider.getPing();
String text = ping + " ms";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public void renderDummy(RenderScope scope) {
String text = "10 ms";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
}
@@ -0,0 +1,96 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.SpeedometerInterface;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class Speedometer extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> axis = Property.select(1, "Absolute", "X axis", "Y axis");
private static Property<Integer> unitText = Property.select(0, "None", "Blocks/s", "blocks/s", "b/s");
private static Property<Boolean> speedText = Property.bool(true);
private static FeatureLayout dimensions = new FeatureLayout(40, 18);
private final SpeedometerInterface provider;
private double speed = 0.;
public Speedometer(SpeedometerInterface provider) {
super(new FeatureDetails("Speedometer", "speed")
.description("Displays your current speed")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
axis.named("Speed type"),
unitText.named("Unit text"),
speedText.named("Show speed text"),
dimensions.prop());
this.provider = provider;
}
@Override
public void renderDummy(RenderScope scope) {
renderSpeed(2.71, scope);
}
@Override
public void renderHud(RenderScope scope) {
SpeedometerInterface.Velocity velocity = provider.getVelocity();
boolean onGround = provider.isOnGround();
double y = onGround ? 0.0 : velocity.y;
switch (axis.value) {
case 0: // Absolute
speed = Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(y, 2) + Math.pow(velocity.z, 2));
break;
case 1: // X axis
speed = Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(velocity.z, 2));
break;
case 2: // Y axis
speed = Math.abs(y);
break;
}
renderSpeed(speed * 20, scope); // blocks/tick to blocks/s
}
public void renderSpeed(double speed, RenderScope scope) {
String text = String.format("%.2f ", speed);
switch (unitText.value) {
case 1 -> text += "Blocks/s";
case 2 -> text += "blocks/s";
case 3 -> text += "b/s";
}
if (speedText.value) {
text = "Speed: " + text;
}
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,87 @@
package org.saturnclient.feature.features;
import java.util.List;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.feature.interfaces.StatusEffectsInterface;
import org.saturnclient.ui.RenderScope;
public class StatusEffects extends Feature implements HudFeature {
public static Property<Boolean> enabled = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(60, 0);
private final StatusEffectsInterface provider;
public StatusEffects(StatusEffectsInterface provider) {
super(new FeatureDetails("Status effects", "effect")
.description("Displays status effects")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
this.provider = provider;
}
private void renderEffects(RenderScope scope, List<StatusEffectsInterface.Effect> effects) {
int row = 0;
for (StatusEffectsInterface.Effect effect : effects) {
if (!effect.shouldShowIcon())
continue;
scope.drawSpriteStretched(effect.getIcon(), 0, 18 * row, 16, 16);
scope.drawText(0.5f, getDurationAsString(effect), 18, 18 * row + 3, dimensions.font.value, -1);
row++;
}
dimensions.height = 18 * row;
}
@Override
public void renderHud(RenderScope scope) {
renderEffects(scope, provider.getActiveEffects());
}
@Override
public void renderDummy(RenderScope scope) {
renderEffects(scope, provider.getDummyEffects());
}
private String getDurationAsString(StatusEffectsInterface.Effect effect) {
if (effect.isInfinite()) {
return effect.getInfiniteText();
}
int totalSeconds = effect.getDurationSeconds();
if (totalSeconds >= 3600) {
return totalSeconds / 3600 + "h";
} else if (totalSeconds >= 60) {
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
return String.format("%d:%02d", minutes, seconds);
} else {
return totalSeconds + "s";
}
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,100 @@
package org.saturnclient.feature.features;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
public class Tps extends Feature implements HudFeature {
private static Property<Boolean> enabled = Property.bool(false);
private static FeatureLayout dimensions = new FeatureLayout(60, Fonts.getHeight());
private static long lastWorldAge = -1;
private static long lastRealTime = -1;
private static double currentTps = 20.0;
private static final int SAMPLE_COUNT = 2;
private static final double[] samples = new double[SAMPLE_COUNT];
static {
java.util.Arrays.fill(samples, 20.0);
}
private static int sampleIndex = 0;
private static int samplesFilled = 0;
public Tps() {
super(new FeatureDetails("TPS Display", "tps")
.description("Displays the TPS")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
}
public static void onTimePacket(long worldAge) {
long now = System.currentTimeMillis();
if (lastWorldAge != -1 && lastRealTime != -1) {
long tickDelta = worldAge - lastWorldAge;
long timeDelta = now - lastRealTime;
if (timeDelta > 0 && tickDelta > 0) {
double sample = (tickDelta / (double) timeDelta) * 1000.0;
sample = Math.min(sample, 20.0);
samples[sampleIndex % SAMPLE_COUNT] = sample;
sampleIndex++;
samplesFilled = Math.min(samplesFilled + 1, SAMPLE_COUNT);
double sum = 0;
for (int i = 0; i < samplesFilled; i++)
sum += samples[i];
currentTps = sum / samplesFilled;
}
}
lastWorldAge = worldAge;
lastRealTime = now;
}
public static void reset() {
lastWorldAge = -1;
lastRealTime = -1;
currentTps = 20.0;
sampleIndex = 0;
samplesFilled = SAMPLE_COUNT;
for (int i = 0; i < SAMPLE_COUNT; i++)
samples[i] = 20.0;
}
@Override
public void renderHud(RenderScope scope) {
String text = String.format("%.1f TPS", currentTps);
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public void renderDummy(RenderScope scope) {
String text = "20.0 TPS";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
@Override
public FeatureLayout getDimensions() {
return dimensions;
}
}
@@ -0,0 +1,59 @@
package org.saturnclient.feature.features;
import org.saturnclient.common.provider.GLFWProvider;
import org.saturnclient.config.manager.Property;
import org.saturnclient.feature.Feature;
import org.saturnclient.feature.FeatureDetails;
public class Zoom extends Feature {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> toggle = Property.bool(false);
public static Property<Integer> zoomKey = Property.keybinding(GLFWProvider.GLFW_KEY_C);
public static Property<Float> zoomLevel = Property.floatProp(3.0f);
public static boolean isZooming = false;
public Zoom() {
super(
new FeatureDetails("Zoom", "zoom")
.description("Allows you to zoom in for a closer view")
.tags("Camera", "Utility")
.version("0.1.0"),
enabled.named("Enabled"),
toggle.named("Toggle zoom"),
zoomKey.named("Zoom Keybinding"),
zoomLevel.named("Zoom Level"));
}
@Override
public void tick() {
if (toggle.value) {
if (zoomKey.wasKeyPressed()) {
isZooming = !isZooming;
}
} else {
isZooming = zoomKey.isKeyPressed();
}
}
public static float getZoomLevel() {
return zoomLevel.value;
}
public static boolean shouldZoom() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
if (!e) {
isZooming = false;
}
}
@Override
public boolean isEnabled() {
return enabled.value;
}
}
@@ -0,0 +1,26 @@
package org.saturnclient.feature.interfaces;
import org.saturnclient.common.ref.game.ItemStackRef;
public interface ArmorDisplayInterface {
ItemStackRef getMainHand();
ItemStackRef getHelmet();
ItemStackRef getChestplate();
ItemStackRef getLeggings();
ItemStackRef getBoots();
ItemStackRef getDummyMainHand();
ItemStackRef getDummyHelmet();
ItemStackRef getDummyChestplate();
ItemStackRef getDummyLeggings();
ItemStackRef getDummyBoots();
}
@@ -0,0 +1,20 @@
package org.saturnclient.feature.interfaces;
public interface AutoSprintInterface {
boolean hasPlayer();
boolean hasNetwork();
boolean isForwardPressed();
boolean isBackPressed();
boolean isSneaking();
boolean hasHorizontalCollision();
boolean isUsingItem();
void setSprinting(boolean sprinting);
}
@@ -0,0 +1,11 @@
package org.saturnclient.feature.interfaces;
public interface CoordinatesInterface {
int getPlayerX();
int getPlayerY();
int getPlayerZ();
}
@@ -0,0 +1,7 @@
package org.saturnclient.feature.interfaces;
public interface CrosshairInterface {
boolean isTargetingLivingEntity();
}
@@ -0,0 +1,7 @@
package org.saturnclient.feature.interfaces;
public interface DayCounterInterface {
long getDay();
}
@@ -0,0 +1,7 @@
package org.saturnclient.feature.interfaces;
public interface FpsInterface {
int getFps();
}
@@ -0,0 +1,11 @@
package org.saturnclient.feature.interfaces;
public interface FreelookInterface {
boolean isFirstPerson();
void setFirstPerson();
void setThirdPersonBack();
}
@@ -0,0 +1,7 @@
package org.saturnclient.feature.interfaces;
public interface HealthDisplayInterface {
float getPlayerHealth();
}
@@ -0,0 +1,13 @@
package org.saturnclient.feature.interfaces;
public interface KeystrokesInterface {
boolean isForwardPressed();
boolean isBackPressed();
boolean isLeftPressed();
boolean isRightPressed();
boolean isJumpPressed();
boolean isAttackPressed();
boolean isUsePressed();
}
@@ -0,0 +1,23 @@
package org.saturnclient.feature.interfaces;
public interface NametagsInterface {
public static interface EntityState {
String getCustomName();
float getHealth();
float getMaxHealth();
EntityType getEntityType();
}
public static enum EntityType {
PLAYER,
PASSIVE,
HOSTILE,
OTHER
}
}
@@ -0,0 +1,8 @@
package org.saturnclient.feature.interfaces;
public interface PingInterface {
/** Returns current player ping in milliseconds */
int getPing();
}
@@ -0,0 +1,20 @@
package org.saturnclient.feature.interfaces;
public interface SpeedometerInterface {
/** Returns the current player or vehicle velocity in blocks per tick */
Velocity getVelocity();
/** Returns true if player or vehicle is on ground */
boolean isOnGround();
class Velocity {
public final double x, y, z;
public Velocity(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
}
@@ -0,0 +1,51 @@
package org.saturnclient.feature.interfaces;
import java.util.List;
import org.saturnclient.common.ref.asset.SpriteRef;
public interface StatusEffectsInterface {
/** Returns a list of currently active effects */
List<Effect> getActiveEffects();
/** Returns a dummy list of effects for dummy rendering */
List<Effect> getDummyEffects();
/** Represents a single effect for rendering purposes */
class Effect {
private final SpriteRef icon;
private final int durationSeconds;
private final boolean infinite;
private final boolean showIcon;
private final String infiniteText;
public Effect(SpriteRef icon, int durationSeconds, boolean infinite, boolean showIcon, String infiniteText) {
this.icon = icon;
this.durationSeconds = durationSeconds;
this.infinite = infinite;
this.showIcon = showIcon;
this.infiniteText = infiniteText;
}
public SpriteRef getIcon() {
return icon;
}
public int getDurationSeconds() {
return durationSeconds;
}
public boolean isInfinite() {
return infinite;
}
public boolean shouldShowIcon() {
return showIcon;
}
public String getInfiniteText() {
return infiniteText;
}
}
}