From 5dcfa63b2df450e227df272161a985adc87bc8ae Mon Sep 17 00:00:00 2001 From: zyxkad Date: Sun, 27 Jul 2025 23:14:12 -0600 Subject: [PATCH 01/32] implements sneak pose and dismount logic --- .../net/jcm/vsch/accessor/EntityAccessor.java | 7 + .../accessor/FreeRotatePlayerAccessor.java | 10 + .../java/net/jcm/vsch/config/VSCHConfig.java | 4 + .../vsch/entity/player/MultiPartPlayer.java | 103 ++++++++++ .../jcm/vsch/mixin/minecraft/MixinEntity.java | 41 ++++ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 188 ++++++++++++++++++ .../mixin/minecraft/MixinPlayerRenderer.java | 57 ++++++ .../mixin/minecraft/MixinServerPlayer.java | 12 ++ .../net/jcm/vsch/util/EmptyChunkAccess.java | 3 +- .../java/net/jcm/vsch/util/VSCHUtils.java | 7 + src/main/resources/vsch.mixins.json | 3 + 11 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/accessor/EntityAccessor.java create mode 100644 src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java create mode 100644 src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java diff --git a/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java b/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java new file mode 100644 index 00000000..1e4023dd --- /dev/null +++ b/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java @@ -0,0 +1,7 @@ +package net.jcm.vsch.accessor; + +import net.minecraft.world.phys.Vec3; + +public interface EntityAccessor { + Vec3 vsch$collide(Vec3 movement); +} diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java new file mode 100644 index 00000000..66e0b316 --- /dev/null +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -0,0 +1,10 @@ +package net.jcm.vsch.accessor; + +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.Pose; + +public interface FreeRotatePlayerAccessor { + boolean vsch$shouldFreeRotate(); + + EntityDimensions vsch$getVanillaDimensions(Pose pose); +} diff --git a/src/main/java/net/jcm/vsch/config/VSCHConfig.java b/src/main/java/net/jcm/vsch/config/VSCHConfig.java index 14a61b00..25a528cb 100644 --- a/src/main/java/net/jcm/vsch/config/VSCHConfig.java +++ b/src/main/java/net/jcm/vsch/config/VSCHConfig.java @@ -66,6 +66,8 @@ public class VSCHConfig { public static final ForgeConfigSpec.BooleanValue ENABLE_PLACE_SHIP_PLATFORM; + public static final ForgeConfigSpec.BooleanValue PLAYER_FREE_ROTATION_IN_SPACE; + private static final List DEFAULT_ASSEMBLE_BLACKLIST = List.of( "minecraft:barrier", "minecraft:bedrock", @@ -129,6 +131,8 @@ public class VSCHConfig { ENABLE_PLACE_SHIP_PLATFORM = BUILDER.comment("After enabled, the block placed by key N will be spawned as a ship.").define("enable_place_ship_platform", false); + PLAYER_FREE_ROTATION_IN_SPACE = BUILDER.comment("Allow player to free rotate in space.").define("player_free_rotation_in_space", false); + BUILDER.pop(); SPEC = BUILDER.build(); diff --git a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java new file mode 100644 index 00000000..922d7931 --- /dev/null +++ b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java @@ -0,0 +1,103 @@ +package net.jcm.vsch.entity.player; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.entity.PartEntity; + +public class MultiPartPlayer extends PartEntity { + private final EntityDimensions size; + + public MultiPartPlayer(final Player parent, final float size) { + super(parent); + this.size = EntityDimensions.scalable(size, size); + this.refreshDimensions(); + } + + @Override + protected void defineSynchedData() {} + + @Override + protected void readAdditionalSaveData(final CompoundTag data) {} + + @Override + protected void addAdditionalSaveData(final CompoundTag data) {} + + @Override + public boolean shouldBeSaved() { + return false; + } + + @Override + public boolean isNoGravity() { + return true; + } + + @Override + public boolean isPickable() { + return this.getParent().isPickable(); + } + + @Override + public ItemStack getPickResult() { + return this.getParent().getPickResult(); + } + + @Override + public boolean isInvulnerableTo(final DamageSource source) { + return this.getParent().isInvulnerableTo(source); + } + + @Override + public boolean hurt(final DamageSource source, final float damage) { + if (this.isInvulnerableTo(source)) { + return false; + } + return this.getParent().hurt(source, damage); + } + + @Override + public boolean is(final Entity other) { + return this == other || this.getParent().is(other); + } + + @Override + public Entity getVehicle() { + return this.getParent().getVehicle(); + } + + @Override + public Entity getRootVehicle() { + return this.getParent().getRootVehicle(); + } + + @Override + public EntityDimensions getDimensions(final Pose pose) { + return this.size; + } + + @Override + public Vec3 getDeltaMovement() { + return this.getParent().getDeltaMovement(); + } + + @Override + public void setDeltaMovement(final Vec3 vel) { + this.getParent().setDeltaMovement(vel); + } + + @Override + public void addDeltaMovement(final Vec3 vel) { + this.getParent().addDeltaMovement(vel); + } + + @Override + public void tick() { + this.firstTick = false; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java new file mode 100644 index 00000000..f2c20e97 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -0,0 +1,41 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.EntityAccessor; +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.entity.PartEntity; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Entity.class) +public abstract class MixinEntity implements EntityAccessor { + @Shadow + protected abstract Vec3 collide(Vec3 movement); + + @Override + public Vec3 vsch$collide(final Vec3 movement) { + return this.collide(movement); + } + + @Inject(method = "collide", at = @At("RETURN"), cancellable = true) + public void collide(final Vec3 originMovement, final CallbackInfoReturnable cir) { + if (!(((Object)(this)) instanceof Player player) || !(player instanceof FreeRotatePlayerAccessor frp)) { + return; + } + if (!frp.vsch$shouldFreeRotate()) { + return; + } + Vec3 movement = cir.getReturnValue(); + for (PartEntity part : player.getParts()) { + movement = ((EntityAccessor)(part)).vsch$collide(movement); + } + cir.setReturnValue(movement); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java new file mode 100644 index 00000000..75083b9f --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -0,0 +1,188 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.entity.player.MultiPartPlayer; +import net.jcm.vsch.util.VSCHUtils; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.MoverType; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +import org.spongepowered.asm.mixin.Final; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; + +@Mixin(Player.class) +public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlayerAccessor { + @Shadow + @Final + private static Map POSES; + + @Unique + private static final float SPACE_ENTITY_SIZE = 0.6f; + @Unique + private static final EntityDimensions SPACE_ENTITY_DIM = EntityDimensions.scalable(SPACE_ENTITY_SIZE, SPACE_ENTITY_SIZE); + + @Unique + private MultiPartPlayer[] parts; + @Unique + private MultiPartPlayer chestPart; + @Unique + private MultiPartPlayer feetPart; + @Unique + private Pose oldPose; + + protected MixinPlayer() { + super(null, null); + } + + @Inject(method = "", at = @At("RETURN")) + public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { + final Player player = (Player)((Object)(this)); + this.chestPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); + this.feetPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); + this.parts = new MultiPartPlayer[]{this.chestPart, this.feetPart}; + this.oldPose = this.getPose(); + this.refreshDimensions(); + } + + @Override + public boolean isMultipartEntity() { + return true; + } + + @Override + public MultiPartPlayer[] getParts() { + return this.parts; + } + + @Override + public boolean vsch$shouldFreeRotate() { + return !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); + } + + @Override + public EntityDimensions vsch$getVanillaDimensions(final Pose pose) { + return POSES.getOrDefault(pose, Player.STANDING_DIMENSIONS); + } + + @Override + public void setPose(final Pose newPose) { + super.setPose(newPose); + if (!this.vsch$shouldFreeRotate()) { + return; + } + if (!this.level().isClientSide) { + return; + } + final Pose oldPose = this.oldPose; + if (oldPose == newPose) { + return; + } + this.oldPose = newPose; + final float oldHeight = this.vsch$getVanillaDimensions(oldPose).height; + final float newHeight = this.vsch$getVanillaDimensions(newPose).height; + final Vec3 pos = this.position(); + double dx = 0, dy = newHeight - oldHeight, dz = 0; + this.setPos(pos.x + dx, pos.y + dy, pos.z + dz); + } + + @Override + public boolean startRiding(final Entity vehicle, final boolean force) { + final boolean ok = super.startRiding(vehicle, force); + if (ok) { + this.refreshDimensions(); + } + return ok; + } + + @Inject(method = "removeVehicle", at = @At("RETURN")) + public void removeVehicle(final CallbackInfo ci) { + this.refreshDimensions(); + } + + @Override + public void dismountTo(final double x, final double y, final double z) { + final float oldHeight = this.vsch$getVanillaDimensions(this.getPose()).height; + final float newHeight = SPACE_ENTITY_SIZE; + super.dismountTo(x, y + oldHeight - newHeight, z); + } + + @Override + public boolean isInLava() { + return super.isInLava() || this.chestPart.isInLava() || this.feetPart.isInLava(); + } + + @Override + public AABB getBoundingBoxForCulling() { + return super.getBoundingBoxForCulling().minmax(this.chestPart.getBoundingBoxForCulling()).minmax(this.feetPart.getBoundingBoxForCulling()); + } + + @Inject(method = "getStandingEyeHeight", at = @At("RETURN"), cancellable = true) + public void getStandingEyeHeight(final Pose pose, final EntityDimensions dimension, final CallbackInfoReturnable cir) { + if (!this.vsch$shouldFreeRotate()) { + return; + } + final float height = this.vsch$getVanillaDimensions(pose).height; + float eyeHeight = cir.getReturnValueF(); + eyeHeight += SPACE_ENTITY_SIZE - height; + cir.setReturnValue(eyeHeight); + } + + @Inject(method = "getDimensions", at = @At("HEAD"), cancellable = true) + public void getDimensions(final Pose pose, final CallbackInfoReturnable cir) { + if (this.vsch$shouldFreeRotate()) { + cir.setReturnValue(SPACE_ENTITY_DIM); + } + } + + @Inject(method = "maybeBackOffFromEdge", at = @At("HEAD"), cancellable = true) + protected void maybeBackOffFromEdge(final Vec3 movement, final MoverType moveType, final CallbackInfoReturnable cir) { + if (!this.vsch$shouldFreeRotate()) { + return; + } + // TODO: implement edge backoff in space, may respect to delta movement + cir.setReturnValue(movement); + } + + @Override + protected void checkFallDamage(final double dy, final boolean onGround, final BlockState block, final BlockPos pos) { + // TODO: implement fall damage / collision damage in space + } + + @Inject(method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setSpeed(F)V", ordinal = 0)) + public void aiStep(final CallbackInfo ci) { + final float height = this.vsch$getVanillaDimensions(this.getPose()).height; + double feetX = 0; + double feetY = SPACE_ENTITY_SIZE - height; + double feetZ = 0; + this.updatePartPos(this.chestPart, feetX / 2, feetY / 2, feetZ / 2); + this.updatePartPos(this.feetPart, feetX, feetY, feetZ); + } + + @Unique + private void updatePartPos(final MultiPartPlayer part, final double dx, final double dy, final double dz) { + Vec3 pos = this.position(); + if (this.vsch$shouldFreeRotate()) { + pos = pos.add(dx, dy, dz); + } + part.setOldPosAndRot(); + part.setPos(pos); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java new file mode 100644 index 00000000..bd5e3bc5 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java @@ -0,0 +1,57 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.player.PlayerRenderer; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.phys.Vec3; + +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerRenderer.class) +public abstract class MixinPlayerRenderer { + @Inject(method = "render", at = @At("HEAD")) + public void render$before( + final AbstractClientPlayer player, + final float yaw, + final float partialTick, + final PoseStack poseStack, + final MultiBufferSource bufferSource, + final int packedLight, + final CallbackInfo ci + ) { + poseStack.pushPose(); + // poseStack.mulPose(); + } + + @Inject(method = "render", at = @At("RETURN")) + public void render$after( + final AbstractClientPlayer player, + final float yaw, + final float partialTick, + final PoseStack poseStack, + final MultiBufferSource bufferSource, + final int packedLight, + final CallbackInfo ci + ) { + poseStack.popPose(); + } + + @Inject(method = "getRenderOffset", at = @At("RETURN"), cancellable = true) + public void getRenderOffset(final AbstractClientPlayer player, final float partialTick, final CallbackInfoReturnable cir) { + if (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$shouldFreeRotate()) { + return; + } + Vec3 offset = cir.getReturnValue(); + final EntityDimensions vanillaDim = frp.vsch$getVanillaDimensions(player.getPose()); + offset = offset.add(0, 0.6 - vanillaDim.height, 0); + cir.setReturnValue(offset); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java new file mode 100644 index 00000000..7dda4fb9 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java @@ -0,0 +1,12 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.minecraft.server.level.ServerPlayer; + +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(ServerPlayer.class) +public abstract class MixinServerPlayer extends MixinPlayer { +} diff --git a/src/main/java/net/jcm/vsch/util/EmptyChunkAccess.java b/src/main/java/net/jcm/vsch/util/EmptyChunkAccess.java index bab17c00..f2c742df 100644 --- a/src/main/java/net/jcm/vsch/util/EmptyChunkAccess.java +++ b/src/main/java/net/jcm/vsch/util/EmptyChunkAccess.java @@ -128,7 +128,6 @@ public static boolean shouldUseEmptyChunk(final Level level, final int x, final if (VSGameUtilsKt.isChunkInShipyard(level, x, z)) { return false; } - final CosmosModVariables.WorldVariables worldVars = CosmosModVariables.WorldVariables.get(level); - return worldVars.dimension_type.getString(level.dimension().location().toString()).equals("space"); + return VSCHUtils.isSpaceLevel(level); } } diff --git a/src/main/java/net/jcm/vsch/util/VSCHUtils.java b/src/main/java/net/jcm/vsch/util/VSCHUtils.java index 77e35369..436c9820 100644 --- a/src/main/java/net/jcm/vsch/util/VSCHUtils.java +++ b/src/main/java/net/jcm/vsch/util/VSCHUtils.java @@ -25,6 +25,7 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.targeting.TargetingConditions; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; @@ -142,6 +143,12 @@ public static ServerLevel dimToLevel(final String dimensionString) { return ValkyrienSkiesMod.getCurrentServer().getLevel(ResourceKey.create(Registries.DIMENSION, new ResourceLocation(dimensionString))); } + public static boolean isSpaceLevel(final Level level) { + return level.dimension().location().getNamespace().equals("cosmos"); + // final CosmosModVariables.WorldVariables worldVars = CosmosModVariables.WorldVariables.get(level); + // return worldVars.dimension_type.getString(level.dimension().location().toString()).equals("space"); + } + /** * Gets the nearest (if available) planet to the position in the dimensionId. * diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 28c036a5..e89b820c 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -16,7 +16,10 @@ "create.MixinLinearActuatorBlockEntity", "create.MixinMechanicalBearingBlockEntity", "minecraft.MixinChunkMap", + "minecraft.MixinEntity", "minecraft.MixinMob", + "minecraft.MixinPlayer", + "minecraft.MixinPlayerRenderer", "valkyrienskies.MixinShipAssemblyKt", "valkyrienskies.MixinVSGameUtilsKt", "valkyrienskies.accessor.ServerShipObjectWorldAccessor" From a2c5f28dbfec65691ada4f3db6d8e5c0e80df715 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 12:14:50 -0600 Subject: [PATCH 02/32] fix dismount to --- .../vsch/mixin/minecraft/MixinServerPlayer.java | 16 ++++++++++++++-- src/main/resources/vsch.mixins.json | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java index 7dda4fb9..3fa37d93 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java @@ -4,9 +4,21 @@ 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; +import org.spongepowered.asm.mixin.injection.ModifyArg; @Mixin(ServerPlayer.class) public abstract class MixinServerPlayer extends MixinPlayer { + @ModifyArg( + method = "dismountTo", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;setPos(DDD)V"), + index = 1 + ) + public double dismountTo$setPos$y(double y) { + if (this.vsch$shouldFreeRotate()) { + final float oldHeight = this.vsch$getVanillaDimensions(this.getPose()).height; + final float newHeight = 0.6f; + y += oldHeight - newHeight; + } + return y; + } } diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index e89b820c..887f6cb6 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -20,6 +20,7 @@ "minecraft.MixinMob", "minecraft.MixinPlayer", "minecraft.MixinPlayerRenderer", + "minecraft.MixinServerPlayer", "valkyrienskies.MixinShipAssemblyKt", "valkyrienskies.MixinVSGameUtilsKt", "valkyrienskies.accessor.ServerShipObjectWorldAccessor" From e7e156408255d83eef3c3e159c806a028ab413e2 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 12:44:27 -0600 Subject: [PATCH 03/32] fix collision with ships --- .../net/jcm/vsch/accessor/EntityAccessor.java | 3 +++ .../vsch/entity/player/MultiPartPlayer.java | 8 ++++++ .../jcm/vsch/mixin/minecraft/MixinEntity.java | 26 ++++++++++++++++++- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 8 ++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java b/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java index 1e4023dd..ad6e2373 100644 --- a/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/EntityAccessor.java @@ -1,7 +1,10 @@ package net.jcm.vsch.accessor; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; public interface EntityAccessor { Vec3 vsch$collide(Vec3 movement); + void vsch$checkInsideBlocks(); + void vsch$onInsideBlock(BlockState block); } diff --git a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java index 922d7931..3655577d 100644 --- a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java +++ b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java @@ -1,5 +1,7 @@ package net.jcm.vsch.entity.player; +import net.jcm.vsch.accessor.EntityAccessor; + import net.minecraft.nbt.CompoundTag; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; @@ -7,6 +9,7 @@ import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import net.minecraftforge.entity.PartEntity; @@ -33,6 +36,11 @@ public boolean shouldBeSaved() { return false; } + @Override + protected void onInsideBlock(final BlockState block) { + ((EntityAccessor)(this.getParent())).vsch$onInsideBlock(block); + } + @Override public boolean isNoGravity() { return true; diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java index f2c20e97..0ad773a9 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -5,9 +5,13 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import net.minecraftforge.entity.PartEntity; +import org.valkyrienskies.mod.common.util.EntityShipCollisionUtils; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -16,16 +20,35 @@ @Mixin(Entity.class) public abstract class MixinEntity implements EntityAccessor { + @Shadow + public abstract Level level(); + @Shadow protected abstract Vec3 collide(Vec3 movement); + @Shadow + protected abstract void checkInsideBlocks(); + + @Shadow + protected abstract void onInsideBlock(BlockState block); + @Override public Vec3 vsch$collide(final Vec3 movement) { return this.collide(movement); } + @Override + public void vsch$checkInsideBlocks() { + this.checkInsideBlocks(); + } + + @Override + public void vsch$onInsideBlock(final BlockState block) { + this.onInsideBlock(block); + } + @Inject(method = "collide", at = @At("RETURN"), cancellable = true) - public void collide(final Vec3 originMovement, final CallbackInfoReturnable cir) { + private void collide(final Vec3 originMovement, final CallbackInfoReturnable cir) { if (!(((Object)(this)) instanceof Player player) || !(player instanceof FreeRotatePlayerAccessor frp)) { return; } @@ -34,6 +57,7 @@ public void collide(final Vec3 originMovement, final CallbackInfoReturnable part : player.getParts()) { + movement = EntityShipCollisionUtils.INSTANCE.adjustEntityMovementForShipCollisions(part, movement, part.getBoundingBox(), this.level()); movement = ((EntityAccessor)(part)).vsch$collide(movement); } cir.setReturnValue(movement); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 75083b9f..8c05f788 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -1,5 +1,6 @@ package net.jcm.vsch.mixin.minecraft; +import net.jcm.vsch.accessor.EntityAccessor; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.jcm.vsch.entity.player.MultiPartPlayer; import net.jcm.vsch.util.VSCHUtils; @@ -103,6 +104,13 @@ public void setPose(final Pose newPose) { this.setPos(pos.x + dx, pos.y + dy, pos.z + dz); } + @Override + protected void checkInsideBlocks() { + super.checkInsideBlocks(); + ((EntityAccessor)(this.chestPart)).vsch$checkInsideBlocks(); + ((EntityAccessor)(this.feetPart)).vsch$checkInsideBlocks(); + } + @Override public boolean startRiding(final Entity vehicle, final boolean force) { final boolean ok = super.startRiding(vehicle, force); From 381ccf9292c6f232f770cd186f7845f3e555b864 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 13:20:35 -0600 Subject: [PATCH 04/32] make player has no firction --- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 35 +++++++++++++++++++ .../java/net/jcm/vsch/util/BooleanRef.java | 9 +++++ 2 files changed, 44 insertions(+) create mode 100644 src/main/java/net/jcm/vsch/util/BooleanRef.java diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 8c05f788..b85090fb 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -3,6 +3,7 @@ import net.jcm.vsch.accessor.EntityAccessor; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.jcm.vsch.entity.player.MultiPartPlayer; +import net.jcm.vsch.util.BooleanRef; import net.jcm.vsch.util.VSCHUtils; import com.mojang.authlib.GameProfile; @@ -17,6 +18,9 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; + +import org.valkyrienskies.mod.common.VSGameUtilsKt; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -39,6 +43,8 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay private static final float SPACE_ENTITY_SIZE = 0.6f; @Unique private static final EntityDimensions SPACE_ENTITY_DIM = EntityDimensions.scalable(SPACE_ENTITY_SIZE, SPACE_ENTITY_SIZE); + @Unique + private static final double SUPPORT_CHECK_DISTANCE = 0.1; @Unique private MultiPartPlayer[] parts; @@ -104,6 +110,35 @@ public void setPose(final Pose newPose) { this.setPos(pos.x + dx, pos.y + dy, pos.z + dz); } + @Override + public boolean shouldDiscardFriction() { + if (super.shouldDiscardFriction()) { + return true; + } + if (!this.vsch$shouldFreeRotate()) { + return false; + } + final Level level = this.level(); + final BooleanRef hasFirction = new BooleanRef(false); + final Entity[] parts = new Entity[]{this, this.chestPart, this.feetPart}; + for (final Entity part : parts) { + VSGameUtilsKt.transformFromWorldToNearbyShipsAndWorld(level, part.getBoundingBox(), (box) -> { + if (hasFirction.value) { + return; + } + for (final VoxelShape shape : level.getBlockCollisions(part, box.inflate(SUPPORT_CHECK_DISTANCE))) { + if (!shape.isEmpty()) { + hasFirction.value = true; + } + } + }); + if (hasFirction.value) { + break; + } + } + return !hasFirction.value; + } + @Override protected void checkInsideBlocks() { super.checkInsideBlocks(); diff --git a/src/main/java/net/jcm/vsch/util/BooleanRef.java b/src/main/java/net/jcm/vsch/util/BooleanRef.java new file mode 100644 index 00000000..c2ddbbf6 --- /dev/null +++ b/src/main/java/net/jcm/vsch/util/BooleanRef.java @@ -0,0 +1,9 @@ +package net.jcm.vsch.util; + +public final class BooleanRef { + public boolean value; + + public BooleanRef(final boolean value) { + this.value = value; + } +} From 3e6743e42b00312d14ccff46b1686a3902b57149 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 14:15:21 -0600 Subject: [PATCH 05/32] fix Y firction --- .../accessor/FreeRotatePlayerAccessor.java | 2 + .../vsch/mixin/client/MixinLocalPlayer.java | 44 +++++++++++++++++++ .../mixin/minecraft/MixinLivingEntity.java | 35 +++++++++++++++ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 23 ++++++---- src/main/resources/vsch.mixins.json | 4 +- 5 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 66e0b316..f0974da9 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -6,5 +6,7 @@ public interface FreeRotatePlayerAccessor { boolean vsch$shouldFreeRotate(); + boolean vsch$hasSupportingBlock(); + EntityDimensions vsch$getVanillaDimensions(Pose pose); } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java new file mode 100644 index 00000000..b132fae1 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -0,0 +1,44 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.mixin.minecraft.MixinPlayer; + +import net.minecraft.client.player.Input; +import net.minecraft.client.player.LocalPlayer; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LocalPlayer.class) +public abstract class MixinLocalPlayer extends MixinPlayer { + @Shadow + public Input input; + + @Shadow + protected abstract boolean isControlledCamera(); + + @Inject( + method = "aiStep", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/player/LocalPlayer;jumpableVehicle()Lnet/minecraft/world/entity/PlayerRideableJumping;", + ordinal = 0 + ) + ) + public void aiStep$afterFlying(final CallbackInfo ci) { + if (!this.getAbilities().flying && this.isControlledCamera()) { + int direction = 0; + if (this.input.shiftKeyDown) { + direction--; + } + if (this.input.jumping) { + direction++; + } + if (direction != 0) { + this.setDeltaMovement(this.getDeltaMovement().add(0, direction * this.getFlyingSpeed(), 0)); + } + } + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java new file mode 100644 index 00000000..c87f6f4e --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java @@ -0,0 +1,35 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity extends Entity { + protected MixinLivingEntity() { + super(null, null); + } + + @WrapOperation( + method = "travel", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;shouldDiscardFriction()Z") + ), + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setDeltaMovement(DDD)V", ordinal = 0) + ) + public void travel(final LivingEntity self, double x, double y, double z, final Operation operation) { + if (self instanceof FreeRotatePlayerAccessor frp && frp.vsch$hasSupportingBlock()) { + x *= 0.91; + y *= 0.91; + z *= 0.91; + } + operation.call(self, x, y, z); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index b85090fb..50909c14 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -13,6 +13,7 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.player.Abilities; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; @@ -59,6 +60,9 @@ protected MixinPlayer() { super(null, null); } + @Shadow + public abstract Abilities getAbilities(); + @Inject(method = "", at = @At("RETURN")) public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { final Player player = (Player)((Object)(this)); @@ -112,31 +116,34 @@ public void setPose(final Pose newPose) { @Override public boolean shouldDiscardFriction() { - if (super.shouldDiscardFriction()) { - return true; - } + return super.shouldDiscardFriction() || this.vsch$shouldFreeRotate(); + } + + @Override + public boolean vsch$hasSupportingBlock() { if (!this.vsch$shouldFreeRotate()) { return false; } final Level level = this.level(); - final BooleanRef hasFirction = new BooleanRef(false); + final BooleanRef hasSupport = new BooleanRef(false); final Entity[] parts = new Entity[]{this, this.chestPart, this.feetPart}; for (final Entity part : parts) { VSGameUtilsKt.transformFromWorldToNearbyShipsAndWorld(level, part.getBoundingBox(), (box) -> { - if (hasFirction.value) { + if (hasSupport.value) { return; } for (final VoxelShape shape : level.getBlockCollisions(part, box.inflate(SUPPORT_CHECK_DISTANCE))) { if (!shape.isEmpty()) { - hasFirction.value = true; + hasSupport.value = true; + return; } } }); - if (hasFirction.value) { + if (hasSupport.value) { break; } } - return !hasFirction.value; + return hasSupport.value; } @Override diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 887f6cb6..3294c52b 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -17,6 +17,7 @@ "create.MixinMechanicalBearingBlockEntity", "minecraft.MixinChunkMap", "minecraft.MixinEntity", + "minecraft.MixinLivingEntity", "minecraft.MixinMob", "minecraft.MixinPlayer", "minecraft.MixinPlayerRenderer", @@ -26,7 +27,8 @@ "valkyrienskies.accessor.ServerShipObjectWorldAccessor" ], "client": [ - "client.MixinGui" + "client.MixinGui", + "client.MixinLocalPlayer" ], "injectors": { "defaultRequire": 1 From bb9046caf1fa660f477848322b731e3370c1ed71 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 14:38:14 -0600 Subject: [PATCH 06/32] fix crouching pose when flying --- .../vsch/mixin/client/MixinLocalPlayer.java | 39 +++++++++++++------ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 18 ++++++++- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index b132fae1..6d780ff8 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -5,6 +5,8 @@ import net.minecraft.client.player.Input; import net.minecraft.client.player.LocalPlayer; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -19,6 +21,20 @@ public abstract class MixinLocalPlayer extends MixinPlayer { @Shadow protected abstract boolean isControlledCamera(); + @WrapOperation( + method = "aiStep", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isShiftKeyDown()Z", ordinal = 0) + ) + protected boolean aiStep$setCrouching$isShiftKeyDown(final LocalPlayer self, final Operation operation) { + if (((Boolean)(operation.call(self))) == Boolean.FALSE) { + return false; + } + if (this.getAbilities().flying || !this.vsch$shouldFreeRotate()) { + return true; + } + return this.onGround(); + } + @Inject( method = "aiStep", at = @At( @@ -28,17 +44,18 @@ public abstract class MixinLocalPlayer extends MixinPlayer { ) ) public void aiStep$afterFlying(final CallbackInfo ci) { - if (!this.getAbilities().flying && this.isControlledCamera()) { - int direction = 0; - if (this.input.shiftKeyDown) { - direction--; - } - if (this.input.jumping) { - direction++; - } - if (direction != 0) { - this.setDeltaMovement(this.getDeltaMovement().add(0, direction * this.getFlyingSpeed(), 0)); - } + if (!this.vsch$shouldFreeRotate() || this.getAbilities().flying || !this.isControlledCamera()) { + return; + } + int direction = 0; + if (this.input.shiftKeyDown) { + direction--; + } + if (this.input.jumping) { + direction++; + } + if (direction != 0) { + this.setDeltaMovement(this.getDeltaMovement().add(0, direction * this.getFlyingSpeed(), 0)); } } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 50909c14..a181fd2a 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -23,6 +23,8 @@ import org.valkyrienskies.mod.common.VSGameUtilsKt; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -114,9 +116,23 @@ public void setPose(final Pose newPose) { this.setPos(pos.x + dx, pos.y + dy, pos.z + dz); } + @WrapOperation( + method = "updatePlayerPose", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isShiftKeyDown()Z") + ) + protected boolean updatePlayerPose$isShiftKeyDown(final Player self, final Operation operation) { + if (((Boolean)(operation.call(self))) == Boolean.FALSE) { + return false; + } + if (this.getAbilities().flying || !this.vsch$shouldFreeRotate()) { + return true; + } + return this.onGround(); + } + @Override public boolean shouldDiscardFriction() { - return super.shouldDiscardFriction() || this.vsch$shouldFreeRotate(); + return super.shouldDiscardFriction() || !this.getAbilities().flying && this.vsch$shouldFreeRotate(); } @Override From 6547c2235147ab6f62601d6d6acf6e86ea0017ca Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 15:18:06 -0600 Subject: [PATCH 07/32] allow to step in free rotation --- .../jcm/vsch/entity/player/MultiPartPlayer.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java index 3655577d..903607f6 100644 --- a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java +++ b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java @@ -41,6 +41,11 @@ protected void onInsideBlock(final BlockState block) { ((EntityAccessor)(this.getParent())).vsch$onInsideBlock(block); } + @Override + public boolean onGround() { + return this.getParent().onGround(); + } + @Override public boolean isNoGravity() { return true; @@ -56,6 +61,11 @@ public ItemStack getPickResult() { return this.getParent().getPickResult(); } + @Override + public boolean isShiftKeyDown() { + return this.getParent().isShiftKeyDown(); + } + @Override public boolean isInvulnerableTo(final DamageSource source) { return this.getParent().isInvulnerableTo(source); @@ -104,6 +114,11 @@ public void addDeltaMovement(final Vec3 vel) { this.getParent().addDeltaMovement(vel); } + @Override + public float maxUpStep() { + return this.getParent().maxUpStep(); + } + @Override public void tick() { this.firstTick = false; From 224290a2fd5ba96bbfac6580a99c0c9867ec639f Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 28 Jul 2025 23:09:16 -0600 Subject: [PATCH 08/32] trying rotate camera --- .../accessor/FreeRotatePlayerAccessor.java | 12 +- .../jcm/vsch/mixin/client/MixinCamera.java | 61 +++++++ .../vsch/mixin/client/MixinGameRenderer.java | 44 +++++ .../vsch/mixin/client/MixinLocalPlayer.java | 12 +- .../mixin/client/MixinPlayerRenderer.java | 71 ++++++++ .../jcm/vsch/mixin/minecraft/MixinEntity.java | 11 +- .../mixin/minecraft/MixinLivingEntity.java | 2 +- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 160 +++++++++++++++--- .../mixin/minecraft/MixinPlayerRenderer.java | 57 ------- .../mixin/minecraft/MixinServerPlayer.java | 2 +- src/main/resources/vsch.mixins.json | 6 +- 11 files changed, 342 insertions(+), 96 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java delete mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index f0974da9..428535cb 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -3,8 +3,18 @@ import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.Pose; +import org.joml.Quaternionf; + public interface FreeRotatePlayerAccessor { - boolean vsch$shouldFreeRotate(); + boolean vsch$isFreeRotating(); + + Quaternionf vsch$getRotation(); + + void vsch$setRotation(Quaternionf rotation); + + Quaternionf vsch$getRotationO(); + + void vsch$setRotationO(Quaternionf rotation); boolean vsch$hasSupportingBlock(); diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java new file mode 100644 index 00000000..fd46f444 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java @@ -0,0 +1,61 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.client.Camera; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.BlockGetter; + +import org.joml.Quaternionf; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Camera.class) +public abstract class MixinCamera { + @Shadow + private Entity entity; + + @Shadow + @Final + private Quaternionf rotation; + + @Inject( + method = "setup", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 0) + ) + public void setup( + final BlockGetter level, + final Entity entity, + final boolean isThirdPerson, + final boolean isMirrored, + final float partialTick, + final CallbackInfo ci + ) { + if (!(entity instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return; + } + // frp.vsch$getRotationO().slerp(frp.vsch$getRotation(), partialTick, this.rotation); + this.rotation.set(frp.vsch$getRotation()); + if (isThirdPerson && isMirrored) { + this.rotation.conjugate(); + } + } + + @WrapOperation( + method = "setRotation", + at = @At(value = "INVOKE", target = "Lorg/joml/Quaternionf;rotationYXZ(FFF)Lorg/joml/Quaternionf;", remap = false) + ) + private Quaternionf setRotation$rotationYXZ(final Quaternionf rotation, final float y, final float x, final float z, final Operation operation) { + if (this.entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + return rotation; + } + return operation.call(rotation, y, x, z); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java new file mode 100644 index 00000000..4cd35e1e --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java @@ -0,0 +1,44 @@ +package net.jcm.vsch.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Camera; +import net.minecraft.client.renderer.GameRenderer; + +import org.joml.Quaternionf; + +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(GameRenderer.class) +public abstract class MixinGameRenderer { + @Redirect( + method = "renderLevel", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") + ), + at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 0) + ) + public void renderLevel$mulPose$0( + final PoseStack stack, + final Quaternionf rotationX, + @Local final Camera camera + ) { + stack.mulPose(camera.rotation()); + } + + @Redirect( + method = "renderLevel", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") + ), + at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 1) + ) + public void renderLevel$mulPose$1( + final PoseStack stack, + final Quaternionf rotationY + ) { + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 6d780ff8..06623db1 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -25,14 +25,16 @@ public abstract class MixinLocalPlayer extends MixinPlayer { method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isShiftKeyDown()Z", ordinal = 0) ) - protected boolean aiStep$setCrouching$isShiftKeyDown(final LocalPlayer self, final Operation operation) { - if (((Boolean)(operation.call(self))) == Boolean.FALSE) { + protected boolean aiStep$setCrouching$isShiftKeyDown(final LocalPlayer self, final Operation operation) { + if (operation.call(self) == Boolean.FALSE) { return false; } - if (this.getAbilities().flying || !this.vsch$shouldFreeRotate()) { + if (this.getAbilities().flying || !this.vsch$isFreeRotating()) { return true; } - return this.onGround(); + // TODO: fix crouching pose + // return this.onGround(); + return false; } @Inject( @@ -44,7 +46,7 @@ public abstract class MixinLocalPlayer extends MixinPlayer { ) ) public void aiStep$afterFlying(final CallbackInfo ci) { - if (!this.vsch$shouldFreeRotate() || this.getAbilities().flying || !this.isControlledCamera()) { + if (!this.vsch$isFreeRotating() || this.getAbilities().flying || !this.isControlledCamera()) { return; } int direction = 0; diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java new file mode 100644 index 00000000..068fa10e --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java @@ -0,0 +1,71 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.player.PlayerRenderer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.phys.Vec3; + +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(PlayerRenderer.class) +public abstract class MixinPlayerRenderer { + @WrapMethod(method = "render") + public void render( + final AbstractClientPlayer player, + final float yaw, + final float partialTick, + final PoseStack poseStack, + final MultiBufferSource bufferSource, + final int packedLight, + final Operation operation + ) { + System.out.println("rendering player: " + player + ", " + (player instanceof FreeRotatePlayerAccessor) + ", " + (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating())); + if (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + operation.call(player, yaw, partialTick, poseStack, bufferSource, packedLight); + return; + } + + poseStack.pushPose(); + + final float playerYHeadRotO = player.yHeadRotO; + final float playerYHeadRot = player.yHeadRot; + final float playerYBodyRotO = player.yBodyRotO; + final float playerYBodyRot = player.yBodyRot; + final float playerXRotO = player.xRotO; + final float playerXRot = player.getXRot(); + final Quaternionf rotation = new Quaternionf(frp.vsch$getRotation()); + + player.yHeadRotO = 0; + player.yHeadRot = 0; + player.yBodyRotO = 0; + player.yBodyRot = 0; + player.xRotO = 0; + player.setXRot(0); + + final EntityDimensions vanillaDim = frp.vsch$getVanillaDimensions(player.getPose()); + poseStack.translate(0, 0.6f / 2, 0); + poseStack.mulPose(rotation); + poseStack.translate(0, 0.6f - vanillaDim.height - 0.6f / 2, 0); + + operation.call(player, yaw, partialTick, poseStack, bufferSource, packedLight); + + player.yHeadRotO = playerYHeadRotO; + player.yHeadRot = playerYHeadRot; + player.yBodyRotO = playerYBodyRotO; + player.yBodyRot = playerYBodyRot; + player.xRotO = playerXRotO; + player.setXRot(playerXRot); + + poseStack.popPose(); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java index 0ad773a9..687f2b6b 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -16,6 +16,7 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(Entity.class) @@ -52,7 +53,7 @@ private void collide(final Vec3 originMovement, final CallbackInfoReturnable operation) { if (self instanceof FreeRotatePlayerAccessor frp && frp.vsch$hasSupportingBlock()) { x *= 0.91; y *= 0.91; diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index a181fd2a..2b1c095e 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -8,6 +8,7 @@ import com.mojang.authlib.GameProfile; import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.LivingEntity; @@ -21,6 +22,8 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; +import org.joml.Quaternionf; +import org.joml.Vector3f; import org.valkyrienskies.mod.common.VSGameUtilsKt; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; @@ -49,6 +52,12 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay @Unique private static final double SUPPORT_CHECK_DISTANCE = 0.1; + @Unique + private boolean freeRotation = false; + @Unique + private Quaternionf rotation = new Quaternionf(); + @Unique + private Quaternionf rotationO = new Quaternionf(); @Unique private MultiPartPlayer[] parts; @Unique @@ -68,11 +77,12 @@ protected MixinPlayer() { @Inject(method = "", at = @At("RETURN")) public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { final Player player = (Player)((Object)(this)); + this.chestPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); this.feetPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); this.parts = new MultiPartPlayer[]{this.chestPart, this.feetPart}; this.oldPose = this.getPose(); - this.refreshDimensions(); + this.updateDefaultFreeRotation(); } @Override @@ -86,8 +96,73 @@ public MultiPartPlayer[] getParts() { } @Override - public boolean vsch$shouldFreeRotate() { - return !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); + public boolean vsch$isFreeRotating() { + return this.freeRotation; + } + + @Override + public Quaternionf vsch$getRotation() { + return this.rotation; + } + + @Override + public void vsch$setRotation(final Quaternionf rotation) { + if (this.rotation != rotation) { + this.rotation.set(rotation); + } + final Vector3f angles = this.rotation.getEulerAnglesYXZ(new Vector3f()); + super.setXRot(angles.x * Mth.RAD_TO_DEG); + super.setYRot(-angles.y * Mth.RAD_TO_DEG); + this.yHeadRot = this.yBodyRot = this.getYRot(); + } + + @Override + public Quaternionf vsch$getRotationO() { + return this.rotationO; + } + + @Override + public void vsch$setRotationO(final Quaternionf rotation) { + if (this.rotationO != rotation) { + this.rotationO.set(rotation); + } + final Vector3f angles = this.rotationO.getEulerAnglesYXZ(new Vector3f()); + this.xRotO = angles.x * Mth.RAD_TO_DEG; + this.yHeadRotO = this.yBodyRotO = this.yRotO = -angles.y * Mth.RAD_TO_DEG; + } + + @Override + public void setYRot(final float yRot) { + super.setYRot(yRot); + this.reCalcRotation(); + } + + @Override + public void setXRot(final float xRot) { + super.setXRot(xRot); + this.reCalcRotation(); + } + + @Unique + private void reCalcRotation() { + if (this.rotation == null) { + return; + } + this.rotation.rotationYXZ(Mth.DEG_TO_RAD * -this.getYRot(), Mth.DEG_TO_RAD * this.getXRot(), 0); + } + + @Unique + protected void updateDefaultFreeRotation() { + final boolean freeRotation = !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); + if (this.freeRotation == freeRotation) { + return; + } + this.freeRotation = freeRotation; + this.refreshDimensions(); + if (freeRotation) { + this.reCalcRotation(); + this.rotationO.rotationYXZ(Mth.DEG_TO_RAD * -this.yRotO, Mth.DEG_TO_RAD * this.xRotO, 0); + } } @Override @@ -98,7 +173,7 @@ public MultiPartPlayer[] getParts() { @Override public void setPose(final Pose newPose) { super.setPose(newPose); - if (!this.vsch$shouldFreeRotate()) { + if (!this.vsch$isFreeRotating()) { return; } if (!this.level().isClientSide) { @@ -120,24 +195,47 @@ public void setPose(final Pose newPose) { method = "updatePlayerPose", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isShiftKeyDown()Z") ) - protected boolean updatePlayerPose$isShiftKeyDown(final Player self, final Operation operation) { - if (((Boolean)(operation.call(self))) == Boolean.FALSE) { + protected boolean updatePlayerPose$isShiftKeyDown(final Player self, final Operation operation) { + if (operation.call(self) == Boolean.FALSE) { return false; } - if (this.getAbilities().flying || !this.vsch$shouldFreeRotate()) { + if (this.getAbilities().flying || !this.vsch$isFreeRotating()) { return true; } - return this.onGround(); + // TODO: fix crouching pose + // return this.onGround(); + return false; + } + + @Override + public void turn(final double x, final double y) { + if (!this.vsch$isFreeRotating()) { + super.turn(x, y); + return; + } + if (x == 0 && y == 0) { + return; + } + final float yaw = (float)(x) * 0.15f * Mth.DEG_TO_RAD; + final float pitch = -(float)(y) * 0.15f * Mth.DEG_TO_RAD; + final Vector3f localX = new Vector3f(1, 0, 0).rotate(this.rotation); + final Vector3f localY = new Vector3f(0, 1, 0).rotate(this.rotation); + + final Quaternionf pitchRot = new Quaternionf().rotationAxis(pitch, localX.x, localX.y, localX.z); + final Quaternionf yawRot = new Quaternionf().rotationAxis(yaw, localY.x, localY.y, localY.z); + + this.vsch$setRotation(this.rotation.mul(pitchRot).mul(yawRot).normalize()); + this.vsch$setRotationO(this.rotationO.mul(pitchRot).mul(yawRot).normalize()); } @Override public boolean shouldDiscardFriction() { - return super.shouldDiscardFriction() || !this.getAbilities().flying && this.vsch$shouldFreeRotate(); + return super.shouldDiscardFriction() || !this.getAbilities().flying && this.vsch$isFreeRotating(); } @Override public boolean vsch$hasSupportingBlock() { - if (!this.vsch$shouldFreeRotate()) { + if (!this.vsch$isFreeRotating()) { return false; } final Level level = this.level(); @@ -169,18 +267,28 @@ protected void checkInsideBlocks() { ((EntityAccessor)(this.feetPart)).vsch$checkInsideBlocks(); } + @Override + public boolean isInLava() { + return super.isInLava() || this.chestPart.isInLava() || this.feetPart.isInLava(); + } + + @Override + protected void checkFallDamage(final double dy, final boolean onGround, final BlockState block, final BlockPos pos) { + // TODO: implement fall damage / collision damage in space + } + @Override public boolean startRiding(final Entity vehicle, final boolean force) { final boolean ok = super.startRiding(vehicle, force); if (ok) { - this.refreshDimensions(); + this.updateDefaultFreeRotation(); } return ok; } @Inject(method = "removeVehicle", at = @At("RETURN")) public void removeVehicle(final CallbackInfo ci) { - this.refreshDimensions(); + this.updateDefaultFreeRotation(); } @Override @@ -190,11 +298,6 @@ public void dismountTo(final double x, final double y, final double z) { super.dismountTo(x, y + oldHeight - newHeight, z); } - @Override - public boolean isInLava() { - return super.isInLava() || this.chestPart.isInLava() || this.feetPart.isInLava(); - } - @Override public AABB getBoundingBoxForCulling() { return super.getBoundingBoxForCulling().minmax(this.chestPart.getBoundingBoxForCulling()).minmax(this.feetPart.getBoundingBoxForCulling()); @@ -202,7 +305,7 @@ public AABB getBoundingBoxForCulling() { @Inject(method = "getStandingEyeHeight", at = @At("RETURN"), cancellable = true) public void getStandingEyeHeight(final Pose pose, final EntityDimensions dimension, final CallbackInfoReturnable cir) { - if (!this.vsch$shouldFreeRotate()) { + if (!this.vsch$isFreeRotating()) { return; } final float height = this.vsch$getVanillaDimensions(pose).height; @@ -213,14 +316,14 @@ public void getStandingEyeHeight(final Pose pose, final EntityDimensions dimensi @Inject(method = "getDimensions", at = @At("HEAD"), cancellable = true) public void getDimensions(final Pose pose, final CallbackInfoReturnable cir) { - if (this.vsch$shouldFreeRotate()) { + if (this.vsch$isFreeRotating()) { cir.setReturnValue(SPACE_ENTITY_DIM); } } @Inject(method = "maybeBackOffFromEdge", at = @At("HEAD"), cancellable = true) protected void maybeBackOffFromEdge(final Vec3 movement, final MoverType moveType, final CallbackInfoReturnable cir) { - if (!this.vsch$shouldFreeRotate()) { + if (!this.vsch$isFreeRotating()) { return; } // TODO: implement edge backoff in space, may respect to delta movement @@ -228,24 +331,25 @@ protected void maybeBackOffFromEdge(final Vec3 movement, final MoverType moveTyp } @Override - protected void checkFallDamage(final double dy, final boolean onGround, final BlockState block, final BlockPos pos) { - // TODO: implement fall damage / collision damage in space + protected void setLevel(final Level level) { + super.setLevel(level); + this.updateDefaultFreeRotation(); } @Inject(method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setSpeed(F)V", ordinal = 0)) public void aiStep(final CallbackInfo ci) { + this.setYBodyRot(this.getYHeadRot()); final float height = this.vsch$getVanillaDimensions(this.getPose()).height; - double feetX = 0; - double feetY = SPACE_ENTITY_SIZE - height; - double feetZ = 0; - this.updatePartPos(this.chestPart, feetX / 2, feetY / 2, feetZ / 2); - this.updatePartPos(this.feetPart, feetX, feetY, feetZ); + final Vector3f feetPos = new Vector3f(0, SPACE_ENTITY_SIZE - height, 0); + feetPos.rotate(this.rotation); + this.updatePartPos(this.feetPart, feetPos.x, feetPos.y, feetPos.z); + this.updatePartPos(this.chestPart, feetPos.x / 2, feetPos.y / 2, feetPos.z / 2); } @Unique private void updatePartPos(final MultiPartPlayer part, final double dx, final double dy, final double dz) { Vec3 pos = this.position(); - if (this.vsch$shouldFreeRotate()) { + if (this.vsch$isFreeRotating()) { pos = pos.add(dx, dy, dz); } part.setOldPosAndRot(); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java deleted file mode 100644 index bd5e3bc5..00000000 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayerRenderer.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.jcm.vsch.mixin.minecraft; - -import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.entity.player.PlayerRenderer; -import net.minecraft.world.entity.EntityDimensions; -import net.minecraft.world.phys.Vec3; - -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; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(PlayerRenderer.class) -public abstract class MixinPlayerRenderer { - @Inject(method = "render", at = @At("HEAD")) - public void render$before( - final AbstractClientPlayer player, - final float yaw, - final float partialTick, - final PoseStack poseStack, - final MultiBufferSource bufferSource, - final int packedLight, - final CallbackInfo ci - ) { - poseStack.pushPose(); - // poseStack.mulPose(); - } - - @Inject(method = "render", at = @At("RETURN")) - public void render$after( - final AbstractClientPlayer player, - final float yaw, - final float partialTick, - final PoseStack poseStack, - final MultiBufferSource bufferSource, - final int packedLight, - final CallbackInfo ci - ) { - poseStack.popPose(); - } - - @Inject(method = "getRenderOffset", at = @At("RETURN"), cancellable = true) - public void getRenderOffset(final AbstractClientPlayer player, final float partialTick, final CallbackInfoReturnable cir) { - if (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$shouldFreeRotate()) { - return; - } - Vec3 offset = cir.getReturnValue(); - final EntityDimensions vanillaDim = frp.vsch$getVanillaDimensions(player.getPose()); - offset = offset.add(0, 0.6 - vanillaDim.height, 0); - cir.setReturnValue(offset); - } -} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java index 3fa37d93..292fe4d1 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java @@ -14,7 +14,7 @@ public abstract class MixinServerPlayer extends MixinPlayer { index = 1 ) public double dismountTo$setPos$y(double y) { - if (this.vsch$shouldFreeRotate()) { + if (this.vsch$isFreeRotating()) { final float oldHeight = this.vsch$getVanillaDimensions(this.getPose()).height; final float newHeight = 0.6f; y += oldHeight - newHeight; diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 3294c52b..de3de21b 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -20,15 +20,17 @@ "minecraft.MixinLivingEntity", "minecraft.MixinMob", "minecraft.MixinPlayer", - "minecraft.MixinPlayerRenderer", "minecraft.MixinServerPlayer", "valkyrienskies.MixinShipAssemblyKt", "valkyrienskies.MixinVSGameUtilsKt", "valkyrienskies.accessor.ServerShipObjectWorldAccessor" ], "client": [ + "client.MixinCamera", + "client.MixinGameRenderer", "client.MixinGui", - "client.MixinLocalPlayer" + "client.MixinLocalPlayer", + "client.MixinPlayerRenderer" ], "injectors": { "defaultRequire": 1 From 509572536b564edeeb1ffbb85e80db2a56d4f2f8 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 29 Jul 2025 08:54:57 -0600 Subject: [PATCH 09/32] finally made mouse rotation works --- .../jcm/vsch/mixin/client/MixinCamera.java | 5 ++--- .../vsch/mixin/client/MixinGameRenderer.java | 22 +++++++++++++++++-- .../mixin/client/MixinPlayerRenderer.java | 1 - .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 20 ++++++++--------- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java index fd46f444..02e0b2da 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java @@ -41,10 +41,9 @@ public void setup( if (!(entity instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { return; } - // frp.vsch$getRotationO().slerp(frp.vsch$getRotation(), partialTick, this.rotation); - this.rotation.set(frp.vsch$getRotation()); + frp.vsch$getRotationO().slerp(frp.vsch$getRotation(), partialTick, this.rotation); if (isThirdPerson && isMirrored) { - this.rotation.conjugate(); + this.rotation.rotateY((float)(Math.PI)); } } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java index 4cd35e1e..988c7681 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java @@ -1,10 +1,12 @@ package net.jcm.vsch.mixin.client; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; import net.minecraft.client.Camera; import net.minecraft.client.renderer.GameRenderer; import org.joml.Quaternionf; +import org.joml.Vector3f; import com.llamalad7.mixinextras.sugar.Local; import org.spongepowered.asm.mixin.Mixin; @@ -23,10 +25,13 @@ public abstract class MixinGameRenderer { ) public void renderLevel$mulPose$0( final PoseStack stack, - final Quaternionf rotationX, + final Quaternionf rotationZ, @Local final Camera camera ) { - stack.mulPose(camera.rotation()); + final Vector3f angles = camera.rotation().getEulerAnglesYXZ(new Vector3f()); + stack.mulPose(Axis.ZP.rotation(angles.z)); + stack.mulPose(Axis.XP.rotation(angles.x)); + stack.mulPose(Axis.YP.rotation(-angles.y + (float)(Math.PI))); } @Redirect( @@ -37,6 +42,19 @@ public abstract class MixinGameRenderer { at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 1) ) public void renderLevel$mulPose$1( + final PoseStack stack, + final Quaternionf rotationX + ) { + } + + @Redirect( + method = "renderLevel", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") + ), + at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 2) + ) + public void renderLevel$mulPose$2( final PoseStack stack, final Quaternionf rotationY ) { diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java index 068fa10e..867c5cde 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java @@ -29,7 +29,6 @@ public void render( final int packedLight, final Operation operation ) { - System.out.println("rendering player: " + player + ", " + (player instanceof FreeRotatePlayerAccessor) + ", " + (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating())); if (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { operation.call(player, yaw, partialTick, poseStack, bufferSource, packedLight); return; diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 2b1c095e..cc003bfa 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -148,7 +148,8 @@ private void reCalcRotation() { if (this.rotation == null) { return; } - this.rotation.rotationYXZ(Mth.DEG_TO_RAD * -this.getYRot(), Mth.DEG_TO_RAD * this.getXRot(), 0); + final Vector3f oldAngles = this.rotation.getEulerAnglesYXZ(new Vector3f()); + this.rotation.rotationYXZ(Mth.DEG_TO_RAD * -this.getYRot(), Mth.DEG_TO_RAD * this.getXRot(), oldAngles.z); } @Unique @@ -161,7 +162,8 @@ protected void updateDefaultFreeRotation() { this.refreshDimensions(); if (freeRotation) { this.reCalcRotation(); - this.rotationO.rotationYXZ(Mth.DEG_TO_RAD * -this.yRotO, Mth.DEG_TO_RAD * this.xRotO, 0); + final Vector3f oldAnglesO = this.rotation.getEulerAnglesYXZ(new Vector3f()); + this.rotationO.rotationYXZ(Mth.DEG_TO_RAD * -this.yRotO, Mth.DEG_TO_RAD * this.xRotO, oldAnglesO.z); } } @@ -216,16 +218,14 @@ public void turn(final double x, final double y) { if (x == 0 && y == 0) { return; } - final float yaw = (float)(x) * 0.15f * Mth.DEG_TO_RAD; - final float pitch = -(float)(y) * 0.15f * Mth.DEG_TO_RAD; - final Vector3f localX = new Vector3f(1, 0, 0).rotate(this.rotation); - final Vector3f localY = new Vector3f(0, 1, 0).rotate(this.rotation); + final float yaw = -(float)(x) * 0.15f * Mth.DEG_TO_RAD; + final float pitch = (float)(y) * 0.15f * Mth.DEG_TO_RAD; - final Quaternionf pitchRot = new Quaternionf().rotationAxis(pitch, localX.x, localX.y, localX.z); - final Quaternionf yawRot = new Quaternionf().rotationAxis(yaw, localY.x, localY.y, localY.z); + final Quaternionf pitchRot = new Quaternionf().rotateX(pitch); + final Quaternionf yawRot = new Quaternionf().rotateY(yaw); - this.vsch$setRotation(this.rotation.mul(pitchRot).mul(yawRot).normalize()); - this.vsch$setRotationO(this.rotationO.mul(pitchRot).mul(yawRot).normalize()); + this.vsch$setRotation(this.rotation.mul(yawRot).mul(pitchRot).normalize()); + this.vsch$setRotationO(this.rotationO.mul(yawRot).mul(pitchRot).normalize()); } @Override From 81990c3b7c1260dd9930e1d623ac0a513022b792 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 29 Jul 2025 12:34:30 -0600 Subject: [PATCH 10/32] fix player tag --- ...ientboundPlayerPositionPacketAccessor.java | 7 ++ .../ServerboundMovePlayerPacketAccessor.java | 7 ++ .../client/MixinClientPacketListener.java | 42 ++++++++++ .../client/MixinLivingEntityRenderer.java | 76 +++++++++++++++++ .../vsch/mixin/client/MixinLocalPlayer.java | 52 +++++++----- .../client/MixinMultiPlayerGameMode.java | 27 +++++++ .../mixin/client/MixinPlayerRenderer.java | 70 ---------------- .../MixinClientboundPlayerPositionPacket.java | 37 +++++++++ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 71 +++++++++++++--- .../MixinServerGamePacketListenerImpl.java | 72 +++++++++++++++++ .../MixinServerboundMovePlayerPacket.java | 23 ++++++ ...xinServerboundMovePlayerPacket_PosRot.java | 26 ++++++ .../MixinServerboundMovePlayerPacket_Rot.java | 26 ++++++ src/main/resources/vsch.mixins.json | 81 ++++++++++--------- 14 files changed, 481 insertions(+), 136 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java create mode 100644 src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java delete mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java diff --git a/src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java b/src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java new file mode 100644 index 00000000..70c5375a --- /dev/null +++ b/src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java @@ -0,0 +1,7 @@ +package net.jcm.vsch.accessor; + +import org.joml.Quaternionf; + +public interface ClientboundPlayerPositionPacketAccessor { + Quaternionf vsch$getRotation(); +} diff --git a/src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java b/src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java new file mode 100644 index 00000000..12a889c1 --- /dev/null +++ b/src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java @@ -0,0 +1,7 @@ +package net.jcm.vsch.accessor; + +import org.joml.Quaternionf; + +public interface ServerboundMovePlayerPacketAccessor { + Quaternionf vsch$getRotation(); +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java new file mode 100644 index 00000000..c5e62998 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java @@ -0,0 +1,42 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.accessor.ClientboundPlayerPositionPacketAccessor; +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; + +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.world.entity.player.Player; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +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(ClientPacketListener.class) +public abstract class MixinClientPacketListener { + @Inject( + method = "handleMovePlayer", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundAcceptTeleportationPacket;") + ) + public void handleMovePlayer$acceptTeleport$before(final ClientboundPlayerPositionPacket packet, final CallbackInfo ci, @Local final Player player) { + if (!(player instanceof FreeRotatePlayerAccessor frp)) { + return; + } + frp.vsch$setRotation(((ClientboundPlayerPositionPacketAccessor)(packet)).vsch$getRotation()); + } + + @ModifyExpressionValue( + method = "handleMovePlayer", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") + ) + public ServerboundMovePlayerPacket.PosRot handleMovePlayer$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { + if (player instanceof FreeRotatePlayerAccessor frp) { + ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + } + return packet; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java new file mode 100644 index 00000000..db4f8f83 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java @@ -0,0 +1,76 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; + +import org.joml.Quaternionf; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LivingEntityRenderer.class) +public abstract class MixinLivingEntityRenderer { + @WrapOperation( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;setupRotations(Lnet/minecraft/world/entity/LivingEntity;Lcom/mojang/blaze3d/vertex/PoseStack;FFF)V" + ) + ) + protected void render$setupRotations( + final LivingEntityRenderer self, + final LivingEntity entity, + final PoseStack poseStack, + final float bob, + final float yaw, + final float partialTick, + final Operation operation + ) { + if (!(entity instanceof Player player) || !(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + operation.call(self, entity, poseStack, bob, yaw, partialTick); + return; + } + + final Quaternionf rotation = frp.vsch$getRotationO().slerp(frp.vsch$getRotation(), partialTick, new Quaternionf()); + + final EntityDimensions vanillaDim = frp.vsch$getVanillaDimensions(player.getPose()); + poseStack.translate(0, 0.6f / 2, 0); + poseStack.mulPose(rotation); + poseStack.translate(0, 0.6f - vanillaDim.height - 0.6f / 2, 0); + + operation.call(self, entity, poseStack, bob, 0f, partialTick); + } + + @WrapOperation( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/model/EntityModel;setupAnim(Lnet/minecraft/world/entity/Entity;FFFFF)V" + ) + ) + public void render$setupAnim( + final EntityModel model, + final Entity entity, + final float limbSwing, + final float limbSwingAmount, + final float age, + float headYaw, + float headPitch, + final Operation operation + ) { + if ((entity instanceof Player player) && (player instanceof FreeRotatePlayerAccessor frp) && frp.vsch$isFreeRotating()) { + headYaw = 0; + headPitch = 0; + } + operation.call(model, entity, limbSwing, limbSwingAmount, age, headYaw, headPitch); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 06623db1..04091d4f 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -1,10 +1,13 @@ package net.jcm.vsch.mixin.client; +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; import net.jcm.vsch.mixin.minecraft.MixinPlayer; import net.minecraft.client.player.Input; import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import org.spongepowered.asm.mixin.Mixin; @@ -21,6 +24,33 @@ public abstract class MixinLocalPlayer extends MixinPlayer { @Shadow protected abstract boolean isControlledCamera(); + @ModifyExpressionValue( + method = "tick", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$Rot;") + ) + public ServerboundMovePlayerPacket.Rot tick$new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { + ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + return packet; + } + + @ModifyExpressionValue( + method = "sendPosition", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") + ) + public ServerboundMovePlayerPacket.PosRot sendPosition$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet) { + ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + return packet; + } + + @ModifyExpressionValue( + method = "sendPosition", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$Rot;") + ) + public ServerboundMovePlayerPacket.Rot sendPosition$new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { + ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + return packet; + } + @WrapOperation( method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isShiftKeyDown()Z", ordinal = 0) @@ -37,27 +67,11 @@ public abstract class MixinLocalPlayer extends MixinPlayer { return false; } - @Inject( - method = "aiStep", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/player/LocalPlayer;jumpableVehicle()Lnet/minecraft/world/entity/PlayerRideableJumping;", - ordinal = 0 - ) - ) - public void aiStep$afterFlying(final CallbackInfo ci) { + @Inject(method = "serverAiStep", at = @At("RETURN")) + public void serverAiStep(final CallbackInfo ci) { if (!this.vsch$isFreeRotating() || this.getAbilities().flying || !this.isControlledCamera()) { return; } - int direction = 0; - if (this.input.shiftKeyDown) { - direction--; - } - if (this.input.jumping) { - direction++; - } - if (direction != 0) { - this.setDeltaMovement(this.getDeltaMovement().add(0, direction * this.getFlyingSpeed(), 0)); - } + this.yya = (this.input.jumping ? 1 : 0) + (this.input.shiftKeyDown ? -1 : 0); } } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java new file mode 100644 index 00000000..63063667 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java @@ -0,0 +1,27 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; + +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.world.entity.player.Player; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(MultiPlayerGameMode.class) +public abstract class MixinMultiPlayerGameMode { + @ModifyExpressionValue( + method = "useItem", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") + ) + public ServerboundMovePlayerPacket.PosRot useItem$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { + if (player instanceof FreeRotatePlayerAccessor frp) { + ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + } + return packet; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java deleted file mode 100644 index 867c5cde..00000000 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinPlayerRenderer.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.jcm.vsch.mixin.client; - -import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.entity.player.PlayerRenderer; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.EntityDimensions; -import net.minecraft.world.phys.Vec3; - -import org.joml.Quaternionf; -import org.joml.Vector3f; - -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(PlayerRenderer.class) -public abstract class MixinPlayerRenderer { - @WrapMethod(method = "render") - public void render( - final AbstractClientPlayer player, - final float yaw, - final float partialTick, - final PoseStack poseStack, - final MultiBufferSource bufferSource, - final int packedLight, - final Operation operation - ) { - if (!(player instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { - operation.call(player, yaw, partialTick, poseStack, bufferSource, packedLight); - return; - } - - poseStack.pushPose(); - - final float playerYHeadRotO = player.yHeadRotO; - final float playerYHeadRot = player.yHeadRot; - final float playerYBodyRotO = player.yBodyRotO; - final float playerYBodyRot = player.yBodyRot; - final float playerXRotO = player.xRotO; - final float playerXRot = player.getXRot(); - final Quaternionf rotation = new Quaternionf(frp.vsch$getRotation()); - - player.yHeadRotO = 0; - player.yHeadRot = 0; - player.yBodyRotO = 0; - player.yBodyRot = 0; - player.xRotO = 0; - player.setXRot(0); - - final EntityDimensions vanillaDim = frp.vsch$getVanillaDimensions(player.getPose()); - poseStack.translate(0, 0.6f / 2, 0); - poseStack.mulPose(rotation); - poseStack.translate(0, 0.6f - vanillaDim.height - 0.6f / 2, 0); - - operation.call(player, yaw, partialTick, poseStack, bufferSource, packedLight); - - player.yHeadRotO = playerYHeadRotO; - player.yHeadRot = playerYHeadRot; - player.yBodyRotO = playerYBodyRotO; - player.yBodyRot = playerYBodyRot; - player.xRotO = playerXRotO; - player.setXRot(playerXRot); - - poseStack.popPose(); - } -} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java new file mode 100644 index 00000000..91f5f3cd --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java @@ -0,0 +1,37 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.ClientboundPlayerPositionPacketAccessor; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; + +import org.joml.Quaternionf; + +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; + +@Mixin(ClientboundPlayerPositionPacket.class) +public abstract class MixinClientboundPlayerPositionPacket implements Packet, ClientboundPlayerPositionPacketAccessor { + @Unique + private Quaternionf rotation = new Quaternionf(); + + @Override + public Quaternionf vsch$getRotation() { + return this.rotation; + } + + @Inject(method = "(Lnet/minecraft/network/FriendlyByteBuf;)V", at = @At("RETURN")) + public void init$read(final FriendlyByteBuf buf, final CallbackInfo ci) { + this.rotation.set(buf.readQuaternion()).normalize(); + } + + @Inject(method = "write", at = @At("RETURN")) + public void write(final FriendlyByteBuf buf, final CallbackInfo ci) { + buf.writeQuaternion(this.rotation); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index cc003bfa..fddec19b 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -8,6 +8,9 @@ import com.mojang.authlib.GameProfile; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; @@ -23,6 +26,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; import org.joml.Quaternionf; +import org.joml.Vector3d; import org.joml.Vector3f; import org.valkyrienskies.mod.common.VSGameUtilsKt; @@ -110,7 +114,7 @@ public MultiPartPlayer[] getParts() { if (this.rotation != rotation) { this.rotation.set(rotation); } - final Vector3f angles = this.rotation.getEulerAnglesYXZ(new Vector3f()); + final Vector3f angles = rotation.getEulerAnglesYXZ(new Vector3f()); super.setXRot(angles.x * Mth.RAD_TO_DEG); super.setYRot(-angles.y * Mth.RAD_TO_DEG); this.yHeadRot = this.yBodyRot = this.getYRot(); @@ -145,11 +149,12 @@ public void setXRot(final float xRot) { @Unique private void reCalcRotation() { - if (this.rotation == null) { + if (this.firstTick) { return; } - final Vector3f oldAngles = this.rotation.getEulerAnglesYXZ(new Vector3f()); - this.rotation.rotationYXZ(Mth.DEG_TO_RAD * -this.getYRot(), Mth.DEG_TO_RAD * this.getXRot(), oldAngles.z); + final Quaternionf rotation = this.vsch$getRotation(); + final Vector3f oldAngles = rotation.getEulerAnglesYXZ(new Vector3f()); + this.vsch$setRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -this.getYRot(), Mth.DEG_TO_RAD * this.getXRot(), oldAngles.z)); } @Unique @@ -162,11 +167,28 @@ protected void updateDefaultFreeRotation() { this.refreshDimensions(); if (freeRotation) { this.reCalcRotation(); - final Vector3f oldAnglesO = this.rotation.getEulerAnglesYXZ(new Vector3f()); + final Vector3f oldAnglesO = this.rotationO.getEulerAnglesYXZ(new Vector3f()); this.rotationO.rotationYXZ(Mth.DEG_TO_RAD * -this.yRotO, Mth.DEG_TO_RAD * this.xRotO, oldAnglesO.z); } } + @Inject(method = "readAdditionalSaveData", at = @At("RETURN")) + public void readAdditionalSaveData(final CompoundTag data, final CallbackInfo ci) { + if (data.contains("RotationQuat", Tag.TAG_LIST)) { + final ListTag rotQuat = data.getList("RotationQuat", Tag.TAG_FLOAT); + this.rotation.set(rotQuat.getFloat(0), rotQuat.getFloat(1), rotQuat.getFloat(2), rotQuat.getFloat(3)); + if (!this.rotation.normalize().isFinite()) { + this.rotation.set(0, 0, 0, 1); + } + this.rotationO.set(this.rotation); + } + } + + @Inject(method = "addAdditionalSaveData", at = @At("RETURN")) + public void addAdditionalSaveData(final CompoundTag data, final CallbackInfo ci) { + data.put("RotationQuat", this.newFloatList(this.rotation.x, this.rotation.y, this.rotation.z, this.rotation.w)); + } + @Override public EntityDimensions vsch$getVanillaDimensions(final Pose pose) { return POSES.getOrDefault(pose, Player.STANDING_DIMENSIONS); @@ -198,7 +220,7 @@ public void setPose(final Pose newPose) { at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isShiftKeyDown()Z") ) protected boolean updatePlayerPose$isShiftKeyDown(final Player self, final Operation operation) { - if (operation.call(self) == Boolean.FALSE) { + if (!operation.call(self)) { return false; } if (this.getAbilities().flying || !this.vsch$isFreeRotating()) { @@ -224,7 +246,7 @@ public void turn(final double x, final double y) { final Quaternionf pitchRot = new Quaternionf().rotateX(pitch); final Quaternionf yawRot = new Quaternionf().rotateY(yaw); - this.vsch$setRotation(this.rotation.mul(yawRot).mul(pitchRot).normalize()); + this.vsch$setRotation(this.vsch$getRotation().mul(yawRot).mul(pitchRot).normalize()); this.vsch$setRotationO(this.rotationO.mul(yawRot).mul(pitchRot).normalize()); } @@ -272,6 +294,24 @@ public boolean isInLava() { return super.isInLava() || this.chestPart.isInLava() || this.feetPart.isInLava(); } + @Override + public void moveRelative(final float power, final Vec3 movement) { + if (!this.vsch$isFreeRotating()) { + super.moveRelative(power, movement); + return; + } + final double speed = movement.lengthSqr(); + if (speed < 1.0e-7) { + return; + } + final Vector3d move = new Vector3d(movement.x, movement.y, movement.z); + if (speed > 1) { + move.normalize(); + } + this.vsch$getRotation().transform(move.mul(power)); + this.setDeltaMovement(this.getDeltaMovement().add(move.x, move.y, move.z)); + } + @Override protected void checkFallDamage(final double dy, final boolean onGround, final BlockState block, final BlockPos pos) { // TODO: implement fall damage / collision damage in space @@ -336,12 +376,23 @@ protected void setLevel(final Level level) { this.updateDefaultFreeRotation(); } - @Inject(method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setSpeed(F)V", ordinal = 0)) - public void aiStep(final CallbackInfo ci) { + @Override + public void baseTick() { + super.baseTick(); + this.rotationO.set(this.vsch$getRotation()); + } + + @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;updatePlayerPose()V", ordinal = 0)) + public void tick(final CallbackInfo ci) { + this.updateParts(); + } + + @Unique + private void updateParts() { this.setYBodyRot(this.getYHeadRot()); final float height = this.vsch$getVanillaDimensions(this.getPose()).height; final Vector3f feetPos = new Vector3f(0, SPACE_ENTITY_SIZE - height, 0); - feetPos.rotate(this.rotation); + feetPos.rotate(this.vsch$getRotation()); this.updatePartPos(this.feetPart, feetPos.x, feetPos.y, feetPos.z); this.updatePartPos(this.chestPart, feetPos.x / 2, feetPos.y / 2, feetPos.z / 2); } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java new file mode 100644 index 00000000..22f07c91 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java @@ -0,0 +1,72 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.ClientboundPlayerPositionPacketAccessor; +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; + +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; + +import org.joml.Quaternionf; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerGamePacketListenerImpl.class) +public abstract class MixinServerGamePacketListenerImpl { + @Shadow + public ServerPlayer player; + + @WrapOperation( + method = "handleMovePlayer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;containsInvalidValues(DDDFF)Z" + ) + ) + public boolean handleMovePlayer$containsInvalidValues( + final double x, + final double y, + final double z, + final float yRot, + final float xRot, + final Operation operation, + final ServerboundMovePlayerPacket packet + ) { + if (!operation.call(x, y, z, yRot, xRot)) { + return false; + } + if (!packet.hasRotation()) { + return false; + } + final Quaternionf rotation = ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation(); + return !rotation.isFinite(); + } + + @Inject(method = "handleMovePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;wrapDegrees(F)F")) + public void handleMovePlayer$wrapDegrees(final ServerboundMovePlayerPacket packet, final CallbackInfo ci) { + if (!packet.hasRotation() || !(this.player instanceof FreeRotatePlayerAccessor frp)) { + return; + } + frp.vsch$setRotation(((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation()); + } + + @ModifyExpressionValue( + method = "teleport(DDDFFLjava/util/Set;)V", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundPlayerPositionPacket;") + ) + public ClientboundPlayerPositionPacket teleport$new$ClientboundPlayerPositionPacket(final ClientboundPlayerPositionPacket packet) { + if (this.player instanceof FreeRotatePlayerAccessor frp) { + ((ClientboundPlayerPositionPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + } + return packet; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket.java new file mode 100644 index 00000000..0c58ca87 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket.java @@ -0,0 +1,23 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; + +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ServerGamePacketListener; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; + +import org.joml.Quaternionf; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(ServerboundMovePlayerPacket.class) +public abstract class MixinServerboundMovePlayerPacket implements Packet, ServerboundMovePlayerPacketAccessor { + @Unique + private Quaternionf rotation = new Quaternionf(); + + @Override + public Quaternionf vsch$getRotation() { + return this.rotation; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java new file mode 100644 index 00000000..e56d65e3 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java @@ -0,0 +1,26 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; + +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ServerboundMovePlayerPacket.PosRot.class) +public abstract class MixinServerboundMovePlayerPacket_PosRot extends MixinServerboundMovePlayerPacket { + @Inject(method = "read", at = @At("RETURN")) + private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { + final ServerboundMovePlayerPacketAccessor packet = (ServerboundMovePlayerPacketAccessor)(cir.getReturnValue()); + packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + } + + @Inject(method = "write", at = @At("RETURN")) + public void write(final FriendlyByteBuf buf, final CallbackInfo ci) { + buf.writeQuaternion(this.vsch$getRotation()); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java new file mode 100644 index 00000000..5e6b9271 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java @@ -0,0 +1,26 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; + +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ServerboundMovePlayerPacket.Rot.class) +public abstract class MixinServerboundMovePlayerPacket_Rot extends MixinServerboundMovePlayerPacket { + @Inject(method = "read", at = @At("RETURN")) + private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { + final ServerboundMovePlayerPacketAccessor packet = (ServerboundMovePlayerPacketAccessor)(cir.getReturnValue()); + packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + } + + @Inject(method = "write", at = @At("RETURN")) + public void write(final FriendlyByteBuf buf, final CallbackInfo ci) { + buf.writeQuaternion(this.vsch$getRotation()); + } +} diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index de3de21b..61ff0859 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -1,38 +1,45 @@ { - "required": true, - "minVersion": "0.8", - "package": "net.jcm.vsch.mixin", - "compatibilityLevel": "JAVA_17", - "refmap": "mixins.vsch.refmap.json", - "mixins": [ - "cosmos.MixinLightRenderer", - "cosmos.MixinPlaceplatformOnKeyPressedProcedure", - "cosmos.MixinShipspawnspaceProcedure", - "cosmos.MixinSpacesuitwornLogicProcedure", - "create.MixinBeltBlockEntity", - "create.MixinClockworkBearingBlockEntity", - "create.MixinControlledContraptionEntity", - "create.MixinKineticBlockEntity", - "create.MixinLinearActuatorBlockEntity", - "create.MixinMechanicalBearingBlockEntity", - "minecraft.MixinChunkMap", - "minecraft.MixinEntity", - "minecraft.MixinLivingEntity", - "minecraft.MixinMob", - "minecraft.MixinPlayer", - "minecraft.MixinServerPlayer", - "valkyrienskies.MixinShipAssemblyKt", - "valkyrienskies.MixinVSGameUtilsKt", - "valkyrienskies.accessor.ServerShipObjectWorldAccessor" - ], - "client": [ - "client.MixinCamera", - "client.MixinGameRenderer", - "client.MixinGui", - "client.MixinLocalPlayer", - "client.MixinPlayerRenderer" - ], - "injectors": { - "defaultRequire": 1 - } -} \ No newline at end of file + "required": true, + "minVersion": "0.8", + "package": "net.jcm.vsch.mixin", + "compatibilityLevel": "JAVA_17", + "refmap": "mixins.vsch.refmap.json", + "mixins": [ + "cosmos.MixinLightRenderer", + "cosmos.MixinPlaceplatformOnKeyPressedProcedure", + "cosmos.MixinShipspawnspaceProcedure", + "cosmos.MixinSpacesuitwornLogicProcedure", + "create.MixinBeltBlockEntity", + "create.MixinClockworkBearingBlockEntity", + "create.MixinControlledContraptionEntity", + "create.MixinKineticBlockEntity", + "create.MixinLinearActuatorBlockEntity", + "create.MixinMechanicalBearingBlockEntity", + "minecraft.MixinChunkMap", + "minecraft.MixinClientboundPlayerPositionPacket", + "minecraft.MixinEntity", + "minecraft.MixinLivingEntity", + "minecraft.MixinMob", + "minecraft.MixinPlayer", + "minecraft.MixinServerGamePacketListenerImpl", + "minecraft.MixinServerPlayer", + "minecraft.MixinServerboundMovePlayerPacket", + "minecraft.MixinServerboundMovePlayerPacket_PosRot", + "minecraft.MixinServerboundMovePlayerPacket_Rot", + "valkyrienskies.MixinShipAssemblyKt", + "valkyrienskies.MixinVSGameUtilsKt", + "valkyrienskies.accessor.ServerShipObjectWorldAccessor" + ], + "client": [ + "client.MixinCamera", + "client.MixinClientPacketListener", + "client.MixinGameRenderer", + "client.MixinGui", + "client.MixinLivingEntityRenderer", + "client.MixinLocalPlayer", + "client.MixinMultiPlayerGameMode" + ], + "injectors": { + "defaultRequire": 1 + } +} From 04fbedcb0999fe0e2ea587ea4b0c87a39ec77c50 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 29 Jul 2025 14:33:26 -0600 Subject: [PATCH 11/32] sync rotation between client & server player cannot be upsidedown somehow --- ...ientboundPlayerPositionPacketAccessor.java | 7 --- ...java => EntityRotationPacketAccessor.java} | 2 +- .../accessor/FreeRotatePlayerAccessor.java | 4 ++ .../client/MixinClientPacketListener.java | 49 +++++++++++++++-- .../vsch/mixin/client/MixinLocalPlayer.java | 19 ++----- .../client/MixinMultiPlayerGameMode.java | 4 +- ...xinClientboundMoveEntityPacket_PosRot.java | 20 +++++++ .../MixinClientboundMoveEntityPacket_Rot.java | 20 +++++++ .../jcm/vsch/mixin/minecraft/MixinEntity.java | 5 +- .../MixinEntityRotationPacketSubClasses.java | 25 +++++++++ ...t.java => MixinEntityRotationPackets.java} | 11 ++-- ...a => MixinEntityRotationPacketsFinal.java} | 14 +++-- .../mixin/minecraft/MixinLivingEntity.java | 21 +++++++ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 16 +++++- .../mixin/minecraft/MixinServerEntity.java | 55 +++++++++++++++++++ .../MixinServerGamePacketListenerImpl.java | 9 ++- .../mixin/minecraft/MixinServerPlayer.java | 14 +++++ ...xinServerboundMovePlayerPacket_PosRot.java | 12 +--- .../MixinServerboundMovePlayerPacket_Rot.java | 12 +--- src/main/resources/vsch.mixins.json | 8 ++- 20 files changed, 260 insertions(+), 67 deletions(-) delete mode 100644 src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java rename src/main/java/net/jcm/vsch/accessor/{ServerboundMovePlayerPacketAccessor.java => EntityRotationPacketAccessor.java} (63%) create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPacketSubClasses.java rename src/main/java/net/jcm/vsch/mixin/minecraft/{MixinServerboundMovePlayerPacket.java => MixinEntityRotationPackets.java} (56%) rename src/main/java/net/jcm/vsch/mixin/minecraft/{MixinClientboundPlayerPositionPacket.java => MixinEntityRotationPacketsFinal.java} (69%) create mode 100644 src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java diff --git a/src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java b/src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java deleted file mode 100644 index 70c5375a..00000000 --- a/src/main/java/net/jcm/vsch/accessor/ClientboundPlayerPositionPacketAccessor.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.jcm.vsch.accessor; - -import org.joml.Quaternionf; - -public interface ClientboundPlayerPositionPacketAccessor { - Quaternionf vsch$getRotation(); -} diff --git a/src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java b/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java similarity index 63% rename from src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java rename to src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java index 12a889c1..9d36f9d8 100644 --- a/src/main/java/net/jcm/vsch/accessor/ServerboundMovePlayerPacketAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java @@ -2,6 +2,6 @@ import org.joml.Quaternionf; -public interface ServerboundMovePlayerPacketAccessor { +public interface EntityRotationPacketAccessor { Quaternionf vsch$getRotation(); } diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 428535cb..c9f03f55 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -16,6 +16,10 @@ public interface FreeRotatePlayerAccessor { void vsch$setRotationO(Quaternionf rotation); + Quaternionf vsch$getLerpRotation(); + + void vsch$setLerpRotation(Quaternionf rotation); + boolean vsch$hasSupportingBlock(); EntityDimensions vsch$getVanillaDimensions(Pose pose); diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java index c5e62998..a15f4298 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java @@ -1,14 +1,20 @@ package net.jcm.vsch.mixin.client; -import net.jcm.vsch.accessor.ClientboundPlayerPositionPacketAccessor; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; -import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.player.RemotePlayer; +import net.minecraft.network.protocol.game.ClientboundAddPlayerPacket; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import org.joml.Quaternionf; + import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; import org.spongepowered.asm.mixin.Mixin; @@ -18,6 +24,41 @@ @Mixin(ClientPacketListener.class) public abstract class MixinClientPacketListener { + @Inject( + method = "handleAddPlayer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/multiplayer/ClientLevel;addPlayer(ILnet/minecraft/client/player/AbstractClientPlayer;)V" + ) + ) + public void handleAddPlayer(final ClientboundAddPlayerPacket packet, final CallbackInfo ci, @Local final RemotePlayer player) { + if (!(player instanceof FreeRotatePlayerAccessor frp)) { + return; + } + final Quaternionf rotation = ((EntityRotationPacketAccessor)(packet)).vsch$getRotation(); + frp.vsch$setRotation(rotation); + frp.vsch$setRotationO(rotation); + } + + @Inject(method = "handleTeleportEntity", at = @At("RETURN")) + public void handleTeleportEntity(final ClientboundTeleportEntityPacket packet, final CallbackInfo ci, @Local final Entity entity) { + if (!(entity instanceof FreeRotatePlayerAccessor frp)) { + return; + } + frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); + } + + @Inject(method = "handleMoveEntity", at = @At("RETURN")) + public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final CallbackInfo ci, @Local final Entity entity) { + if (entity == null || entity.isControlledByLocalInstance()) { + return; + } + if (!(entity instanceof FreeRotatePlayerAccessor frp) || !packet.hasRotation()) { + return; + } + frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); + } + @Inject( method = "handleMovePlayer", at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundAcceptTeleportationPacket;") @@ -26,7 +67,7 @@ public abstract class MixinClientPacketListener { if (!(player instanceof FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setRotation(((ClientboundPlayerPositionPacketAccessor)(packet)).vsch$getRotation()); + frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); } @ModifyExpressionValue( @@ -35,7 +76,7 @@ public abstract class MixinClientPacketListener { ) public ServerboundMovePlayerPacket.PosRot handleMovePlayer$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { if (player instanceof FreeRotatePlayerAccessor frp) { - ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 04091d4f..1e5c891a 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -1,6 +1,6 @@ package net.jcm.vsch.mixin.client; -import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.jcm.vsch.mixin.minecraft.MixinPlayer; import net.minecraft.client.player.Input; @@ -25,11 +25,11 @@ public abstract class MixinLocalPlayer extends MixinPlayer { protected abstract boolean isControlledCamera(); @ModifyExpressionValue( - method = "tick", + method = {"tick", "sendPosition"}, at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$Rot;") ) - public ServerboundMovePlayerPacket.Rot tick$new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { - ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + public ServerboundMovePlayerPacket.Rot new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); return packet; } @@ -38,16 +38,7 @@ public abstract class MixinLocalPlayer extends MixinPlayer { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") ) public ServerboundMovePlayerPacket.PosRot sendPosition$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet) { - ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); - return packet; - } - - @ModifyExpressionValue( - method = "sendPosition", - at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$Rot;") - ) - public ServerboundMovePlayerPacket.Rot sendPosition$new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { - ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java index 63063667..de59e020 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java @@ -1,7 +1,7 @@ package net.jcm.vsch.mixin.client; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; -import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; import net.minecraft.client.multiplayer.MultiPlayerGameMode; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; @@ -20,7 +20,7 @@ public abstract class MixinMultiPlayerGameMode { ) public ServerboundMovePlayerPacket.PosRot useItem$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { if (player instanceof FreeRotatePlayerAccessor frp) { - ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java new file mode 100644 index 00000000..c3fe4ed2 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java @@ -0,0 +1,20 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; + +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; + +@Mixin(ClientboundMoveEntityPacket.PosRot.class) +public abstract class MixinClientboundMoveEntityPacket_PosRot { + @Inject(method = "read", at = @At("RETURN")) + private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { + final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); + packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java new file mode 100644 index 00000000..3889e452 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java @@ -0,0 +1,20 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; + +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; + +@Mixin(ClientboundMoveEntityPacket.Rot.class) +public abstract class MixinClientboundMoveEntityPacket_Rot { + @Inject(method = "read", at = @At("RETURN")) + private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { + final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); + packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java index 687f2b6b..1b6bf3ea 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -66,9 +66,8 @@ private void collide(final Vec3 originMovement, final CallbackInfoReturnable, ServerboundMovePlayerPacketAccessor { +@Mixin({ + ClientboundMoveEntityPacket.class, + ServerboundMovePlayerPacket.class +}) +public abstract class MixinEntityRotationPackets implements EntityRotationPacketAccessor { @Unique private Quaternionf rotation = new Quaternionf(); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPacketsFinal.java similarity index 69% rename from src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java rename to src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPacketsFinal.java index 91f5f3cd..4c6e7cbf 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundPlayerPositionPacket.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPacketsFinal.java @@ -1,11 +1,11 @@ package net.jcm.vsch.mixin.minecraft; -import net.jcm.vsch.accessor.ClientboundPlayerPositionPacketAccessor; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundAddPlayerPacket; import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; import org.joml.Quaternionf; @@ -15,8 +15,12 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(ClientboundPlayerPositionPacket.class) -public abstract class MixinClientboundPlayerPositionPacket implements Packet, ClientboundPlayerPositionPacketAccessor { +@Mixin({ + ClientboundAddPlayerPacket.class, + ClientboundPlayerPositionPacket.class, + ClientboundTeleportEntityPacket.class +}) +public abstract class MixinEntityRotationPacketsFinal implements EntityRotationPacketAccessor { @Unique private Quaternionf rotation = new Quaternionf(); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java index 75da5b2c..9b55984d 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java @@ -7,9 +7,13 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LivingEntity.class) public abstract class MixinLivingEntity extends Entity { @@ -17,6 +21,9 @@ protected MixinLivingEntity() { super(null, null); } + @Shadow + protected int lerpSteps; + @WrapOperation( method = "travel", slice = @Slice( @@ -32,4 +39,18 @@ public void travel(final LivingEntity self, double x, double y, double z, final } operation.call(self, x, y, z); } + + @Inject( + method = "aiStep", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setXRot(F)V", ordinal = 0) + ), + at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;lerpSteps:I", opcode = Opcodes.PUTFIELD) + ) + public void aiStep$lerp(final CallbackInfo ci) { + if (!(this instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return; + } + frp.vsch$setRotation(frp.vsch$getRotation().slerp(frp.vsch$getLerpRotation(), 1.0f / this.lerpSteps)); + } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index fddec19b..37fb5cab 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -63,6 +63,8 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay @Unique private Quaternionf rotationO = new Quaternionf(); @Unique + private Quaternionf rotationLerp = new Quaternionf(); + @Unique private MultiPartPlayer[] parts; @Unique private MultiPartPlayer chestPart; @@ -114,7 +116,7 @@ public MultiPartPlayer[] getParts() { if (this.rotation != rotation) { this.rotation.set(rotation); } - final Vector3f angles = rotation.getEulerAnglesYXZ(new Vector3f()); + final Vector3f angles = this.rotation.getEulerAnglesYXZ(new Vector3f()); super.setXRot(angles.x * Mth.RAD_TO_DEG); super.setYRot(-angles.y * Mth.RAD_TO_DEG); this.yHeadRot = this.yBodyRot = this.getYRot(); @@ -135,6 +137,16 @@ public MultiPartPlayer[] getParts() { this.yHeadRotO = this.yBodyRotO = this.yRotO = -angles.y * Mth.RAD_TO_DEG; } + @Override + public Quaternionf vsch$getLerpRotation() { + return this.rotationLerp; + } + + @Override + public void vsch$setLerpRotation(final Quaternionf rotation) { + this.rotationLerp.set(rotation); + } + @Override public void setYRot(final float yRot) { super.setYRot(yRot); @@ -149,7 +161,7 @@ public void setXRot(final float xRot) { @Unique private void reCalcRotation() { - if (this.firstTick) { + if (this.firstTick || !this.freeRotation) { return; } final Quaternionf rotation = this.vsch$getRotation(); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java new file mode 100644 index 00000000..2fd536c9 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java @@ -0,0 +1,55 @@ +package net.jcm.vsch.mixin.minecraft; + +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.world.entity.Entity; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ServerEntity.class) +public abstract class MixinServerEntity { + @Shadow + @Final + private Entity entity; + + @ModifyExpressionValue( + method = "sendChanges", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundTeleportEntityPacket;") + ) + public ClientboundTeleportEntityPacket new$ClientboundTeleportEntityPacket(final ClientboundTeleportEntityPacket packet) { + if (this.entity instanceof FreeRotatePlayerAccessor frp) { + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + } + return packet; + } + + @ModifyExpressionValue( + method = "sendChanges", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundMoveEntityPacket$PosRot;") + ) + public ClientboundMoveEntityPacket.PosRot new$ClientboundMoveEntityPacket$PosRot(final ClientboundMoveEntityPacket.PosRot packet) { + if (this.entity instanceof FreeRotatePlayerAccessor frp) { + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + } + return packet; + } + + @ModifyExpressionValue( + method = "sendChanges", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundMoveEntityPacket$Rot;") + ) + public ClientboundMoveEntityPacket.Rot new$ClientboundMoveEntityPacket$Rot(final ClientboundMoveEntityPacket.Rot packet) { + if (this.entity instanceof FreeRotatePlayerAccessor frp) { + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + } + return packet; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java index 22f07c91..91ab11f9 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java @@ -1,8 +1,7 @@ package net.jcm.vsch.mixin.minecraft; -import net.jcm.vsch.accessor.ClientboundPlayerPositionPacketAccessor; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; -import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; @@ -47,7 +46,7 @@ public abstract class MixinServerGamePacketListenerImpl { if (!packet.hasRotation()) { return false; } - final Quaternionf rotation = ((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation(); + final Quaternionf rotation = ((EntityRotationPacketAccessor)(packet)).vsch$getRotation(); return !rotation.isFinite(); } @@ -56,7 +55,7 @@ public abstract class MixinServerGamePacketListenerImpl { if (!packet.hasRotation() || !(this.player instanceof FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setRotation(((ServerboundMovePlayerPacketAccessor)(packet)).vsch$getRotation()); + frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); } @ModifyExpressionValue( @@ -65,7 +64,7 @@ public abstract class MixinServerGamePacketListenerImpl { ) public ClientboundPlayerPositionPacket teleport$new$ClientboundPlayerPositionPacket(final ClientboundPlayerPositionPacket packet) { if (this.player instanceof FreeRotatePlayerAccessor frp) { - ((ClientboundPlayerPositionPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java index 292fe4d1..b4bdaa29 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java @@ -1,7 +1,11 @@ package net.jcm.vsch.mixin.minecraft; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; + +import net.minecraft.network.protocol.game.ClientboundAddPlayerPacket; import net.minecraft.server.level.ServerPlayer; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyArg; @@ -21,4 +25,14 @@ public abstract class MixinServerPlayer extends MixinPlayer { } return y; } + + @ModifyExpressionValue( + method = "getAddEntityPacket", + at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundAddPlayerPacket;") + ) + public ClientboundAddPlayerPacket new$ClientboundAddPlayerPacket(final ClientboundAddPlayerPacket packet) { + ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + return packet; + } + } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java index e56d65e3..9fc77f56 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java @@ -1,6 +1,6 @@ package net.jcm.vsch.mixin.minecraft; -import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; @@ -8,19 +8,13 @@ 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ServerboundMovePlayerPacket.PosRot.class) -public abstract class MixinServerboundMovePlayerPacket_PosRot extends MixinServerboundMovePlayerPacket { +public abstract class MixinServerboundMovePlayerPacket_PosRot { @Inject(method = "read", at = @At("RETURN")) private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { - final ServerboundMovePlayerPacketAccessor packet = (ServerboundMovePlayerPacketAccessor)(cir.getReturnValue()); + final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); } - - @Inject(method = "write", at = @At("RETURN")) - public void write(final FriendlyByteBuf buf, final CallbackInfo ci) { - buf.writeQuaternion(this.vsch$getRotation()); - } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java index 5e6b9271..03e9055c 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java @@ -1,6 +1,6 @@ package net.jcm.vsch.mixin.minecraft; -import net.jcm.vsch.accessor.ServerboundMovePlayerPacketAccessor; +import net.jcm.vsch.accessor.EntityRotationPacketAccessor; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; @@ -8,19 +8,13 @@ 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ServerboundMovePlayerPacket.Rot.class) -public abstract class MixinServerboundMovePlayerPacket_Rot extends MixinServerboundMovePlayerPacket { +public abstract class MixinServerboundMovePlayerPacket_Rot { @Inject(method = "read", at = @At("RETURN")) private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { - final ServerboundMovePlayerPacketAccessor packet = (ServerboundMovePlayerPacketAccessor)(cir.getReturnValue()); + final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); } - - @Inject(method = "write", at = @At("RETURN")) - public void write(final FriendlyByteBuf buf, final CallbackInfo ci) { - buf.writeQuaternion(this.vsch$getRotation()); - } } diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 61ff0859..38695372 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -16,14 +16,18 @@ "create.MixinLinearActuatorBlockEntity", "create.MixinMechanicalBearingBlockEntity", "minecraft.MixinChunkMap", - "minecraft.MixinClientboundPlayerPositionPacket", + "minecraft.MixinClientboundMoveEntityPacket_PosRot", + "minecraft.MixinClientboundMoveEntityPacket_Rot", "minecraft.MixinEntity", + "minecraft.MixinEntityRotationPacketSubClasses", + "minecraft.MixinEntityRotationPackets", + "minecraft.MixinEntityRotationPacketsFinal", "minecraft.MixinLivingEntity", "minecraft.MixinMob", "minecraft.MixinPlayer", + "minecraft.MixinServerEntity", "minecraft.MixinServerGamePacketListenerImpl", "minecraft.MixinServerPlayer", - "minecraft.MixinServerboundMovePlayerPacket", "minecraft.MixinServerboundMovePlayerPacket_PosRot", "minecraft.MixinServerboundMovePlayerPacket_Rot", "valkyrienskies.MixinShipAssemblyKt", From 5557f5b4e693850650fd8e08fe9be18ddc13b494 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 29 Jul 2025 17:37:35 -0600 Subject: [PATCH 12/32] fix local oritantion again --- .../EntityRotationPacketAccessor.java | 2 +- .../client/MixinClientPacketListener.java | 20 +++---- .../vsch/mixin/client/MixinLocalPlayer.java | 4 +- .../client/MixinMultiPlayerGameMode.java | 2 +- ...xinClientboundMoveEntityPacket_PosRot.java | 2 +- .../MixinClientboundMoveEntityPacket_Rot.java | 2 +- .../jcm/vsch/mixin/minecraft/MixinEntity.java | 2 +- .../minecraft/MixinEntityRotationPackets.java | 9 ++- ... MixinEntityRotationPackets_initRead.java} | 20 +------ ... => MixinEntityRotationPackets_write.java} | 10 +++- .../mixin/minecraft/MixinLivingEntity.java | 2 +- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 60 ++++++++++++++++--- .../mixin/minecraft/MixinServerEntity.java | 6 +- .../MixinServerGamePacketListenerImpl.java | 11 ++-- .../mixin/minecraft/MixinServerPlayer.java | 2 +- ...xinServerboundMovePlayerPacket_PosRot.java | 2 +- .../MixinServerboundMovePlayerPacket_Rot.java | 2 +- src/main/resources/vsch.mixins.json | 4 +- 18 files changed, 97 insertions(+), 65 deletions(-) rename src/main/java/net/jcm/vsch/mixin/minecraft/{MixinEntityRotationPacketsFinal.java => MixinEntityRotationPackets_initRead.java} (62%) rename src/main/java/net/jcm/vsch/mixin/minecraft/{MixinEntityRotationPacketSubClasses.java => MixinEntityRotationPackets_write.java} (62%) diff --git a/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java b/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java index 9d36f9d8..3da4cbb1 100644 --- a/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java @@ -3,5 +3,5 @@ import org.joml.Quaternionf; public interface EntityRotationPacketAccessor { - Quaternionf vsch$getRotation(); + Quaternionf vsch$rotation(); } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java index a15f4298..e7be9608 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java @@ -13,8 +13,6 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; -import org.joml.Quaternionf; - import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; import org.spongepowered.asm.mixin.Mixin; @@ -28,16 +26,14 @@ public abstract class MixinClientPacketListener { method = "handleAddPlayer", at = @At( value = "INVOKE", - target = "Lnet/minecraft/client/multiplayer/ClientLevel;addPlayer(ILnet/minecraft/client/player/AbstractClientPlayer;)V" + target = "Lnet/minecraft/client/player/RemotePlayer;setOldPosAndRot()V" ) ) public void handleAddPlayer(final ClientboundAddPlayerPacket packet, final CallbackInfo ci, @Local final RemotePlayer player) { if (!(player instanceof FreeRotatePlayerAccessor frp)) { return; } - final Quaternionf rotation = ((EntityRotationPacketAccessor)(packet)).vsch$getRotation(); - frp.vsch$setRotation(rotation); - frp.vsch$setRotationO(rotation); + frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); } @Inject(method = "handleTeleportEntity", at = @At("RETURN")) @@ -45,7 +41,7 @@ public void handleTeleportEntity(final ClientboundTeleportEntityPacket packet, f if (!(entity instanceof FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); + frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); } @Inject(method = "handleMoveEntity", at = @At("RETURN")) @@ -53,10 +49,12 @@ public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final Cal if (entity == null || entity.isControlledByLocalInstance()) { return; } - if (!(entity instanceof FreeRotatePlayerAccessor frp) || !packet.hasRotation()) { + if (!(entity instanceof FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); + if (packet.hasRotation()) { + frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); + } } @Inject( @@ -67,7 +65,7 @@ public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final Cal if (!(player instanceof FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$getRotation()); + frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); } @ModifyExpressionValue( @@ -76,7 +74,7 @@ public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final Cal ) public ServerboundMovePlayerPacket.PosRot handleMovePlayer$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { if (player instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 1e5c891a..fafd694a 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -29,7 +29,7 @@ public abstract class MixinLocalPlayer extends MixinPlayer { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$Rot;") ) public ServerboundMovePlayerPacket.Rot new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { - ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(this.vsch$getRotation()); return packet; } @@ -38,7 +38,7 @@ public abstract class MixinLocalPlayer extends MixinPlayer { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") ) public ServerboundMovePlayerPacket.PosRot sendPosition$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet) { - ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(this.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(this.vsch$getRotation()); return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java index de59e020..4da9b967 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java @@ -20,7 +20,7 @@ public abstract class MixinMultiPlayerGameMode { ) public ServerboundMovePlayerPacket.PosRot useItem$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { if (player instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$getRotation().set(frp.vsch$getRotation()); + ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java index c3fe4ed2..2c7897e0 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java @@ -15,6 +15,6 @@ public abstract class MixinClientboundMoveEntityPacket_PosRot { @Inject(method = "read", at = @At("RETURN")) private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); - packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + packet.vsch$rotation().set(buf.readQuaternion()).normalize(); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java index 3889e452..b126ca34 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java @@ -15,6 +15,6 @@ public abstract class MixinClientboundMoveEntityPacket_Rot { @Inject(method = "read", at = @At("RETURN")) private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); - packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + packet.vsch$rotation().set(buf.readQuaternion()).normalize(); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java index 1b6bf3ea..e1fd1a7d 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -67,7 +67,7 @@ private void collide(final Vec3 originMovement, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); - packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + packet.vsch$rotation().set(buf.readQuaternion()).normalize(); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java index 03e9055c..50faa978 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java @@ -15,6 +15,6 @@ public abstract class MixinServerboundMovePlayerPacket_Rot { @Inject(method = "read", at = @At("RETURN")) private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); - packet.vsch$getRotation().set(buf.readQuaternion()).normalize(); + packet.vsch$rotation().set(buf.readQuaternion()).normalize(); } } diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 38695372..161a366d 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -19,9 +19,9 @@ "minecraft.MixinClientboundMoveEntityPacket_PosRot", "minecraft.MixinClientboundMoveEntityPacket_Rot", "minecraft.MixinEntity", - "minecraft.MixinEntityRotationPacketSubClasses", "minecraft.MixinEntityRotationPackets", - "minecraft.MixinEntityRotationPacketsFinal", + "minecraft.MixinEntityRotationPackets_initRead", + "minecraft.MixinEntityRotationPackets_write", "minecraft.MixinLivingEntity", "minecraft.MixinMob", "minecraft.MixinPlayer", From dc9b5a9699ff1ca1d205fc736b11aff8b52fc2b9 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 29 Jul 2025 18:31:48 -0600 Subject: [PATCH 13/32] fix RemotePlayer orientation --- .../vsch/mixin/client/MixinRemotePlayer.java | 34 +++++++++++++++++++ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 2 +- src/main/resources/vsch.mixins.json | 3 +- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java new file mode 100644 index 00000000..91c3ef96 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java @@ -0,0 +1,34 @@ +package net.jcm.vsch.mixin.client; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.RemotePlayer; + +import org.objectweb.asm.Opcodes; +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.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(RemotePlayer.class) +public abstract class MixinRemotePlayer extends AbstractClientPlayer { + protected MixinRemotePlayer() { + super(null, null); + } + + @Inject( + method = "aiStep", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/RemotePlayer;setXRot(F)V", ordinal = 0) + ), + at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/RemotePlayer;lerpSteps:I", opcode = Opcodes.PUTFIELD) + ) + public void aiStep$lerp(final CallbackInfo ci) { + if (!(this instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return; + } + frp.vsch$setRotation(frp.vsch$getRotation().nlerp(frp.vsch$getLerpRotation(), 1.0f / this.lerpSteps).normalize()); + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 1b9dcc74..b9bc02bb 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -185,7 +185,7 @@ private void reCalcRotation() { } final Quaternionf rotation = this.vsch$getRotation(); final Vector3f oldAngles = rotation.getEulerAnglesYXZ(new Vector3f()); - this.vsch$setRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -super.getYRot(), Mth.DEG_TO_RAD * -super.getXRot(), oldAngles.z)); + this.vsch$setRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -super.getYRot(), Mth.DEG_TO_RAD * super.getXRot(), oldAngles.z)); } @Unique diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 161a366d..d603c728 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -41,7 +41,8 @@ "client.MixinGui", "client.MixinLivingEntityRenderer", "client.MixinLocalPlayer", - "client.MixinMultiPlayerGameMode" + "client.MixinMultiPlayerGameMode", + "client.MixinRemotePlayer" ], "injectors": { "defaultRequire": 1 From bea0b594f6ae0e29728867e33a3152e44f148d58 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 29 Jul 2025 19:14:35 -0600 Subject: [PATCH 14/32] fix render when player is not free rotating --- .../net/jcm/vsch/client/ClientEvents.java | 22 +++++++ .../vsch/mixin/client/MixinGameRenderer.java | 62 ------------------- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 55 ++++++++++++---- .../java/net/jcm/vsch/util/VSCHUtils.java | 8 ++- src/main/resources/vsch.mixins.json | 1 - 5 files changed, 69 insertions(+), 79 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/client/ClientEvents.java delete mode 100644 src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java new file mode 100644 index 00000000..1953754c --- /dev/null +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -0,0 +1,22 @@ +package net.jcm.vsch.client; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.client.Camera; +import net.minecraft.util.Mth; +import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import org.joml.Vector3f; + +@Mod.EventBusSubscriber +public final class ClientEvents { + @SubscribeEvent + public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event) { + final Camera camera = event.getCamera(); + if (camera.getEntity() instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + event.setRoll(camera.rotation().getEulerAnglesYXZ(new Vector3f()).z * Mth.RAD_TO_DEG); + } + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java deleted file mode 100644 index 988c7681..00000000 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinGameRenderer.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.jcm.vsch.mixin.client; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; -import net.minecraft.client.Camera; -import net.minecraft.client.renderer.GameRenderer; - -import org.joml.Quaternionf; -import org.joml.Vector3f; - -import com.llamalad7.mixinextras.sugar.Local; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.Slice; - -@Mixin(GameRenderer.class) -public abstract class MixinGameRenderer { - @Redirect( - method = "renderLevel", - slice = @Slice( - from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") - ), - at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 0) - ) - public void renderLevel$mulPose$0( - final PoseStack stack, - final Quaternionf rotationZ, - @Local final Camera camera - ) { - final Vector3f angles = camera.rotation().getEulerAnglesYXZ(new Vector3f()); - stack.mulPose(Axis.ZP.rotation(angles.z)); - stack.mulPose(Axis.XP.rotation(angles.x)); - stack.mulPose(Axis.YP.rotation(-angles.y + (float)(Math.PI))); - } - - @Redirect( - method = "renderLevel", - slice = @Slice( - from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") - ), - at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 1) - ) - public void renderLevel$mulPose$1( - final PoseStack stack, - final Quaternionf rotationX - ) { - } - - @Redirect( - method = "renderLevel", - slice = @Slice( - from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setup(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/world/entity/Entity;ZZF)V") - ), - at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 2) - ) - public void renderLevel$mulPose$2( - final PoseStack stack, - final Quaternionf rotationY - ) { - } -} diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index b9bc02bb..4354fe0d 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -11,6 +11,9 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; @@ -55,9 +58,11 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay private static final EntityDimensions SPACE_ENTITY_DIM = EntityDimensions.scalable(SPACE_ENTITY_SIZE, SPACE_ENTITY_SIZE); @Unique private static final double SUPPORT_CHECK_DISTANCE = 0.1; + @Unique + private static final EntityDataAccessor FREE_ROTATION_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BOOLEAN); @Unique - private boolean freeRotation = false; + private boolean wasFreeRotating = false; @Unique private Quaternionf rotation = new Quaternionf(); @Unique @@ -88,7 +93,11 @@ public void postInit(final Level level, final BlockPos pos, final float yRot, fi this.feetPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); this.parts = new MultiPartPlayer[]{this.chestPart, this.feetPart}; this.oldPose = this.getPose(); - this.updateDefaultFreeRotation(); + } + + @Inject(method = "", at = @At("RETURN")) + private void defineSynchedData(final CallbackInfo ci) { + this.entityData.define(FREE_ROTATION_ID, false); } @Override @@ -103,7 +112,7 @@ public MultiPartPlayer[] getParts() { @Override public boolean vsch$isFreeRotating() { - return this.freeRotation; + return !this.firstTick && this.entityData.get(FREE_ROTATION_ID); } @Override @@ -116,6 +125,9 @@ public MultiPartPlayer[] getParts() { if (this.rotation != rotation) { this.rotation.set(rotation); } + if (!this.vsch$isFreeRotating()) { + return; + } final Vector3f angles = this.rotation.getEulerAnglesYXZ(new Vector3f()); super.setXRot(angles.x * Mth.RAD_TO_DEG); final float yRot = -angles.y * Mth.RAD_TO_DEG; @@ -133,6 +145,9 @@ public MultiPartPlayer[] getParts() { if (this.rotationO != rotation) { this.rotationO.set(rotation); } + if (!this.vsch$isFreeRotating()) { + return; + } final Vector3f angles = this.rotationO.getEulerAnglesYXZ(new Vector3f()); this.xRotO = angles.x * Mth.RAD_TO_DEG; this.yHeadRotO = this.yBodyRotO = this.yRotO = -angles.y * Mth.RAD_TO_DEG; @@ -191,16 +206,7 @@ private void reCalcRotation() { @Unique protected void updateDefaultFreeRotation() { final boolean freeRotation = !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); - if (this.freeRotation == freeRotation) { - return; - } - this.freeRotation = freeRotation; - this.refreshDimensions(); - if (freeRotation) { - this.reCalcRotation(); - final Vector3f oldAnglesO = this.rotationO.getEulerAnglesYXZ(new Vector3f()); - this.rotationO.rotationYXZ(Mth.DEG_TO_RAD * -this.yRotO, Mth.DEG_TO_RAD * this.xRotO, oldAnglesO.z); - } + this.entityData.set(FREE_ROTATION_ID, freeRotation); } @Inject(method = "readAdditionalSaveData", at = @At("RETURN")) @@ -296,6 +302,10 @@ public void absMoveTo(final double x, final double y, final double z, final floa @Override public void moveTo(final double x, final double y, final double z, final float yRot, final float xRot) { + if (!this.vsch$isFreeRotating()) { + super.moveTo(x, y, z, yRot, xRot); + return; + } this.setPosRaw(x, y, z); super.setYRot(yRot); super.setXRot(xRot); @@ -368,6 +378,10 @@ public void moveRelative(final float power, final Vec3 movement) { @Override protected void checkFallDamage(final double dy, final boolean onGround, final BlockState block, final BlockPos pos) { + if (!this.vsch$isFreeRotating()) { + super.checkFallDamage(dy, onGround, block, pos); + return; + } // TODO: implement fall damage / collision damage in space } @@ -387,6 +401,10 @@ public void removeVehicle(final CallbackInfo ci) { @Override public void dismountTo(final double x, final double y, final double z) { + if (!this.vsch$isFreeRotating()) { + super.dismountTo(x, y, z); + return; + } final float oldHeight = this.vsch$getVanillaDimensions(this.getPose()).height; final float newHeight = SPACE_ENTITY_SIZE; super.dismountTo(x, y + oldHeight - newHeight, z); @@ -432,7 +450,18 @@ protected void setLevel(final Level level) { @Override public void baseTick() { + if (this.firstTick) { + this.updateDefaultFreeRotation(); + } super.baseTick(); + final boolean freeRotation = this.vsch$isFreeRotating(); + if (freeRotation != this.wasFreeRotating) { + this.wasFreeRotating = freeRotation; + this.refreshDimensions(); + if (freeRotation) { + this.reCalcRotation(); + } + } this.vsch$setRotationO(this.vsch$getRotation()); } diff --git a/src/main/java/net/jcm/vsch/util/VSCHUtils.java b/src/main/java/net/jcm/vsch/util/VSCHUtils.java index 436c9820..62740518 100644 --- a/src/main/java/net/jcm/vsch/util/VSCHUtils.java +++ b/src/main/java/net/jcm/vsch/util/VSCHUtils.java @@ -144,9 +144,11 @@ public static ServerLevel dimToLevel(final String dimensionString) { } public static boolean isSpaceLevel(final Level level) { - return level.dimension().location().getNamespace().equals("cosmos"); - // final CosmosModVariables.WorldVariables worldVars = CosmosModVariables.WorldVariables.get(level); - // return worldVars.dimension_type.getString(level.dimension().location().toString()).equals("space"); + if (level.isClientSide) { + return false; + } + final CosmosModVariables.WorldVariables worldVars = CosmosModVariables.WorldVariables.get(level); + return worldVars.dimension_type.getString(level.dimension().location().toString()).equals("space"); } /** diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index d603c728..c499ca1d 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -37,7 +37,6 @@ "client": [ "client.MixinCamera", "client.MixinClientPacketListener", - "client.MixinGameRenderer", "client.MixinGui", "client.MixinLivingEntityRenderer", "client.MixinLocalPlayer", From 3ef3811a8dd804a2807cd9b4dad6967c8e7122f7 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Thu, 31 Jul 2025 09:52:03 -0600 Subject: [PATCH 15/32] add headPitch --- .../accessor/FreeRotatePlayerAccessor.java | 12 +++- .../net/jcm/vsch/client/ClientEvents.java | 1 + .../vsch/entity/player/MultiPartPlayer.java | 37 +++++++--- .../client/MixinLivingEntityRenderer.java | 4 +- .../vsch/mixin/client/MixinLocalPlayer.java | 6 +- .../vsch/mixin/client/MixinRemotePlayer.java | 2 +- .../jcm/vsch/mixin/minecraft/MixinEntity.java | 2 +- .../mixin/minecraft/MixinLivingEntity.java | 2 +- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 70 +++++++++++++------ 9 files changed, 94 insertions(+), 42 deletions(-) diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index c9f03f55..5f9a70ca 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -6,6 +6,8 @@ import org.joml.Quaternionf; public interface FreeRotatePlayerAccessor { + EntityDimensions vsch$getVanillaDimensions(Pose pose); + boolean vsch$isFreeRotating(); Quaternionf vsch$getRotation(); @@ -16,11 +18,15 @@ public interface FreeRotatePlayerAccessor { void vsch$setRotationO(Quaternionf rotation); - Quaternionf vsch$getLerpRotation(); - void vsch$setLerpRotation(Quaternionf rotation); + float vsch$getHeadPitch(); + + void vsch$setLerpHeadPitch(float pitch); + boolean vsch$hasSupportingBlock(); - EntityDimensions vsch$getVanillaDimensions(Pose pose); + void vsch$setOldPosAndRot(); + + void vsch$stepLerp(int steps); } diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index 1953754c..a2ba4a48 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -17,6 +17,7 @@ public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event final Camera camera = event.getCamera(); if (camera.getEntity() instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { event.setRoll(camera.rotation().getEulerAnglesYXZ(new Vector3f()).z * Mth.RAD_TO_DEG); + event.setPitch(event.getPitch() + frp.vsch$getHeadPitch()); } } } diff --git a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java index 903607f6..9ec6d767 100644 --- a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java +++ b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java @@ -1,5 +1,6 @@ package net.jcm.vsch.entity.player; +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.jcm.vsch.accessor.EntityAccessor; import net.minecraft.nbt.CompoundTag; @@ -15,10 +16,12 @@ public class MultiPartPlayer extends PartEntity { private final EntityDimensions size; + private final boolean isFeet; - public MultiPartPlayer(final Player parent, final float size) { + public MultiPartPlayer(final Player parent, final float size, final boolean isFeet) { super(parent); this.size = EntityDimensions.scalable(size, size); + this.isFeet = isFeet; this.refreshDimensions(); } @@ -37,13 +40,23 @@ public boolean shouldBeSaved() { } @Override - protected void onInsideBlock(final BlockState block) { - ((EntityAccessor)(this.getParent())).vsch$onInsideBlock(block); + public boolean isAlive() { + return ((FreeRotatePlayerAccessor)(this.getParent())).vsch$isFreeRotating(); + } + + @Override + public boolean isSpectator() { + return !this.isAlive(); } @Override public boolean onGround() { - return this.getParent().onGround(); + return this.isFeet && super.onGround(); + } + + @Override + protected void onInsideBlock(final BlockState block) { + ((EntityAccessor)(this.getParent())).vsch$onInsideBlock(block); } @Override @@ -53,12 +66,12 @@ public boolean isNoGravity() { @Override public boolean isPickable() { - return this.getParent().isPickable(); + return this.isAlive() && this.getParent().isPickable(); } @Override public ItemStack getPickResult() { - return this.getParent().getPickResult(); + return this.isAlive() ? this.getParent().getPickResult() : null; } @Override @@ -68,7 +81,7 @@ public boolean isShiftKeyDown() { @Override public boolean isInvulnerableTo(final DamageSource source) { - return this.getParent().isInvulnerableTo(source); + return !this.isAlive() || this.getParent().isInvulnerableTo(source); } @Override @@ -106,12 +119,16 @@ public Vec3 getDeltaMovement() { @Override public void setDeltaMovement(final Vec3 vel) { - this.getParent().setDeltaMovement(vel); + if (this.isAlive()) { + this.getParent().setDeltaMovement(vel); + } } @Override public void addDeltaMovement(final Vec3 vel) { - this.getParent().addDeltaMovement(vel); + if (this.isAlive()) { + this.getParent().addDeltaMovement(vel); + } } @Override @@ -120,7 +137,7 @@ public float maxUpStep() { } @Override - public void tick() { + public void baseTick() { this.firstTick = false; } } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java index db4f8f83..fc7c6fab 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java @@ -57,7 +57,7 @@ public abstract class MixinLivingEntityRenderer { target = "Lnet/minecraft/client/model/EntityModel;setupAnim(Lnet/minecraft/world/entity/Entity;FFFFF)V" ) ) - public void render$setupAnim( + public void render$setupAnim(setupAnim final EntityModel model, final Entity entity, final float limbSwing, @@ -69,7 +69,7 @@ public abstract class MixinLivingEntityRenderer { ) { if ((entity instanceof Player player) && (player instanceof FreeRotatePlayerAccessor frp) && frp.vsch$isFreeRotating()) { headYaw = 0; - headPitch = 0; + headPitch = frp.vsch$getHeadPitch(); } operation.call(model, entity, limbSwing, limbSwingAmount, age, headYaw, headPitch); } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index fafd694a..d2eb91bb 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -47,15 +47,13 @@ public abstract class MixinLocalPlayer extends MixinPlayer { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isShiftKeyDown()Z", ordinal = 0) ) protected boolean aiStep$setCrouching$isShiftKeyDown(final LocalPlayer self, final Operation operation) { - if (operation.call(self) == Boolean.FALSE) { + if (!operation.call(self)) { return false; } if (this.getAbilities().flying || !this.vsch$isFreeRotating()) { return true; } - // TODO: fix crouching pose - // return this.onGround(); - return false; + return this.onGround(); } @Inject(method = "serverAiStep", at = @At("RETURN")) diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java index 91c3ef96..2a385b7b 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinRemotePlayer.java @@ -29,6 +29,6 @@ protected MixinRemotePlayer() { if (!(this instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { return; } - frp.vsch$setRotation(frp.vsch$getRotation().nlerp(frp.vsch$getLerpRotation(), 1.0f / this.lerpSteps).normalize()); + frp.vsch$stepLerp(this.lerpSteps); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java index e1fd1a7d..a54c2d45 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -67,7 +67,7 @@ private void collide(final Vec3 originMovement, final CallbackInfoReturnable Date: Thu, 31 Jul 2025 10:23:27 -0600 Subject: [PATCH 16/32] fix misc --- .../net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java | 2 +- src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java index fc7c6fab..54f50a83 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java @@ -57,7 +57,7 @@ public abstract class MixinLivingEntityRenderer { target = "Lnet/minecraft/client/model/EntityModel;setupAnim(Lnet/minecraft/world/entity/Entity;FFFFF)V" ) ) - public void render$setupAnim(setupAnim + public void render$setupAnim( final EntityModel model, final Entity entity, final float limbSwing, diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index cdb9dc70..545c5318 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -95,8 +95,8 @@ protected MixinPlayer() { public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { final Player player = (Player)((Object)(this)); - this.chestPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); - this.feetPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE); + this.chestPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE, false); + this.feetPart = new MultiPartPlayer(player, SPACE_ENTITY_SIZE, true); this.parts = new MultiPartPlayer[]{this.chestPart, this.feetPart}; this.oldPose = this.getPose(); } From 418e74273d6a2a7ec84c58ada7ac63af971754e2 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Wed, 6 Aug 2025 09:24:53 -0600 Subject: [PATCH 17/32] implement multi part player entity pushing --- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 545c5318..a56faa6a 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -17,6 +17,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; @@ -24,6 +25,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; @@ -41,9 +43,11 @@ 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.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.List; import java.util.Map; @Mixin(Player.class) @@ -386,6 +390,21 @@ public boolean isInLava() { return super.isInLava() || this.chestPart.isInLava() || this.feetPart.isInLava(); } + @Override + public boolean isInWater() { + return super.isInWater() || this.chestPart.isInWater() || this.feetPart.isInWater(); + } + + @Override + public boolean isInWaterRainOrBubble() { + return super.isInWaterRainOrBubble() || this.chestPart.isInWaterRainOrBubble() || this.feetPart.isInWaterRainOrBubble(); + } + + @Override + public boolean isInWaterOrBubble() { + return super.isInWaterOrBubble() || this.chestPart.isInWaterOrBubble() || this.feetPart.isInWaterOrBubble(); + } + @Override public void moveRelative(final float power, final Vec3 movement) { if (!this.vsch$isFreeRotating()) { @@ -478,6 +497,23 @@ protected void setLevel(final Level level) { this.updateDefaultFreeRotation(); } + @Override + protected void pushEntities() { + super.pushEntities(); + if (!this.vsch$isFreeRotating()) { + return; + } + final Level level = this.level(); + for (final MultiPartPlayer part : this.parts) { + level.getEntities( + EntityTypeTest.forClass(Player.class), + part.getBoundingBox(), + EntitySelector.pushableBy(this) + ) + .forEach((e) -> e.push(part)); + } + } + @Override public void baseTick() { if (this.firstTick) { @@ -495,11 +531,37 @@ public void baseTick() { this.vsch$setOldPosAndRot(); } - @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;updatePlayerPose()V", ordinal = 0)) - public void tick(final CallbackInfo ci) { + @Inject( + method = "tick", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;updatePlayerPose()V", ordinal = 0) + ) + public void tick$updatePlayerPose(final CallbackInfo ci) { this.updateParts(); } + @WrapOperation( + method = "aiStep", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isSpectator()Z") + ), + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;", + ordinal = 0 + ) + ) + public List aiStep$getEntities$touch( + final Level level, + final Entity self, + AABB box, + final Operation> operation + ) { + for (final MultiPartPlayer part : this.parts) { + box = box.minmax(part.getBoundingBox().inflate(0.5, 0.5, 0.5)); + } + return operation.call(level, self, box); + } + @Unique private void updateParts() { this.setYBodyRot(this.getYHeadRot()); From 4df35cb037b3e8dcfdf8caad508bdb605099e1a2 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Wed, 6 Aug 2025 10:55:06 -0600 Subject: [PATCH 18/32] fix collision --- .../vsch/entity/player/MultiPartPlayer.java | 42 ++++++++++--- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 62 +++++++++++++++++-- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java index 9ec6d767..c639991a 100644 --- a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java +++ b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java @@ -12,6 +12,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.Team; import net.minecraftforge.entity.PartEntity; public class MultiPartPlayer extends PartEntity { @@ -70,8 +71,33 @@ public boolean isPickable() { } @Override - public ItemStack getPickResult() { - return this.isAlive() ? this.getParent().getPickResult() : null; + public boolean isPushable() { + return this.isAlive() && this.getParent().isPushable(); + } + + @Override + public void push(final double x, final double y, final double z) { + if (this.isAlive()) { + this.getParent().push(x, y, z); + } + } + + @Override + public boolean hurt(final DamageSource source, final float damage) { + if (this.isInvulnerableTo(source)) { + return false; + } + return this.getParent().hurt(source, damage); + } + + @Override + public boolean isPassenger() { + return this.isAlive() && this.getParent().isPassenger(); + } + + @Override + public boolean isVehicle() { + return this.isAlive() && this.getParent().isVehicle(); } @Override @@ -85,11 +111,13 @@ public boolean isInvulnerableTo(final DamageSource source) { } @Override - public boolean hurt(final DamageSource source, final float damage) { - if (this.isInvulnerableTo(source)) { - return false; - } - return this.getParent().hurt(source, damage); + public String getScoreboardName() { + return this.getParent().getScoreboardName(); + } + + @Override + public ItemStack getPickResult() { + return this.isAlive() ? this.getParent().getPickResult() : null; } @Override diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index a56faa6a..7e630bbe 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -29,6 +29,7 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.scores.Team; import org.joml.Quaternionf; import org.joml.Vector3d; @@ -49,6 +50,7 @@ import java.util.List; import java.util.Map; +import java.util.function.Predicate; @Mixin(Player.class) public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlayerAccessor { @@ -218,6 +220,9 @@ private void reCalcRotation() { @Unique protected void updateDefaultFreeRotation() { + if (this.level().isClientSide) { + return; + } final boolean freeRotation = !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); this.entityData.set(FREE_ROTATION_ID, freeRotation); } @@ -499,18 +504,40 @@ protected void setLevel(final Level level) { @Override protected void pushEntities() { - super.pushEntities(); if (!this.vsch$isFreeRotating()) { + super.pushEntities(); return; } final Level level = this.level(); - for (final MultiPartPlayer part : this.parts) { + final Team selfTeam = this.getTeam(); + final Team.CollisionRule selfTeamRule = selfTeam == null ? Team.CollisionRule.ALWAYS : selfTeam.getCollisionRule(); + if (selfTeamRule == Team.CollisionRule.NEVER) { + return; + } + Predicate selector = EntitySelector.NO_SPECTATORS + .and(Entity::isPushable) + .and((e) -> { + final Team team = e.getTeam(); + final Team.CollisionRule teamRule = team == null ? Team.CollisionRule.ALWAYS : team.getCollisionRule(); + if (teamRule == Team.CollisionRule.NEVER) { + return false; + } + final boolean sameTeam = selfTeam != null && selfTeam.isAlliedTo(team); + if (sameTeam) { + return selfTeamRule != Team.CollisionRule.PUSH_OWN_TEAM && teamRule != Team.CollisionRule.PUSH_OWN_TEAM; + } + return selfTeamRule != Team.CollisionRule.PUSH_OTHER_TEAMS && teamRule != Team.CollisionRule.PUSH_OTHER_TEAMS; + }); + // if (level.isClientSide()) { + // selector.and((e) -> e instanceof Player || e instanceof MultiPartPlayer); + // } + for (final Entity part : new Entity[]{this, this.chestPart, this.feetPart}) { level.getEntities( - EntityTypeTest.forClass(Player.class), + part, part.getBoundingBox(), - EntitySelector.pushableBy(this) + selector ) - .forEach((e) -> e.push(part)); + .forEach((e) -> push3Dim(part, e)); } } @@ -581,4 +608,29 @@ private void updatePartPos(final MultiPartPlayer part, final double dx, final do part.setOldPosAndRot(); part.setPos(pos); } + + @Unique + private static void push3Dim(final Entity e1, final Entity e2) { + if (e1.noPhysics || e2.noPhysics || e1.isPassengerOfSameVehicle(e2)) { + return; + } + final AABB e1Box = e1.getBoundingBox(); + final AABB e2Box = e2.getBoundingBox(); + final Vector3d movement = new Vector3d( + e1.getX() - e2.getX(), + (e1.getY() + e1Box.getYsize() / 2) - (e2.getY() + e2Box.getYsize() / 2), + e1.getZ() - e2.getZ() + ); + final double lengthSqr = movement.lengthSquared(); + if (lengthSqr < 0.0001) { + return; + } + movement.normalize(Math.min(1 / Math.sqrt(lengthSqr), 0.2)).mul(1.0 / 20); + if (!e1.isVehicle() && e1.isPushable()) { + e1.push(movement.x, movement.y, movement.z); + } + if (!e2.isVehicle() && e2.isPushable()) { + e2.push(-movement.x, -movement.y, -movement.z); + } + } } From be05882e45f2d2c23da025d9170fffa23279fbfd Mon Sep 17 00:00:00 2001 From: zyxkad Date: Wed, 6 Aug 2025 12:12:49 -0600 Subject: [PATCH 19/32] add player roll --- gradle.properties | 2 +- src/main/java/net/jcm/vsch/VSCHMod.java | 7 ++-- .../net/jcm/vsch/client/ClientEvents.java | 19 ++++++++++ .../net/jcm/vsch/client/VSCHKeyBindings.java | 36 +++++++++++++++++++ .../net/jcm/vsch/config/VSCHClientConfig.java | 28 +++++++++++++++ .../java/net/jcm/vsch/config/VSCHConfig.java | 10 ++++-- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 32 +++++++++++++---- .../resources/assets/vsch/lang/en_us.json | 2 ++ .../resources/assets/vsch/lang/zh_cn.json | 2 ++ 9 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java create mode 100644 src/main/java/net/jcm/vsch/config/VSCHClientConfig.java diff --git a/gradle.properties b/gradle.properties index fa2560c4..533b8db5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ mod_version=1.3.0 minecraft_version=1.20.1 minecraft_version_range=[1.20.1] -forge_version=47.2.0 +forge_version=47.4.0 forge_version_range=[47,) loader_version_range=[47,) mapping_channel=official diff --git a/src/main/java/net/jcm/vsch/VSCHMod.java b/src/main/java/net/jcm/vsch/VSCHMod.java index 2d93b7d9..2f4358b4 100644 --- a/src/main/java/net/jcm/vsch/VSCHMod.java +++ b/src/main/java/net/jcm/vsch/VSCHMod.java @@ -7,6 +7,7 @@ import net.jcm.vsch.compat.create.ponder.VSCHPonderRegistrateBlocks; import net.jcm.vsch.compat.create.ponder.VSCHPonderRegistry; import net.jcm.vsch.compat.create.ponder.VSCHPonderTags; +import net.jcm.vsch.config.VSCHClientConfig; import net.jcm.vsch.config.VSCHConfig; import net.jcm.vsch.entity.VSCHEntities; import net.jcm.vsch.event.GravityInducer; @@ -28,12 +29,14 @@ public class VSCHMod { public static final String MODID = "vsch"; public VSCHMod() { - IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + final FMLJavaModLoadingContext context = FMLJavaModLoadingContext.get(); + IEventBus modBus = context.getModEventBus(); VSCHItems.register(modBus); VSCHBlocks.register(modBus); VSCHBlockEntities.register(modBus); - VSCHConfig.register(ModLoadingContext.get()); + VSCHClientConfig.register(context); + VSCHConfig.register(context); VSCHTab.register(modBus); VSCHEntities.register(modBus); VSCHTags.register(); diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index a2ba4a48..264009ed 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -5,6 +5,7 @@ import net.minecraft.client.Camera; import net.minecraft.util.Mth; import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -20,4 +21,22 @@ public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event event.setPitch(event.getPitch() + frp.vsch$getHeadPitch()); } } + + private static long lastFrameTime = System.nanoTime(); + private static volatile float spf = 0; + + public static float getSpf() { + return spf; + } + + @SubscribeEvent + public static void onRenderTick(final TickEvent.RenderTickEvent event) { + switch (event.phase) { + case START -> { + final long now = System.nanoTime(); + spf = Math.min((float)((now - lastFrameTime) / 1.0e9), 0.1f); + lastFrameTime = now; + } + } + } } diff --git a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java new file mode 100644 index 00000000..31c3219f --- /dev/null +++ b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java @@ -0,0 +1,36 @@ +package net.jcm.vsch.client; + +import net.jcm.vsch.VSCHMod; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterKeyMappingsEvent; +import net.minecraftforge.client.settings.KeyConflictContext; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = VSCHMod.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) +public final class VSCHKeyBindings { + public static final KeyMapping ROLL_CLOCKWISE = new KeyMapping( + "key." + VSCHMod.MODID + ".roll_clockwise", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + -1, + "key.categories.movement" + ); + + public static final KeyMapping ROLL_COUNTER_CLOCKWISE = new KeyMapping( + "key." + VSCHMod.MODID + ".roll_counter_clockwise", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + -1, + "key.categories.movement" + ); + + @SubscribeEvent + public static void register(final RegisterKeyMappingsEvent event) { + event.register(ROLL_COUNTER_CLOCKWISE); + event.register(ROLL_CLOCKWISE); + } +} diff --git a/src/main/java/net/jcm/vsch/config/VSCHClientConfig.java b/src/main/java/net/jcm/vsch/config/VSCHClientConfig.java new file mode 100644 index 00000000..180d305c --- /dev/null +++ b/src/main/java/net/jcm/vsch/config/VSCHClientConfig.java @@ -0,0 +1,28 @@ +package net.jcm.vsch.config; + +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.config.ModConfig; + +public final class VSCHClientConfig { + public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + public static final ForgeConfigSpec SPEC; + + /* Experimental */ + + public static final ForgeConfigSpec.DoubleValue PLAYER_ROLL_SPEED; + + static { + BUILDER.push("Experimental"); + + PLAYER_ROLL_SPEED = BUILDER.comment("How fast does player roll with the roll keys in deg/s").defineInRange("player_roll_speed", 80.0, 1, 720); + + BUILDER.pop(); + + SPEC = BUILDER.build(); + } + + public static void register(ModLoadingContext context){ + context.registerConfig(ModConfig.Type.CLIENT, SPEC, "vsch-client-config.toml"); + } +} diff --git a/src/main/java/net/jcm/vsch/config/VSCHConfig.java b/src/main/java/net/jcm/vsch/config/VSCHConfig.java index 95d1c6f3..360ad0ad 100644 --- a/src/main/java/net/jcm/vsch/config/VSCHConfig.java +++ b/src/main/java/net/jcm/vsch/config/VSCHConfig.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.Set; -public class VSCHConfig { +public final class VSCHConfig { private static final Gson GSON = new GsonBuilder().create(); private static final TypeToken> STRING_INT_MAP_TYPE = new TypeToken>(){}; @@ -73,6 +73,8 @@ public class VSCHConfig { public static final ForgeConfigSpec.BooleanValue ENABLE_PLACE_SHIP_PLATFORM; + /* Experimental */ + public static final ForgeConfigSpec.BooleanValue PLAYER_FREE_ROTATION_IN_SPACE; private static final List DEFAULT_ASSEMBLE_BLACKLIST = List.of( @@ -139,6 +141,10 @@ public class VSCHConfig { ENABLE_PLACE_SHIP_PLATFORM = BUILDER.comment("After enabled, the block placed by key N will be spawned as a ship.").define("enable_place_ship_platform", false); + BUILDER.pop(); + + BUILDER.push("Experimental"); + PLAYER_FREE_ROTATION_IN_SPACE = BUILDER.comment("Allow player to free rotate in space.").define("player_free_rotation_in_space", false); BUILDER.pop(); @@ -147,7 +153,7 @@ public class VSCHConfig { } public static void register(ModLoadingContext context){ - context.registerConfig(ModConfig.Type.SERVER, VSCHConfig.SPEC, "vsch-config.toml"); + context.registerConfig(ModConfig.Type.SERVER, SPEC, "vsch-config.toml"); } private static String getDefaultThrusterFuelConsumeRates() { diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 7e630bbe..c8a3f4ef 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -2,6 +2,10 @@ import net.jcm.vsch.accessor.EntityAccessor; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.client.ClientEvents; +import net.jcm.vsch.client.VSCHKeyBindings; +import net.jcm.vsch.config.VSCHClientConfig; +import net.jcm.vsch.config.VSCHConfig; import net.jcm.vsch.entity.player.MultiPartPlayer; import net.jcm.vsch.util.BooleanRef; import net.jcm.vsch.util.VSCHUtils; @@ -223,7 +227,7 @@ protected void updateDefaultFreeRotation() { if (this.level().isClientSide) { return; } - final boolean freeRotation = !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); + final boolean freeRotation = VSCHConfig.PLAYER_FREE_ROTATION_IN_SPACE.get() && !this.isPassenger() && VSCHUtils.isSpaceLevel(this.level()); this.entityData.set(FREE_ROTATION_ID, freeRotation); } @@ -299,17 +303,33 @@ public void turn(final double x, final double y) { super.turn(x, y); return; } - if (x == 0 && y == 0) { + + final boolean isClientSide = this.level().isClientSide; + + final float roll; + if (isClientSide) { + int rollDir = 0; + if (VSCHKeyBindings.ROLL_CLOCKWISE.isDown()) { + rollDir++; + } + if (VSCHKeyBindings.ROLL_COUNTER_CLOCKWISE.isDown()) { + rollDir--; + } + roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * ClientEvents.getSpf() * Mth.DEG_TO_RAD; + } else { + roll = 0; + } + + if (x == 0 && y == 0 && roll == 0) { return; } final float yaw = -(float)(x) * 0.15f * Mth.DEG_TO_RAD; final float pitch = (float)(y) * 0.15f * Mth.DEG_TO_RAD; - final Quaternionf pitchRot = new Quaternionf().rotateX(pitch); - final Quaternionf yawRot = new Quaternionf().rotateY(yaw); + final Quaternionf rotation = new Quaternionf().rotateYXZ(yaw, pitch, roll); - this.vsch$setRotation(this.vsch$getRotation().mul(yawRot).mul(pitchRot).normalize()); - this.vsch$setRotationO(this.rotationO.mul(yawRot).mul(pitchRot).normalize()); + this.vsch$setRotation(this.vsch$getRotation().mul(rotation).normalize()); + this.vsch$setRotationO(this.rotationO.mul(rotation).normalize()); } @Override diff --git a/src/main/resources/assets/vsch/lang/en_us.json b/src/main/resources/assets/vsch/lang/en_us.json index 4ba9d604..3a530376 100644 --- a/src/main/resources/assets/vsch/lang/en_us.json +++ b/src/main/resources/assets/vsch/lang/en_us.json @@ -17,6 +17,8 @@ "block.vsch.thruster_block": "Thruster", "item.vsch.magnet_boot": "Magnet Boots", "item.vsch.wrench": "Wrench", + "key.vsch.roll_clockwise": "Roll Clockwise", + "key.vsch.roll_counter_clockwise": "Roll Counter Clockwise", "config.jade.plugin_vsch.gyro_component_config": "Gyro Mode Details", "config.jade.plugin_vsch.thruster_component_config": "Thruster Mode Details", diff --git a/src/main/resources/assets/vsch/lang/zh_cn.json b/src/main/resources/assets/vsch/lang/zh_cn.json index d986dafb..7875b543 100644 --- a/src/main/resources/assets/vsch/lang/zh_cn.json +++ b/src/main/resources/assets/vsch/lang/zh_cn.json @@ -17,6 +17,8 @@ "block.vsch.thruster_block": "推进器", "item.vsch.magnet_boot": "磁力靴", "item.vsch.wrench": "扳手", + "key.vsch.roll_clockwise": "顺时针旋转", + "key.vsch.roll_counter_clockwise": "逆时针旋转", "config.jade.plugin_vsch.gyro_component_config": "陀螺仪配置信息", "config.jade.plugin_vsch.thruster_component_config": "推进器配置信息", From 964b7c819e4f6c4f33885158d6185ee86c3efc92 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Wed, 6 Aug 2025 19:25:25 -0600 Subject: [PATCH 20/32] trying to make magnet boot align with the ship --- .../accessor/FreeRotatePlayerAccessor.java | 12 +++++ .../jcm/vsch/items/custom/MagnetBootItem.java | 44 +++++++++++++++---- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 13 ++++++ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 5f9a70ca..774b3bc0 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -2,14 +2,26 @@ import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.Pose; +import net.minecraft.world.phys.Vec3; import org.joml.Quaternionf; +import org.joml.Vector3d; public interface FreeRotatePlayerAccessor { EntityDimensions vsch$getVanillaDimensions(Pose pose); boolean vsch$isFreeRotating(); + Vec3 vsch$getFeetPosition(); + + default Vec3 vsch$getDownVector() { + if (!this.vsch$isFreeRotating()) { + return new Vec3(0, -1, 0); + } + final Vector3d posY = this.vsch$getRotation().transformUnitPositiveY(new Vector3d()); + return new Vec3(-posY.x, -posY.y, -posY.z); + } + Quaternionf vsch$getRotation(); void vsch$setRotation(Quaternionf rotation); diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index 987a2ce4..8135c154 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -1,7 +1,11 @@ package net.jcm.vsch.items.custom; +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.jcm.vsch.config.VSCHConfig; import net.lointain.cosmos.item.SteelarmourItem; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.DoubleTag; import net.minecraft.nbt.NbtOps; @@ -15,8 +19,13 @@ import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.Level; import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; +import org.joml.Quaternionf; +import org.valkyrienskies.core.api.ships.Ship; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + public class MagnetBootItem extends ArmorItem { private static final String TAG_DISABLED = "Disabled"; private static final String TAG_READY = "Ready"; @@ -85,11 +94,17 @@ public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, double maxDistance = getAttractDistance(); - Vec3 direction = new Vec3(0, -1, 0); // TODO: maybe we can change the direction to match the ship that player stands on? - Vec3 startPos = entity.position(); // Starting position (player's position) - Vec3 endPos = startPos.add(direction.scale(maxDistance)); // End position (straight down) + Vec3 startPos = entity.position(); // Starting position (player's feet position) + Vec3 direction = new Vec3(0, -1, 0); + // TODO: maybe we can change the direction to match the ship that player stands on? + if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + startPos = frp.vsch$getFeetPosition(); + direction = frp.vsch$getDownVector(); + } + + final Vec3 endPos = startPos.add(direction.scale(maxDistance)); // End position (straight down) - HitResult hitResult = level.clip(new ClipContext( + final HitResult hitResult = level.clip(new ClipContext( startPos, endPos, ClipContext.Block.COLLIDER, // Raycast considers block collision shapes, maybe we don't want this? @@ -112,15 +127,28 @@ public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, return; } - //mAtH - double distance = startPos.distanceToSqr(hitResult.getLocation()); - double scaledForce = Math.min(maxDistance * maxDistance / distance * MIN_FORCE, getMaxForce()); + final BlockHitResult blockHit = ((BlockHitResult)(hitResult)); - Vec3 force = direction.scale(scaledForce); + // mAtH + final double distance = startPos.distanceToSqr(hitResult.getLocation()); + final double scaledForce = Math.min(maxDistance * maxDistance / distance * MIN_FORCE, getMaxForce()); + + final Vec3 force = direction.scale(scaledForce); tag.putDouble("Force", scaledForce); entity.push(force.x, force.y, force.z); + if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + final BlockPos blockPos = blockHit.getBlockPos(); + final Direction face = blockHit.getDirection(); + final Quaternionf destRot = new Quaternionf().rotateTo(0, 1, 0, face.getStepX(), face.getStepY(), face.getStepZ()); + final Ship ship = VSGameUtilsKt.getShipManagingPos(level, blockPos); + if (ship != null) { + new Quaternionf().setFromNormalized(ship.getShipToWorld()).mul(destRot, destRot); + } + frp.vsch$setRotation(frp.vsch$getRotation().slerp(destRot, 0.2f)); + } + //level.addParticle(ParticleTypes.HEART, player.getX(), player.getY(), player.getZ(), 0, 0, 0); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index c8a3f4ef..8913cf9f 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -131,6 +131,19 @@ public MultiPartPlayer[] getParts() { return !this.firstTick && this.entityData.get(FREE_ROTATION_ID); } + @Unique + private Vec3 getFreeHeadCenter() { + return this.position().add(0, SPACE_ENTITY_SIZE / 2, 0); + } + + @Override + public Vec3 vsch$getFeetPosition() { + if (!this.vsch$isFreeRotating()) { + return this.position(); + } + return this.getFreeHeadCenter().add(this.vsch$getDownVector().scale(SPACE_ENTITY_SIZE * 2)); + } + @Override public Quaternionf vsch$getRotation() { return this.rotation; From a97d93fe61e844033fe78cdaffb5895ad0963a31 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Sat, 20 Sep 2025 01:45:37 -0600 Subject: [PATCH 21/32] complete head rotation --- .../EntityRotationPacketAccessor.java | 4 + .../accessor/FreeRotatePlayerAccessor.java | 26 ++- .../net/jcm/vsch/client/ClientEvents.java | 10 +- .../net/jcm/vsch/client/VSCHKeyBindings.java | 11 + .../client/key/DoubleClickKeyMapping.java | 41 ++++ .../jcm/vsch/mixin/client/MixinCamera.java | 2 +- .../client/MixinClientPacketListener.java | 35 ++- .../client/MixinLivingEntityRenderer.java | 13 +- .../vsch/mixin/client/MixinLocalPlayer.java | 10 +- .../client/MixinMultiPlayerGameMode.java | 7 +- ...xinClientboundMoveEntityPacket_PosRot.java | 2 + .../MixinClientboundMoveEntityPacket_Rot.java | 2 + .../minecraft/MixinEntityRotationPackets.java | 24 ++ .../MixinEntityRotationPackets_initRead.java | 2 + .../MixinEntityRotationPackets_write.java | 2 + .../jcm/vsch/mixin/minecraft/MixinMob.java | 38 ++-- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 208 +++++++++++++++--- .../mixin/minecraft/MixinServerEntity.java | 21 +- .../MixinServerGamePacketListenerImpl.java | 15 +- .../mixin/minecraft/MixinServerPlayer.java | 5 +- ...xinServerboundMovePlayerPacket_PosRot.java | 2 + .../MixinServerboundMovePlayerPacket_Rot.java | 2 + 22 files changed, 390 insertions(+), 92 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/client/key/DoubleClickKeyMapping.java diff --git a/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java b/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java index 3da4cbb1..150dde7e 100644 --- a/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/EntityRotationPacketAccessor.java @@ -4,4 +4,8 @@ public interface EntityRotationPacketAccessor { Quaternionf vsch$rotation(); + float vsch$getHeadPitch(); + void vsch$setHeadPitch(float pitch); + float vsch$getHeadYaw(); + void vsch$setHeadYaw(float yaw); } diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 5f9a70ca..913d98a8 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -10,20 +10,36 @@ public interface FreeRotatePlayerAccessor { boolean vsch$isFreeRotating(); - Quaternionf vsch$getRotation(); + Quaternionf vsch$getBodyRotation(); - void vsch$setRotation(Quaternionf rotation); + void vsch$setBodyRotation(Quaternionf rotation); - Quaternionf vsch$getRotationO(); + Quaternionf vsch$getBodyRotationO(); - void vsch$setRotationO(Quaternionf rotation); + void vsch$setBodyRotationO(Quaternionf rotation); - void vsch$setLerpRotation(Quaternionf rotation); + void vsch$setLerpBodyRotation(Quaternionf rotation); + + Quaternionf vsch$getHeadRotation(); + + Quaternionf vsch$getHeadRotationO(); float vsch$getHeadPitch(); + void vsch$setHeadPitch(float pitch); + + float vsch$getHeadPitchO(); + void vsch$setLerpHeadPitch(float pitch); + float vsch$getHeadYaw(); + + void vsch$setHeadYaw(float yaw); + + float vsch$getHeadYawO(); + + void vsch$setLerpHeadYaw(float yaw); + boolean vsch$hasSupportingBlock(); void vsch$setOldPosAndRot(); diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index 264009ed..22b1ff08 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -13,12 +13,16 @@ @Mod.EventBusSubscriber public final class ClientEvents { + private static final Vector3f CAMERA_ROT_VEC = new Vector3f(); + @SubscribeEvent public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event) { final Camera camera = event.getCamera(); - if (camera.getEntity() instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { - event.setRoll(camera.rotation().getEulerAnglesYXZ(new Vector3f()).z * Mth.RAD_TO_DEG); - event.setPitch(event.getPitch() + frp.vsch$getHeadPitch()); + if (camera.getEntity() instanceof final FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + camera.rotation().getEulerAnglesYXZ(CAMERA_ROT_VEC); + event.setPitch(CAMERA_ROT_VEC.x * Mth.RAD_TO_DEG); + event.setYaw(-CAMERA_ROT_VEC.y * Mth.RAD_TO_DEG); + event.setRoll(CAMERA_ROT_VEC.z * Mth.RAD_TO_DEG); } } diff --git a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java index 31c3219f..9f5e29f2 100644 --- a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java +++ b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java @@ -1,6 +1,7 @@ package net.jcm.vsch.client; import net.jcm.vsch.VSCHMod; +import net.jcm.vsch.client.key.DoubleClickKeyMapping; import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.KeyMapping; @@ -9,6 +10,7 @@ import net.minecraftforge.client.settings.KeyConflictContext; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import org.lwjgl.glfw.GLFW; @Mod.EventBusSubscriber(modid = VSCHMod.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public final class VSCHKeyBindings { @@ -28,9 +30,18 @@ public final class VSCHKeyBindings { "key.categories.movement" ); + public static final DoubleClickKeyMapping UNLOCK_HEAD_ROTATION = new DoubleClickKeyMapping( + "key." + VSCHMod.MODID + ".unlock_head_rotation", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_LEFT_SUPER, + "key.categories.movement" + ); + @SubscribeEvent public static void register(final RegisterKeyMappingsEvent event) { event.register(ROLL_COUNTER_CLOCKWISE); event.register(ROLL_CLOCKWISE); + event.register(UNLOCK_HEAD_ROTATION); } } diff --git a/src/main/java/net/jcm/vsch/client/key/DoubleClickKeyMapping.java b/src/main/java/net/jcm/vsch/client/key/DoubleClickKeyMapping.java new file mode 100644 index 00000000..1a991cae --- /dev/null +++ b/src/main/java/net/jcm/vsch/client/key/DoubleClickKeyMapping.java @@ -0,0 +1,41 @@ +package net.jcm.vsch.client.key; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; +import net.minecraftforge.client.settings.KeyConflictContext; + +public class DoubleClickKeyMapping extends KeyMapping { + private long lastDown = 0; + private long doubleClicked = 0; + + public DoubleClickKeyMapping(final String name, final KeyConflictContext context, final InputConstants.Type type, final int defaultKey, final String category) { + super(name, context, type, defaultKey, category); + } + + public boolean consumeDoubleClick() { + if (this.doubleClicked == 0) { + return false; + } + final boolean valid = this.doubleClicked + 500 >= System.currentTimeMillis(); + this.doubleClicked = 0; + return valid; + } + + @Override + public void setDown(final boolean value) { + super.setDown(value); + final long now = System.currentTimeMillis(); + if (!value) { + if (this.lastDown != 0 && this.lastDown + 180 < now) { + this.lastDown = 0; + } + return; + } + if (this.lastDown + 300 < now) { + this.lastDown = now; + return; + } + this.lastDown = 0; + this.doubleClicked = now; + } +} diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java index 02e0b2da..a679f042 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java @@ -41,7 +41,7 @@ public void setup( if (!(entity instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { return; } - frp.vsch$getRotationO().slerp(frp.vsch$getRotation(), partialTick, this.rotation); + frp.vsch$getHeadRotationO().slerp(frp.vsch$getHeadRotation(), partialTick, this.rotation); if (isThirdPerson && isMirrored) { this.rotation.rotateY((float)(Math.PI)); } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java index e7be9608..af66237c 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinClientPacketListener.java @@ -30,18 +30,24 @@ public abstract class MixinClientPacketListener { ) ) public void handleAddPlayer(final ClientboundAddPlayerPacket packet, final CallbackInfo ci, @Local final RemotePlayer player) { - if (!(player instanceof FreeRotatePlayerAccessor frp)) { + if (!(player instanceof final FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + frp.vsch$setBodyRotation(packetAccessor.vsch$rotation()); + frp.vsch$setHeadPitch(packetAccessor.vsch$getHeadPitch()); + frp.vsch$setHeadYaw(packetAccessor.vsch$getHeadYaw()); } @Inject(method = "handleTeleportEntity", at = @At("RETURN")) public void handleTeleportEntity(final ClientboundTeleportEntityPacket packet, final CallbackInfo ci, @Local final Entity entity) { - if (!(entity instanceof FreeRotatePlayerAccessor frp)) { + if (!(entity instanceof final FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + frp.vsch$setLerpBodyRotation(packetAccessor.vsch$rotation()); + frp.vsch$setLerpHeadPitch(packetAccessor.vsch$getHeadPitch()); + frp.vsch$setLerpHeadYaw(packetAccessor.vsch$getHeadYaw()); } @Inject(method = "handleMoveEntity", at = @At("RETURN")) @@ -49,11 +55,14 @@ public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final Cal if (entity == null || entity.isControlledByLocalInstance()) { return; } - if (!(entity instanceof FreeRotatePlayerAccessor frp)) { + if (!(entity instanceof final FreeRotatePlayerAccessor frp)) { return; } if (packet.hasRotation()) { - frp.vsch$setLerpRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + frp.vsch$setLerpBodyRotation(packetAccessor.vsch$rotation()); + frp.vsch$setLerpHeadPitch(packetAccessor.vsch$getHeadPitch()); + frp.vsch$setLerpHeadYaw(packetAccessor.vsch$getHeadYaw()); } } @@ -62,10 +71,13 @@ public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final Cal at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundAcceptTeleportationPacket;") ) public void handleMovePlayer$acceptTeleport$before(final ClientboundPlayerPositionPacket packet, final CallbackInfo ci, @Local final Player player) { - if (!(player instanceof FreeRotatePlayerAccessor frp)) { + if (!(player instanceof final FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + frp.vsch$setBodyRotation(packetAccessor.vsch$rotation()); + frp.vsch$setHeadPitch(packetAccessor.vsch$getHeadPitch()); + frp.vsch$setHeadYaw(packetAccessor.vsch$getHeadYaw()); } @ModifyExpressionValue( @@ -73,8 +85,11 @@ public void handleMoveEntity(final ClientboundMoveEntityPacket packet, final Cal at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") ) public ServerboundMovePlayerPacket.PosRot handleMovePlayer$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { - if (player instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); + if (player instanceof final FreeRotatePlayerAccessor frp) { + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(frp.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(frp.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(frp.vsch$getHeadYaw()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java index 54f50a83..e59bb42a 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLivingEntityRenderer.java @@ -5,6 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.model.EntityModel; import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.LivingEntity; @@ -14,6 +15,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -40,7 +42,7 @@ public abstract class MixinLivingEntityRenderer { return; } - final Quaternionf rotation = frp.vsch$getRotationO().slerp(frp.vsch$getRotation(), partialTick, new Quaternionf()); + final Quaternionf rotation = frp.vsch$getBodyRotationO().slerp(frp.vsch$getBodyRotation(), partialTick, new Quaternionf()); final EntityDimensions vanillaDim = frp.vsch$getVanillaDimensions(player.getPose()); poseStack.translate(0, 0.6f / 2, 0); @@ -65,11 +67,12 @@ public abstract class MixinLivingEntityRenderer { final float age, float headYaw, float headPitch, - final Operation operation + final Operation operation, + @Local(argsOnly = true, ordinal = 1) final float partialTick ) { - if ((entity instanceof Player player) && (player instanceof FreeRotatePlayerAccessor frp) && frp.vsch$isFreeRotating()) { - headYaw = 0; - headPitch = frp.vsch$getHeadPitch(); + if ((entity instanceof final Player player) && (player instanceof final FreeRotatePlayerAccessor frp) && frp.vsch$isFreeRotating()) { + headYaw = Mth.rotLerp(partialTick, -frp.vsch$getHeadYawO() * Mth.RAD_TO_DEG, -frp.vsch$getHeadYaw() * Mth.RAD_TO_DEG); + headPitch = Mth.rotLerp(partialTick, frp.vsch$getHeadPitchO() * Mth.RAD_TO_DEG, frp.vsch$getHeadPitch() * Mth.RAD_TO_DEG); } operation.call(model, entity, limbSwing, limbSwingAmount, age, headYaw, headPitch); } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index d2eb91bb..470d0416 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -29,7 +29,10 @@ public abstract class MixinLocalPlayer extends MixinPlayer { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$Rot;") ) public ServerboundMovePlayerPacket.Rot new$ServerboundMovePlayerPacket$Rot(final ServerboundMovePlayerPacket.Rot packet) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(this.vsch$getRotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(this.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(this.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(this.vsch$getHeadYaw()); return packet; } @@ -38,7 +41,10 @@ public abstract class MixinLocalPlayer extends MixinPlayer { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") ) public ServerboundMovePlayerPacket.PosRot sendPosition$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(this.vsch$getRotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(this.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(this.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(this.vsch$getHeadYaw()); return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java index 4da9b967..71df4929 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinMultiPlayerGameMode.java @@ -19,8 +19,11 @@ public abstract class MixinMultiPlayerGameMode { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ServerboundMovePlayerPacket$PosRot;") ) public ServerboundMovePlayerPacket.PosRot useItem$new$ServerboundMovePlayerPacket$PosRot(final ServerboundMovePlayerPacket.PosRot packet, @Local final Player player) { - if (player instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); + if (player instanceof final FreeRotatePlayerAccessor frp) { + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(frp.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(frp.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(frp.vsch$getHeadYaw()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java index 2c7897e0..3265fd03 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_PosRot.java @@ -16,5 +16,7 @@ public abstract class MixinClientboundMoveEntityPacket_PosRot { private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); packet.vsch$rotation().set(buf.readQuaternion()).normalize(); + packet.vsch$setHeadPitch(buf.readFloat()); + packet.vsch$setHeadYaw(buf.readFloat()); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java index b126ca34..4e87b02a 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinClientboundMoveEntityPacket_Rot.java @@ -16,5 +16,7 @@ public abstract class MixinClientboundMoveEntityPacket_Rot { private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); packet.vsch$rotation().set(buf.readQuaternion()).normalize(); + packet.vsch$setHeadPitch(buf.readFloat()); + packet.vsch$setHeadYaw(buf.readFloat()); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets.java index b0ba43ee..9c929e35 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets.java @@ -23,9 +23,33 @@ public abstract class MixinEntityRotationPackets implements EntityRotationPacketAccessor { @Unique private Quaternionf rotation = new Quaternionf(); + @Unique + private float headPitch = 0; + @Unique + private float headYaw = 0; @Override public Quaternionf vsch$rotation() { return this.rotation; } + + @Override + public float vsch$getHeadPitch() { + return this.headPitch; + } + + @Override + public void vsch$setHeadPitch(final float pitch) { + this.headPitch = pitch; + } + + @Override + public float vsch$getHeadYaw() { + return this.headYaw; + } + + @Override + public void vsch$setHeadYaw(final float yaw) { + this.headYaw = yaw; + } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_initRead.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_initRead.java index 9e2f591a..b5b01e6d 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_initRead.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_initRead.java @@ -21,5 +21,7 @@ public abstract class MixinEntityRotationPackets_initRead implements EntityRotat @Inject(method = "(Lnet/minecraft/network/FriendlyByteBuf;)V", at = @At("RETURN")) public void init$read(final FriendlyByteBuf buf, final CallbackInfo ci) { this.vsch$rotation().set(buf.readQuaternion()).normalize(); + this.vsch$setHeadPitch(buf.readFloat()); + this.vsch$setHeadYaw(buf.readFloat()); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_write.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_write.java index c7a09b51..9f0615ef 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_write.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntityRotationPackets_write.java @@ -27,5 +27,7 @@ public abstract class MixinEntityRotationPackets_write implements EntityRotation @Inject(method = "write", at = @At("RETURN")) public void write(final FriendlyByteBuf buf, final CallbackInfo ci) { buf.writeQuaternion(this.vsch$rotation()); + buf.writeFloat(this.vsch$getHeadPitch()); + buf.writeFloat(this.vsch$getHeadYaw()); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinMob.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinMob.java index 2e46bacd..39e4b356 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinMob.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinMob.java @@ -13,25 +13,25 @@ @Mixin(Mob.class) public abstract class MixinMob extends Entity { - private MixinMob() { - super(null, null); - } + private MixinMob() { + super(null, null); + } - @Shadow - public abstract Iterable getArmorSlots(); + @Shadow + public abstract Iterable getArmorSlots(); - @Inject(method = "baseTick()V", at = @At(value = "HEAD")) - private void tickArmors(CallbackInfo cb) { - Level level = this.level(); - int i = 0; - for (ItemStack stack : this.getArmorSlots()) { - if (!stack.isEmpty()) { - Item item = stack.getItem(); - if (item != null) { - item.inventoryTick(stack, level, this, i, false); - } - } - i++; - } - } + @Inject(method = "baseTick()V", at = @At(value = "HEAD")) + private void tickArmors(CallbackInfo cb) { + Level level = this.level(); + int i = 0; + for (ItemStack stack : this.getArmorSlots()) { + if (!stack.isEmpty()) { + Item item = stack.getItem(); + if (item != null) { + item.inventoryTick(stack, level, this, i, false); + } + } + i++; + } + } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index a60c4349..b33e1875 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -71,6 +71,8 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay private static final double SUPPORT_CHECK_DISTANCE = 0.1; @Unique private static final EntityDataAccessor FREE_ROTATION_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BOOLEAN); + @Unique + private static final float BODY_ROT_CLAMP = 50 * Mth.DEG_TO_RAD; @Unique private boolean wasFreeRotating = false; @@ -81,12 +83,22 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay @Unique private Quaternionf rotationLerp = new Quaternionf(); @Unique + private Quaternionf headRotation = new Quaternionf(); + @Unique + private Quaternionf headRotationO = new Quaternionf(); + @Unique private float headPitch = 0; @Unique private float headPitchO = 0; @Unique private float headPitchLerp = 0; @Unique + private float headYaw = 0; + @Unique + private float headYawO = 0; + @Unique + private float headYawLerp = 0; + @Unique private MultiPartPlayer[] parts; @Unique private MultiPartPlayer chestPart; @@ -133,12 +145,12 @@ public MultiPartPlayer[] getParts() { } @Override - public Quaternionf vsch$getRotation() { + public Quaternionf vsch$getBodyRotation() { return this.rotation; } @Override - public void vsch$setRotation(final Quaternionf rotation) { + public void vsch$setBodyRotation(final Quaternionf rotation) { if (this.rotation != rotation) { this.rotation.set(rotation); } @@ -146,19 +158,16 @@ public MultiPartPlayer[] getParts() { return; } final Vector3f angles = this.rotation.getEulerAnglesYXZ(new Vector3f()); - super.setXRot(angles.x * Mth.RAD_TO_DEG); - final float yRot = -angles.y * Mth.RAD_TO_DEG; - super.setYRot(yRot); - this.yBodyRot = yRot; + this.yBodyRot = -this.rotation.getEulerAnglesYXZ(new Vector3f()).y * Mth.RAD_TO_DEG; } @Override - public Quaternionf vsch$getRotationO() { + public Quaternionf vsch$getBodyRotationO() { return this.rotationO; } @Override - public void vsch$setRotationO(final Quaternionf rotation) { + public void vsch$setBodyRotationO(final Quaternionf rotation) { if (this.rotationO != rotation) { this.rotationO.set(rotation); } @@ -167,32 +176,46 @@ public MultiPartPlayer[] getParts() { } final Vector3f angles = this.rotationO.getEulerAnglesYXZ(new Vector3f()); this.xRotO = angles.x * Mth.RAD_TO_DEG; - this.yBodyRotO = this.yRotO = -angles.y * Mth.RAD_TO_DEG; + this.yBodyRotO = -angles.y * Mth.RAD_TO_DEG; } @Override - public void vsch$setLerpRotation(final Quaternionf rotation) { + public void vsch$setLerpBodyRotation(final Quaternionf rotation) { this.rotationLerp.set(rotation); } @Override - public float getYRot() { - return this.vsch$isFreeRotating() - ? -this.rotation.getEulerAnglesYXZ(new Vector3f()).y * Mth.RAD_TO_DEG - : super.getYRot(); + public Quaternionf vsch$getHeadRotation() { + return this.headRotation; } @Override - public void setYRot(final float yRot) { - if (!this.vsch$isFreeRotating()) { - super.setYRot(yRot); - } + public Quaternionf vsch$getHeadRotationO() { + return this.headRotationO; + } + + @Unique + private void reCalcHeadRotation() { + this.headRotation.set(this.rotation).rotateY(this.headYaw).rotateX(this.headPitch); + final Vector3f angles = this.headRotation.getEulerAnglesYXZ(new Vector3f()); + super.setXRot(angles.x * Mth.RAD_TO_DEG); + final float yRot = -angles.y * Mth.RAD_TO_DEG; + super.setYRot(yRot); + this.yHeadRot = yRot; + } + + @Unique + private void reCalcHeadRotationO() { + this.headRotationO.set(this.rotationO).rotateY(this.headYawO).rotateX(this.headPitchO); + final Vector3f angles = this.headRotationO.getEulerAnglesYXZ(new Vector3f()); + this.xRotO = angles.x * Mth.RAD_TO_DEG; + this.yHeadRotO = this.yRotO = -angles.y * Mth.RAD_TO_DEG; } @Override public float getXRot() { return this.vsch$isFreeRotating() - ? this.rotation.getEulerAnglesYXZ(new Vector3f()).x * Mth.RAD_TO_DEG + ? this.vsch$getHeadRotation().getEulerAnglesYXZ(new Vector3f()).x * Mth.RAD_TO_DEG : super.getXRot(); } @@ -203,24 +226,87 @@ public void setXRot(final float xRot) { } } + @Override + public float getYRot() { + return this.vsch$isFreeRotating() + ? -this.vsch$getHeadRotation().getEulerAnglesYXZ(new Vector3f()).y * Mth.RAD_TO_DEG + : super.getYRot(); + } + + @Override + public void setYRot(final float yRot) { + if (!this.vsch$isFreeRotating()) { + super.setYRot(yRot); + } + } + + @Override + public float getViewXRot(final float partialTick) { + final Vector3f angles = new Vector3f(); + final float xRot = this.vsch$getHeadRotation().getEulerAnglesYXZ(angles).x * Mth.RAD_TO_DEG; + final float xRotO = this.vsch$getHeadRotationO().getEulerAnglesYXZ(angles).x * Mth.RAD_TO_DEG; + return Mth.lerp(partialTick, xRotO, xRot); + } + + @Override + public float getViewYRot(final float partialTick) { + final Vector3f angles = new Vector3f(); + final float yRot = -this.vsch$getHeadRotation().getEulerAnglesYXZ(angles).y * Mth.RAD_TO_DEG; + final float yRotO = -this.vsch$getHeadRotationO().getEulerAnglesYXZ(angles).y * Mth.RAD_TO_DEG; + return Mth.lerp(partialTick, yRotO, yRot); + } + @Override public float vsch$getHeadPitch() { return this.headPitch; } + @Override + public void vsch$setHeadPitch(final float pitch) { + this.headPitch = pitch; + } + + @Override + public float vsch$getHeadPitchO() { + return this.headPitchO; + } + @Override public void vsch$setLerpHeadPitch(final float pitch) { this.headPitchLerp = pitch; } + @Override + public float vsch$getHeadYaw() { + return this.headYaw; + } + + @Override + public void vsch$setHeadYaw(final float yaw) { + this.headYaw = yaw; + } + + @Override + public float vsch$getHeadYawO() { + return this.headYawO; + } + + @Override + public void vsch$setLerpHeadYaw(final float yaw) { + this.headYawLerp = yaw; + } + @Unique private void reCalcRotation() { if (this.firstTick || !this.vsch$isFreeRotating()) { return; } - final Quaternionf rotation = this.vsch$getRotation(); + final Quaternionf rotation = this.vsch$getBodyRotation(); final Vector3f oldAngles = rotation.getEulerAnglesYXZ(new Vector3f()); - this.vsch$setRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -super.getYRot(), Mth.DEG_TO_RAD * super.getXRot(), oldAngles.z)); + this.vsch$setBodyRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -super.getYRot(), Mth.DEG_TO_RAD * super.getXRot(), oldAngles.z)); + this.headPitch = 0; + this.headYaw = 0; + this.reCalcHeadRotation(); } @Unique @@ -242,11 +328,17 @@ public void readAdditionalSaveData(final CompoundTag data, final CallbackInfo ci } this.rotationO.set(this.rotation); } + this.headPitchO = this.headPitch = data.getFloat("HeadPitch"); + this.headYawO = this.headYaw = data.getFloat("HeadYaw"); + this.reCalcHeadRotation(); + this.reCalcHeadRotationO(); } @Inject(method = "addAdditionalSaveData", at = @At("RETURN")) public void addAdditionalSaveData(final CompoundTag data, final CallbackInfo ci) { data.put("RotationQuat", this.newFloatList(this.rotation.x, this.rotation.y, this.rotation.z, this.rotation.w)); + data.putFloat("HeadPitch", this.headPitch); + data.putFloat("HeadYaw", this.headYaw); } @Override @@ -307,8 +399,15 @@ public void turn(final double x, final double y) { final boolean isClientSide = this.level().isClientSide; - final float roll; + float roll = 0; + boolean lockHeadRotate = false; if (isClientSide) { + if (VSCHKeyBindings.UNLOCK_HEAD_ROTATION.consumeDoubleClick()) { + this.headPitch = 0; + this.reCalcHeadRotation(); + return; + } + lockHeadRotate = !VSCHKeyBindings.UNLOCK_HEAD_ROTATION.isDown(); int rollDir = 0; if (VSCHKeyBindings.ROLL_CLOCKWISE.isDown()) { rollDir++; @@ -317,8 +416,6 @@ public void turn(final double x, final double y) { rollDir--; } roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * ClientEvents.getSpf() * Mth.DEG_TO_RAD; - } else { - roll = 0; } if (x == 0 && y == 0 && roll == 0) { @@ -327,10 +424,37 @@ public void turn(final double x, final double y) { final float yaw = -(float)(x) * 0.15f * Mth.DEG_TO_RAD; final float pitch = (float)(y) * 0.15f * Mth.DEG_TO_RAD; - final Quaternionf rotation = new Quaternionf().rotateYXZ(yaw, pitch, roll); + final Quaternionf relRotation = new Quaternionf(); + float newHeadYaw = this.headYaw + yaw; + if (newHeadYaw > BODY_ROT_CLAMP) { + relRotation.rotationY(newHeadYaw - BODY_ROT_CLAMP); + newHeadYaw = BODY_ROT_CLAMP; + } else if (newHeadYaw < -BODY_ROT_CLAMP) { + relRotation.rotationY(newHeadYaw + BODY_ROT_CLAMP); + newHeadYaw = -BODY_ROT_CLAMP; + } + this.headYawO += newHeadYaw - this.headYaw; + this.headYaw = newHeadYaw; + if (lockHeadRotate) { + relRotation.rotateX(pitch); + } else { + float newHeadPitch = this.headPitch + pitch; + if (newHeadPitch > Mth.HALF_PI) { + relRotation.rotateX(newHeadPitch - Mth.HALF_PI); + newHeadPitch = Mth.HALF_PI; + } else if (newHeadPitch < -Mth.HALF_PI) { + relRotation.rotateX(newHeadPitch + Mth.HALF_PI); + newHeadPitch = -Mth.HALF_PI; + } + this.headPitchO += newHeadPitch - this.headPitch; + this.headPitch = newHeadPitch; + } + relRotation.rotateZ(roll); - this.vsch$setRotation(this.vsch$getRotation().mul(rotation).normalize()); - this.vsch$setRotationO(this.rotationO.mul(rotation).normalize()); + this.vsch$setBodyRotation(this.vsch$getBodyRotation().mul(relRotation).normalize()); + this.vsch$setBodyRotationO(this.rotationO.mul(relRotation).normalize()); + this.reCalcHeadRotation(); + this.reCalcHeadRotationO(); } @Override @@ -340,10 +464,12 @@ public void absMoveTo(final double x, final double y, final double z, final floa return; } this.absMoveTo(x, y, z); - super.setYRot(yRot % 360f); - super.setXRot(xRot % 360f); + // super.setYRot(yRot % 360f); + // super.setXRot(xRot % 360f); // this.reCalcRotation(); - this.vsch$setRotationO(this.vsch$getRotation()); + this.vsch$setBodyRotationO(this.vsch$getBodyRotation()); + this.reCalcHeadRotation(); + this.reCalcHeadRotationO(); } @Override @@ -362,14 +488,18 @@ public void moveTo(final double x, final double y, final double z, final float y @Override public void vsch$setOldPosAndRot() { - this.vsch$setRotationO(this.vsch$getRotation()); + this.vsch$setBodyRotationO(this.vsch$getBodyRotation()); this.headPitchO = this.headPitch; + this.headYawO = this.headYaw; + this.reCalcHeadRotationO(); } @Override public void vsch$stepLerp(final int steps) { - this.vsch$setRotation(this.vsch$getRotation().nlerp(this.rotationLerp, 1.0f / steps).normalize()); + this.vsch$setBodyRotation(this.vsch$getBodyRotation().nlerp(this.rotationLerp, 1.0f / steps).normalize()); this.headPitch += (this.headPitchLerp - this.headPitch) / steps; + this.headYaw += (this.headYawLerp - this.headYaw) / steps; + this.reCalcHeadRotation(); } @Override @@ -445,7 +575,15 @@ public void moveRelative(final float power, final Vec3 movement) { if (speed > 1) { move.normalize(); } - this.vsch$getRotation().transform(move.mul(power)); + this.vsch$getHeadRotation().transform(move.mul(power)); + final float yawSpeed = this.headYaw * 0.3f; + if (Math.abs(yawSpeed) < 1e-6) { + this.headYaw = 0; + } else { + this.headYaw -= yawSpeed; + this.vsch$setBodyRotation(this.vsch$getBodyRotation().rotateY(yawSpeed)); + this.reCalcHeadRotation(); + } this.setDeltaMovement(this.getDeltaMovement().add(move.x, move.y, move.z)); } @@ -615,7 +753,7 @@ private void updateParts() { this.setYBodyRot(this.getYHeadRot()); final float height = this.vsch$getVanillaDimensions(this.getPose()).height; final Vector3f feetPos = new Vector3f(0, SPACE_ENTITY_SIZE - height, 0); - feetPos.rotate(this.vsch$getRotation()); + feetPos.rotate(this.vsch$getBodyRotation()); this.updatePartPos(this.feetPart, feetPos.x, feetPos.y, feetPos.z); this.updatePartPos(this.chestPart, feetPos.x / 2, feetPos.y / 2, feetPos.z / 2); } @@ -643,7 +781,7 @@ private static void push3Dim(final Entity e1, final Entity e2) { e1.getZ() - e2.getZ() ); final double lengthSqr = movement.lengthSquared(); - if (lengthSqr < 0.0001) { + if (lengthSqr < 1e-4) { return; } movement.normalize(Math.min(1 / Math.sqrt(lengthSqr), 0.2)).mul(1.0 / 20); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java index ad450187..2eafe6e4 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerEntity.java @@ -25,8 +25,11 @@ public abstract class MixinServerEntity { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundTeleportEntityPacket;") ) public ClientboundTeleportEntityPacket new$ClientboundTeleportEntityPacket(final ClientboundTeleportEntityPacket packet) { - if (this.entity instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); + if (this.entity instanceof final FreeRotatePlayerAccessor frp) { + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(frp.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(frp.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(frp.vsch$getHeadYaw()); } return packet; } @@ -36,8 +39,11 @@ public abstract class MixinServerEntity { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundMoveEntityPacket$PosRot;") ) public ClientboundMoveEntityPacket.PosRot new$ClientboundMoveEntityPacket$PosRot(final ClientboundMoveEntityPacket.PosRot packet) { - if (this.entity instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); + if (this.entity instanceof final FreeRotatePlayerAccessor frp) { + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(frp.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(frp.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(frp.vsch$getHeadYaw()); } return packet; } @@ -47,8 +53,11 @@ public abstract class MixinServerEntity { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundMoveEntityPacket$Rot;") ) public ClientboundMoveEntityPacket.Rot new$ClientboundMoveEntityPacket$Rot(final ClientboundMoveEntityPacket.Rot packet) { - if (this.entity instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); + if (this.entity instanceof final FreeRotatePlayerAccessor frp) { + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(frp.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(frp.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(frp.vsch$getHeadYaw()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java index 22a6c78d..e83ec995 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java @@ -44,7 +44,10 @@ public abstract class MixinServerGamePacketListenerImpl { if (!packet.hasRotation()) { return false; } - return !((EntityRotationPacketAccessor)(packet)).vsch$rotation().isFinite(); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + return !packetAccessor.vsch$rotation().isFinite() || + !Double.isFinite(packetAccessor.vsch$getHeadPitch()) || + !Double.isFinite(packetAccessor.vsch$getHeadYaw()); } @Inject(method = "handleMovePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;wrapDegrees(F)F", ordinal = 0)) @@ -52,7 +55,10 @@ public abstract class MixinServerGamePacketListenerImpl { if (!packet.hasRotation() || !(this.player instanceof FreeRotatePlayerAccessor frp)) { return; } - frp.vsch$setRotation(((EntityRotationPacketAccessor)(packet)).vsch$rotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + frp.vsch$setBodyRotation(packetAccessor.vsch$rotation()); + frp.vsch$setHeadPitch(packetAccessor.vsch$getHeadPitch()); + frp.vsch$setHeadYaw(packetAccessor.vsch$getHeadYaw()); } @ModifyExpressionValue( @@ -61,7 +67,10 @@ public abstract class MixinServerGamePacketListenerImpl { ) public ClientboundPlayerPositionPacket teleport$new$ClientboundPlayerPositionPacket(final ClientboundPlayerPositionPacket packet) { if (this.player instanceof FreeRotatePlayerAccessor frp) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(frp.vsch$getRotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(frp.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(frp.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(frp.vsch$getHeadYaw()); } return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java index c3ec3786..957b5112 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerPlayer.java @@ -31,7 +31,10 @@ public abstract class MixinServerPlayer extends MixinPlayer { at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundAddPlayerPacket;") ) public ClientboundAddPlayerPacket new$ClientboundAddPlayerPacket(final ClientboundAddPlayerPacket packet) { - ((EntityRotationPacketAccessor)(packet)).vsch$rotation().set(this.vsch$getRotation()); + final EntityRotationPacketAccessor packetAccessor = ((EntityRotationPacketAccessor)(packet)); + packetAccessor.vsch$rotation().set(this.vsch$getBodyRotation()); + packetAccessor.vsch$setHeadPitch(this.vsch$getHeadPitch()); + packetAccessor.vsch$setHeadYaw(this.vsch$getHeadYaw()); return packet; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java index 572cad89..6ce03c4e 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_PosRot.java @@ -16,5 +16,7 @@ public abstract class MixinServerboundMovePlayerPacket_PosRot { private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); packet.vsch$rotation().set(buf.readQuaternion()).normalize(); + packet.vsch$setHeadPitch(buf.readFloat()); + packet.vsch$setHeadYaw(buf.readFloat()); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java index 50faa978..e7735f58 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerboundMovePlayerPacket_Rot.java @@ -16,5 +16,7 @@ public abstract class MixinServerboundMovePlayerPacket_Rot { private static void read(final FriendlyByteBuf buf, final CallbackInfoReturnable cir) { final EntityRotationPacketAccessor packet = (EntityRotationPacketAccessor)(cir.getReturnValue()); packet.vsch$rotation().set(buf.readQuaternion()).normalize(); + packet.vsch$setHeadPitch(buf.readFloat()); + packet.vsch$setHeadYaw(buf.readFloat()); } } From e05b3feedd7a3df7a63a2574a0e74fbd97205930 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Sun, 21 Sep 2025 15:28:00 -0600 Subject: [PATCH 22/32] trying collision --- .../accessor/FreeRotatePlayerAccessor.java | 2 + .../net/jcm/vsch/client/ClientEvents.java | 18 +- .../net/jcm/vsch/client/VSCHKeyBindings.java | 2 +- .../vsch/entity/player/MultiPartPlayer.java | 9 + .../jcm/vsch/items/custom/MagnetBootItem.java | 73 +++- .../vsch/mixin/client/MixinLocalPlayer.java | 7 + .../jcm/vsch/mixin/minecraft/MixinEntity.java | 23 +- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 398 +++++++++++++++--- .../MixinEntityShipCollisionUtils.java | 39 ++ .../java/net/jcm/vsch/util/CollisionUtil.java | 152 +++++++ src/main/resources/vsch.mixins.json | 1 + 11 files changed, 633 insertions(+), 91 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityShipCollisionUtils.java create mode 100644 src/main/java/net/jcm/vsch/util/CollisionUtil.java diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 2c69bcf1..92cf3ef3 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -12,6 +12,8 @@ public interface FreeRotatePlayerAccessor { boolean vsch$isFreeRotating(); + Vec3 vsch$getHeadCenter(); + Vec3 vsch$getFeetPosition(); default Vec3 vsch$getDownVector() { diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index 22b1ff08..c14d1498 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -9,6 +9,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import org.joml.Quaternionf; import org.joml.Vector3f; @Mod.EventBusSubscriber @@ -18,9 +19,20 @@ public final class ClientEvents { @SubscribeEvent public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event) { final Camera camera = event.getCamera(); - if (camera.getEntity() instanceof final FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { - camera.rotation().getEulerAnglesYXZ(CAMERA_ROT_VEC); - event.setPitch(CAMERA_ROT_VEC.x * Mth.RAD_TO_DEG); + if (!(camera.getEntity() instanceof final FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return; + } + final Quaternionf rotation = camera.rotation(); + rotation.getEulerAnglesYXZ(CAMERA_ROT_VEC); + event.setPitch(CAMERA_ROT_VEC.x * Mth.RAD_TO_DEG); + if (Math.abs(Math.abs(CAMERA_ROT_VEC.x) - Mth.HALF_PI) < 0.025f) { + // Lovely gimbal lock + event.setYaw((float) (-Math.toDegrees(Math.atan2( + -2 * (rotation.x * rotation.z - rotation.y * rotation.w), + 1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z) + )))); + event.setRoll(0); + } else { event.setYaw(-CAMERA_ROT_VEC.y * Mth.RAD_TO_DEG); event.setRoll(CAMERA_ROT_VEC.z * Mth.RAD_TO_DEG); } diff --git a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java index 9f5e29f2..2719ea17 100644 --- a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java +++ b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java @@ -34,7 +34,7 @@ public final class VSCHKeyBindings { "key." + VSCHMod.MODID + ".unlock_head_rotation", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, - GLFW.GLFW_KEY_LEFT_SUPER, + GLFW.GLFW_KEY_LEFT_ALT, "key.categories.movement" ); diff --git a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java index c639991a..b582a076 100644 --- a/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java +++ b/src/main/java/net/jcm/vsch/entity/player/MultiPartPlayer.java @@ -7,6 +7,7 @@ import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -18,6 +19,7 @@ public class MultiPartPlayer extends PartEntity { private final EntityDimensions size; private final boolean isFeet; + public boolean testing = false; public MultiPartPlayer(final Player parent, final float size, final boolean isFeet) { super(parent); @@ -164,6 +166,13 @@ public float maxUpStep() { return this.getParent().maxUpStep(); } + @Override + public void move(final MoverType moverType, final Vec3 movement) { + if (this.isAlive()) { + this.getParent().move(moverType, movement); + } + } + @Override public void baseTick() { this.firstTick = false; diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index 8135c154..e40de931 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -10,7 +10,9 @@ import net.minecraft.nbt.DoubleTag; import net.minecraft.nbt.NbtOps; import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ArmorItem; @@ -18,11 +20,14 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; +import org.joml.Matrix3f; import org.joml.Quaternionf; +import org.joml.Quaternionfc; +import org.joml.Vector3f; import org.valkyrienskies.core.api.ships.Ship; import org.valkyrienskies.mod.common.VSGameUtilsKt; @@ -44,27 +49,40 @@ public double getMaxForce() { return VSCHConfig.MAGNET_BOOT_MAX_FORCE.get().doubleValue(); } - public boolean getEnabled(ItemStack stack) { + public boolean getEnabled(final ItemStack stack) { if (!(stack.getItem() instanceof MagnetBootItem)) { return false; } - CompoundTag tag = stack.getTag(); + final CompoundTag tag = stack.getTag(); return tag == null || !tag.getBoolean(TAG_DISABLED); } - public boolean getReady(ItemStack stack) { + public boolean getReady(final ItemStack stack) { if (!(stack.getItem() instanceof MagnetBootItem)) { return false; } - CompoundTag tag = stack.getTag(); + final CompoundTag tag = stack.getTag(); return tag != null && tag.getBoolean(TAG_READY); } - public Vec3 getDirection(ItemStack stack) { + public boolean isWorking(final ItemStack stack) { + if (!(stack.getItem() instanceof MagnetBootItem)) { + return false; + } + final CompoundTag tag = stack.getTag(); + return tag != null && !tag.getBoolean(TAG_DISABLED) && tag.getBoolean(TAG_READY); + } + + public static boolean isMagnetized(final LivingEntity entity) { + final ItemStack stack = entity.getItemBySlot(EquipmentSlot.FEET); + return !stack.isEmpty() && stack.getItem() instanceof final MagnetBootItem boot && boot.isWorking(stack); + } + + public Vec3 getDirection(final ItemStack stack) { if (!(stack.getItem() instanceof MagnetBootItem)) { return null; } - CompoundTag tag = stack.getTag(); + final CompoundTag tag = stack.getTag(); if (tag == null) { return null; } @@ -88,15 +106,14 @@ public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, return; } - CompoundTag tag = stack.getOrCreateTag(); - boolean disabled = tag.getBoolean(TAG_DISABLED); - boolean wasReady = tag.getBoolean(TAG_READY); + final CompoundTag tag = stack.getOrCreateTag(); + final boolean disabled = tag.getBoolean(TAG_DISABLED); + final boolean wasReady = tag.getBoolean(TAG_READY); - double maxDistance = getAttractDistance(); + final double maxDistance = this.getAttractDistance(); Vec3 startPos = entity.position(); // Starting position (player's feet position) Vec3 direction = new Vec3(0, -1, 0); - // TODO: maybe we can change the direction to match the ship that player stands on? if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { startPos = frp.vsch$getFeetPosition(); direction = frp.vsch$getDownVector(); @@ -107,8 +124,8 @@ public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, final HitResult hitResult = level.clip(new ClipContext( startPos, endPos, - ClipContext.Block.COLLIDER, // Raycast considers block collision shapes, maybe we don't want this? - ClipContext.Fluid.NONE, // Ignore fluids + ClipContext.Block.COLLIDER, + ClipContext.Fluid.NONE, entity )); @@ -140,15 +157,35 @@ public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { final BlockPos blockPos = blockHit.getBlockPos(); - final Direction face = blockHit.getDirection(); - final Quaternionf destRot = new Quaternionf().rotateTo(0, 1, 0, face.getStepX(), face.getStepY(), face.getStepZ()); + final Quaternionf destRot = blockHit.getDirection().getRotation(); final Ship ship = VSGameUtilsKt.getShipManagingPos(level, blockPos); if (ship != null) { - new Quaternionf().setFromNormalized(ship.getShipToWorld()).mul(destRot, destRot); + destRot.premul(new Quaternionf().setFromNormalized(ship.getShipToWorld())); } - frp.vsch$setRotation(frp.vsch$getRotation().slerp(destRot, 0.2f)); + final Quaternionf rotation = frp.vsch$getBodyRotation(); + frp.vsch$setBodyRotation(rotateTowards(rotation, destRot)); } //level.addParticle(ParticleTypes.HEART, player.getX(), player.getY(), player.getZ(), 0, 0, 0); } + + private static Quaternionf rotateTowards(final Quaternionf current, final Quaternionfc target) { + final Quaternionf proj = projectedRotation(current, target); + if (proj.conjugate(new Quaternionf()).mul(current).angle() < 0.025f) { + return current.set(proj); + } + return current.slerp(proj, 0.15f); + } + + private static Quaternionf projectedRotation(final Quaternionfc current, final Quaternionfc target) { + final Vector3f up = target.transform(new Vector3f(0, 1, 0)); + final Vector3f forward = current.transform(new Vector3f(0, 0, -1)); + final Vector3f forwardProj = forward.fma(-forward.dot(up), up, new Vector3f()); + if (forwardProj.lengthSquared() < 1e-6) { + forwardProj.set(up.x, up.y, 0); + } + forwardProj.normalize(); + final Vector3f right = forwardProj.cross(up, new Vector3f()).normalize(); + return new Quaternionf().setFromNormalized(new Matrix3f(right, up, forwardProj.negate())); + } } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 470d0416..25cfae58 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -69,4 +69,11 @@ public void serverAiStep(final CallbackInfo ci) { } this.yya = (this.input.jumping ? 1 : 0) + (this.input.shiftKeyDown ? -1 : 0); } + + @Inject(method = "moveTowardsClosestSpace", at = @At("HEAD"), cancellable = true) + private void moveTowardsClosestSpace(final double x, final double z, final CallbackInfo ci) { + if (this.vsch$isFreeRotating()) { + ci.cancel(); + } + } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java index a54c2d45..69970c35 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinEntity.java @@ -4,20 +4,15 @@ import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; -import net.minecraftforge.entity.PartEntity; - -import org.valkyrienskies.mod.common.util.EntityShipCollisionUtils; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(Entity.class) public abstract class MixinEntity implements EntityAccessor { @@ -48,25 +43,9 @@ public abstract class MixinEntity implements EntityAccessor { this.onInsideBlock(block); } - @Inject(method = "collide", at = @At("RETURN"), cancellable = true) - private void collide(final Vec3 originMovement, final CallbackInfoReturnable cir) { - if (!(((Object)(this)) instanceof Player player) || !(player instanceof FreeRotatePlayerAccessor frp)) { - return; - } - if (!frp.vsch$isFreeRotating()) { - return; - } - Vec3 movement = cir.getReturnValue(); - for (PartEntity part : player.getParts()) { - movement = EntityShipCollisionUtils.INSTANCE.adjustEntityMovementForShipCollisions(part, movement, part.getBoundingBox(), this.level()); - movement = ((EntityAccessor)(part)).vsch$collide(movement); - } - cir.setReturnValue(movement); - } - @Inject(method = "setOldPosAndRot", at = @At("RETURN")) private void setOldPosAndRot(final CallbackInfo ci) { - if (((Object)(this)) instanceof FreeRotatePlayerAccessor frp) { + if (((Object)(this)) instanceof final FreeRotatePlayerAccessor frp) { frp.vsch$setOldPosAndRot(); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 9fa59241..ab9f405c 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -7,7 +7,9 @@ import net.jcm.vsch.config.VSCHClientConfig; import net.jcm.vsch.config.VSCHConfig; import net.jcm.vsch.entity.player.MultiPartPlayer; +import net.jcm.vsch.items.custom.MagnetBootItem; import net.jcm.vsch.util.BooleanRef; +import net.jcm.vsch.util.CollisionUtil; import net.jcm.vsch.util.VSCHUtils; import net.jcm.vsch.util.wapi.LevelData; @@ -19,6 +21,7 @@ import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.tags.BlockTags; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; @@ -29,16 +32,22 @@ import net.minecraft.world.entity.player.Abilities; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.scores.Team; +import org.joml.Matrix4d; +import org.joml.Matrix4dc; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; +import org.joml.primitives.AABBd; +import org.valkyrienskies.core.api.ships.LoadedShip; import org.valkyrienskies.mod.common.VSGameUtilsKt; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; @@ -55,6 +64,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Predicate; @Mixin(Player.class) @@ -66,6 +76,8 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay @Unique private static final float SPACE_ENTITY_SIZE = 0.6f; @Unique + private static final float HALF_SPACE_ENTITY_SIZE = SPACE_ENTITY_SIZE / 2; + @Unique private static final EntityDimensions SPACE_ENTITY_DIM = EntityDimensions.scalable(SPACE_ENTITY_SIZE, SPACE_ENTITY_SIZE); @Unique private static final double SUPPORT_CHECK_DISTANCE = 0.1; @@ -106,6 +118,10 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay private MultiPartPlayer feetPart; @Unique private Pose oldPose; + @Unique + private int jumpCD = 0; + @Unique + private float nextStep = 0; protected MixinPlayer() { super(null, null); @@ -114,6 +130,9 @@ protected MixinPlayer() { @Shadow public abstract Abilities getAbilities(); + @Shadow + protected abstract boolean isStayingOnGroundSurface(); + @Inject(method = "", at = @At("RETURN")) public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { final Player player = (Player)((Object)(this)); @@ -144,8 +163,11 @@ public MultiPartPlayer[] getParts() { return !this.firstTick && this.entityData.get(FREE_ROTATION_ID); } - @Unique - private Vec3 getFreeHeadCenter() { + @Override + public Vec3 vsch$getHeadCenter() { + if (!this.vsch$isFreeRotating()) { + return this.getEyePosition(); + } return this.position().add(0, SPACE_ENTITY_SIZE / 2, 0); } @@ -154,7 +176,7 @@ private Vec3 getFreeHeadCenter() { if (!this.vsch$isFreeRotating()) { return this.position(); } - return this.getFreeHeadCenter().add(this.vsch$getDownVector().scale(SPACE_ENTITY_SIZE * 2)); + return this.vsch$getHeadCenter().add(this.vsch$getDownVector().scale(SPACE_ENTITY_SIZE * 2)); } @Override @@ -172,6 +194,7 @@ private Vec3 getFreeHeadCenter() { } final Vector3f angles = this.rotation.getEulerAnglesYXZ(new Vector3f()); this.yBodyRot = -this.rotation.getEulerAnglesYXZ(new Vector3f()).y * Mth.RAD_TO_DEG; + this.reCalcHeadRotation(); } @Override @@ -190,6 +213,7 @@ private Vec3 getFreeHeadCenter() { final Vector3f angles = this.rotationO.getEulerAnglesYXZ(new Vector3f()); this.xRotO = angles.x * Mth.RAD_TO_DEG; this.yBodyRotO = -angles.y * Mth.RAD_TO_DEG; + this.reCalcHeadRotationO(); } @Override @@ -309,6 +333,12 @@ public float getViewYRot(final float partialTick) { this.headYawLerp = yaw; } + @Unique + private Vector3d vsch$getRelativeDeltaMovement() { + final Vec3 movement = this.getDeltaMovement(); + return this.vsch$getBodyRotation().transformInverse(new Vector3d(movement.x, movement.y, movement.z)); + } + @Unique private void reCalcRotation() { if (this.firstTick || !this.vsch$isFreeRotating()) { @@ -316,10 +346,9 @@ private void reCalcRotation() { } final Quaternionf rotation = this.vsch$getBodyRotation(); final Vector3f oldAngles = rotation.getEulerAnglesYXZ(new Vector3f()); - this.vsch$setBodyRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -super.getYRot(), Mth.DEG_TO_RAD * super.getXRot(), oldAngles.z)); this.headPitch = 0; this.headYaw = 0; - this.reCalcHeadRotation(); + this.vsch$setBodyRotation(rotation.rotationYXZ(Mth.DEG_TO_RAD * -super.getYRot(), Mth.DEG_TO_RAD * super.getXRot(), oldAngles.z)); } @Unique @@ -341,7 +370,7 @@ public void readAdditionalSaveData(final CompoundTag data, final CallbackInfo ci } this.rotationO.set(this.rotation); } - this.headPitchO = this.headPitch = data.getFloat("HeadPitch"); + this.headPitchO = this.headPitch = 0; this.headYawO = this.headYaw = data.getFloat("HeadYaw"); this.reCalcHeadRotation(); this.reCalcHeadRotationO(); @@ -350,7 +379,6 @@ public void readAdditionalSaveData(final CompoundTag data, final CallbackInfo ci @Inject(method = "addAdditionalSaveData", at = @At("RETURN")) public void addAdditionalSaveData(final CompoundTag data, final CallbackInfo ci) { data.put("RotationQuat", this.newFloatList(this.rotation.x, this.rotation.y, this.rotation.z, this.rotation.w)); - data.putFloat("HeadPitch", this.headPitch); data.putFloat("HeadYaw", this.headYaw); } @@ -403,6 +431,11 @@ public boolean onGround() { return this.onGround(); } + @Unique + private boolean isBodyRotationLocked() { + return MagnetBootItem.isMagnetized(this); + } + @Override public void turn(final double x, final double y) { if (!this.vsch$isFreeRotating()) { @@ -414,21 +447,24 @@ public void turn(final double x, final double y) { float roll = 0; boolean lockHeadRotate = false; + final boolean lockBodyRotate = this.isBodyRotationLocked(); if (isClientSide) { if (VSCHKeyBindings.UNLOCK_HEAD_ROTATION.consumeDoubleClick()) { this.headPitch = 0; this.reCalcHeadRotation(); return; } - lockHeadRotate = !VSCHKeyBindings.UNLOCK_HEAD_ROTATION.isDown(); - int rollDir = 0; - if (VSCHKeyBindings.ROLL_CLOCKWISE.isDown()) { - rollDir++; - } - if (VSCHKeyBindings.ROLL_COUNTER_CLOCKWISE.isDown()) { - rollDir--; + if (!lockBodyRotate) { + lockHeadRotate = !VSCHKeyBindings.UNLOCK_HEAD_ROTATION.isDown(); + int rollDir = 0; + if (VSCHKeyBindings.ROLL_CLOCKWISE.isDown()) { + rollDir++; + } + if (VSCHKeyBindings.ROLL_COUNTER_CLOCKWISE.isDown()) { + rollDir--; + } + roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * ClientEvents.getSpf() * Mth.DEG_TO_RAD; } - roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * ClientEvents.getSpf() * Mth.DEG_TO_RAD; } if (x == 0 && y == 0 && roll == 0) { @@ -449,25 +485,31 @@ public void turn(final double x, final double y) { this.headYawO += newHeadYaw - this.headYaw; this.headYaw = newHeadYaw; if (lockHeadRotate) { - relRotation.rotateX(pitch); + if (!lockBodyRotate) { + relRotation.rotateX(pitch); + } } else { float newHeadPitch = this.headPitch + pitch; if (newHeadPitch > Mth.HALF_PI) { - relRotation.rotateX(newHeadPitch - Mth.HALF_PI); + if (!lockBodyRotate) { + relRotation.rotateX(newHeadPitch - Mth.HALF_PI); + } newHeadPitch = Mth.HALF_PI; } else if (newHeadPitch < -Mth.HALF_PI) { - relRotation.rotateX(newHeadPitch + Mth.HALF_PI); + if (!lockBodyRotate) { + relRotation.rotateX(newHeadPitch + Mth.HALF_PI); + } newHeadPitch = -Mth.HALF_PI; } this.headPitchO += newHeadPitch - this.headPitch; this.headPitch = newHeadPitch; } - relRotation.rotateZ(roll); + if (!lockBodyRotate) { + relRotation.rotateZ(roll); + } this.vsch$setBodyRotation(this.vsch$getBodyRotation().mul(relRotation).normalize()); this.vsch$setBodyRotationO(this.rotationO.mul(relRotation).normalize()); - this.reCalcHeadRotation(); - this.reCalcHeadRotationO(); } @Override @@ -481,8 +523,6 @@ public void absMoveTo(final double x, final double y, final double z, final floa // super.setXRot(xRot % 360f); // this.reCalcRotation(); this.vsch$setBodyRotationO(this.vsch$getBodyRotation()); - this.reCalcHeadRotation(); - this.reCalcHeadRotationO(); } @Override @@ -501,18 +541,16 @@ public void moveTo(final double x, final double y, final double z, final float y @Override public void vsch$setOldPosAndRot() { - this.vsch$setBodyRotationO(this.vsch$getBodyRotation()); this.headPitchO = this.headPitch; this.headYawO = this.headYaw; - this.reCalcHeadRotationO(); + this.vsch$setBodyRotationO(this.vsch$getBodyRotation()); } @Override public void vsch$stepLerp(final int steps) { - this.vsch$setBodyRotation(this.vsch$getBodyRotation().nlerp(this.rotationLerp, 1.0f / steps).normalize()); this.headPitch += (this.headPitchLerp - this.headPitch) / steps; this.headYaw += (this.headYawLerp - this.headYaw) / steps; - this.reCalcHeadRotation(); + this.vsch$setBodyRotation(this.vsch$getBodyRotation().nlerp(this.rotationLerp, 1.0f / steps).normalize()); } @Override @@ -549,9 +587,37 @@ public boolean shouldDiscardFriction() { @Override protected void checkInsideBlocks() { - super.checkInsideBlocks(); - ((EntityAccessor)(this.chestPart)).vsch$checkInsideBlocks(); - ((EntityAccessor)(this.feetPart)).vsch$checkInsideBlocks(); + if (!this.vsch$isFreeRotating()) { + super.checkInsideBlocks(); + return; + } + } + + @Override + protected void checkSupportingBlock(final boolean onGround, final Vec3 movement) { + if (!onGround) { + super.checkSupportingBlock(onGround, movement); + return; + } + final AABB bb = this.getBoundingBox(); + final AABB movedBB = movement == null + ? bb + : bb.expandTowards(Math.signum(movement.x) * 1e-6, Math.signum(movement.y) * 1e-6, Math.signum(movement.z) * 1e-6); + Optional supportingBlockPos = this.level().findSupportingBlock(this, movedBB); + if (!supportingBlockPos.isPresent() && movement != null) { + supportingBlockPos = this.level().findSupportingBlock(this, movedBB.move(-movement.x, -movement.y, -movement.z)); + } + this.mainSupportingBlockPos = supportingBlockPos; + } + + @Override + protected BlockPos getOnPos(final float dist) { + final BlockPos mainSupportingBlockPos = this.mainSupportingBlockPos.orElse(null); + if (mainSupportingBlockPos != null) { + return mainSupportingBlockPos; + } + final Vector3d rel = this.vsch$getBodyRotation().transform(new Vector3d(0, -HALF_SPACE_ENTITY_SIZE / 2 - dist, 0)); + return BlockPos.containing(this.vsch$getFeetPosition().add(rel.x, rel.y, rel.z)); } @Override @@ -575,31 +641,241 @@ public boolean isInWaterOrBubble() { } @Override - public void moveRelative(final float power, final Vec3 movement) { + public boolean isFallFlying() { + return this.vsch$isFreeRotating() ? false : super.isFallFlying(); + } + + @Override + public Vec3 handleRelativeFrictionAndCalculateMovement(final Vec3 movement, final float friction) { if (!this.vsch$isFreeRotating()) { - super.moveRelative(power, movement); - return; + return super.handleRelativeFrictionAndCalculateMovement(movement, friction); } + final float power = this.getFlyingSpeed(); final double speed = movement.lengthSqr(); - if (speed < 1.0e-7) { + if (speed > 1e-8) { + final Vector3d move = new Vector3d(movement.x, movement.y, movement.z); + if (speed > 1) { + move.normalize(); + } + final float yawSpeed = this.headYaw * 0.3f; + if (Math.abs(yawSpeed) < 1e-6) { + this.headYaw = 0; + } else { + this.headYaw -= yawSpeed; + this.vsch$setBodyRotation(this.vsch$getBodyRotation().rotateY(yawSpeed)); + } + final boolean bodyLocked = this.isBodyRotationLocked(); + final Quaternionf rotation = bodyLocked + ? (this.headYaw == 0 ? this.vsch$getBodyRotation() : this.vsch$getBodyRotation().rotateY(this.headYaw, new Quaternionf())) + : this.vsch$getHeadRotation(); + move.mul(power); + rotation.transform(move); + this.setDeltaMovement(this.getDeltaMovement().add(move.x, move.y, move.z)); + if (bodyLocked && this.jumping && !this.isStayingOnGroundSurface() /*&& this.onGround()*/ && this.jumpCD == 0) { + final Vec3 dm0 = this.getDeltaMovement(); + final Vector3d dm = rotation.transformInverse(new Vector3d(dm0.x, dm0.y, dm0.z)); + dm.y = this.getJumpPower(); + rotation.transform(dm); + this.setDeltaMovement(new Vec3(dm.x, dm.y, dm.z)); + this.jumpCD = 10; + } + } + // this.setDeltaMovement(this.handleOnClimbable(this.getDeltaMovement())); + this.move(MoverType.SELF, this.getDeltaMovement()); + return this.getDeltaMovement(); + } + + @Override + public void move(final MoverType moverType, Vec3 movement) { + if (!this.vsch$isFreeRotating()) { + super.move(moverType, movement); return; } - final Vector3d move = new Vector3d(movement.x, movement.y, movement.z); - if (speed > 1) { - move.normalize(); + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + return; } - this.vsch$getHeadRotation().transform(move.mul(power)); - final float yawSpeed = this.headYaw * 0.3f; - if (Math.abs(yawSpeed) < 1e-6) { - this.headYaw = 0; + + this.wasOnFire = this.isOnFire(); + + if (moverType == MoverType.PISTON) { + movement = this.limitPistonMovement(movement); + if (movement.equals(Vec3.ZERO)) { + return; + } + } + + System.out.println("moving: " + moverType + " : " + movement); + if (this.stuckSpeedMultiplier.lengthSqr() > 1e-7) { + movement = movement.multiply(this.stuckSpeedMultiplier); + this.stuckSpeedMultiplier = Vec3.ZERO; + this.setDeltaMovement(Vec3.ZERO); + } + movement = this.maybeBackOffFromEdge(movement, moverType); + + final Quaternionf rotation = this.vsch$getBodyRotation(); + final Vector3d movementWill = rotation.transformInverse(new Vector3d(movement.x, movement.y, movement.z)); + final Vec3 movementWillVec = new Vec3(movementWill.x, movementWill.y, movementWill.z); + movement = this.collide(movement); + final Vector3d movementActual = rotation.transformInverse(new Vector3d(movement.x, movement.y, movement.z)); + final double movedDist = movement.length(); + if (movedDist > 1e-4) { + // TODO: reset fallDistance + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } + final boolean collideX = Math.abs(movementWill.x - movementActual.x) > 1e-5; + final boolean collideZ = Math.abs(movementWill.z - movementActual.z) > 1e-5; + final boolean collideY = Math.abs(movementWill.y - movementActual.y) > 1e-6; + this.horizontalCollision = collideX || collideZ; + this.verticalCollision = collideY; + this.verticalCollisionBelow = collideY && movementWill.y < 0; + // TODO: detect minorHorizontalCollision + this.minorHorizontalCollision = this.horizontalCollision && false; + this.setOnGroundWithKnownMovement(this.verticalCollisionBelow, movement); + final BlockPos groundPos = this.getOnPosLegacy(); + final BlockState groundState = this.level().getBlockState(groundPos); + this.checkFallDamage(movementActual.y, this.onGround(), groundState, groundPos); + if (this.isRemoved()) { + return; + } + if (this.horizontalCollision) { + final Vector3d dm = this.vsch$getRelativeDeltaMovement(); + if (collideX) { + dm.x = 0; + } + if (collideZ) { + dm.z = 0; + } + rotation.transform(dm); + this.setDeltaMovement(dm.x, dm.y, dm.z); + } + if (collideY) { + // TODO: Block.updateEntityAfterFallOn + } + if (this.onGround()) { + // TODO: Block.stepOn + } + final Entity.MovementEmission movementEmission = this.getMovementEmission(); + if (movementEmission.emitsAnything() && !this.isPassenger()) { + // TODO + final float scaledMoveDist = (float) (movedDist * 0.6); + this.flyDist += scaledMoveDist; + this.walkDist += scaledMoveDist; + this.moveDist += scaledMoveDist; + final BlockPos steppingPos = this.getOnPos(); + final BlockState stepping = this.level().getBlockState(steppingPos); + if (!stepping.isAir() && this.moveDist > this.nextStep) { + final boolean steppingOnGround = steppingPos.equals(groundPos); + boolean stepped = this.vibrationAndSoundEffectsFromBlock(groundPos, groundState, movementEmission.emitsSounds(), steppingOnGround, movementWillVec); + if (!steppingOnGround) { + stepped |= this.vibrationAndSoundEffectsFromBlock(steppingPos, stepping, false, movementEmission.emitsEvents(), movementWillVec); + } + if (stepped) { + this.nextStep = this.moveDist + 1; + } else if (this.isInWater()) { + this.nextStep = this.moveDist + 1; + if (movementEmission.emitsSounds()) { + this.waterSwimSound(); + } + if (movementEmission.emitsEvents()) { + this.gameEvent(GameEvent.SWIM); + } + } + } + } + this.tryCheckInsideBlocks(); + if ( + this.level().getBlockStatesIfLoaded(this.getBoundingBox().deflate(1e-6)) + .noneMatch((state) -> state.is(BlockTags.FIRE) || state.is(Blocks.LAVA)) + ) { + if (this.getRemainingFireTicks() <= 0) { + this.setRemainingFireTicks(-this.getFireImmuneTicks()); + } + if (this.wasOnFire && (this.isInPowderSnow || this.isInWaterRainOrBubble())) { + this.playEntityOnFireExtinguishedSound(); + } + } + if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) { + this.setRemainingFireTicks(-this.getFireImmuneTicks()); + } + } + + @Unique + private Vec3 collide(Vec3 movement) { + final Level level = this.level(); + System.out.println("colliding: " + movement); + final Vec3 position = this.vsch$getHeadCenter(); + final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); + final AABBd box = new AABBd( + -dimensions.width / 2, 0.6 / 2 - dimensions.height, -dimensions.width / 2, + dimensions.width / 2, 0.6 / 2, dimensions.width / 2 + ); + final Matrix4dc entityToWorld = new Matrix4d() + .translation(position.x, position.y, position.z) + .rotate(this.vsch$getBodyRotation()); + final Matrix4dc worldToEntity = entityToWorld.invert(new Matrix4d()); + final Vector3d newMovement = new Vector3d(movement.x, movement.y, movement.z); + worldToEntity.transformDirection(newMovement); + if (newMovement.x < 0) { + box.minX += newMovement.x; } else { - this.headYaw -= yawSpeed; - this.vsch$setBodyRotation(this.vsch$getBodyRotation().rotateY(yawSpeed)); - this.reCalcHeadRotation(); + box.maxX += newMovement.x; } - this.setDeltaMovement(this.getDeltaMovement().add(move.x, move.y, move.z)); + if (newMovement.y < 0) { + box.minY += newMovement.y; + } else { + box.maxY += newMovement.y; + } + if (newMovement.z < 0) { + box.minZ += newMovement.z; + } else { + box.maxZ += newMovement.z; + } + final AABBd worldBox = box.transform(entityToWorld, new AABBd()); + System.out.println("box: " + box + " -> " + worldBox); + + CollisionUtil.checkCollision(newMovement, level, this, box, worldToEntity, entityToWorld); + + final Matrix4d entityToShip = new Matrix4d(); + final Matrix4d shipToEntity = new Matrix4d(); + final String dimId = VSGameUtilsKt.getDimensionId(level); + for (final LoadedShip ship : VSGameUtilsKt.getShipObjectWorld(level).getLoadedShips()) { + if (!ship.getChunkClaimDimension().equals(dimId)) { + continue; + } + if (!ship.getWorldAABB().intersectsAABB(worldBox)) { + continue; + } + entityToShip.set(ship.getWorldToShip()).mul(entityToWorld); + shipToEntity.set(worldToEntity).mul(ship.getShipToWorld()); + CollisionUtil.checkCollision(newMovement, level, this, box, shipToEntity, entityToShip); + } + + if (Math.abs(newMovement.x) < 1e-7) { + newMovement.x = 0; + } + if (Math.abs(newMovement.y) < 1e-7) { + newMovement.y = 0; + } + if (Math.abs(newMovement.z) < 1e-7) { + newMovement.z = 0; + } + entityToWorld.transformDirection(newMovement); + if (Math.abs(newMovement.x) < 1e-7) { + newMovement.x = 0; + } + if (Math.abs(newMovement.y) < 1e-7) { + newMovement.y = 0; + } + if (Math.abs(newMovement.z) < 1e-7) { + newMovement.z = 0; + } + movement = new Vec3(newMovement.x, newMovement.y, newMovement.z); + System.out.println("final movement: " + movement); + return movement; } + @Override protected void checkFallDamage(final double dy, final boolean onGround, final BlockState block, final BlockPos pos) { if (!this.vsch$isFreeRotating()) { @@ -701,7 +977,7 @@ protected void pushEntities() { return selfTeamRule != Team.CollisionRule.PUSH_OTHER_TEAMS && teamRule != Team.CollisionRule.PUSH_OTHER_TEAMS; }); // if (level.isClientSide()) { - // selector.and((e) -> e instanceof Player || e instanceof MultiPartPlayer); + // selector = selector.and((e) -> e instanceof Player || e instanceof MultiPartPlayer); // } for (final Entity part : new Entity[]{this, this.chestPart, this.feetPart}) { level.getEntities( @@ -718,6 +994,9 @@ public void baseTick() { if (this.firstTick) { this.updateDefaultFreeRotation(); } + if (this.jumpCD > 0) { + this.jumpCD--; + } super.baseTick(); final boolean freeRotation = this.vsch$isFreeRotating(); if (freeRotation != this.wasFreeRotating) { @@ -763,7 +1042,6 @@ public void baseTick() { @Unique private void updateParts() { - this.setYBodyRot(this.getYHeadRot()); final float height = this.vsch$getVanillaDimensions(this.getPose()).height; final Vector3f feetPos = new Vector3f(0, SPACE_ENTITY_SIZE - height, 0); feetPos.rotate(this.vsch$getBodyRotation()); @@ -805,4 +1083,30 @@ private static void push3Dim(final Entity e1, final Entity e2) { e2.push(-movement.x, -movement.y, -movement.z); } } + + @Unique + private static boolean isStateClimbable(final BlockState state) { + return state.is(BlockTags.CLIMBABLE) || state.is(Blocks.POWDER_SNOW); + } + + @Unique + private boolean vibrationAndSoundEffectsFromBlock( + final BlockPos pos, final BlockState state, + final boolean playSound, final boolean emitEvent, + final Vec3 movement + ) { + if (state.isAir()) { + return false; + } + if (this.onGround() || isStateClimbable(state) || this.isCrouching() && Math.abs(movement.y) < 1e-6 || this.isOnRails()) { + if (playSound) { + this.playStepSound(pos, state); + } + if (emitEvent) { + this.level().gameEvent(GameEvent.STEP, this.position(), GameEvent.Context.of(this, state)); + } + return true; + } + return false; + } } diff --git a/src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityShipCollisionUtils.java b/src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityShipCollisionUtils.java new file mode 100644 index 00000000..60a8c4a9 --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityShipCollisionUtils.java @@ -0,0 +1,39 @@ +package net.jcm.vsch.mixin.valkyrienskies; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.entity.player.MultiPartPlayer; + +import net.minecraft.world.entity.Entity; + +import org.valkyrienskies.core.api.ships.LoadedShip; +import org.valkyrienskies.mod.common.util.EntityShipCollisionUtils; +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; +import java.util.stream.StreamSupport; + +@Mixin(EntityShipCollisionUtils.class) +public class MixinEntityShipCollisionUtils { + @ModifyExpressionValue( + method = "getShipPolygonsCollidingWithEntity", + at = @At( + value = "INVOKE", + target = "Lorg/valkyrienskies/core/api/ships/QueryableShipData;getIntersecting(Lorg/joml/primitives/AABBdc;)Ljava/lang/Iterable;" + ), + remap = false + ) + private Iterable getShipPolygonsCollidingWithEntity$getIntersecting( + final Iterable ships, + @Local(argsOnly = true) final Entity entity + ) { + if (entity instanceof final FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + return List.of(); + } + return ships; + } +} diff --git a/src/main/java/net/jcm/vsch/util/CollisionUtil.java b/src/main/java/net/jcm/vsch/util/CollisionUtil.java new file mode 100644 index 00000000..d22d904d --- /dev/null +++ b/src/main/java/net/jcm/vsch/util/CollisionUtil.java @@ -0,0 +1,152 @@ +package net.jcm.vsch.util; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.CollisionGetter; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; + +import org.joml.Vector3d; +import org.joml.Matrix4dc; +import org.joml.primitives.AABBd; + +import java.util.ArrayList; +import java.util.List; + +public final class CollisionUtil { + private CollisionUtil() {} + + public static Vector3d checkCollision( + final Vector3d movement, + final CollisionGetter level, + final Entity entity, + final AABBd box, + final Matrix4dc voxel2box, + final Matrix4dc box2voxel + ) { + final AABBd box1 = box; + final AABBd box2 = box1.transform(box2voxel, new AABBd()); + for (final VoxelShape shape : level.getBlockCollisions(entity, toAABB(box2))) { + if (shape.isEmpty()) { + continue; + } + checkShapeCollision(movement, box1, box2, shape, voxel2box); + } + return movement; + } + + private static void checkShapeCollision( + final Vector3d movement, + final AABBd box, + final AABBd boxInVoxel, + final VoxelShape shape, + final Matrix4dc voxel2box + ) { + final AABBd voxel = toAABBd(shape.bounds()); + if (!boxInVoxel.intersectsAABB(voxel)) { + return; + } + if (!voxel.transform(voxel2box).intersectsAABB(box)) { + return; + } + final AABBd voxelInBox = new AABBd(); + shape.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { + voxel + .setMin(minX, minY, minZ) + .setMax(maxX, maxY, maxZ); + if (!boxInVoxel.intersectsAABB(voxel)) { + return; + } + voxel.transform(voxel2box, voxelInBox); + if (!box.intersectsAABB(voxelInBox)) { + return; + } + checkAABBCollision(movement, box, boxInVoxel, voxelInBox, voxel, voxel2box); + }); + } + + private static void checkAABBCollision( + final Vector3d movement, + final AABBd box1, + final AABBd box2, + final AABBd voxel1, + final AABBd voxel2, + final Matrix4dc voxel2box + ) { + final List vecs = new ArrayList<>(6); + if (box1.minX < voxel1.minX) { + if (box1.maxX < voxel1.maxX) { + vecs.add(new Vector3d(voxel1.minX - box1.maxX, 0, 0)); + } + } else if (box1.maxX > voxel1.maxX) { + vecs.add(new Vector3d(voxel1.maxX - box1.minX, 0, 0)); + } + if (box1.minY < voxel1.minY) { + if (box1.maxY < voxel1.maxY) { + vecs.add(new Vector3d(0, voxel1.minY - box1.maxY, 0)); + } + } else if (box1.maxY > voxel1.maxY) { + vecs.add(new Vector3d(0, voxel1.maxY - box1.minY, 0)); + } + if (box1.minZ < voxel1.minZ) { + if (box1.maxZ < voxel1.maxZ) { + vecs.add(new Vector3d(0, 0, voxel1.minZ - box1.maxZ)); + } + } else if (box1.maxZ > voxel1.maxZ) { + vecs.add(new Vector3d(0, 0, voxel1.maxZ - box1.minZ)); + } + if (box2.minX < voxel2.minX) { + if (box2.maxX < voxel2.maxX) { + vecs.add(voxel2box.transformDirection(new Vector3d(voxel2.minX - box2.maxX, 0, 0))); + } + } else if (box2.maxX > voxel2.maxX) { + vecs.add(voxel2box.transformDirection(new Vector3d(voxel2.maxX - box2.minX, 0, 0))); + } + if (box2.minY < voxel2.minY) { + if (box2.maxY < voxel2.maxY) { + vecs.add(voxel2box.transformDirection(new Vector3d(0, voxel2.minY - box2.maxY, 0))); + } + } else if (box2.maxY > voxel2.maxY) { + vecs.add(voxel2box.transformDirection(new Vector3d(0, voxel2.maxY - box2.minY, 0))); + } + if (box2.minZ < voxel2.minZ) { + if (box2.maxZ < voxel2.maxZ) { + vecs.add(voxel2box.transformDirection(new Vector3d(0, 0, voxel2.minZ - box2.maxZ))); + } + } else if (box2.maxZ > voxel2.maxZ) { + vecs.add(voxel2box.transformDirection(new Vector3d(0, 0, voxel2.maxZ - box2.minZ))); + } + if (vecs.isEmpty()) { + return; + } + Vector3d minVec = null; + double minLen = 0; + final Vector3d normal = new Vector3d(); + for (final Vector3d v : vecs) { + final double l = v.length(); + if (l < 1e-7) { + continue; + } + final double proj = movement.dot(v.div(l, normal)); + if (proj > 0) { + continue; + } + final double corr = Math.min(l, -proj); + if (minVec == null || corr < minLen) { + minVec = normal; + minLen = corr; + } + } + if (minVec != null) { + movement.add(minVec.mul(minLen)); + } + } + + private static AABBd toAABBd(final AABB box) { + return new AABBd(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); + } + + private static AABB toAABB(final AABBd box) { + return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); + } +} diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index 7895af4a..bf186841 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -33,6 +33,7 @@ "minecraft.MixinServerPlayer", "minecraft.MixinServerboundMovePlayerPacket_PosRot", "minecraft.MixinServerboundMovePlayerPacket_Rot", + "valkyrienskies.MixinEntityShipCollisionUtils", "valkyrienskies.MixinShipAssemblyKt", "valkyrienskies.MixinVSGameUtilsKt", "valkyrienskies.accessor.ServerShipObjectWorldAccessor" From a928f0e00ff6845a681fcb6fef58a2172afbd836 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Sun, 21 Sep 2025 19:51:06 -0600 Subject: [PATCH 23/32] partially fixed horizontal collision --- .../net/jcm/vsch/client/ClientEvents.java | 19 -- .../vsch/mixin/client/MixinLocalPlayer.java | 1 + .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 40 ++-- .../java/net/jcm/vsch/util/CollisionUtil.java | 180 ++++++++++++------ src/main/resources/vsch.mixins.json | 1 - 5 files changed, 138 insertions(+), 103 deletions(-) diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index c14d1498..7ba18acc 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -5,7 +5,6 @@ import net.minecraft.client.Camera; import net.minecraft.util.Mth; import net.minecraftforge.client.event.ViewportEvent; -import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -37,22 +36,4 @@ public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event event.setRoll(CAMERA_ROT_VEC.z * Mth.RAD_TO_DEG); } } - - private static long lastFrameTime = System.nanoTime(); - private static volatile float spf = 0; - - public static float getSpf() { - return spf; - } - - @SubscribeEvent - public static void onRenderTick(final TickEvent.RenderTickEvent event) { - switch (event.phase) { - case START -> { - final long now = System.nanoTime(); - spf = Math.min((float)((now - lastFrameTime) / 1.0e9), 0.1f); - lastFrameTime = now; - } - } - } } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 25cfae58..bc2991bd 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -74,6 +74,7 @@ public void serverAiStep(final CallbackInfo ci) { private void moveTowardsClosestSpace(final double x, final double z, final CallbackInfo ci) { if (this.vsch$isFreeRotating()) { ci.cancel(); + return; } } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index ab9f405c..3e785973 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -119,6 +119,8 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay @Unique private Pose oldPose; @Unique + private long lastRollTime; + @Unique private int jumpCD = 0; @Unique private float nextStep = 0; @@ -463,7 +465,9 @@ public void turn(final double x, final double y) { if (VSCHKeyBindings.ROLL_COUNTER_CLOCKWISE.isDown()) { rollDir--; } - roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * ClientEvents.getSpf() * Mth.DEG_TO_RAD; + long now = System.nanoTime(); + roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * Math.min((float) ((now - this.lastRollTime) / 1.0e9), 0.1f) * Mth.DEG_TO_RAD; + this.lastRollTime = now; } } @@ -705,7 +709,7 @@ public void move(final MoverType moverType, Vec3 movement) { } } - System.out.println("moving: " + moverType + " : " + movement); + System.out.println("moving: " + this + ": " + moverType + " : " + movement); if (this.stuckSpeedMultiplier.lengthSqr() > 1e-7) { movement = movement.multiply(this.stuckSpeedMultiplier); this.stuckSpeedMultiplier = Vec3.ZERO; @@ -716,7 +720,7 @@ public void move(final MoverType moverType, Vec3 movement) { final Quaternionf rotation = this.vsch$getBodyRotation(); final Vector3d movementWill = rotation.transformInverse(new Vector3d(movement.x, movement.y, movement.z)); final Vec3 movementWillVec = new Vec3(movementWill.x, movementWill.y, movementWill.z); - movement = this.collide(movement); + movement = this.betterCollide(movement); final Vector3d movementActual = rotation.transformInverse(new Vector3d(movement.x, movement.y, movement.z)); final double movedDist = movement.length(); if (movedDist > 1e-4) { @@ -801,7 +805,7 @@ public void move(final MoverType moverType, Vec3 movement) { } @Unique - private Vec3 collide(Vec3 movement) { + private Vec3 betterCollide(Vec3 movement) { final Level level = this.level(); System.out.println("colliding: " + movement); final Vec3 position = this.vsch$getHeadCenter(); @@ -816,23 +820,8 @@ private Vec3 collide(Vec3 movement) { final Matrix4dc worldToEntity = entityToWorld.invert(new Matrix4d()); final Vector3d newMovement = new Vector3d(movement.x, movement.y, movement.z); worldToEntity.transformDirection(newMovement); - if (newMovement.x < 0) { - box.minX += newMovement.x; - } else { - box.maxX += newMovement.x; - } - if (newMovement.y < 0) { - box.minY += newMovement.y; - } else { - box.maxY += newMovement.y; - } - if (newMovement.z < 0) { - box.minZ += newMovement.z; - } else { - box.maxZ += newMovement.z; - } - final AABBd worldBox = box.transform(entityToWorld, new AABBd()); - System.out.println("box: " + box + " -> " + worldBox); + final AABBd checkBox = CollisionUtil.expandTowards(new AABBd(box), newMovement).transform(entityToWorld); + System.out.println("box: " + box + " -> " + checkBox); CollisionUtil.checkCollision(newMovement, level, this, box, worldToEntity, entityToWorld); @@ -843,7 +832,7 @@ private Vec3 collide(Vec3 movement) { if (!ship.getChunkClaimDimension().equals(dimId)) { continue; } - if (!ship.getWorldAABB().intersectsAABB(worldBox)) { + if (!ship.getWorldAABB().intersectsAABB(checkBox)) { continue; } entityToShip.set(ship.getWorldToShip()).mul(entityToWorld); @@ -910,6 +899,13 @@ public void dismountTo(final double x, final double y, final double z) { super.dismountTo(x, y + oldHeight - newHeight, z); } + @Override + protected void moveTowardsClosestSpace(final double x, final double y, final double z) { + if (!this.vsch$isFreeRotating()) { + super.moveTowardsClosestSpace(x, y, z); + } + } + @Override public AABB getBoundingBoxForCulling() { return super.getBoundingBoxForCulling().minmax(this.chestPart.getBoundingBoxForCulling()).minmax(this.feetPart.getBoundingBoxForCulling()); diff --git a/src/main/java/net/jcm/vsch/util/CollisionUtil.java b/src/main/java/net/jcm/vsch/util/CollisionUtil.java index d22d904d..23b84e01 100644 --- a/src/main/java/net/jcm/vsch/util/CollisionUtil.java +++ b/src/main/java/net/jcm/vsch/util/CollisionUtil.java @@ -7,6 +7,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; import org.joml.Vector3d; +import org.joml.Vector3dc; import org.joml.Matrix4dc; import org.joml.primitives.AABBd; @@ -24,29 +25,40 @@ public static Vector3d checkCollision( final Matrix4dc voxel2box, final Matrix4dc box2voxel ) { - final AABBd box1 = box; - final AABBd box2 = box1.transform(box2voxel, new AABBd()); - for (final VoxelShape shape : level.getBlockCollisions(entity, toAABB(box2))) { + final Vector3d movementInVoxel = box2voxel.transformDirection(movement, new Vector3d()); + final AABBd box1A = box; + final AABBd box1B = expandTowards(new AABBd(box1A), movement); + final AABBd box2A = box.transform(box2voxel, new AABBd()); + final AABBd box2B = expandTowards(new AABBd(box2A), movementInVoxel); + final Vector3d correction = new Vector3d(); + for (final VoxelShape shape : level.getBlockCollisions(entity, toAABB(box2B))) { if (shape.isEmpty()) { continue; } - checkShapeCollision(movement, box1, box2, shape, voxel2box); + checkShapeCollision(movement, movementInVoxel, box1A, box1B, box2A, box2B, shape, voxel2box, correction); + } + if (correction.lengthSquared() > 1e-7) { + movement.add(correction); } return movement; } private static void checkShapeCollision( final Vector3d movement, - final AABBd box, - final AABBd boxInVoxel, + final Vector3d movementInVoxel, + final AABBd boxA, + final AABBd boxB, + final AABBd boxInVoxelA, + final AABBd boxInVoxelB, final VoxelShape shape, - final Matrix4dc voxel2box + final Matrix4dc voxel2box, + final Vector3d correction ) { final AABBd voxel = toAABBd(shape.bounds()); - if (!boxInVoxel.intersectsAABB(voxel)) { + if (!boxInVoxelB.intersectsAABB(voxel)) { return; } - if (!voxel.transform(voxel2box).intersectsAABB(box)) { + if (!voxel.transform(voxel2box).intersectsAABB(boxB)) { return; } final AABBd voxelInBox = new AABBd(); @@ -54,94 +66,138 @@ private static void checkShapeCollision( voxel .setMin(minX, minY, minZ) .setMax(maxX, maxY, maxZ); - if (!boxInVoxel.intersectsAABB(voxel)) { + if (!boxInVoxelB.intersectsAABB(voxel)) { return; } voxel.transform(voxel2box, voxelInBox); - if (!box.intersectsAABB(voxelInBox)) { + if (!boxB.intersectsAABB(voxelInBox)) { return; } - checkAABBCollision(movement, box, boxInVoxel, voxelInBox, voxel, voxel2box); + checkAABBCollision(movement, movementInVoxel, boxA, boxB, boxInVoxelA, boxInVoxelB, voxelInBox, voxel, voxel2box, correction); }); } private static void checkAABBCollision( final Vector3d movement, - final AABBd box1, - final AABBd box2, + final Vector3d movementInVoxel, + final AABBd box1A, + final AABBd box1B, + final AABBd box2A, + final AABBd box2B, final AABBd voxel1, final AABBd voxel2, - final Matrix4dc voxel2box + final Matrix4dc voxel2box, + final Vector3d correction ) { - final List vecs = new ArrayList<>(6); - if (box1.minX < voxel1.minX) { - if (box1.maxX < voxel1.maxX) { - vecs.add(new Vector3d(voxel1.minX - box1.maxX, 0, 0)); + final List vecs = new ArrayList<>(6); + if (box1A.minX < voxel1.minX) { + if (box1A.maxX < voxel1.maxX) { + // ( box /// [ XXX ) \\\ voxel ] + vecs.add(new TransformData(new Vector3d(-1, 0, 0), box1A.maxX - voxel1.minX, box1B.maxX - voxel1.minX)); + } else { + // ( box /// [ voxel XXX ] /// ) } - } else if (box1.maxX > voxel1.maxX) { - vecs.add(new Vector3d(voxel1.maxX - box1.minX, 0, 0)); + } else if (box1A.maxX > voxel1.maxX) { + // [ voxel \\\ ( XXX ] /// box ) + vecs.add(new TransformData(new Vector3d(1, 0, 0), voxel1.maxX - box1A.minX, voxel1.maxX - box1B.minX)); + } else { + // [voxel \\\ ( box XXX ) \\\ ) } - if (box1.minY < voxel1.minY) { - if (box1.maxY < voxel1.maxY) { - vecs.add(new Vector3d(0, voxel1.minY - box1.maxY, 0)); + if (box1A.minY < voxel1.minY) { + if (box1A.maxY < voxel1.maxY) { + vecs.add(new TransformData(new Vector3d(0, -1, 0), box1A.maxY - voxel1.minY, box1B.maxY - voxel1.minY)); } - } else if (box1.maxY > voxel1.maxY) { - vecs.add(new Vector3d(0, voxel1.maxY - box1.minY, 0)); + } else if (box1A.maxY > voxel1.maxY) { + vecs.add(new TransformData(new Vector3d(0, 1, 0), voxel1.maxY - box1A.minY, voxel1.maxY - box1B.minY)); } - if (box1.minZ < voxel1.minZ) { - if (box1.maxZ < voxel1.maxZ) { - vecs.add(new Vector3d(0, 0, voxel1.minZ - box1.maxZ)); + if (box1A.minZ < voxel1.minZ) { + if (box1A.maxZ < voxel1.maxZ) { + vecs.add(new TransformData(new Vector3d(0, 0, -1), box1A.maxZ - voxel1.minZ, box1B.maxZ - voxel1.minZ)); } - } else if (box1.maxZ > voxel1.maxZ) { - vecs.add(new Vector3d(0, 0, voxel1.maxZ - box1.minZ)); + } else if (box1A.maxZ > voxel1.maxZ) { + vecs.add(new TransformData(new Vector3d(0, 0, 1), voxel1.maxZ - box1A.minZ, voxel1.maxZ - box1B.minZ)); } - if (box2.minX < voxel2.minX) { - if (box2.maxX < voxel2.maxX) { - vecs.add(voxel2box.transformDirection(new Vector3d(voxel2.minX - box2.maxX, 0, 0))); + if (box2A.minX < voxel2.minX) { + if (box2A.maxX < voxel2.maxX) { + vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(-1, 0, 0)), box2A.maxX - voxel2.minX, box2B.maxX - voxel2.minX)); } - } else if (box2.maxX > voxel2.maxX) { - vecs.add(voxel2box.transformDirection(new Vector3d(voxel2.maxX - box2.minX, 0, 0))); + } else if (box2A.maxX > voxel2.maxX) { + vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(1, 0, 0)), voxel2.maxX - box2A.minX, voxel2.maxX - box2B.minX)); } - if (box2.minY < voxel2.minY) { - if (box2.maxY < voxel2.maxY) { - vecs.add(voxel2box.transformDirection(new Vector3d(0, voxel2.minY - box2.maxY, 0))); + if (box2A.minY < voxel2.minY) { + if (box2A.maxY < voxel2.maxY) { + vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, -1, 0)), box2A.maxY - voxel2.minY, box2B.maxY - voxel2.minY)); } - } else if (box2.maxY > voxel2.maxY) { - vecs.add(voxel2box.transformDirection(new Vector3d(0, voxel2.maxY - box2.minY, 0))); + } else if (box2A.maxY > voxel2.maxY) { + vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, 1, 0)), voxel2.maxY - box2A.minY, voxel2.maxY - box2B.minY)); } - if (box2.minZ < voxel2.minZ) { - if (box2.maxZ < voxel2.maxZ) { - vecs.add(voxel2box.transformDirection(new Vector3d(0, 0, voxel2.minZ - box2.maxZ))); + if (box2A.minZ < voxel2.minZ) { + if (box2A.maxZ < voxel2.maxZ) { + vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, 0, -1)), box2A.maxZ - voxel2.minZ, box2B.maxZ - voxel2.minZ)); } - } else if (box2.maxZ > voxel2.maxZ) { - vecs.add(voxel2box.transformDirection(new Vector3d(0, 0, voxel2.maxZ - box2.minZ))); + } else if (box2A.maxZ > voxel2.maxZ) { + vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, 0, 1)), voxel2.maxZ - box2A.minZ, voxel2.maxZ - box2B.minZ)); } if (vecs.isEmpty()) { return; } - Vector3d minVec = null; - double minLen = 0; - final Vector3d normal = new Vector3d(); - for (final Vector3d v : vecs) { - final double l = v.length(); - if (l < 1e-7) { - continue; - } - final double proj = movement.dot(v.div(l, normal)); + double minDist = Double.POSITIVE_INFINITY; + Vector3d corrVec = new Vector3d(); + for (final TransformData d : vecs) { + final double proj = movement.dot(d.normal()); if (proj > 0) { continue; } - final double corr = Math.min(l, -proj); - if (minVec == null || corr < minLen) { - minVec = normal; - minLen = corr; + if (d.embed() < minDist) { + minDist = d.embed(); + d.normal().mul(Math.min(d.correction(), -proj), corrVec); } } - if (minVec != null) { - movement.add(minVec.mul(minLen)); + if (corrVec.lengthSquared() > 1e-7) { + if (movement.x < -1e-7) { + correction.x = Math.max(correction.x, corrVec.x); + } else if (movement.x > 1e-7) { + correction.x = Math.min(correction.x, corrVec.x); + } else if (Math.abs(corrVec.x) > Math.abs(correction.x)) { + correction.x = corrVec.x; + } + if (movement.y < -1e-7) { + correction.y = Math.max(correction.y, corrVec.y); + } else if (movement.y > 1e-7) { + correction.y = Math.min(correction.y, corrVec.y); + } else if (Math.abs(corrVec.y) > Math.abs(correction.y)) { + correction.y = corrVec.y; + } + if (movement.z < -1e-7) { + correction.z = Math.max(correction.z, corrVec.z); + } else if (movement.z > 1e-7) { + correction.z = Math.min(correction.z, corrVec.z); + } else if (Math.abs(corrVec.z) > Math.abs(correction.z)) { + correction.z = corrVec.z; + } } } + public static AABBd expandTowards(final AABBd box, final Vector3dc vec) { + final double x = vec.x(), y = vec.y(), z = vec.z(); + if (x < 0) { + box.minX += x; + } else { + box.maxX += x; + } + if (y < 0) { + box.minY += y; + } else { + box.maxY += y; + } + if (z < 0) { + box.minZ += z; + } else { + box.maxZ += z; + } + return box; + } + private static AABBd toAABBd(final AABB box) { return new AABBd(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); } @@ -149,4 +205,6 @@ private static AABBd toAABBd(final AABB box) { private static AABB toAABB(final AABBd box) { return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); } + + private record TransformData(Vector3dc normal, double embed, double correction) {} } diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index bf186841..f9e234ff 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -42,7 +42,6 @@ "client.MixinCamera", "client.MixinClientPacketListener", "client.MixinGui", - "client.MixinGui", "client.MixinLivingEntityRenderer", "client.MixinLocalPlayer", "client.MixinMultiPlayerGameMode", From 0c99da2b6d7473f4230467477f37f2f680514c39 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Sun, 21 Sep 2025 20:28:48 -0600 Subject: [PATCH 24/32] proper collision --- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 2 - .../java/net/jcm/vsch/util/CollisionUtil.java | 138 ++++++++++++------ 2 files changed, 93 insertions(+), 47 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 3e785973..7cfb518b 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -807,7 +807,6 @@ public void move(final MoverType moverType, Vec3 movement) { @Unique private Vec3 betterCollide(Vec3 movement) { final Level level = this.level(); - System.out.println("colliding: " + movement); final Vec3 position = this.vsch$getHeadCenter(); final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); final AABBd box = new AABBd( @@ -821,7 +820,6 @@ private Vec3 betterCollide(Vec3 movement) { final Vector3d newMovement = new Vector3d(movement.x, movement.y, movement.z); worldToEntity.transformDirection(newMovement); final AABBd checkBox = CollisionUtil.expandTowards(new AABBd(box), newMovement).transform(entityToWorld); - System.out.println("box: " + box + " -> " + checkBox); CollisionUtil.checkCollision(newMovement, level, this, box, worldToEntity, entityToWorld); diff --git a/src/main/java/net/jcm/vsch/util/CollisionUtil.java b/src/main/java/net/jcm/vsch/util/CollisionUtil.java index 23b84e01..84f7a944 100644 --- a/src/main/java/net/jcm/vsch/util/CollisionUtil.java +++ b/src/main/java/net/jcm/vsch/util/CollisionUtil.java @@ -15,6 +15,15 @@ import java.util.List; public final class CollisionUtil { + private static final double EPS = 1e-7; + private static final double EPS2 = 1e-2; + + private static final AxisSet AXES = new AxisSet( + new Vector3d(-1, 0, 0), new Vector3d(1, 0, 0), + new Vector3d(0, -1, 0), new Vector3d(0, 1, 0), + new Vector3d(0, 0, -1), new Vector3d(0, 0, 1) + ); + private CollisionUtil() {} public static Vector3d checkCollision( @@ -30,14 +39,15 @@ public static Vector3d checkCollision( final AABBd box1B = expandTowards(new AABBd(box1A), movement); final AABBd box2A = box.transform(box2voxel, new AABBd()); final AABBd box2B = expandTowards(new AABBd(box2A), movementInVoxel); + final AxisSet voxelAxes = AXES.transform(voxel2box); final Vector3d correction = new Vector3d(); for (final VoxelShape shape : level.getBlockCollisions(entity, toAABB(box2B))) { if (shape.isEmpty()) { continue; } - checkShapeCollision(movement, movementInVoxel, box1A, box1B, box2A, box2B, shape, voxel2box, correction); + checkShapeCollision(movement, movementInVoxel, box1A, box1B, box2A, box2B, shape, voxel2box, voxelAxes, correction); } - if (correction.lengthSquared() > 1e-7) { + if (correction.lengthSquared() > EPS) { movement.add(correction); } return movement; @@ -52,6 +62,7 @@ private static void checkShapeCollision( final AABBd boxInVoxelB, final VoxelShape shape, final Matrix4dc voxel2box, + final AxisSet voxelAxes, final Vector3d correction ) { final AABBd voxel = toAABBd(shape.bounds()); @@ -73,7 +84,7 @@ private static void checkShapeCollision( if (!boxB.intersectsAABB(voxelInBox)) { return; } - checkAABBCollision(movement, movementInVoxel, boxA, boxB, boxInVoxelA, boxInVoxelB, voxelInBox, voxel, voxel2box, correction); + checkAABBCollision(movement, movementInVoxel, boxA, boxB, boxInVoxelA, boxInVoxelB, voxelInBox, voxel, voxel2box, voxelAxes, correction); }); } @@ -87,56 +98,76 @@ private static void checkAABBCollision( final AABBd voxel1, final AABBd voxel2, final Matrix4dc voxel2box, + final AxisSet voxelAxes, final Vector3d correction ) { final List vecs = new ArrayList<>(6); - if (box1A.minX < voxel1.minX) { - if (box1A.maxX < voxel1.maxX) { - // ( box /// [ XXX ) \\\ voxel ] - vecs.add(new TransformData(new Vector3d(-1, 0, 0), box1A.maxX - voxel1.minX, box1B.maxX - voxel1.minX)); + final boolean + collideX1 = box1A.maxX > voxel1.minX + EPS2 && box1A.minX + EPS2 < voxel1.maxX, + collideY1 = box1A.maxY > voxel1.minY + EPS2 && box1A.minY + EPS2 < voxel1.maxY, + collideZ1 = box1A.maxZ > voxel1.minZ + EPS2 && box1A.minZ + EPS2 < voxel1.maxZ, + collideX2 = box2A.maxX > voxel2.minX + EPS2 && box2A.minX + EPS2 < voxel2.maxX, + collideY2 = box2A.maxY > voxel2.minY + EPS2 && box2A.minY + EPS2 < voxel2.maxY, + collideZ2 = box2A.maxZ > voxel2.minZ + EPS2 && box2A.minZ + EPS2 < voxel2.maxZ; + if (collideY1 && collideZ1) { + if (box1A.minX < voxel1.minX) { + if (box1A.maxX < voxel1.maxX) { + // ( box /// [ XXX ) \\\ voxel ] + vecs.add(new TransformData(AXES.xn(), box1A.maxX - voxel1.minX, box1B.maxX - voxel1.minX)); + } else { + // ( box /// [ voxel XXX ] /// ) + } + } else if (box1A.maxX > voxel1.maxX) { + // [ voxel \\\ ( XXX ] /// box ) + vecs.add(new TransformData(AXES.xp(), voxel1.maxX - box1A.minX, voxel1.maxX - box1B.minX)); } else { - // ( box /// [ voxel XXX ] /// ) + // [voxel \\\ ( box XXX ) \\\ ) } - } else if (box1A.maxX > voxel1.maxX) { - // [ voxel \\\ ( XXX ] /// box ) - vecs.add(new TransformData(new Vector3d(1, 0, 0), voxel1.maxX - box1A.minX, voxel1.maxX - box1B.minX)); - } else { - // [voxel \\\ ( box XXX ) \\\ ) } - if (box1A.minY < voxel1.minY) { - if (box1A.maxY < voxel1.maxY) { - vecs.add(new TransformData(new Vector3d(0, -1, 0), box1A.maxY - voxel1.minY, box1B.maxY - voxel1.minY)); + if (collideX1 && collideZ1) { + if (box1A.minY < voxel1.minY) { + if (box1A.maxY < voxel1.maxY) { + vecs.add(new TransformData(AXES.yn(), box1A.maxY - voxel1.minY, box1B.maxY - voxel1.minY)); + } + } else if (box1A.maxY > voxel1.maxY) { + vecs.add(new TransformData(AXES.yp(), voxel1.maxY - box1A.minY, voxel1.maxY - box1B.minY)); } - } else if (box1A.maxY > voxel1.maxY) { - vecs.add(new TransformData(new Vector3d(0, 1, 0), voxel1.maxY - box1A.minY, voxel1.maxY - box1B.minY)); } - if (box1A.minZ < voxel1.minZ) { - if (box1A.maxZ < voxel1.maxZ) { - vecs.add(new TransformData(new Vector3d(0, 0, -1), box1A.maxZ - voxel1.minZ, box1B.maxZ - voxel1.minZ)); + if (collideX1 && collideY1) { + if (box1A.minZ < voxel1.minZ) { + if (box1A.maxZ < voxel1.maxZ) { + vecs.add(new TransformData(AXES.zn(), box1A.maxZ - voxel1.minZ, box1B.maxZ - voxel1.minZ)); + } + } else if (box1A.maxZ > voxel1.maxZ) { + vecs.add(new TransformData(AXES.zp(), voxel1.maxZ - box1A.minZ, voxel1.maxZ - box1B.minZ)); } - } else if (box1A.maxZ > voxel1.maxZ) { - vecs.add(new TransformData(new Vector3d(0, 0, 1), voxel1.maxZ - box1A.minZ, voxel1.maxZ - box1B.minZ)); } - if (box2A.minX < voxel2.minX) { - if (box2A.maxX < voxel2.maxX) { - vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(-1, 0, 0)), box2A.maxX - voxel2.minX, box2B.maxX - voxel2.minX)); + if (collideY2 && collideZ2) { + if (box2A.minX < voxel2.minX) { + if (box2A.maxX < voxel2.maxX) { + vecs.add(new TransformData(voxelAxes.xn(), box2A.maxX - voxel2.minX, box2B.maxX - voxel2.minX)); + } + } else if (box2A.maxX > voxel2.maxX) { + vecs.add(new TransformData(voxelAxes.xp(), voxel2.maxX - box2A.minX, voxel2.maxX - box2B.minX)); } - } else if (box2A.maxX > voxel2.maxX) { - vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(1, 0, 0)), voxel2.maxX - box2A.minX, voxel2.maxX - box2B.minX)); } - if (box2A.minY < voxel2.minY) { - if (box2A.maxY < voxel2.maxY) { - vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, -1, 0)), box2A.maxY - voxel2.minY, box2B.maxY - voxel2.minY)); + if (collideX2 && collideZ2) { + if (box2A.minY < voxel2.minY) { + if (box2A.maxY < voxel2.maxY) { + vecs.add(new TransformData(voxelAxes.yn(), box2A.maxY - voxel2.minY, box2B.maxY - voxel2.minY)); + } + } else if (box2A.maxY > voxel2.maxY) { + vecs.add(new TransformData(voxelAxes.yp(), voxel2.maxY - box2A.minY, voxel2.maxY - box2B.minY)); } - } else if (box2A.maxY > voxel2.maxY) { - vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, 1, 0)), voxel2.maxY - box2A.minY, voxel2.maxY - box2B.minY)); } - if (box2A.minZ < voxel2.minZ) { - if (box2A.maxZ < voxel2.maxZ) { - vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, 0, -1)), box2A.maxZ - voxel2.minZ, box2B.maxZ - voxel2.minZ)); + if (collideX2 && collideY2) { + if (box2A.minZ < voxel2.minZ) { + if (box2A.maxZ < voxel2.maxZ) { + vecs.add(new TransformData(voxelAxes.zn(), box2A.maxZ - voxel2.minZ, box2B.maxZ - voxel2.minZ)); + } + } else if (box2A.maxZ > voxel2.maxZ) { + vecs.add(new TransformData(voxelAxes.zp(), voxel2.maxZ - box2A.minZ, voxel2.maxZ - box2B.minZ)); } - } else if (box2A.maxZ > voxel2.maxZ) { - vecs.add(new TransformData(voxel2box.transformDirection(new Vector3d(0, 0, 1)), voxel2.maxZ - box2A.minZ, voxel2.maxZ - box2B.minZ)); } if (vecs.isEmpty()) { return; @@ -153,24 +184,24 @@ private static void checkAABBCollision( d.normal().mul(Math.min(d.correction(), -proj), corrVec); } } - if (corrVec.lengthSquared() > 1e-7) { - if (movement.x < -1e-7) { + if (corrVec.lengthSquared() > EPS) { + if (movement.x < -EPS) { correction.x = Math.max(correction.x, corrVec.x); - } else if (movement.x > 1e-7) { + } else if (movement.x > EPS) { correction.x = Math.min(correction.x, corrVec.x); } else if (Math.abs(corrVec.x) > Math.abs(correction.x)) { correction.x = corrVec.x; } - if (movement.y < -1e-7) { + if (movement.y < -EPS) { correction.y = Math.max(correction.y, corrVec.y); - } else if (movement.y > 1e-7) { + } else if (movement.y > EPS) { correction.y = Math.min(correction.y, corrVec.y); } else if (Math.abs(corrVec.y) > Math.abs(correction.y)) { correction.y = corrVec.y; } - if (movement.z < -1e-7) { + if (movement.z < -EPS) { correction.z = Math.max(correction.z, corrVec.z); - } else if (movement.z > 1e-7) { + } else if (movement.z > EPS) { correction.z = Math.min(correction.z, corrVec.z); } else if (Math.abs(corrVec.z) > Math.abs(correction.z)) { correction.z = corrVec.z; @@ -207,4 +238,21 @@ private static AABB toAABB(final AABBd box) { } private record TransformData(Vector3dc normal, double embed, double correction) {} + + private record AxisSet( + Vector3dc xn, Vector3dc xp, + Vector3dc yn, Vector3dc yp, + Vector3dc zn, Vector3dc zp + ) { + public AxisSet transform(final Matrix4dc transform) { + return new AxisSet( + transform.transformDirection(this.xn, new Vector3d()), + transform.transformDirection(this.xp, new Vector3d()), + transform.transformDirection(this.yn, new Vector3d()), + transform.transformDirection(this.yp, new Vector3d()), + transform.transformDirection(this.zn, new Vector3d()), + transform.transformDirection(this.zp, new Vector3d()) + ); + } + } } From 4db177858d2d6d1d49587e83e997e8d6705aa67f Mon Sep 17 00:00:00 2001 From: zyxkad Date: Sun, 21 Sep 2025 21:34:34 -0600 Subject: [PATCH 25/32] toggeable magnet boots --- src/main/java/net/jcm/vsch/VSCHMod.java | 11 +-- .../net/jcm/vsch/client/ClientEvents.java | 25 +++++++ .../net/jcm/vsch/client/VSCHKeyBindings.java | 9 +++ .../net/jcm/vsch/items/IToggleableItem.java | 26 +++++++ .../jcm/vsch/items/custom/MagnetBootItem.java | 25 +++++-- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 2 - .../net/jcm/vsch/network/INetworkPacket.java | 10 +++ .../net/jcm/vsch/network/VSCHNetwork.java | 68 +++++++++++++++++++ .../vsch/network/c2s/ToggleItemPacketC2S.java | 44 ++++++++++++ .../resources/assets/vsch/lang/en_us.json | 5 ++ .../resources/assets/vsch/lang/zh_cn.json | 5 ++ 11 files changed, 219 insertions(+), 11 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/items/IToggleableItem.java create mode 100644 src/main/java/net/jcm/vsch/network/INetworkPacket.java create mode 100644 src/main/java/net/jcm/vsch/network/VSCHNetwork.java create mode 100644 src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java diff --git a/src/main/java/net/jcm/vsch/VSCHMod.java b/src/main/java/net/jcm/vsch/VSCHMod.java index 32fd8e15..82611481 100644 --- a/src/main/java/net/jcm/vsch/VSCHMod.java +++ b/src/main/java/net/jcm/vsch/VSCHMod.java @@ -12,6 +12,7 @@ import net.jcm.vsch.entity.VSCHEntities; import net.jcm.vsch.event.GravityInducer; import net.jcm.vsch.items.VSCHItems; +import net.jcm.vsch.network.VSCHNetwork; import net.jcm.vsch.util.assemble.MoveUtil; import net.minecraftforge.client.event.EntityRenderersEvent; @@ -27,20 +28,22 @@ @Mod(VSCHMod.MODID) public class VSCHMod { public static final String MODID = "vsch"; + public static final String VERSION = ModLoadingContext.get().getActiveContainer().getModInfo().getVersion().toString(); public VSCHMod() { final FMLJavaModLoadingContext context = FMLJavaModLoadingContext.get(); final IEventBus modBus = context.getModEventBus(); - VSCHItems.register(modBus); - VSCHBlocks.register(modBus); + MoveUtil.registerDefaultMovers(); VSCHBlockEntities.register(modBus); + VSCHBlocks.register(modBus); VSCHClientConfig.register(context); VSCHConfig.register(context); - VSCHTab.register(modBus); VSCHEntities.register(modBus); + VSCHItems.register(modBus); + VSCHNetwork.register(); + VSCHTab.register(modBus); VSCHTags.register(); - MoveUtil.registerDefaultMovers(); // Register commands (I took this code from another one of my mods, can't be bothered to make it consistent with the rest of this) MinecraftForge.EVENT_BUS.register(ModCommands.class); diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index 7ba18acc..1d1003fb 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -1,10 +1,17 @@ package net.jcm.vsch.client; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.items.IToggleableItem; +import net.jcm.vsch.items.custom.MagnetBootItem; import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; import net.minecraft.util.Mth; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.player.Inventory; import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -36,4 +43,22 @@ public static void onComputeCamera(final ViewportEvent.ComputeCameraAngles event event.setRoll(CAMERA_ROT_VEC.z * Mth.RAD_TO_DEG); } } + + @SubscribeEvent + public static void onClientTick(final TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) { + return; + } + final LocalPlayer player = Minecraft.getInstance().player; + final Inventory inventory = player == null ? null : player.getInventory(); + final int bootSlot = EquipmentSlot.FEET.getIndex(Inventory.INVENTORY_SIZE); + while (VSCHKeyBindings.TOGGLE_MAGNET_BOOT.consumeClick()) { + if (inventory == null) { + continue; + } + if (inventory.getItem(bootSlot).getItem() instanceof MagnetBootItem) { + IToggleableItem.toggleSlot(player, bootSlot); + } + } + } } diff --git a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java index 2719ea17..0c06a13d 100644 --- a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java +++ b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java @@ -38,10 +38,19 @@ public final class VSCHKeyBindings { "key.categories.movement" ); + public static final KeyMapping TOGGLE_MAGNET_BOOT = new KeyMapping( + "key." + VSCHMod.MODID + ".toggle_magnet_boot", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_GRAVE_ACCENT, + "key.categories." + VSCHMod.MODID + ); + @SubscribeEvent public static void register(final RegisterKeyMappingsEvent event) { event.register(ROLL_COUNTER_CLOCKWISE); event.register(ROLL_CLOCKWISE); event.register(UNLOCK_HEAD_ROTATION); + event.register(TOGGLE_MAGNET_BOOT); } } diff --git a/src/main/java/net/jcm/vsch/items/IToggleableItem.java b/src/main/java/net/jcm/vsch/items/IToggleableItem.java new file mode 100644 index 00000000..d238583f --- /dev/null +++ b/src/main/java/net/jcm/vsch/items/IToggleableItem.java @@ -0,0 +1,26 @@ +package net.jcm.vsch.items; + +import net.jcm.vsch.network.VSCHNetwork; +import net.jcm.vsch.network.c2s.ToggleItemPacketC2S; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; + +public interface IToggleableItem { + void onToggle(Player owner, int slot, ItemStack stack); + + static boolean toggleSlot(final Player player, final int slot) { + final ItemStack stack = player.getInventory().getItem(slot); + if (stack.isEmpty()) { + return false; + } + if (!(stack.getItem() instanceof final IToggleableItem item)) { + return false; + } + item.onToggle(player, slot, stack); + if (player.level().isClientSide) { + VSCHNetwork.sendToServer(new ToggleItemPacketC2S(slot)); + } + return true; + } +} diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index e40de931..c6f21d1d 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -2,13 +2,14 @@ import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.jcm.vsch.config.VSCHConfig; -import net.lointain.cosmos.item.SteelarmourItem; +import net.jcm.vsch.items.IToggleableItem; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.DoubleTag; import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; @@ -31,7 +32,7 @@ import org.valkyrienskies.core.api.ships.Ship; import org.valkyrienskies.mod.common.VSGameUtilsKt; -public class MagnetBootItem extends ArmorItem { +public class MagnetBootItem extends ArmorItem implements IToggleableItem { private static final String TAG_DISABLED = "Disabled"; private static final String TAG_READY = "Ready"; private static final String TAG_DIRECTION = "Direction"; @@ -90,8 +91,22 @@ public Vec3 getDirection(final ItemStack stack) { } @Override - public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, boolean selected) { - if (!(entity instanceof LivingEntity livingEntity)) { + public void onToggle(final Player owner, final int slot, final ItemStack stack) { + final CompoundTag tag = stack.getOrCreateTag(); + final boolean disable = !tag.getBoolean(TAG_DISABLED); + tag.putBoolean(TAG_DISABLED, disable); + if (owner.level().isClientSide) { + return; + } + owner.displayClientMessage( + Component.translatable(disable ? "vsch.message.magnet_boot.disabled" : "vsch.message.magnet_boot.enabled"), + true + ); + } + + @Override + public void inventoryTick(final ItemStack stack, final Level level, final Entity entity, final int slot, final boolean selected) { + if (!(entity instanceof final LivingEntity livingEntity)) { return; } if (livingEntity.getItemBySlot(this.getEquipmentSlot()) != stack) { @@ -102,7 +117,7 @@ public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, if (entity.noPhysics || entity.isPassenger()) { return; } - if (entity instanceof Player player && player.getAbilities().flying) { + if (entity instanceof final Player player && player.getAbilities().flying) { return; } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 7cfb518b..441e673d 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -709,7 +709,6 @@ public void move(final MoverType moverType, Vec3 movement) { } } - System.out.println("moving: " + this + ": " + moverType + " : " + movement); if (this.stuckSpeedMultiplier.lengthSqr() > 1e-7) { movement = movement.multiply(this.stuckSpeedMultiplier); this.stuckSpeedMultiplier = Vec3.ZERO; @@ -858,7 +857,6 @@ private Vec3 betterCollide(Vec3 movement) { newMovement.z = 0; } movement = new Vec3(newMovement.x, newMovement.y, newMovement.z); - System.out.println("final movement: " + movement); return movement; } diff --git a/src/main/java/net/jcm/vsch/network/INetworkPacket.java b/src/main/java/net/jcm/vsch/network/INetworkPacket.java new file mode 100644 index 00000000..219b1d66 --- /dev/null +++ b/src/main/java/net/jcm/vsch/network/INetworkPacket.java @@ -0,0 +1,10 @@ +package net.jcm.vsch.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +public interface INetworkPacket { + void encode(FriendlyByteBuf buf); + + void handle(NetworkEvent.Context ctx); +} diff --git a/src/main/java/net/jcm/vsch/network/VSCHNetwork.java b/src/main/java/net/jcm/vsch/network/VSCHNetwork.java new file mode 100644 index 00000000..8253dac0 --- /dev/null +++ b/src/main/java/net/jcm/vsch/network/VSCHNetwork.java @@ -0,0 +1,68 @@ +package net.jcm.vsch.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.network.simple.SimpleChannel; + +import net.jcm.vsch.VSCHMod; +import net.jcm.vsch.network.c2s.ToggleItemPacketC2S; + +import java.util.Optional; +import java.util.function.Function; + +public final class VSCHNetwork { + private VSCHNetwork() {} + + private static final String PROTOCOL_VERSION = VSCHMod.VERSION; + public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( + new ResourceLocation(VSCHMod.MODID, "main"), + () -> PROTOCOL_VERSION, + PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals + ); + private static int id = 0; + + public static void register() { + registerC2S(ToggleItemPacketC2S.class, ToggleItemPacketC2S::decode); + } + + public static void registerC2S(final Class clazz, final Function decoder) { + CHANNEL.registerMessage( + id++, + clazz, + INetworkPacket::encode, + decoder, + (packet, ctx) -> packet.handle(ctx.get()), + Optional.of(NetworkDirection.PLAY_TO_SERVER) + ); + } + + public static void registerS2C(final Class clazz, final Function decoder) { + CHANNEL.registerMessage( + id++, + clazz, + INetworkPacket::encode, + decoder, + (packet, ctx) -> packet.handle(ctx.get()), + Optional.of(NetworkDirection.PLAY_TO_CLIENT) + ); + } + + public static void sendToServer(final INetworkPacket packet) { + CHANNEL.sendToServer(packet); + } + + public static void sendToPlayer(final INetworkPacket packet, final ServerPlayer player) { + CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), packet); + } + + public static void sendToTracking(final INetworkPacket packet, final ServerLevel level, final BlockPos pos) { + level.getChunkSource().chunkMap.getPlayers(new ChunkPos(pos), false).forEach((p) -> sendToPlayer(packet, (ServerPlayer) (p))); + } +} diff --git a/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java b/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java new file mode 100644 index 00000000..372a7779 --- /dev/null +++ b/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java @@ -0,0 +1,44 @@ +package net.jcm.vsch.network.c2s; + +import net.jcm.vsch.items.IToggleableItem; +import net.jcm.vsch.network.INetworkPacket; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkEvent; + +public class ToggleItemPacketC2S implements INetworkPacket { + public final int slot; + + public ToggleItemPacketC2S(final int slot) { + this.slot = slot; + } + + @Override + public void encode(final FriendlyByteBuf buf) { + buf.writeVarInt(this.slot); + } + + public static ToggleItemPacketC2S decode(final FriendlyByteBuf buf) { + return new ToggleItemPacketC2S(buf.readVarInt()); + } + + @Override + public void handle(final NetworkEvent.Context ctx) { + ctx.setPacketHandled(true); + final ServerPlayer player = ctx.getSender(); + if (player == null) { + return; + } + ctx.enqueueWork(() -> { + final ItemStack stack = player.getInventory().getItem(this.slot); + if (stack.isEmpty()) { + return; + } + if (stack.getItem() instanceof final IToggleableItem item) { + item.onToggle(player, this.slot, stack); + } + }); + } +} diff --git a/src/main/resources/assets/vsch/lang/en_us.json b/src/main/resources/assets/vsch/lang/en_us.json index 3a530376..2c45d71d 100644 --- a/src/main/resources/assets/vsch/lang/en_us.json +++ b/src/main/resources/assets/vsch/lang/en_us.json @@ -3,6 +3,8 @@ "vsch.global": "§6GLOBAL", "vsch.itemtab": "Starlance", "vsch.message.gyro": "Strength set to §6%s%%", + "vsch.message.magnet_boot.disabled": "Magnet boots §4DISABLED", + "vsch.message.magnet_boot.enabled": "Magnet boots §2ENABLED", "vsch.message.mode": "Mode: ", "vsch.message.strength": "Strength: ", "vsch.message.toggle": "Mode set to ", @@ -17,8 +19,11 @@ "block.vsch.thruster_block": "Thruster", "item.vsch.magnet_boot": "Magnet Boots", "item.vsch.wrench": "Wrench", + "key.categories.vsch": "Starlance", "key.vsch.roll_clockwise": "Roll Clockwise", "key.vsch.roll_counter_clockwise": "Roll Counter Clockwise", + "key.vsch.unlock_head_rotation": "Unlock Head Rotation", + "key.vsch.toggle_magnet_boot": "Toggle Magnet Boots", "config.jade.plugin_vsch.gyro_component_config": "Gyro Mode Details", "config.jade.plugin_vsch.thruster_component_config": "Thruster Mode Details", diff --git a/src/main/resources/assets/vsch/lang/zh_cn.json b/src/main/resources/assets/vsch/lang/zh_cn.json index 7875b543..a7214ff3 100644 --- a/src/main/resources/assets/vsch/lang/zh_cn.json +++ b/src/main/resources/assets/vsch/lang/zh_cn.json @@ -3,6 +3,8 @@ "vsch.global": "§6全局", "vsch.itemtab": "星槊", "vsch.message.gyro": "力度已设置为 §6%s%%", + "vsch.message.magnet_boot.disabled": "磁力靴 §4已禁用", + "vsch.message.magnet_boot.enabled": "磁力靴 §2已启用", "vsch.message.mode": "模式: ", "vsch.message.strength": "力度: ", "vsch.message.toggle": "将模式设置为", @@ -17,8 +19,11 @@ "block.vsch.thruster_block": "推进器", "item.vsch.magnet_boot": "磁力靴", "item.vsch.wrench": "扳手", + "key.categories.vsch": "星槊", "key.vsch.roll_clockwise": "顺时针旋转", "key.vsch.roll_counter_clockwise": "逆时针旋转", + "key.vsch.unlock_head_rotation": "解锁头部旋转", + "key.vsch.toggle_magnet_boot": "切换磁力靴模式", "config.jade.plugin_vsch.gyro_component_config": "陀螺仪配置信息", "config.jade.plugin_vsch.thruster_component_config": "推进器配置信息", From 2fe45f289e7f5477bf4cde3a43babe24baed01a9 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 22 Sep 2025 12:24:29 -0600 Subject: [PATCH 26/32] allow magnet boots works with curios --- build.gradle | 19 +++++--- gradle.properties | 13 ++--- src/main/java/net/jcm/vsch/VSCHMod.java | 7 +-- src/main/java/net/jcm/vsch/VSCHTags.java | 1 + .../net/jcm/vsch/client/ClientEvents.java | 5 +- .../net/jcm/vsch/client/VSCHKeyBindings.java | 6 +-- .../vsch/client/renderer/GyroRenderer.java | 1 + .../java/net/jcm/vsch/compat/CompatMods.java | 1 + .../vsch/compat/curios/MagnetBootCurio.java | 31 ++++++++++++ .../net/jcm/vsch/compat/jade/JadeCompat.java | 2 + .../java/net/jcm/vsch/config/VSCHConfig.java | 1 + .../net/jcm/vsch/items/IToggleableItem.java | 24 +++++++++- .../java/net/jcm/vsch/items/VSCHItems.java | 11 +++-- .../jcm/vsch/items/custom/MagnetBootItem.java | 47 ++++++++++++++++-- .../vsch/mixin/client/MixinLocalPlayer.java | 20 ++++++++ .../mixin/minecraft/MixinLivingEntity.java | 23 ++++++++- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 22 ++------- .../net/jcm/vsch/network/VSCHNetwork.java | 1 + .../vsch/network/c2s/ToggleItemPacketC2S.java | 48 ++++++++++++++++++- .../jcm/vsch/ship/ShipLandingAttachment.java | 1 + .../java/net/jcm/vsch/util/CollisionUtil.java | 4 +- .../java/net/jcm/vsch/util/VSCHUtils.java | 36 ++++++++++++++ .../net/jcm/vsch/util/wapi/LevelData.java | 1 + .../resources/assets/vsch/lang/en_us.json | 1 + .../resources/assets/vsch/lang/zh_cn.json | 1 + .../data/curios/tags/items/boots.json | 6 +++ .../data/vsch/curios/entities/entities.json | 8 ++++ .../data/vsch/curios/slots/boots.json | 5 ++ 28 files changed, 286 insertions(+), 60 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/compat/curios/MagnetBootCurio.java create mode 100644 src/main/resources/data/curios/tags/items/boots.json create mode 100644 src/main/resources/data/vsch/curios/entities/entities.json create mode 100644 src/main/resources/data/vsch/curios/slots/boots.json diff --git a/build.gradle b/build.gradle index 98eb7510..6c3a54c2 100644 --- a/build.gradle +++ b/build.gradle @@ -176,6 +176,13 @@ repositories { includeModule("org.squiddev", "Cobalt") } } + maven { + name = "Illusive Soulworks maven" + url = "https://maven.theillusivec4.top/" + content { + includeGroup("top.theillusivec4.curios") + } + } } dependencies { @@ -199,22 +206,22 @@ dependencies { //runtimeOnly fg.deobf("mekanism:Mekanism:${mekanism_version}:generators") //runtimeOnly fg.deobf("mekanism:Mekanism:${mekanism_version}:additions") - implementation fg.deobf("curse.maven:jade-324717:${jade_id}") + implementation fg.deobf("curse.maven:jade-324717:5876199") implementation "org.joml:joml:1.10.5" implementation "com.fasterxml.jackson.core:jackson-annotations:2.13.3" implementation "thedarkcolour:kotlinforforge:4.10.0" implementation fg.deobf("cc.tweaked:cc-tweaked-${minecraft_version}-forge:${cc_version}") implementation fg.deobf("maven.modrinth:cosmic-horizons-cosmos:${cosmos_version}") - compileOnly fg.deobf("com.jozufozu.flywheel:flywheel-forge-${flywheel_minecraft_version}:${flywheel_version}") - implementation fg.deobf("com.simibubi.create:create-${create_minecraft_version}:${create_version}:all") + compileOnly fg.deobf("com.jozufozu.flywheel:flywheel-forge-${minecraft_version}:${flywheel_version}") + implementation fg.deobf("com.simibubi.create:create-${minecraft_version}:${create_version}:all") - //compileOnly fg.deobf("curse.maven:female-gender-forge-481655:4603538") + compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}:api") //RUNTIME runtimeOnly fg.deobf("mezz.jei:jei-${minecraft_version}-forge:${jei_version}") -// runtimeOnly fg.deobf("maven.modrinth:create-clockwork:1.20.1-0.1.16") -// runtimeOnly fg.deobf("maven.modrinth:architectury-api:9.2.14+forge") + // runtimeOnly fg.deobf("maven.modrinth:create-clockwork:1.20.1-0.1.16") + // runtimeOnly fg.deobf("maven.modrinth:architectury-api:9.2.14+forge") runtimeOnly fg.deobf("maven.modrinth:jade-addons-forge:5.3.1+forge") //runtimeOnly fg.deobf("curse.maven:rubidium-574856:4494903") //runtimeOnly fg.deobf("curse.maven:ctm-267602:3933537") diff --git a/gradle.properties b/gradle.properties index 80c31a64..a4c418fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,17 +30,14 @@ vs2_mc_version=120 #vs_core_version=1.1.0+cf7b0d3c5b vs2_version=2.3.0-beta.5+bcda04a482 vs_core_version=1.1.0+b19b27c4a4 -jade_id=5876199 cc_version=1.114.2 -create_minecraft_version = 1.20.1 -flywheel_minecraft_version = 1.20.1 -create_version = 0.5.1.j-55 -flywheel_version = 0.6.11-13 -registrate_version = MC1.20-1.3.3 - +create_version=0.5.1.j-55 +curios_version=5.14.1+1.20.1 +flywheel_version=0.6.11-13 +jei_version=15.2.0.27 lodestone_version=1.4.1.435 -jei_version = 15.2.0.27 +registrate_version=MC1.20-1.3.3 curse_api_token= curse_project_id=1130106 diff --git a/src/main/java/net/jcm/vsch/VSCHMod.java b/src/main/java/net/jcm/vsch/VSCHMod.java index 82611481..2bf9af19 100644 --- a/src/main/java/net/jcm/vsch/VSCHMod.java +++ b/src/main/java/net/jcm/vsch/VSCHMod.java @@ -28,9 +28,11 @@ @Mod(VSCHMod.MODID) public class VSCHMod { public static final String MODID = "vsch"; + @SuppressWarnings("removal") public static final String VERSION = ModLoadingContext.get().getActiveContainer().getModInfo().getVersion().toString(); public VSCHMod() { + @SuppressWarnings("removal") final FMLJavaModLoadingContext context = FMLJavaModLoadingContext.get(); final IEventBus modBus = context.getModEventBus(); @@ -68,8 +70,3 @@ public void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { // event.registerEntityRenderer(VSCHEntities.MAGNET_ENTITY.get(), NoopRenderer::new); } } - - - - - diff --git a/src/main/java/net/jcm/vsch/VSCHTags.java b/src/main/java/net/jcm/vsch/VSCHTags.java index 7d150224..1c4a4f5f 100644 --- a/src/main/java/net/jcm/vsch/VSCHTags.java +++ b/src/main/java/net/jcm/vsch/VSCHTags.java @@ -19,6 +19,7 @@ private Fluids() {} public static final TagKey HYDROGEN = tag("liquid_hydrogen"); public static final TagKey OXYGEN = tag("liquid_oxygen"); + @SuppressWarnings("removal") private static TagKey tag(String name) { return TagKey.create(Registries.FLUID, new ResourceLocation(VSCHMod.MODID, name)); } diff --git a/src/main/java/net/jcm/vsch/client/ClientEvents.java b/src/main/java/net/jcm/vsch/client/ClientEvents.java index 1d1003fb..e2dfa7e4 100644 --- a/src/main/java/net/jcm/vsch/client/ClientEvents.java +++ b/src/main/java/net/jcm/vsch/client/ClientEvents.java @@ -56,9 +56,10 @@ public static void onClientTick(final TickEvent.ClientTickEvent event) { if (inventory == null) { continue; } - if (inventory.getItem(bootSlot).getItem() instanceof MagnetBootItem) { - IToggleableItem.toggleSlot(player, bootSlot); + if (inventory.getItem(bootSlot).getItem() instanceof MagnetBootItem && IToggleableItem.toggleSlot(player, bootSlot)) { + continue; } + IToggleableItem.toggleCuriosSlot(player, "boots", (stack) -> stack.getItem() instanceof MagnetBootItem); } } } diff --git a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java index 0c06a13d..dd49305d 100644 --- a/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java +++ b/src/main/java/net/jcm/vsch/client/VSCHKeyBindings.java @@ -18,7 +18,7 @@ public final class VSCHKeyBindings { "key." + VSCHMod.MODID + ".roll_clockwise", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, - -1, + GLFW.GLFW_KEY_C, "key.categories.movement" ); @@ -26,7 +26,7 @@ public final class VSCHKeyBindings { "key." + VSCHMod.MODID + ".roll_counter_clockwise", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, - -1, + GLFW.GLFW_KEY_Z, "key.categories.movement" ); @@ -42,7 +42,7 @@ public final class VSCHKeyBindings { "key." + VSCHMod.MODID + ".toggle_magnet_boot", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, - GLFW.GLFW_KEY_GRAVE_ACCENT, + GLFW.GLFW_KEY_X, "key.categories." + VSCHMod.MODID ); diff --git a/src/main/java/net/jcm/vsch/client/renderer/GyroRenderer.java b/src/main/java/net/jcm/vsch/client/renderer/GyroRenderer.java index 11dad6e2..35abe842 100644 --- a/src/main/java/net/jcm/vsch/client/renderer/GyroRenderer.java +++ b/src/main/java/net/jcm/vsch/client/renderer/GyroRenderer.java @@ -31,6 +31,7 @@ public class GyroRenderer implements BlockEntityRenderer { private static final ModelTextures CORE_MODEL; static { + @SuppressWarnings("removal") final ResourceLocation resource = new ResourceLocation(VSCHMod.MODID, "block/gyro"); CORE_MODEL = new ModelTextures( TextureLocation.fromNonStandardSize(resource, 12, 44, 128), diff --git a/src/main/java/net/jcm/vsch/compat/CompatMods.java b/src/main/java/net/jcm/vsch/compat/CompatMods.java index a136fa1a..85546ae3 100644 --- a/src/main/java/net/jcm/vsch/compat/CompatMods.java +++ b/src/main/java/net/jcm/vsch/compat/CompatMods.java @@ -9,6 +9,7 @@ public enum CompatMods { COMPUTERCRAFT("computercraft"), CREATE("create"), + CURIOS("curios"), JADE("jade"); private final String modId; diff --git a/src/main/java/net/jcm/vsch/compat/curios/MagnetBootCurio.java b/src/main/java/net/jcm/vsch/compat/curios/MagnetBootCurio.java new file mode 100644 index 00000000..73310cd6 --- /dev/null +++ b/src/main/java/net/jcm/vsch/compat/curios/MagnetBootCurio.java @@ -0,0 +1,31 @@ +package net.jcm.vsch.compat.curios; + +import net.jcm.vsch.items.custom.MagnetBootItem; + +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +import top.theillusivec4.curios.api.SlotContext; +import top.theillusivec4.curios.api.type.capability.ICurio; + +public final class MagnetBootCurio implements ICurio { + private final MagnetBootItem item; + private final ItemStack stack; + + public MagnetBootCurio(final MagnetBootItem item, final ItemStack stack) { + this.item = item; + this.stack = stack; + } + + @Override + public ItemStack getStack() { + return this.stack; + } + + @SuppressWarnings("removal") + @Override + public void curioTick(final SlotContext context) { + final LivingEntity owner = context.getWearer(); + this.item.onInventoryTick(this.stack, owner.level(), owner); + } +} diff --git a/src/main/java/net/jcm/vsch/compat/jade/JadeCompat.java b/src/main/java/net/jcm/vsch/compat/jade/JadeCompat.java index 04532117..52158d52 100644 --- a/src/main/java/net/jcm/vsch/compat/jade/JadeCompat.java +++ b/src/main/java/net/jcm/vsch/compat/jade/JadeCompat.java @@ -15,7 +15,9 @@ @WailaPlugin public class JadeCompat implements IWailaPlugin { + @SuppressWarnings("removal") public static final ResourceLocation THRUSTER_BLOCK = new ResourceLocation(VSCHMod.MODID, "thruster_component_config"); + @SuppressWarnings("removal") public static final ResourceLocation GYRO_BLOCK = new ResourceLocation(VSCHMod.MODID, "gyro_component_config"); @Override diff --git a/src/main/java/net/jcm/vsch/config/VSCHConfig.java b/src/main/java/net/jcm/vsch/config/VSCHConfig.java index f12efdef..61697bcf 100644 --- a/src/main/java/net/jcm/vsch/config/VSCHConfig.java +++ b/src/main/java/net/jcm/vsch/config/VSCHConfig.java @@ -183,6 +183,7 @@ public static Map getThrusterFuelConsumeRates() { return GSON.fromJson(fuels, STRING_INT_MAP_TYPE); } + @SuppressWarnings("removal") public static Set getAssembleBlacklistSet() { if (ASSEMBLE_BLACKLIST_SET == null) { ASSEMBLE_BLACKLIST_SET = Set.copyOf(ASSEMBLE_BLACKLIST.get().stream().map(ResourceLocation::new).toList()); diff --git a/src/main/java/net/jcm/vsch/items/IToggleableItem.java b/src/main/java/net/jcm/vsch/items/IToggleableItem.java index d238583f..ac09bb9a 100644 --- a/src/main/java/net/jcm/vsch/items/IToggleableItem.java +++ b/src/main/java/net/jcm/vsch/items/IToggleableItem.java @@ -1,13 +1,17 @@ package net.jcm.vsch.items; +import net.jcm.vsch.compat.CompatMods; import net.jcm.vsch.network.VSCHNetwork; import net.jcm.vsch.network.c2s.ToggleItemPacketC2S; +import net.jcm.vsch.util.VSCHUtils; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import java.util.function.Predicate; + public interface IToggleableItem { - void onToggle(Player owner, int slot, ItemStack stack); + void onToggle(Player owner, ItemStack stack); static boolean toggleSlot(final Player player, final int slot) { final ItemStack stack = player.getInventory().getItem(slot); @@ -17,10 +21,26 @@ static boolean toggleSlot(final Player player, final int slot) { if (!(stack.getItem() instanceof final IToggleableItem item)) { return false; } - item.onToggle(player, slot, stack); + item.onToggle(player, stack); if (player.level().isClientSide) { VSCHNetwork.sendToServer(new ToggleItemPacketC2S(slot)); } return true; } + + static boolean toggleCuriosSlot(final Player player, final String id, final Predicate tester) { + return VSCHUtils.testCuriosItems(player, id, (stack, slot) -> { + if (!(stack.getItem() instanceof final IToggleableItem item)) { + return false; + } + if (!tester.test(stack)) { + return false; + } + item.onToggle(player, stack); + if (player.level().isClientSide) { + VSCHNetwork.sendToServer(new ToggleItemPacketC2S(id, slot)); + } + return true; + }); + } } diff --git a/src/main/java/net/jcm/vsch/items/VSCHItems.java b/src/main/java/net/jcm/vsch/items/VSCHItems.java index 5b81cf9f..58c67059 100644 --- a/src/main/java/net/jcm/vsch/items/VSCHItems.java +++ b/src/main/java/net/jcm/vsch/items/VSCHItems.java @@ -1,14 +1,15 @@ package net.jcm.vsch.items; +import net.jcm.vsch.VSCHMod; +import net.jcm.vsch.VSCHTab; import net.jcm.vsch.items.custom.MagnetBootItem; +import net.jcm.vsch.items.custom.WrenchItem; + +import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.ArmorMaterials; import net.minecraft.world.item.Item; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.registries.DeferredRegister; -import net.jcm.vsch.VSCHMod; -import net.jcm.vsch.VSCHTab; -import net.minecraft.world.item.ArmorItem.Type; -import net.jcm.vsch.items.custom.WrenchItem; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; @@ -22,7 +23,7 @@ public class VSCHItems { public static final RegistryObject MAGNET_BOOT = ITEMS.register( "magnet_boot", - () -> new MagnetBootItem(ArmorMaterials.IRON, Type.BOOTS, new Item.Properties()) + () -> new MagnetBootItem(ArmorMaterials.IRON, ArmorItem.Type.BOOTS, new Item.Properties()) ); public static void register(IEventBus eventBus) { diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index c6f21d1d..ea682125 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -1,8 +1,11 @@ package net.jcm.vsch.items.custom; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.compat.CompatMods; +import net.jcm.vsch.compat.curios.MagnetBootCurio; import net.jcm.vsch.config.VSCHConfig; import net.jcm.vsch.items.IToggleableItem; +import net.jcm.vsch.util.VSCHUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -24,6 +27,11 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; + +import top.theillusivec4.curios.api.CuriosCapability; import org.joml.Matrix3f; import org.joml.Quaternionf; @@ -76,7 +84,17 @@ public boolean isWorking(final ItemStack stack) { public static boolean isMagnetized(final LivingEntity entity) { final ItemStack stack = entity.getItemBySlot(EquipmentSlot.FEET); - return !stack.isEmpty() && stack.getItem() instanceof final MagnetBootItem boot && boot.isWorking(stack); + if (!stack.isEmpty() && stack.getItem() instanceof final MagnetBootItem boot && boot.isWorking(stack)) { + return true; + } + if (!CompatMods.CURIOS.isLoaded()) { + return false; + } + return VSCHUtils.testCuriosItems( + entity, + "boots", + (stack2, slot) -> stack2.getItem() instanceof final MagnetBootItem boot && boot.isWorking(stack2) + ); } public Vec3 getDirection(final ItemStack stack) { @@ -91,7 +109,7 @@ public Vec3 getDirection(final ItemStack stack) { } @Override - public void onToggle(final Player owner, final int slot, final ItemStack stack) { + public void onToggle(final Player owner, final ItemStack stack) { final CompoundTag tag = stack.getOrCreateTag(); final boolean disable = !tag.getBoolean(TAG_DISABLED); tag.putBoolean(TAG_DISABLED, disable); @@ -106,12 +124,13 @@ public void onToggle(final Player owner, final int slot, final ItemStack stack) @Override public void inventoryTick(final ItemStack stack, final Level level, final Entity entity, final int slot, final boolean selected) { + this.onInventoryTick(stack, level, entity); + } + + public void onInventoryTick(final ItemStack stack, final Level level, final Entity entity) { if (!(entity instanceof final LivingEntity livingEntity)) { return; } - if (livingEntity.getItemBySlot(this.getEquipmentSlot()) != stack) { - return; - } // Ignore no physics entities if (entity.noPhysics || entity.isPassenger()) { @@ -184,6 +203,24 @@ public void inventoryTick(final ItemStack stack, final Level level, final Entity //level.addParticle(ParticleTypes.HEART, player.getX(), player.getY(), player.getZ(), 0, 0, 0); } + @Override + public ICapabilityProvider initCapabilities(final ItemStack stack, final CompoundTag nbt) { + return new ICapabilityProvider() { + private LazyOptional curiosCap = LazyOptional.empty(); + + @Override + public LazyOptional getCapability(final Capability cap, final Direction dir) { + if (CompatMods.CURIOS.isLoaded() && cap == CuriosCapability.ITEM) { + if (!this.curiosCap.isPresent()) { + this.curiosCap = LazyOptional.of(() -> new MagnetBootCurio(MagnetBootItem.this, stack)); + } + return this.curiosCap.cast(); + } + return LazyOptional.empty(); + } + }; + } + private static Quaternionf rotateTowards(final Quaternionf current, final Quaternionfc target) { final Quaternionf proj = projectedRotation(current, target); if (proj.conjugate(new Quaternionf()).mul(current).angle() < 0.025f) { diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index bc2991bd..f52189f7 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -6,6 +6,7 @@ import net.minecraft.client.player.Input; import net.minecraft.client.player.LocalPlayer; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.util.Mth; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; @@ -20,6 +21,14 @@ public abstract class MixinLocalPlayer extends MixinPlayer { @Shadow public Input input; + @Shadow + public float yBob; + @Shadow + public float xBob; + @Shadow + public float yBobO; + @Shadow + public float xBobO; @Shadow protected abstract boolean isControlledCamera(); @@ -68,6 +77,17 @@ public void serverAiStep(final CallbackInfo ci) { return; } this.yya = (this.input.jumping ? 1 : 0) + (this.input.shiftKeyDown ? -1 : 0); + // TODO: fix arm animation + this.xBob = this.xBobO + Mth.wrapDegrees(this.getViewXRot(1) - this.xBobO) * 0.5f; + this.yBob = this.yBobO + Mth.wrapDegrees(this.getViewYRot(1) - this.yBobO) * 0.5f; + while (this.yBob > 180) { + this.yBob -= 360; + this.yBobO -= 360; + } + while (this.yBob < -180) { + this.yBob += 360; + this.yBobO += 360; + } } @Inject(method = "moveTowardsClosestSpace", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java index 6f52439f..f117abcb 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java @@ -4,6 +4,10 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; + +import org.joml.Vector3d; +import org.joml.Quaternionf; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; @@ -24,6 +28,9 @@ protected MixinLivingEntity() { @Shadow protected int lerpSteps; + @Shadow + protected abstract float getJumpPower(); + @WrapOperation( method = "travel", slice = @Slice( @@ -48,9 +55,23 @@ public void travel(final LivingEntity self, double x, double y, double z, final at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;lerpSteps:I", opcode = Opcodes.PUTFIELD) ) public void aiStep$lerp(final CallbackInfo ci) { - if (!(this instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + if (!(this instanceof final FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { return; } frp.vsch$stepLerp(this.lerpSteps); } + + @Inject(method = "jumpFromGround", at = @At("HEAD"), cancellable = true) + protected void jumpFromGround(final CallbackInfo ci) { + if (!(this instanceof final FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return; + } + final Quaternionf rotation = frp.vsch$getBodyRotation(); + final Vec3 dm0 = this.getDeltaMovement(); + final Vector3d dm = rotation.transformInverse(new Vector3d(dm0.x, dm0.y, dm0.z)); + dm.y = this.getJumpPower(); + rotation.transform(dm); + this.setDeltaMovement(new Vec3(dm.x, dm.y, dm.z)); + this.hasImpulse = true; + } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 441e673d..dc8fcdbf 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -405,18 +405,10 @@ public void setPose(final Pose newPose) { this.oldPose = newPose; final float oldHeight = this.vsch$getVanillaDimensions(oldPose).height; final float newHeight = this.vsch$getVanillaDimensions(newPose).height; - final Vec3 pos = this.position(); - double dx = 0, dy = newHeight - oldHeight, dz = 0; - this.setPos(pos.x + dx, pos.y + dy, pos.z + dz); - } - - @Override - public boolean onGround() { - if (!this.vsch$isFreeRotating()) { - return super.onGround(); + if (oldHeight == newHeight) { + return; } - // TODO: fix on ground check - return false; + this.setPos(this.position().add(this.vsch$getDownVector().scale(oldHeight - newHeight))); } @WrapOperation( @@ -675,14 +667,6 @@ public Vec3 handleRelativeFrictionAndCalculateMovement(final Vec3 movement, fina move.mul(power); rotation.transform(move); this.setDeltaMovement(this.getDeltaMovement().add(move.x, move.y, move.z)); - if (bodyLocked && this.jumping && !this.isStayingOnGroundSurface() /*&& this.onGround()*/ && this.jumpCD == 0) { - final Vec3 dm0 = this.getDeltaMovement(); - final Vector3d dm = rotation.transformInverse(new Vector3d(dm0.x, dm0.y, dm0.z)); - dm.y = this.getJumpPower(); - rotation.transform(dm); - this.setDeltaMovement(new Vec3(dm.x, dm.y, dm.z)); - this.jumpCD = 10; - } } // this.setDeltaMovement(this.handleOnClimbable(this.getDeltaMovement())); this.move(MoverType.SELF, this.getDeltaMovement()); diff --git a/src/main/java/net/jcm/vsch/network/VSCHNetwork.java b/src/main/java/net/jcm/vsch/network/VSCHNetwork.java index 8253dac0..9eba5038 100644 --- a/src/main/java/net/jcm/vsch/network/VSCHNetwork.java +++ b/src/main/java/net/jcm/vsch/network/VSCHNetwork.java @@ -21,6 +21,7 @@ public final class VSCHNetwork { private VSCHNetwork() {} private static final String PROTOCOL_VERSION = VSCHMod.VERSION; + @SuppressWarnings("removal") public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(VSCHMod.MODID, "main"), () -> PROTOCOL_VERSION, diff --git a/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java b/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java index 372a7779..e4232fcf 100644 --- a/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java +++ b/src/main/java/net/jcm/vsch/network/c2s/ToggleItemPacketC2S.java @@ -1,5 +1,6 @@ package net.jcm.vsch.network.c2s; +import net.jcm.vsch.compat.CompatMods; import net.jcm.vsch.items.IToggleableItem; import net.jcm.vsch.network.INetworkPacket; @@ -8,20 +9,40 @@ import net.minecraft.world.item.ItemStack; import net.minecraftforge.network.NetworkEvent; +import top.theillusivec4.curios.api.CuriosApi; +import top.theillusivec4.curios.api.SlotResult; +import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; + public class ToggleItemPacketC2S implements INetworkPacket { + public final boolean isCurios; public final int slot; + public final String id; public ToggleItemPacketC2S(final int slot) { + this.isCurios = false; this.slot = slot; + this.id = null; + } + + public ToggleItemPacketC2S(final String id, final int slot) { + this.isCurios = true; + this.slot = slot; + this.id = id; } @Override public void encode(final FriendlyByteBuf buf) { + buf.writeBoolean(this.isCurios); buf.writeVarInt(this.slot); + if (this.isCurios) { + buf.writeUtf(this.id, 256); + } } public static ToggleItemPacketC2S decode(final FriendlyByteBuf buf) { - return new ToggleItemPacketC2S(buf.readVarInt()); + final boolean isCurios = buf.readBoolean(); + final int slot = buf.readVarInt(); + return isCurios ? new ToggleItemPacketC2S(buf.readUtf(256), slot) : new ToggleItemPacketC2S(slot); } @Override @@ -31,13 +52,36 @@ public void handle(final NetworkEvent.Context ctx) { if (player == null) { return; } + if (this.isCurios) { + if (!CompatMods.CURIOS.isLoaded()) { + return; + } + ctx.enqueueWork(() -> { + final ICuriosItemHandler curiosInv = CuriosApi.getCuriosInventory(player).orElse(null); + if (curiosInv == null) { + return; + } + final SlotResult result = curiosInv.findCurio(this.id, this.slot).orElse(null); + if (result == null) { + return; + } + final ItemStack stack = result.stack(); + if (stack.isEmpty()) { + return; + } + if (stack.getItem() instanceof final IToggleableItem item) { + item.onToggle(player, stack); + } + }); + return; + } ctx.enqueueWork(() -> { final ItemStack stack = player.getInventory().getItem(this.slot); if (stack.isEmpty()) { return; } if (stack.getItem() instanceof final IToggleableItem item) { - item.onToggle(player, this.slot, stack); + item.onToggle(player, stack); } }); } diff --git a/src/main/java/net/jcm/vsch/ship/ShipLandingAttachment.java b/src/main/java/net/jcm/vsch/ship/ShipLandingAttachment.java index 35d86a33..e88b1b11 100644 --- a/src/main/java/net/jcm/vsch/ship/ShipLandingAttachment.java +++ b/src/main/java/net/jcm/vsch/ship/ShipLandingAttachment.java @@ -57,6 +57,7 @@ private Map getLaunchPositions0() { return positions; } + @SuppressWarnings("removal") @JsonSetter("launchPositions") private void setLaunchPositions0(final Map positions) { this.launchPositions.clear(); diff --git a/src/main/java/net/jcm/vsch/util/CollisionUtil.java b/src/main/java/net/jcm/vsch/util/CollisionUtil.java index 84f7a944..0b872fd3 100644 --- a/src/main/java/net/jcm/vsch/util/CollisionUtil.java +++ b/src/main/java/net/jcm/vsch/util/CollisionUtil.java @@ -15,8 +15,8 @@ import java.util.List; public final class CollisionUtil { - private static final double EPS = 1e-7; - private static final double EPS2 = 1e-2; + private static final double EPS = 1e-6; + private static final double EPS2 = 1.0 / 64; private static final AxisSet AXES = new AxisSet( new Vector3d(-1, 0, 0), new Vector3d(1, 0, 0), diff --git a/src/main/java/net/jcm/vsch/util/VSCHUtils.java b/src/main/java/net/jcm/vsch/util/VSCHUtils.java index a4d28083..36b48f9b 100644 --- a/src/main/java/net/jcm/vsch/util/VSCHUtils.java +++ b/src/main/java/net/jcm/vsch/util/VSCHUtils.java @@ -1,6 +1,7 @@ package net.jcm.vsch.util; import net.jcm.vsch.VSCHMod; +import net.jcm.vsch.compat.CompatMods; import net.lointain.cosmos.network.CosmosModVariables; import net.lointain.cosmos.procedures.DistanceOrderProviderProcedure; @@ -25,6 +26,7 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.targeting.TargetingConditions; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.phys.AABB; @@ -52,10 +54,16 @@ import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; +import top.theillusivec4.curios.api.CuriosApi; +import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; +import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; +import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BiPredicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -129,6 +137,7 @@ public static AABBd transformToAABBd(final ShipTransform transform, final AABBic * @param dimension The dimension ID string in format registry_namespace:registry_name:dimension_namespace:dimension_name * @return A {@link net.minecraft.server.level.ServerLevel ServerLevel} instance with the dimension ID given */ + @SuppressWarnings("removal") public static ServerLevel registeryDimToLevel(final String dimension) { // Split 'minecraft:dimension:namespace:dimension_name' into [minecraft, dimension, namespace, dimension_name] final String[] parts = dimension.split(":"); @@ -139,6 +148,7 @@ public static ServerLevel registeryDimToLevel(final String dimension) { return ValkyrienSkiesMod.getCurrentServer().getLevel(levelId); } + @SuppressWarnings("removal") public static ServerLevel dimToLevel(final String dimensionString) { return ValkyrienSkiesMod.getCurrentServer().getLevel(ResourceKey.create(Registries.DIMENSION, new ResourceLocation(dimensionString))); } @@ -180,4 +190,30 @@ public static List getLoadedShipsInLevel(ServerLevel level) { public static Component getWarningComponent() { return Component.translatable(VSCHMod.MODID+".tooltip.may_need_fuel_or_energy").withStyle(ChatFormatting.ITALIC, ChatFormatting.GRAY); } + + + public static boolean testCuriosItems(final LivingEntity entity, final String id, final BiPredicate tester) { + if (!CompatMods.CURIOS.isLoaded()) { + return false; + } + final ICuriosItemHandler curiosInv = CuriosApi.getCuriosInventory(entity).orElse(null); + if (curiosInv == null) { + return false; + } + final ICurioStacksHandler stacksHandler = curiosInv.getStacksHandler(id).orElse(null); + if (stacksHandler == null) { + return false; + } + final IDynamicStackHandler stacks = stacksHandler.getStacks(); + for (int slot = 0; slot < stacks.getSlots(); slot++) { + final ItemStack stack = stacks.getStackInSlot(slot); + if (stack.isEmpty()) { + continue; + } + if (tester.test(stack, slot)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/net/jcm/vsch/util/wapi/LevelData.java b/src/main/java/net/jcm/vsch/util/wapi/LevelData.java index c469bd35..0c53bcf2 100644 --- a/src/main/java/net/jcm/vsch/util/wapi/LevelData.java +++ b/src/main/java/net/jcm/vsch/util/wapi/LevelData.java @@ -53,6 +53,7 @@ public static LevelData get0(final Level level) { return get1(CosmosModVariables.WorldVariables.get(level), level.dimension(), new HashMap<>()); } + @SuppressWarnings("removal") private static LevelData get1( final CosmosModVariables.WorldVariables worldVars, final ResourceKey dimension, diff --git a/src/main/resources/assets/vsch/lang/en_us.json b/src/main/resources/assets/vsch/lang/en_us.json index 2c45d71d..782978f6 100644 --- a/src/main/resources/assets/vsch/lang/en_us.json +++ b/src/main/resources/assets/vsch/lang/en_us.json @@ -27,6 +27,7 @@ "config.jade.plugin_vsch.gyro_component_config": "Gyro Mode Details", "config.jade.plugin_vsch.thruster_component_config": "Thruster Mode Details", + "curios.identifier.boots": "Boots", "vsch.ponder.drag_inducer.header": "Drag inducer", "vsch.ponder.drag_inducer.text_1": "Sometimes, it can be hard to slow down in space", diff --git a/src/main/resources/assets/vsch/lang/zh_cn.json b/src/main/resources/assets/vsch/lang/zh_cn.json index a7214ff3..5572156d 100644 --- a/src/main/resources/assets/vsch/lang/zh_cn.json +++ b/src/main/resources/assets/vsch/lang/zh_cn.json @@ -27,6 +27,7 @@ "config.jade.plugin_vsch.gyro_component_config": "陀螺仪配置信息", "config.jade.plugin_vsch.thruster_component_config": "推进器配置信息", + "curios.identifier.boots": "靴子", "vsch.ponder.drag_inducer.header": "阻力发生器", "vsch.ponder.drag_inducer.text_1": "有时, 在太空中很难停下来", diff --git a/src/main/resources/data/curios/tags/items/boots.json b/src/main/resources/data/curios/tags/items/boots.json new file mode 100644 index 00000000..6952b0e9 --- /dev/null +++ b/src/main/resources/data/curios/tags/items/boots.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "vsch:magnet_boot" + ] +} diff --git a/src/main/resources/data/vsch/curios/entities/entities.json b/src/main/resources/data/vsch/curios/entities/entities.json new file mode 100644 index 00000000..5cf8b9a5 --- /dev/null +++ b/src/main/resources/data/vsch/curios/entities/entities.json @@ -0,0 +1,8 @@ +{ + "entities": [ + "minecraft:player" + ], + "slots": [ + "boots" + ] +} diff --git a/src/main/resources/data/vsch/curios/slots/boots.json b/src/main/resources/data/vsch/curios/slots/boots.json new file mode 100644 index 00000000..80d8fb1e --- /dev/null +++ b/src/main/resources/data/vsch/curios/slots/boots.json @@ -0,0 +1,5 @@ +{ + "order": 190, + "icon": "minecraft:item/empty_armor_slot_boots", + "validators": ["curios:tag"] +} From 7ce729871b29910940b53806b83b02f63ac826d4 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 22 Sep 2025 13:18:31 -0600 Subject: [PATCH 27/32] sprint to rotate body back to view vector --- .../vsch/mixin/client/MixinLocalPlayer.java | 9 ++ .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 93 +++++++++++-------- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index f52189f7..7082c56b 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -16,6 +16,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(LocalPlayer.class) public abstract class MixinLocalPlayer extends MixinPlayer { @@ -97,4 +98,12 @@ private void moveTowardsClosestSpace(final double x, final double z, final Callb return; } } + + @Inject(method = "canStartSprinting", at = @At("HEAD"), cancellable = true) + private void canStartSprinting(final CallbackInfoReturnable cir) { + if (!this.vsch$isFreeRotating()) { + return; + } + cir.setReturnValue(false); + } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index dc8fcdbf..53a2021f 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -14,6 +14,7 @@ import net.jcm.vsch.util.wapi.LevelData; import com.mojang.authlib.GameProfile; +import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; @@ -443,9 +444,11 @@ public void turn(final double x, final double y) { boolean lockHeadRotate = false; final boolean lockBodyRotate = this.isBodyRotationLocked(); if (isClientSide) { + final long now = System.nanoTime(); if (VSCHKeyBindings.UNLOCK_HEAD_ROTATION.consumeDoubleClick()) { this.headPitch = 0; this.reCalcHeadRotation(); + this.lastRollTime = now; return; } if (!lockBodyRotate) { @@ -457,55 +460,67 @@ public void turn(final double x, final double y) { if (VSCHKeyBindings.ROLL_COUNTER_CLOCKWISE.isDown()) { rollDir--; } - long now = System.nanoTime(); roll = rollDir * VSCHClientConfig.PLAYER_ROLL_SPEED.get().floatValue() * Math.min((float) ((now - this.lastRollTime) / 1.0e9), 0.1f) * Mth.DEG_TO_RAD; - this.lastRollTime = now; } + this.lastRollTime = now; } - if (x == 0 && y == 0 && roll == 0) { - return; - } - final float yaw = -(float)(x) * 0.15f * Mth.DEG_TO_RAD; - final float pitch = (float)(y) * 0.15f * Mth.DEG_TO_RAD; - - final Quaternionf relRotation = new Quaternionf(); - float newHeadYaw = this.headYaw + yaw; - if (newHeadYaw > BODY_ROT_CLAMP) { - relRotation.rotationY(newHeadYaw - BODY_ROT_CLAMP); - newHeadYaw = BODY_ROT_CLAMP; - } else if (newHeadYaw < -BODY_ROT_CLAMP) { - relRotation.rotationY(newHeadYaw + BODY_ROT_CLAMP); - newHeadYaw = -BODY_ROT_CLAMP; - } - this.headYawO += newHeadYaw - this.headYaw; - this.headYaw = newHeadYaw; - if (lockHeadRotate) { - if (!lockBodyRotate) { - relRotation.rotateX(pitch); - } - } else { - float newHeadPitch = this.headPitch + pitch; - if (newHeadPitch > Mth.HALF_PI) { + if (x != 0 || y != 0 || roll != 0) { + final float yaw = -(float)(x) * 0.15f * Mth.DEG_TO_RAD; + final float pitch = (float)(y) * 0.15f * Mth.DEG_TO_RAD; + + final Quaternionf relRotation = new Quaternionf(); + if (lockHeadRotate) { + relRotation.rotationY(yaw); if (!lockBodyRotate) { - relRotation.rotateX(newHeadPitch - Mth.HALF_PI); + relRotation.rotateX(pitch); } - newHeadPitch = Mth.HALF_PI; - } else if (newHeadPitch < -Mth.HALF_PI) { - if (!lockBodyRotate) { - relRotation.rotateX(newHeadPitch + Mth.HALF_PI); + } else { + float newHeadYaw = this.headYaw + yaw; + if (newHeadYaw > BODY_ROT_CLAMP) { + relRotation.rotationY(newHeadYaw - BODY_ROT_CLAMP); + newHeadYaw = BODY_ROT_CLAMP; + } else if (newHeadYaw < -BODY_ROT_CLAMP) { + relRotation.rotationY(newHeadYaw + BODY_ROT_CLAMP); + newHeadYaw = -BODY_ROT_CLAMP; } - newHeadPitch = -Mth.HALF_PI; + this.headYawO += newHeadYaw - this.headYaw; + this.headYaw = newHeadYaw; + + float newHeadPitch = this.headPitch + pitch; + if (newHeadPitch > Mth.HALF_PI) { + if (!lockBodyRotate) { + relRotation.rotateX(newHeadPitch - Mth.HALF_PI); + } + newHeadPitch = Mth.HALF_PI; + } else if (newHeadPitch < -Mth.HALF_PI) { + if (!lockBodyRotate) { + relRotation.rotateX(newHeadPitch + Mth.HALF_PI); + } + newHeadPitch = -Mth.HALF_PI; + } + this.headPitchO += newHeadPitch - this.headPitch; + this.headPitch = newHeadPitch; } - this.headPitchO += newHeadPitch - this.headPitch; - this.headPitch = newHeadPitch; - } - if (!lockBodyRotate) { - relRotation.rotateZ(roll); + if (!lockBodyRotate) { + relRotation.rotateZ(roll); + if (lockHeadRotate) { + final Quaternionf headLocal = new Quaternionf().rotationYXZ(this.headYaw, this.headPitch, 0); + relRotation.premul(headLocal); + relRotation.mul(headLocal.conjugate()); + } + } + + this.vsch$setBodyRotation(this.vsch$getBodyRotation().mul(relRotation).normalize()); + this.vsch$setBodyRotationO(this.rotationO.mul(relRotation).normalize()); } - this.vsch$setBodyRotation(this.vsch$getBodyRotation().mul(relRotation).normalize()); - this.vsch$setBodyRotationO(this.rotationO.mul(relRotation).normalize()); + if (isClientSide && !lockBodyRotate && Minecraft.getInstance().options.keySprint.isDown()) { + final Quaternionf headRotation = this.vsch$getHeadRotation(); + this.headPitch = -Mth.HALF_PI; + this.headYaw = 0; + this.vsch$setBodyRotation(this.vsch$getBodyRotation().set(headRotation).rotateX(Mth.HALF_PI)); + } } @Override From 16fba30eacab72bb0dcb0abc87b09fc7f65d30e9 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 22 Sep 2025 14:30:53 -0600 Subject: [PATCH 28/32] fix jump --- .../accessor/FreeRotatePlayerAccessor.java | 2 +- .../vsch/accessor/LivingEntityAccessor.java | 5 +++ .../jcm/vsch/items/custom/MagnetBootItem.java | 22 ++++++---- .../vsch/mixin/client/MixinLocalPlayer.java | 2 +- .../mixin/minecraft/MixinLivingEntity.java | 22 +++++++++- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 15 ++++--- .../MixinServerGamePacketListenerImpl.java | 42 +++++++++++++++++++ 7 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/accessor/LivingEntityAccessor.java diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 92cf3ef3..9abb2b66 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -7,7 +7,7 @@ import org.joml.Quaternionf; import org.joml.Vector3d; -public interface FreeRotatePlayerAccessor { +public interface FreeRotatePlayerAccessor extends LivingEntityAccessor { EntityDimensions vsch$getVanillaDimensions(Pose pose); boolean vsch$isFreeRotating(); diff --git a/src/main/java/net/jcm/vsch/accessor/LivingEntityAccessor.java b/src/main/java/net/jcm/vsch/accessor/LivingEntityAccessor.java new file mode 100644 index 00000000..3e01bc75 --- /dev/null +++ b/src/main/java/net/jcm/vsch/accessor/LivingEntityAccessor.java @@ -0,0 +1,5 @@ +package net.jcm.vsch.accessor; + +public interface LivingEntityAccessor { + int vsch$getTickSinceLastJump(); +} diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index ea682125..ce162d38 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -1,6 +1,7 @@ package net.jcm.vsch.items.custom; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.accessor.LivingEntityAccessor; import net.jcm.vsch.compat.CompatMods; import net.jcm.vsch.compat.curios.MagnetBootCurio; import net.jcm.vsch.config.VSCHConfig; @@ -117,21 +118,20 @@ public void onToggle(final Player owner, final ItemStack stack) { return; } owner.displayClientMessage( - Component.translatable(disable ? "vsch.message.magnet_boot.disabled" : "vsch.message.magnet_boot.enabled"), + Component.translatable(disable ? "vsch.message.magnet_boot.disabled" : "vsch.message.magnet_boot.enabled"), true ); } @Override public void inventoryTick(final ItemStack stack, final Level level, final Entity entity, final int slot, final boolean selected) { - this.onInventoryTick(stack, level, entity); - } - - public void onInventoryTick(final ItemStack stack, final Level level, final Entity entity) { if (!(entity instanceof final LivingEntity livingEntity)) { return; } + this.onInventoryTick(stack, level, livingEntity); + } + public void onInventoryTick(final ItemStack stack, final Level level, final LivingEntity entity) { // Ignore no physics entities if (entity.noPhysics || entity.isPassenger()) { return; @@ -182,12 +182,16 @@ public void onInventoryTick(final ItemStack stack, final Level level, final Enti // mAtH final double distance = startPos.distanceToSqr(hitResult.getLocation()); - final double scaledForce = Math.min(maxDistance * maxDistance / distance * MIN_FORCE, getMaxForce()); + if (((LivingEntityAccessor) (entity)).vsch$getTickSinceLastJump() > 15 || distance > 1.5) { + final double scaledForce = Math.min(maxDistance * maxDistance / distance * MIN_FORCE, getMaxForce()); - final Vec3 force = direction.scale(scaledForce); - tag.putDouble("Force", scaledForce); + final Vec3 force = direction.scale(scaledForce); + tag.putDouble("Force", scaledForce); - entity.push(force.x, force.y, force.z); + entity.push(force.x, force.y, force.z); + } else { + tag.putDouble("Force", 0); + } if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { final BlockPos blockPos = blockHit.getBlockPos(); diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java index 7082c56b..c7994930 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinLocalPlayer.java @@ -77,7 +77,7 @@ public void serverAiStep(final CallbackInfo ci) { if (!this.vsch$isFreeRotating() || this.getAbilities().flying || !this.isControlledCamera()) { return; } - this.yya = (this.input.jumping ? 1 : 0) + (this.input.shiftKeyDown ? -1 : 0); + this.yya = this.isBodyRotationLocked() ? 0 : ((this.input.jumping ? 1 : 0) + (this.input.shiftKeyDown ? -1 : 0)); // TODO: fix arm animation this.xBob = this.xBobO + Mth.wrapDegrees(this.getViewXRot(1) - this.xBobO) * 0.5f; this.yBob = this.yBobO + Mth.wrapDegrees(this.getViewYRot(1) - this.yBobO) * 0.5f; diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java index f117abcb..2765b743 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java @@ -1,6 +1,7 @@ package net.jcm.vsch.mixin.minecraft; import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; +import net.jcm.vsch.accessor.LivingEntityAccessor; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; @@ -14,23 +15,31 @@ import org.objectweb.asm.Opcodes; 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.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LivingEntity.class) -public abstract class MixinLivingEntity extends Entity { +public abstract class MixinLivingEntity extends Entity implements LivingEntityAccessor { protected MixinLivingEntity() { super(null, null); } @Shadow protected int lerpSteps; + @Unique + private int tickSinceLastJump = Integer.MAX_VALUE; @Shadow protected abstract float getJumpPower(); + @Override + public int vsch$getTickSinceLastJump() { + return this.tickSinceLastJump; + } + @WrapOperation( method = "travel", slice = @Slice( @@ -47,6 +56,15 @@ public void travel(final LivingEntity self, double x, double y, double z, final operation.call(self, x, y, z); } + @Inject(method = "aiStep", at = @At("HEAD")) + public void aiStep(final CallbackInfo ci) { + if (this.tickSinceLastJump < 100) { + this.tickSinceLastJump++; + } else { + this.tickSinceLastJump = Integer.MAX_VALUE; + } + } + @Inject( method = "aiStep", slice = @Slice( @@ -73,5 +91,7 @@ protected void jumpFromGround(final CallbackInfo ci) { rotation.transform(dm); this.setDeltaMovement(new Vec3(dm.x, dm.y, dm.z)); this.hasImpulse = true; + this.tickSinceLastJump = 0; + ci.cancel(); } } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 53a2021f..d1830b2d 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -122,8 +122,6 @@ public abstract class MixinPlayer extends LivingEntity implements FreeRotatePlay @Unique private long lastRollTime; @Unique - private int jumpCD = 0; - @Unique private float nextStep = 0; protected MixinPlayer() { @@ -427,7 +425,7 @@ public void setPose(final Pose newPose) { } @Unique - private boolean isBodyRotationLocked() { + protected boolean isBodyRotationLocked() { return MagnetBootItem.isMagnetized(this); } @@ -606,10 +604,14 @@ protected void checkInsideBlocks() { @Override protected void checkSupportingBlock(final boolean onGround, final Vec3 movement) { - if (!onGround) { + if (!this.vsch$isFreeRotating()) { super.checkSupportingBlock(onGround, movement); return; } + if (!onGround) { + this.mainSupportingBlockPos = Optional.empty(); + return; + } final AABB bb = this.getBoundingBox(); final AABB movedBB = movement == null ? bb @@ -817,13 +819,13 @@ private Vec3 betterCollide(Vec3 movement) { final Matrix4dc worldToEntity = entityToWorld.invert(new Matrix4d()); final Vector3d newMovement = new Vector3d(movement.x, movement.y, movement.z); worldToEntity.transformDirection(newMovement); - final AABBd checkBox = CollisionUtil.expandTowards(new AABBd(box), newMovement).transform(entityToWorld); CollisionUtil.checkCollision(newMovement, level, this, box, worldToEntity, entityToWorld); final Matrix4d entityToShip = new Matrix4d(); final Matrix4d shipToEntity = new Matrix4d(); final String dimId = VSGameUtilsKt.getDimensionId(level); + final AABBd checkBox = CollisionUtil.expandTowards(new AABBd(box), newMovement).transform(entityToWorld); for (final LoadedShip ship : VSGameUtilsKt.getShipObjectWorld(level).getLoadedShips()) { if (!ship.getChunkClaimDimension().equals(dimId)) { continue; @@ -985,9 +987,6 @@ public void baseTick() { if (this.firstTick) { this.updateDefaultFreeRotation(); } - if (this.jumpCD > 0) { - this.jumpCD--; - } super.baseTick(); final boolean freeRotation = this.vsch$isFreeRotating(); if (freeRotation != this.wasFreeRotating) { diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java index e83ec995..c26e1816 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinServerGamePacketListenerImpl.java @@ -8,13 +8,18 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.joml.Vector3d; + import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ServerGamePacketListenerImpl.class) @@ -61,6 +66,43 @@ public abstract class MixinServerGamePacketListenerImpl { frp.vsch$setHeadYaw(packetAccessor.vsch$getHeadYaw()); } + @WrapOperation( + method = "handleMovePlayer", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getBoundingBox()Lnet/minecraft/world/phys/AABB;"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;jumpFromGround()V") + ), + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerPlayer;onGround()Z" + ) + ) + public boolean handleMovePlayer$jumpFromGround( + final ServerPlayer player, + final Operation operation, + @Local(argsOnly = true) final ServerboundMovePlayerPacket packet, + @Local final LocalBooleanRef jumped + ) { + if (!operation.call(player)) { + return false; + } + if (!(player instanceof final FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return true; + } + final Vector3d movement = new Vector3d(packet.getX(player.getX()), packet.getY(player.getY()), packet.getZ(player.getZ())); + frp.vsch$getBodyRotation().transformInverse(movement); + final boolean movedUp = movement.y > 0; + jumped.set(movedUp); + if (!movedUp) { + return false; + } + if (packet.isOnGround()) { + return false; + } + player.jumpFromGround(); + return false; + } + @ModifyExpressionValue( method = "teleport(DDDFFLjava/util/Set;)V", at = @At(value = "NEW", target = "Lnet/minecraft/network/protocol/game/ClientboundPlayerPositionPacket;") From 44c5d8da86c332661bd7982e0ee632759ef07cc3 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 23 Sep 2025 09:12:26 -0600 Subject: [PATCH 29/32] implement maybeBackOffFromEdge --- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 88 +++++++++++++++++-- .../java/net/jcm/vsch/util/CollisionUtil.java | 83 +++++++++++++++++ 2 files changed, 163 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index d1830b2d..3e5451e7 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -134,6 +134,9 @@ protected MixinPlayer() { @Shadow protected abstract boolean isStayingOnGroundSurface(); + @Shadow + protected abstract boolean isAboveGround(); + @Inject(method = "", at = @At("RETURN")) public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { final Player player = (Player)((Object)(this)); @@ -824,8 +827,8 @@ private Vec3 betterCollide(Vec3 movement) { final Matrix4d entityToShip = new Matrix4d(); final Matrix4d shipToEntity = new Matrix4d(); - final String dimId = VSGameUtilsKt.getDimensionId(level); final AABBd checkBox = CollisionUtil.expandTowards(new AABBd(box), newMovement).transform(entityToWorld); + final String dimId = VSGameUtilsKt.getDimensionId(level); for (final LoadedShip ship : VSGameUtilsKt.getShipObjectWorld(level).getLoadedShips()) { if (!ship.getChunkClaimDimension().equals(dimId)) { continue; @@ -896,6 +899,12 @@ public void dismountTo(final double x, final double y, final double z) { super.dismountTo(x, y + oldHeight - newHeight, z); } + @Override + protected void setLevel(final Level level) { + super.setLevel(level); + this.updateDefaultFreeRotation(); + } + @Override protected void moveTowardsClosestSpace(final double x, final double y, final double z) { if (!this.vsch$isFreeRotating()) { @@ -929,18 +938,81 @@ public void getDimensions(final Pose pose, final CallbackInfoReturnable cir) { + protected void maybeBackOffFromEdge(final Vec3 movement, final MoverType moverType, final CallbackInfoReturnable cir) { if (!this.vsch$isFreeRotating()) { return; } - // TODO: implement edge backoff in space, may respect to delta movement - cir.setReturnValue(movement); + cir.setReturnValue(this.maybeBackOffFromEdgeImpl(movement, moverType)); } - @Override - protected void setLevel(final Level level) { - super.setLevel(level); - this.updateDefaultFreeRotation(); + @Unique + private Vec3 maybeBackOffFromEdgeImpl(final Vec3 movement, final MoverType moverType) { + if (movement.y > 0) { + return movement; + } + if (moverType != MoverType.SELF && moverType != MoverType.PLAYER) { + return movement; + } + if (!this.isStayingOnGroundSurface() || !this.isAboveGround()) { + return movement; + } + if (!this.isBodyRotationLocked()) { + return movement; + } + final Quaternionf rotation = this.vsch$getBodyRotation(); + final Vector3d relMove = rotation.transformInverse(new Vector3d(movement.x, movement.y, movement.z)); + final double maxDownStep = -this.maxUpStep(); + final double step = 0.05; + final double moveY = relMove.y; + double moveX = relMove.x; + double moveZ = relMove.z; + while (moveX != 0 && !this.hasCollisionAfterMovement(relMove.set(moveX, maxDownStep, 0))) { + if (Math.abs(moveX) < step) { + moveX = 0; + break; + } + moveX -= step * Math.signum(moveX); + } + + while (moveZ != 0 && !this.hasCollisionAfterMovement(relMove.set(0, maxDownStep, moveZ))) { + if (Math.abs(moveZ) < step) { + moveZ = 0; + break; + } + moveZ -= step * Math.signum(moveZ); + } + + while (moveX != 0 && moveZ != 0 && !this.hasCollisionAfterMovement(relMove.set(moveX, maxDownStep, moveZ))) { + if (Math.abs(moveX) < step) { + moveX = 0; + break; + } + moveX -= step * Math.signum(moveX); + + if (Math.abs(moveZ) < step) { + moveZ = 0; + break; + } + moveZ -= step * Math.signum(moveZ); + } + rotation.transform(relMove.set(moveX, moveY, moveZ)); + return new Vec3(relMove.x, relMove.y, relMove.z); + } + + @Unique + private boolean hasCollisionAfterMovement(final Vector3d movement) { + final Level level = this.level(); + final Vec3 position = this.vsch$getHeadCenter(); + final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); + final double deflate = 0.07; + final AABBd box = new AABBd( + -dimensions.width / 2 + deflate, 0.6 / 2 - dimensions.height, -dimensions.width / 2 + deflate, + dimensions.width / 2 - deflate, 0.6 / 2, dimensions.width / 2 - deflate + ); + final Matrix4dc entityToWorld = new Matrix4d() + .translation(position.x, position.y, position.z) + .rotate(this.vsch$getBodyRotation()); + return CollisionUtil.willCollideAny(level, this, box, entityToWorld, movement); } @Override diff --git a/src/main/java/net/jcm/vsch/util/CollisionUtil.java b/src/main/java/net/jcm/vsch/util/CollisionUtil.java index 0b872fd3..437a9587 100644 --- a/src/main/java/net/jcm/vsch/util/CollisionUtil.java +++ b/src/main/java/net/jcm/vsch/util/CollisionUtil.java @@ -2,14 +2,18 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.CollisionGetter; +import net.minecraft.world.level.Level; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; import org.joml.Vector3d; import org.joml.Vector3dc; +import org.joml.Matrix4d; import org.joml.Matrix4dc; import org.joml.primitives.AABBd; +import org.valkyrienskies.core.api.ships.LoadedShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; import java.util.ArrayList; import java.util.List; @@ -26,6 +30,85 @@ public final class CollisionUtil { private CollisionUtil() {} + public static boolean willCollide( + final CollisionGetter level, + final Entity entity, + final AABBd box, + final Matrix4dc box2voxel, + final Matrix4dc voxel2box, + final Vector3d movement + ) { + final Vector3d movementInVoxel = box2voxel.transformDirection(movement, new Vector3d()); + final AABBd box1 = box.translate(movement, new AABBd()); + final AABBd box2 = box.transform(box2voxel, new AABBd()).translate(movementInVoxel); + final boolean[] intersect = new boolean[1]; + for (final VoxelShape shape : level.getBlockCollisions(entity, toAABB(box2))) { + if (shape.isEmpty()) { + continue; + } + final AABBd voxel = toAABBd(shape.bounds()); + if (!box2.intersectsAABB(voxel)) { + continue; + } + if (!voxel.transform(voxel2box).intersectsAABB(box1)) { + continue; + } + final AABBd voxelInBox = new AABBd(); + shape.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { + if (intersect[0]) { + return; + } + voxel + .setMin(minX, minY, minZ) + .setMax(maxX, maxY, maxZ); + if (!box2.intersectsAABB(voxel)) { + return; + } + voxel.transform(voxel2box, voxelInBox); + if (!box1.intersectsAABB(voxelInBox)) { + return; + } + intersect[0] = true; + }); + if (intersect[0]) { + return true; + } + } + return false; + } + + public static boolean willCollideAny( + final Level level, + final Entity entity, + final AABBd box, + final Matrix4dc box2world, + final Vector3d movement + ) { + final Matrix4dc world2box = box2world.invert(new Matrix4d()); + if (willCollide(level, entity, box, box2world, world2box, movement)) { + return true; + } + + final Matrix4d entityToShip = new Matrix4d(); + final Matrix4d shipToEntity = new Matrix4d(); + final AABBd checkBox = box.transform(box2world, new AABBd()); + final String dimId = VSGameUtilsKt.getDimensionId(level); + for (final LoadedShip ship : VSGameUtilsKt.getShipObjectWorld(level).getLoadedShips()) { + if (!ship.getChunkClaimDimension().equals(dimId)) { + continue; + } + if (!ship.getWorldAABB().intersectsAABB(checkBox)) { + continue; + } + entityToShip.set(ship.getWorldToShip()).mul(box2world); + shipToEntity.set(world2box).mul(ship.getShipToWorld()); + if (willCollide(level, entity, box, entityToShip, shipToEntity, movement)) { + return true; + } + } + return false; + } + public static Vector3d checkCollision( final Vector3d movement, final CollisionGetter level, From c9f347167a46b4f93dbbd9e281af37c04e851710 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 23 Sep 2025 17:35:45 -0600 Subject: [PATCH 30/32] make magnet boots working at edge --- .../accessor/FreeRotatePlayerAccessor.java | 6 + .../jcm/vsch/items/custom/MagnetBootItem.java | 128 +++++++++++++----- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 105 +++++++++----- .../java/net/jcm/vsch/util/CollisionUtil.java | 67 ++++++++- 4 files changed, 238 insertions(+), 68 deletions(-) diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index 9abb2b66..fd4a0bba 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -1,11 +1,15 @@ package net.jcm.vsch.accessor; +import net.minecraft.core.BlockPos; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.Pose; import net.minecraft.world.phys.Vec3; import org.joml.Quaternionf; import org.joml.Vector3d; +import org.joml.primitives.AABBd; + +import java.util.function.Consumer; public interface FreeRotatePlayerAccessor extends LivingEntityAccessor { EntityDimensions vsch$getVanillaDimensions(Pose pose); @@ -56,6 +60,8 @@ public interface FreeRotatePlayerAccessor extends LivingEntityAccessor { boolean vsch$hasSupportingBlock(); + BlockPos vsch$findSupportingBlock(Consumer boxModifier); + void vsch$setOldPosAndRot(); void vsch$stepLerp(int steps); diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index ce162d38..04e60924 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -6,6 +6,7 @@ import net.jcm.vsch.compat.curios.MagnetBootCurio; import net.jcm.vsch.config.VSCHConfig; import net.jcm.vsch.items.IToggleableItem; +import net.jcm.vsch.util.CollisionUtil; import net.jcm.vsch.util.VSCHUtils; import net.minecraft.core.BlockPos; @@ -37,6 +38,7 @@ import org.joml.Matrix3f; import org.joml.Quaternionf; import org.joml.Quaternionfc; +import org.joml.Vector3d; import org.joml.Vector3f; import org.valkyrienskies.core.api.ships.Ship; import org.valkyrienskies.mod.common.VSGameUtilsKt; @@ -45,6 +47,7 @@ public class MagnetBootItem extends ArmorItem implements IToggleableItem { private static final String TAG_DISABLED = "Disabled"; private static final String TAG_READY = "Ready"; private static final String TAG_DIRECTION = "Direction"; + private static final String TAG_ATTACHING = "Attaching"; private static final double MIN_FORCE = 0.01; public MagnetBootItem(ArmorMaterial pMaterial, Type pType, Properties pProperties) { @@ -83,14 +86,22 @@ public boolean isWorking(final ItemStack stack) { return tag != null && !tag.getBoolean(TAG_DISABLED) && tag.getBoolean(TAG_READY); } + public Long getWorkingShip(final ItemStack stack) { + if (!(stack.getItem() instanceof MagnetBootItem)) { + return null; + } + final CompoundTag tag = stack.getTag(); + if (tag == null || tag.getBoolean(TAG_DISABLED) || !tag.getBoolean(TAG_READY)) { + return null; + } + return tag.contains(TAG_ATTACHING) ? tag.getLong(TAG_ATTACHING) : null; + } + public static boolean isMagnetized(final LivingEntity entity) { final ItemStack stack = entity.getItemBySlot(EquipmentSlot.FEET); if (!stack.isEmpty() && stack.getItem() instanceof final MagnetBootItem boot && boot.isWorking(stack)) { return true; } - if (!CompatMods.CURIOS.isLoaded()) { - return false; - } return VSCHUtils.testCuriosItems( entity, "boots", @@ -146,43 +157,48 @@ public void onInventoryTick(final ItemStack stack, final Level level, final Livi final double maxDistance = this.getAttractDistance(); - Vec3 startPos = entity.position(); // Starting position (player's feet position) - Vec3 direction = new Vec3(0, -1, 0); - if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { - startPos = frp.vsch$getFeetPosition(); - direction = frp.vsch$getDownVector(); - } - - final Vec3 endPos = startPos.add(direction.scale(maxDistance)); // End position (straight down) + final FreeRotatePlayerAccessor frp = entity instanceof final FreeRotatePlayerAccessor frp0 && frp0.vsch$isFreeRotating() ? frp0 : null; + final Vec3 startPos = frp != null ? frp.vsch$getFeetPosition() : entity.position(); + final Vec3 direction = frp != null ? frp.vsch$getDownVector() : new Vec3(0, -1, 0); - final HitResult hitResult = level.clip(new ClipContext( - startPos, - endPos, - ClipContext.Block.COLLIDER, - ClipContext.Fluid.NONE, - entity - )); + final BlockPos blockPos = frp != null + ? frp.vsch$findSupportingBlock((detectBox) -> { + detectBox.maxY = detectBox.minY + 0.1; + detectBox.minY -= maxDistance; + }) + : CollisionUtil.findSupportingBlockNoOrientation(entity, maxDistance); - if (hitResult.getType() != HitResult.Type.BLOCK) { + if (blockPos == null) { if (wasReady) { tag.putBoolean(TAG_READY, false); tag.remove(TAG_DIRECTION); } return; } + if (!wasReady) { tag.putBoolean(TAG_READY, true); - Vec3.CODEC.encodeStart(NbtOps.INSTANCE, direction).result().ifPresent(pos -> tag.put(TAG_DIRECTION, pos)); + tag.put(TAG_DIRECTION, Vec3.CODEC.encodeStart(NbtOps.INSTANCE, direction).result().orElse(null)); } if (disabled) { return; } - final BlockHitResult blockHit = ((BlockHitResult)(hitResult)); - - // mAtH - final double distance = startPos.distanceToSqr(hitResult.getLocation()); - if (((LivingEntityAccessor) (entity)).vsch$getTickSinceLastJump() > 15 || distance > 1.5) { + final Ship ship = VSGameUtilsKt.getShipManagingPos(level, blockPos); + // TODO: get the accurate distance that repect block shape + final Vector3d blockCenter = new Vector3d(blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5); + if (ship != null) { + tag.putLong(TAG_ATTACHING, ship.getId()); + ship.getShipToWorld().transformPosition(blockCenter); + } else { + tag.remove(TAG_ATTACHING); + } + final Vector3d displacment = new Vector3d(startPos.x, startPos.y, startPos.z).sub(blockCenter); + if (frp != null) { + frp.vsch$getBodyRotation().transformInverse(displacment); + } + final double distance = Math.max(displacment.y, 0); + if (((LivingEntityAccessor) (entity)).vsch$getTickSinceLastJump() > 15 || distance > 1) { final double scaledForce = Math.min(maxDistance * maxDistance / distance * MIN_FORCE, getMaxForce()); final Vec3 force = direction.scale(scaledForce); @@ -193,18 +209,66 @@ public void onInventoryTick(final ItemStack stack, final Level level, final Livi tag.putDouble("Force", 0); } - if (entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { - final BlockPos blockPos = blockHit.getBlockPos(); - final Quaternionf destRot = blockHit.getDirection().getRotation(); - final Ship ship = VSGameUtilsKt.getShipManagingPos(level, blockPos); + //level.addParticle(ParticleTypes.HEART, player.getX(), player.getY(), player.getZ(), 0, 0, 0); + + if (frp == null) { + return; + } + final Vector3d scaledDir = new Vector3d(direction.x, direction.y, direction.z).mul(distance); + if (ship != null) { + ship.getWorldToShip().transformDirection(scaledDir); + } + + final Quaternionf rotation = frp.vsch$getBodyRotation(); + + final int[] dirCounts = new int[Direction.values().length]; + Direction mostDir = null; + int mostDirCount = 0; + + final double halfWidth = frp.vsch$getVanillaDimensions(entity.getPose()).width / 2.0 + 1e-3; + final Vector3d[] checkPoints = new Vector3d[]{ + new Vector3d(0, 0, 0), + new Vector3d(-halfWidth, 0, -halfWidth), + new Vector3d(-halfWidth, 0, halfWidth), + new Vector3d(halfWidth, 0, -halfWidth), + new Vector3d(halfWidth, 0, halfWidth), + }; + for (final Vector3d checkPoint : checkPoints) { + rotation.transform(checkPoint); + checkPoint.add(startPos.x, startPos.y, startPos.z); + if (ship != null) { + ship.getWorldToShip().transformPosition(checkPoint); + } + final Vec3 checkPos = new Vec3(checkPoint.x, checkPoint.y, checkPoint.z); + final BlockHitResult hitResult = level.clip( + new ClipContext( + checkPos, + checkPos.add(scaledDir.x, scaledDir.y, scaledDir.z), + ClipContext.Block.COLLIDER, + ClipContext.Fluid.NONE, + entity + ) + ); + if (hitResult.getType() != HitResult.Type.BLOCK) { + continue; + } + final Direction dir = hitResult.getDirection(); + final int count = ++dirCounts[dir.ordinal()]; + if (count > mostDirCount) { + mostDir = dir; + mostDirCount = count; + } else if (count == mostDirCount) { + mostDir = null; + } + } + + if (mostDir != null) { + final Quaternionf destRot = mostDir.getRotation(); if (ship != null) { destRot.premul(new Quaternionf().setFromNormalized(ship.getShipToWorld())); } - final Quaternionf rotation = frp.vsch$getBodyRotation(); frp.vsch$setBodyRotation(rotateTowards(rotation, destRot)); } - - //level.addParticle(ParticleTypes.HEART, player.getX(), player.getY(), player.getZ(), 0, 0, 0); } @Override diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 3e5451e7..b219a917 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -66,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Predicate; @Mixin(Player.class) @@ -137,6 +138,9 @@ protected MixinPlayer() { @Shadow protected abstract boolean isAboveGround(); + @Shadow + protected abstract boolean isLocalPlayer(); + @Inject(method = "", at = @At("RETURN")) public void postInit(final Level level, final BlockPos pos, final float yRot, final GameProfile profile, final CallbackInfo ci) { final Player player = (Player)((Object)(this)); @@ -172,7 +176,13 @@ public MultiPartPlayer[] getParts() { if (!this.vsch$isFreeRotating()) { return this.getEyePosition(); } - return this.position().add(0, SPACE_ENTITY_SIZE / 2, 0); + return this.position().add(0, HALF_SPACE_ENTITY_SIZE, 0); + } + + @Unique + private Vector3d getFeetRelativePos() { + final float height = this.vsch$getVanillaDimensions(this.getPose()).height; + return new Vector3d(0, HALF_SPACE_ENTITY_SIZE - height, 0); } @Override @@ -180,7 +190,28 @@ public MultiPartPlayer[] getParts() { if (!this.vsch$isFreeRotating()) { return this.position(); } - return this.vsch$getHeadCenter().add(this.vsch$getDownVector().scale(SPACE_ENTITY_SIZE * 2)); + final Vector3d feetRel = this.vsch$getBodyRotation().transform(this.getFeetRelativePos()); + return this.vsch$getHeadCenter().add(feetRel.x, feetRel.y, feetRel.z); + } + + @Unique + private AABBd createLocalBB() { + final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); + return new AABBd( + -dimensions.width / 2, HALF_SPACE_ENTITY_SIZE - dimensions.height, -dimensions.width / 2, + dimensions.width / 2, HALF_SPACE_ENTITY_SIZE, dimensions.width / 2 + ); + } + + @Unique + private Matrix4dc getSelfToWorld() { + if (!this.vsch$isFreeRotating()) { + return new Matrix4d(); + } + final Vec3 position = this.position(); + return new Matrix4d() + .translation(position.x, position.y + HALF_SPACE_ENTITY_SIZE, position.z) + .rotate(this.vsch$getBodyRotation()); } @Override @@ -439,12 +470,14 @@ public void turn(final double x, final double y) { return; } - final boolean isClientSide = this.level().isClientSide; + // TODO: prevent / clamp turning when going to hit block + + final boolean isLocalPlayer = this.isLocalPlayer(); float roll = 0; boolean lockHeadRotate = false; final boolean lockBodyRotate = this.isBodyRotationLocked(); - if (isClientSide) { + if (isLocalPlayer) { final long now = System.nanoTime(); if (VSCHKeyBindings.UNLOCK_HEAD_ROTATION.consumeDoubleClick()) { this.headPitch = 0; @@ -516,7 +549,7 @@ public void turn(final double x, final double y) { this.vsch$setBodyRotationO(this.rotationO.mul(relRotation).normalize()); } - if (isClientSide && !lockBodyRotate && Minecraft.getInstance().options.keySprint.isDown()) { + if (isLocalPlayer && !lockBodyRotate && Minecraft.getInstance().options.keySprint.isDown()) { final Quaternionf headRotation = this.vsch$getHeadRotation(); this.headPitch = -Mth.HALF_PI; this.headYaw = 0; @@ -598,11 +631,10 @@ public boolean shouldDiscardFriction() { } @Override - protected void checkInsideBlocks() { - if (!this.vsch$isFreeRotating()) { - super.checkInsideBlocks(); - return; - } + public BlockPos vsch$findSupportingBlock(final Consumer boxModifier) { + final AABBd bb = this.createLocalBB(); + boxModifier.accept(bb); + return CollisionUtil.findSupportingBlock(this.level(), this, bb, this.getFeetRelativePos(), this.getSelfToWorld()); } @Override @@ -615,15 +647,27 @@ protected void checkSupportingBlock(final boolean onGround, final Vec3 movement) this.mainSupportingBlockPos = Optional.empty(); return; } - final AABB bb = this.getBoundingBox(); - final AABB movedBB = movement == null - ? bb - : bb.expandTowards(Math.signum(movement.x) * 1e-6, Math.signum(movement.y) * 1e-6, Math.signum(movement.z) * 1e-6); - Optional supportingBlockPos = this.level().findSupportingBlock(this, movedBB); - if (!supportingBlockPos.isPresent() && movement != null) { - supportingBlockPos = this.level().findSupportingBlock(this, movedBB.move(-movement.x, -movement.y, -movement.z)); + final AABBd bb = this.createLocalBB(); + final Vector3d move = movement == null + ? null + : this.vsch$getBodyRotation().transformInverse(new Vector3d(movement.x, movement.y, movement.z)); + if (move != null) { + CollisionUtil.expandTowards(bb, Math.signum(move.x) * 1e-6, Math.signum(move.y) * 1e-6, Math.signum(move.z) * 1e-6); + } + final Vector3d feetPos = this.getFeetRelativePos(); + final Matrix4dc entityToWorld = this.getSelfToWorld(); + + BlockPos supportingBlockPos = CollisionUtil.findSupportingBlock(this.level(), this, bb, feetPos, entityToWorld); + if (supportingBlockPos == null && move != null) { + supportingBlockPos = CollisionUtil.findSupportingBlock( + this.level(), + this, + bb.translate(move.negate()), + feetPos, + entityToWorld + ); } - this.mainSupportingBlockPos = supportingBlockPos; + this.mainSupportingBlockPos = Optional.ofNullable(supportingBlockPos); } @Override @@ -632,7 +676,7 @@ protected BlockPos getOnPos(final float dist) { if (mainSupportingBlockPos != null) { return mainSupportingBlockPos; } - final Vector3d rel = this.vsch$getBodyRotation().transform(new Vector3d(0, -HALF_SPACE_ENTITY_SIZE / 2 - dist, 0)); + final Vector3d rel = this.vsch$getBodyRotation().transform(new Vector3d(0, -dist, 0)); return BlockPos.containing(this.vsch$getFeetPosition().add(rel.x, rel.y, rel.z)); } @@ -764,7 +808,6 @@ public void move(final MoverType moverType, Vec3 movement) { } final Entity.MovementEmission movementEmission = this.getMovementEmission(); if (movementEmission.emitsAnything() && !this.isPassenger()) { - // TODO final float scaledMoveDist = (float) (movedDist * 0.6); this.flyDist += scaledMoveDist; this.walkDist += scaledMoveDist; @@ -810,15 +853,12 @@ public void move(final MoverType moverType, Vec3 movement) { @Unique private Vec3 betterCollide(Vec3 movement) { final Level level = this.level(); - final Vec3 position = this.vsch$getHeadCenter(); final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); final AABBd box = new AABBd( - -dimensions.width / 2, 0.6 / 2 - dimensions.height, -dimensions.width / 2, - dimensions.width / 2, 0.6 / 2, dimensions.width / 2 + -dimensions.width / 2, HALF_SPACE_ENTITY_SIZE - dimensions.height, -dimensions.width / 2, + dimensions.width / 2, HALF_SPACE_ENTITY_SIZE, dimensions.width / 2 ); - final Matrix4dc entityToWorld = new Matrix4d() - .translation(position.x, position.y, position.z) - .rotate(this.vsch$getBodyRotation()); + final Matrix4dc entityToWorld = this.getSelfToWorld(); final Matrix4dc worldToEntity = entityToWorld.invert(new Matrix4d()); final Vector3d newMovement = new Vector3d(movement.x, movement.y, movement.z); worldToEntity.transformDirection(newMovement); @@ -922,7 +962,7 @@ public void getStandingEyeHeight(final Pose pose, final EntityDimensions dimensi if (!this.vsch$isFreeRotating()) { return; } - cir.setReturnValue(SPACE_ENTITY_SIZE / 2); + cir.setReturnValue(HALF_SPACE_ENTITY_SIZE); // TODO: fix eye height after rotated // final float height = this.vsch$getVanillaDimensions(pose).height; // float eyeHeight = cir.getReturnValueF(); @@ -1001,18 +1041,13 @@ private Vec3 maybeBackOffFromEdgeImpl(final Vec3 movement, final MoverType mover @Unique private boolean hasCollisionAfterMovement(final Vector3d movement) { - final Level level = this.level(); - final Vec3 position = this.vsch$getHeadCenter(); final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); final double deflate = 0.07; final AABBd box = new AABBd( - -dimensions.width / 2 + deflate, 0.6 / 2 - dimensions.height, -dimensions.width / 2 + deflate, - dimensions.width / 2 - deflate, 0.6 / 2, dimensions.width / 2 - deflate + -dimensions.width / 2 + deflate, HALF_SPACE_ENTITY_SIZE - dimensions.height, -dimensions.width / 2 + deflate, + dimensions.width / 2 - deflate, HALF_SPACE_ENTITY_SIZE, dimensions.width / 2 - deflate ); - final Matrix4dc entityToWorld = new Matrix4d() - .translation(position.x, position.y, position.z) - .rotate(this.vsch$getBodyRotation()); - return CollisionUtil.willCollideAny(level, this, box, entityToWorld, movement); + return CollisionUtil.willCollideAny(this.level(), this, box, this.getSelfToWorld(), movement); } @Override diff --git a/src/main/java/net/jcm/vsch/util/CollisionUtil.java b/src/main/java/net/jcm/vsch/util/CollisionUtil.java index 437a9587..775d31e2 100644 --- a/src/main/java/net/jcm/vsch/util/CollisionUtil.java +++ b/src/main/java/net/jcm/vsch/util/CollisionUtil.java @@ -1,6 +1,8 @@ package net.jcm.vsch.util; +import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.level.CollisionGetter; import net.minecraft.world.level.Level; import net.minecraft.world.phys.AABB; @@ -292,8 +294,71 @@ private static void checkAABBCollision( } } + public static BlockPos findSupportingBlockNoOrientation(final Entity entity, final double downExtend) { + final Vec3 position = entity.position(); + final EntityDimensions dimensions = entity.getDimensions(entity.getPose()); + final AABBd box = new AABBd( + -dimensions.width / 2, -downExtend, -dimensions.width / 2, + dimensions.width / 2, dimensions.height, dimensions.width / 2 + ); + final Matrix4dc box2world = new Matrix4d().translation(position.x, position.y, position.z); + return findSupportingBlock(entity.level(), entity, box, new Vector3d(), box2world); + } + + public static BlockPos findSupportingBlock( + final Level level, + final Entity entity, + final AABBd box, + final Vector3dc feetPos, + final Matrix4dc box2world + ) { + BlockPos closestBlock = null; + double closestDistance = Double.POSITIVE_INFINITY; + + final AABBd worldBox = box.transform(box2world, new AABBd()); + final Vector3d feetPos2 = new Vector3d(); + { + final BlockPos block = level.findSupportingBlock(entity, toAABB(worldBox)).orElse(null); + if (block != null) { + box2world.transformPosition(feetPos, feetPos2); + final double dist = block.distToCenterSqr(feetPos2.x, feetPos2.y, feetPos2.z); + // TODO: maybe check the actual face to face distance instead? + closestDistance = dist; + closestBlock = block; + } + } + + final Matrix4d entityToShip = new Matrix4d(); + final AABBd box2 = new AABBd(); + final String dimId = VSGameUtilsKt.getDimensionId(level); + for (final LoadedShip ship : VSGameUtilsKt.getShipObjectWorld(level).getLoadedShips()) { + if (!ship.getChunkClaimDimension().equals(dimId)) { + continue; + } + if (!ship.getWorldAABB().intersectsAABB(worldBox)) { + continue; + } + entityToShip.set(ship.getWorldToShip()).mul(box2world); + final BlockPos block = level.findSupportingBlock(entity, toAABB(box.transform(entityToShip, box2))).orElse(null); + if (block == null) { + continue; + } + entityToShip.transformPosition(feetPos, feetPos2); + final double dist = block.distToCenterSqr(feetPos2.x, feetPos2.y, feetPos2.z); + if (dist >= closestDistance) { + continue; + } + closestDistance = dist; + closestBlock = block; + } + return closestBlock; + } + public static AABBd expandTowards(final AABBd box, final Vector3dc vec) { - final double x = vec.x(), y = vec.y(), z = vec.z(); + return expandTowards(box, vec.x(), vec.y(), vec.z()); + } + + public static AABBd expandTowards(final AABBd box, final double x, final double y, final double z) { if (x < 0) { box.minX += x; } else { From ba10fac85a30bcbfdb53a494d3973639517da291 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Tue, 23 Sep 2025 18:24:49 -0600 Subject: [PATCH 31/32] implement fall damage --- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index b219a917..3928f6c4 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -706,14 +706,14 @@ public boolean isFallFlying() { } @Override - public Vec3 handleRelativeFrictionAndCalculateMovement(final Vec3 movement, final float friction) { + public Vec3 handleRelativeFrictionAndCalculateMovement(final Vec3 relMove, final float friction) { if (!this.vsch$isFreeRotating()) { - return super.handleRelativeFrictionAndCalculateMovement(movement, friction); + return super.handleRelativeFrictionAndCalculateMovement(relMove, friction); } final float power = this.getFlyingSpeed(); - final double speed = movement.lengthSqr(); + final double speed = relMove.lengthSqr(); if (speed > 1e-8) { - final Vector3d move = new Vector3d(movement.x, movement.y, movement.z); + final Vector3d move = new Vector3d(relMove.x, relMove.y, relMove.z); if (speed > 1) { move.normalize(); } @@ -732,8 +732,21 @@ public Vec3 handleRelativeFrictionAndCalculateMovement(final Vec3 movement, fina rotation.transform(move); this.setDeltaMovement(this.getDeltaMovement().add(move.x, move.y, move.z)); } + final double movementBefore = this.getDeltaMovement().length(); // this.setDeltaMovement(this.handleOnClimbable(this.getDeltaMovement())); + this.setDeltaMovement(this.getDeltaMovement()); this.move(MoverType.SELF, this.getDeltaMovement()); + + // Fall damage check + if (!this.level().isClientSide) { + final double movementAfter = this.getDeltaMovement().length(); + final double movementDiff = movementBefore - movementAfter; + final double fallDamage = movementDiff * 10 - 6; + if (fallDamage > 0) { + this.playSound(fallDamage > 4 ? this.getFallSounds().big() : this.getFallSounds().small(), 1, 1); + this.hurt(this.damageSources().flyIntoWall(), (float) (fallDamage)); + } + } return this.getDeltaMovement(); } @@ -785,7 +798,7 @@ public void move(final MoverType moverType, Vec3 movement) { this.setOnGroundWithKnownMovement(this.verticalCollisionBelow, movement); final BlockPos groundPos = this.getOnPosLegacy(); final BlockState groundState = this.level().getBlockState(groundPos); - this.checkFallDamage(movementActual.y, this.onGround(), groundState, groundPos); + // this.checkFallDamage(movementActual.y, this.onGround(), groundState, groundPos); if (this.isRemoved()) { return; } @@ -834,6 +847,7 @@ public void move(final MoverType moverType, Vec3 movement) { } } this.tryCheckInsideBlocks(); + this.setDeltaMovement(movement); if ( this.level().getBlockStatesIfLoaded(this.getBoundingBox().deflate(1e-6)) .noneMatch((state) -> state.is(BlockTags.FIRE) || state.is(Blocks.LAVA)) @@ -911,7 +925,7 @@ protected void checkFallDamage(final double dy, final boolean onGround, final Bl super.checkFallDamage(dy, onGround, block, pos); return; } - // TODO: implement fall damage / collision damage in space + // fall damage is implemented in handleRelativeFrictionAndCalculateMovement } @Override From b759e4881c68c12fde49af272db662561551e047 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Thu, 25 Sep 2025 11:56:50 -0600 Subject: [PATCH 32/32] entity drag on ship has some issue maybe final commit before rebuild --- .../accessor/FreeRotatePlayerAccessor.java | 9 +++ .../jcm/vsch/items/custom/MagnetBootItem.java | 6 ++ .../jcm/vsch/mixin/client/MixinCamera.java | 4 +- .../mixin/minecraft/MixinLivingEntity.java | 40 +++++++++- .../jcm/vsch/mixin/minecraft/MixinPlayer.java | 75 +++++++++++-------- .../valkyrienskies/MixinEntityDragger.java | 62 +++++++++++++++ .../java/net/jcm/vsch/util/BooleanRef.java | 9 --- src/main/resources/vsch.mixins.json | 1 + 8 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityDragger.java delete mode 100644 src/main/java/net/jcm/vsch/util/BooleanRef.java diff --git a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java index fd4a0bba..e34155c8 100644 --- a/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java +++ b/src/main/java/net/jcm/vsch/accessor/FreeRotatePlayerAccessor.java @@ -8,6 +8,7 @@ import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.primitives.AABBd; +import org.valkyrienskies.core.api.ships.Ship; import java.util.function.Consumer; @@ -20,6 +21,8 @@ public interface FreeRotatePlayerAccessor extends LivingEntityAccessor { Vec3 vsch$getFeetPosition(); + void vsch$setFeetPosition(double x, double y, double z); + default Vec3 vsch$getDownVector() { if (!this.vsch$isFreeRotating()) { return new Vec3(0, -1, 0); @@ -60,8 +63,14 @@ public interface FreeRotatePlayerAccessor extends LivingEntityAccessor { boolean vsch$hasSupportingBlock(); + default BlockPos vsch$findSupportingBlock() { + return this.vsch$findSupportingBlock((box) -> {}); + } + BlockPos vsch$findSupportingBlock(Consumer boxModifier); + Ship vsch$getSupportingShip(); + void vsch$setOldPosAndRot(); void vsch$stepLerp(int steps); diff --git a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java index 04e60924..4739753d 100644 --- a/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java +++ b/src/main/java/net/jcm/vsch/items/custom/MagnetBootItem.java @@ -97,6 +97,10 @@ public Long getWorkingShip(final ItemStack stack) { return tag.contains(TAG_ATTACHING) ? tag.getLong(TAG_ATTACHING) : null; } + public static Long getMagnetizedShip(final ItemStack stack) { + return stack.getItem() instanceof final MagnetBootItem boot ? boot.getWorkingShip(stack) : null; + } + public static boolean isMagnetized(final LivingEntity entity) { final ItemStack stack = entity.getItemBySlot(EquipmentSlot.FEET); if (!stack.isEmpty() && stack.getItem() instanceof final MagnetBootItem boot && boot.isWorking(stack)) { @@ -267,7 +271,9 @@ public void onInventoryTick(final ItemStack stack, final Level level, final Livi if (ship != null) { destRot.premul(new Quaternionf().setFromNormalized(ship.getShipToWorld())); } + final Vec3 feetPos = startPos; frp.vsch$setBodyRotation(rotateTowards(rotation, destRot)); + frp.vsch$setFeetPosition(feetPos.x, feetPos.y, feetPos.z); } } diff --git a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java index a679f042..e2fa37c8 100644 --- a/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java +++ b/src/main/java/net/jcm/vsch/mixin/client/MixinCamera.java @@ -38,7 +38,7 @@ public void setup( final float partialTick, final CallbackInfo ci ) { - if (!(entity instanceof FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + if (!(entity instanceof final FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { return; } frp.vsch$getHeadRotationO().slerp(frp.vsch$getHeadRotation(), partialTick, this.rotation); @@ -52,7 +52,7 @@ public void setup( at = @At(value = "INVOKE", target = "Lorg/joml/Quaternionf;rotationYXZ(FFF)Lorg/joml/Quaternionf;", remap = false) ) private Quaternionf setRotation$rotationYXZ(final Quaternionf rotation, final float y, final float x, final float z, final Operation operation) { - if (this.entity instanceof FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + if (this.entity instanceof final FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { return rotation; } return operation.call(rotation, y, x, z); diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java index 2765b743..4fedadd0 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinLivingEntity.java @@ -3,11 +3,13 @@ import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; import net.jcm.vsch.accessor.LivingEntityAccessor; +import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.phys.Vec3; import org.joml.Vector3d; +import org.joml.Vector3dc; import org.joml.Quaternionf; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; @@ -23,6 +25,9 @@ @Mixin(LivingEntity.class) public abstract class MixinLivingEntity extends Entity implements LivingEntityAccessor { + @Unique + private static final Vector3dc ZERO_VEC3 = new Vector3d(); + protected MixinLivingEntity() { super(null, null); } @@ -48,10 +53,37 @@ protected MixinLivingEntity() { at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setDeltaMovement(DDD)V", ordinal = 0) ) public void travel(final LivingEntity self, double x, double y, double z, final Operation operation) { - if (self instanceof FreeRotatePlayerAccessor frp && frp.vsch$hasSupportingBlock()) { - x *= 0.91; - y *= 0.91; - z *= 0.91; + if (self instanceof final FreeRotatePlayerAccessor frp && frp.vsch$isFreeRotating()) { + final Vec3 movement = self.getDeltaMovement(); + x = movement.x; + y = movement.y; + z = movement.z; + final double inflate = 1.0 / 16; + final BlockPos pos = frp.vsch$findSupportingBlock((box) -> { + box.minX -= inflate; + box.minY -= inflate; + box.minZ -= inflate; + box.maxX += inflate; + box.maxY += inflate; + box.maxZ += inflate; + }); + if (pos != null) { + // TODO: maybe check the block's friction + final double friction = 0.1; + final double inertia = 1 - friction; + x *= inertia; + if (Math.abs(x) < 1e-6) { + x = 0; + } + y *= inertia; + if (Math.abs(y) < 1e-6) { + y = 0; + } + z *= inertia; + if (Math.abs(z) < 1e-6) { + z = 0; + } + } } operation.call(self, x, y, z); } diff --git a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java index 3928f6c4..752e1b95 100644 --- a/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java +++ b/src/main/java/net/jcm/vsch/mixin/minecraft/MixinPlayer.java @@ -8,7 +8,6 @@ import net.jcm.vsch.config.VSCHConfig; import net.jcm.vsch.entity.player.MultiPartPlayer; import net.jcm.vsch.items.custom.MagnetBootItem; -import net.jcm.vsch.util.BooleanRef; import net.jcm.vsch.util.CollisionUtil; import net.jcm.vsch.util.VSCHUtils; import net.jcm.vsch.util.wapi.LevelData; @@ -27,11 +26,13 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySelector; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.player.Abilities; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; @@ -49,6 +50,7 @@ import org.joml.Vector3f; import org.joml.primitives.AABBd; import org.valkyrienskies.core.api.ships.LoadedShip; +import org.valkyrienskies.core.api.ships.Ship; import org.valkyrienskies.mod.common.VSGameUtilsKt; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; @@ -168,7 +170,7 @@ public MultiPartPlayer[] getParts() { @Override public boolean vsch$isFreeRotating() { - return !this.firstTick && this.entityData.get(FREE_ROTATION_ID); + return !this.firstTick && this.entityData.get(FREE_ROTATION_ID) && !this.isPassenger(); } @Override @@ -194,6 +196,16 @@ private Vector3d getFeetRelativePos() { return this.vsch$getHeadCenter().add(feetRel.x, feetRel.y, feetRel.z); } + @Override + public void vsch$setFeetPosition(final double x, final double y, final double z) { + if (!this.vsch$isFreeRotating()) { + this.setPos(x, y, z); + return; + } + final Vector3d feetRel = this.vsch$getBodyRotation().transform(this.getFeetRelativePos()); + this.setPos(x - feetRel.x, y - feetRel.y - HALF_SPACE_ENTITY_SIZE, z - feetRel.z); + } + @Unique private AABBd createLocalBB() { final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); @@ -314,6 +326,9 @@ public void setYRot(final float yRot) { @Override public float getViewXRot(final float partialTick) { + if (!this.vsch$isFreeRotating()) { + return super.getViewXRot(partialTick); + } final Vector3f angles = new Vector3f(); final float xRot = this.vsch$getHeadRotation().getEulerAnglesYXZ(angles).x * Mth.RAD_TO_DEG; final float xRotO = this.vsch$getHeadRotationO().getEulerAnglesYXZ(angles).x * Mth.RAD_TO_DEG; @@ -322,6 +337,9 @@ public float getViewXRot(final float partialTick) { @Override public float getViewYRot(final float partialTick) { + if (!this.vsch$isFreeRotating()) { + return super.getViewYRot(partialTick); + } final Vector3f angles = new Vector3f(); final float yRot = -this.vsch$getHeadRotation().getEulerAnglesYXZ(angles).y * Mth.RAD_TO_DEG; final float yRotO = -this.vsch$getHeadRotationO().getEulerAnglesYXZ(angles).y * Mth.RAD_TO_DEG; @@ -376,7 +394,7 @@ public float getViewYRot(final float partialTick) { @Unique private void reCalcRotation() { - if (this.firstTick || !this.vsch$isFreeRotating()) { + if (!this.vsch$isFreeRotating()) { return; } final Quaternionf rotation = this.vsch$getBodyRotation(); @@ -603,33 +621,6 @@ public boolean shouldDiscardFriction() { return super.shouldDiscardFriction() || !this.getAbilities().flying && this.vsch$isFreeRotating(); } - @Override - public boolean vsch$hasSupportingBlock() { - if (!this.vsch$isFreeRotating()) { - return false; - } - final Level level = this.level(); - final BooleanRef hasSupport = new BooleanRef(false); - final Entity[] parts = new Entity[]{this, this.chestPart, this.feetPart}; - for (final Entity part : parts) { - VSGameUtilsKt.transformFromWorldToNearbyShipsAndWorld(level, part.getBoundingBox(), (box) -> { - if (hasSupport.value) { - return; - } - for (final VoxelShape shape : level.getBlockCollisions(part, box.inflate(SUPPORT_CHECK_DISTANCE))) { - if (!shape.isEmpty()) { - hasSupport.value = true; - return; - } - } - }); - if (hasSupport.value) { - break; - } - } - return hasSupport.value; - } - @Override public BlockPos vsch$findSupportingBlock(final Consumer boxModifier) { final AABBd bb = this.createLocalBB(); @@ -637,6 +628,28 @@ public boolean shouldDiscardFriction() { return CollisionUtil.findSupportingBlock(this.level(), this, bb, this.getFeetRelativePos(), this.getSelfToWorld()); } + @Override + public Ship vsch$getSupportingShip() { + final ItemStack boots = this.getItemBySlot(EquipmentSlot.FEET); + final Long shipId = MagnetBootItem.getMagnetizedShip(boots); + if (shipId != null) { + return VSGameUtilsKt.getShipObjectWorld(this.level()).getAllShips().getById(shipId); + } + final double inflate = 2.0 / 16; + final BlockPos pos = this.vsch$findSupportingBlock((box) -> { + box.minX -= inflate; + box.minY -= inflate; + box.minZ -= inflate; + box.maxX += inflate; + box.maxY += inflate; + box.maxZ += inflate; + }); + if (pos == null) { + return null; + } + return VSGameUtilsKt.getShipManagingPos(this.level(), pos); + } + @Override protected void checkSupportingBlock(final boolean onGround, final Vec3 movement) { if (!this.vsch$isFreeRotating()) { @@ -1056,7 +1069,7 @@ private Vec3 maybeBackOffFromEdgeImpl(final Vec3 movement, final MoverType mover @Unique private boolean hasCollisionAfterMovement(final Vector3d movement) { final EntityDimensions dimensions = this.vsch$getVanillaDimensions(this.getPose()); - final double deflate = 0.07; + final double deflate = 0.1; final AABBd box = new AABBd( -dimensions.width / 2 + deflate, HALF_SPACE_ENTITY_SIZE - dimensions.height, -dimensions.width / 2 + deflate, dimensions.width / 2 - deflate, HALF_SPACE_ENTITY_SIZE, dimensions.width / 2 - deflate diff --git a/src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityDragger.java b/src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityDragger.java new file mode 100644 index 00000000..1f45420f --- /dev/null +++ b/src/main/java/net/jcm/vsch/mixin/valkyrienskies/MixinEntityDragger.java @@ -0,0 +1,62 @@ +package net.jcm.vsch.mixin.valkyrienskies; + +import net.jcm.vsch.accessor.FreeRotatePlayerAccessor; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; + +import org.joml.Matrix4d; +import org.joml.Quaternionf; +import org.joml.Vector3d; +import org.joml.Vector3dc; +import org.valkyrienskies.core.api.ships.Ship; +import org.valkyrienskies.core.api.ships.properties.ShipTransform; +import org.valkyrienskies.mod.common.util.EntityDragger; +import org.valkyrienskies.mod.common.util.EntityDraggingInformation; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(EntityDragger.class) +public class MixinEntityDragger { + @Unique + private static final Vector3dc ZERO_VEC3 = new Vector3d(); + + @WrapOperation( + method = "dragEntitiesWithShips", + at = @At(value = "INVOKE", target = "Lorg/valkyrienskies/mod/common/util/EntityDraggingInformation;getLastShipStoodOn()Ljava/lang/Long;"), + remap = false + ) + public Long dragEntitiesWithShips$getLastShipStoodOn( + final EntityDraggingInformation dragInfo, + final Operation operation, + @Local final Entity entity + ) { + if (!(entity instanceof final FreeRotatePlayerAccessor frp) || !frp.vsch$isFreeRotating()) { + return operation.call(dragInfo); + } + final Vector3dc lastMovement = dragInfo.getAddedMovementLastTick(); + final Ship ship = frp.vsch$getSupportingShip(); + if (ship == null) { + if (!lastMovement.equals(ZERO_VEC3)) { + entity.setDeltaMovement(entity.getDeltaMovement().add(lastMovement.x(), lastMovement.y(), lastMovement.z())); + dragInfo.setAddedMovementLastTick(ZERO_VEC3); + } + return null; + } + final ShipTransform transform = ship.getTransform(); + final ShipTransform prevTransform = ship.getPrevTickTransform(); + final Matrix4d old2new = transform.getShipToWorld().mul(prevTransform.getWorldToShip(), new Matrix4d()); + frp.vsch$setBodyRotation(new Quaternionf().setFromUnnormalized(old2new).mul(frp.vsch$getBodyRotation()).normalize()); + final Vec3 feetPos = frp.vsch$getFeetPosition(); + final Vector3d newFeetPos = old2new.transformPosition(new Vector3d(feetPos.x, feetPos.y, feetPos.z)); + frp.vsch$setFeetPosition(newFeetPos.x, newFeetPos.y, newFeetPos.z); + final Vector3d movement = newFeetPos.sub(feetPos.x, feetPos.y, feetPos.z); + dragInfo.setAddedMovementLastTick(movement); + return null; + } +} diff --git a/src/main/java/net/jcm/vsch/util/BooleanRef.java b/src/main/java/net/jcm/vsch/util/BooleanRef.java deleted file mode 100644 index c2ddbbf6..00000000 --- a/src/main/java/net/jcm/vsch/util/BooleanRef.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.jcm.vsch.util; - -public final class BooleanRef { - public boolean value; - - public BooleanRef(final boolean value) { - this.value = value; - } -} diff --git a/src/main/resources/vsch.mixins.json b/src/main/resources/vsch.mixins.json index f9e234ff..f17570c9 100644 --- a/src/main/resources/vsch.mixins.json +++ b/src/main/resources/vsch.mixins.json @@ -33,6 +33,7 @@ "minecraft.MixinServerPlayer", "minecraft.MixinServerboundMovePlayerPacket_PosRot", "minecraft.MixinServerboundMovePlayerPacket_Rot", + "valkyrienskies.MixinEntityDragger", "valkyrienskies.MixinEntityShipCollisionUtils", "valkyrienskies.MixinShipAssemblyKt", "valkyrienskies.MixinVSGameUtilsKt",