Compare commits

..
Author SHA1 Message Date
selimaj-dev c567b282a5 Merge pull request #12 from RudideC/master
added health display
2026-02-13 12:03:56 +01:00
RudideC 5f19abebbf added health display
ts tuff
2026-02-13 10:06:13 +01:00
Klesti Selimaj 58eda56f37 Merge pull request #11 from RudideC/master
zoom mod
2026-01-21 21:57:42 +01:00
RudideC 50cab402d4 image 2026-01-21 21:52:28 +01:00
RudideC fa7161d371 zoom module 2026-01-21 21:52:24 +01:00
Klesti Selimaj 252808a467 Merge pull request #10 from RudideC/master
added nofog, daycounter and speedometer
2026-01-20 21:56:36 +01:00
RudideC 31e9a8d56e speedometer module finished
logo included
2026-01-20 15:30:57 +01:00
RudideC ba1b68d8fa speedometer works in vehicles 2026-01-20 14:57:59 +01:00
RudideC dcd82fa58e default is x axis 2026-01-20 14:51:22 +01:00
RudideC 4132a05c8f blocks/tick -> blocks/s 2026-01-20 14:44:29 +01:00
RudideC 47f8dd5e22 testing speedometer 2026-01-20 14:35:49 +01:00
RudideC 9b30ec7016 small change 2026-01-20 14:18:44 +01:00
RudideC d10f229ffa daycounter image 2026-01-19 20:55:07 +01:00
RudideC 061ed674e3 daycounter mod
counts days
2026-01-19 20:54:59 +01:00
RudideC 5b5f781095 nofog mod
you can customise if you want to be able to see through liquids, but only lava and powdered snow work (not water)
2026-01-17 00:24:39 +01:00
Klesti Selimaj 48f7aefbf6 Merge pull request #9 from RudideC/master 2026-01-16 22:12:15 +01:00
RudideC 8f111badf6 oops, forgot 2026-01-16 22:05:35 +01:00
RudideC 687bda68a1 renamed potiondisplay to status effects
this is the name most clients use
2026-01-16 22:04:57 +01:00
RudideC 8e2c986ae4 fullbright
new mod
2026-01-16 22:02:48 +01:00
RudideC faa95e664a faster timeout
(so i don't have to wait minutes before testing)
2026-01-16 22:02:25 +01:00
RudideC c9230da168 faster builds
yes
2026-01-16 22:01:59 +01:00
RudideC 7002fbec0e prevent race condition 2026-01-16 19:41:37 +01:00
RudideC 496d27b861 enabled mod list updates
each mod enable/disable updates the list
2026-01-16 19:41:29 +01:00
RudideC 3c37b80bc2 dont sprint when using item
anticheat compliance
2026-01-16 19:02:51 +01:00
selimaj-dev 920657ddd3 Beta 2 2025-09-08 18:46:29 +02:00
selimaj-dev 32035a2f7d Fixed flat cloak with new optimised textures 2025-09-06 12:07:30 +02:00
Leo Codes c230f4f5ec Merge pull request #7 from saturnclientmc/optimised-cloak
Optimised cloak texture size
2025-09-06 02:24:46 +02:00
selimaj-dev 50d1028e53 Optimised cloak texture size 2025-09-06 02:21:40 +02:00
Leo Codes 780cb9ba9a Merge pull request #6 from saturnclientmc/cloak-edge-cases
Cloak edge cases
2025-09-05 18:05:24 +02:00
selimaj-dev 65df9e9a5e Fixed cloak when crouching #5 2025-09-05 17:49:45 +02:00
selimaj-dev 66f8a13faa Added a wave effect #5 2025-09-05 17:09:46 +02:00
selimaj-dev 1726542c84 Fixed cloak when swimming #5 2025-09-05 16:35:57 +02:00
48 changed files with 706 additions and 187 deletions
File diff suppressed because one or more lines are too long
+7 -2
View File
@@ -1,12 +1,17 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.jvmargs=-Xmx1G -XX:+UseParallelGC
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.7
loader_version=0.16.10
fabric_version=0.114.0+1.21.4
mod_version = 0.0.5-beta1
mod_version = 0.0.5-beta2
maven_group = org.saturnclient
archives_base_name = saturnclient
@@ -13,10 +13,16 @@ public class ModManager {
new AutoSprint(),
new ArmorDisplay(),
new Fps(),
new PotionDisplay(),
new StatusEffects(),
new Coordinates(),
new Freelook(),
new Keystrokes()
new Keystrokes(),
new Fullbright(),
new NoFog(),
new DayCounter(),
new Speedometer(),
new Zoom(),
new HealthDisplay()
};
public static void init() {
@@ -31,7 +37,7 @@ public class ModManager {
});
}
public static void updateEnabledModules() {
public static synchronized void updateEnabledModules() {
ENABLED_MODS.clear();
for (Module m : ALL_MODS) {
if (m.isEnabled()) {
@@ -24,7 +24,12 @@ public abstract class Module {
public abstract boolean isEnabled();
public abstract void setEnabled(boolean e);
public final void setEnabled(boolean e) {
onEnabled(e);
ModManager.updateEnabledModules();
}
public abstract void onEnabled(boolean e);
public final String getName() {
return details.name;
@@ -96,7 +96,7 @@ public class ArmorDisplay extends Module implements HudMod {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -24,8 +24,12 @@ public class AutoSprint extends Module {
if (SaturnClient.client.player == null || SaturnClient.client.getNetworkHandler() == null) {
return;
}
if (SaturnClient.client.options.forwardKey.isPressed() && !SaturnClient.client.options.backKey.isPressed() && !SaturnClient.client.player.isSneaking() &&
!SaturnClient.client.player.horizontalCollision) {
if (SaturnClient.client.options.forwardKey.isPressed()
&& !SaturnClient.client.options.backKey.isPressed()
&& !SaturnClient.client.player.isSneaking()
&& !SaturnClient.client.player.horizontalCollision
&& !SaturnClient.client.player.isUsingItem()) {
Objects.requireNonNull(SaturnClient.client.player).setSprinting(true);
}
}
@@ -36,7 +40,7 @@ public class AutoSprint extends Module {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -69,7 +69,7 @@ public class Coordinates extends Module implements HudMod {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -42,7 +42,7 @@ public class Crosshair extends Module {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,61 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public class DayCounter extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static ModDimensions dimensions = new ModDimensions(40, 18);
long day = 0;
public DayCounter() {
super(new ModuleDetails("DayCounter", "day")
.description("Displays the number of days that have passed")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop()
);
}
@Override
public void renderDummy(RenderScope scope) {
renderDay(271, scope);
}
@Override
public void renderHud(RenderScope scope) {
day = (SaturnClient.client.world.getTimeOfDay() / 24000L);
renderDay(day, scope);
}
public void renderDay(long day, RenderScope scope) {
String text = "Day: " + day;
scope.drawText(text,
0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -42,7 +42,7 @@ public class Fps extends Module implements HudMod {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
@@ -60,7 +60,7 @@ public class Freelook extends Module {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
@@ -0,0 +1,39 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.Property;
public class Fullbright extends Module {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> brightness = Property.integer(100);
public Fullbright() {
super(
new ModuleDetails("Fullbright", "fullbright")
.description("Allows you to see in the dark")
.tags("Camera")
.version("v0.1.0"),
enabled.named("Enabled"),
brightness.named("Brightness %"));
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
public static boolean shouldOverrideBrightness() {
return enabled.value;
}
public static float getBrightnessValue() {
return brightness.value / 10;
}
}
@@ -0,0 +1,85 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.util.math.Vec3d;
public class HealthDisplay extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> displayMode = Property.select(0, "Health", "Hearts");
private static Property<Integer> decimals = Property.select(0, "0", "1", "2");
private static ModDimensions dimensions = new ModDimensions(40, 18);
double speed = 0.;
Vec3d velocity;
double y = 0.;
public HealthDisplay() {
super(new ModuleDetails("Health display", "health")
.description("Displays your current health")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
displayMode.named("Display mode"),
decimals.named("Decimals"),
dimensions.prop()
);
}
@Override
public void renderDummy(RenderScope scope) {
renderSpeed(10, scope);
}
@Override
public void renderHud(RenderScope scope) {
float health = SaturnClient.client.player.getHealth();
switch (displayMode.value) {
case 1:
health = health / 2;
}
renderSpeed(health, scope);
}
public void renderSpeed(float health, RenderScope scope) {
String text = "";
switch (decimals.value) {
case 0:
text = String.format("%.0f ", health);
break;
case 1:
text = String.format("%.1f ", health);
break;
case 2:
text = String.format("%.2f ", health);
break;
}
scope.drawText(text,
0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -167,7 +167,7 @@ public class Keystrokes extends Module implements HudMod {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
dimensionsDirty = true;
}
@@ -0,0 +1,39 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.Property;
public class NoFog extends Module {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> liquids = Property.bool(false);
public NoFog() {
super(
new ModuleDetails("No Fog", "nofog")
.description("Stops fog from rendering")
.tags("Camera")
.version("v0.1.0"),
enabled.named("Enabled"),
liquids.named("See through liquids"));
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
// static for the mixin
public static boolean isActive() {
return enabled.value;
}
public static boolean liquids() {
return liquids.value;
}
}
@@ -0,0 +1,114 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.util.math.Vec3d;
import net.minecraft.entity.Entity;
public class Speedometer extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> axis = Property.select(1, "Absolute", "X axis", "Y axis");
private static Property<Integer> unitText = Property.select(0, "None", "Blocks/s", "blocks/s", "b/s");
private static Property<Boolean> speedText = Property.bool(true);
private static ModDimensions dimensions = new ModDimensions(40, 18);
double speed = 0.;
Vec3d velocity;
double y = 0.;
public Speedometer() {
super(new ModuleDetails("Speedometer", "speed")
.description("Displays your current speed")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
axis.named("Speed type"),
unitText.named("Unit text"),
speedText.named("Show speed text"),
dimensions.prop()
);
}
@Override
public void renderDummy(RenderScope scope) {
renderSpeed(2.71, scope);
}
@Override
public void renderHud(RenderScope scope) {
Entity vehicle = SaturnClient.client.player.getVehicle();
Entity player = SaturnClient.client.player;
if (vehicle == null) {
velocity = player.getVelocity();
y = player.isOnGround() ? 0. : velocity.y;
} else {
velocity = vehicle.getVelocity();
y = vehicle.isOnGround() ? 0. : velocity.y;
}
switch (axis.value) {
case 0: // Absolute
speed = (Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(y, 2) + Math.pow(velocity.z, 2)));
break;
case 1: // x axis
speed = (Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(velocity.z, 2)));
break;
case 2: // y axis
speed = (Math.abs(y));
break;
}
renderSpeed(speed * 20, scope); // blocks/tick to blocks/s
}
public void renderSpeed(double speed, RenderScope scope) {
String f = String.format("%.2f ", speed);
switch (unitText.value) {
case 1:
f += "Blocks/s";
break;
case 2:
f += "blocks/s";
break;
case 3:
f += "b/s";
break;
}
String text = f;
if (speedText.value) {
text = "Speed: " + text;
}
scope.drawText(text,
0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -20,7 +20,7 @@ import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
public class PotionDisplay extends Module implements HudMod {
public class StatusEffects extends Module implements HudMod {
public static Property<Boolean> enabled = Property.bool(false);
private static ModDimensions dimensions = new ModDimensions(60, 0);
@@ -30,9 +30,9 @@ public class PotionDisplay extends Module implements HudMod {
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2),
};
public PotionDisplay() {
super(new ModuleDetails("Potion Display", "potion")
.description("Displays potion effects")
public StatusEffects() {
super(new ModuleDetails("Status effects", "effect")
.description("Displays status effects")
.version("v0.1.0")
.tags("Utility"),
@@ -99,7 +99,7 @@ public class PotionDisplay extends Module implements HudMod {
}
@Override
public void setEnabled(boolean e) {
public void onEnabled(boolean e) {
enabled.value = e;
}
}
@@ -0,0 +1,60 @@
package org.saturnclient.modules.mods;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.config.Property;
public class Zoom extends Module {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> toggle = Property.bool(false);
public static Property<Integer> zoomKey = Property.keybinding(GLFW.GLFW_KEY_C);
public static Property<Float> zoomLevel = Property.floatProp(3.0f);
public static boolean isZooming = false;
public Zoom() {
super(
new ModuleDetails("Zoom", "zoom")
.description("Allows you to zoom in for a closer view")
.tags("Camera", "Utility")
.version("0.1.0"),
enabled.named("Enabled"),
toggle.named("Toggle zoom"),
zoomKey.named("Zoom Keybinding"),
zoomLevel.named("Zoom Level")
);
}
@Override
public void tick() {
if (toggle.value) {
if (zoomKey.wasKeyPressed()) {
isZooming = !isZooming;
}
} else {
isZooming = zoomKey.isKeyPressed();
}
}
public static float getZoomLevel() {
return zoomLevel.value;
}
public static boolean shouldZoom() {
return enabled.value;
}
@Override
public void onEnabled(boolean e) {
enabled.value = e;
if (!e) {
isZooming = false;
}
}
@Override
public boolean isEnabled() {
return enabled.value;
}
}
@@ -0,0 +1,33 @@
package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.render.BackgroundRenderer;
import org.saturnclient.modules.mods.NoFog;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(BackgroundRenderer.class)
public class BackgroundRendererMixin {
@ModifyVariable(method = "applyFog", at = @At("HEAD"), ordinal = 0, argsOnly = true)
private static float modifyFogStart(float viewDistance) {
if (NoFog.isActive()) {
return Float.MAX_VALUE; // this could be customised
}
return viewDistance;
}
// liquids
@Redirect(
method = "applyFog",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isSpectator()Z")
)
private static boolean redirectIsSpectator(Entity entity) {
if (NoFog.isActive() && NoFog.liquids()) {
return true; // pretend to be spectator
}
return entity.isSpectator();
}
}
@@ -0,0 +1,22 @@
package org.saturnclient.modules.mods.mixins;
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;
import org.saturnclient.modules.mods.Zoom;
import net.minecraft.client.render.GameRenderer;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
private void onGetFov(CallbackInfoReturnable<Float> cir) {
if (Zoom.isZooming && Zoom.shouldZoom()) {
float originalFov = cir.getReturnValue();
float zoomedFov = originalFov / Zoom.getZoomLevel();
cir.setReturnValue(zoomedFov);
}
}
}
@@ -0,0 +1,23 @@
// code modified from Gamma utils: https://github.com/Sjouwer/gamma-utils
package org.saturnclient.modules.mods.mixins;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.render.LightmapTextureManager;
import org.saturnclient.modules.mods.Fullbright;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(LightmapTextureManager.class)
public class LightmapTextureManagerMixin {
@ModifyExpressionValue(
method = "update",
at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0)
)
private float modifyGamma(float original) {
if (Fullbright.shouldOverrideBrightness()) {
return Fullbright.getBrightnessValue();
}
return original;
}
}
@@ -15,6 +15,9 @@ public class Network {
static BufferedReader in;
static byte[] key = new byte[32];
private static final int CONNECTION_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
public static class AES {
public static byte[] encryptAES256(String message, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
@@ -37,7 +40,11 @@ public class Network {
// Generate AES key
new SecureRandom().nextBytes(key);
socket = new Socket("77.247.92.168", 8080);
socket = new Socket();
socket.connect(new InetSocketAddress("77.247.92.168", 8080), CONNECTION_TIMEOUT);
socket.setSoTimeout(READ_TIMEOUT);
// out = new DataOutputStream(socket.getOutputStream());
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@@ -13,10 +13,6 @@ import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.render.entity.model.EntityModelLayers;
import net.minecraft.client.render.entity.model.LoadedEntityModels;
import net.minecraft.client.render.entity.model.PlayerCapeModel;
import net.minecraft.client.render.entity.model.PlayerEntityModel;
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
@@ -29,18 +25,58 @@ import net.minecraft.util.Identifier;
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<PlayerEntityRenderState, PlayerEntityModel> {
private final EquipmentModelLoader equipmentModelLoader;
private final BipedEntityModel<PlayerEntityRenderState> model;
private static final int PARTS = 16;
private float currentVelocity = 0.0f;
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<PlayerEntityRenderState, PlayerEntityModel> context,
LoadedEntityModels modelLoader,
EquipmentModelLoader equipmentModelLoader) {
super(context);
this.model = new PlayerCapeModel<PlayerEntityRenderState>(
modelLoader.getModelPart(EntityModelLayers.PLAYER_CAPE));
this.equipmentModelLoader = equipmentModelLoader;
}
@@ -63,97 +99,65 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
MatrixStack.Entry entry = matrixStack.peek();
if (!flipWinding) {
// Counter-clockwise winding (front face)
// Vertex 1 (bottom-left)
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)
// 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);
// Vertex 2 (bottom-right)
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)
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);
// Vertex 3 (top-right)
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)
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
.normal(normalX, normalY, normalZ);
// Vertex 4 (top-left)
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)
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
.normal(normalX, normalY, normalZ);
} else {
// Clockwise winding (back face) - reverse vertex order
// Vertex 4 (top-left)
// 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)
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
.normal(normalX, normalY, normalZ);
// Vertex 3 (top-right)
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)
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
.normal(normalX, normalY, normalZ);
// Vertex 2 (bottom-right)
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)
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);
// Vertex 1 (bottom-left)
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)
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 float getCurve(int i, float curveMagnitude, PlayerEntityRenderState playerState) {
float powCurve = (float) Math.pow((double) (PARTS - i) / PARTS, 2) * curveMagnitude;
float waveFreq = 0.6f;
float phase = (playerState.age + playerState.handSwingProgress) * 0.4f;
float sineAmplitude = 0.02f;
float sineCurve = (float) Math.sin(i * waveFreq + phase) * sineAmplitude;
float t = Math.min(1.0f, Math.max(0.0f, (curveMagnitude - 0.3f) / 0.2f));
t = t * t * (3 - 2 * t);
return (1.0f - t) * powCurve + t * (powCurve + sineCurve);
}
private void renderCape(MatrixStack matrixStack, VertexConsumer vertexConsumer,
PlayerEntityRenderState playerState, int light, int overlay, float rotation,
float curveMagnitude) {
float capeWidth = 10.0f / 16.0f; // 10 pixels wide
float capeHeight = 16.0f / 16.0f; // 16 pixels tall
float capeDepth = 1.0f / 16.0f; // 1 pixel deep
PlayerEntityRenderState playerState, int light, int overlay,
float rotation, float curveMagnitude) {
float capeWidth = 10.0f / 16.0f;
float capeHeight = 16.0f / 16.0f;
float capeDepth = 1.0f / 16.0f;
float capePartHeight = capeHeight / PARTS;
matrixStack.push();
matrixStack.translate(0.0f, 1.25, 0.125f);
matrixStack.translate(0, -capeHeight, 0);
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(rotation));
matrixStack.translate(0, capeHeight, 0);
@@ -164,101 +168,112 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
float z1 = 0.0f;
float z2 = capeDepth;
float frontU1 = 1.0f / 64.0f; // Start at pixel 1
float frontU2 = 11.0f / 64.0f; // End at pixel 11 (10 pixels wide)
float frontV1 = 1.0f / 32.0f; // Start at pixel 1
float frontV2 = 17.0f / 32.0f; // End at pixel 17 (16 pixels tall)
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;
// Back face UV (inside of cape) - typically offset in cape texture
float backU1 = 12.0f / 64.0f; // Back texture starts after front
float backU2 = 22.0f / 64.0f; // 10 pixels wide
float backV2 = 17.0f / 32.0f;
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;
// Top face UV (edge along shoulders)
float topU1 = 1.0f / 64.0f; // Start at pixel 1
float topU2 = 11.0f / 64.0f; // End at pixel 11
float topV1 = 0.0f / 32.0f; // Start at pixel 0
float topV2 = 1.0f / 32.0f; // End at pixel 1
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;
// Bottom face UV (lower edge of cape)
float bottomU1 = 11.0f / 64.0f; // Start at pixel 11
float bottomU2 = 22.0f / 64.0f; // End at pixel 22
float bottomV1 = 0.0f / 32.0f; // Start at pixel 0
float bottomV2 = 1.0f / 32.0f; // End at pixel 1
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();
for (int i = 0; i < PARTS; i++) {
float y3 = -(capePartHeight * i);
float y4 = -(capePartHeight * (i + 1));
float totalVHeight = frontV2 - frontV1;
float partVHeight = totalVHeight / PARTS;
float curveOffsetTop = (float) Math.pow((double) (PARTS - i) / PARTS, 2) * curveMagnitude;
float curveOffsetBottom = (float) Math.pow((double) (PARTS - i - 1) / PARTS, 2)
* curveMagnitude;
float curveOffsetTop = getCurve(i, curveMagnitude, playerState);
float curveOffsetBottom = getCurve(i + 1, curveMagnitude, playerState);
// FRONT FACE
this.renderCapeQuad(vertexConsumer, matrixStack,
new Vec3d(x2, y4, z2 + curveOffsetBottom), new Vec3d(x1, y4, z2 +
curveOffsetBottom),
new Vec3d(x1, y3, z2 + curveOffsetTop), new Vec3d(x2, y3, z2 +
curveOffsetTop),
frontU1, frontV2 - (partVHeight * i), frontU2, frontV2 - (partVHeight * (i +
1)),
// FRONT FACE (outside)
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x2, y4, z2 + curveOffsetBottom),
new Vec3d(x1, y4, z2 + curveOffsetBottom),
new Vec3d(x1, y3, z2 + curveOffsetTop), new Vec3d(x2, y3, z2 + curveOffsetTop),
frontU1, frontV2 - (frontPartV * i),
frontU2, frontV2 - (frontPartV * (i + 1)),
light, overlay,
0.0f, 0.0f, 1.0f, false);
// BACK FACE
this.renderCapeQuad(vertexConsumer, matrixStack,
// BACK FACE (inside)
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x1, y4, z1 + curveOffsetBottom),
new Vec3d(x2, y4, z1 + curveOffsetBottom),
new Vec3d(x2, y3, z1 + curveOffsetTop), new Vec3d(x1, y3, z1 + curveOffsetTop),
backU1, backV2 - (partVHeight * i), backU2, backV2 - (partVHeight * (i + 1)),
backU1, backV2 - (backPartV * i),
backU2, backV2 - (backPartV * (i + 1)),
light, overlay,
0.0f, 0.0f, 1.0f, false);
// LEFT FACE
this.renderCapeQuad(vertexConsumer, matrixStack,
// LEFT FACE (thickness at x2)
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x2, y4, z1 + curveOffsetBottom),
new Vec3d(x2, y4, z2 + curveOffsetBottom),
new Vec3d(x2, y3, z2 + curveOffsetTop), new Vec3d(x2, y3, z1 + curveOffsetTop),
0.0f, frontV2 - (partVHeight * i), 1.0f / 64.0f,
frontV2 - (partVHeight * (i + 1)),
leftU1, leftV2 - (leftPartV * i),
leftU2, leftV2 - (leftPartV * (i + 1)),
light, overlay,
1.0f, 0.0f, 0.0f, false);
// RIGHT FACE
this.renderCapeQuad(vertexConsumer, matrixStack,
// RIGHT FACE (thickness at x1)
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x1, y4, z2 + curveOffsetBottom),
new Vec3d(x1, y4, z1 + curveOffsetBottom),
new Vec3d(x1, y3, z1 + curveOffsetTop), new Vec3d(x1, y3, z2 + curveOffsetTop),
frontU2, frontV2 - (partVHeight * i), frontU2 + 1.0f / 64.0f,
frontV2 - (partVHeight * (i + 1)),
rightU1, rightV2 - (rightPartV * i),
rightU2, rightV2 - (rightPartV * (i + 1)),
light, overlay,
-1.0f, 0.0f, 0.0f, false);
}
// TOP FACE
// TOP FACE (shoulders edge) – use its dedicated strip
matrixStack.push();
matrixStack.translate(0.0f, y2 + capeDepth, 0.0f);
matrixStack.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(90.0f));
matrixStack.translate(0.0f, -y2, 0.0f);
this.renderCapeQuad(vertexConsumer, matrixStack,
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x2, y2 + capeDepth, z2), new Vec3d(x1, y2 + capeDepth, z2),
new Vec3d(x1, y2, z2),
new Vec3d(x2, y2, z2),
new Vec3d(x1, y2, z2), new Vec3d(x2, y2, z2),
topU1, topV1, topU2, topV2,
light, overlay,
0.0f, 1.0f, 0.0f, true);
matrixStack.pop();
// BOTTOM FACE
float curveOffsetTop = (float) Math.pow(1.0f, 2) * curveMagnitude;
float curveOffsetBottom = (float) Math.pow(1.0f, 2) * curveMagnitude;
this.renderCapeQuad(vertexConsumer, matrixStack,
new Vec3d(x2, 0.0f, z2 + curveOffsetBottom),
new Vec3d(x1, 0.0f, z2 + curveOffsetBottom),
new Vec3d(x1, 0.0f, z1 + curveOffsetTop), new Vec3d(x2, 0.0f, z1 + curveOffsetTop),
// BOTTOM FACE – use bottom strip
float curveOffset = getCurve(0, curveMagnitude, playerState);
this.renderCapeQuad(
vertexConsumer, matrixStack,
new Vec3d(x2, 0.0f, z2 + curveOffset), new Vec3d(x1, 0.0f, z2 + curveOffset),
new Vec3d(x1, 0.0f, z1 + curveOffset), new Vec3d(x2, 0.0f, z1 + curveOffset),
bottomU1, bottomV1, bottomU2, bottomV2,
light, overlay,
0.0f, 0.0f, 1.0f, false);
@@ -282,10 +297,8 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
}
Identifier customCape = Cloaks.getCurrentCloakTexture(uuid);
if (customCape == null
|| this.hasCustomModelForLayer(
playerEntityRenderState.equippedChestStack,
LayerType.WINGS)) {
if (customCape == null || this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack,
LayerType.WINGS)) {
return;
}
@@ -297,44 +310,44 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
light = (skyLight << 20) | (blockLight << 4);
matrixStack.push();
VertexConsumer vertexConsumer = vertexConsumerProvider
.getBuffer(RenderLayer.getEntityAlpha(customCape));
if (SaturnClientConfig.bendyCloaks.value) {
float targetVelocity = ((6.0F
+ playerEntityRenderState.field_53537 / 2.0F
+ playerEntityRenderState.field_53536) * 0.02f);
targetVelocity = Math.max(0.0f, targetVelocity - 0.1f);
float targetVelocity = ((6.0F + playerEntityRenderState.field_53537 / 2.0F
+ playerEntityRenderState.field_53536) * 0.02f);
targetVelocity = Math.max(0.0f, targetVelocity - 0.1f);
float accelerationRate = 0.02f;
this.currentVelocity = this.currentVelocity
+ (targetVelocity - this.currentVelocity)
* accelerationRate;
float accelerationRate = 0.02f;
this.currentVelocity = this.currentVelocity
+ (targetVelocity - this.currentVelocity) * accelerationRate;
float rotation = this.currentVelocity * 72.0f;
float curve = this.currentVelocity * 0.4f;
float rotation = this.currentVelocity * 72.0f;
float curve = this.currentVelocity * 0.4f;
matrixStack.translate(0.0f, -0.25f, 0.0f);
if (this.hasCustomModelForLayer(
playerEntityRenderState.equippedChestStack,
LayerType.HUMANOID)) {
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
}
renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light,
OverlayTexture.DEFAULT_UV,
Math.min(rotation, 60.0f), Math.min(curve, 0.5f));
} else {
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack,
LayerType.HUMANOID)) {
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
}
((PlayerEntityModel) this.getContextModel()).copyTransforms(this.model);
this.model.setAngles(playerEntityRenderState);
this.model.render(matrixStack, vertexConsumer, light, OverlayTexture.DEFAULT_UV);
if (playerEntityRenderState.sneaking && playerEntityRenderState.isInSneakingPose) {
matrixStack.translate(0, 0.24f, 0);
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(27.0f));
curve += 0.2f;
}
matrixStack.translate(0.0f, -0.25f, 0.0f);
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack,
LayerType.HUMANOID)) {
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
}
if (playerEntityRenderState.isSwimming) {
curve = 0.0f;
rotation = 0.0f;
}
if (!SaturnClientConfig.bendyCloaks.value) {
curve = 0.0f;
}
renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light,
OverlayTexture.DEFAULT_UV, Math.min(rotation, 60.0f), Math.min(curve, 0.5f));
matrixStack.pop();
}
}
@@ -25,6 +25,6 @@ public abstract class PlayerEntityRendererMixin
@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, ctx.getEntityModels(), ctx.getEquipmentModelLoader()));
this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader()));
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+4 -1
View File
@@ -6,7 +6,10 @@
"client": [
"CameraMixin",
"EntityMixin",
"RenderMixin"
"RenderMixin",
"LightmapTextureManagerMixin",
"BackgroundRendererMixin",
"GameRendererMixin"
],
"injectors": {
"defaultRequire": 1