nametags fullbright and health display with keystrokes

This commit is contained in:
2026-03-07 00:25:11 +01:00
parent 9fee71154e
commit 439dc2653e
15 changed files with 250 additions and 99 deletions
@@ -14,6 +14,8 @@ public class FabricModuleProvider implements ModuleProvider {
new DayCounter(new DayCounterFabric()),
new Fps(new FpsFabric()),
new Freelook(new FreelookFabric()),
new HealthDisplay(new HealthDisplayFabric()),
new Keystrokes(new KeystrokesFabric()),
new Clock(),
};
@@ -0,0 +1,20 @@
package org.saturnclient.impl.modules;
import org.saturnclient.modules.interfaces.HealthDisplayInterface;
import net.minecraft.client.MinecraftClient;
public class HealthDisplayFabric implements HealthDisplayInterface {
private final MinecraftClient mc = MinecraftClient.getInstance();
@Override
public float getPlayerHealth() {
if (mc.player == null) {
return 0f;
}
return mc.player.getHealth();
}
}
@@ -0,0 +1,45 @@
package org.saturnclient.impl.modules;
import org.saturnclient.modules.interfaces.KeystrokesInterface;
import net.minecraft.client.MinecraftClient;
public class KeystrokesFabric implements KeystrokesInterface {
private final MinecraftClient mc = MinecraftClient.getInstance();
@Override
public boolean isForwardPressed() {
return mc.options.forwardKey.isPressed();
}
@Override
public boolean isBackPressed() {
return mc.options.backKey.isPressed();
}
@Override
public boolean isLeftPressed() {
return mc.options.leftKey.isPressed();
}
@Override
public boolean isRightPressed() {
return mc.options.rightKey.isPressed();
}
@Override
public boolean isJumpPressed() {
return mc.options.jumpKey.isPressed();
}
@Override
public boolean isAttackPressed() {
return mc.options.attackKey.isPressed();
}
@Override
public boolean isUsePressed() {
return mc.options.useKey.isPressed();
}
}
@@ -0,0 +1,49 @@
package org.saturnclient.impl.modules;
import org.saturnclient.modules.interfaces.NametagsInterface;
import org.saturnclient.modules.mods.utils.HealthRenderState;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.text.Text;
public class NametagsFabric implements NametagsInterface {
public static class EntityStateImpl implements EntityState {
private final LivingEntityRenderState state;
public EntityStateImpl(LivingEntityRenderState state) {
this.state = state;
}
@Override
public String getCustomName() {
Text name = state.customName;
return name != null ? name.getString() : null;
}
@Override
public float getHealth() {
if (!(state instanceof HealthRenderState hrs))
return 0.0f;
return hrs.saturn$getHealth();
}
@Override
public float getMaxHealth() {
if (!(state instanceof HealthRenderState hrs))
return 0.0f;
return hrs.saturn$getMaxHealth();
}
@Override
public EntityType getEntityType() {
if (!(state instanceof HealthRenderState hrs))
return null;
return hrs.saturn$getEntityType();
}
}
}
@@ -1,39 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.config.manager.Property;
public class Fullbright extends Module {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> brightness = Property.integer(100);
public Fullbright() {
super(
new ModuleDetails("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;
}
}
@@ -1,84 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.config.manager.Property;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
import net.minecraft.util.math.Vec3d;
public class HealthDisplay extends Module implements HudMod {
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 ModDimensions dimensions = new ModDimensions(40, 18);
double speed = 0.;
Vec3d velocity;
double y = 0.;
public HealthDisplay() {
super(new ModuleDetails("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());
}
@Override
public void renderDummy(RenderScope scope) {
renderHealth(10, scope);
}
@Override
public void renderHud(RenderScope scope) {
float health = SaturnClient.client.player.getHealth();
switch (displayMode.value) {
case 1:
health = health / 2;
}
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 ModDimensions getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,152 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.config.manager.Property;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.modules.ModuleDetails;
public class Keystrokes extends Module implements HudMod {
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 ModDimensions dimensions = new ModDimensions(78, 54);
// 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() {
super(new ModuleDetails("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"));
dimensions.renderBackground = false;
}
@Override
public void tick() {
w = SaturnClient.client.options.forwardKey.isPressed();
a = SaturnClient.client.options.leftKey.isPressed();
s = SaturnClient.client.options.backKey.isPressed();
d = SaturnClient.client.options.rightKey.isPressed();
lmb = SaturnClient.client.options.attackKey.isPressed();
rmb = SaturnClient.client.options.useKey.isPressed();
space = SaturnClient.client.options.jumpKey.isPressed();
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 ModDimensions getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,156 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.modules.mods.utils.HealthRenderState;
import org.saturnclient.config.manager.Property;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
// todo: rendering modes, custom names
public class Nametags extends Module {
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() {
super(
new ModuleDetails("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() {
}
public static String getHealthString(LivingEntityRenderState state, HealthRenderState hrs, 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(LivingEntityRenderState state) {
if (state.customName == null)
return null;
if (!healthDisplay.value)
return state.customName.getString(); // name only
if (!(state instanceof HealthRenderState hrs))
return null;
float health = hrs.saturn$getHealth();
float maxHealth = hrs.saturn$getMaxHealth();
HealthRenderState.EntityType type = hrs.saturn$getEntityType();
if (type == HealthRenderState.EntityType.PASSIVE && !passive.value)
return null;
if (type == HealthRenderState.EntityType.HOSTILE && !hostile.value)
return null;
if (type == HealthRenderState.EntityType.PLAYER && !players.value)
return null;
if (type == HealthRenderState.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";
String ret = color + obf + state.customName.getString() + reset + " " + hpColor
+ getHealthString(state, hrs, health, maxHealth) + heart;
return ret;
}
public static boolean shouldReplaceName() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
}
@@ -2,7 +2,7 @@ package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import org.saturnclient.modules.mods.utils.HealthRenderState;
import org.saturnclient.modules.interfaces.NametagsInterface.EntityType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@@ -11,7 +11,7 @@ public class LivingEntityRenderStateMixin implements org.saturnclient.modules.mo
@Unique private float saturn$health = 0f;
@Unique private float saturn$maxHealth = 1f;
@Unique private HealthRenderState.EntityType saturn$entityType = HealthRenderState.EntityType.OTHER;
@Unique private EntityType saturn$entityType = EntityType.OTHER;
public float saturn$getHealth() { return saturn$health; }
public float saturn$getMaxHealth() { return saturn$maxHealth; }
@@ -20,7 +20,7 @@ public class LivingEntityRenderStateMixin implements org.saturnclient.modules.mo
this.saturn$maxHealth = maxHealth;
}
public HealthRenderState.EntityType saturn$getEntityType() { return saturn$entityType; }
public void saturn$setEntityType(HealthRenderState.EntityType type) { this.saturn$entityType = type; }
public EntityType saturn$getEntityType() { return saturn$entityType; }
public void saturn$setEntityType(EntityType type) { this.saturn$entityType = type; }
}
@@ -2,6 +2,8 @@ package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import org.saturnclient.modules.interfaces.NametagsInterface.EntityType;
import org.saturnclient.modules.mods.utils.HealthRenderState;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.HostileEntity;
@@ -17,18 +19,19 @@ public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extend
@Inject(method = "updateRenderState", at = @At("TAIL"))
private void saturn$captureHealth(T entity, S state, float tickDelta, CallbackInfo ci) {
if (!(state instanceof HealthRenderState hrs)) return;
if (!(state instanceof HealthRenderState hrs))
return;
hrs.saturn$setHealth(entity.getHealth(), entity.getMaxHealth());
if (entity instanceof PlayerEntity) {
hrs.saturn$setEntityType(HealthRenderState.EntityType.PLAYER);
hrs.saturn$setEntityType(EntityType.PLAYER);
} else if (entity instanceof HostileEntity) {
hrs.saturn$setEntityType(HealthRenderState.EntityType.HOSTILE);
hrs.saturn$setEntityType(EntityType.HOSTILE);
} else if (entity instanceof PassiveEntity) {
hrs.saturn$setEntityType(HealthRenderState.EntityType.PASSIVE);
hrs.saturn$setEntityType(EntityType.PASSIVE);
} else {
hrs.saturn$setEntityType(HealthRenderState.EntityType.OTHER);
hrs.saturn$setEntityType(EntityType.OTHER);
}
}
}
@@ -6,6 +6,8 @@ import net.minecraft.client.render.entity.state.EntityRenderState;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.saturnclient.impl.modules.NametagsFabric;
import org.saturnclient.modules.mods.Nametags;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -34,7 +36,7 @@ public abstract class NametagsMixin {
if (!Nametags.shouldReplaceName()) return;
if (!(state instanceof LivingEntityRenderState living)) return;
String replacement = Nametags.getNametagString(living);
String replacement = Nametags.getNametagString(new NametagsFabric.EntityStateImpl(living));
if (replacement == null) return;
ci.cancel();
@@ -1,14 +1,15 @@
package org.saturnclient.modules.mods.utils;
import org.saturnclient.modules.interfaces.NametagsInterface.EntityType;
public interface HealthRenderState {
float saturn$getHealth();
float saturn$getMaxHealth();
void saturn$setHealth(float health, float maxHealth);
EntityType saturn$getEntityType();
void saturn$setEntityType(EntityType type);
enum EntityType {
PLAYER, HOSTILE, PASSIVE, OTHER
}
void saturn$setEntityType(EntityType type);
}