diff --git a/src/main/java/org/saturnclient/common/module/EntityModule.java b/src/main/java/org/saturnclient/common/module/EntityModule.java new file mode 100644 index 0000000..434b353 --- /dev/null +++ b/src/main/java/org/saturnclient/common/module/EntityModule.java @@ -0,0 +1,43 @@ +package org.saturnclient.common.module; + +/** + * EntityModule provides queries about entities in the current world. + * + * Designed to be thin so that feature logic is not polluted with + * engine-specific entity types. + */ +public interface EntityModule { + + // --------------------------------------------------------------- + // Crosshair / targeting + // --------------------------------------------------------------- + + /** Returns true when the player's crosshair is aimed at a living entity. */ + boolean isTargetingLivingEntity(); + + // --------------------------------------------------------------- + // Nametag entity state + // --------------------------------------------------------------- + + /** + * Represents the minimal observable state of an entity needed for + * nametag rendering. Implementations are created per-entity each + * frame; they are intentionally not cached. + */ + interface EntityState { + + /** The entity's display name, or {@code null} if it has none. */ + String getCustomName(); + + float getHealth(); + float getMaxHealth(); + EntityType getEntityType(); + } + + enum EntityType { + PLAYER, + HOSTILE, + PASSIVE, + OTHER + } +} diff --git a/src/main/java/org/saturnclient/common/module/NetworkModule.java b/src/main/java/org/saturnclient/common/module/NetworkModule.java new file mode 100644 index 0000000..55e50ff --- /dev/null +++ b/src/main/java/org/saturnclient/common/module/NetworkModule.java @@ -0,0 +1,18 @@ +package org.saturnclient.common.module; + +/** + * NetworkModule provides access to the current server connection. + * + * Methods return safe defaults (false / -1) when not connected. + */ +public interface NetworkModule { + + /** Returns true when a server connection is active. */ + boolean hasNetwork(); + + /** + * Returns the current ping to the server in milliseconds. + * Returns {@code -1} when unavailable. + */ + int getPing(); +} diff --git a/src/main/java/org/saturnclient/common/module/PlayerModule.java b/src/main/java/org/saturnclient/common/module/PlayerModule.java new file mode 100644 index 0000000..b6bcc58 --- /dev/null +++ b/src/main/java/org/saturnclient/common/module/PlayerModule.java @@ -0,0 +1,129 @@ +package org.saturnclient.common.module; + +import java.util.List; + +import org.saturnclient.common.ref.game.ItemStackRef; +import org.saturnclient.feature.features.StatusEffectsFeature; + +/** + * PlayerModule provides access to the state of the local player. + * + * All methods return safe defaults (false / 0 / empty) when no player + * is present so that features do not need to null-check everywhere. + */ +public interface PlayerModule { + + // --------------------------------------------------------------- + // Presence + // --------------------------------------------------------------- + + /** Returns true when a local player entity exists. */ + boolean hasPlayer(); + + // --------------------------------------------------------------- + // Position + // --------------------------------------------------------------- + + int getX(); + + int getY(); + + int getZ(); + + // --------------------------------------------------------------- + // Movement / input + // --------------------------------------------------------------- + + boolean isForwardPressed(); + + boolean isBackPressed(); + + boolean isLeftPressed(); + + boolean isRightPressed(); + + boolean isJumpPressed(); + + boolean isAttackPressed(); + + boolean isUsePressed(); + + boolean isSneaking(); + + boolean isUsingItem(); + + boolean hasHorizontalCollision(); + + boolean isOnGround(); + + void setSprinting(boolean sprinting); + + // --------------------------------------------------------------- + // Velocity + // --------------------------------------------------------------- + + Velocity getVelocity(); + + /** Immutable snapshot of the player's velocity components. */ + final class Velocity { + public final double x; + public final double y; + public final double z; + + public Velocity(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + } + + // --------------------------------------------------------------- + // Stats + // --------------------------------------------------------------- + + float getHealth(); + + // --------------------------------------------------------------- + // Effects / Potions + // --------------------------------------------------------------- + + /** + * Returns a list of the player’s currently active effects. + * Safe default: empty list if no player is present. + */ + List getActiveEffects(); + + /** + * Returns a list of dummy effects used in HUD editor / preview mode. + * Safe default: empty list. + */ + List getDummyEffects(); + + // --------------------------------------------------------------- + // Inventory / equipment + // --------------------------------------------------------------- + + ItemStackRef getMainHand(); + + ItemStackRef getHelmet(); + + ItemStackRef getChestplate(); + + ItemStackRef getLeggings(); + + ItemStackRef getBoots(); + + // --------------------------------------------------------------- + // Dummy / preview data (used in HUD editor) + // --------------------------------------------------------------- + + ItemStackRef getDummyMainHand(); + + ItemStackRef getDummyHelmet(); + + ItemStackRef getDummyChestplate(); + + ItemStackRef getDummyLeggings(); + + ItemStackRef getDummyBoots(); +} diff --git a/src/main/java/org/saturnclient/common/module/RenderModule.java b/src/main/java/org/saturnclient/common/module/RenderModule.java new file mode 100644 index 0000000..15633ca --- /dev/null +++ b/src/main/java/org/saturnclient/common/module/RenderModule.java @@ -0,0 +1,31 @@ +package org.saturnclient.common.module; + +/** + * RenderModule provides access to render-time information that does + * not belong to the player or world, such as the current window + * dimensions, camera perspective, and the frame's FPS counter. + */ +public interface RenderModule { + + // --------------------------------------------------------------- + // Window + // --------------------------------------------------------------- + + int getScaledWindowWidth(); + int getScaledWindowHeight(); + + // --------------------------------------------------------------- + // Performance + // --------------------------------------------------------------- + + int getFps(); + + // --------------------------------------------------------------- + // Camera perspective + // --------------------------------------------------------------- + + boolean isFirstPerson(); + + void setFirstPerson(); + void setThirdPersonBack(); +} diff --git a/src/main/java/org/saturnclient/common/module/WorldModule.java b/src/main/java/org/saturnclient/common/module/WorldModule.java new file mode 100644 index 0000000..c74a482 --- /dev/null +++ b/src/main/java/org/saturnclient/common/module/WorldModule.java @@ -0,0 +1,27 @@ +package org.saturnclient.common.module; + +/** + * WorldModule provides access to world-level information that is not + * tied to the local player (time of day, total age, etc.). + * + * All methods return safe defaults when no world is loaded. + */ +public interface WorldModule { + + /** Returns true when a world is currently loaded. */ + boolean hasWorld(); + + /** + * Returns the total world age in ticks. + * Used by {@link org.saturnclient.feature.TpsFeature} to compute TPS. + */ + long getWorldAge(); + + /** + * Returns the number of in-game days that have passed. + * Computed as {@code worldAge / 24000}. + */ + default long getDay() { + return getWorldAge() / 24_000L; + } +} diff --git a/src/main/java/org/saturnclient/common/provider/ModuleProvider.java b/src/main/java/org/saturnclient/common/provider/ModuleProvider.java index ab7f314..e5a529a 100644 --- a/src/main/java/org/saturnclient/common/provider/ModuleProvider.java +++ b/src/main/java/org/saturnclient/common/provider/ModuleProvider.java @@ -1,5 +1,35 @@ package org.saturnclient.common.provider; +import org.saturnclient.common.module.EntityModule; +import org.saturnclient.common.module.NetworkModule; +import org.saturnclient.common.module.PlayerModule; +import org.saturnclient.common.module.RenderModule; +import org.saturnclient.common.module.WorldModule; + +/** + * ModuleProvider is the single entry point for features to access + * all common engine abstractions. Implementations are supplied by + * the platform-specific client bootstrap (e.g. the Fabric/Forge mod). + * + * Features should receive a ModuleProvider in their constructor and + * store it; they must NOT cache individual sub-modules across ticks + * because the provider may swap implementations at runtime (e.g. on + * world change). + */ public interface ModuleProvider { - + + /** Access to the local player and movement state. */ + PlayerModule player(); + + /** Access to the game world (time, TPS packets, etc.). */ + WorldModule world(); + + /** Access to entity / nametag queries. */ + EntityModule entity(); + + /** Access to rendering utilities (window size, textures, etc.). */ + RenderModule render(); + + /** Access to the network / server connection. */ + NetworkModule network(); } diff --git a/src/main/java/org/saturnclient/feature/FeatureManager.java b/src/main/java/org/saturnclient/feature/FeatureManager.java index d806470..28d8bc6 100644 --- a/src/main/java/org/saturnclient/feature/FeatureManager.java +++ b/src/main/java/org/saturnclient/feature/FeatureManager.java @@ -3,12 +3,13 @@ package org.saturnclient.feature; import java.util.ArrayList; import java.util.List; +import org.saturnclient.feature.features.*; + public class FeatureManager { public static List ENABLED_MODS = new ArrayList<>(); public static Feature[] MODS = {}; - public static void init(ModuleProvider provider) { - MODS = provider.getMods(); + public static void init() { updateEnabledModules(); } @@ -20,12 +21,4 @@ public class FeatureManager { } } } - - public static Feature[] getAllMods() { - return MODS; - } - - public interface ModuleProvider { - Feature[] getMods(); - } } diff --git a/src/main/java/org/saturnclient/feature/features/ArmorDisplay.java b/src/main/java/org/saturnclient/feature/features/ArmorDisplay.java deleted file mode 100644 index 007a8d6..0000000 --- a/src/main/java/org/saturnclient/feature/features/ArmorDisplay.java +++ /dev/null @@ -1,118 +0,0 @@ -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 enabled = Property.bool(false); - private static FeatureLayout dimensions = new FeatureLayout(40, 75); - private static Property 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; - } -} diff --git a/src/main/java/org/saturnclient/feature/features/ArmorDisplayFeature.java b/src/main/java/org/saturnclient/feature/features/ArmorDisplayFeature.java new file mode 100644 index 0000000..bb625a1 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/ArmorDisplayFeature.java @@ -0,0 +1,126 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.PlayerModule; +import org.saturnclient.common.ref.game.ItemStackRef; +import org.saturnclient.config.manager.Property; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; +import org.saturnclient.ui.RenderScope; + +/** + * ArmorDisplayFeature renders the durability of the player's equipped + * armor pieces (and optionally the main-hand item) as a HUD overlay. + * + * Engine access is provided exclusively through {@link ModuleProvider}. + */ +public class ArmorDisplayFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final Property useMainHand = Property.bool(true); + private static final FeatureLayout layout = new FeatureLayout(40, 75); + + private final ModuleProvider modules; + + public ArmorDisplayFeature(ModuleProvider modules) { + super( + new FeatureDetails("Armor Display", "armor") + .description("Displays armor durability") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + useMainHand.named("Use Main Hand"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + PlayerModule player = modules.player(); + renderArmor(scope, + player.getMainHand(), + player.getHelmet(), + player.getChestplate(), + player.getLeggings(), + player.getBoots()); + } + + @Override + public void renderDummy(RenderScope scope) { + PlayerModule player = modules.player(); + renderArmor(scope, + player.getDummyMainHand(), + player.getDummyHelmet(), + player.getDummyChestplate(), + player.getDummyLeggings(), + player.getDummyBoots()); + } + + // --------------------------------------------------------------- + // Rendering helpers + // --------------------------------------------------------------- + + private void renderArmor(RenderScope scope, + ItemStackRef mainHand, + ItemStackRef helmet, + ItemStackRef chestplate, + ItemStackRef leggings, + ItemStackRef boots) { + + int row = 0; + + if (useMainHand.value && !mainHand.isEmpty()) { + row = renderSlot(scope, mainHand, row); + } + if (!helmet.isEmpty()) + row = renderSlot(scope, helmet, row); + if (!chestplate.isEmpty()) + row = renderSlot(scope, chestplate, row); + if (!leggings.isEmpty()) + row = renderSlot(scope, leggings, row); + if (!boots.isEmpty()) + row = renderSlot(scope, boots, row); + } + + /** + * Renders a single equipment slot and returns the next row index. + */ + private int renderSlot(RenderScope scope, ItemStackRef item, int row) { + scope.drawItem(item, 0, 15 * row); + + if (item.getMaxDamage() > 0) { + int remaining = item.getMaxDamage() - item.getDamage(); + scope.drawText(0.5f, String.valueOf(remaining), + 17, (15 * row) + 3, + layout.font.value, layout.fgColor.value); + } + + return row + 1; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/AutoSprint.java b/src/main/java/org/saturnclient/feature/features/AutoSprint.java deleted file mode 100644 index 57a709d..0000000 --- a/src/main/java/org/saturnclient/feature/features/AutoSprint.java +++ /dev/null @@ -1,50 +0,0 @@ -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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/AutoSprintFeature.java b/src/main/java/org/saturnclient/feature/features/AutoSprintFeature.java new file mode 100644 index 0000000..f513191 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/AutoSprintFeature.java @@ -0,0 +1,63 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.NetworkModule; +import org.saturnclient.common.module.PlayerModule; +import org.saturnclient.config.manager.Property; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; + +/** + * AutoSprintFeature automatically enables sprinting whenever the + * player moves forward and no sprint-blocking condition is active. + */ +public class AutoSprintFeature extends Feature { + + private static final Property enabled = Property.bool(false); + + private final ModuleProvider modules; + + public AutoSprintFeature(ModuleProvider modules) { + super( + new FeatureDetails("Auto Sprint", "sprint") + .description("Makes the player always sprint") + .tags("Movement") + .version("v0.2.0"), + enabled.named("Enabled")); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public void tick() { + PlayerModule player = modules.player(); + NetworkModule network = modules.network(); + + if (!player.hasPlayer() || !network.hasNetwork()) { + return; + } + + if (player.isForwardPressed() + && !player.isBackPressed() + && !player.isSneaking() + && !player.hasHorizontalCollision() + && !player.isUsingItem()) { + + player.setSprinting(true); + } + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Clock.java b/src/main/java/org/saturnclient/feature/features/Clock.java deleted file mode 100644 index 54327dc..0000000 --- a/src/main/java/org/saturnclient/feature/features/Clock.java +++ /dev/null @@ -1,82 +0,0 @@ -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 enabled = Property.bool(false); - private static Property format = Property.select(0, "24 hour", "12 hour"); - private static Property 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/ClockFeature.java b/src/main/java/org/saturnclient/feature/features/ClockFeature.java new file mode 100644 index 0000000..2d7f5d3 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/ClockFeature.java @@ -0,0 +1,92 @@ +package org.saturnclient.feature.features; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * ClockFeature displays the real-world time as a HUD element. + * No engine access is required; {@link LocalTime} is used directly. + */ +public class ClockFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final Property format = Property.select(0, "24 hour", "12 hour"); + private static final Property showSeconds = Property.bool(false); + private static final FeatureLayout layout = new FeatureLayout(60, Fonts.getHeight()); + + public ClockFeature() { + super( + new FeatureDetails("Clock", "clock") + .description("Displays the current real-world time") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + format.named("Format"), + showSeconds.named("Show seconds"), + layout.prop()); + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderClock(scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderClock(scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderClock(RenderScope scope) { + String text = buildTimeString(LocalTime.now()); + + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + layout.width = Fonts.getWidth(text, layout.font.value); + layout.height = 18 * text.split("\n").length; + } + + private String buildTimeString(LocalTime time) { + return switch (format.value) { + case 1 -> time.format(showSeconds.value + ? DateTimeFormatter.ofPattern("hh:mm:ss a") + : DateTimeFormatter.ofPattern("hh:mm a")); + default -> time.format(showSeconds.value + ? DateTimeFormatter.ofPattern("HH:mm:ss") + : DateTimeFormatter.ofPattern("HH:mm")); + }; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Coordinates.java b/src/main/java/org/saturnclient/feature/features/Coordinates.java deleted file mode 100644 index e1fb418..0000000 --- a/src/main/java/org/saturnclient/feature/features/Coordinates.java +++ /dev/null @@ -1,98 +0,0 @@ -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 enabled = Property.bool(false); - - private static Property 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/CoordinatesFeature.java b/src/main/java/org/saturnclient/feature/features/CoordinatesFeature.java new file mode 100644 index 0000000..ef7ea76 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/CoordinatesFeature.java @@ -0,0 +1,96 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * CoordinatesFeature displays the player's current XYZ position as a + * HUD element with multiple configurable display formats. + */ +public class CoordinatesFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final Property displayMethod = Property.select(0, + "Flat", + "Flat annotated", + "Newline", + "Newline annotated"); + private static final FeatureLayout layout = new FeatureLayout(40, 18); + + private final ModuleProvider modules; + + public CoordinatesFeature(ModuleProvider modules) { + super( + new FeatureDetails("Coordinates", "coords") + .description("Displays your current coordinates") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + displayMethod.named("Display Method"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderCoords( + modules.player().getX(), + modules.player().getY(), + modules.player().getZ(), + scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderCoords(532, 69, 253, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderCoords(int x, int y, int z, RenderScope scope) { + String fmt = switch (displayMethod.value) { + case 1 -> "X: %d Y: %d Z: %d"; + case 2 -> "%d\n%d\n%d"; + case 3 -> "X: %d\nY: %d\nZ: %d"; + default -> "%d %d %d"; + }; + + String text = String.format(fmt, x, y, z); + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + + layout.width = Fonts.getWidth(text, layout.font.value); + layout.height = 18 * text.split("\n").length; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Crosshair.java b/src/main/java/org/saturnclient/feature/features/Crosshair.java deleted file mode 100644 index 7f5fbf9..0000000 --- a/src/main/java/org/saturnclient/feature/features/Crosshair.java +++ /dev/null @@ -1,55 +0,0 @@ -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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/CrosshairFeature.java b/src/main/java/org/saturnclient/feature/features/CrosshairFeature.java new file mode 100644 index 0000000..e0133a4 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/CrosshairFeature.java @@ -0,0 +1,64 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.RenderModule; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Textures; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; + +/** + * CrosshairFeature renders an alternative crosshair texture when the + * player is aiming at a living entity. + */ +public class CrosshairFeature extends Feature { + + public static final Property enabled = Property.bool(false); + + private static final int CROSSHAIR_SIZE = 15; + + private final ModuleProvider modules; + + public CrosshairFeature(ModuleProvider modules) { + super( + new FeatureDetails("Crosshair", "crosshair") + .description("Changes the crosshair when aiming at an entity") + .tags("Visuals", "Utility") + .version("v0.2.0"), + enabled.named("Enabled")); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public void render(RenderScope scope) { + if (!enabled.value || !modules.entity().isTargetingLivingEntity()) { + return; + } + + RenderModule render = modules.render(); + int x = (render.getScaledWindowWidth() - CROSSHAIR_SIZE) / 2; + int y = (render.getScaledWindowHeight() - CROSSHAIR_SIZE) / 2; + + scope.drawTexture( + Textures.CROSSHAIR_RANGE, + x, y, + 0.0F, 0.0F, + CROSSHAIR_SIZE, CROSSHAIR_SIZE); + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/DayCounter.java b/src/main/java/org/saturnclient/feature/features/DayCounter.java deleted file mode 100644 index 19985ed..0000000 --- a/src/main/java/org/saturnclient/feature/features/DayCounter.java +++ /dev/null @@ -1,71 +0,0 @@ -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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/DayCounterFeature.java b/src/main/java/org/saturnclient/feature/features/DayCounterFeature.java new file mode 100644 index 0000000..ef997a8 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/DayCounterFeature.java @@ -0,0 +1,79 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * DayCounterFeature shows how many in-game days have elapsed. + * Day count is derived from the world age via {@link WorldModule}. + */ +public class DayCounterFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final FeatureLayout layout = new FeatureLayout(40, 18); + + private final ModuleProvider modules; + + public DayCounterFeature(ModuleProvider modules) { + super( + new FeatureDetails("Day Counter", "day") + .description("Displays the number of in-game days elapsed") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderDay(modules.world().getDay(), scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderDay(271L, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderDay(long day, RenderScope scope) { + String text = "Day: " + day; + + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + layout.width = Fonts.getWidth(text, layout.font.value); + layout.height = 18 * text.split("\n").length; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Fps.java b/src/main/java/org/saturnclient/feature/features/Fps.java deleted file mode 100644 index 8a80419..0000000 --- a/src/main/java/org/saturnclient/feature/features/Fps.java +++ /dev/null @@ -1,64 +0,0 @@ -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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/FpsFeature.java b/src/main/java/org/saturnclient/feature/features/FpsFeature.java new file mode 100644 index 0000000..bf73e9d --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/FpsFeature.java @@ -0,0 +1,77 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * FpsFeature displays the current frames-per-second as a HUD element. + * FPS is obtained from {@link RenderModule} rather than cached in the feature. + */ +public class FpsFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final FeatureLayout layout = new FeatureLayout(60, Fonts.getHeight()); + + private final ModuleProvider modules; + + public FpsFeature(ModuleProvider modules) { + super( + new FeatureDetails("FPS Display", "fps") + .description("Displays current FPS") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderFps(modules.render().getFps(), scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderFps(369, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderFps(int fps, RenderScope scope) { + String text = fps + " FPS"; + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + layout.width = Fonts.getWidth(text, layout.font.value); + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Freelook.java b/src/main/java/org/saturnclient/feature/features/Freelook.java deleted file mode 100644 index 998721c..0000000 --- a/src/main/java/org/saturnclient/feature/features/Freelook.java +++ /dev/null @@ -1,88 +0,0 @@ -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 enabled = Property.bool(false); - private static Property toggle = Property.bool(true); - - public static Property 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/FreelookFeature.java b/src/main/java/org/saturnclient/feature/features/FreelookFeature.java new file mode 100644 index 0000000..f2aaf90 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/FreelookFeature.java @@ -0,0 +1,92 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.RenderModule; +import org.saturnclient.common.provider.GLFWProvider; +import org.saturnclient.config.manager.Property; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; + +/** + * FreelookFeature lets the player look around freely (in third-person) + * without changing the direction their character walks. + * + * Camera perspective is controlled through {@link RenderModule} so + * that the feature itself contains no direct Minecraft API calls. + */ +public class FreelookFeature extends Feature { + + private static final Property enabled = Property.bool(false); + private static final Property toggle = Property.bool(true); + public static final Property freelookKey = Property.keybinding(GLFWProvider.GLFW_KEY_H); + + /** Whether freelook is currently active (read by the camera mixin). */ + public static boolean isFreeLooking = false; + private static boolean wasFirstPerson = false; + + private final ModuleProvider modules; + + public FreelookFeature(ModuleProvider modules) { + super( + new FeatureDetails("Freelook", "freelook") + .description("Look around freely without moving your character") + .tags("Movement", "Camera") + .version("v0.2.0"), + enabled.named("Enabled"), + toggle.named("Toggle freelook"), + freelookKey.named("Freelook Keybinding")); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @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(); + } + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + if (!e && isFreeLooking) + stopFreelook(); + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + // --------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------- + + private void startFreelook() { + RenderModule render = modules.render(); + wasFirstPerson = render.isFirstPerson(); + render.setThirdPersonBack(); + isFreeLooking = true; + } + + private void stopFreelook() { + if (wasFirstPerson) { + modules.render().setFirstPerson(); + } + isFreeLooking = false; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Fullbright.java b/src/main/java/org/saturnclient/feature/features/Fullbright.java deleted file mode 100644 index 0a7bb5d..0000000 --- a/src/main/java/org/saturnclient/feature/features/Fullbright.java +++ /dev/null @@ -1,39 +0,0 @@ -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 enabled = Property.bool(false); - private static Property 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; - } - -} diff --git a/src/main/java/org/saturnclient/feature/features/FullbrightFeature.java b/src/main/java/org/saturnclient/feature/features/FullbrightFeature.java new file mode 100644 index 0000000..9627848 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/FullbrightFeature.java @@ -0,0 +1,63 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.config.manager.Property; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; + +/** + * FullbrightFeature overrides the game's gamma/brightness to let the + * player see in complete darkness. + * + * Actual brightness injection happens in a mixin; this class owns only + * the configuration and the two static query methods the mixin calls. + * + * No ModuleProvider is needed — this feature has no per-tick engine + * interaction; the mixin polls the static helpers directly. + */ +public class FullbrightFeature extends Feature { + + private static final Property enabled = Property.bool(false); + private static final Property brightness = Property.integer(100); + + public FullbrightFeature() { + super( + new FeatureDetails("Fullbright", "fullbright") + .description("Allows you to see in the dark") + .tags("Camera") + .version("v0.2.0"), + enabled.named("Enabled"), + brightness.named("Brightness %")); + } + + // --------------------------------------------------------------- + // Mixin query API (static so the mixin can call without an instance) + // --------------------------------------------------------------- + + /** Returns {@code true} when the brightness override should be applied. */ + public static boolean shouldOverrideBrightness() { + return enabled.value; + } + + /** + * Returns the brightness multiplier to apply. + * The property stores a percentage (0–100+); the mixin expects a + * normalised float where 1.0 ≈ 100 %. + */ + public static float getBrightnessValue() { + return brightness.value / 10.0f; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/HealthDisplay.java b/src/main/java/org/saturnclient/feature/features/HealthDisplay.java deleted file mode 100644 index 4919791..0000000 --- a/src/main/java/org/saturnclient/feature/features/HealthDisplay.java +++ /dev/null @@ -1,96 +0,0 @@ -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 enabled = Property.bool(false); - private static Property displayMode = Property.select(0, "Health", "Hearts"); - private static Property 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/HealthDisplayFeature.java b/src/main/java/org/saturnclient/feature/features/HealthDisplayFeature.java new file mode 100644 index 0000000..e77b0fc --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/HealthDisplayFeature.java @@ -0,0 +1,93 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * HealthDisplayFeature renders the player's current health as a HUD + * element, with configurable display modes (raw health vs. hearts) and + * decimal precision. + */ +public class HealthDisplayFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final Property displayMode = Property.select(0, "Health", "Hearts"); + private static final Property decimals = Property.select(0, "0", "1", "2"); + private static final FeatureLayout layout = new FeatureLayout(40, 18); + + private final ModuleProvider modules; + + public HealthDisplayFeature(ModuleProvider modules) { + super( + new FeatureDetails("Health Display", "health") + .description("Displays your current health") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + displayMode.named("Display mode"), + decimals.named("Decimals"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + float health = modules.player().getHealth(); + if (displayMode.value == 1) + health /= 2f; // convert to hearts + renderHealth(health, scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderHealth(10f, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderHealth(float health, RenderScope scope) { + String fmt = switch (decimals.value) { + case 1 -> "%.1f "; + case 2 -> "%.2f "; + default -> "%.0f "; + }; + + String text = String.format(fmt, health); + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + + layout.width = Fonts.getWidth(text, layout.font.value); + layout.height = 18 * text.split("\n").length; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Keystrokes.java b/src/main/java/org/saturnclient/feature/features/Keystrokes.java deleted file mode 100644 index 2955781..0000000 --- a/src/main/java/org/saturnclient/feature/features/Keystrokes.java +++ /dev/null @@ -1,156 +0,0 @@ -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 enabled = Property.bool(false); - private static final Property showMouse = Property.bool(true); - private static final Property showSpace = Property.bool(false); - - private static final Property clickBg = Property.color(0xFFCCCCCC); - private static final Property 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/KeystrokesFeature.java b/src/main/java/org/saturnclient/feature/features/KeystrokesFeature.java new file mode 100644 index 0000000..663d1ab --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/KeystrokesFeature.java @@ -0,0 +1,168 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.PlayerModule; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * KeystrokesFeature renders the WASD / mouse / space key states as an + * on-screen overlay. + */ +public class KeystrokesFeature extends Feature implements HudFeature { + + // --------------------------------------------------------------- + // Configuration + // --------------------------------------------------------------- + + private static final Property enabled = Property.bool(false); + private static final Property showMouse = Property.bool(true); + private static final Property showSpace = Property.bool(false); + + private static final Property clickBg = Property.color(0xFFCCCCCC); + private static final Property clickFg = Property.color(0xFFFFFFFF); + + private static final FeatureLayout layout = new FeatureLayout(78, 54); + + // --------------------------------------------------------------- + // 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_H = 24; + private static final int SPACE_H = 19; + + // --------------------------------------------------------------- + // Cached key states (updated in tick) + // --------------------------------------------------------------- + + private boolean w, a, s, d, lmb, rmb, space; + + private final ModuleProvider modules; + + public KeystrokesFeature(ModuleProvider modules) { + 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"), + layout.prop(), + clickFg.named("Clicked fg"), + clickBg.named("Clicked bg")); + + this.modules = modules; + layout.renderBackground = false; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public void tick() { + PlayerModule player = modules.player(); + w = player.isForwardPressed(); + a = player.isLeftPressed(); + s = player.isBackPressed(); + d = player.isRightPressed(); + lmb = player.isAttackPressed(); + rmb = player.isUsePressed(); + space = player.isJumpPressed(); + refreshHeight(); + } + + @Override + public void renderHud(RenderScope scope) { + render(scope, true); + } + + @Override + public void renderDummy(RenderScope scope) { + render(scope, false); + } + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void render(RenderScope scope, boolean live) { + int yOffset = 0; + + // W (top centre) + renderKey(scope, live && w, 'W', KEY_SIZE + KEY_SPACING, yOffset); + yOffset += KEY_SIZE + KEY_SPACING; + + // A S D + 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; + + // Spacebar (optional) + if (showSpace.value) { + renderSpaceKey(scope, live && space, 0, yOffset); + yOffset += SPACE_H + KEY_SPACING; + } + + // Mouse buttons (optional) + if (showMouse.value) { + renderMouseKey(scope, live && lmb, "LMB", 0, yOffset); + renderMouseKey(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 : layout.bgColor.value; + int fg = pressed ? clickFg.value : layout.fgColor.value; + scope.drawRoundedRectangle(x, y, KEY_SIZE, KEY_SIZE, layout.radius.value, bg); + scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, layout.font.value, fg); + } + + private void renderMouseKey(RenderScope scope, boolean pressed, String label, int x, int y) { + int bg = pressed ? clickBg.value : layout.bgColor.value; + int fg = pressed ? clickFg.value : layout.fgColor.value; + scope.drawRoundedRectangle(x, y, MOUSE_WIDTH, MOUSE_H, layout.radius.value, bg); + scope.drawText(0.6f, label, x + 9, y + 7, layout.font.value, fg); + } + + private void renderSpaceKey(RenderScope scope, boolean pressed, int x, int y) { + int bg = pressed ? clickBg.value : layout.bgColor.value; + int fg = pressed ? clickFg.value : layout.fgColor.value; + scope.drawRoundedRectangle(x, y, layout.width, SPACE_H, layout.radius.value, bg); + int lineY = y + SPACE_H / 2; + scope.drawRect(x + (layout.width - 30) / 2, lineY, 30, 1, fg); + } + + private void refreshHeight() { + int h = KEY_SIZE * 2 + KEY_SPACING; + if (showSpace.value) + h += SPACE_H + KEY_SPACING; + if (showMouse.value) + h += MOUSE_H + KEY_SPACING; + layout.height = h; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Nametags.java b/src/main/java/org/saturnclient/feature/features/Nametags.java deleted file mode 100644 index bbb2877..0000000 --- a/src/main/java/org/saturnclient/feature/features/Nametags.java +++ /dev/null @@ -1,121 +0,0 @@ -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 enabled = Property.bool(false); - private static Property healthDisplay = Property.bool(false); - private static Property players = Property.bool(true); - private static Property hostile = Property.bool(false); - private static Property passive = Property.bool(false); - private static Property heartEmoji = Property.bool(true); - private static Property obfuscate = Property.bool(false); - private static Property unit = Property.select(0, "Health", "Hearts"); - private static Property format = Property.select(0, "Value", "Value / Total", "%"); - private static Property 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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/NametagsFeature.java b/src/main/java/org/saturnclient/feature/features/NametagsFeature.java new file mode 100644 index 0000000..b4da7c9 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/NametagsFeature.java @@ -0,0 +1,143 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.module.EntityModule; +import org.saturnclient.config.manager.Property; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; + +/** + * NametagsFeature modifies the nametags displayed above entities. + * + * Actual rendering is injected via a mixin; this class owns the + * configuration and exposes two static query methods the mixin uses: + * {@link #shouldReplaceName()} and + * {@link #getNametagString(EntityModule.EntityState)}. + * + * No ModuleProvider is required here because the mixin passes the + * already-resolved {@link EntityModule.EntityState} directly. + */ +public class NametagsFeature extends Feature { + + // --------------------------------------------------------------- + // Configuration + // --------------------------------------------------------------- + + private static final Property enabled = Property.bool(false); + private static final Property healthDisplay = Property.bool(false); + private static final Property players = Property.bool(true); + private static final Property hostile = Property.bool(false); + private static final Property passive = Property.bool(false); + private static final Property heartEmoji = Property.bool(true); + private static final Property obfuscate = Property.bool(false); + + private static final Property unit = Property.select(0, "Health", "Hearts"); + private static final Property format = Property.select(0, "Value", "Value / Total", "%"); + + private static final Property 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 final Property 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" + }; + + // --------------------------------------------------------------- + // Constructor + // --------------------------------------------------------------- + + public NametagsFeature() { + super( + new FeatureDetails("Nametags", "nametags") + .description("Allows you to modify entity nametags") + .tags("Camera", "Utility") + .version("v0.2.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")); + } + + // --------------------------------------------------------------- + // Mixin query API + // --------------------------------------------------------------- + + /** Returns {@code true} when nametag replacement is active. */ + public static boolean shouldReplaceName() { + return enabled.value; + } + + /** + * Builds the replacement nametag string for the given entity state, + * or {@code null} if this entity should not have its tag replaced. + */ + public static String getNametagString(EntityModule.EntityState state) { + if (state.getCustomName() == null) + return null; + if (!healthDisplay.value) + return state.getCustomName(); + + float health = state.getHealth(); + float maxHealth = state.getMaxHealth(); + + EntityModule.EntityType type = state.getEntityType(); + if (type == EntityModule.EntityType.PASSIVE && !passive.value) + return null; + if (type == EntityModule.EntityType.HOSTILE && !hostile.value) + return null; + if (type == EntityModule.EntityType.PLAYER && !players.value) + return null; + if (type == EntityModule.EntityType.OTHER) + return null; + + if (health < 0f || maxHealth < 0f || health > maxHealth) + return null; + + String hpText = buildHealthString(health, maxHealth); + String hpClr = COLOR_CODES[healthColor.value]; + String nameClr = COLOR_CODES[nameColor.value]; + String obfCode = obfuscate.value ? "§k" : ""; + String heart = heartEmoji.value ? " ❤" : ""; + + return nameClr + obfCode + state.getCustomName() + "§r " + + hpClr + hpText + heart; + } + + // --------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------- + + private static String buildHealthString(float health, float maxHealth) { + float divisor = unit.value + 1; + return switch (format.value) { + case 1 -> String.format("%.1f", health / divisor) + + " / " + String.format("%.1f", maxHealth / divisor); + case 2 -> String.format("%.0f%%", (health / maxHealth) * 100f); + default -> String.format("%.1f", health / divisor); + }; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/NoFog.java b/src/main/java/org/saturnclient/feature/features/NoFog.java deleted file mode 100644 index 1f8e1da..0000000 --- a/src/main/java/org/saturnclient/feature/features/NoFog.java +++ /dev/null @@ -1,39 +0,0 @@ -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 enabled = Property.bool(false); - private static Property 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; - } -} diff --git a/src/main/java/org/saturnclient/feature/features/NoFogFeature.java b/src/main/java/org/saturnclient/feature/features/NoFogFeature.java new file mode 100644 index 0000000..45f7dea --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/NoFogFeature.java @@ -0,0 +1,56 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.config.manager.Property; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; + +/** + * NoFogFeature suppresses distance fog and (optionally) liquid fog. + * + * Like {@link FullbrightFeature}, the actual suppression is applied + * inside a mixin. This class owns the configuration and provides + * static query methods the mixin calls. + */ +public class NoFogFeature extends Feature { + + private static final Property enabled = Property.bool(false); + private static final Property liquids = Property.bool(false); + + public NoFogFeature() { + super( + new FeatureDetails("No Fog", "nofog") + .description("Stops fog from rendering") + .tags("Camera") + .version("v0.2.0"), + enabled.named("Enabled"), + liquids.named("See through liquids")); + } + + // --------------------------------------------------------------- + // Mixin query API + // --------------------------------------------------------------- + + /** Returns {@code true} when fog should be suppressed. */ + public static boolean isActive() { + return enabled.value; + } + + /** Returns {@code true} when liquid-fog suppression is also active. */ + public static boolean liquids() { + return liquids.value; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Ping.java b/src/main/java/org/saturnclient/feature/features/Ping.java deleted file mode 100644 index 0ebdb78..0000000 --- a/src/main/java/org/saturnclient/feature/features/Ping.java +++ /dev/null @@ -1,59 +0,0 @@ -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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/PingFeature.java b/src/main/java/org/saturnclient/feature/features/PingFeature.java new file mode 100644 index 0000000..a933356 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/PingFeature.java @@ -0,0 +1,77 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * PingFeature displays the current server round-trip time in + * milliseconds as a HUD element. + */ +public class PingFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final FeatureLayout layout = new FeatureLayout(60, Fonts.getHeight()); + + private final ModuleProvider modules; + + public PingFeature(ModuleProvider modules) { + super( + new FeatureDetails("Ping Display", "ping") + .description("Displays ping to the server") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderPing(modules.network().getPing(), scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderPing(10, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderPing(int ping, RenderScope scope) { + String text = ping + " ms"; + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + layout.width = Fonts.getWidth(text, layout.font.value); + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Speedometer.java b/src/main/java/org/saturnclient/feature/features/Speedometer.java deleted file mode 100644 index 71f89b1..0000000 --- a/src/main/java/org/saturnclient/feature/features/Speedometer.java +++ /dev/null @@ -1,96 +0,0 @@ -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 enabled = Property.bool(false); - private static Property axis = Property.select(1, "Absolute", "X axis", "Y axis"); - private static Property unitText = Property.select(0, "None", "Blocks/s", "blocks/s", "b/s"); - private static Property 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/SpeedometerFeature.java b/src/main/java/org/saturnclient/feature/features/SpeedometerFeature.java new file mode 100644 index 0000000..68b120e --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/SpeedometerFeature.java @@ -0,0 +1,111 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.PlayerModule; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * SpeedometerFeature displays the player's current movement speed as + * a HUD element. Velocity data comes from {@link PlayerModule}. + */ +public class SpeedometerFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final Property axis = Property.select(1, "Absolute", "Horizontal", "Vertical"); + private static final Property unitText = Property.select(0, "None", "Blocks/s", "blocks/s", "b/s"); + private static final Property showLabel = Property.bool(true); + private static final FeatureLayout layout = new FeatureLayout(40, 18); + + private double cachedSpeed = 0.0; + + private final ModuleProvider modules; + + public SpeedometerFeature(ModuleProvider modules) { + super( + new FeatureDetails("Speedometer", "speed") + .description("Displays your current movement speed") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + axis.named("Speed type"), + unitText.named("Unit text"), + showLabel.named("Show speed label"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + PlayerModule player = modules.player(); + PlayerModule.Velocity v = player.getVelocity(); + boolean onGround = player.isOnGround(); + + double effectiveY = onGround ? 0.0 : v.y; + + cachedSpeed = switch (axis.value) { + case 0 -> Math.sqrt(v.x * v.x + effectiveY * effectiveY + v.z * v.z); // absolute 3-D + case 2 -> Math.abs(effectiveY); // vertical only + default -> Math.sqrt(v.x * v.x + v.z * v.z); // horizontal + }; + + renderSpeed(cachedSpeed * 20.0, scope); // blocks/tick → blocks/s + } + + @Override + public void renderDummy(RenderScope scope) { + renderSpeed(2.71, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderSpeed(double bps, RenderScope scope) { + String text = String.format("%.2f ", bps); + + text += switch (unitText.value) { + case 1 -> "Blocks/s"; + case 2 -> "blocks/s"; + case 3 -> "b/s"; + default -> ""; + }; + + if (showLabel.value) { + text = "Speed: " + text; + } + + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + layout.width = Fonts.getWidth(text, layout.font.value); + layout.height = 18 * text.split("\n").length; + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/StatusEffects.java b/src/main/java/org/saturnclient/feature/features/StatusEffects.java deleted file mode 100644 index fb21930..0000000 --- a/src/main/java/org/saturnclient/feature/features/StatusEffects.java +++ /dev/null @@ -1,87 +0,0 @@ -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 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 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; - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/features/StatusEffectsFeature.java b/src/main/java/org/saturnclient/feature/features/StatusEffectsFeature.java new file mode 100644 index 0000000..58152a9 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/StatusEffectsFeature.java @@ -0,0 +1,129 @@ +package org.saturnclient.feature.features; + +import java.util.List; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.ref.asset.SpriteRef; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * StatusEffectsFeature displays the player's active potion effects as + * a HUD overlay. + * + * The effect list is supplied by the platform through an interface + * returned by a future {@code PlayerModule#getActiveEffects()} call. + * Until that is wired up, the existing {@link StatusEffectsInterface} + * bridge is used, accessed via {@link ModuleProvider}. + */ +public class StatusEffectsFeature extends Feature implements HudFeature { + + public static final Property enabled = Property.bool(false); + private static final FeatureLayout layout = new FeatureLayout(60, 0); + + private final ModuleProvider modules; + + public StatusEffectsFeature(ModuleProvider modules) { + super( + new FeatureDetails("Status Effects", "effect") + .description("Displays active status effects") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + layout.prop()); + + this.modules = modules; + } + + // --------------------------------------------------------------- + // Effect data contract (inner interface keeps things self-contained) + // --------------------------------------------------------------- + + /** + * Minimal view of a single active status effect. + * Implementations are provided by the platform bridge. + */ + public interface EffectView { + boolean shouldShowIcon(); + + SpriteRef getIcon(); + + boolean isInfinite(); + + String getInfiniteText(); + + int getDurationSeconds(); + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderEffects(scope, modules.player().getActiveEffects()); + } + + @Override + public void renderDummy(RenderScope scope) { + renderEffects(scope, modules.player().getDummyEffects()); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderEffects(RenderScope scope, List effects) { + int row = 0; + + for (EffectView effect : effects) { + if (!effect.shouldShowIcon()) + continue; + + scope.drawSpriteStretched(effect.getIcon(), 0, 18 * row, 16, 16); + scope.drawText(0.5f, durationString(effect), + 18, 18 * row + 3, + layout.font.value, -1); + row++; + } + + layout.height = 18 * row; + } + + private static String durationString(EffectView effect) { + if (effect.isInfinite()) + return effect.getInfiniteText(); + + int totalSeconds = effect.getDurationSeconds(); + if (totalSeconds >= 3600) { + return (totalSeconds / 3600) + "h"; + } else if (totalSeconds >= 60) { + return String.format("%d:%02d", totalSeconds / 60, totalSeconds % 60); + } else { + return totalSeconds + "s"; + } + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Tps.java b/src/main/java/org/saturnclient/feature/features/Tps.java deleted file mode 100644 index 9f9bafc..0000000 --- a/src/main/java/org/saturnclient/feature/features/Tps.java +++ /dev/null @@ -1,100 +0,0 @@ -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 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; - } -} diff --git a/src/main/java/org/saturnclient/feature/features/TpsFeature.java b/src/main/java/org/saturnclient/feature/features/TpsFeature.java new file mode 100644 index 0000000..d3741b7 --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/TpsFeature.java @@ -0,0 +1,141 @@ +package org.saturnclient.feature.features; + +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.feature.Feature; +import org.saturnclient.feature.FeatureDetails; +import org.saturnclient.feature.FeatureLayout; +import org.saturnclient.feature.HudFeature; + +/** + * TpsFeature estimates the server's ticks-per-second by measuring the + * elapsed real time between consecutive world-time packets. + * + * TPS calculation is event-driven: the networking layer (or mixin) + * calls {@link #onTimePacket(long)} whenever a time-update packet + * arrives. No ModuleProvider is required because there is no + * per-frame polling of the engine. + */ +public class TpsFeature extends Feature implements HudFeature { + + private static final Property enabled = Property.bool(false); + private static final FeatureLayout layout = new FeatureLayout(60, Fonts.getHeight()); + + // --------------------------------------------------------------- + // TPS sampling state (static — shared across all instances) + // --------------------------------------------------------------- + + 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 long lastWorldAge = -1L; + private static long lastRealTime = -1L; + private static double currentTps = 20.0; + private static int sampleIndex = 0; + private static int samplesFilled = SAMPLE_COUNT; + + // --------------------------------------------------------------- + // Constructor + // --------------------------------------------------------------- + + public TpsFeature() { + super( + new FeatureDetails("TPS Display", "tps") + .description("Displays the server's ticks per second") + .version("v0.2.0") + .tags("Utility"), + enabled.named("Enabled"), + layout.prop()); + } + + // --------------------------------------------------------------- + // Packet hook (called by the networking mixin) + // --------------------------------------------------------------- + + /** + * Must be called every time the client receives a world-time packet + * carrying the updated {@code worldAge} value. + */ + public static void onTimePacket(long worldAge) { + long now = System.currentTimeMillis(); + + if (lastWorldAge != -1L && lastRealTime != -1L) { + long tickDelta = worldAge - lastWorldAge; + long timeDelta = now - lastRealTime; + + if (timeDelta > 0 && tickDelta > 0) { + double sample = Math.min((tickDelta / (double) timeDelta) * 1000.0, 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; + } + + /** Resets TPS history (e.g. on server disconnect). */ + public static void reset() { + lastWorldAge = -1L; + lastRealTime = -1L; + currentTps = 20.0; + sampleIndex = 0; + samplesFilled = SAMPLE_COUNT; + java.util.Arrays.fill(samples, 20.0); + } + + // --------------------------------------------------------------- + // HudFeature + // --------------------------------------------------------------- + + @Override + public void renderHud(RenderScope scope) { + renderTps(currentTps, scope); + } + + @Override + public void renderDummy(RenderScope scope) { + renderTps(20.0, scope); + } + + // --------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------- + + private void renderTps(double tps, RenderScope scope) { + String text = String.format("%.1f TPS", tps); + scope.drawText(text, 0, 0, layout.font.value, layout.fgColor.value); + layout.width = Fonts.getWidth(text, layout.font.value); + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public FeatureLayout getDimensions() { + return layout; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + } +} diff --git a/src/main/java/org/saturnclient/feature/features/Zoom.java b/src/main/java/org/saturnclient/feature/features/Zoom.java deleted file mode 100644 index 48e3f54..0000000 --- a/src/main/java/org/saturnclient/feature/features/Zoom.java +++ /dev/null @@ -1,59 +0,0 @@ -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 enabled = Property.bool(false); - private static Property toggle = Property.bool(false); - public static Property zoomKey = Property.keybinding(GLFWProvider.GLFW_KEY_C); - public static Property 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; - } -} diff --git a/src/main/java/org/saturnclient/feature/features/ZoomFeature.java b/src/main/java/org/saturnclient/feature/features/ZoomFeature.java new file mode 100644 index 0000000..a9778ef --- /dev/null +++ b/src/main/java/org/saturnclient/feature/features/ZoomFeature.java @@ -0,0 +1,78 @@ +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; + +/** + * ZoomFeature multiplies the camera's field-of-view to create an + * optical-zoom effect. + * + * The FOV override is applied inside a mixin that polls + * {@link #shouldZoom()} and {@link #getZoomLevel()} each frame. + * No ModuleProvider is needed — there is no per-tick engine query. + */ +public class ZoomFeature extends Feature { + + private static final Property enabled = Property.bool(false); + private static final Property toggle = Property.bool(false); + public static final Property zoomKey = Property.keybinding(GLFWProvider.GLFW_KEY_C); + public static final Property zoomLevel = Property.floatProp(3.0f); + + /** Whether zoom is currently active (read by the FOV mixin). */ + public static boolean isZooming = false; + + public ZoomFeature() { + super( + new FeatureDetails("Zoom", "zoom") + .description("Zoom in for a closer view") + .tags("Camera", "Utility") + .version("v0.2.0"), + enabled.named("Enabled"), + toggle.named("Toggle zoom"), + zoomKey.named("Zoom Keybinding"), + zoomLevel.named("Zoom Level")); + } + + // --------------------------------------------------------------- + // Feature contract + // --------------------------------------------------------------- + + @Override + public void tick() { + if (toggle.value) { + if (zoomKey.wasKeyPressed()) { + isZooming = !isZooming; + } + } else { + isZooming = zoomKey.isKeyPressed(); + } + } + + @Override + public void onEnabled(boolean e) { + enabled.value = e; + if (!e) + isZooming = false; + } + + @Override + public boolean isEnabled() { + return enabled.value; + } + + // --------------------------------------------------------------- + // Mixin query API + // --------------------------------------------------------------- + + /** Returns {@code true} when the FOV override should be applied. */ + public static boolean shouldZoom() { + return enabled.value && isZooming; + } + + /** Returns the zoom divisor (e.g. 3.0 → one-third of normal FOV). */ + public static float getZoomLevel() { + return zoomLevel.value; + } +} diff --git a/src/main/java/org/saturnclient/feature/interfaces/ArmorDisplayInterface.java b/src/main/java/org/saturnclient/feature/interfaces/ArmorDisplayInterface.java deleted file mode 100644 index 597c9ad..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/ArmorDisplayInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -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(); -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/AutoSprintInterface.java b/src/main/java/org/saturnclient/feature/interfaces/AutoSprintInterface.java deleted file mode 100644 index d3f23a6..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/AutoSprintInterface.java +++ /dev/null @@ -1,20 +0,0 @@ -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); -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/CoordinatesInterface.java b/src/main/java/org/saturnclient/feature/interfaces/CoordinatesInterface.java deleted file mode 100644 index 8e562c8..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/CoordinatesInterface.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface CoordinatesInterface { - - int getPlayerX(); - - int getPlayerY(); - - int getPlayerZ(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/CrosshairInterface.java b/src/main/java/org/saturnclient/feature/interfaces/CrosshairInterface.java deleted file mode 100644 index bcc784c..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/CrosshairInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface CrosshairInterface { - - boolean isTargetingLivingEntity(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/DayCounterInterface.java b/src/main/java/org/saturnclient/feature/interfaces/DayCounterInterface.java deleted file mode 100644 index 59764d3..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/DayCounterInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface DayCounterInterface { - - long getDay(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/FpsInterface.java b/src/main/java/org/saturnclient/feature/interfaces/FpsInterface.java deleted file mode 100644 index 637d152..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/FpsInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface FpsInterface { - - int getFps(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/FreelookInterface.java b/src/main/java/org/saturnclient/feature/interfaces/FreelookInterface.java deleted file mode 100644 index d6eaf60..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/FreelookInterface.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface FreelookInterface { - - boolean isFirstPerson(); - - void setFirstPerson(); - - void setThirdPersonBack(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/HealthDisplayInterface.java b/src/main/java/org/saturnclient/feature/interfaces/HealthDisplayInterface.java deleted file mode 100644 index a846062..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/HealthDisplayInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface HealthDisplayInterface { - - float getPlayerHealth(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/KeystrokesInterface.java b/src/main/java/org/saturnclient/feature/interfaces/KeystrokesInterface.java deleted file mode 100644 index 3601cf1..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/KeystrokesInterface.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface KeystrokesInterface { - - boolean isForwardPressed(); - boolean isBackPressed(); - boolean isLeftPressed(); - boolean isRightPressed(); - boolean isJumpPressed(); - boolean isAttackPressed(); - boolean isUsePressed(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/NametagsInterface.java b/src/main/java/org/saturnclient/feature/interfaces/NametagsInterface.java deleted file mode 100644 index b3f8249..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/NametagsInterface.java +++ /dev/null @@ -1,23 +0,0 @@ -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 - } - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/PingInterface.java b/src/main/java/org/saturnclient/feature/interfaces/PingInterface.java deleted file mode 100644 index 2423bf5..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/PingInterface.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.saturnclient.feature.interfaces; - -public interface PingInterface { - - /** Returns current player ping in milliseconds */ - int getPing(); - -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/SpeedometerInterface.java b/src/main/java/org/saturnclient/feature/interfaces/SpeedometerInterface.java deleted file mode 100644 index 9ebde2f..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/SpeedometerInterface.java +++ /dev/null @@ -1,20 +0,0 @@ -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; - } - } -} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/feature/interfaces/StatusEffectsInterface.java b/src/main/java/org/saturnclient/feature/interfaces/StatusEffectsInterface.java deleted file mode 100644 index 4e4728e..0000000 --- a/src/main/java/org/saturnclient/feature/interfaces/StatusEffectsInterface.java +++ /dev/null @@ -1,51 +0,0 @@ -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 getActiveEffects(); - - /** Returns a dummy list of effects for dummy rendering */ - List 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; - } - } -} \ No newline at end of file