Fixed freelook

This commit is contained in:
2026-03-20 19:22:59 +01:00
parent 462b6fc462
commit 787ffd7bcb
104 changed files with 122 additions and 7361 deletions
-96
View File
@@ -1,96 +0,0 @@
plugins {
id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"
id 'maven-publish'
}
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
}
repositories {
maven { name = "KoSmX Maven"; url = 'https://maven.kosmx.dev/' }
}
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}"
)
// 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}"}
}
}
// 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.
}
}
-23
View File
@@ -1,23 +0,0 @@
# 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.1
yarn_mappings=1.21.1+build.3
loader_version=0.18.4
loom_version=1.15-SNAPSHOT
# Mod Properties
mod_version=1.0.0
maven_group=org.saturnclient
archives_base_name=saturn-client
# Dependencies
fabric_api_version=0.116.9+1.21.1
player_anim = 2.0.4+1.21.1
-10
View File
@@ -1,10 +0,0 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
@@ -1,411 +0,0 @@
package org.saturnclient.impl.cosmetics;
import java.util.Arrays;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.config.Config;
import org.saturnclient.cosmetics.Cloaks;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
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.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
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<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
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<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> featureRendererContext) {
super(featureRendererContext);
Arrays.fill(segmentValues, 0.0f);
}
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, 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;
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta,
float animationProgress, float headYaw, float headPitch) {
if (entity.isInvisible() || entity.getSkinTextures().capeTexture() != null) {
return;
}
SaturnPlayer player = SaturnPlayer.get(entity.getUuid());
if (player == null) {
return;
}
ItemStack chestStack = entity.getInventory().armor.get(1);
Identifier customCape = (Identifier) (Object) Cloaks.getCurrentCloakTexture(player.uuid);
if (customCape == null || chestStack.itemMatches(Items.ELYTRA.getRegistryEntry())) {
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 = vertexConsumers.getBuffer(RenderLayer.getEntityAlpha(customCape));
matrices.push();
matrices.translate(0.0f, 0.0f, 0.12f);
if (!chestStack.isEmpty()) {
matrices.translate(0.0F, -0.053125F, 0.06875F);
}
if (entity.isInSneakingPose()) {
matrices.translate(0, 0.16f, 0.0f);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f));
}
long now = System.currentTimeMillis();
if (now - lastUpdate >= 20) {
float velX = (float) (MathHelper.lerp((double) tickDelta, entity.prevCapeX,
entity.capeX)
- MathHelper.lerp((double) tickDelta, entity.prevX, entity.getX()));
float velZ = (float) (MathHelper.lerp((double) tickDelta, entity.prevCapeZ,
entity.capeZ)
- MathHelper.lerp((double) tickDelta, entity.prevZ, entity.getZ()));
float rawVelY = (float) (MathHelper.lerp((double) tickDelta, entity.prevCapeY, entity.capeY)
- MathHelper.lerp((double) tickDelta, entity.prevY, entity.getY()));
float velHorizontal = Math.min(0.6f, velZ > velX ? velZ : velX);
float velY = (rawVelY > 0.25f ? rawVelY : 0.0f);
float value = entity.isSwimming() ? 0.0f : Math.max(0.0f, Math.min(2.0f, velHorizontal + velY));
if (Config.cloakPhysics.value) {
updateSegmentValues(value);
} else {
float last = segmentValues[0];
float maxStep = 0.02f;
float next = value;
if (next > last)
next = Math.min(next, last + maxStep);
else if (next < last)
next = Math.max(next, last - maxStep);
Arrays.fill(segmentValues, next);
}
lastUpdate = now;
}
renderCape(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV);
matrices.pop();
}
// 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;
// }
// Identifier customCape = (Identifier) (Object)
// Cloaks.getCurrentCloakTexture(player.uuid);
// 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(RenderLayer.getEntityAlpha(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(1.0f, playerEntityRenderState.field_53537 / 108.0f);
// float rawVelY = playerEntityRenderState.field_53536;
// float velY = (rawVelY > 4.0f ? rawVelY : 0.0f) / 16;
// float value = playerEntityRenderState.isSwimming ? 0.0f : velX + velY;
// if (Config.cloakPhysics.value) {
// updateSegmentValues(value);
// } else {
// float last = segmentValues[0];
// float maxStep = 0.02f;
// float next = value;
// if (next > last)
// next = Math.min(next, last + maxStep);
// else if (next < last)
// next = Math.max(next, last - maxStep);
// Arrays.fill(segmentValues, next);
// }
// lastUpdate = now;
// }
// renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light,
// OverlayTexture.DEFAULT_UV);
// matrixStack.pop();
// }
}
@@ -1,43 +0,0 @@
package org.saturnclient.impl.cosmetics;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.impl.cosmetics.obj.MtlLoader;
import org.saturnclient.impl.cosmetics.obj.ObjModel;
import net.minecraft.client.network.AbstractClientPlayerEntity;
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.util.math.MatrixStack;
public class HatFeatureRenderer
extends FeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
public HatFeatureRenderer(
FeatureRendererContext<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> featureRendererContext) {
super(featureRendererContext);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta,
float animationProgress, float headYaw, float headPitch) {
SaturnPlayer player = SaturnPlayer.get(entity.getUuid());
if (entity.isInvisible() || player == null || player.hat.isEmpty()) {
return;
}
matrices.push();
this.getContextModel().head.rotate(matrices);
ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat),
matrices, vertexConsumers, light,
OverlayTexture.DEFAULT_UV);
matrices.pop();
}
}
@@ -1,56 +0,0 @@
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<Identifier, Map<String, Mtl>> loadedMtls = new HashMap<>();
static Map<String, Mtl> loadMtl(Identifier mtlId) throws IOException {
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
.getResource(mtlId).get().getInputStream()) {
List<Mtl> mtlList = MtlReader.read(is);
Map<String, Mtl> mtlMap = new java.util.LinkedHashMap<>();
for (Mtl mtl : mtlList) {
mtlMap.put(mtl.getName(), mtl);
}
return mtlMap;
}
}
public static Map<String, Mtl> of(Identifier mtlId) {
if (loadedMtls.containsKey(mtlId))
return loadedMtls.get(mtlId);
try {
Map<String, Mtl> mtl = loadMtl(mtlId);
loadedMtls.put(mtlId, mtl);
return mtl;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static Map<String, Mtl> 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"));
}
}
@@ -1,102 +0,0 @@
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<Identifier, ObjModel> 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> 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<String, Mtl> 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();
}
}
@@ -1,111 +0,0 @@
package org.saturnclient.impl.cosmetics.obj;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import de.javagl.obj.*;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
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<String, Mtl> mtlMap, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, int overlay) {
MatrixStack.Entry entry = matrices.peek();
Map<String, Obj> materialGroups = ObjSplitting.splitByMaterialGroups(obj);
for (Map.Entry<String, Obj> 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
Identifier texture = Identifier.of("minecraft:textures/misc/white.png");
if (mtl != null) {
String mapKd = mtl.getMapKd();
if (mapKd != null) {
// "#key" references are not resolvable at runtime without a texture map,
// so we only support direct resource location strings here
if (!mapKd.startsWith("#")) {
texture = Identifier.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(RenderLayer.getEntityAlpha(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);
}
}
@@ -1,70 +0,0 @@
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.setColor(x, y, pixels[index++]);
}
}
} else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
nativeImage.setColor(x, y, bufferedImage.getRGB(x, y));
}
}
}
NativeImageBackedTexture texture = new NativeImageBackedTexture(nativeImage);
SaturnClient.client.execute(() ->
SaturnClient.client.getTextureManager().registerTexture(identifier, texture)
);
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to register texture: {}", identifierRef, e);
}
}
public static void registerBufferedImageTexturesBulk(Map<IdentifierRef, BufferedImage> textures) {
long start = System.currentTimeMillis();
textures.forEach(IdentifierUtils::registerBufferedImageTextureFast);
long end = System.currentTimeMillis();
SaturnClient.LOGGER.info(
"Registered {} textures in {}ms",
textures.size(),
(end - start)
);
}
}
@@ -1,13 +0,0 @@
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();
}
@@ -1,19 +0,0 @@
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();
}
@@ -1,59 +0,0 @@
package org.saturnclient.impl.mixins;
import org.joml.Quaternionf;
import org.saturnclient.common.ref.render.MatrixStackRef;
import org.saturnclient.common.ref.render.QuaternionfRef;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.client.util.math.MatrixStack;
@Mixin(MatrixStack.class)
public abstract class MatrixStackMixin implements MatrixStackRef {
@Shadow
public abstract void translate(double x, double y, double z);
@Shadow
public abstract void translate(float x, float y, float z);
@Shadow
public abstract void scale(float x, float y, float z);
@Shadow
public abstract void multiply(Quaternionf quaternion);
@Shadow
public abstract void multiply(Quaternionf quaternion, float originX, float originY, float originZ);
@Shadow
public abstract void push();
@Shadow
public abstract void pop();
@Invoker("peek")
public abstract MatrixStack.Entry peekM();
@Shadow
public abstract boolean isEmpty();
@Shadow
public abstract void loadIdentity();
@Override
public void multiply(QuaternionfRef quaternion) {
this.multiply((Quaternionf) quaternion);
}
@Override
public void multiply(QuaternionfRef quaternion, float originX, float originY, float originZ) {
this.multiply((Quaternionf) quaternion, originX, originY, originZ);
}
@Override
public Object peek() {
return this.peekM();
}
}
@@ -1,130 +0,0 @@
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);
}
}
@@ -1,9 +0,0 @@
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 {
}
@@ -1,9 +0,0 @@
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 {
}
@@ -1,24 +0,0 @@
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);
}
}
@@ -1,22 +0,0 @@
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();
}
@@ -1,80 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.entity.LivingEntity;
import org.saturnclient.common.module.EntityModule;
/**
* Fabric implementation of {@link EntityModule}.
*
* Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}.
* Entity state for nametag rendering is supplied at render time by
* {@link org.saturnclient.modules.mixins.LivingEntityRendererMixin} which
* writes into the render state; {@link NametagsMixin} then constructs
* a {@link RenderStateEntityState} from the already-extracted snapshot.
*/
public class EntityModuleFabric implements EntityModule {
private final MinecraftClient mc;
public EntityModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
@Override
public boolean isTargetingLivingEntity() {
HitResult target = mc.crosshairTarget;
if (target == null || target.getType() != HitResult.Type.ENTITY)
return false;
Entity hit = ((EntityHitResult) target).getEntity();
return hit instanceof LivingEntity;
}
// ---------------------------------------------------------------
// Render-state adapter
// ---------------------------------------------------------------
/**
* Wraps a {@link HealthRenderState} (already injected into the
* Minecraft render-state object) as a platform-neutral
* {@link EntityState} for use by {@link NametagsFeature}.
*
* This is constructed inside {@link NametagsMixin} where both the
* render-state mixin interface and the entity name are available.
*/
public static final class RenderStateEntityState implements EntityState {
private final String customName;
private final HealthRenderState hrs;
public RenderStateEntityState(String customName, HealthRenderState hrs) {
this.customName = customName;
this.hrs = hrs;
}
@Override
public String getCustomName() {
return customName;
}
@Override
public float getHealth() {
return hrs.saturn$getHealth();
}
@Override
public float getMaxHealth() {
return hrs.saturn$getMaxHealth();
}
@Override
public EntityType getEntityType() {
return hrs.saturn$getEntityType();
}
}
}
@@ -1,20 +0,0 @@
package org.saturnclient.impl.modules;
import org.saturnclient.common.module.EntityModule;
/**
* 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);
EntityModule.EntityType saturn$getEntityType();
void saturn$setEntityType(EntityModule.EntityType type);
}
@@ -1,37 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.PlayerListEntry;
import org.saturnclient.common.module.NetworkModule;
/**
* Fabric implementation of {@link NetworkModule}.
*
* Connection presence is checked via the network handler.
* Ping is read from the player's {@link PlayerListEntry} which the
* server populates; returns {@code -1} when unavailable.
*/
public class NetworkModuleFabric implements NetworkModule {
private final MinecraftClient mc;
public NetworkModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
@Override
public boolean hasNetwork() {
return mc.getNetworkHandler() != null;
}
@Override
public int getPing() {
if (mc.player == null || mc.getNetworkHandler() == null)
return -1;
PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid());
return entry != null ? entry.getLatency() : -1;
}
}
@@ -1,302 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import org.saturnclient.common.module.PlayerModule;
import org.saturnclient.common.ref.asset.SpriteRef;
import org.saturnclient.common.ref.game.ItemStackRef;
import org.saturnclient.feature.features.StatusEffectsFeature;
import org.saturnclient.saturnclient.SaturnClient;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Fabric implementation of {@link PlayerModule}.
*
* All access to {@link MinecraftClient} and {@link ClientPlayerEntity}
* is encapsulated here. Features only ever see the {@link PlayerModule}
* interface; they never import Minecraft classes directly.
*
* Every method guards against a missing player and returns a safe
* default so features are free of null checks.
*/
public class PlayerModuleFabric implements PlayerModule {
private final MinecraftClient mc;
public PlayerModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
// ---------------------------------------------------------------
// Internal helper — never returns null
// ---------------------------------------------------------------
/** Returns the local player, or {@code null} if one is not loaded. */
private ClientPlayerEntity player() {
return mc.player;
}
// ---------------------------------------------------------------
// Presence
// ---------------------------------------------------------------
@Override
public boolean hasPlayer() {
return mc.player != null;
}
// ---------------------------------------------------------------
// Position
// ---------------------------------------------------------------
@Override
public int getX() {
return hasPlayer() ? (int) player().getX() : 0;
}
@Override
public int getY() {
return hasPlayer() ? (int) player().getY() : 0;
}
@Override
public int getZ() {
return hasPlayer() ? (int) player().getZ() : 0;
}
// ---------------------------------------------------------------
// Input / movement
// ---------------------------------------------------------------
@Override
public boolean isForwardPressed() {
return mc.options.forwardKey.isPressed();
}
@Override
public boolean isBackPressed() {
return mc.options.backKey.isPressed();
}
@Override
public boolean isLeftPressed() {
return mc.options.leftKey.isPressed();
}
@Override
public boolean isRightPressed() {
return mc.options.rightKey.isPressed();
}
@Override
public boolean isJumpPressed() {
return mc.options.jumpKey.isPressed();
}
@Override
public boolean isAttackPressed() {
return mc.options.attackKey.isPressed();
}
@Override
public boolean isUsePressed() {
return mc.options.useKey.isPressed();
}
@Override
public boolean isSneaking() {
return hasPlayer() && player().isSneaking();
}
@Override
public boolean isUsingItem() {
return hasPlayer() && player().isUsingItem();
}
@Override
public boolean hasHorizontalCollision() {
return hasPlayer() && player().horizontalCollision;
}
@Override
public boolean isOnGround() {
return hasPlayer() && player().isOnGround();
}
@Override
public void setSprinting(boolean sprinting) {
if (hasPlayer())
player().setSprinting(sprinting);
}
// ---------------------------------------------------------------
// Velocity
// ---------------------------------------------------------------
@Override
public Velocity getVelocity() {
if (!hasPlayer())
return Velocity.ZERO;
var v = player().getVelocity();
return new Velocity(v.x, v.y, v.z);
}
// ---------------------------------------------------------------
// Stats
// ---------------------------------------------------------------
@Override
public float getHealth() {
return hasPlayer() ? player().getHealth() : 0f;
}
// ---------------------------------------------------------------
// Equipment
// ---------------------------------------------------------------
@Override
public ItemStackRef getMainHand() {
return wrap(hasPlayer() ? player().getMainHandStack() : ItemStack.EMPTY);
}
@Override
public ItemStackRef getHelmet() {
return wrap(hasPlayer() ? player().getInventory().armor.get(3) : ItemStack.EMPTY);
}
@Override
public ItemStackRef getChestplate() {
return wrap(hasPlayer() ? player().getInventory().armor.get(2) : ItemStack.EMPTY);
}
@Override
public ItemStackRef getLeggings() {
return wrap(hasPlayer() ? player().getInventory().armor.get(1) : ItemStack.EMPTY);
}
@Override
public ItemStackRef getBoots() {
return wrap(hasPlayer() ? player().getInventory().armor.get(0) : ItemStack.EMPTY);
}
// ---------------------------------------------------------------
// Dummy equipment
// ---------------------------------------------------------------
@Override
public ItemStackRef getDummyMainHand() {
return wrap(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<? extends StatusEffectsFeature.EffectView> getActiveEffects() {
if (!hasPlayer())
return Collections.emptyList();
var effects = player().getActiveStatusEffects().values();
List<FabricEffectView> result = new ArrayList<>(effects.size());
for (StatusEffectInstance instance : effects) {
result.add(new FabricEffectView(instance));
}
return result;
}
@Override
public List<? extends StatusEffectsFeature.EffectView> getDummyEffects() {
if (!hasPlayer())
return Collections.emptyList();
var effects = player().getActiveStatusEffects().values();
List<FabricEffectView> result = new ArrayList<>(effects.size());
for (StatusEffectInstance instance : effects) {
result.add(new FabricEffectView(instance));
}
return result;
}
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
private static ItemStackRef wrap(ItemStack stack) {
return (ItemStackRef) (Object) stack;
}
// ---------------------------------------------------------------
// Inner — EffectView adapter
// ---------------------------------------------------------------
/**
* Bridges a Fabric {@link StatusEffectInstance} to the
* platform-neutral {@link StatusEffectsFeature.EffectView}.
*/
public static final class FabricEffectView implements StatusEffectsFeature.EffectView {
private final StatusEffectInstance instance;
public FabricEffectView(StatusEffectInstance instance) {
this.instance = instance;
}
@Override
public boolean shouldShowIcon() {
return instance.shouldShowIcon();
}
/**
* Returns the Minecraft sprite identifier for this effect's icon.
* The platform-specific renderer knows how to draw a
* {@link net.minecraft.util.Identifier}.
*/
@Override
public SpriteRef getIcon() {
return (SpriteRef) SaturnClient.client.getStatusEffectSpriteManager().getSprite(instance.getEffectType());
}
@Override
public boolean isInfinite() {
return instance.isInfinite();
}
@Override
public String getInfiniteText() {
return "∞";
}
@Override
public int getDurationSeconds() {
return instance.getDuration() / 20; // ticks → seconds
}
}
}
@@ -1,64 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.Perspective;
import org.saturnclient.common.module.RenderModule;
/**
* Fabric implementation of {@link RenderModule}.
*
* Window dimensions and FPS are read directly from
* {@link MinecraftClient}. Camera perspective control delegates to
* {@link net.minecraft.client.option.GameOptions}.
*/
public class RenderModuleFabric implements RenderModule {
private final MinecraftClient mc;
public RenderModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
// ---------------------------------------------------------------
// Window
// ---------------------------------------------------------------
@Override
public int getScaledWindowWidth() {
return mc.getWindow().getScaledWidth();
}
@Override
public int getScaledWindowHeight() {
return mc.getWindow().getScaledHeight();
}
// ---------------------------------------------------------------
// Performance
// ---------------------------------------------------------------
@Override
public int getFps() {
return mc.getCurrentFps();
}
// ---------------------------------------------------------------
// Camera perspective
// ---------------------------------------------------------------
@Override
public boolean isFirstPerson() {
return mc.options.getPerspective() == Perspective.FIRST_PERSON;
}
@Override
public void setFirstPerson() {
mc.options.setPerspective(Perspective.FIRST_PERSON);
}
@Override
public void setThirdPersonBack() {
mc.options.setPerspective(Perspective.THIRD_PERSON_BACK);
}
}
@@ -1,36 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import org.saturnclient.common.module.WorldModule;
/**
* Fabric implementation of {@link WorldModule}.
*
* Reads the client world's time directly from {@link MinecraftClient}.
* Returns safe defaults when no world is loaded.
*/
public class WorldModuleFabric implements WorldModule {
private final MinecraftClient mc;
public WorldModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
@Override
public boolean hasWorld() {
return mc.world != null;
}
/**
* Returns the total world age in ticks, or {@code 0} when no world
* is loaded. Used by {@link TpsFeature#onTimePacket} (which is
* called from the mixin and stores its own copy) and by
* {@link DayCounterFeature} for the day count.
*/
@Override
public long getWorldAge() {
return hasWorld() ? mc.world.getTime() : 0L;
}
}
@@ -1,37 +0,0 @@
package org.saturnclient.impl.modules.mixins;
import net.minecraft.client.render.GameRenderer;
import org.saturnclient.feature.features.ZoomFeature;
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 ZoomFeature} is active.
*
* The old code checked both {@code Zoom.isZooming} and
* {@code Zoom.shouldZoom()} separately. The refactored
* {@link ZoomFeature#shouldZoom()} already ORs both conditions
* ({@code enabled.value && isZooming}), so only one check is needed.
*
* No {@link org.saturnclient.common.module.ModuleProvider} 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<Float> cir) {
if (ZoomFeature.shouldZoom()) {
float zoomedFov = cir.getReturnValue() / ZoomFeature.getZoomLevel();
cir.setReturnValue(zoomedFov);
}
}
}
@@ -1,35 +0,0 @@
package org.saturnclient.impl.modules.mixins;
// 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.feature.features.FullbrightFeature;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
/**
* Overrides the lightmap's gamma computation when
* {@link FullbrightFeature} 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 FullbrightFeature}. 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 (FullbrightFeature.shouldOverrideBrightness()) {
return FullbrightFeature.getBrightnessValue();
}
return original;
}
}
@@ -1,55 +0,0 @@
package org.saturnclient.impl.modules.mixins;
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.module.EntityModule;
import org.saturnclient.impl.modules.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 EntityModule.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 {
}
// public abstract class LivingEntityRendererMixin<T extends LivingEntity, S
// extends LivingEntityRenderState> {
// @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
// EntityModule.EntityType type;
// if (entity instanceof PlayerEntity)
// type = EntityModule.EntityType.PLAYER;
// else if (entity instanceof HostileEntity)
// type = EntityModule.EntityType.HOSTILE;
// else if (entity instanceof PassiveEntity)
// type = EntityModule.EntityType.PASSIVE;
// else
// type = EntityModule.EntityType.OTHER;
// hrs.saturn$setEntityType(type);
// }
// }
@@ -1,94 +0,0 @@
package org.saturnclient.impl.modules.mixins;
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.feature.features.NametagsFeature;
import org.saturnclient.impl.modules.EntityModuleFabric;
import org.saturnclient.impl.modules.HealthRenderState;
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 NametagsFeature#getNametagString}.
*
* Key changes from the original:
*
* <ul>
* <li>The old {@code NametagsFabric.EntityStateImpl} bridge class is
* replaced by {@link EntityModuleFabric.RenderStateEntityState},
* which wraps the {@link HealthRenderState} already injected into
* the render state by {@link LivingEntityRenderStateMixin}.</li>
* <li>The feature import changes from {@code Nametags} to
* {@link NametagsFeature}; the two static methods
* ({@code shouldReplaceName} / {@code getNametagString}) are
* unchanged in signature.</li>
* <li>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.</li>
* </ul>
*
* 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<Boolean> saturn$rendering = ThreadLocal.withInitial(() -> false);
// @Shadow
// protected abstract <S extends EntityRenderState> void renderLabelIfPresent(
// S state, Text text,
// MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light);
// @Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true)
// private <S extends EntityRenderState> 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 (!NametagsFeature.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;
// EntityModuleFabric.RenderStateEntityState entityState = new EntityModuleFabric.RenderStateEntityState(
// customName, hrs);
// // Ask the feature for the replacement string
// String replacement = NametagsFeature.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);
// }
// }
}
@@ -1,85 +0,0 @@
package org.saturnclient.impl.modules.mixins;
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.feature.Feature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.FeatureManager;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.impl.ui.RenderScopeImpl;
import org.saturnclient.impl.ui.SaturnScreenFabric;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
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 FeatureManager}, and
* dispatches either {@link HudFeature#renderHud} (for HUD elements) or
* {@link Feature#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.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
for (Feature feature : FeatureManager.ENABLED_MODS) {
if (!feature.isEnabled())
continue;
if (feature instanceof HudFeature hud) {
FeatureLayout dim = hud.getDimensions();
scope.getMatrixStack().push();
scope.getMatrixStack().translate(dim.x.value, (float) dim.y.value, 0f);
scope.getMatrixStack().scale(dim.scale.value, dim.scale.value, 1.0f);
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);
}
}
}
}
@@ -1,30 +0,0 @@
package org.saturnclient.impl.modules.mixins;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket;
import org.saturnclient.feature.features.TpsFeature;
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 TpsFeature}'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 TpsFeature#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 TpsFeature}.
*/
@Mixin(ClientPlayNetworkHandler.class)
public class TpsMixin {
@Inject(method = "onWorldTimeUpdate", at = @At("HEAD"))
private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) {
TpsFeature.onTimePacket(packet.getTime());
}
}
@@ -1,18 +0,0 @@
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));
}
}
@@ -1,68 +0,0 @@
package org.saturnclient.impl.provider;
import net.minecraft.client.MinecraftClient;
import org.saturnclient.common.provider.ModuleProvider;
import org.saturnclient.impl.modules.EntityModuleFabric;
import org.saturnclient.impl.modules.NetworkModuleFabric;
import org.saturnclient.impl.modules.PlayerModuleFabric;
import org.saturnclient.impl.modules.RenderModuleFabric;
import org.saturnclient.impl.modules.WorldModuleFabric;
import org.saturnclient.common.module.*;
/**
* Fabric implementation of {@link ModuleProvider}.
*
* Instantiated once during client startup (e.g. in the mod initialiser)
* and passed to every feature constructor. All five sub-modules share
* the same {@link MinecraftClient} reference so there is no
* inconsistency between modules reading state within the same tick.
*
* <pre>{@code
* ModuleProvider provider = new ModuleProviderFabric(MinecraftClient.getInstance());
* FeatureManager.register(new AutoSprintFeature(provider));
* FeatureManager.register(new CoordinatesFeature(provider));
* // …
* }</pre>
*/
public class ModuleProviderFabric implements ModuleProvider {
private final PlayerModule player;
private final WorldModule world;
private final EntityModule entity;
private final RenderModule render;
private final NetworkModule network;
public ModuleProviderFabric(MinecraftClient mc) {
this.player = new PlayerModuleFabric(mc);
this.world = new WorldModuleFabric(mc);
this.entity = new EntityModuleFabric(mc);
this.render = new RenderModuleFabric(mc);
this.network = new NetworkModuleFabric(mc);
}
@Override
public PlayerModule player() {
return player;
}
@Override
public WorldModule world() {
return world;
}
@Override
public EntityModule entity() {
return entity;
}
@Override
public RenderModule render() {
return render;
}
@Override
public NetworkModule network() {
return network;
}
}
@@ -1,25 +0,0 @@
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);
}
}
@@ -1,72 +0,0 @@
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;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
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.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);
}
}
@@ -1,85 +0,0 @@
package org.saturnclient.impl.ui;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.saturnclient.common.ref.render.QuaternionfRef;
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.DiffuseLighting;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.entity.LivingEntity;
public class EntityDrawerImpl implements EntityDrawer {
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f,
float mouseX,
float mouseY, LivingEntity entity, float angle, boolean negativeAngle) {
float g = (float) (x1 + x2) / 2.0F;
float h = (float) (y1 + y2) / 2.0F;
renderScope.enableScissor(0, 6, x2, y2);
float i = (float) Math.atan((double) ((g - mouseX) / 40.0F));
float j = (float) Math.atan((double) ((h - mouseY) / 40.0F));
Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F);
float k = entity.bodyYaw;
float l = entity.getYaw();
float m = entity.getPitch();
float n = entity.prevHeadYaw;
float o = entity.headYaw;
entity.setYaw(angle);
entity.bodyYaw = angle;
entity.setPitch(-(j * 20.0F));
entity.headYaw = negativeAngle ? (i * 40.0F) + 180f : -(i * 40.0F);
entity.prevHeadYaw = entity.getHeadYaw();
entity.setCustomNameVisible(false);
float p = entity.getScale();
Vector3f vector3f = new Vector3f(0.0F, entity.getHeight() / 2.0F + f * p, 0.0F);
float q = (float) size / p;
drawEntity(renderScope, g, h, q, vector3f, quaternionf, null, entity);
entity.setCustomNameVisible(true);
entity.bodyYaw = k;
entity.setYaw(l);
entity.setPitch(m);
entity.prevHeadYaw = n;
entity.headYaw = o;
renderScope.disableScissor();
}
public static void drawEntity(RenderScope renderScope, float x, float y, float size, Vector3f vector3f,
Quaternionf quaternionf, @Nullable Quaternionf quaternionf2, LivingEntity entity) {
renderScope.getMatrixStack().push();
renderScope.getMatrixStack().translate((double) x, (double) y, 50.0);
renderScope.getMatrixStack().scale(size, size, -size);
renderScope.getMatrixStack().translate(vector3f.x, vector3f.y, vector3f.z);
renderScope.getMatrixStack().multiply((QuaternionfRef) quaternionf);
renderScope.draw();
DiffuseLighting.method_34742();
EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
entityRenderDispatcher.setRenderShadows(false);
if (renderScope instanceof RenderScopeImpl r) {
r.draw((vertexConsumers) -> {
entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 1.0F, 1.0f,
r.matrices, vertexConsumers,
15728880);
});
}
renderScope.draw();
entityRenderDispatcher.setRenderShadows(true);
renderScope.getMatrixStack().pop();
DiffuseLighting.enableGuiDepthLighting();
}
@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, entity, angle,
negativeAngle);
}
}
}
@@ -1,501 +0,0 @@
package org.saturnclient.impl.ui;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import com.mojang.blaze3d.systems.RenderSystem;
import org.saturnclient.common.provider.Providers;
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.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.font.TextRenderer;
import net.minecraft.client.font.TextRenderer.TextLayerType;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.DiffuseLighting;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.VertexFormat.DrawMode;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.Window;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
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.util.math.RotationAxis;
import net.minecraft.world.World;
public class RenderScopeImpl implements RenderScope {
public MatrixStack matrices;
public VertexConsumerProvider.Immediate vertexConsumers;
private ScissorStack scissorStack = new ScissorStack();
private int opacity = 255 << 24;
public RenderScopeImpl(DrawContext context) {
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
}
public RenderScopeImpl(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
this.matrices = matrices;
this.vertexConsumers = vertexConsumers;
this.scissorStack = new ScissorStack();
}
@Override
public MatrixStackRef getMatrixStack() {
return (MatrixStackRef) 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 drawRect(int x, int y, int width, int height, int color) {
if (color == 0)
return;
color = getColor(color);
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int x1 = x;
int x2 = x + width;
int y1 = y;
int y2 = y + height;
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(RenderLayer.getGui());
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0).color(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 i = 0;
for (String line : text.split("\n")) {
color = getColor(color);
matrices.push();
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
matrices.scale(scale, scale, 1.0f);
TextRenderer textRenderer = SaturnClient.client.textRenderer;
textRenderer.draw((Text) Fonts.setFont(line, font), 0,
font == 0 ? 1 : 7, color, false,
this.matrices.peek().getPositionMatrix(),
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
matrices.pop();
i++;
}
}
private void drawRoundedCorner(int width, int height, int radius, int color) {
int w = width * 20;
int h = height * 20;
for (int y = 0; y < h; y++) {
int startX = 0;
if (y < radius) {
double dy = radius - y - 0.5;
double dx = Math.sqrt(Math.max(0, radius * radius - dy * dy));
startX = radius - (int) dx;
}
this.drawRect(startX, y, w - startX, 1, color);
}
}
private void drawRoundedSide(int cornerWidth, int cornerHeight, int radius, int color) {
// Top
this.matrices.push();
this.matrices.scale(0.05f, 0.05f, 1.0f);
this.drawRoundedCorner(cornerWidth, cornerHeight, radius * 10, color);
this.matrices.pop();
// Bottom
this.matrices.push();
this.matrices.translate(cornerWidth, cornerHeight * 2, 0);
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(90));
this.matrices.translate(0, -cornerWidth, 0);
this.matrices.scale(0.05f, 0.05f, 1.0f);
this.drawRoundedCorner(cornerHeight, cornerWidth, radius * 10, color);
this.matrices.pop();
}
@Override
public void drawRoundedRectangle(int x, int y, int width, int height, int radius, int color) {
if (color == 0)
return;
radius = Math.min(radius, Math.min(width, height));
int cornerWidth = width / 2;
int cornerHeight = height / 2;
this.matrices.push();
this.matrices.translate(x, y, 0);
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
this.matrices.translate(cornerWidth * 2, cornerHeight * 2, 0);
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(180));
this.matrices.translate(0, 0, 0);
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
this.matrices.pop();
}
@Override
public int getScaledWindowWidth() {
return SaturnClient.client.getWindow().getScaledWidth();
}
@Override
public int getScaledWindowHeight() {
return SaturnClient.client.getWindow().getScaledHeight();
}
@Override
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, int color) {
this.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) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
}
@Override
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height,
int regionWith,
int regionHeight, int textureWidth, int textureHeight) {
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, 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) {
this.drawTexturedQuad((Identifier) (Object) sprite, x, x + width, y, y + height,
(u + 0.0F) / (float) textureWidth,
(u + (float) regionWidth) / (float) textureWidth, (v + 0.0F) / (float) textureHeight,
(v + (float) regionHeight) / (float) textureHeight, color);
}
/* Minecraft sprites */
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int color) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWith,
int regionHeight, int textureWidth, int textureHeight) {
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, regionHeight, textureWidth, textureHeight, -1);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth,
int regionHeight, int textureWidth, int textureHeight, int color) {
this.drawTexturedQuad(sprite, x, x + width, y, y + height, (u + 0.0F) / (float) textureWidth,
(u + (float) regionWidth) / (float) textureWidth, (v + 0.0F) / (float) textureHeight,
(v + (float) regionHeight) / (float) 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 (sprite.toString().endsWith(".svg")) {
sprite = (Identifier) (Object) SvgTexture.getSvg(Providers.saturn.getClient(),
(IdentifierRef) (Object) sprite,
(x2 - x1) * 2, (y2 - y1) * 2);
}
if (color == 0)
return;
x1 *= 4;
x2 *= 4;
y1 *= 4;
y2 *= 4;
color = getColor(color);
matrices.push();
matrices.scale(0.25f, 0.25f, 1.0f);
RenderSystem.setShaderTexture(0, sprite);
RenderSystem.setShader(GameRenderer::getPositionTexProgram);
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
BufferBuilder bufferBuilder = Tessellator.getInstance().begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE);
bufferBuilder.vertex(matrix4f, (float) x1, (float) y1, 0f).texture(u1, v1);
bufferBuilder.vertex(matrix4f, (float) x1, (float) y2, 0f).texture(u1, v2);
bufferBuilder.vertex(matrix4f, (float) x2, (float) y2, 0f).texture(u2, v2);
bufferBuilder.vertex(matrix4f, (float) x2, (float) y1, 0f).texture(u2, v1);
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end());
matrices.pop();
}
@Override
public void enableScissor(int x1, int y1, int x2, int y2) {
this.setScissor(this.scissorStack.push(new ScreenRect(x1, y1, x2 - x1, y2 - y1)));
}
public void disableScissor() {
this.setScissor(this.scissorStack.pop());
}
public boolean scissorContains(int x, int y) {
return this.scissorStack.contains(x, y);
}
private void setScissor(@Nullable ScreenRect rect) {
if (rect != null) {
Window window = SaturnClient.client.getWindow();
int i = window.getFramebufferHeight();
double d = window.getScaleFactor();
double e = (double) rect.getLeft() * d;
double f = (double) i - (double) rect.getBottom() * d;
double g = (double) rect.width() * d;
double h = (double) rect.height() * d;
RenderSystem.enableScissor((int) e, (int) f, Math.max(0, (int) g), Math.max(0, (int) h));
} else {
RenderSystem.disableScissor();
}
}
@Override
public void draw() {
this.vertexConsumers.draw();
}
public void draw(Consumer<VertexConsumerProvider> drawer) {
drawer.accept(this.vertexConsumers);
this.vertexConsumers.draw();
}
static class ScissorStack {
private final Deque<ScreenRect> stack = new ArrayDeque<>();
ScissorStack() {
}
public ScreenRect push(ScreenRect rect) {
ScreenRect screenRect = (ScreenRect) this.stack.peekLast();
if (screenRect != null) {
ScreenRect screenRect2 = (ScreenRect) Objects.requireNonNullElse(rect.intersection(screenRect),
ScreenRect.empty());
this.stack.addLast(screenRect2);
return screenRect2;
} else {
this.stack.addLast(rect);
return rect;
}
}
@Nullable
public ScreenRect pop() {
if (this.stack.isEmpty()) {
throw new IllegalStateException("Scissor stack underflow");
} else {
this.stack.removeLast();
return (ScreenRect) this.stack.peekLast();
}
}
public boolean contains(int x, int y) {
return this.stack.isEmpty() ? true : ((ScreenRect) this.stack.peek()).contains(x, y);
}
}
@Override
public void drawItem(ItemStackRef item, int x, int y) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, item, x, y, 0);
}
@Override
public void drawItem(ItemStackRef stack, int x, int y, int seed) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, stack, x, y, seed);
}
@Override
public void drawItem(ItemStackRef stack, int x, int y, int seed, int z) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, (ItemStack) (Object) stack, x, y, seed, z);
}
@Override
public void drawItemWithoutEntity(ItemStackRef stack, int x, int y) {
this.drawItemWithoutEntity(stack, x, y, 0);
}
@Override
public void drawItemWithoutEntity(ItemStackRef stack, int x, int y, int seed) {
this.drawItem((LivingEntity) null, SaturnClient.client.world, stack, x, y, seed);
}
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStackRef stack, int x, int y,
int seed) {
this.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()) {
BakedModel bakedModel = SaturnClient.client.getItemRenderer().getModel(stack, world, entity, seed);
this.matrices.push();
this.matrices.translate((float) (x + 8), (float) (y + 8), (float) (150 + (bakedModel.hasDepth() ? z : 0)));
try {
this.matrices.scale(16.0F, -16.0F, 16.0F);
boolean bl = !bakedModel.isSideLit();
if (bl) {
DiffuseLighting.disableGuiDepthLighting();
}
SaturnClient.client.getItemRenderer().renderItem(stack, ModelTransformationMode.GUI, false,
this.matrices,
this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV, bakedModel);
this.draw();
if (bl) {
DiffuseLighting.enableGuiDepthLighting();
}
} 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);
}
this.matrices.pop();
}
}
@Override
public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height) {
this.drawSpriteStretched(sprite, x, y, width, height, -1);
}
@Override
public void drawSpriteStretched(SpriteRef saturnSprite, int x, int y, int width, int height, int color) {
Sprite sprite = (Sprite) saturnSprite;
if (color == 0)
return;
if (width != 0 && height != 0) {
this.drawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, sprite.getMinU(), sprite.getMaxU(),
sprite.getMinV(), sprite.getMaxV(), color);
}
}
public void fill(int x1, int y1, int x2, int y2, int color) {
this.fill(x1, y1, x2, y2, 0, color);
}
public void fill(int x1, int y1, int x2, int y2, int z, int color) {
this.fill(RenderLayer.getGui(), x1, y1, x2, y2, z, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int color) {
this.fill(layer, x1, y1, x2, y2, 0, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int z, int color) {
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(layer);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, (float) z).color(color);
}
public void drawBorder(int x, int y, int width, int height, int color) {
this.fill(x, y, x + width, y + 1, color);
this.fill(x, y + height - 1, x + width, y + height, color);
this.fill(x, y + 1, x + 1, y + height - 1, color);
this.fill(x + width - 1, y + 1, x + width, y + height - 1, color);
}
}
@@ -1,161 +0,0 @@
package org.saturnclient.impl.ui;
import java.time.Duration;
import java.time.Instant;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
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.gl.PostEffectProcessor;
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);
}
// private final Pool pool = new Pool(3);
public final SaturnScreen screen;
public static void preload(MinecraftClient client) {
TextureManager textureManager = client.getTextureManager();
// Force upload each texture to GPU
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();
}
@SuppressWarnings("deprecation")
@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, screen.backgroundOpacity, delta);
}
// PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
// Identifier.ofVanilla("blur"),
// DefaultFramebufferSet.MAIN_ONLY);
// if (postEffectProcessor != null) {
// postEffectProcessor.setUniforms("Radius", screen.backgroundBlur * Math.min((float) elapsed / 700, 1.0f));
// postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
// }
// We are using a Abstracted RenderScope because older minecraft versions don't
// use DrawContext
RenderScope renderScope = new RenderScopeImpl(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
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);
}
// ------------------------------------------
// ScreenProvider implementations
// ------------------------------------------
@Override
public void close() {
this.screen.onClose();
super.close();
}
@Override
public int getWidth() {
return width * 2;
}
@Override
public int getHeight() {
return height * 2;
}
}
@@ -1,52 +0,0 @@
package org.saturnclient.saturnclient;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.client.ServiceClient;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.MinecraftClient;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import com.mojang.authlib.GameProfile;
public class PlayerTracker {
private static Set<UUID> trackedPlayers = new HashSet<>();
public static void initialize() {
// When client joins a server, reset tracked players
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
trackedPlayers.clear();
client.execute(() -> {
checkForNewPlayers(SaturnClient.client);
});
});
// Check for new players every tick
ClientTickEvents.END_CLIENT_TICK.register(client -> checkForNewPlayers(client));
}
private static void checkForNewPlayers(MinecraftClient client) {
if (client.getNetworkHandler() == null)
return;
for (PlayerListEntry player : client.getNetworkHandler().getPlayerList()) {
UUID playerUUID = player.getProfile().getId();
if (!trackedPlayers.contains(playerUUID)) {
// A new player has joined
onPlayerJoin(client, player);
trackedPlayers.add(playerUUID);
}
}
}
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile();
ServiceClient.player(profile.getId(), profile.getName());
}
}
@@ -1,72 +0,0 @@
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.cosmetics.Hats;
import org.saturnclient.feature.FeatureManager;
import org.saturnclient.cosmetics.Cloaks;
import org.saturnclient.common.provider.Providers;
import org.saturnclient.impl.provider.GLFWProviderImpl;
import org.saturnclient.impl.provider.ModuleProviderFabric;
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.saturnclient.event.KeyInputHandler;
import org.saturnclient.config.Config;
import org.saturnclient.config.manager.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.module = new ModuleProviderFabric(client);
SkinPreview.DRAWER = new EntityDrawerImpl();
Config.init();
FeatureManager.init();
client.execute(() -> {
SaturnScreenFabric.preload(client);
});
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
ClientTickEvents.END_CLIENT_TICK.register(client -> {
for (org.saturnclient.feature.Feature m : FeatureManager.ENABLED_MODS) {
m.tick();
}
});
KeyInputHandler.register();
Emotes.initialize();
if (ServiceClient.authenticate()) {
Cloaks.initialize();
Hats.initialize();
LOGGER.info(MOD_ID + " initialization complete");
} else {
LOGGER.error("Failed to authenticate with the server");
}
}
}
@@ -1,57 +0,0 @@
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.client.ServiceClient;
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.removeLayer(1000);
ServiceClient.emote("");
}
}
});
}
}
@@ -1,12 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.VertexConsumerProvider;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(DrawContext.class)
public interface DrawContextAccessor {
@Accessor("vertexConsumers")
VertexConsumerProvider.Immediate getVertexConsumers();
}
@@ -1,101 +0,0 @@
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.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityAttachmentType;
import net.minecraft.entity.player.PlayerEntity;
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<T extends Entity> {
@Shadow
public EntityRenderDispatcher dispatcher;
@Shadow
private TextRenderer textRenderer;
/**
* @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)
protected void renderLabelIfPresent(T entity, Text text, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, float tickDelta, CallbackInfo ci) {
UUID uuid = isSaturn(entity);
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(entity, iconText.append(nameText), matrices, vertexConsumers, light, tickDelta);
ci.cancel();
}
}
private UUID isSaturn(T entity) {
if (entity instanceof PlayerEntity playerEntity) {
SaturnPlayer player = SaturnPlayer.get(playerEntity.getUuid());
if (player != null) {
return player.uuid;
}
}
return null;
}
private void renderLabel(T entity, Text text, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, float tickDelta) {
double d = this.dispatcher.getSquaredDistanceToCamera(entity);
if (!(d > (double) 4096.0F)) {
Vec3d vec3d = entity.getAttachments().getPointNullable(EntityAttachmentType.NAME_TAG, 0,
entity.getYaw(tickDelta));
if (vec3d != null) {
boolean bl = !entity.isSneaky();
int i = "deadmau5".equals(text.getString()) ? -10 : 0;
matrices.push();
matrices.translate(vec3d.x, vec3d.y + (double) 0.5F, vec3d.z);
matrices.multiply(this.dispatcher.getRotation());
matrices.scale(0.025F, -0.025F, 0.025F);
Matrix4f matrix4f = matrices.peek().getPositionMatrix();
float f = SaturnClient.client.options.getTextBackgroundOpacity(0.25F);
int j = (int) (f * 255.0F) << 24;
TextRenderer textRenderer = this.textRenderer;
float g = (float) (-textRenderer.getWidth(text) / 2);
textRenderer.draw(text, g, (float) i, 553648127, false, matrix4f, vertexConsumers,
bl ? TextLayerType.SEE_THROUGH : TextLayerType.NORMAL, j, light);
if (bl) {
textRenderer.draw(text, g, (float) i, -1, false, matrix4f, vertexConsumers, TextLayerType.NORMAL, 0,
light);
}
matrices.pop();
}
}
}
}
@@ -1,29 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.network.AbstractClientPlayerEntity;
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 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<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
protected PlayerEntityRendererMixin() {
super(null, null, 0.0f);
}
@Inject(method = "<init>", at = @At("TAIL"))
private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
this.addFeature(new HatFeatureRenderer(this));
this.addFeature(new CloakFeatureRenderer(this));
}
}
@@ -1,27 +0,0 @@
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;
}
}
@@ -1,161 +0,0 @@
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<Optional<Throwable>> 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());
}
}
}
}
@@ -1,43 +0,0 @@
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);
}
}
@@ -1,32 +0,0 @@
/*
* 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;
}
}
@@ -1,28 +0,0 @@
{
"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",
"mods.mixins.json",
"impl.mixins.json"
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "${minecraft_version}"
}
}
@@ -1,19 +0,0 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.saturnclient.impl.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"IdentifierMixin",
"ItemStackMixin",
"MatrixStackMixin",
"MinecraftClientMixin",
"QuaternionfMixin",
"WindowMixin",
"TextMixin",
"SpriteMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -1,17 +0,0 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.saturnclient.impl.modules.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GameRendererMixin",
"LightmapTextureManagerMixin",
"LivingEntityRendererMixin",
"NametagsMixin",
"RenderMixin",
"TpsMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -1,18 +0,0 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.saturnclient.saturnclient.mixin",
"compatibilityLevel": "JAVA_21",
"client": [
"TabListMixin",
"NameTagMixin",
"PlayerEntityRendererMixin",
"WhoAmI",
"DrawContextAccessor",
"SplashOverlayMixin",
"SetScreenMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -0,0 +1,11 @@
package org.saturnclient.impl.modules;
public interface CameraOverriddenEntity {
float freelook$getCameraPitch();
float freelook$getCameraYaw();
void freelook$setCameraPitch(float pitch);
void freelook$setCameraYaw(float yaw);
}
@@ -0,0 +1,46 @@
package org.saturnclient.impl.modules.mixins;
import org.saturnclient.feature.features.FreelookFeature;
import org.saturnclient.impl.modules.CameraOverriddenEntity;
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 (FreelookFeature.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 (!FreelookFeature.isFreeLooking && cameraEntity instanceof ClientPlayerEntity) {
firstTime = true;
}
}
}
@@ -0,0 +1,62 @@
package org.saturnclient.impl.modules.mixins;
import org.saturnclient.feature.features.FreelookFeature;
import org.saturnclient.impl.modules.CameraOverriddenEntity;
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 (FreelookFeature.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;
}
}
@@ -10,7 +10,9 @@
"LivingEntityRenderStateMixin",
"NametagsMixin",
"RenderMixin",
"TpsMixin"
"TpsMixin",
"CameraMixin",
"EntityMixin"
],
"injectors": {
"defaultRequire": 1
-96
View File
@@ -1,96 +0,0 @@
plugins {
id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"
id 'maven-publish'
}
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
}
repositories {
maven { name = "KoSmX Maven"; url = 'https://maven.kosmx.dev/' }
}
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}"
)
// 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}"}
}
}
// 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.
}
}
-23
View File
@@ -1,23 +0,0 @@
# 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
yarn_mappings=1.21+build.9
loader_version=0.18.4
loom_version=1.15-SNAPSHOT
# Mod Properties
mod_version=1.0.0
maven_group=org.saturnclient
archives_base_name=saturn-client
# Dependencies
fabric_api_version=0.102.0+1.21
player_anim = 2.0.4+1.21.1
-10
View File
@@ -1,10 +0,0 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
@@ -1,411 +0,0 @@
package org.saturnclient.impl.cosmetics;
import java.util.Arrays;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.config.Config;
import org.saturnclient.cosmetics.Cloaks;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
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.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
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<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
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<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> featureRendererContext) {
super(featureRendererContext);
Arrays.fill(segmentValues, 0.0f);
}
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, 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;
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta,
float animationProgress, float headYaw, float headPitch) {
if (entity.isInvisible() || entity.getSkinTextures().capeTexture() != null) {
return;
}
SaturnPlayer player = SaturnPlayer.get(entity.getUuid());
if (player == null) {
return;
}
ItemStack chestStack = entity.getInventory().armor.get(1);
Identifier customCape = (Identifier) (Object) Cloaks.getCurrentCloakTexture(player.uuid);
if (customCape == null || chestStack.itemMatches(Items.ELYTRA.getRegistryEntry())) {
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 = vertexConsumers.getBuffer(RenderLayer.getEntityAlpha(customCape));
matrices.push();
matrices.translate(0.0f, 0.0f, 0.12f);
if (!chestStack.isEmpty()) {
matrices.translate(0.0F, -0.053125F, 0.06875F);
}
if (entity.isInSneakingPose()) {
matrices.translate(0, 0.16f, 0.0f);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f));
}
long now = System.currentTimeMillis();
if (now - lastUpdate >= 20) {
float velX = (float) (MathHelper.lerp((double) tickDelta, entity.prevCapeX,
entity.capeX)
- MathHelper.lerp((double) tickDelta, entity.prevX, entity.getX()));
float velZ = (float) (MathHelper.lerp((double) tickDelta, entity.prevCapeZ,
entity.capeZ)
- MathHelper.lerp((double) tickDelta, entity.prevZ, entity.getZ()));
float rawVelY = (float) (MathHelper.lerp((double) tickDelta, entity.prevCapeY, entity.capeY)
- MathHelper.lerp((double) tickDelta, entity.prevY, entity.getY()));
float velHorizontal = Math.min(0.6f, velZ > velX ? velZ : velX);
float velY = (rawVelY > 0.25f ? rawVelY : 0.0f);
float value = entity.isSwimming() ? 0.0f : Math.max(0.0f, Math.min(2.0f, velHorizontal + velY));
if (Config.cloakPhysics.value) {
updateSegmentValues(value);
} else {
float last = segmentValues[0];
float maxStep = 0.02f;
float next = value;
if (next > last)
next = Math.min(next, last + maxStep);
else if (next < last)
next = Math.max(next, last - maxStep);
Arrays.fill(segmentValues, next);
}
lastUpdate = now;
}
renderCape(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV);
matrices.pop();
}
// 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;
// }
// Identifier customCape = (Identifier) (Object)
// Cloaks.getCurrentCloakTexture(player.uuid);
// 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(RenderLayer.getEntityAlpha(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(1.0f, playerEntityRenderState.field_53537 / 108.0f);
// float rawVelY = playerEntityRenderState.field_53536;
// float velY = (rawVelY > 4.0f ? rawVelY : 0.0f) / 16;
// float value = playerEntityRenderState.isSwimming ? 0.0f : velX + velY;
// if (Config.cloakPhysics.value) {
// updateSegmentValues(value);
// } else {
// float last = segmentValues[0];
// float maxStep = 0.02f;
// float next = value;
// if (next > last)
// next = Math.min(next, last + maxStep);
// else if (next < last)
// next = Math.max(next, last - maxStep);
// Arrays.fill(segmentValues, next);
// }
// lastUpdate = now;
// }
// renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light,
// OverlayTexture.DEFAULT_UV);
// matrixStack.pop();
// }
}
@@ -1,43 +0,0 @@
package org.saturnclient.impl.cosmetics;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.impl.cosmetics.obj.MtlLoader;
import org.saturnclient.impl.cosmetics.obj.ObjModel;
import net.minecraft.client.network.AbstractClientPlayerEntity;
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.util.math.MatrixStack;
public class HatFeatureRenderer
extends FeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
public HatFeatureRenderer(
FeatureRendererContext<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> featureRendererContext) {
super(featureRendererContext);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta,
float animationProgress, float headYaw, float headPitch) {
SaturnPlayer player = SaturnPlayer.get(entity.getUuid());
if (entity.isInvisible() || player == null || player.hat.isEmpty()) {
return;
}
matrices.push();
this.getContextModel().head.rotate(matrices);
ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat),
matrices, vertexConsumers, light,
OverlayTexture.DEFAULT_UV);
matrices.pop();
}
}
@@ -1,56 +0,0 @@
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<Identifier, Map<String, Mtl>> loadedMtls = new HashMap<>();
static Map<String, Mtl> loadMtl(Identifier mtlId) throws IOException {
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
.getResource(mtlId).get().getInputStream()) {
List<Mtl> mtlList = MtlReader.read(is);
Map<String, Mtl> mtlMap = new java.util.LinkedHashMap<>();
for (Mtl mtl : mtlList) {
mtlMap.put(mtl.getName(), mtl);
}
return mtlMap;
}
}
public static Map<String, Mtl> of(Identifier mtlId) {
if (loadedMtls.containsKey(mtlId))
return loadedMtls.get(mtlId);
try {
Map<String, Mtl> mtl = loadMtl(mtlId);
loadedMtls.put(mtlId, mtl);
return mtl;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static Map<String, Mtl> 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"));
}
}
@@ -1,102 +0,0 @@
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<Identifier, ObjModel> 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> 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<String, Mtl> 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();
}
}
@@ -1,111 +0,0 @@
package org.saturnclient.impl.cosmetics.obj;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import de.javagl.obj.*;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
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<String, Mtl> mtlMap, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, int overlay) {
MatrixStack.Entry entry = matrices.peek();
Map<String, Obj> materialGroups = ObjSplitting.splitByMaterialGroups(obj);
for (Map.Entry<String, Obj> 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
Identifier texture = Identifier.of("minecraft:textures/misc/white.png");
if (mtl != null) {
String mapKd = mtl.getMapKd();
if (mapKd != null) {
// "#key" references are not resolvable at runtime without a texture map,
// so we only support direct resource location strings here
if (!mapKd.startsWith("#")) {
texture = Identifier.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(RenderLayer.getEntityAlpha(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);
}
}
@@ -1,70 +0,0 @@
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.setColor(x, y, pixels[index++]);
}
}
} else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
nativeImage.setColor(x, y, bufferedImage.getRGB(x, y));
}
}
}
NativeImageBackedTexture texture = new NativeImageBackedTexture(nativeImage);
SaturnClient.client.execute(() ->
SaturnClient.client.getTextureManager().registerTexture(identifier, texture)
);
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to register texture: {}", identifierRef, e);
}
}
public static void registerBufferedImageTexturesBulk(Map<IdentifierRef, BufferedImage> textures) {
long start = System.currentTimeMillis();
textures.forEach(IdentifierUtils::registerBufferedImageTextureFast);
long end = System.currentTimeMillis();
SaturnClient.LOGGER.info(
"Registered {} textures in {}ms",
textures.size(),
(end - start)
);
}
}
@@ -1,13 +0,0 @@
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();
}
@@ -1,19 +0,0 @@
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();
}
@@ -1,59 +0,0 @@
package org.saturnclient.impl.mixins;
import org.joml.Quaternionf;
import org.saturnclient.common.ref.render.MatrixStackRef;
import org.saturnclient.common.ref.render.QuaternionfRef;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.client.util.math.MatrixStack;
@Mixin(MatrixStack.class)
public abstract class MatrixStackMixin implements MatrixStackRef {
@Shadow
public abstract void translate(double x, double y, double z);
@Shadow
public abstract void translate(float x, float y, float z);
@Shadow
public abstract void scale(float x, float y, float z);
@Shadow
public abstract void multiply(Quaternionf quaternion);
@Shadow
public abstract void multiply(Quaternionf quaternion, float originX, float originY, float originZ);
@Shadow
public abstract void push();
@Shadow
public abstract void pop();
@Invoker("peek")
public abstract MatrixStack.Entry peekM();
@Shadow
public abstract boolean isEmpty();
@Shadow
public abstract void loadIdentity();
@Override
public void multiply(QuaternionfRef quaternion) {
this.multiply((Quaternionf) quaternion);
}
@Override
public void multiply(QuaternionfRef quaternion, float originX, float originY, float originZ) {
this.multiply((Quaternionf) quaternion, originX, originY, originZ);
}
@Override
public Object peek() {
return this.peekM();
}
}
@@ -1,130 +0,0 @@
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);
}
}
@@ -1,9 +0,0 @@
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 {
}
@@ -1,9 +0,0 @@
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 {
}
@@ -1,24 +0,0 @@
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);
}
}
@@ -1,22 +0,0 @@
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();
}
@@ -1,80 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.entity.LivingEntity;
import org.saturnclient.common.module.EntityModule;
/**
* Fabric implementation of {@link EntityModule}.
*
* Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}.
* Entity state for nametag rendering is supplied at render time by
* {@link org.saturnclient.modules.mixins.LivingEntityRendererMixin} which
* writes into the render state; {@link NametagsMixin} then constructs
* a {@link RenderStateEntityState} from the already-extracted snapshot.
*/
public class EntityModuleFabric implements EntityModule {
private final MinecraftClient mc;
public EntityModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
@Override
public boolean isTargetingLivingEntity() {
HitResult target = mc.crosshairTarget;
if (target == null || target.getType() != HitResult.Type.ENTITY)
return false;
Entity hit = ((EntityHitResult) target).getEntity();
return hit instanceof LivingEntity;
}
// ---------------------------------------------------------------
// Render-state adapter
// ---------------------------------------------------------------
/**
* Wraps a {@link HealthRenderState} (already injected into the
* Minecraft render-state object) as a platform-neutral
* {@link EntityState} for use by {@link NametagsFeature}.
*
* This is constructed inside {@link NametagsMixin} where both the
* render-state mixin interface and the entity name are available.
*/
public static final class RenderStateEntityState implements EntityState {
private final String customName;
private final HealthRenderState hrs;
public RenderStateEntityState(String customName, HealthRenderState hrs) {
this.customName = customName;
this.hrs = hrs;
}
@Override
public String getCustomName() {
return customName;
}
@Override
public float getHealth() {
return hrs.saturn$getHealth();
}
@Override
public float getMaxHealth() {
return hrs.saturn$getMaxHealth();
}
@Override
public EntityType getEntityType() {
return hrs.saturn$getEntityType();
}
}
}
@@ -1,20 +0,0 @@
package org.saturnclient.impl.modules;
import org.saturnclient.common.module.EntityModule;
/**
* 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);
EntityModule.EntityType saturn$getEntityType();
void saturn$setEntityType(EntityModule.EntityType type);
}
@@ -1,37 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.PlayerListEntry;
import org.saturnclient.common.module.NetworkModule;
/**
* Fabric implementation of {@link NetworkModule}.
*
* Connection presence is checked via the network handler.
* Ping is read from the player's {@link PlayerListEntry} which the
* server populates; returns {@code -1} when unavailable.
*/
public class NetworkModuleFabric implements NetworkModule {
private final MinecraftClient mc;
public NetworkModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
@Override
public boolean hasNetwork() {
return mc.getNetworkHandler() != null;
}
@Override
public int getPing() {
if (mc.player == null || mc.getNetworkHandler() == null)
return -1;
PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid());
return entry != null ? entry.getLatency() : -1;
}
}
@@ -1,302 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import org.saturnclient.common.module.PlayerModule;
import org.saturnclient.common.ref.asset.SpriteRef;
import org.saturnclient.common.ref.game.ItemStackRef;
import org.saturnclient.feature.features.StatusEffectsFeature;
import org.saturnclient.saturnclient.SaturnClient;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Fabric implementation of {@link PlayerModule}.
*
* All access to {@link MinecraftClient} and {@link ClientPlayerEntity}
* is encapsulated here. Features only ever see the {@link PlayerModule}
* interface; they never import Minecraft classes directly.
*
* Every method guards against a missing player and returns a safe
* default so features are free of null checks.
*/
public class PlayerModuleFabric implements PlayerModule {
private final MinecraftClient mc;
public PlayerModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
// ---------------------------------------------------------------
// Internal helper — never returns null
// ---------------------------------------------------------------
/** Returns the local player, or {@code null} if one is not loaded. */
private ClientPlayerEntity player() {
return mc.player;
}
// ---------------------------------------------------------------
// Presence
// ---------------------------------------------------------------
@Override
public boolean hasPlayer() {
return mc.player != null;
}
// ---------------------------------------------------------------
// Position
// ---------------------------------------------------------------
@Override
public int getX() {
return hasPlayer() ? (int) player().getX() : 0;
}
@Override
public int getY() {
return hasPlayer() ? (int) player().getY() : 0;
}
@Override
public int getZ() {
return hasPlayer() ? (int) player().getZ() : 0;
}
// ---------------------------------------------------------------
// Input / movement
// ---------------------------------------------------------------
@Override
public boolean isForwardPressed() {
return mc.options.forwardKey.isPressed();
}
@Override
public boolean isBackPressed() {
return mc.options.backKey.isPressed();
}
@Override
public boolean isLeftPressed() {
return mc.options.leftKey.isPressed();
}
@Override
public boolean isRightPressed() {
return mc.options.rightKey.isPressed();
}
@Override
public boolean isJumpPressed() {
return mc.options.jumpKey.isPressed();
}
@Override
public boolean isAttackPressed() {
return mc.options.attackKey.isPressed();
}
@Override
public boolean isUsePressed() {
return mc.options.useKey.isPressed();
}
@Override
public boolean isSneaking() {
return hasPlayer() && player().isSneaking();
}
@Override
public boolean isUsingItem() {
return hasPlayer() && player().isUsingItem();
}
@Override
public boolean hasHorizontalCollision() {
return hasPlayer() && player().horizontalCollision;
}
@Override
public boolean isOnGround() {
return hasPlayer() && player().isOnGround();
}
@Override
public void setSprinting(boolean sprinting) {
if (hasPlayer())
player().setSprinting(sprinting);
}
// ---------------------------------------------------------------
// Velocity
// ---------------------------------------------------------------
@Override
public Velocity getVelocity() {
if (!hasPlayer())
return Velocity.ZERO;
var v = player().getVelocity();
return new Velocity(v.x, v.y, v.z);
}
// ---------------------------------------------------------------
// Stats
// ---------------------------------------------------------------
@Override
public float getHealth() {
return hasPlayer() ? player().getHealth() : 0f;
}
// ---------------------------------------------------------------
// Equipment
// ---------------------------------------------------------------
@Override
public ItemStackRef getMainHand() {
return wrap(hasPlayer() ? player().getMainHandStack() : ItemStack.EMPTY);
}
@Override
public ItemStackRef getHelmet() {
return wrap(hasPlayer() ? player().getInventory().armor.get(3) : ItemStack.EMPTY);
}
@Override
public ItemStackRef getChestplate() {
return wrap(hasPlayer() ? player().getInventory().armor.get(2) : ItemStack.EMPTY);
}
@Override
public ItemStackRef getLeggings() {
return wrap(hasPlayer() ? player().getInventory().armor.get(1) : ItemStack.EMPTY);
}
@Override
public ItemStackRef getBoots() {
return wrap(hasPlayer() ? player().getInventory().armor.get(0) : ItemStack.EMPTY);
}
// ---------------------------------------------------------------
// Dummy equipment
// ---------------------------------------------------------------
@Override
public ItemStackRef getDummyMainHand() {
return wrap(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<? extends StatusEffectsFeature.EffectView> getActiveEffects() {
if (!hasPlayer())
return Collections.emptyList();
var effects = player().getActiveStatusEffects().values();
List<FabricEffectView> result = new ArrayList<>(effects.size());
for (StatusEffectInstance instance : effects) {
result.add(new FabricEffectView(instance));
}
return result;
}
@Override
public List<? extends StatusEffectsFeature.EffectView> getDummyEffects() {
if (!hasPlayer())
return Collections.emptyList();
var effects = player().getActiveStatusEffects().values();
List<FabricEffectView> result = new ArrayList<>(effects.size());
for (StatusEffectInstance instance : effects) {
result.add(new FabricEffectView(instance));
}
return result;
}
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
private static ItemStackRef wrap(ItemStack stack) {
return (ItemStackRef) (Object) stack;
}
// ---------------------------------------------------------------
// Inner — EffectView adapter
// ---------------------------------------------------------------
/**
* Bridges a Fabric {@link StatusEffectInstance} to the
* platform-neutral {@link StatusEffectsFeature.EffectView}.
*/
public static final class FabricEffectView implements StatusEffectsFeature.EffectView {
private final StatusEffectInstance instance;
public FabricEffectView(StatusEffectInstance instance) {
this.instance = instance;
}
@Override
public boolean shouldShowIcon() {
return instance.shouldShowIcon();
}
/**
* Returns the Minecraft sprite identifier for this effect's icon.
* The platform-specific renderer knows how to draw a
* {@link net.minecraft.util.Identifier}.
*/
@Override
public SpriteRef getIcon() {
return (SpriteRef) SaturnClient.client.getStatusEffectSpriteManager().getSprite(instance.getEffectType());
}
@Override
public boolean isInfinite() {
return instance.isInfinite();
}
@Override
public String getInfiniteText() {
return "∞";
}
@Override
public int getDurationSeconds() {
return instance.getDuration() / 20; // ticks → seconds
}
}
}
@@ -1,64 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.Perspective;
import org.saturnclient.common.module.RenderModule;
/**
* Fabric implementation of {@link RenderModule}.
*
* Window dimensions and FPS are read directly from
* {@link MinecraftClient}. Camera perspective control delegates to
* {@link net.minecraft.client.option.GameOptions}.
*/
public class RenderModuleFabric implements RenderModule {
private final MinecraftClient mc;
public RenderModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
// ---------------------------------------------------------------
// Window
// ---------------------------------------------------------------
@Override
public int getScaledWindowWidth() {
return mc.getWindow().getScaledWidth();
}
@Override
public int getScaledWindowHeight() {
return mc.getWindow().getScaledHeight();
}
// ---------------------------------------------------------------
// Performance
// ---------------------------------------------------------------
@Override
public int getFps() {
return mc.getCurrentFps();
}
// ---------------------------------------------------------------
// Camera perspective
// ---------------------------------------------------------------
@Override
public boolean isFirstPerson() {
return mc.options.getPerspective() == Perspective.FIRST_PERSON;
}
@Override
public void setFirstPerson() {
mc.options.setPerspective(Perspective.FIRST_PERSON);
}
@Override
public void setThirdPersonBack() {
mc.options.setPerspective(Perspective.THIRD_PERSON_BACK);
}
}
@@ -1,36 +0,0 @@
package org.saturnclient.impl.modules;
import net.minecraft.client.MinecraftClient;
import org.saturnclient.common.module.WorldModule;
/**
* Fabric implementation of {@link WorldModule}.
*
* Reads the client world's time directly from {@link MinecraftClient}.
* Returns safe defaults when no world is loaded.
*/
public class WorldModuleFabric implements WorldModule {
private final MinecraftClient mc;
public WorldModuleFabric(MinecraftClient mc) {
this.mc = mc;
}
@Override
public boolean hasWorld() {
return mc.world != null;
}
/**
* Returns the total world age in ticks, or {@code 0} when no world
* is loaded. Used by {@link TpsFeature#onTimePacket} (which is
* called from the mixin and stores its own copy) and by
* {@link DayCounterFeature} for the day count.
*/
@Override
public long getWorldAge() {
return hasWorld() ? mc.world.getTime() : 0L;
}
}
@@ -1,37 +0,0 @@
package org.saturnclient.impl.modules.mixins;
import net.minecraft.client.render.GameRenderer;
import org.saturnclient.feature.features.ZoomFeature;
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 ZoomFeature} is active.
*
* The old code checked both {@code Zoom.isZooming} and
* {@code Zoom.shouldZoom()} separately. The refactored
* {@link ZoomFeature#shouldZoom()} already ORs both conditions
* ({@code enabled.value && isZooming}), so only one check is needed.
*
* No {@link org.saturnclient.common.module.ModuleProvider} 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<Float> cir) {
if (ZoomFeature.shouldZoom()) {
float zoomedFov = cir.getReturnValue() / ZoomFeature.getZoomLevel();
cir.setReturnValue(zoomedFov);
}
}
}
@@ -1,35 +0,0 @@
package org.saturnclient.impl.modules.mixins;
// 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.feature.features.FullbrightFeature;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
/**
* Overrides the lightmap's gamma computation when
* {@link FullbrightFeature} 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 FullbrightFeature}. 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 (FullbrightFeature.shouldOverrideBrightness()) {
return FullbrightFeature.getBrightnessValue();
}
return original;
}
}
@@ -1,55 +0,0 @@
package org.saturnclient.impl.modules.mixins;
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.module.EntityModule;
import org.saturnclient.impl.modules.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 EntityModule.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 {
}
// public abstract class LivingEntityRendererMixin<T extends LivingEntity, S
// extends LivingEntityRenderState> {
// @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
// EntityModule.EntityType type;
// if (entity instanceof PlayerEntity)
// type = EntityModule.EntityType.PLAYER;
// else if (entity instanceof HostileEntity)
// type = EntityModule.EntityType.HOSTILE;
// else if (entity instanceof PassiveEntity)
// type = EntityModule.EntityType.PASSIVE;
// else
// type = EntityModule.EntityType.OTHER;
// hrs.saturn$setEntityType(type);
// }
// }
@@ -1,94 +0,0 @@
package org.saturnclient.impl.modules.mixins;
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.feature.features.NametagsFeature;
import org.saturnclient.impl.modules.EntityModuleFabric;
import org.saturnclient.impl.modules.HealthRenderState;
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 NametagsFeature#getNametagString}.
*
* Key changes from the original:
*
* <ul>
* <li>The old {@code NametagsFabric.EntityStateImpl} bridge class is
* replaced by {@link EntityModuleFabric.RenderStateEntityState},
* which wraps the {@link HealthRenderState} already injected into
* the render state by {@link LivingEntityRenderStateMixin}.</li>
* <li>The feature import changes from {@code Nametags} to
* {@link NametagsFeature}; the two static methods
* ({@code shouldReplaceName} / {@code getNametagString}) are
* unchanged in signature.</li>
* <li>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.</li>
* </ul>
*
* 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<Boolean> saturn$rendering = ThreadLocal.withInitial(() -> false);
// @Shadow
// protected abstract <S extends EntityRenderState> void renderLabelIfPresent(
// S state, Text text,
// MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light);
// @Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true)
// private <S extends EntityRenderState> 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 (!NametagsFeature.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;
// EntityModuleFabric.RenderStateEntityState entityState = new EntityModuleFabric.RenderStateEntityState(
// customName, hrs);
// // Ask the feature for the replacement string
// String replacement = NametagsFeature.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);
// }
// }
}
@@ -1,85 +0,0 @@
package org.saturnclient.impl.modules.mixins;
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.feature.Feature;
import org.saturnclient.feature.FeatureLayout;
import org.saturnclient.feature.FeatureManager;
import org.saturnclient.feature.HudFeature;
import org.saturnclient.impl.ui.RenderScopeImpl;
import org.saturnclient.impl.ui.SaturnScreenFabric;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
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 FeatureManager}, and
* dispatches either {@link HudFeature#renderHud} (for HUD elements) or
* {@link Feature#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.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
for (Feature feature : FeatureManager.ENABLED_MODS) {
if (!feature.isEnabled())
continue;
if (feature instanceof HudFeature hud) {
FeatureLayout dim = hud.getDimensions();
scope.getMatrixStack().push();
scope.getMatrixStack().translate(dim.x.value, (float) dim.y.value, 0f);
scope.getMatrixStack().scale(dim.scale.value, dim.scale.value, 1.0f);
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);
}
}
}
}
@@ -1,30 +0,0 @@
package org.saturnclient.impl.modules.mixins;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket;
import org.saturnclient.feature.features.TpsFeature;
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 TpsFeature}'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 TpsFeature#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 TpsFeature}.
*/
@Mixin(ClientPlayNetworkHandler.class)
public class TpsMixin {
@Inject(method = "onWorldTimeUpdate", at = @At("HEAD"))
private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) {
TpsFeature.onTimePacket(packet.getTime());
}
}
@@ -1,18 +0,0 @@
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));
}
}
@@ -1,68 +0,0 @@
package org.saturnclient.impl.provider;
import net.minecraft.client.MinecraftClient;
import org.saturnclient.common.provider.ModuleProvider;
import org.saturnclient.impl.modules.EntityModuleFabric;
import org.saturnclient.impl.modules.NetworkModuleFabric;
import org.saturnclient.impl.modules.PlayerModuleFabric;
import org.saturnclient.impl.modules.RenderModuleFabric;
import org.saturnclient.impl.modules.WorldModuleFabric;
import org.saturnclient.common.module.*;
/**
* Fabric implementation of {@link ModuleProvider}.
*
* Instantiated once during client startup (e.g. in the mod initialiser)
* and passed to every feature constructor. All five sub-modules share
* the same {@link MinecraftClient} reference so there is no
* inconsistency between modules reading state within the same tick.
*
* <pre>{@code
* ModuleProvider provider = new ModuleProviderFabric(MinecraftClient.getInstance());
* FeatureManager.register(new AutoSprintFeature(provider));
* FeatureManager.register(new CoordinatesFeature(provider));
* // …
* }</pre>
*/
public class ModuleProviderFabric implements ModuleProvider {
private final PlayerModule player;
private final WorldModule world;
private final EntityModule entity;
private final RenderModule render;
private final NetworkModule network;
public ModuleProviderFabric(MinecraftClient mc) {
this.player = new PlayerModuleFabric(mc);
this.world = new WorldModuleFabric(mc);
this.entity = new EntityModuleFabric(mc);
this.render = new RenderModuleFabric(mc);
this.network = new NetworkModuleFabric(mc);
}
@Override
public PlayerModule player() {
return player;
}
@Override
public WorldModule world() {
return world;
}
@Override
public EntityModule entity() {
return entity;
}
@Override
public RenderModule render() {
return render;
}
@Override
public NetworkModule network() {
return network;
}
}
@@ -1,25 +0,0 @@
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);
}
}
@@ -1,72 +0,0 @@
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;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
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.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);
}
}
@@ -1,85 +0,0 @@
package org.saturnclient.impl.ui;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.saturnclient.common.ref.render.QuaternionfRef;
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.DiffuseLighting;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.entity.LivingEntity;
public class EntityDrawerImpl implements EntityDrawer {
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f,
float mouseX,
float mouseY, LivingEntity entity, float angle, boolean negativeAngle) {
float g = (float) (x1 + x2) / 2.0F;
float h = (float) (y1 + y2) / 2.0F;
renderScope.enableScissor(0, 6, x2, y2);
float i = (float) Math.atan((double) ((g - mouseX) / 40.0F));
float j = (float) Math.atan((double) ((h - mouseY) / 40.0F));
Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F);
float k = entity.bodyYaw;
float l = entity.getYaw();
float m = entity.getPitch();
float n = entity.prevHeadYaw;
float o = entity.headYaw;
entity.setYaw(angle);
entity.bodyYaw = angle;
entity.setPitch(-(j * 20.0F));
entity.headYaw = negativeAngle ? (i * 40.0F) + 180f : -(i * 40.0F);
entity.prevHeadYaw = entity.getHeadYaw();
entity.setCustomNameVisible(false);
float p = entity.getScale();
Vector3f vector3f = new Vector3f(0.0F, entity.getHeight() / 2.0F + f * p, 0.0F);
float q = (float) size / p;
drawEntity(renderScope, g, h, q, vector3f, quaternionf, null, entity);
entity.setCustomNameVisible(true);
entity.bodyYaw = k;
entity.setYaw(l);
entity.setPitch(m);
entity.prevHeadYaw = n;
entity.headYaw = o;
renderScope.disableScissor();
}
public static void drawEntity(RenderScope renderScope, float x, float y, float size, Vector3f vector3f,
Quaternionf quaternionf, @Nullable Quaternionf quaternionf2, LivingEntity entity) {
renderScope.getMatrixStack().push();
renderScope.getMatrixStack().translate((double) x, (double) y, 50.0);
renderScope.getMatrixStack().scale(size, size, -size);
renderScope.getMatrixStack().translate(vector3f.x, vector3f.y, vector3f.z);
renderScope.getMatrixStack().multiply((QuaternionfRef) quaternionf);
renderScope.draw();
DiffuseLighting.method_34742();
EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
entityRenderDispatcher.setRenderShadows(false);
if (renderScope instanceof RenderScopeImpl r) {
r.draw((vertexConsumers) -> {
entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 1.0F, 1.0f,
r.matrices, vertexConsumers,
15728880);
});
}
renderScope.draw();
entityRenderDispatcher.setRenderShadows(true);
renderScope.getMatrixStack().pop();
DiffuseLighting.enableGuiDepthLighting();
}
@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, entity, angle,
negativeAngle);
}
}
}
@@ -1,501 +0,0 @@
package org.saturnclient.impl.ui;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import com.mojang.blaze3d.systems.RenderSystem;
import org.saturnclient.common.provider.Providers;
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.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.font.TextRenderer;
import net.minecraft.client.font.TextRenderer.TextLayerType;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.DiffuseLighting;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.VertexFormat.DrawMode;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.Window;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
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.util.math.RotationAxis;
import net.minecraft.world.World;
public class RenderScopeImpl implements RenderScope {
public MatrixStack matrices;
public VertexConsumerProvider.Immediate vertexConsumers;
private ScissorStack scissorStack = new ScissorStack();
private int opacity = 255 << 24;
public RenderScopeImpl(DrawContext context) {
this(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
}
public RenderScopeImpl(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
this.matrices = matrices;
this.vertexConsumers = vertexConsumers;
this.scissorStack = new ScissorStack();
}
@Override
public MatrixStackRef getMatrixStack() {
return (MatrixStackRef) 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 drawRect(int x, int y, int width, int height, int color) {
if (color == 0)
return;
color = getColor(color);
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int x1 = x;
int x2 = x + width;
int y1 = y;
int y2 = y + height;
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(RenderLayer.getGui());
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0).color(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 i = 0;
for (String line : text.split("\n")) {
color = getColor(color);
matrices.push();
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
matrices.scale(scale, scale, 1.0f);
TextRenderer textRenderer = SaturnClient.client.textRenderer;
textRenderer.draw((Text) Fonts.setFont(line, font), 0,
font == 0 ? 1 : 7, color, false,
this.matrices.peek().getPositionMatrix(),
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
matrices.pop();
i++;
}
}
private void drawRoundedCorner(int width, int height, int radius, int color) {
int w = width * 20;
int h = height * 20;
for (int y = 0; y < h; y++) {
int startX = 0;
if (y < radius) {
double dy = radius - y - 0.5;
double dx = Math.sqrt(Math.max(0, radius * radius - dy * dy));
startX = radius - (int) dx;
}
this.drawRect(startX, y, w - startX, 1, color);
}
}
private void drawRoundedSide(int cornerWidth, int cornerHeight, int radius, int color) {
// Top
this.matrices.push();
this.matrices.scale(0.05f, 0.05f, 1.0f);
this.drawRoundedCorner(cornerWidth, cornerHeight, radius * 10, color);
this.matrices.pop();
// Bottom
this.matrices.push();
this.matrices.translate(cornerWidth, cornerHeight * 2, 0);
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(90));
this.matrices.translate(0, -cornerWidth, 0);
this.matrices.scale(0.05f, 0.05f, 1.0f);
this.drawRoundedCorner(cornerHeight, cornerWidth, radius * 10, color);
this.matrices.pop();
}
@Override
public void drawRoundedRectangle(int x, int y, int width, int height, int radius, int color) {
if (color == 0)
return;
radius = Math.min(radius, Math.min(width, height));
int cornerWidth = width / 2;
int cornerHeight = height / 2;
this.matrices.push();
this.matrices.translate(x, y, 0);
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
this.matrices.translate(cornerWidth * 2, cornerHeight * 2, 0);
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(180));
this.matrices.translate(0, 0, 0);
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
this.matrices.pop();
}
@Override
public int getScaledWindowWidth() {
return SaturnClient.client.getWindow().getScaledWidth();
}
@Override
public int getScaledWindowHeight() {
return SaturnClient.client.getWindow().getScaledHeight();
}
@Override
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, int color) {
this.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) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
}
@Override
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height,
int regionWith,
int regionHeight, int textureWidth, int textureHeight) {
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, 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) {
this.drawTexturedQuad((Identifier) (Object) sprite, x, x + width, y, y + height,
(u + 0.0F) / (float) textureWidth,
(u + (float) regionWidth) / (float) textureWidth, (v + 0.0F) / (float) textureHeight,
(v + (float) regionHeight) / (float) textureHeight, color);
}
/* Minecraft sprites */
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int color) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWith,
int regionHeight, int textureWidth, int textureHeight) {
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, regionHeight, textureWidth, textureHeight, -1);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth,
int regionHeight, int textureWidth, int textureHeight, int color) {
this.drawTexturedQuad(sprite, x, x + width, y, y + height, (u + 0.0F) / (float) textureWidth,
(u + (float) regionWidth) / (float) textureWidth, (v + 0.0F) / (float) textureHeight,
(v + (float) regionHeight) / (float) 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 (sprite.toString().endsWith(".svg")) {
sprite = (Identifier) (Object) SvgTexture.getSvg(Providers.saturn.getClient(),
(IdentifierRef) (Object) sprite,
(x2 - x1) * 2, (y2 - y1) * 2);
}
if (color == 0)
return;
x1 *= 4;
x2 *= 4;
y1 *= 4;
y2 *= 4;
color = getColor(color);
matrices.push();
matrices.scale(0.25f, 0.25f, 1.0f);
RenderSystem.setShaderTexture(0, sprite);
RenderSystem.setShader(GameRenderer::getPositionTexProgram);
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
BufferBuilder bufferBuilder = Tessellator.getInstance().begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE);
bufferBuilder.vertex(matrix4f, (float) x1, (float) y1, 0f).texture(u1, v1);
bufferBuilder.vertex(matrix4f, (float) x1, (float) y2, 0f).texture(u1, v2);
bufferBuilder.vertex(matrix4f, (float) x2, (float) y2, 0f).texture(u2, v2);
bufferBuilder.vertex(matrix4f, (float) x2, (float) y1, 0f).texture(u2, v1);
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end());
matrices.pop();
}
@Override
public void enableScissor(int x1, int y1, int x2, int y2) {
this.setScissor(this.scissorStack.push(new ScreenRect(x1, y1, x2 - x1, y2 - y1)));
}
public void disableScissor() {
this.setScissor(this.scissorStack.pop());
}
public boolean scissorContains(int x, int y) {
return this.scissorStack.contains(x, y);
}
private void setScissor(@Nullable ScreenRect rect) {
if (rect != null) {
Window window = SaturnClient.client.getWindow();
int i = window.getFramebufferHeight();
double d = window.getScaleFactor();
double e = (double) rect.getLeft() * d;
double f = (double) i - (double) rect.getBottom() * d;
double g = (double) rect.width() * d;
double h = (double) rect.height() * d;
RenderSystem.enableScissor((int) e, (int) f, Math.max(0, (int) g), Math.max(0, (int) h));
} else {
RenderSystem.disableScissor();
}
}
@Override
public void draw() {
this.vertexConsumers.draw();
}
public void draw(Consumer<VertexConsumerProvider> drawer) {
drawer.accept(this.vertexConsumers);
this.vertexConsumers.draw();
}
static class ScissorStack {
private final Deque<ScreenRect> stack = new ArrayDeque<>();
ScissorStack() {
}
public ScreenRect push(ScreenRect rect) {
ScreenRect screenRect = (ScreenRect) this.stack.peekLast();
if (screenRect != null) {
ScreenRect screenRect2 = (ScreenRect) Objects.requireNonNullElse(rect.intersection(screenRect),
ScreenRect.empty());
this.stack.addLast(screenRect2);
return screenRect2;
} else {
this.stack.addLast(rect);
return rect;
}
}
@Nullable
public ScreenRect pop() {
if (this.stack.isEmpty()) {
throw new IllegalStateException("Scissor stack underflow");
} else {
this.stack.removeLast();
return (ScreenRect) this.stack.peekLast();
}
}
public boolean contains(int x, int y) {
return this.stack.isEmpty() ? true : ((ScreenRect) this.stack.peek()).contains(x, y);
}
}
@Override
public void drawItem(ItemStackRef item, int x, int y) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, item, x, y, 0);
}
@Override
public void drawItem(ItemStackRef stack, int x, int y, int seed) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, stack, x, y, seed);
}
@Override
public void drawItem(ItemStackRef stack, int x, int y, int seed, int z) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, (ItemStack) (Object) stack, x, y, seed, z);
}
@Override
public void drawItemWithoutEntity(ItemStackRef stack, int x, int y) {
this.drawItemWithoutEntity(stack, x, y, 0);
}
@Override
public void drawItemWithoutEntity(ItemStackRef stack, int x, int y, int seed) {
this.drawItem((LivingEntity) null, SaturnClient.client.world, stack, x, y, seed);
}
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStackRef stack, int x, int y,
int seed) {
this.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()) {
BakedModel bakedModel = SaturnClient.client.getItemRenderer().getModel(stack, world, entity, seed);
this.matrices.push();
this.matrices.translate((float) (x + 8), (float) (y + 8), (float) (150 + (bakedModel.hasDepth() ? z : 0)));
try {
this.matrices.scale(16.0F, -16.0F, 16.0F);
boolean bl = !bakedModel.isSideLit();
if (bl) {
DiffuseLighting.disableGuiDepthLighting();
}
SaturnClient.client.getItemRenderer().renderItem(stack, ModelTransformationMode.GUI, false,
this.matrices,
this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV, bakedModel);
this.draw();
if (bl) {
DiffuseLighting.enableGuiDepthLighting();
}
} 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);
}
this.matrices.pop();
}
}
@Override
public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height) {
this.drawSpriteStretched(sprite, x, y, width, height, -1);
}
@Override
public void drawSpriteStretched(SpriteRef saturnSprite, int x, int y, int width, int height, int color) {
Sprite sprite = (Sprite) saturnSprite;
if (color == 0)
return;
if (width != 0 && height != 0) {
this.drawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, sprite.getMinU(), sprite.getMaxU(),
sprite.getMinV(), sprite.getMaxV(), color);
}
}
public void fill(int x1, int y1, int x2, int y2, int color) {
this.fill(x1, y1, x2, y2, 0, color);
}
public void fill(int x1, int y1, int x2, int y2, int z, int color) {
this.fill(RenderLayer.getGui(), x1, y1, x2, y2, z, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int color) {
this.fill(layer, x1, y1, x2, y2, 0, color);
}
public void fill(RenderLayer layer, int x1, int y1, int x2, int y2, int z, int color) {
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(layer);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, (float) z).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, (float) z).color(color);
}
public void drawBorder(int x, int y, int width, int height, int color) {
this.fill(x, y, x + width, y + 1, color);
this.fill(x, y + height - 1, x + width, y + height, color);
this.fill(x, y + 1, x + 1, y + height - 1, color);
this.fill(x + width - 1, y + 1, x + width, y + height - 1, color);
}
}
@@ -1,161 +0,0 @@
package org.saturnclient.impl.ui;
import java.time.Duration;
import java.time.Instant;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
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.gl.PostEffectProcessor;
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);
}
// private final Pool pool = new Pool(3);
public final SaturnScreen screen;
public static void preload(MinecraftClient client) {
TextureManager textureManager = client.getTextureManager();
// Force upload each texture to GPU
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();
}
@SuppressWarnings("deprecation")
@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, screen.backgroundOpacity, delta);
}
// PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
// Identifier.ofVanilla("blur"),
// DefaultFramebufferSet.MAIN_ONLY);
// if (postEffectProcessor != null) {
// postEffectProcessor.setUniforms("Radius", screen.backgroundBlur * Math.min((float) elapsed / 700, 1.0f));
// postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
// }
// We are using a Abstracted RenderScope because older minecraft versions don't
// use DrawContext
RenderScope renderScope = new RenderScopeImpl(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
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);
}
// ------------------------------------------
// ScreenProvider implementations
// ------------------------------------------
@Override
public void close() {
this.screen.onClose();
super.close();
}
@Override
public int getWidth() {
return width * 2;
}
@Override
public int getHeight() {
return height * 2;
}
}
@@ -1,52 +0,0 @@
package org.saturnclient.saturnclient;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.client.ServiceClient;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.MinecraftClient;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import com.mojang.authlib.GameProfile;
public class PlayerTracker {
private static Set<UUID> trackedPlayers = new HashSet<>();
public static void initialize() {
// When client joins a server, reset tracked players
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
trackedPlayers.clear();
client.execute(() -> {
checkForNewPlayers(SaturnClient.client);
});
});
// Check for new players every tick
ClientTickEvents.END_CLIENT_TICK.register(client -> checkForNewPlayers(client));
}
private static void checkForNewPlayers(MinecraftClient client) {
if (client.getNetworkHandler() == null)
return;
for (PlayerListEntry player : client.getNetworkHandler().getPlayerList()) {
UUID playerUUID = player.getProfile().getId();
if (!trackedPlayers.contains(playerUUID)) {
// A new player has joined
onPlayerJoin(client, player);
trackedPlayers.add(playerUUID);
}
}
}
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile();
ServiceClient.player(profile.getId(), profile.getName());
}
}
@@ -1,72 +0,0 @@
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.cosmetics.Hats;
import org.saturnclient.feature.FeatureManager;
import org.saturnclient.cosmetics.Cloaks;
import org.saturnclient.common.provider.Providers;
import org.saturnclient.impl.provider.GLFWProviderImpl;
import org.saturnclient.impl.provider.ModuleProviderFabric;
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.saturnclient.event.KeyInputHandler;
import org.saturnclient.config.Config;
import org.saturnclient.config.manager.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.module = new ModuleProviderFabric(client);
SkinPreview.DRAWER = new EntityDrawerImpl();
Config.init();
FeatureManager.init();
client.execute(() -> {
SaturnScreenFabric.preload(client);
});
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
ClientTickEvents.END_CLIENT_TICK.register(client -> {
for (org.saturnclient.feature.Feature m : FeatureManager.ENABLED_MODS) {
m.tick();
}
});
KeyInputHandler.register();
Emotes.initialize();
if (ServiceClient.authenticate()) {
Cloaks.initialize();
Hats.initialize();
LOGGER.info(MOD_ID + " initialization complete");
} else {
LOGGER.error("Failed to authenticate with the server");
}
}
}
@@ -1,57 +0,0 @@
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.client.ServiceClient;
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.removeLayer(1000);
ServiceClient.emote("");
}
}
});
}
}
@@ -1,12 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.VertexConsumerProvider;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(DrawContext.class)
public interface DrawContextAccessor {
@Accessor("vertexConsumers")
VertexConsumerProvider.Immediate getVertexConsumers();
}
@@ -1,101 +0,0 @@
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.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityAttachmentType;
import net.minecraft.entity.player.PlayerEntity;
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<T extends Entity> {
@Shadow
public EntityRenderDispatcher dispatcher;
@Shadow
private TextRenderer textRenderer;
/**
* @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)
protected void renderLabelIfPresent(T entity, Text text, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, float tickDelta, CallbackInfo ci) {
UUID uuid = isSaturn(entity);
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(entity, iconText.append(nameText), matrices, vertexConsumers, light, tickDelta);
ci.cancel();
}
}
private UUID isSaturn(T entity) {
if (entity instanceof PlayerEntity playerEntity) {
SaturnPlayer player = SaturnPlayer.get(playerEntity.getUuid());
if (player != null) {
return player.uuid;
}
}
return null;
}
private void renderLabel(T entity, Text text, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, float tickDelta) {
double d = this.dispatcher.getSquaredDistanceToCamera(entity);
if (!(d > (double) 4096.0F)) {
Vec3d vec3d = entity.getAttachments().getPointNullable(EntityAttachmentType.NAME_TAG, 0,
entity.getYaw(tickDelta));
if (vec3d != null) {
boolean bl = !entity.isSneaky();
int i = "deadmau5".equals(text.getString()) ? -10 : 0;
matrices.push();
matrices.translate(vec3d.x, vec3d.y + (double) 0.5F, vec3d.z);
matrices.multiply(this.dispatcher.getRotation());
matrices.scale(0.025F, -0.025F, 0.025F);
Matrix4f matrix4f = matrices.peek().getPositionMatrix();
float f = SaturnClient.client.options.getTextBackgroundOpacity(0.25F);
int j = (int) (f * 255.0F) << 24;
TextRenderer textRenderer = this.textRenderer;
float g = (float) (-textRenderer.getWidth(text) / 2);
textRenderer.draw(text, g, (float) i, 553648127, false, matrix4f, vertexConsumers,
bl ? TextLayerType.SEE_THROUGH : TextLayerType.NORMAL, j, light);
if (bl) {
textRenderer.draw(text, g, (float) i, -1, false, matrix4f, vertexConsumers, TextLayerType.NORMAL, 0,
light);
}
matrices.pop();
}
}
}
}
@@ -1,29 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.network.AbstractClientPlayerEntity;
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 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<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
protected PlayerEntityRendererMixin() {
super(null, null, 0.0f);
}
@Inject(method = "<init>", at = @At("TAIL"))
private void addCustomFeatureRenderer(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
this.addFeature(new HatFeatureRenderer(this));
this.addFeature(new CloakFeatureRenderer(this));
}
}
@@ -1,27 +0,0 @@
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;
}
}
@@ -1,161 +0,0 @@
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<Optional<Throwable>> 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());
}
}
}
}
@@ -1,43 +0,0 @@
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);
}
}
@@ -1,32 +0,0 @@
/*
* 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;
}
}

Some files were not shown because too many files have changed in this diff Show More