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 extends StatusEffectsFeature.EffectView> getActiveEffects() {
+ if (!hasPlayer())
+ return Collections.emptyList();
+
+ var effects = player().getActiveStatusEffects().values();
+ List