From 670961accee8344275df6ff55f73fa87143e9f5b Mon Sep 17 00:00:00 2001 From: Kr4ight Date: Tue, 7 Apr 2026 20:36:49 +0200 Subject: [PATCH] Added 1.21.7 #72 --- versions/1.21.7/build.gradle | 109 ++++++ versions/1.21.7/gradle.properties | 21 ++ versions/1.21.7/settings.gradle | 10 + .../impl/cosmetics/CloakFeatureRenderer.java | 335 +++++++++++++++++ .../impl/cosmetics/HatFeatureRenderer.java | 42 +++ .../impl/cosmetics/obj/MtlLoader.java | 56 +++ .../impl/cosmetics/obj/ObjModel.java | 102 +++++ .../impl/cosmetics/obj/ObjRenderer.java | 112 ++++++ .../impl/cosmetics/utils/IdentifierUtils.java | 71 ++++ .../impl/cosmetics/utils/ShaderUtils.java | 39 ++ .../entity/CameraOverriddenEntity.java | 11 + .../features/entity/EntityFeatureImpl.java | 79 ++++ .../features/entity/HealthRenderState.java | 24 ++ .../features/mixins/entity/CameraMixin.java | 46 +++ .../features/mixins/entity/EntityMixin.java | 62 ++++ .../features/mixins/misc/NametagsMixin.java | 95 +++++ .../render/BackgroundRendererMixin.java | 21 ++ .../mixins/render/GameRendererMixin.java | 37 ++ .../render/LightmapTextureManagerMixin.java | 35 ++ .../render/LivingEntityRenderStateMixin.java | 60 +++ .../render/LivingEntityRendererMixin.java | 51 +++ .../features/mixins/render/RenderMixin.java | 82 +++++ .../impl/features/mixins/world/TpsMixin.java | 30 ++ .../features/network/NetworkFeatureImpl.java | 37 ++ .../features/player/PlayerFeatureImpl.java | 254 +++++++++++++ .../features/render/RenderFeatureImpl.java | 64 ++++ .../impl/features/world/WorldFeatureImpl.java | 36 ++ .../impl/mixins/IdentifierMixin.java | 13 + .../impl/mixins/ItemStackMixin.java | 19 + .../impl/mixins/MinecraftClientMixin.java | 162 ++++++++ .../impl/mixins/QuaternionfMixin.java | 9 + .../saturnclient/impl/mixins/SpriteMixin.java | 9 + .../impl/mixins/StatusEffectMixin.java | 43 +++ .../saturnclient/impl/mixins/TextMixin.java | 24 ++ .../saturnclient/impl/mixins/WindowMixin.java | 22 ++ .../impl/provider/FeatureProviderFabric.java | 68 ++++ .../impl/provider/GLFWProviderImpl.java | 18 + .../impl/provider/RefConstructorImpl.java | 25 ++ .../impl/provider/SaturnProviderImpl.java | 66 ++++ .../impl/ref/Matrix3x2fStackRef.java | 44 +++ .../impl/ui/EntityDrawerImpl.java | 70 ++++ .../saturnclient/impl/ui/RenderScopeImpl.java | 347 ++++++++++++++++++ .../impl/ui/SaturnScreenFabric.java | 134 +++++++ .../saturnclient/SaturnClient.java | 64 ++++ .../saturnclient/event/KeyInputHandler.java | 56 +++ .../mixin/DrawContextAccessor.java | 12 + .../saturnclient/mixin/NameTagMixin.java | 93 +++++ .../mixin/PlayerEntityRendererMixin.java | 30 ++ .../saturnclient/mixin/SetScreenMixin.java | 27 ++ .../mixin/SplashOverlayMixin.java | 161 ++++++++ .../saturnclient/mixin/TabListMixin.java | 43 +++ .../saturnclient/mixin/WhoAmI.java | 32 ++ .../1.21.7/src/main/resources/fabric.mod.json | 28 ++ .../src/main/resources/features.mixins.json | 21 ++ .../src/main/resources/impl.mixins.json | 19 + .../main/resources/saturnclient.mixins.json | 18 + 56 files changed, 3598 insertions(+) create mode 100644 versions/1.21.7/build.gradle create mode 100644 versions/1.21.7/gradle.properties create mode 100644 versions/1.21.7/settings.gradle create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/HatFeatureRenderer.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/MtlLoader.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjModel.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjRenderer.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/IdentifierUtils.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/CameraOverriddenEntity.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/EntityFeatureImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/HealthRenderState.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/EntityMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/misc/NametagsMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/BackgroundRendererMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRenderStateMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRendererMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/world/TpsMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/network/NetworkFeatureImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/player/PlayerFeatureImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/render/RenderFeatureImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/features/world/WorldFeatureImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/IdentifierMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/QuaternionfMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/SpriteMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/TextMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/provider/FeatureProviderFabric.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/provider/RefConstructorImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/ref/Matrix3x2fStackRef.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/ui/EntityDrawerImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/SaturnClient.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/DrawContextAccessor.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/PlayerEntityRendererMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java create mode 100644 versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/WhoAmI.java create mode 100644 versions/1.21.7/src/main/resources/fabric.mod.json create mode 100644 versions/1.21.7/src/main/resources/features.mixins.json create mode 100644 versions/1.21.7/src/main/resources/impl.mixins.json create mode 100644 versions/1.21.7/src/main/resources/saturnclient.mixins.json diff --git a/versions/1.21.7/build.gradle b/versions/1.21.7/build.gradle new file mode 100644 index 0000000..657c2fa --- /dev/null +++ b/versions/1.21.7/build.gradle @@ -0,0 +1,109 @@ +plugins { + id 'net.fabricmc.fabric-loom-remap' version "${loom_version}" + id 'maven-publish' +} + +version = "${project.mod_version}+${project.minecraft_version}" +group = project.maven_group + +base { + archivesName = project.archives_base_name +} + +repositories { + // maven { name = "KoSmX Maven"; url = 'https://maven.kosmx.dev/' } + maven { name = "JitPack"; url = 'https://jitpack.io' } +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" + + // Player Animation Library + // include modImplementation( + // "dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}" + // ) + + implementation project(":common") + + include implementation("com.github.selimaj-dev:session-java:0.1.6") + include implementation("de.javagl:obj:0.4.0") + include implementation("com.formdev:svgSalamander:1.1.4") + include implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0") + include implementation("com.fasterxml.jackson.core:jackson-annotations:2.17.0") + include implementation("com.fasterxml.jackson.core:jackson-core:2.17.0") + + // Optional: BendyLib + // include modRuntimeOnly( + // "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}" + // ) +} + +/* + * ================================================================ + * Resource Processing + * ================================================================ + * Injects version values into fabric.mod.json. + */ +processResources { + inputs.property "version", project.version + inputs.property "minecraft_version", project.minecraft_version + inputs.property "loader_version", project.loader_version + filteringCharset "UTF-8" + + filesMatching("fabric.mod.json") { + expand( + "version": project.version, + "minecraft_version": project.minecraft_version, + "loader_version": project.loader_version + ) + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 21 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +jar { + inputs.property "archivesName", project.base.archivesName + + from("LICENSE") { + rename { "${it}_${inputs.properties.archivesName}"} + } + + from project(":common").sourceSets.main.output + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +// configure the maven publication +publishing { + publications { + create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} \ No newline at end of file diff --git a/versions/1.21.7/gradle.properties b/versions/1.21.7/gradle.properties new file mode 100644 index 0000000..0daa03a --- /dev/null +++ b/versions/1.21.7/gradle.properties @@ -0,0 +1,21 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + +# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349 +org.gradle.configuration-cache=false + +# Fabric Properties +# check these on https://fabricmc.net/develop +minecraft_version=1.21.7 +yarn_mappings=1.21.7+build.8 +loader_version=0.19.0 +loom_version=1.15-SNAPSHOT + +fabric_api_version=0.129.0+1.21.7 + +# Mod Properties +maven_group=org.saturnclient +archives_base_name=saturn-client + +# player_anim = 2.0.2+1.21.5 diff --git a/versions/1.21.7/settings.gradle b/versions/1.21.7/settings.gradle new file mode 100644 index 0000000..75c4d72 --- /dev/null +++ b/versions/1.21.7/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java new file mode 100644 index 0000000..54ce803 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java @@ -0,0 +1,335 @@ +package org.saturnclient.impl.cosmetics; + +import java.util.Arrays; + +import org.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.config.Config; +import org.saturnclient.cosmetics.Cloaks; +import org.saturnclient.impl.cosmetics.utils.ShaderUtils; + +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.equipment.EquipmentModel; +import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; +import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.EquippableComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.item.equipment.EquipmentAsset; +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.math.RotationAxis; +import net.minecraft.util.math.Vec3d; + +/** + * The cloak renderer + * - **Efficient**: uses a image only consisting of the cloak texture, 16x8 + * whitespace. + * - **Curving**: Curves when player velocity increases. + * - **Wavey**: Makes the cloak feel like wind, when jump sprinting. + */ +public class CloakFeatureRenderer extends FeatureRenderer { + private final EquipmentModelLoader equipmentModelLoader; + private static final int PARTS = 24; + private final float[] segmentValues = new float[PARTS]; + + private static final float TEX_W = 176f; + private static final float TEX_H = 138f; + + private static final class PxRect { + final int x, y, w, h; + + PxRect(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + float u1() { + return x / TEX_W; + } + + float v1() { + return y / TEX_H; + } + + float u2() { + return (x + w) / TEX_W; + } + + float v2() { + return (y + h) / TEX_H; + } + } + + private static final PxRect FRONT_RECT = new PxRect(8, 8, 80, 128); + private static final PxRect BACK_RECT = new PxRect(96, 8, 80, 128); + private static final PxRect LEFT_EDGE_RECT = new PxRect(0, 8, 8, 128); + private static final PxRect RIGHT_EDGE_RECT = new PxRect(88, 8, 8, 128); + private static final PxRect TOP_RECT = new PxRect(8, 0, 80, 8); + private static final PxRect BOTTOM_RECT = new PxRect(88, 0, 80, 8); + + public CloakFeatureRenderer(FeatureRendererContext context, + EquipmentModelLoader equipmentModelLoader) { + super(context); + this.equipmentModelLoader = equipmentModelLoader; + Arrays.fill(segmentValues, 0.0f); + } + + private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) { + EquippableComponent equippableComponent = (EquippableComponent) stack + .get(DataComponentTypes.EQUIPPABLE); + if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) { + EquipmentModel equipmentModel = this.equipmentModelLoader + .get((RegistryKey) equippableComponent.assetId().get()); + return !equipmentModel.getLayers(layerType).isEmpty(); + } else { + return false; + } + } + + private void renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack matrixStack, + Vec3d bottomLeft, Vec3d bottomRight, Vec3d topRight, Vec3d topLeft, + float u1, float v1, float u2, float v2, int light, int overlay, + float normalX, float normalY, float normalZ, boolean flipWinding) { + MatrixStack.Entry entry = matrixStack.peek(); + + if (!flipWinding) { + // CCW + vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y, + (float) bottomLeft.z) + .color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y, + (float) bottomRight.z) + .color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y, + (float) topRight.z) + .color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y, + (float) topLeft.z) + .color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + } else { + // CW + vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y, + (float) topLeft.z) + .color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y, + (float) topRight.z) + .color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y, + (float) bottomRight.z) + .color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y, + (float) bottomLeft.z) + .color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light) + .normal(normalX, normalY, normalZ); + } + } + + private void renderCape(MatrixStack matrixStack, VertexConsumer vertexConsumer, PlayerEntityRenderState playerState, + int light, int overlay) { + float capeWidth = 10.0f / 16.0f; + float capeHeight = 16.0f / 16.0f; + float capeDepth = 1.0f / 16.0f; + float capePartHeight = capeHeight / PARTS; + + float x1 = -capeWidth / 2.0f; + float x2 = capeWidth / 2.0f; + + final float frontU1 = FRONT_RECT.u1(); + final float frontU2 = FRONT_RECT.u2(); + final float frontV1 = FRONT_RECT.v1(); + final float frontV2 = FRONT_RECT.v2(); + final float frontPartV = (frontV2 - frontV1) / PARTS; + final float backU1 = BACK_RECT.u1(); + final float backU2 = BACK_RECT.u2(); + final float backV1 = BACK_RECT.v1(); + final float backV2 = BACK_RECT.v2(); + final float backPartV = (backV2 - backV1) / PARTS; + final float leftU1 = LEFT_EDGE_RECT.u1(); + final float leftU2 = LEFT_EDGE_RECT.u2(); + final float leftV1 = LEFT_EDGE_RECT.v1(); + final float leftV2 = LEFT_EDGE_RECT.v2(); + final float leftPartV = (leftV2 - leftV1) / PARTS; + final float rightU1 = RIGHT_EDGE_RECT.u1(); + final float rightU2 = RIGHT_EDGE_RECT.u2(); + final float rightV1 = RIGHT_EDGE_RECT.v1(); + final float rightV2 = RIGHT_EDGE_RECT.v2(); + final float rightPartV = (rightV2 - rightV1) / PARTS; + final float topU1 = TOP_RECT.u1(); + final float topU2 = TOP_RECT.u2(); + final float topV1 = TOP_RECT.v1(); + final float topV2 = TOP_RECT.v2(); + final float bottomU1 = BOTTOM_RECT.u1(); + final float bottomU2 = BOTTOM_RECT.u2(); + final float bottomV1 = BOTTOM_RECT.v1(); + final float bottomV2 = BOTTOM_RECT.v2(); + + float curY = 0.0f; + float curZ = 0.0f; + + for (int i = 0; i < PARTS; i++) { + float angle = (2.0f - segmentValues[i]) * ((float) Math.PI / 2f); + float dirY = -(float) Math.cos(angle); + float dirZ = (float) Math.sin(angle); + + // 1. Thickness at the START of this segment (Must match PREVIOUS segment's end) + float prevAngle = (i > 0) ? (2.0f - segmentValues[i - 1]) * ((float) Math.PI / 2f) : angle; + float startAvgAngle = (angle + prevAngle) / 2.0f; + float thickYStart = (float) Math.sin(startAvgAngle) * capeDepth; + float thickZStart = (float) Math.cos(startAvgAngle) * capeDepth; + + // 2. Thickness at the END of this segment (Must match NEXT segment's start) + float nextAngle = (i < PARTS - 1) ? (2.0f - segmentValues[i + 1]) * ((float) Math.PI / 2f) : angle; + float endAvgAngle = (angle + nextAngle) / 2.0f; + float thickYEnd = (float) Math.sin(endAvgAngle) * capeDepth; + float thickZEnd = (float) Math.cos(endAvgAngle) * capeDepth; + + float nextY = curY + dirY * capePartHeight; + float nextZ = curZ + dirZ * capePartHeight; + + // Inner Vertices (The spine of the cape) + Vec3d innerTopLeft = new Vec3d(x2, curY, curZ); + Vec3d innerTopRight = new Vec3d(x1, curY, curZ); + Vec3d innerBotLeft = new Vec3d(x2, nextY, nextZ); + Vec3d innerBotRight = new Vec3d(x1, nextY, nextZ); + + // Outer Vertices (Offset by the specific joint thickness) + Vec3d outerTopLeft = new Vec3d(x2, curY - thickYStart, curZ - thickZStart); + Vec3d outerTopRight = new Vec3d(x1, curY - thickYStart, curZ - thickZStart); + Vec3d outerBotLeft = new Vec3d(x2, nextY - thickYEnd, nextZ - thickZEnd); + Vec3d outerBotRight = new Vec3d(x1, nextY - thickYEnd, nextZ - thickZEnd); + + // FRONT (facing camera) + this.renderCapeQuad(vertexConsumer, matrixStack, + outerBotRight, outerBotLeft, outerTopLeft, outerTopRight, + frontU2, frontV1 + (frontPartV * i), frontU1, frontV1 + (frontPartV * (i + 1)), + light, overlay, 0, 0, 1, false); + + // BACK (Facing player) + this.renderCapeQuad(vertexConsumer, matrixStack, + innerBotLeft, innerBotRight, innerTopRight, innerTopLeft, + backU2, backV1 + (backPartV * i), backU1, backV1 + (backPartV * (i + 1)), + light, overlay, 0, 0, -1, false); + + // LEFT EDGE + this.renderCapeQuad(vertexConsumer, matrixStack, + innerBotLeft, outerBotLeft, outerTopLeft, innerTopLeft, + leftU1, leftV1 + (leftPartV * i), leftU2, leftV1 + (leftPartV * (i + 1)), + light, overlay, 1, 0, 0, false); + + // RIGHT EDGE + this.renderCapeQuad(vertexConsumer, matrixStack, + outerBotRight, innerBotRight, innerTopRight, outerTopRight, + rightU1, rightV1 + (rightPartV * i), rightU2, rightV1 + (rightPartV * (i + 1)), + light, overlay, -1, 0, 0, false); + + // TOP FACE (Only on first segment) + if (i == 0) { + this.renderCapeQuad(vertexConsumer, matrixStack, + outerTopLeft, outerTopRight, innerTopRight, innerTopLeft, + topU1, topV1, topU2, topV2, light, overlay, 0, 1, 0, false); + } + + // BOTTOM FACE (Only on last segment) + if (i == PARTS - 1) { + this.renderCapeQuad(vertexConsumer, matrixStack, + innerBotLeft, innerBotRight, outerBotRight, outerBotLeft, + bottomU1, bottomV1, bottomU2, bottomV2, light, overlay, 0, -1, 0, false); + } + + curY = nextY; + curZ = nextZ; + } + } + + // Shift array left and insert new value at the start + private void updateSegmentValues(float value) { + // Shift all elements one position to the right (from end to start) + for (int i = segmentValues.length - 1; i > 0; i--) { + segmentValues[i] = segmentValues[i - 1]; + } + + // Insert the new value at index 0 + segmentValues[0] = value; + } + + private long lastUpdate = 0; + + public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, + PlayerEntityRenderState playerEntityRenderState, float f, float g) { + if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible + || playerEntityRenderState.skinTextures.capeTexture() != null) { + return; + } + + SaturnPlayer player = SaturnPlayer.get(playerEntityRenderState.name); + + if (player == null) { + return; + } + + IdentifierRef customCape = Cloaks.getCurrentCloakTexture(player.cloak); + if (customCape == null + || this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) { + return; + } + + int minBrightness = 7; + int blockLight = (light >> 4) & 0xF; + int skyLight = (light >> 20) & 0xF; + blockLight = Math.max(blockLight, minBrightness); + skyLight = Math.max(skyLight, minBrightness); + light = (skyLight << 20) | (blockLight << 4); + + VertexConsumer vertexConsumer = vertexConsumerProvider + .getBuffer(ShaderUtils.getRenderLayer(customCape)); + + matrixStack.push(); + + matrixStack.translate(0.0f, 0.0f, 0.12f); + + if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) { + matrixStack.translate(0.0F, -0.053125F, 0.06875F); + } + + if (playerEntityRenderState.isInSneakingPose) { + matrixStack.translate(0, 0.16f, 0.0f); + matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f)); + } + + long now = System.currentTimeMillis(); + if (now - lastUpdate >= 20) { + float velX = Math.min(0.6f, playerEntityRenderState.field_53537 / 108.0f); + float rawVelY = playerEntityRenderState.field_53536; + float velY = (rawVelY > 3.4f ? rawVelY : rawVelY < -3.4f ? rawVelY : 0.0f) / 16; + + float value = Math.max(0.0f, Math.min(2.0f, playerEntityRenderState.isSwimming ? 0.0f : velX + velY)); + + if (Config.cloakPhysics.value) { + updateSegmentValues(segmentValues[0] + (value - segmentValues[0]) * 0.3f); + } else { + Arrays.fill(segmentValues, segmentValues[0] + (value - segmentValues[0]) * 0.05f); + } + lastUpdate = now; + } + + renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light, OverlayTexture.DEFAULT_UV); + + matrixStack.pop(); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/HatFeatureRenderer.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/HatFeatureRenderer.java new file mode 100644 index 0000000..cce3ec2 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/HatFeatureRenderer.java @@ -0,0 +1,42 @@ +package org.saturnclient.impl.cosmetics; + +import org.joml.Quaternionf; +import org.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.impl.cosmetics.obj.MtlLoader; +import org.saturnclient.impl.cosmetics.obj.ObjModel; + +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.model.ModelPart; + +public class HatFeatureRenderer extends FeatureRenderer { + public HatFeatureRenderer(FeatureRendererContext context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, + PlayerEntityRenderState state, float limbAngle, float limbDistance) { + SaturnPlayer player = SaturnPlayer.get(state.name); + + if (state.invisible || player == null || player.hat.isEmpty()) { + return; + } + + matrices.push(); + + ModelPart head = this.getContextModel().head; + matrices.multiply(new Quaternionf().rotationZYX(head.roll, head.yaw, head.pitch)); + + ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat), + matrices, vertexConsumers, light, + OverlayTexture.DEFAULT_UV); + + matrices.pop(); + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/MtlLoader.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/MtlLoader.java new file mode 100644 index 0000000..11b06d3 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/MtlLoader.java @@ -0,0 +1,56 @@ +package org.saturnclient.impl.cosmetics.obj; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.javagl.obj.*; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Identifier; + +public class MtlLoader { + static Map> loadedMtls = new HashMap<>(); + + static Map loadMtl(Identifier mtlId) throws IOException { + try (InputStream is = MinecraftClient.getInstance().getResourceManager() + .getResource(mtlId).get().getInputStream()) { + List mtlList = MtlReader.read(is); + Map mtlMap = new java.util.LinkedHashMap<>(); + for (Mtl mtl : mtlList) { + mtlMap.put(mtl.getName(), mtl); + } + return mtlMap; + } + } + + public static Map of(Identifier mtlId) { + if (loadedMtls.containsKey(mtlId)) + return loadedMtls.get(mtlId); + + try { + Map mtl = loadMtl(mtlId); + loadedMtls.put(mtlId, mtl); + + return mtl; + } catch (IOException e) { + e.printStackTrace(); + + return null; + } + + } + + public static Map cosmetic(String cosmeticId) { + int index = cosmeticId.indexOf('_'); + + String mtlId = (index == -1) ? "" : cosmeticId.substring(index + 1); + + if (mtlId.isEmpty()) + mtlId = "white"; + + return of(Identifier.of("saturnclient", "models/cosmetic/materials/" + mtlId + ".mtl")); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjModel.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjModel.java new file mode 100644 index 0000000..3e7237e --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjModel.java @@ -0,0 +1,102 @@ +package org.saturnclient.impl.cosmetics.obj; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.javagl.obj.Mtl; +import de.javagl.obj.Obj; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; + +public class ObjModel { + private static final ObjectMapper MAPPER = new ObjectMapper(); + public static final Map loadedObjModels = new HashMap<>(); + + public record Config( + @JsonProperty("translate") Vec3d transform, + @JsonProperty("scale") Vec3d scale, + @JsonProperty("rotation") Vec3d rotation) { + + public Config { + transform = transform == null ? new Vec3d(0, 0, 0) : transform; + scale = scale == null ? new Vec3d(1, 1, 1) : scale; + rotation = rotation == null ? new Vec3d(0, 0, 0) : rotation; + } + + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + public record Vec3d(float x, float y, float z) { + } + } + + Obj obj; + Config config; + + public static ObjModel cosmetic(String cosmeticKind, String id) { + return of(Identifier.of("saturnclient", "models/cosmetic/" + cosmeticKind + "/" + id.split("_")[0] + "/model")); + } + + public static ObjModel of(Identifier obj) { + if (loadedObjModels.containsKey(obj)) + return loadedObjModels.get(obj); + + ObjModel o = new ObjModel(obj); + loadedObjModels.put(obj, o); + return o; + } + + ObjModel(Identifier identifier) { + Identifier objId = Identifier.of(identifier.getNamespace(), identifier.getPath() + ".obj"); + Identifier jsonId = Identifier.of(identifier.getNamespace(), identifier.getPath() + ".json"); + + try { + this.obj = ObjRenderer.loadObj(objId); + } catch (IOException e) { + e.printStackTrace(); + } + + // Try loading JSON config if it exists + ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager(); + Optional resource = resourceManager.getResource(jsonId); + + if (resource.isPresent()) { + try (InputStream stream = resource.get().getInputStream()) { + this.config = MAPPER.readValue(stream, Config.class); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void render(Map mtlMap, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, + int overlay) { + matrices.push(); + + matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f)); + matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f)); + + if (this.config != null) { + matrices.translate(config.transform.x, config.transform.y, config.transform.z); + + matrices.scale(config.scale.x, config.scale.y, config.scale.z); + + matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(config.rotation.x)); + matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(config.rotation.y)); + matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Z.rotationDegrees(config.rotation.z)); + } + + ObjRenderer.renderObj(this.obj, mtlMap, matrices, vertexConsumers, light, overlay); + + matrices.pop(); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjRenderer.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjRenderer.java new file mode 100644 index 0000000..97498cc --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/obj/ObjRenderer.java @@ -0,0 +1,112 @@ +package org.saturnclient.impl.cosmetics.obj; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.impl.cosmetics.utils.ShaderUtils; + +import de.javagl.obj.*; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Identifier; + +public class ObjRenderer { + + public static Obj loadObj(Identifier objId) throws IOException { + try (InputStream is = MinecraftClient.getInstance().getResourceManager() + .getResource(objId).get().getInputStream()) { + return ObjReader.read(is); + } + } + + public static void renderObj(Obj obj, Map mtlMap, MatrixStack matrices, + VertexConsumerProvider vertexConsumers, int light, int overlay) { + MatrixStack.Entry entry = matrices.peek(); + + Map materialGroups = ObjSplitting.splitByMaterialGroups(obj); + + for (Map.Entry group : materialGroups.entrySet()) { + String materialName = group.getKey(); + Obj groupObj = group.getValue(); + + Mtl mtl = mtlMap != null ? mtlMap.get(materialName) : null; + + // Resolve texture identifier using same logic as ObjUnbakedModelModel + IdentifierRef texture = IdentifierRef.ofVanilla("textures/misc/white.png"); + + if (mtl != null) { + String mapKd = mtl.getMapKd(); + if (mapKd != null) { + if (!mapKd.startsWith("#")) { + texture = IdentifierRef.of(mapKd); + } + } + } + + // Diffuse color (Kd) + float r = 1f, g = 1f, b = 1f; + if (mtl != null) { + FloatTuple kd = mtl.getKd(); + if (kd != null) { + r = kd.getX(); + g = kd.getY(); + b = kd.getZ(); + } + } + + VertexConsumer consumer = vertexConsumers.getBuffer(ShaderUtils.getRenderLayer(texture)); + + int faces = groupObj.getNumFaces(); + final float fr = r, fg = g, fb = b; + + for (int f = 0; f < faces; f++) { + ObjFace face = groupObj.getFace(f); + int verts = face.getNumVertices(); + + // Your original winding order logic, untouched + for (int i = verts - 1; i >= 0; i--) { + writeVertex(groupObj, face, i, consumer, entry, light, overlay, fr, fg, fb); + } + } + } + } + + private static void writeVertex(Obj obj, ObjFace face, int index, VertexConsumer consumer, + MatrixStack.Entry matrix, int light, int overlay, + float r, float g, float b) { + + FloatTuple pos = obj.getVertex(face.getVertexIndex(index)); + float x = pos.getX(); + float y = pos.getY(); + float z = pos.getZ(); + + float nx = 0f, ny = 1f, nz = 0f; + int normalIndex = face.getNormalIndex(index); + if (normalIndex >= 0) { + FloatTuple n = obj.getNormal(normalIndex); + nx = n.getX(); + ny = n.getY(); + nz = n.getZ(); + } + + float u = 0f, v = 0f; + int texIndex = face.getTexCoordIndex(index); + if (texIndex >= 0) { + FloatTuple tc = obj.getTexCoord(texIndex); + u = tc.getX(); + v = 1f - tc.getY(); // Flip V: OBJ bottom-left origin -> Minecraft top-left + } + + consumer.vertex(matrix, x, y, z) + .color(r, g, b, 1f) + .texture(u, v) + .overlay(overlay) + .light(light) + .normal(matrix, nx, ny, nz); + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/IdentifierUtils.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/IdentifierUtils.java new file mode 100644 index 0000000..bd8be2d --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/IdentifierUtils.java @@ -0,0 +1,71 @@ +package org.saturnclient.impl.cosmetics.utils; + +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.saturnclient.SaturnClient; + +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; +import net.minecraft.util.Identifier; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.util.Map; + +public class IdentifierUtils { + + public static void registerBufferedImageTextureFast(IdentifierRef identifierRef, BufferedImage bufferedImage) { + try { + Identifier identifier = (Identifier) (Object) identifierRef; + + int width = bufferedImage.getWidth(); + int height = bufferedImage.getHeight(); + + NativeImage nativeImage = new NativeImage(NativeImage.Format.RGBA, width, height, false); + + if (bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) { + int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData(); + + int index = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + nativeImage.setColorArgb(x, y, pixels[index++]); + } + } + + } else { + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + nativeImage.setColorArgb(x, y, bufferedImage.getRGB(x, y)); + } + } + + } + + SaturnClient.client.execute(() -> { + NativeImageBackedTexture texture = new NativeImageBackedTexture( + () -> identifier.toString(), + nativeImage); + + SaturnClient.client.getTextureManager().registerTexture(identifier, texture); + }); + + } catch (Exception e) { + SaturnClient.LOGGER.error("Failed to register texture: {}", identifierRef, e); + } + } + + public static void registerBufferedImageTexturesBulk(Map textures) { + + long start = System.currentTimeMillis(); + + textures.forEach(IdentifierUtils::registerBufferedImageTextureFast); + + long end = System.currentTimeMillis(); + + SaturnClient.LOGGER.info( + "Registered {} textures in {}ms", + textures.size(), + (end - start)); + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java new file mode 100644 index 0000000..2e8ec23 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java @@ -0,0 +1,39 @@ +package org.saturnclient.impl.cosmetics.utils; + +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.cosmetics.utils.GifUtils; + +import com.mojang.blaze3d.pipeline.RenderPipeline; + +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.RenderPhase.Textures; +import net.minecraft.client.render.block.entity.EndPortalBlockEntityRenderer; +import net.minecraft.util.Identifier; + +public class ShaderUtils { + public static final RenderPipeline END_GATEWAY_PIPELINE = RenderPipelines.register( + RenderPipeline.builder(new RenderPipeline.Snippet[] { RenderPipelines.RENDERTYPE_END_PORTAL_SNIPPET }) + .withLocation("pipeline/end_gateway") + .withShaderDefine("PORTAL_LAYERS", 16) + .withCull(false) + .build()); + + public static final RenderLayer END_GATEWAY = RenderLayer.of("end_gateway", 1536, false, false, + END_GATEWAY_PIPELINE, + RenderLayer.MultiPhaseParameters.builder() + .texture(Textures.create().add(EndPortalBlockEntityRenderer.SKY_TEXTURE, false) + .add(EndPortalBlockEntityRenderer.PORTAL_TEXTURE, false).build()) + .build(false)); + + public static RenderLayer getRenderLayer(IdentifierRef texture) { + String path = texture.toString(); + if (path.endsWith(".gif")) { + return RenderLayer.getEntityAlpha((Identifier) (Object) GifUtils.get(texture)); + } else if (path.endsWith("end.png")) { + return END_GATEWAY; + } else { + return RenderLayer.getEntityAlpha((Identifier) (Object) texture); + } + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/CameraOverriddenEntity.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/CameraOverriddenEntity.java new file mode 100644 index 0000000..310b69d --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/CameraOverriddenEntity.java @@ -0,0 +1,11 @@ +package org.saturnclient.impl.features.entity; + +public interface CameraOverriddenEntity { + float freelook$getCameraPitch(); + + float freelook$getCameraYaw(); + + void freelook$setCameraPitch(float pitch); + + void freelook$setCameraYaw(float yaw); +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/EntityFeatureImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/EntityFeatureImpl.java new file mode 100644 index 0000000..901542e --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/EntityFeatureImpl.java @@ -0,0 +1,79 @@ +package org.saturnclient.impl.features.entity; + +import org.saturnclient.common.feature.EntityFeature; + +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; + +/** + * Fabric implementation of {@link EntityFeature}. + * + * 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 EntityFeatureImpl implements EntityFeature { + + private final MinecraftClient mc; + + public EntityFeatureImpl(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.7/src/main/java/org/saturnclient/impl/features/entity/HealthRenderState.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/HealthRenderState.java new file mode 100644 index 0000000..7c62f81 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/entity/HealthRenderState.java @@ -0,0 +1,24 @@ +package org.saturnclient.impl.features.entity; + +import org.saturnclient.common.feature.EntityFeature; + +/** + * Injected interface on + * {@link net.minecraft.client.render.entity.state.LivingEntityRenderState}. + * + * Carries the entity's health snapshot and classification into the render + * pipeline so that {@link NametagsMixin} can read them without touching + * the live entity again. + */ +public interface HealthRenderState { + + float saturn$getHealth(); + + float saturn$getMaxHealth(); + + void saturn$setHealth(float health, float maxHealth); + + EntityFeature.EntityType saturn$getEntityType(); + + void saturn$setEntityType(EntityFeature.EntityType type); +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java new file mode 100644 index 0000000..e1ff321 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java @@ -0,0 +1,46 @@ +package org.saturnclient.impl.features.mixins.entity; + +import org.saturnclient.impl.features.entity.CameraOverriddenEntity; +import org.saturnclient.mod.mods.FreelookMod; +import org.saturnclient.saturnclient.SaturnClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.render.Camera; +import net.minecraft.entity.Entity; +import net.minecraft.world.BlockView; + +@Mixin(Camera.class) +public abstract class CameraMixin { + @Unique + boolean firstTime = true; + + @Shadow + protected abstract void setRotation(float yaw, float pitch); + + @Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER)) + public void lockRotation(BlockView focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing, + float tickDelta, CallbackInfo ci) { + if (FreelookMod.isFreeLooking() && cameraEntity instanceof ClientPlayerEntity) { + CameraOverriddenEntity cameraOverriddenEntity = (CameraOverriddenEntity) cameraEntity; + + if (firstTime && SaturnClient.client.player != null) { + cameraOverriddenEntity.freelook$setCameraPitch(SaturnClient.client.player.getPitch()); + cameraOverriddenEntity.freelook$setCameraYaw(SaturnClient.client.player.getYaw()); + firstTime = false; + } + this.setRotation(cameraOverriddenEntity.freelook$getCameraYaw(), + cameraOverriddenEntity.freelook$getCameraPitch()); + + } + if (!FreelookMod.isFreeLooking() && cameraEntity instanceof ClientPlayerEntity) { + firstTime = true; + } + } + +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/EntityMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/EntityMixin.java new file mode 100644 index 0000000..a079e19 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/entity/EntityMixin.java @@ -0,0 +1,62 @@ +package org.saturnclient.impl.features.mixins.entity; + +import org.saturnclient.impl.features.entity.CameraOverriddenEntity; +import org.saturnclient.mod.mods.FreelookMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; + +@Mixin(Entity.class) +public class EntityMixin implements CameraOverriddenEntity { + @Unique + private float cameraPitch; + + @Unique + private float cameraYaw; + + @Inject(method = "changeLookDirection", at = @At("HEAD"), cancellable = true) + public void changeCameraLookDirection(double xDelta, double yDelta, CallbackInfo ci) { + // noinspection ConstantValue// IntelliJ is incorrect here, this code block is + // reachable + if (FreelookMod.isFreeLooking() && (Object) this instanceof ClientPlayerEntity) { + double pitchDelta = (yDelta * 0.15); + double yawDelta = (xDelta * 0.15); + + this.cameraPitch = MathHelper.clamp(this.cameraPitch + (float) pitchDelta, -90.0f, 90.0f); + this.cameraYaw += (float) yawDelta; + + ci.cancel(); + + } + } + + @Override + @Unique + public float freelook$getCameraPitch() { + return this.cameraPitch; + } + + @Override + @Unique + public float freelook$getCameraYaw() { + return this.cameraYaw; + } + + @Override + @Unique + public void freelook$setCameraPitch(float pitch) { + this.cameraPitch = pitch; + } + + @Override + @Unique + public void freelook$setCameraYaw(float yaw) { + this.cameraYaw = yaw; + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/misc/NametagsMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/misc/NametagsMixin.java new file mode 100644 index 0000000..8c1763d --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/misc/NametagsMixin.java @@ -0,0 +1,95 @@ +package org.saturnclient.impl.features.mixins.misc; + +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.state.EntityRenderState; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +import org.saturnclient.impl.features.entity.EntityFeatureImpl; +import org.saturnclient.impl.features.entity.HealthRenderState; +import org.saturnclient.impl.features.mixins.render.LivingEntityRenderStateMixin; +import org.saturnclient.mod.mods.NametagsMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Intercepts nametag rendering and replaces the displayed text with + * the string produced by {@link NametagsMod#getNametagString}. + * + * Key changes from the original: + * + *
    + *
  • The old {@code NametagsFabric.EntityStateImpl} bridge class is + * replaced by {@link EntityFeatureImpl.RenderStateEntityState}, + * which wraps the {@link HealthRenderState} already injected into + * the render state by {@link LivingEntityRenderStateMixin}.
  • + *
  • The feature import changes from {@code Nametags} to + * {@link NametagsMod}; the two static methods + * ({@code shouldReplaceName} / {@code getNametagString}) are + * unchanged in signature.
  • + *
  • The entity's display name is extracted from the render state's + * {@code nameVisible} / text fields where available, falling back + * to the plain text string passed to this method.
  • + *
