From a1b76e6c43d73a09f5d32fccb074b032d212f995 Mon Sep 17 00:00:00 2001 From: mbmikaelyan Date: Sat, 4 Apr 2026 18:21:21 -0700 Subject: [PATCH 1/2] Fix crossing rail not working with experimental minecart improvements NewMinecartBehaviorMixin was missing the getRailShape() logic that OldMinecartBehaviorMixin had, so crossing direction switching was never applied when experimental minecarts were enabled. Also updates isPoweringRail signatures to match the changed BlockState.is(Object) signature in 26.1. --- .../mixin/NewMinecartBehaviorMixin.java | 83 +++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java b/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java index 74a96ac..95eee2c 100644 --- a/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java +++ b/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java @@ -6,11 +6,15 @@ import net.minecraft.world.entity.vehicle.minecart.NewMinecartBehavior; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.PoweredRailBlock; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.block.state.properties.RailShape; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.phys.Vec3; import net.robofox.copperrails.CopperRails; import net.robofox.copperrails.CopperRailsConfig; +import net.robofox.copperrails.block.ModBlocks; import net.robofox.copperrails.block.custom.GenericCopperRailBlock; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -25,8 +29,75 @@ protected NewMinecartBehaviorMixin(AbstractMinecart minecart) { super(minecart); } + /** + * Returns the effective RailShape for a crossing block, flipping it when powered. + * This mirrors the same fix in OldMinecartBehaviorMixin so that crossing direction + * switching works with the experimental minecart physics engine too. + */ @Unique - private boolean isPoweringRail(BlockState state, Block block) { + private RailShape getRailShape(BlockState blockState, Property property) { + RailShape railShape = blockState.getValue(property); + if (blockState.is(ModBlocks.RAIL_CROSSING)) { + boolean isPowered = blockState.getValue(PoweredRailBlock.POWERED); + if (isPowered) { + switch (railShape) { + case NORTH_SOUTH: + return RailShape.EAST_WEST; + case EAST_WEST: + return RailShape.NORTH_SOUTH; + default: + CopperRails.LOGGER.error("Crossing rail has invalid shape"); + } + } + } + return railShape; + } + + /** + * moveAlongTrack ordinal 0: first RailShape getValue call (byte offset 167). + * Note: ordinal 0 of getValue overall is a Boolean (POWERED), which is skipped by type. + * Mixin ordinal counts only calls matching the exact target descriptor, so ordinal 0 here + * is the first RailShape cast getValue. + */ + @Redirect( + method = "moveAlongTrack", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;", + ordinal = 0)) + public > T getMoveAlongTrackShapeMixin0(BlockState blockState, Property property) { + return (T) getRailShape(blockState, property); + } + + /** + * moveAlongTrack ordinal 1: second RailShape getValue call (byte offset 369). + */ + @Redirect( + method = "moveAlongTrack", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;", + ordinal = 1)) + public > T getMoveAlongTrackShapeMixin1(BlockState blockState, Property property) { + return (T) getRailShape(blockState, property); + } + + /** + * adjustToRails ordinal 0: positions the cart on the rail after shape is determined. + * Equivalent to getPos/getPosOffs in OldMinecartBehavior. + */ + @Redirect( + method = "adjustToRails", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;", + ordinal = 0)) + public > T getAdjustToRailsMixin(BlockState blockState, Property property) { + return (T) getRailShape(blockState, property); + } + + @Unique + private boolean isPoweringRail(BlockState state, Object block) { // This code is injected into the start of AbstractMinecartEntity.moveAlongTrack()V if (block == Blocks.POWERED_RAIL) { Block unknownRail = state.getBlock(); @@ -34,7 +105,7 @@ private boolean isPoweringRail(BlockState state, Block block) { return (unknownRail instanceof GenericCopperRailBlock || unknownRail == Blocks.POWERED_RAIL); } else { CopperRails.LOGGER.warn("isOf() Mixin called with something else than Blocks.POWERED_RAIL"); - return state.is(block); + return state.is((net.minecraft.world.level.block.Block) block); } } @@ -42,8 +113,8 @@ private boolean isPoweringRail(BlockState state, Block block) { method = "calculateHaltTrackSpeed", at = @At( value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;is(Lnet/minecraft/world/level/block/Block;)Z")) - public boolean isPoweringRailHaltTrackSpeed(BlockState state, Block block) { + target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")) + public boolean isPoweringRailHaltTrackSpeed(BlockState state, Object block) { return isPoweringRail(state, block); } @@ -51,8 +122,8 @@ public boolean isPoweringRailHaltTrackSpeed(BlockState state, Block block) { method = "calculateBoostTrackSpeed", at = @At( value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;is(Lnet/minecraft/world/level/block/Block;)Z")) - public boolean isPoweringRailBoostTrackSpeed(BlockState state, Block block) { + target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")) + public boolean isPoweringRailBoostTrackSpeed(BlockState state, Object block) { return isPoweringRail(state, block); } From 68a8fd4b34beb93490097a0d90c1e47afac951e1 Mon Sep 17 00:00:00 2001 From: mbmikaelyan Date: Sat, 4 Apr 2026 19:00:27 -0700 Subject: [PATCH 2/2] Use type-checked redirect instead of ordinals for moveAlongTrack Replaced two ordinal-based redirects with a single redirect that intercepts all getValue calls in moveAlongTrack and uses a type check to only apply the crossing rail shape fix when the property is a RailShape. This avoids fragile ordinal counting and correctly handles the mixed Boolean/RailShape getValue calls in the method. --- .../mixin/NewMinecartBehaviorMixin.java | 108 ++++-------------- 1 file changed, 23 insertions(+), 85 deletions(-) diff --git a/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java b/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java index 95eee2c..2049e40 100644 --- a/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java +++ b/src/main/java/net/robofox/copperrails/mixin/NewMinecartBehaviorMixin.java @@ -29,11 +29,6 @@ protected NewMinecartBehaviorMixin(AbstractMinecart minecart) { super(minecart); } - /** - * Returns the effective RailShape for a crossing block, flipping it when powered. - * This mirrors the same fix in OldMinecartBehaviorMixin so that crossing direction - * switching works with the experimental minecart physics engine too. - */ @Unique private RailShape getRailShape(BlockState blockState, Property property) { RailShape railShape = blockState.getValue(property); @@ -41,101 +36,53 @@ private RailShape getRailShape(BlockState blockState, Property proper boolean isPowered = blockState.getValue(PoweredRailBlock.POWERED); if (isPowered) { switch (railShape) { - case NORTH_SOUTH: - return RailShape.EAST_WEST; - case EAST_WEST: - return RailShape.NORTH_SOUTH; - default: - CopperRails.LOGGER.error("Crossing rail has invalid shape"); + case NORTH_SOUTH: return RailShape.EAST_WEST; + case EAST_WEST: return RailShape.NORTH_SOUTH; + default: CopperRails.LOGGER.error("Crossing rail has invalid shape"); } } } return railShape; } - /** - * moveAlongTrack ordinal 0: first RailShape getValue call (byte offset 167). - * Note: ordinal 0 of getValue overall is a Boolean (POWERED), which is skipped by type. - * Mixin ordinal counts only calls matching the exact target descriptor, so ordinal 0 here - * is the first RailShape cast getValue. - */ - @Redirect( - method = "moveAlongTrack", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;", - ordinal = 0)) - public > T getMoveAlongTrackShapeMixin0(BlockState blockState, Property property) { - return (T) getRailShape(blockState, property); - } - - /** - * moveAlongTrack ordinal 1: second RailShape getValue call (byte offset 369). - */ - @Redirect( - method = "moveAlongTrack", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;", - ordinal = 1)) - public > T getMoveAlongTrackShapeMixin1(BlockState blockState, Property property) { - return (T) getRailShape(blockState, property); + @SuppressWarnings("unchecked") + @Unique + private > T getValueMaybeRailShape(BlockState blockState, Property property) { + if (property.getValueClass() == RailShape.class) { + return (T) getRailShape(blockState, (Property) property); + } + return blockState.getValue(property); } - /** - * adjustToRails ordinal 0: positions the cart on the rail after shape is determined. - * Equivalent to getPos/getPosOffs in OldMinecartBehavior. - */ - @Redirect( - method = "adjustToRails", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;", - ordinal = 0)) - public > T getAdjustToRailsMixin(BlockState blockState, Property property) { - return (T) getRailShape(blockState, property); + @Redirect(method = "moveAlongTrack", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/level/block/state/BlockState;getValue(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;")) + public > T getMoveAlongTrackMixin(BlockState blockState, Property property) { + return getValueMaybeRailShape(blockState, property); } @Unique private boolean isPoweringRail(BlockState state, Object block) { - // This code is injected into the start of AbstractMinecartEntity.moveAlongTrack()V if (block == Blocks.POWERED_RAIL) { Block unknownRail = state.getBlock(); - // We want to check if this is a powering rail return (unknownRail instanceof GenericCopperRailBlock || unknownRail == Blocks.POWERED_RAIL); } else { CopperRails.LOGGER.warn("isOf() Mixin called with something else than Blocks.POWERED_RAIL"); - return state.is((net.minecraft.world.level.block.Block) block); + return state.is((Block) block); } } - @Redirect( - method = "calculateHaltTrackSpeed", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")) + @Redirect(method = "calculateHaltTrackSpeed", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")) public boolean isPoweringRailHaltTrackSpeed(BlockState state, Object block) { return isPoweringRail(state, block); } - @Redirect( - method = "calculateBoostTrackSpeed", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")) + @Redirect(method = "calculateBoostTrackSpeed", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")) public boolean isPoweringRailBoostTrackSpeed(BlockState state, Object block) { return isPoweringRail(state, block); } -// @Inject( -// method = "calculateBoostTrackSpeed", -// at = @At( -// value = "INVOKE", -// target = "Lnet/minecraft/world/phys/Vec3;length()D", -// ordinal = 0) -// ) -// private void - @Unique public int getMaxRailSpeed(BlockState blockState) { Block block = blockState.getBlock(); @@ -144,20 +91,14 @@ public int getMaxRailSpeed(BlockState blockState) { CopperRails.LOGGER.error("Could not access to server gamerules ! Please report this bug"); return CopperRailsConfig.MAX_RAIL_SPEED_NOT_EXPERIMENTAL_BPS; } - GameRules gamerules = server.getWorldData().getGameRules(); + GameRules gamerules = server.getGameRules(); int maxSpeed = getMaxSpeedByRailType(block, gamerules); return Integer.min(maxSpeed, gamerules.get(GameRules.MAX_MINECART_SPEED)); } - @ModifyVariable( - method = "calculateBoostTrackSpeed", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/phys/Vec3;length()D", - ordinal = 0), - argsOnly = true) + @ModifyVariable(method = "calculateBoostTrackSpeed", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/phys/Vec3;length()D", ordinal = 0), argsOnly = true) private Vec3 slowedDownVec3(Vec3 vec3, @Local(ordinal = 0, argsOnly = true) BlockState blockState) { - // We already know that blockstate is a powered rail. float maxSpeed = getMaxRailSpeed(blockState) / 20.0F; if (vec3.length() > maxSpeed) { double d = Double.max(vec3.length() * 0.95 - 0.06, maxSpeed); @@ -165,7 +106,4 @@ private Vec3 slowedDownVec3(Vec3 vec3, @Local(ordinal = 0, argsOnly = true) Bloc } return vec3; } - - - -} \ No newline at end of file +}