From ceeb64282f94a20126883b5f17d1a06f7c4d4466 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 8 Mar 2026 15:55:44 +0100 Subject: [PATCH] Modules implementation --- .../impl/modules/EntityModuleFabric.java | 80 +++++ .../impl/modules/ModuleProviderFabric.java | 63 ++++ .../impl/modules/NetworkModuleFabric.java | 37 +++ .../impl/modules/PlayerModuleFabric.java | 301 ++++++++++++++++++ .../impl/modules/RenderModuleFabric.java | 64 ++++ .../impl/modules/WorldModuleFabric.java | 36 +++ 6 files changed, 581 insertions(+) create mode 100644 versions/1.21.4/src/main/java/org/saturnclient/impl/modules/EntityModuleFabric.java create mode 100644 versions/1.21.4/src/main/java/org/saturnclient/impl/modules/ModuleProviderFabric.java create mode 100644 versions/1.21.4/src/main/java/org/saturnclient/impl/modules/NetworkModuleFabric.java create mode 100644 versions/1.21.4/src/main/java/org/saturnclient/impl/modules/PlayerModuleFabric.java create mode 100644 versions/1.21.4/src/main/java/org/saturnclient/impl/modules/RenderModuleFabric.java create mode 100644 versions/1.21.4/src/main/java/org/saturnclient/impl/modules/WorldModuleFabric.java diff --git a/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/EntityModuleFabric.java b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/EntityModuleFabric.java new file mode 100644 index 0000000..a920623 --- /dev/null +++ b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/EntityModuleFabric.java @@ -0,0 +1,80 @@ +package org.saturnclient.impl.modules; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.entity.LivingEntity; + +import org.saturnclient.common.module.EntityModule; +import org.saturnclient.modules.HealthRenderState; + +/** + * Fabric implementation of {@link EntityModule}. + * + * Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}. + * Entity state for nametag rendering is supplied at render time by + * {@link org.saturnclient.modules.mixins.LivingEntityRendererMixin} which + * writes into the render state; {@link NametagsMixin} then constructs + * a {@link RenderStateEntityState} from the already-extracted snapshot. + */ +public class EntityModuleFabric implements EntityModule { + + private final MinecraftClient mc; + + public EntityModuleFabric(MinecraftClient mc) { + this.mc = mc; + } + + @Override + public boolean isTargetingLivingEntity() { + HitResult target = mc.crosshairTarget; + if (target == null || target.getType() != HitResult.Type.ENTITY) + return false; + Entity hit = ((EntityHitResult) target).getEntity(); + return hit instanceof LivingEntity; + } + + // --------------------------------------------------------------- + // Render-state adapter + // --------------------------------------------------------------- + + /** + * Wraps a {@link HealthRenderState} (already injected into the + * Minecraft render-state object) as a platform-neutral + * {@link EntityState} for use by {@link NametagsFeature}. + * + * This is constructed inside {@link NametagsMixin} where both the + * render-state mixin interface and the entity name are available. + */ + public static final class RenderStateEntityState implements EntityState { + + private final String customName; + private final HealthRenderState hrs; + + public RenderStateEntityState(String customName, HealthRenderState hrs) { + this.customName = customName; + this.hrs = hrs; + } + + @Override + public String getCustomName() { + return customName; + } + + @Override + public float getHealth() { + return hrs.saturn$getHealth(); + } + + @Override + public float getMaxHealth() { + return hrs.saturn$getMaxHealth(); + } + + @Override + public EntityType getEntityType() { + return hrs.saturn$getEntityType(); + } + } +} diff --git a/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/ModuleProviderFabric.java b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/ModuleProviderFabric.java new file mode 100644 index 0000000..cadd51d --- /dev/null +++ b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/ModuleProviderFabric.java @@ -0,0 +1,63 @@ +package org.saturnclient.impl.modules; + +import net.minecraft.client.MinecraftClient; + +import org.saturnclient.common.provider.ModuleProvider; +import org.saturnclient.common.module.*; + +/** + * Fabric implementation of {@link ModuleProvider}. + * + * Instantiated once during client startup (e.g. in the mod initialiser) + * and passed to every feature constructor. All five sub-modules share + * the same {@link MinecraftClient} reference so there is no + * inconsistency between modules reading state within the same tick. + * + *
{@code
+ * ModuleProvider provider = new ModuleProviderFabric(MinecraftClient.getInstance());
+ * FeatureManager.register(new AutoSprintFeature(provider));
+ * FeatureManager.register(new CoordinatesFeature(provider));
+ * // …
+ * }
+ */ +public class ModuleProviderFabric implements ModuleProvider { + + private final PlayerModule player; + private final WorldModule world; + private final EntityModule entity; + private final RenderModule render; + private final NetworkModule network; + + public ModuleProviderFabric(MinecraftClient mc) { + this.player = new PlayerModuleFabric(mc); + this.world = new WorldModuleFabric(mc); + this.entity = new EntityModuleFabric(mc); + this.render = new RenderModuleFabric(mc); + this.network = new NetworkModuleFabric(mc); + } + + @Override + public PlayerModule player() { + return player; + } + + @Override + public WorldModule world() { + return world; + } + + @Override + public EntityModule entity() { + return entity; + } + + @Override + public RenderModule render() { + return render; + } + + @Override + public NetworkModule network() { + return network; + } +} diff --git a/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/NetworkModuleFabric.java b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/NetworkModuleFabric.java new file mode 100644 index 0000000..61b1b9c --- /dev/null +++ b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/NetworkModuleFabric.java @@ -0,0 +1,37 @@ +package org.saturnclient.impl.modules; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.PlayerListEntry; + +import org.saturnclient.common.module.NetworkModule; + +/** + * Fabric implementation of {@link NetworkModule}. + * + * Connection presence is checked via the network handler. + * Ping is read from the player's {@link PlayerListEntry} which the + * server populates; returns {@code -1} when unavailable. + */ +public class NetworkModuleFabric implements NetworkModule { + + private final MinecraftClient mc; + + public NetworkModuleFabric(MinecraftClient mc) { + this.mc = mc; + } + + @Override + public boolean hasNetwork() { + return mc.getNetworkHandler() != null; + } + + @Override + public int getPing() { + if (mc.player == null || mc.getNetworkHandler() == null) + return -1; + + PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid()); + + return entry != null ? entry.getLatency() : -1; + } +} diff --git a/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/PlayerModuleFabric.java b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/PlayerModuleFabric.java new file mode 100644 index 0000000..b04c4c5 --- /dev/null +++ b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/PlayerModuleFabric.java @@ -0,0 +1,301 @@ +package org.saturnclient.impl.modules; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.item.ItemStack; + +import org.saturnclient.common.module.PlayerModule; +import org.saturnclient.common.ref.asset.SpriteRef; +import org.saturnclient.common.ref.game.ItemStackRef; +import org.saturnclient.feature.features.StatusEffectsFeature; +import org.saturnclient.saturnclient.SaturnClient; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Fabric implementation of {@link PlayerModule}. + * + * All access to {@link MinecraftClient} and {@link ClientPlayerEntity} + * is encapsulated here. Features only ever see the {@link PlayerModule} + * interface; they never import Minecraft classes directly. + * + * Every method guards against a missing player and returns a safe + * default so features are free of null checks. + */ +public class PlayerModuleFabric implements PlayerModule { + + private final MinecraftClient mc; + + public PlayerModuleFabric(MinecraftClient mc) { + this.mc = mc; + } + + // --------------------------------------------------------------- + // Internal helper — never returns null + // --------------------------------------------------------------- + + /** Returns the local player, or {@code null} if one is not loaded. */ + private ClientPlayerEntity player() { + return mc.player; + } + + // --------------------------------------------------------------- + // Presence + // --------------------------------------------------------------- + + @Override + public boolean hasPlayer() { + return mc.player != null; + } + + // --------------------------------------------------------------- + // Position + // --------------------------------------------------------------- + + @Override + public int getX() { + return hasPlayer() ? (int) player().getX() : 0; + } + + @Override + public int getY() { + return hasPlayer() ? (int) player().getY() : 0; + } + + @Override + public int getZ() { + return hasPlayer() ? (int) player().getZ() : 0; + } + + // --------------------------------------------------------------- + // Input / movement + // --------------------------------------------------------------- + + @Override + public boolean isForwardPressed() { + return mc.options.forwardKey.isPressed(); + } + + @Override + public boolean isBackPressed() { + return mc.options.backKey.isPressed(); + } + + @Override + public boolean isLeftPressed() { + return mc.options.leftKey.isPressed(); + } + + @Override + public boolean isRightPressed() { + return mc.options.rightKey.isPressed(); + } + + @Override + public boolean isJumpPressed() { + return mc.options.jumpKey.isPressed(); + } + + @Override + public boolean isAttackPressed() { + return mc.options.attackKey.isPressed(); + } + + @Override + public boolean isUsePressed() { + return mc.options.useKey.isPressed(); + } + + @Override + public boolean isSneaking() { + return hasPlayer() && player().isSneaking(); + } + + @Override + public boolean isUsingItem() { + return hasPlayer() && player().isUsingItem(); + } + + @Override + public boolean hasHorizontalCollision() { + return hasPlayer() && player().horizontalCollision; + } + + @Override + public boolean isOnGround() { + return hasPlayer() && player().isOnGround(); + } + + @Override + public void setSprinting(boolean sprinting) { + if (hasPlayer()) + player().setSprinting(sprinting); + } + + // --------------------------------------------------------------- + // Velocity + // --------------------------------------------------------------- + + @Override + public Velocity getVelocity() { + if (!hasPlayer()) + return Velocity.ZERO; + var v = player().getVelocity(); + return new Velocity(v.x, v.y, v.z); + } + + // --------------------------------------------------------------- + // Stats + // --------------------------------------------------------------- + + @Override + public float getHealth() { + return hasPlayer() ? player().getHealth() : 0f; + } + + // --------------------------------------------------------------- + // Equipment + // --------------------------------------------------------------- + + @Override + public ItemStackRef getMainHand() { + return wrap(hasPlayer() ? player().getMainHandStack() : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getHelmet() { + return wrap(hasPlayer() ? player().getInventory().armor.get(3) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getChestplate() { + return wrap(hasPlayer() ? player().getInventory().armor.get(2) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getLeggings() { + return wrap(hasPlayer() ? player().getInventory().armor.get(1) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getBoots() { + return wrap(hasPlayer() ? player().getInventory().armor.get(0) : ItemStack.EMPTY); + } + + // --------------------------------------------------------------- + // Dummy equipment + // --------------------------------------------------------------- + + @Override + public ItemStackRef getDummyMainHand() { + return wrap(hasPlayer() ? player().getInventory().armor.get(3) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getDummyHelmet() { + return wrap(hasPlayer() ? player().getInventory().armor.get(3) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getDummyChestplate() { + return wrap(hasPlayer() ? player().getInventory().armor.get(2) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getDummyLeggings() { + return wrap(hasPlayer() ? player().getInventory().armor.get(1) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getDummyBoots() { + return wrap(hasPlayer() ? player().getInventory().armor.get(0) : ItemStack.EMPTY); + } + + // --------------------------------------------------------------- + // Status effects + // --------------------------------------------------------------- + + @Override + public List getActiveEffects() { + if (!hasPlayer()) + return Collections.emptyList(); + + var effects = player().getActiveStatusEffects().values(); + List result = new ArrayList<>(effects.size()); + for (StatusEffectInstance instance : effects) { + result.add(new FabricEffectView(instance)); + } + return result; + } + + @Override + public List getDummyEffects() { + if (!hasPlayer()) + return Collections.emptyList(); + + var effects = player().getActiveStatusEffects().values(); + List result = new ArrayList<>(effects.size()); + for (StatusEffectInstance instance : effects) { + result.add(new FabricEffectView(instance)); + } + return result; + } + + // --------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------- + + private static ItemStackRef wrap(ItemStack stack) { + return (ItemStackRef) (Object) stack; + } + + // --------------------------------------------------------------- + // Inner — EffectView adapter + // --------------------------------------------------------------- + + /** + * Bridges a Fabric {@link StatusEffectInstance} to the + * platform-neutral {@link StatusEffectsFeature.EffectView}. + */ + public static final class FabricEffectView implements StatusEffectsFeature.EffectView { + + private final StatusEffectInstance instance; + + public FabricEffectView(StatusEffectInstance instance) { + this.instance = instance; + } + + @Override + public boolean shouldShowIcon() { + return instance.shouldShowIcon(); + } + + /** + * Returns the Minecraft sprite identifier for this effect's icon. + * The platform-specific renderer knows how to draw a + * {@link net.minecraft.util.Identifier}. + */ + @Override + public SpriteRef getIcon() { + return (SpriteRef) SaturnClient.client.getStatusEffectSpriteManager().getSprite(instance.getEffectType()); + } + + @Override + public boolean isInfinite() { + return instance.isInfinite(); + } + + @Override + public String getInfiniteText() { + return "∞"; + } + + @Override + public int getDurationSeconds() { + return instance.getDuration() / 20; // ticks → seconds + } + } +} diff --git a/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/RenderModuleFabric.java b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/RenderModuleFabric.java new file mode 100644 index 0000000..bfde729 --- /dev/null +++ b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/RenderModuleFabric.java @@ -0,0 +1,64 @@ +package org.saturnclient.impl.modules; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.Perspective; + +import org.saturnclient.common.module.RenderModule; + +/** + * Fabric implementation of {@link RenderModule}. + * + * Window dimensions and FPS are read directly from + * {@link MinecraftClient}. Camera perspective control delegates to + * {@link net.minecraft.client.option.GameOptions}. + */ +public class RenderModuleFabric implements RenderModule { + + private final MinecraftClient mc; + + public RenderModuleFabric(MinecraftClient mc) { + this.mc = mc; + } + + // --------------------------------------------------------------- + // Window + // --------------------------------------------------------------- + + @Override + public int getScaledWindowWidth() { + return mc.getWindow().getScaledWidth(); + } + + @Override + public int getScaledWindowHeight() { + return mc.getWindow().getScaledHeight(); + } + + // --------------------------------------------------------------- + // Performance + // --------------------------------------------------------------- + + @Override + public int getFps() { + return mc.getCurrentFps(); + } + + // --------------------------------------------------------------- + // Camera perspective + // --------------------------------------------------------------- + + @Override + public boolean isFirstPerson() { + return mc.options.getPerspective() == Perspective.FIRST_PERSON; + } + + @Override + public void setFirstPerson() { + mc.options.setPerspective(Perspective.FIRST_PERSON); + } + + @Override + public void setThirdPersonBack() { + mc.options.setPerspective(Perspective.THIRD_PERSON_BACK); + } +} diff --git a/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/WorldModuleFabric.java b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/WorldModuleFabric.java new file mode 100644 index 0000000..b555a23 --- /dev/null +++ b/versions/1.21.4/src/main/java/org/saturnclient/impl/modules/WorldModuleFabric.java @@ -0,0 +1,36 @@ +package org.saturnclient.impl.modules; + +import net.minecraft.client.MinecraftClient; + +import org.saturnclient.common.module.WorldModule; + +/** + * Fabric implementation of {@link WorldModule}. + * + * Reads the client world's time directly from {@link MinecraftClient}. + * Returns safe defaults when no world is loaded. + */ +public class WorldModuleFabric implements WorldModule { + + private final MinecraftClient mc; + + public WorldModuleFabric(MinecraftClient mc) { + this.mc = mc; + } + + @Override + public boolean hasWorld() { + return mc.world != null; + } + + /** + * Returns the total world age in ticks, or {@code 0} when no world + * is loaded. Used by {@link TpsFeature#onTimePacket} (which is + * called from the mixin and stores its own copy) and by + * {@link DayCounterFeature} for the day count. + */ + @Override + public long getWorldAge() { + return hasWorld() ? mc.world.getTime() : 0L; + } +}