+ * + * The re-entrancy guard ({@code saturn$rendering}) is kept intact to + * prevent infinite recursion when we call {@code renderLabelIfPresent} + * with the replacement text. + */ +@Mixin(EntityRenderer.class) +public abstract class NametagsMixin { + + /** Guards against the recursive call we make with the replacement text. */ + @Unique + private static final ThreadLocal saturn$rendering = ThreadLocal.withInitial(() -> false); + + @Shadow + protected abstract void renderLabelIfPresent( + S state, Text text, + MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light); + + @Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true) + private void saturn$replaceNametag( + S state, Text text, + MatrixStack matrices, VertexConsumerProvider vertexConsumers, + int light, CallbackInfo ci) { + + // Skip our own recursive call + if (saturn$rendering.get()) + return; + + if (!NametagsMod.shouldReplaceName()) + return; + + // Only living entities carry health data + if (!(state instanceof LivingEntityRenderState living)) + return; + if (!(living instanceof HealthRenderState hrs)) + return; + + // Build the platform-neutral EntityState from the render-state snapshot + // (health/type were written by LivingEntityRendererMixin at extract time). + String customName = text != null ? text.getString() : null; + EntityFeatureImpl.RenderStateEntityState entityState = new EntityFeatureImpl.RenderStateEntityState( + customName, hrs); + + // Ask the feature for the replacement string + String replacement = NametagsMod.getNametagString(entityState); + if (replacement == null) + return; // feature decided not to replace this entity + + ci.cancel(); + saturn$rendering.set(true); + try { + renderLabelIfPresent(state, Text.literal(replacement), matrices, vertexConsumers, light); + } finally { + saturn$rendering.set(false); + } + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/BackgroundRendererMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/BackgroundRendererMixin.java new file mode 100644 index 0000000..0a61479 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/BackgroundRendererMixin.java @@ -0,0 +1,21 @@ +package org.saturnclient.impl.features.mixins.render; + +import net.minecraft.client.render.fog.FogRenderer; + +import org.saturnclient.mod.mods.NoFogMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(FogRenderer.class) +public class BackgroundRendererMixin { + // fog + @ModifyVariable(method = "applyFog", at = @At("HEAD"), ordinal = 0, argsOnly = true) + private static float modifyFogStart(float viewDistance) { + if (NoFogMod.isActive()) { + return viewDistance * 2.0f; + } + + return viewDistance; + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java new file mode 100644 index 0000000..c6d97f3 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java @@ -0,0 +1,37 @@ +package org.saturnclient.impl.features.mixins.render; + +import net.minecraft.client.render.GameRenderer; + +import org.saturnclient.mod.mods.ZoomMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Overrides the game's field-of-view when {@link ZoomMod} is active. + * + * The old code checked both {@code Zoom.isZooming} and + * {@code Zoom.shouldZoom()} separately. The refactored + * {@link ZoomMod#shouldZoom()} already ORs both conditions + * ({@code enabled.value && isZooming}), so only one check is needed. + * + * No {@link org.saturnclient.common.module.ModProvider} is required + * here because zooming is a pure render-time override with no per-tick + * engine queries. + */ +@Mixin(GameRenderer.class) +public class GameRendererMixin { + + /** + * After the vanilla FOV has been computed, divide it by the zoom + * level to produce the narrowed field-of-view. + */ + @Inject(method = "getFov", at = @At("RETURN"), cancellable = true) + private void onGetFov(CallbackInfoReturnable cir) { + if (ZoomMod.shouldZoom()) { + float zoomedFov = cir.getReturnValue() / ZoomMod.getZoomLevel(); + cir.setReturnValue(zoomedFov); + } + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java new file mode 100644 index 0000000..66a3119 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java @@ -0,0 +1,35 @@ +package org.saturnclient.impl.features.mixins.render; + +// Code approach adapted from Gamma Utils: https://github.com/Sjouwer/gamma-utils + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.render.LightmapTextureManager; + +import org.saturnclient.mod.mods.FullbrightMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +/** + * Overrides the lightmap's gamma computation when + * {@link FullbrightMod} is active, replacing the vanilla + * brightness with a user-configured value. + * + * The old import was {@code org.saturnclient.feature.features.Fullbright}; + * it is now {@link FullbrightMod}. The injection target and + * strategy are unchanged. + */ +@Mixin(LightmapTextureManager.class) +public class LightmapTextureManagerMixin { + + /** + * If fullbright is active, substitute the feature's brightness + * value for whatever vanilla calculated. + */ + @ModifyExpressionValue(method = "update", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0)) + private float modifyGamma(float original) { + if (FullbrightMod.shouldOverrideBrightness()) { + return FullbrightMod.getBrightnessValue(); + } + return original; + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRenderStateMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRenderStateMixin.java new file mode 100644 index 0000000..bbb293b --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRenderStateMixin.java @@ -0,0 +1,60 @@ +package org.saturnclient.impl.features.mixins.render; + +import net.minecraft.client.render.entity.state.LivingEntityRenderState; + +import org.saturnclient.common.feature.EntityFeature; +import org.saturnclient.impl.features.entity.HealthRenderState; +import org.saturnclient.impl.features.mixins.misc.NametagsMixin; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +/** + * Implements {@link HealthRenderState} on + * {@link LivingEntityRenderState}, injecting health and entity-type + * storage into every living entity's render-state snapshot. + * + * Previously imported {@code NametagsInterface.EntityType}; the enum + * now lives in {@link EntityFeature.EntityType} which is the single + * canonical definition shared by the mixin layer and the feature layer. + * + * The stored values are written by + * {@link LivingEntityRendererMixin#saturn$captureHealth} each time a + * render state is extracted from a live entity, and read by + * {@link NametagsMixin} at render time. + */ +@Mixin(LivingEntityRenderState.class) +public class LivingEntityRenderStateMixin implements HealthRenderState { + + @Unique + private float saturn$health = 0f; + @Unique + private float saturn$maxHealth = 1f; + @Unique + private EntityFeature.EntityType saturn$entityType = EntityFeature.EntityType.OTHER; + + @Override + public float saturn$getHealth() { + return saturn$health; + } + + @Override + public float saturn$getMaxHealth() { + return saturn$maxHealth; + } + + @Override + public void saturn$setHealth(float health, float maxHealth) { + this.saturn$health = health; + this.saturn$maxHealth = maxHealth; + } + + @Override + public EntityFeature.EntityType saturn$getEntityType() { + return saturn$entityType; + } + + @Override + public void saturn$setEntityType(EntityFeature.EntityType type) { + saturn$entityType = type; + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRendererMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRendererMixin.java new file mode 100644 index 0000000..5e9d6a2 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/LivingEntityRendererMixin.java @@ -0,0 +1,51 @@ +package org.saturnclient.impl.features.mixins.render; + +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.passive.PassiveEntity; +import net.minecraft.entity.player.PlayerEntity; + +import org.saturnclient.common.feature.EntityFeature; +import org.saturnclient.impl.features.entity.HealthRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Captures each living entity's health and type into the render state + * during {@code updateRenderState}, so that nametag rendering can read + * them without touching the live entity again. + * + * Previously imported {@code NametagsInterface.EntityType}; now uses + * {@link EntityFeature.EntityType} — the canonical enum shared across + * the whole module system. + * + * The injection target and strategy are unchanged from the original. + */ +@Mixin(LivingEntityRenderer.class) +public abstract class LivingEntityRendererMixin { + + @Inject(method = "updateRenderState", at = @At("TAIL")) + private void saturn$captureHealth(T entity, S state, float tickDelta, CallbackInfo ci) { + if (!(state instanceof HealthRenderState hrs)) + return; + + hrs.saturn$setHealth(entity.getHealth(), entity.getMaxHealth()); + + // Classify entity type for the nametag feature's filtering logic + EntityFeature.EntityType type; + if (entity instanceof PlayerEntity) + type = EntityFeature.EntityType.PLAYER; + else if (entity instanceof HostileEntity) + type = EntityFeature.EntityType.HOSTILE; + else if (entity instanceof PassiveEntity) + type = EntityFeature.EntityType.PASSIVE; + else + type = EntityFeature.EntityType.OTHER; + + hrs.saturn$setEntityType(type); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java new file mode 100644 index 0000000..f9d59c8 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java @@ -0,0 +1,82 @@ +package org.saturnclient.impl.features.mixins.render; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.client.render.RenderTickCounter; + +import org.saturnclient.impl.ui.RenderScopeImpl; +import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.mod.HudMod; +import org.saturnclient.mod.Mod; +import org.saturnclient.mod.ModLayout; +import org.saturnclient.mod.ModManager; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.screens.HudEditor; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Drives per-frame rendering for all registered features. + * + * This mixin is intentionally kept thin: it creates a {@link RenderScope}, + * iterates the enabled feature list from {@link ModManager}, and + * dispatches either {@link HudMod#renderHud} (for HUD elements) or + * {@link Mod#render} (for world-overlay features like Crosshair). + * + * No feature-specific logic lives here — all decisions about what to + * draw are made inside each feature class. + * + * Changes from the original: + * - Package imports updated to the refactored feature names (no + * more {@code feature.features.*}). + * - The HUD-editor screen check still uses the existing + * {@link SaturnScreenFabric} / {@link HudEditor} pair; that is + * unchanged infrastructure. + */ +@Mixin(InGameHud.class) +public class RenderMixin { + + @Inject(method = "renderChat", at = @At("TAIL")) + public void renderMods(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { + TextRenderer textRenderer = SaturnClient.client.textRenderer; + + // Skip rendering while the HUD editor is open so the editor + // can draw its own preview layout without double-drawing. + if (textRenderer == null) + return; + if (SaturnClient.client.currentScreen instanceof SaturnScreenFabric f + && f.screen instanceof HudEditor) + return; + + RenderScope scope = new RenderScopeImpl(context); + + for (Mod feature : ModManager.ENABLED_MODS) { + if (!feature.isEnabled()) + continue; + + if (feature instanceof HudMod hud) { + ModLayout dim = hud.getDimensions(); + + scope.getMatrixStack().push(); + scope.getMatrixStack().translate(dim.x.value, (float) dim.y.value); + scope.getMatrixStack().scale(dim.scale.value, dim.scale.value); + + if (dim.renderBackground) { + scope.drawRoundedRectangle(0, 0, dim.width, dim.height, + dim.radius.value, dim.bgColor.value); + } + + hud.renderHud(scope); + scope.getMatrixStack().pop(); + + } else { + // Non-HUD features (e.g. CrosshairFeature) use the plain render hook. + feature.render(scope); + } + } + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/world/TpsMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/world/TpsMixin.java new file mode 100644 index 0000000..0818d8b --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/mixins/world/TpsMixin.java @@ -0,0 +1,30 @@ +package org.saturnclient.impl.features.mixins.world; + +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket; + +import org.saturnclient.mod.mods.TpsMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Feeds world-time packets into {@link TpsMod}'s TPS estimator. + * + * This is the only place in the entire codebase that reads a network + * packet and passes data to a feature. The feature's + * {@link TpsMod#onTimePacket(long)} method handles all averaging + * logic; the mixin is intentionally reduced to a single forwarding call. + * + * The only change from the original is renaming the import from + * {@code feature.features.Tps} to {@link TpsMod}. + */ +@Mixin(ClientPlayNetworkHandler.class) +public class TpsMixin { + + @Inject(method = "onWorldTimeUpdate", at = @At("HEAD")) + private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) { + TpsMod.onTimePacket(packet.time()); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/network/NetworkFeatureImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/network/NetworkFeatureImpl.java new file mode 100644 index 0000000..dca37ad --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/network/NetworkFeatureImpl.java @@ -0,0 +1,37 @@ +package org.saturnclient.impl.features.network; + +import org.saturnclient.common.feature.NetworkFeature; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.PlayerListEntry; + +/** + * Fabric implementation of {@link NetworkFeature}. + * + * 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 NetworkFeatureImpl implements NetworkFeature { + + private final MinecraftClient mc; + + public NetworkFeatureImpl(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.7/src/main/java/org/saturnclient/impl/features/player/PlayerFeatureImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/player/PlayerFeatureImpl.java new file mode 100644 index 0000000..a161cd6 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/player/PlayerFeatureImpl.java @@ -0,0 +1,254 @@ +package org.saturnclient.impl.features.player; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; + +import org.saturnclient.common.feature.PlayerFeature; +import org.saturnclient.common.ref.game.EffectRef; +import org.saturnclient.common.ref.game.ItemStackRef; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Fabric implementation of {@link PlayerFeature}. + * + * All access to {@link MinecraftClient} and {@link ClientPlayerEntity} + * is encapsulated here. Features only ever see the {@link PlayerFeature} + * 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 PlayerFeatureImpl implements PlayerFeature { + + private final MinecraftClient mc; + + public PlayerFeatureImpl(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().getEquippedStack(EquipmentSlot.HEAD) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getChestplate() { + return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.CHEST) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getLeggings() { + return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.LEGS) : ItemStack.EMPTY); + } + + @Override + public ItemStackRef getBoots() { + return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.FEET) : ItemStack.EMPTY); + } + + // --------------------------------------------------------------- + // Dummy equipment + // --------------------------------------------------------------- + + @Override + public ItemStackRef getDummyMainHand() { + return wrap(new ItemStack(Items.DIAMOND_SWORD)); + } + + @Override + public ItemStackRef getDummyHelmet() { + return wrap(new ItemStack(Items.DIAMOND_HELMET)); + } + + @Override + public ItemStackRef getDummyChestplate() { + return wrap(new ItemStack(Items.DIAMOND_CHESTPLATE)); + } + + @Override + public ItemStackRef getDummyLeggings() { + return wrap(new ItemStack(Items.DIAMOND_LEGGINGS)); + } + + @Override + public ItemStackRef getDummyBoots() { + return wrap(new ItemStack(Items.DIAMOND_BOOTS)); + } + + // --------------------------------------------------------------- + // 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((EffectRef) instance); + } + return result; + } + + @Override + public List getDummyEffects() { + return List.of( + (EffectRef) new StatusEffectInstance( + Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("speed")).get(), 12000, 2), + (EffectRef) new StatusEffectInstance( + Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("strength")).get(), 12000, 2), + (EffectRef) new StatusEffectInstance( + Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2)); + } + + // --------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------- + + private static ItemStackRef wrap(ItemStack stack) { + return (ItemStackRef) (Object) stack; + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/features/render/RenderFeatureImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/render/RenderFeatureImpl.java new file mode 100644 index 0000000..adb958c --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/render/RenderFeatureImpl.java @@ -0,0 +1,64 @@ +package org.saturnclient.impl.features.render; + +import org.saturnclient.common.feature.RenderFeature; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.Perspective; + +/** + * Fabric implementation of {@link RenderFeature}. + * + * Window dimensions and FPS are read directly from + * {@link MinecraftClient}. Camera perspective control delegates to + * {@link net.minecraft.client.option.GameOptions}. + */ +public class RenderFeatureImpl implements RenderFeature { + + private final MinecraftClient mc; + + public RenderFeatureImpl(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.7/src/main/java/org/saturnclient/impl/features/world/WorldFeatureImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/world/WorldFeatureImpl.java new file mode 100644 index 0000000..4847509 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/features/world/WorldFeatureImpl.java @@ -0,0 +1,36 @@ +package org.saturnclient.impl.features.world; + +import org.saturnclient.common.feature.WorldFeature; + +import net.minecraft.client.MinecraftClient; + +/** + * Fabric implementation of {@link WorldFeature}. + * + * Reads the client world's time directly from {@link MinecraftClient}. + * Returns safe defaults when no world is loaded. + */ +public class WorldFeatureImpl implements WorldFeature { + + private final MinecraftClient mc; + + public WorldFeatureImpl(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; + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/IdentifierMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/IdentifierMixin.java new file mode 100644 index 0000000..edfcdb9 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/IdentifierMixin.java @@ -0,0 +1,13 @@ +package org.saturnclient.impl.mixins; + +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.util.Identifier; + +@Mixin(Identifier.class) +public abstract class IdentifierMixin implements IdentifierRef { + @Shadow + public abstract String toString(); +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java new file mode 100644 index 0000000..9a69341 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java @@ -0,0 +1,19 @@ +package org.saturnclient.impl.mixins; + +import org.saturnclient.common.ref.game.ItemStackRef; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.item.ItemStack; + +@Mixin(ItemStack.class) +public abstract class ItemStackMixin implements ItemStackRef { + @Shadow + public abstract boolean isEmpty(); + + @Shadow + public abstract int getMaxDamage(); + + @Shadow + public abstract int getDamage(); +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java new file mode 100644 index 0000000..b0a269a --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java @@ -0,0 +1,162 @@ +package org.saturnclient.impl.mixins; + +import java.io.File; +import java.io.InputStream; +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.common.ref.game.MinecraftClientRef; +import org.saturnclient.common.ref.render.WindowRef; +import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.ui.SaturnScreen; +import org.saturnclient.ui.screens.TitleMenu; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.gui.screen.option.OptionsScreen; +import net.minecraft.client.gui.screen.world.SelectWorldScreen; +import net.minecraft.client.option.GameOptions; +import net.minecraft.client.session.Session; +import net.minecraft.client.util.Window; +import net.minecraft.resource.ReloadableResourceManagerImpl; +import net.minecraft.util.Identifier; + +@Mixin(MinecraftClient.class) +public abstract class MinecraftClientMixin implements MinecraftClientRef { + @Shadow + public File runDirectory; + + @Shadow + private Window window; + + @Shadow + private ReloadableResourceManagerImpl resourceManager; + + @Shadow + public GameOptions options; + + @Shadow + private Session session; + + @Shadow + public abstract void setScreen(@Nullable Screen screen); + + @Shadow + @Override + public abstract void scheduleStop(); + + @Override + public File getRunDirectory() { + return runDirectory; + } + + @Override + public InputStream getResource(IdentifierRef identifier) { + try { + return resourceManager.getResource((Identifier) (Object) identifier).get().getInputStream(); + } catch (Exception e) { + return null; + } + } + + @Override + public WindowRef getWindow() { + return (WindowRef) (Object) this.window; + } + + @Override + public void setScreen(SaturnScreen screen) { + this.setScreen(new SaturnScreenFabric(screen)); + } + + @Override + public void setScreen(MinecraftScreen screen) { + SaturnScreenFabric exitTarget = new SaturnScreenFabric(new TitleMenu()); + + switch (screen) { + case SelectWorld: + this.setScreen(new SelectWorldScreen(exitTarget)); + break; + + case Multiplayer: + this.setScreen(new MultiplayerScreen(exitTarget)); + break; + + case Options: + this.setScreen(new OptionsScreen(exitTarget, this.options)); + break; + } + } + + @Override + public String getAccessToken() { + if (session == null) { + return null; + } + + return this.session.getAccessToken(); + } + + @Override + public UUID getUuid() { + if (session == null) { + return null; + } + return this.session.getUuidOrNull(); + } + + @Override + public String getUsername() { + if (session == null) { + return null; + } + return this.session.getUsername(); + } + + @Override + public void onClientStopping(Runnable handler) { + ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> handler.run()); + } + + @Override + public void executeOnThread(Runnable runnable) { + ((java.util.concurrent.Executor) (Object) this).execute(runnable); + } + + @Override + @SuppressWarnings("resource") + public String getPlayerListEntry(UUID uuid) { + var client = (MinecraftClient) (Object) this; + var handler = client.getNetworkHandler(); + + if (handler == null) + return null; + + var entry = handler.getPlayerListEntry(uuid); + if (entry == null) + return null; + + return entry.getProfile().getName(); + } + + @Override + @SuppressWarnings("resource") + public UUID getPlayerListEntryUUID(String name) { + var client = (MinecraftClient) (Object) this; + var handler = client.getNetworkHandler(); + + if (handler == null) + return null; + + var entry = handler.getPlayerListEntry(name); + if (entry == null) + return null; + + return entry.getProfile().getId(); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/QuaternionfMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/QuaternionfMixin.java new file mode 100644 index 0000000..628e433 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/QuaternionfMixin.java @@ -0,0 +1,9 @@ +package org.saturnclient.impl.mixins; + +import org.joml.Quaternionf; +import org.saturnclient.common.ref.render.QuaternionfRef; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Quaternionf.class) +public abstract class QuaternionfMixin implements QuaternionfRef { +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/SpriteMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/SpriteMixin.java new file mode 100644 index 0000000..6cf07bc --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/SpriteMixin.java @@ -0,0 +1,9 @@ +package org.saturnclient.impl.mixins; + +import net.minecraft.client.texture.Sprite; +import org.saturnclient.common.ref.asset.SpriteRef; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Sprite.class) +public abstract class SpriteMixin implements SpriteRef { +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java new file mode 100644 index 0000000..df9ad35 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java @@ -0,0 +1,43 @@ +package org.saturnclient.impl.mixins; + +import org.saturnclient.common.ref.asset.SpriteRef; +import org.saturnclient.common.ref.game.EffectRef; +import org.saturnclient.saturnclient.SaturnClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.registry.entry.RegistryEntry; + +@Mixin(StatusEffectInstance.class) +public abstract class StatusEffectMixin implements EffectRef { + + @Shadow + public abstract boolean shouldShowIcon(); + + @Shadow + public abstract boolean isInfinite(); + + @Shadow + public abstract int getDuration(); + + @Shadow + public abstract RegistryEntry getEffectType(); + + @Override + public SpriteRef getIcon() { + return (SpriteRef) SaturnClient.client.getGuiAtlasManager().getSprite((InGameHud.getEffectTexture(getEffectType()))); + } + + @Override + public String getInfiniteText() { + return "∞"; + } + + @Override + public int getDurationSeconds() { + return getDuration() / 20; // ticks → seconds + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/TextMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/TextMixin.java new file mode 100644 index 0000000..3e1a8b8 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/TextMixin.java @@ -0,0 +1,24 @@ +package org.saturnclient.impl.mixins; + +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.common.ref.render.TextRef; +import org.saturnclient.saturnclient.SaturnClient; +import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +@Mixin(Text.class) +public interface TextMixin extends TextRef { + + @Override + public default TextRef withFont(IdentifierRef font) { + return (TextRef) (((Text) this).copy().setStyle(Style.EMPTY.withFont((Identifier) (Object) font))); + } + + @Override + public default int getWidth() { + return SaturnClient.client.textRenderer.getWidth((Text) this); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java new file mode 100644 index 0000000..5629068 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java @@ -0,0 +1,22 @@ +package org.saturnclient.impl.mixins; + +import org.saturnclient.common.ref.render.WindowRef; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.client.util.Window; + +@Mixin(Window.class) +public abstract class WindowMixin implements WindowRef { + @Shadow + public abstract int getWidth(); + + @Shadow + public abstract int getHeight(); + + @Shadow + public abstract int getFramebufferWidth(); + + @Shadow + public abstract int getFramebufferHeight(); +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/FeatureProviderFabric.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/FeatureProviderFabric.java new file mode 100644 index 0000000..c424273 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/FeatureProviderFabric.java @@ -0,0 +1,68 @@ +package org.saturnclient.impl.provider; + +import net.minecraft.client.MinecraftClient; + +import org.saturnclient.common.feature.*; +import org.saturnclient.common.provider.FeatureProvider; +import org.saturnclient.impl.features.entity.EntityFeatureImpl; +import org.saturnclient.impl.features.network.NetworkFeatureImpl; +import org.saturnclient.impl.features.player.PlayerFeatureImpl; +import org.saturnclient.impl.features.render.RenderFeatureImpl; +import org.saturnclient.impl.features.world.WorldFeatureImpl; + +/** + * Fabric implementation of {@link FeatureProvider}. + * + * 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
+ * FeatureProvider provider = new FeatureProviderFabric(MinecraftClient.getInstance());
+ * FeatureManager.register(new AutoSprintFeature(provider));
+ * FeatureManager.register(new CoordinatesFeature(provider));
+ * // …
+ * }
+ */ +public class FeatureProviderFabric implements FeatureProvider { + + private final PlayerFeature player; + private final WorldFeature world; + private final EntityFeature entity; + private final RenderFeature render; + private final NetworkFeature network; + + public FeatureProviderFabric(MinecraftClient mc) { + this.player = new PlayerFeatureImpl(mc); + this.world = new WorldFeatureImpl(mc); + this.entity = new EntityFeatureImpl(mc); + this.render = new RenderFeatureImpl(mc); + this.network = new NetworkFeatureImpl(mc); + } + + @Override + public PlayerFeature player() { + return player; + } + + @Override + public WorldFeature world() { + return world; + } + + @Override + public EntityFeature entity() { + return entity; + } + + @Override + public RenderFeature render() { + return render; + } + + @Override + public NetworkFeature network() { + return network; + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java new file mode 100644 index 0000000..f8a896f --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java @@ -0,0 +1,18 @@ +package org.saturnclient.impl.provider; + +import org.lwjgl.glfw.GLFW; +import org.saturnclient.common.provider.GLFWProvider; +import org.saturnclient.saturnclient.SaturnClient; + +public class GLFWProviderImpl implements GLFWProvider { + @Override + public boolean isKeyPressed(int key) { + return GLFW.glfwGetKey(SaturnClient.client.getWindow().getHandle(), key) == GLFW.GLFW_PRESS + && SaturnClient.client.currentScreen == null; + } + + @Override + public String getKeyName(int key) { + return GLFW.glfwGetKeyName(key, GLFW.glfwGetKeyScancode(key)); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/RefConstructorImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/RefConstructorImpl.java new file mode 100644 index 0000000..cf59bd7 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/RefConstructorImpl.java @@ -0,0 +1,25 @@ +package org.saturnclient.impl.provider; + +import org.saturnclient.common.provider.RefConstructorProvider; +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.common.ref.render.TextRef; + +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class RefConstructorImpl implements RefConstructorProvider { + @Override + public IdentifierRef identifier(String fullPath) { + return (IdentifierRef) (Object) Identifier.of(fullPath); + } + + @Override + public IdentifierRef identifier(String namespace, String path) { + return (IdentifierRef) (Object) Identifier.of(namespace, path); + } + + @Override + public TextRef text(String text) { + return (TextRef) Text.literal(text); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java new file mode 100644 index 0000000..8ac2333 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java @@ -0,0 +1,66 @@ +package org.saturnclient.impl.provider; + +import java.awt.image.BufferedImage; +import java.util.UUID; + +import org.saturnclient.common.provider.SaturnProvider; +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.common.ref.game.MinecraftClientRef; +import org.saturnclient.impl.cosmetics.utils.IdentifierUtils; +import org.saturnclient.saturnclient.SaturnClient; + +public class SaturnProviderImpl implements SaturnProvider { + @Override + public MinecraftClientRef getClient() { + return (MinecraftClientRef) SaturnClient.client; + } + + @Override + public void playEmote(UUID fromPlayerUuid, String emoteIdOrNull) { + // if (SaturnClient.client == null || SaturnClient.client.world == null) { + // return; + // } + + // for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) { + // if (!player.getUuid().equals(fromPlayerUuid)) { + // continue; + // } + + // AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player); + + // if (emoteIdOrNull != null && !emoteIdOrNull.isEmpty()) { + // if (animationStack.isActive() && animationStack.getPriority() == 1000) { + // animationStack.removeLayer(1000); + // } + // animationStack.addAnimLayer(1000, + // PlayerAnimationRegistry + // .getAnimation(Identifier.of("saturnclient", emoteIdOrNull)) + // .playAnimation()); + // } else { + // animationStack.removeLayer(1000); + // } + + // break; + // } + } + + @Override + public void logInfo(String message) { + SaturnClient.LOGGER.info(message); + } + + @Override + public void logError(String message) { + SaturnClient.LOGGER.error(message); + } + + @Override + public void logError(String message, Throwable throwable) { + SaturnClient.LOGGER.error(message, throwable); + } + + @Override + public void registerBufferedImageTexture(IdentifierRef i, BufferedImage bi) { + IdentifierUtils.registerBufferedImageTextureFast(i, bi); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/ref/Matrix3x2fStackRef.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/ref/Matrix3x2fStackRef.java new file mode 100644 index 0000000..62d92e3 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/ref/Matrix3x2fStackRef.java @@ -0,0 +1,44 @@ +package org.saturnclient.impl.ref; + +import org.joml.Matrix3x2fStack; +import org.saturnclient.common.ref.render.MatrixStackRef; + +public class Matrix3x2fStackRef implements MatrixStackRef { + public final Matrix3x2fStack stack; + + public Matrix3x2fStackRef(Matrix3x2fStack stack) { + this.stack = stack; + } + + @Override + public void push() { + stack.pushMatrix(); + } + + @Override + public void pop() { + stack.popMatrix(); + } + + @Override + public void scale(float x, float y) { + stack.scale(x, y); + } + + @Override + public void translate(float x, float y) { + stack.translate(x, y); + } + + @Override + public void rotate(float angle) { + stack.rotate((float) Math.toRadians(angle)); + } + + @Override + public void rotate(float angle, float originX, float originY, float originZ) { + translate(originX, originY); + rotate(angle); + translate(-originX, -originY); + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/EntityDrawerImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/EntityDrawerImpl.java new file mode 100644 index 0000000..c2bfe84 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/EntityDrawerImpl.java @@ -0,0 +1,70 @@ +package org.saturnclient.impl.ui; + +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.components.SkinPreview.EntityDrawer; + +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.state.EntityRenderState; +import net.minecraft.entity.LivingEntity; + +public class EntityDrawerImpl implements EntityDrawer { + @Override + public void render(RenderScope renderScope, boolean negativeAngle, float angle, float scale, ElementContext ctx) { + LivingEntity entity = SaturnClient.client.player; + + if (entity != null) { + drawEntity(renderScope, 0, 0, 75, 78, 30, 0.0625F, ctx.mouseX / scale, ctx.mouseY / scale, + SaturnClient.client.player); + } + } + + public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float scale, + float mouseX, float mouseY, LivingEntity entity) { + float f = (float) (x1 + x2) / 2.0F; + float g = (float) (y1 + y2) / 2.0F; + renderScope.enableScissor(x1, y1, x2, y2); + float h = (float) Math.atan((double) ((f - mouseX) / 40.0F)); + float i = (float) Math.atan((double) ((g - mouseY) / 40.0F)); + Quaternionf quaternionf = (new Quaternionf()).rotateZ((float) Math.PI); + Quaternionf quaternionf2 = (new Quaternionf()).rotateX(i * 20.0F * ((float) Math.PI / 180F)); + quaternionf.mul(quaternionf2); + float j = entity.bodyYaw; + float k = entity.getYaw(); + float l = entity.getPitch(); + float m = entity.lastHeadYaw; + float n = entity.headYaw; + entity.bodyYaw = 180.0F + h * 20.0F; + entity.setYaw(180.0F + h * 40.0F); + entity.setPitch(-i * 20.0F); + entity.headYaw = entity.getYaw(); + entity.lastHeadYaw = entity.getYaw(); + float o = entity.getScale(); + Vector3f vector3f = new Vector3f(0.0F, entity.getHeight() / 2.0F + scale * o, 0.0F); + float p = (float) size / o; + drawEntity(renderScope, x1, y1, x2, y2, p, vector3f, quaternionf, quaternionf2, entity); + entity.bodyYaw = j; + entity.setYaw(k); + entity.setPitch(l); + entity.lastHeadYaw = m; + entity.headYaw = n; + renderScope.disableScissor(); + } + + public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, float scale, + Vector3f translation, Quaternionf rotation, @Nullable Quaternionf overrideCameraAngle, + LivingEntity entity) { + EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher(); + EntityRenderer entityRenderer = entityRenderDispatcher.getRenderer(entity); + EntityRenderState entityRenderState = entityRenderer.getAndUpdateRenderState(entity, 1.0F); + entityRenderState.hitbox = null; + + if (renderScope instanceof RenderScopeImpl i) + i.addEntity(entityRenderState, scale, translation, rotation, overrideCameraAngle, x1, y1, x2, y2); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java new file mode 100644 index 0000000..b613ad4 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java @@ -0,0 +1,347 @@ +package org.saturnclient.impl.ui; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; +import org.joml.Matrix3x2fStack; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import com.mojang.blaze3d.textures.GpuTextureView; + +import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.common.ref.asset.SpriteRef; +import org.saturnclient.common.ref.game.ItemStackRef; +import org.saturnclient.common.ref.render.MatrixStackRef; +import org.saturnclient.impl.ref.Matrix3x2fStackRef; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.saturnclient.mixin.DrawContextAccessor; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.ui.resources.SvgTexture; + +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.ScreenRect; +import net.minecraft.client.gui.render.state.ColoredQuadGuiElementRenderState; +import net.minecraft.client.gui.render.state.GuiRenderState; +import net.minecraft.client.gui.render.state.ItemGuiElementRenderState; +import net.minecraft.client.gui.render.state.TextGuiElementRenderState; +import net.minecraft.client.gui.render.state.TexturedQuadGuiElementRenderState; +import net.minecraft.client.gui.render.state.special.EntityGuiElementRenderState; +import net.minecraft.client.render.entity.state.EntityRenderState; +import net.minecraft.client.render.item.KeyedItemRenderState; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.TextureSetup; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemDisplayContext; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.crash.CrashException; +import net.minecraft.util.crash.CrashReport; +import net.minecraft.util.crash.CrashReportSection; +import net.minecraft.world.World; + +public class RenderScopeImpl implements RenderScope { + + public final Matrix3x2fStackRef matrices; + public final GuiRenderState state; + + private ScissorStack scissorStack; + private int opacity = 0xFF << 24; + + public RenderScopeImpl(DrawContext context) { + this(context.getMatrices(), ((DrawContextAccessor) (Object) context).getState()); + } + + public RenderScopeImpl(Matrix3x2fStack matrices, GuiRenderState state) { + this.matrices = new Matrix3x2fStackRef(matrices); + this.state = state; + this.scissorStack = new ScissorStack(); + } + + @Override + public MatrixStackRef getMatrixStack() { + return matrices; + } + + @Override + public void setOpacity(float alpha) { + this.opacity = ((int) (alpha * 255)) << 24; + } + + @Override + public int getColor(int color) { + int originalAlpha = (color >>> 24) & 0xFF; + int newAlpha = (opacity >>> 24) & 0xFF; + int mixedAlpha = (originalAlpha * newAlpha) / 255; + return (mixedAlpha << 24) | (color & 0x00FFFFFF); + } + + @Override + public void fill(int x1, int y1, int x2, int y2, int color) { + this.state.addSimpleElement( + new ColoredQuadGuiElementRenderState( + RenderPipelines.GUI, + TextureSetup.empty(), + new Matrix3x2f(this.matrices.stack), + x1, y1, x2, y2, color, color, + this.scissorStack.peekLast())); + } + + @Override + public void drawRectangle(int x, int y, int width, int height, int color) { + if (color == 0) + return; + color = getColor(color); + + int x1 = x; + int x2 = x + width; + int y1 = y; + int y2 = y + height; + + if (x2 < x1) { + int tmp = x1; + x1 = x2; + x2 = tmp; + } + if (y2 < y1) { + int tmp = y1; + y1 = y2; + y2 = tmp; + } + + this.fill(x1, y1, x2, y2, color); + } + + @Override + public void drawText(String text, int x, int y, int font, int color) { + drawText(1.0f, text, x, y, font, color); + } + + @Override + public void drawText(float scale, String text, int x, int y, int font, int color) { + if (color == 0) + return; + if (font == 0) + scale *= 2; + + int lineIndex = 0; + for (String line : text.split("\n")) { + int finalColor = getColor(color); + + matrices.push(); + matrices.translate(x, y + (lineIndex * Fonts.getHeight())); + matrices.scale(scale, scale); + + this.state.addText( + new TextGuiElementRenderState( + SaturnClient.client.textRenderer, + ((Text) Fonts.setFont(line, font)).asOrderedText(), + new Matrix3x2f(this.matrices.stack), + 0, font == 0 ? 1 : 8, + finalColor, 0, false, this.scissorStack.peekLast())); + + matrices.pop(); + + lineIndex++; + } + } + + @Override + public int getScaledWindowWidth() { + return SaturnClient.client.getWindow().getScaledWidth(); + } + + @Override + public int getScaledWindowHeight() { + return SaturnClient.client.getWindow().getScaledHeight(); + } + + // Texture rendering + @Override + public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height) { + drawTexture(sprite, x, y, u, v, width, height, -1); + } + + @Override + public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, int color) { + drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color); + } + + @Override + public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, + int regionWidth, int regionHeight, int textureWidth, int textureHeight) { + drawTexture(sprite, x, y, u, v, width, height, regionWidth, regionHeight, textureWidth, textureHeight, -1); + } + + @Override + public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, + int regionWidth, int regionHeight, int textureWidth, int textureHeight, int color) { + drawTexturedQuad((Identifier) (Object) sprite, x, x + width, y, y + height, + (u + 0f) / textureWidth, + (u + regionWidth) / textureWidth, + (v + 0f) / textureHeight, + (v + regionHeight) / textureHeight, + color); + } + + private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2, + float u1, float u2, float v1, float v2, int color) { + if (color == 0) + return; + color = getColor(color); + + if (sprite.getPath().endsWith(".svg")) { + sprite = (Identifier) (Object) SvgTexture.getSvg( + (IdentifierRef) (Object) sprite, + (x2 - x1) * 2, (y2 - y1) * 2); + } + + GpuTextureView gpuTextureView = SaturnClient.client.getTextureManager() + .getTexture(sprite).getGlTextureView(); + + this.state.addSimpleElement(new TexturedQuadGuiElementRenderState( + RenderPipelines.GUI_TEXTURED, + TextureSetup.withoutGlTexture(gpuTextureView), + new Matrix3x2f(this.matrices.stack), + x1, y1, x2, y2, u1, u2, v1, v2, color, + this.scissorStack.peekLast())); + } + + // Scissor management + @Override + public void enableScissor(int x1, int y1, int x2, int y2) { + ScreenRect rect = new ScreenRect(x1, y1, x2 - x1, y2 - y1).transform(this.matrices.stack); + scissorStack.push(rect); + } + + @Override + public void disableScissor() { + scissorStack.pop(); + } + + @Override + public boolean scissorContains(int x, int y) { + return scissorStack.contains(x, y); + } + + public static class ScissorStack { + private final Deque stack = new ArrayDeque<>(); + + public ScreenRect push(ScreenRect rect) { + ScreenRect last = stack.peekLast(); + if (last != null) { + ScreenRect intersection = Objects.requireNonNullElse(rect.intersection(last), ScreenRect.empty()); + stack.addLast(intersection); + return intersection; + } else { + stack.addLast(rect); + return rect; + } + } + + @Nullable + public ScreenRect pop() { + if (stack.isEmpty()) + throw new IllegalStateException("Scissor stack underflow"); + stack.removeLast(); + return stack.peekLast(); + } + + @Nullable + public ScreenRect peekLast() { + return stack.peekLast(); + } + + public boolean contains(int x, int y) { + return stack.isEmpty() || stack.peekLast().contains(x, y); + } + } + + // Item rendering + @Override + public void drawItem(ItemStackRef stack, int x, int y) { + drawItem(null, SaturnClient.client.world, stack, x, y, 0); + } + + private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStackRef stack, int x, int y, + int seed) { + drawItem(entity, world, (ItemStack) (Object) stack, x, y, seed, 0); + } + + private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed, + int z) { + if (!stack.isEmpty()) { + KeyedItemRenderState keyedItemRenderState = new KeyedItemRenderState(); + SaturnClient.client.getItemModelManager().clearAndUpdate(keyedItemRenderState, stack, + ItemDisplayContext.GUI, world, + entity, seed); + + try { + this.state.addItem(new ItemGuiElementRenderState(stack.getItem().getName().toString(), + new Matrix3x2f(this.matrices.stack), keyedItemRenderState, x, y, + this.scissorStack.peekLast())); + } catch (Throwable throwable) { + CrashReport crashReport = CrashReport.create(throwable, "Rendering item"); + CrashReportSection crashReportSection = crashReport.addElement("Item being rendered"); + crashReportSection.add("Item Type", () -> String.valueOf(stack.getItem())); + crashReportSection.add("Item Components", () -> String.valueOf(stack.getComponents())); + crashReportSection.add("Item Foil", () -> String.valueOf(stack.hasGlint())); + throw new CrashException(crashReport); + } + } + } + + @Override + public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height) { + drawSpriteStretched(sprite, x, y, width, height, -1); + } + + @Override + public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height, int color) { + Sprite mcSprite = (Sprite) sprite; + if (color == 0 || width == 0 || height == 0) + return; + + drawTexturedQuad(mcSprite.getAtlasId(), x, x + width, y, y + height, + mcSprite.getMinU(), mcSprite.getMaxU(), mcSprite.getMinV(), mcSprite.getMaxV(), color); + } + + @Override + public void draw() { + // No-op for now + } + + public void drawBorder(int x, int y, int width, int height, int color) { + fill(x, y, x + width, y + 1, color); + fill(x, y + height - 1, x + width, y + height, color); + fill(x, y + 1, x + 1, y + height - 1, color); + fill(x + width - 1, y + 1, x + width, y + height - 1, color); + } + + public void addEntity(EntityRenderState entityState, float scale, Vector3f translation, Quaternionf rotation, + @Nullable Quaternionf overrideCameraAngle, int x1, int y1, int x2, int y2) { + + Matrix3x2f mat = this.matrices.stack; + float offsetX = mat.m20; + float offsetY = mat.m21; + float scaleX = mat.m00; // current matrix X scale + float scaleY = mat.m11; // current matrix Y scale + + int adjustedX1 = (int) (x1 * scaleX + offsetX); + int adjustedY1 = (int) (y1 * scaleY + offsetY); + int adjustedX2 = (int) (x2 * scaleX + offsetX); + int adjustedY2 = (int) (y2 * scaleY + offsetY); + + float adjustedScale = scale * scaleX; // apply matrix scale to entity scale too + + this.state.addSpecialElement(new EntityGuiElementRenderState(entityState, translation, rotation, + overrideCameraAngle, adjustedX1, adjustedY1, adjustedX2, adjustedY2, adjustedScale, + this.scissorStack.peekLast())); + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java b/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java new file mode 100644 index 0000000..3717c6f --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java @@ -0,0 +1,134 @@ +package org.saturnclient.impl.ui; + +import java.time.Duration; +import java.time.Instant; + +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.SaturnScreen; +import org.saturnclient.ui.SaturnScreen.ScreenProvider; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.CubeMapRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.RotatingCubeMapRenderer; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.texture.ResourceTexture; +import net.minecraft.client.texture.TextureManager; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class SaturnScreenFabric extends Screen implements ScreenProvider { + protected static final Identifier PANORAMA = Identifier.of("saturnclient", + "textures/gui/title/background/panorama"); + protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(PANORAMA); + + public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER; + + static { + ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER); + } + + public final SaturnScreen screen; + + public static void preload(MinecraftClient client) { + TextureManager textureManager = client.getTextureManager(); + for (int i = 0; i < 6; i++) { + String var10001 = PANORAMA.getPath(); + Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png"); + ResourceTexture resourceTexture = new ResourceTexture(tex); + textureManager.registerTexture(tex, resourceTexture); + } + } + + public SaturnScreenFabric(SaturnScreen screen) { + super(Text.literal(screen.title)); + screen.provider = this; + this.screen = screen; + } + + @Override + protected void init() { + screen.init(); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + if (screen.start == null) { + screen.start = Instant.now(); + } + long elapsed = Duration.between(screen.start, Instant.now()).toMillis(); + + mouseX *= 2; + mouseY *= 2; + + if (client.world == null && client.getCurrentServerEntry() == null) { + // ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true); + } + + RenderScope renderScope = new RenderScopeImpl(context); + + screen.render(renderScope, mouseX, mouseY, delta, elapsed); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (screen.mouseClicked(mouseX, mouseY, button)) { + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (screen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) { + return true; + } + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (screen.mouseReleased(mouseX, mouseY, button)) { + return true; + } + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + if (screen.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) { + return true; + } + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (screen.keyPressed(keyCode, scanCode, modifiers)) { + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public void resize(MinecraftClient client, int width, int height) { + screen.resize(width, height); + super.resize(client, width, height); + } + + @Override + public void close() { + this.screen.onClose(); + super.close(); + } + + @Override + public int getWidth() { + return width * 2; + } + + @Override + public int getHeight() { + return height * 2; + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/SaturnClient.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/SaturnClient.java new file mode 100644 index 0000000..c44e106 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/SaturnClient.java @@ -0,0 +1,64 @@ +package org.saturnclient.saturnclient; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.minecraft.client.MinecraftClient; + +import org.saturnclient.client.ServiceClient; +import org.saturnclient.cosmetics.Emotes; +import org.saturnclient.common.provider.Providers; +import org.saturnclient.impl.provider.GLFWProviderImpl; +import org.saturnclient.impl.provider.FeatureProviderFabric; +import org.saturnclient.impl.provider.RefConstructorImpl; +import org.saturnclient.impl.provider.SaturnProviderImpl; +import org.saturnclient.impl.ui.EntityDrawerImpl; +import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.mod.ModManager; +import org.saturnclient.saturnclient.event.KeyInputHandler; +import org.saturnclient.config.Config; +import org.saturnclient.config.ConfigManager; +import org.saturnclient.ui.components.SkinPreview; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Main class for the Saturn Client mod. + */ +public class SaturnClient implements ModInitializer { + + public static final String MOD_ID = "saturnclient"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static MinecraftClient client; + + @Override + public void onInitialize() { + LOGGER.info("Initializing " + MOD_ID); + client = MinecraftClient.getInstance(); + + Providers.saturn = new SaturnProviderImpl(); + Providers.refConstructor = new RefConstructorImpl(); + Providers.GLFW = new GLFWProviderImpl(); + Providers.feature = new FeatureProviderFabric(client); + SkinPreview.DRAWER = new EntityDrawerImpl(); + + Config.init(); + ModManager.init(); + + client.execute(() -> { + SaturnScreenFabric.preload(client); + }); + + ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save()); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + for (org.saturnclient.mod.Mod m : ModManager.ENABLED_MODS) { + m.tick(); + } + }); + + KeyInputHandler.register(); + Emotes.initialize(); + ServiceClient.initialize(); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java new file mode 100644 index 0000000..b1bae03 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java @@ -0,0 +1,56 @@ +package org.saturnclient.saturnclient.event; + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; + +import org.saturnclient.config.Config; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.common.provider.Providers; +import org.saturnclient.ui.screens.EmoteWheel; +import org.saturnclient.ui.screens.ShiftMenu; + +// import dev.kosmx.playerAnim.api.layered.AnimationStack; +// import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; + +import org.lwjgl.glfw.GLFW; + +/** + * Handles keyboard input events for the Saturn Client. + * Manages keybindings and their associated actions. + */ +public class KeyInputHandler { + private static final String KEY_CATEGORY = "key.category.saturnclient"; + private static final String MAIN_MENU_KEY_ID = "key.saturnclient.main_menu"; + private static final int DEFAULT_MENU_KEY = GLFW.GLFW_KEY_RIGHT_SHIFT; + private static KeyBinding mainMenuKeyBinding; + + public static void register() { + mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding( + MAIN_MENU_KEY_ID, + InputUtil.Type.KEYSYM, + DEFAULT_MENU_KEY, + KEY_CATEGORY)); + + // Register the event handler for the main menu key + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (mainMenuKeyBinding.wasPressed()) { + Providers.saturn.getClient().setScreen(new ShiftMenu()); + } + + if (Config.openEmoteWheel.wasKeyPressed() && client.currentScreen == null + && !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) { + Providers.saturn.getClient().setScreen(new EmoteWheel()); + } + + if (SaturnClient.client.player != null && SaturnClient.client.player.isSneaking()) { + // AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player); + // if (animationStack.isActive() && animationStack.getPriority() == 1000) { + // animationStack.removeLayer(1000); + // ServiceClient.emote(""); + // } + } + }); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/DrawContextAccessor.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/DrawContextAccessor.java new file mode 100644 index 0000000..9c2d8f0 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/DrawContextAccessor.java @@ -0,0 +1,12 @@ +package org.saturnclient.saturnclient.mixin; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.render.state.GuiRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DrawContext.class) +public interface DrawContextAccessor { + @Accessor("state") + GuiRenderState getState(); +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java new file mode 100644 index 0000000..518494c --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/NameTagMixin.java @@ -0,0 +1,93 @@ +package org.saturnclient.saturnclient.mixin; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.font.TextRenderer.TextLayerType; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.state.EntityRenderState; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.Vec3d; + +import org.saturnclient.client.player.Roles; +import org.saturnclient.client.player.SaturnPlayer; +import org.saturnclient.saturnclient.SaturnClient; + +import java.util.UUID; + +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(value = EntityRenderer.class, priority = 1800) +public abstract class NameTagMixin { + @Shadow + public EntityRenderDispatcher dispatcher; + + /** + * @author HexLeo + * @reason Adds the Saturn Client icon to the player's name if they are online + * with Saturn Client + */ + @Inject(method = "renderLabelIfPresent(Lnet/minecraft/client/render/entity/state/EntityRenderState;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At("HEAD"), cancellable = true) + private void onRenderLabelIfPresent(S state, Text text, MatrixStack matrices, + VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { + + UUID uuid = isSaturn(state); + + if (uuid != null) { + MutableText iconText = Text.literal(Roles.getSaturnIndicator()) + .styled(style -> style.withColor(Roles.getIconColor(uuid))); + + Text nameText = text.copy().styled(style -> style.withColor(Formatting.WHITE)); + + renderLabel(state, iconText.append(nameText), matrices, + vertexConsumers, light); + + ci.cancel(); + } + } + + private UUID isSaturn(S state) { + if (state instanceof PlayerEntityRenderState) { + String name = ((PlayerEntityRenderState) state).name; + SaturnPlayer player = SaturnPlayer.get(name); + if (player != null) { + return player.uuid; + } + } + + return null; + } + + private void renderLabel(S state, Text text, MatrixStack matrices, + VertexConsumerProvider vertexConsumers, int light) { + Vec3d vec3d = state.nameLabelPos; + if (vec3d != null) { + boolean bl = !state.sneaking; + int i = "deadmau5".equals(text.getString()) ? -10 : 0; + matrices.push(); + matrices.translate(vec3d.x, vec3d.y + 0.5, vec3d.z); + matrices.multiply(this.dispatcher.getRotation()); + matrices.scale(0.025F, -0.025F, 0.025F); + Matrix4f matrix4f = matrices.peek().getPositionMatrix(); + TextRenderer textRenderer = SaturnClient.client.textRenderer; + float f = (float) (-textRenderer.getWidth(text)) / 2.0F; + int j = (int) (SaturnClient.client.options.getTextBackgroundOpacity(0.25F) * 255.0F) << 24; + textRenderer.draw(text, f, (float) i, -2130706433, false, matrix4f, vertexConsumers, + bl ? TextLayerType.SEE_THROUGH : TextLayerType.NORMAL, j, light); + if (bl) { + textRenderer.draw(text, f, (float) i, -1, false, matrix4f, vertexConsumers, TextLayerType.NORMAL, 0, + light); + } + matrices.pop(); + } + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/PlayerEntityRendererMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/PlayerEntityRendererMixin.java new file mode 100644 index 0000000..0363dad --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/PlayerEntityRendererMixin.java @@ -0,0 +1,30 @@ +package org.saturnclient.saturnclient.mixin; + +import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.entity.player.PlayerEntity; + +import org.saturnclient.impl.cosmetics.CloakFeatureRenderer; +import org.saturnclient.impl.cosmetics.HatFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerEntityRenderer.class) +public abstract class PlayerEntityRendererMixin + extends LivingEntityRenderer { + + protected PlayerEntityRendererMixin() { + super(null, null, 0.0f); + } + + @Inject(method = "", at = @At("TAIL")) + private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) { + this.addFeature(new HatFeatureRenderer(this)); + this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader())); + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java new file mode 100644 index 0000000..061d641 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java @@ -0,0 +1,27 @@ +package org.saturnclient.saturnclient.mixin; + +import org.saturnclient.config.Config; +import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.ui.screens.TitleMenu; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.TitleScreen; + +@Mixin(MinecraftClient.class) +public class SetScreenMixin { + @ModifyVariable(method = "setScreen", at = @At("HEAD"), argsOnly = true) + private Screen replaceTitleScreen(Screen screen) { + if (Config.saturnTitleScreen.value) { + if (screen instanceof TitleScreen) + return new SaturnScreenFabric(new TitleMenu()); + if (screen == null && ((MinecraftClient) (Object) this).world == null) + return new SaturnScreenFabric(new TitleMenu()); + } + + return screen; + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java new file mode 100644 index 0000000..ecc4823 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -0,0 +1,161 @@ +package org.saturnclient.saturnclient.mixin; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.saturnclient.config.Theme; +import org.saturnclient.impl.ui.RenderScopeImpl; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Textures; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Final; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.SplashOverlay; +import net.minecraft.resource.ResourceReload; +import net.minecraft.util.Util; +import net.minecraft.util.math.MathHelper; + +@Mixin(SplashOverlay.class) +public abstract class SplashOverlayMixin { + + @Shadow + private MinecraftClient client; + @Shadow + private ResourceReload reload; + + @Shadow + private long reloadCompleteTime; + + @Shadow + private long reloadStartTime; + + @Shadow + @Final + private boolean reloading; + + @Shadow + @Final + private Consumer> exceptionHandler; + + @Shadow + private float progress; + + /** + * @author SaturnClient + * @reason Replace vanilla splash rendering completely but keep exit logic + */ + @Overwrite + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + int screenWidth = context.getScaledWindowWidth(); + int screenHeight = context.getScaledWindowHeight(); + long currentTime = Util.getMeasuringTimeMs(); + + // Initialize reload start time if not set + if (reloadStartTime == -1L) { + reloadStartTime = currentTime; + } + + // Compute fade-out progress after reload completes + float fadeOutProgress = reloadCompleteTime > -1L + ? (float) (currentTime - reloadCompleteTime) / 1000f + : -1f; + + int alpha = fadeOutProgress >= 0f + ? MathHelper.ceil((1.0f - MathHelper.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f) + : 255; + + // Compute fade-in progress while reloading + float reloadProgress = reloadStartTime > -1L + ? (float) (currentTime - reloadStartTime) / 500f + : -1f; + + // Get render scope for better UI + RenderScope scope = new RenderScopeImpl(context); + + // Draw background overlay + if (fadeOutProgress >= 1.0F) { + renderCurrentScreen(context, mouseX, mouseY, delta); + int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f); + scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, + Theme.withAlpha(overlayAlpha, 0xFFFFFFFF)); + } else if (reloading) { + if (reloadProgress < 1.0F) { + renderCurrentScreen(context, mouseX, mouseY, delta); + } + scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, + Theme.withAlpha(alpha, 0xFFFFFFFF)); + } else { + scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight, + Theme.withAlpha(alpha, 0xFFFFFFFF)); + } + + float u = this.reload.getProgress(); + this.progress = MathHelper.clamp(this.progress * 0.95F + u * 0.050000012F, 0.0F, 1.0F); + + // Draw central logo + drawLogo(scope, screenWidth, screenHeight, alpha); + renderProgressBar(scope, context, screenWidth, screenHeight, alpha); + + // Remove overlay after fade-out completes + if (fadeOutProgress >= 2.0F) { + client.setOverlay(null); + } + + // Handle reload completion and exceptions + handleReloadCompletion(context); + } + + private void renderCurrentScreen(DrawContext context, int mouseX, int mouseY, float delta) { + if (client.currentScreen != null) { + client.currentScreen.render(context, mouseX, mouseY, delta); + } + } + + /** Draw the main logo centered */ + private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) { + scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60, + Theme.withAlpha(alpha, 0xFFFFFFFF)); + } + + private void renderProgressBar(RenderScope scope, DrawContext context, int screenWidth, int screenHeight, + int alpha) { + int progressY = ((screenHeight - 5) / 2) + 25; + context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF)); + + int progressWidth = Math.min((int) (progress * 120), 120); + int progressX = (screenWidth - 120) / 2; + + context.fill(progressX, progressY, progressX + progressWidth, progressY + 5, + Theme.withAlpha(alpha, 0xFFFFFFFF)); + } + + /** Compute overlay alpha from fade progress */ + private int computeOverlayAlpha(float progress) { + return MathHelper.ceil((1.0F - MathHelper.clamp(progress, 0.0F, 1.0F)) * 255.0F); + } + + /** Handle reload completion logic */ + private void handleReloadCompletion(DrawContext context) { + float reloadProgress = reloadStartTime > -1L ? (float) (Util.getMeasuringTimeMs() - reloadStartTime) / 500f + : -1f; + + if (reloadCompleteTime == -1L && reload.isComplete() && (!reloading || reloadProgress >= 2.0F)) { + try { + reload.throwException(); + exceptionHandler.accept(Optional.empty()); + } catch (Throwable ex) { + exceptionHandler.accept(Optional.of(ex)); + } + + reloadCompleteTime = Util.getMeasuringTimeMs(); + + if (client.currentScreen != null) { + client.currentScreen.init(client, context.getScaledWindowWidth(), context.getScaledWindowHeight()); + } + } + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java new file mode 100644 index 0000000..5dd00f6 --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/TabListMixin.java @@ -0,0 +1,43 @@ +package org.saturnclient.saturnclient.mixin; + +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; +import org.saturnclient.client.player.Roles; +import org.saturnclient.client.player.SaturnPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import com.mojang.authlib.GameProfile; + +@Mixin(PlayerListEntry.class) +public abstract class TabListMixin { + + @Shadow + private GameProfile profile; + + /** + * @author HexLeo + * @reason Adds the Saturn Client icon to the player's name if they are online + * with Saturn Client + */ + @Overwrite + @Nullable + public Text getDisplayName() { + GameProfile profile = ((PlayerListEntry) (Object) this).getProfile(); + String name = profile.getName(); + UUID uuid = profile.getId(); + + if (SaturnPlayer.get(uuid) != null) { + return Text.literal(Roles.getSaturnIndicator()) + .styled(style -> style.withColor(Roles.getIconColor(uuid))) + .append(Text.literal(name).styled(style -> style.withColor(Formatting.WHITE))); + } + return Text.literal(name); + } +} diff --git a/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/WhoAmI.java b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/WhoAmI.java new file mode 100644 index 0000000..041e0cf --- /dev/null +++ b/versions/1.21.7/src/main/java/org/saturnclient/saturnclient/mixin/WhoAmI.java @@ -0,0 +1,32 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Original code from FokshaWasTaken (GitHub), + * Repository: https://github.com/FokshaWasTaken/who-am-i/ +*/ + +package org.saturnclient.saturnclient.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LivingEntityRenderer.class) +public class WhoAmI { + @ModifyExpressionValue(method = "hasLabel(Lnet/minecraft/entity/LivingEntity;D)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;getCameraEntity()Lnet/minecraft/entity/Entity;")) + public Entity obfuscateCameraEntity(Entity original) { + // hasLabel checks if the rendered entity is equal to the camera entity (aka our + // player), + // so by setting the camera entity to null in this instance makes the check + // always succeed + // this also allows to keep the other checks in mojang's code, such as checking + // if the hud is enabled etc. + return null; + } +} \ No newline at end of file diff --git a/versions/1.21.7/src/main/resources/fabric.mod.json b/versions/1.21.7/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..b5d0419 --- /dev/null +++ b/versions/1.21.7/src/main/resources/fabric.mod.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": 1, + "id": "saturnclient", + "version": "${version}", + "name": "saturnclient", + "description": "The ultimate open source client", + "authors": ["Saturn", "ORUS"], + "contact": { + "website": "https://selimaj.dev", + "sources": "https://github.com/saturnclientmc/saturnclient" + }, + "license": "Apache-2.0", + "icon": "assets/saturnclient/icon.png", + "environment": "client", + "entrypoints": { + "main": ["org.saturnclient.saturnclient.SaturnClient"] + }, + "mixins": [ + "saturnclient.mixins.json", + "features.mixins.json", + "impl.mixins.json" + ], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric": "*", + "minecraft": "${minecraft_version}" + } +} diff --git a/versions/1.21.7/src/main/resources/features.mixins.json b/versions/1.21.7/src/main/resources/features.mixins.json new file mode 100644 index 0000000..8d3c331 --- /dev/null +++ b/versions/1.21.7/src/main/resources/features.mixins.json @@ -0,0 +1,21 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.saturnclient.impl.features.mixins", + "compatibilityLevel": "JAVA_21", + "client": [ + "render.GameRendererMixin", + "render.LightmapTextureManagerMixin", + "render.LivingEntityRendererMixin", + "render.LivingEntityRenderStateMixin", + "render.BackgroundRendererMixin", + "misc.NametagsMixin", + "render.RenderMixin", + "world.TpsMixin", + "entity.CameraMixin", + "entity.EntityMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/versions/1.21.7/src/main/resources/impl.mixins.json b/versions/1.21.7/src/main/resources/impl.mixins.json new file mode 100644 index 0000000..ef9d58f --- /dev/null +++ b/versions/1.21.7/src/main/resources/impl.mixins.json @@ -0,0 +1,19 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.saturnclient.impl.mixins", + "compatibilityLevel": "JAVA_21", + "client": [ + "IdentifierMixin", + "ItemStackMixin", + "MinecraftClientMixin", + "QuaternionfMixin", + "WindowMixin", + "TextMixin", + "SpriteMixin", + "StatusEffectMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/versions/1.21.7/src/main/resources/saturnclient.mixins.json b/versions/1.21.7/src/main/resources/saturnclient.mixins.json new file mode 100644 index 0000000..3bc9ccc --- /dev/null +++ b/versions/1.21.7/src/main/resources/saturnclient.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.saturnclient.saturnclient.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "TabListMixin", + "NameTagMixin", + "PlayerEntityRendererMixin", + "WhoAmI", + "DrawContextAccessor", + "SplashOverlayMixin", + "SetScreenMixin" + ], + "injectors": { + "defaultRequire": 1 + } +}