diff --git a/build.gradle b/build.gradle index 7c8b1e3..3648b8a 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + // modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" // Jackson API for config file parsing. implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.12.4' diff --git a/gradle.properties b/gradle.properties index c01b156..c0e628e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.11.6 # Mod Properties - mod_version = 1.1.0 + mod_version = 1.2.0 maven_group = nl.andrewlalis archives_base_name = speed-carts diff --git a/src/main/java/nl/andrewlalis/speed_carts/Config.java b/src/main/java/nl/andrewlalis/speed_carts/Config.java index 972403c..8c42641 100644 --- a/src/main/java/nl/andrewlalis/speed_carts/Config.java +++ b/src/main/java/nl/andrewlalis/speed_carts/Config.java @@ -5,15 +5,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; /** * Contains all configuration options and the logic for loading config. */ public class Config { - private static final Path CONFIG_FILE = Path.of("config", "speed_carts.yaml"); + private static final Path CONFIG_FILE = Paths.get("config", "speed_carts.yaml"); private final double defaultSpeed; private final double minimumSpeed; @@ -36,12 +39,16 @@ public class Config { private void ensureConfigExists() throws IOException { if (!Files.exists(CONFIG_FILE)) { - var out = Files.newOutputStream(CONFIG_FILE); - var defaultConfigInputStream = SpeedCarts.class.getClassLoader().getResourceAsStream("default_config.yaml"); + OutputStream out = Files.newOutputStream(CONFIG_FILE); + InputStream defaultConfigInputStream = SpeedCarts.class.getClassLoader().getResourceAsStream("default_config.yaml"); if (defaultConfigInputStream == null) { throw new IOException("Could not load default_config.yaml"); } - defaultConfigInputStream.transferTo(out); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = defaultConfigInputStream.read(buffer)) > 0) { + out.write(buffer, 0, bytesRead); + } defaultConfigInputStream.close(); out.close(); } diff --git a/src/main/java/nl/andrewlalis/speed_carts/mixin/AbstractMinecartMixin.java b/src/main/java/nl/andrewlalis/speed_carts/mixin/AbstractMinecartMixin.java index 203243c..77dd082 100644 --- a/src/main/java/nl/andrewlalis/speed_carts/mixin/AbstractMinecartMixin.java +++ b/src/main/java/nl/andrewlalis/speed_carts/mixin/AbstractMinecartMixin.java @@ -1,11 +1,16 @@ package nl.andrewlalis.speed_carts.mixin; import com.mojang.datafixers.util.Pair; +import net.minecraft.block.AbstractRailBlock; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.PoweredRailBlock; +import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.SignBlockEntity; import net.minecraft.block.enums.RailShape; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.MovementType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.sound.SoundCategory; @@ -14,10 +19,7 @@ import net.minecraft.state.property.Properties; import net.minecraft.text.Text; import net.minecraft.util.DyeColor; import net.minecraft.util.Identifier; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.Vec3i; +import net.minecraft.util.math.*; import net.minecraft.world.World; import nl.andrewlalis.speed_carts.SpeedCarts; import org.spongepowered.asm.mixin.Mixin; @@ -100,6 +102,8 @@ public abstract class AbstractMinecartMixin extends Entity { @Inject(at = @At("HEAD"), method = "moveOnRail", cancellable = true) public void moveOnRailOverwrite(BlockPos pos, BlockState state, CallbackInfo ci) { this.updateForSpeedModifiers(pos); + this.modifiedMoveOnRail(pos, state); + ci.cancel(); } /** @@ -116,16 +120,17 @@ public abstract class AbstractMinecartMixin extends Entity { return; } - for (var position : this.getPositionsToCheck(pos)) { - if ( - this.world.getBlockEntity(position) instanceof SignBlockEntity sign && - (!sign.getPos().equals(this.lastUpdatedFrom) || this.world.getTime() > this.lastSpeedUpdate + SPEED_UPDATE_COOLDOWN) - ) { - var state = this.world.getBlockState(position); - var dir = (Direction) state.getEntries().get(Properties.HORIZONTAL_FACING); - // Only allow free-standing signs or those facing the cart. - if (dir == null || dir.equals(this.getMovementDirection().getOpposite())) { - if (this.updateSpeedForSign(sign)) return; + for (BlockPos position : this.getPositionsToCheck(pos)) { + BlockEntity blockEntity = this.world.getBlockEntity(position); + if (blockEntity instanceof SignBlockEntity) { + SignBlockEntity sign = (SignBlockEntity) blockEntity; + if (!sign.getPos().equals(this.lastUpdatedFrom) || this.world.getTime() > this.lastSpeedUpdate + SPEED_UPDATE_COOLDOWN) { + BlockState state = this.world.getBlockState(position); + Direction dir = (Direction) state.getEntries().get(Properties.HORIZONTAL_FACING); + // Only allow free-standing signs or those facing the cart. + if (dir == null || dir.equals(this.getMovementDirection().getOpposite())) { + if (this.updateSpeedForSign(sign)) return; + } } } } @@ -190,4 +195,186 @@ public abstract class AbstractMinecartMixin extends Entity { } return positionsToCheck; } + + /** + * Modified version of {@link AbstractMinecartMixin#moveOnRail(BlockPos, BlockState)} + * that allows the minecart to maintain speeds above 32 m/s. + * @param pos The block position of the cart. + * @param state The state of the block the cart is in. + */ + private void modifiedMoveOnRail(BlockPos pos, BlockState state) { + this.fallDistance = 0.0F; + double d = this.getX(); + double e = this.getY(); + double f = this.getZ(); + Vec3d vec3d = this.snapPositionToRail(d, e, f); + e = pos.getY(); + boolean onPoweredRail = false; + boolean onNormalRail = false; + if (state.isOf(Blocks.POWERED_RAIL)) { + onPoweredRail = state.get(PoweredRailBlock.POWERED); + onNormalRail = !onPoweredRail; + } + + double g = 0.0078125D; + if (this.isTouchingWater()) { + g *= 0.2D; + } + + Vec3d velocity = this.getVelocity(); + RailShape railShape = state.get(((AbstractRailBlock)state.getBlock()).getShapeProperty()); + switch (railShape) { + case ASCENDING_EAST -> { + this.setVelocity(velocity.add(-g, 0.0D, 0.0D)); + ++e; + } + case ASCENDING_WEST -> { + this.setVelocity(velocity.add(g, 0.0D, 0.0D)); + ++e; + } + case ASCENDING_NORTH -> { + this.setVelocity(velocity.add(0.0D, 0.0D, g)); + ++e; + } + case ASCENDING_SOUTH -> { + this.setVelocity(velocity.add(0.0D, 0.0D, -g)); + ++e; + } + } + + velocity = this.getVelocity(); + Pair adjacentRailPositions = getAdjacentRailPositionsByShape(railShape); + Vec3i vec3i = adjacentRailPositions.getFirst(); + Vec3i vec3i2 = adjacentRailPositions.getSecond(); + double h = vec3i2.getX() - vec3i.getX(); + double i = vec3i2.getZ() - vec3i.getZ(); + double j = Math.sqrt(h * h + i * i); + double k = velocity.x * h + velocity.z * i; + if (k < 0.0D) { + h = -h; + i = -i; + } + + double l = Math.min(2.0D, velocity.horizontalLength()); + // Only consider using Minecraft's default velocity damper logic when going at 'normal' speeds. + if (this.maxSpeedBps <= DEFAULT_SPEED) { + this.setVelocity(new Vec3d(l * h / j, velocity.y, l * i / j)); + } else { // Otherwise, simply clamp to the computed max speed in blocks per tick. + double speed = this.maxSpeedBps / 20.0; + this.setVelocity(new Vec3d( + Math.max(Math.min(speed, velocity.x), -speed), + velocity.y, + Math.max(Math.min(speed, velocity.z), -speed) + )); + } + + Entity entity = this.getFirstPassenger(); + if (entity instanceof PlayerEntity) { + Vec3d playerVelocity = entity.getVelocity(); + double m = playerVelocity.horizontalLengthSquared(); + double n = this.getVelocity().horizontalLengthSquared(); + if (m > 1.0E-4D && n < 0.01D) { + this.setVelocity(this.getVelocity().add(playerVelocity.x * 0.1D, 0.0D, playerVelocity.z * 0.1D)); + onNormalRail = false; + } + } + + double p; + if (onNormalRail) { + p = this.getVelocity().horizontalLength(); + if (p < 0.03D) { + this.setVelocity(Vec3d.ZERO); + } else { + this.setVelocity(this.getVelocity().multiply(0.5D, 0.0D, 0.5D)); + } + } + + p = (double)pos.getX() + 0.5D + (double)vec3i.getX() * 0.5D; + double q = (double)pos.getZ() + 0.5D + (double)vec3i.getZ() * 0.5D; + double r = (double)pos.getX() + 0.5D + (double)vec3i2.getX() * 0.5D; + double s = (double)pos.getZ() + 0.5D + (double)vec3i2.getZ() * 0.5D; + h = r - p; + i = s - q; + double x; + double v; + double w; + if (h == 0.0D) { + x = f - (double)pos.getZ(); + } else if (i == 0.0D) { + x = d - (double)pos.getX(); + } else { + v = d - p; + w = f - q; + x = (v * h + w * i) * 2.0D; + } + + d = p + h * x; + f = q + i * x; + this.setPosition(d, e, f); + v = this.hasPassengers() ? 0.75D : 1.0D; + w = this.getMaxOffRailSpeed(); + velocity = this.getVelocity(); + Vec3d movement = new Vec3d(MathHelper.clamp(v * velocity.x, -w, w), 0.0D, MathHelper.clamp(v * velocity.z, -w, w)); + System.out.println("Moving: " + movement + ", Max speed: " + w + ", Z velocity: " + velocity.z); + this.move(MovementType.SELF, movement); + if (vec3i.getY() != 0 && MathHelper.floor(this.getX()) - pos.getX() == vec3i.getX() && MathHelper.floor(this.getZ()) - pos.getZ() == vec3i.getZ()) { + this.setPosition(this.getX(), this.getY() + (double)vec3i.getY(), this.getZ()); + } else if (vec3i2.getY() != 0 && MathHelper.floor(this.getX()) - pos.getX() == vec3i2.getX() && MathHelper.floor(this.getZ()) - pos.getZ() == vec3i2.getZ()) { + this.setPosition(this.getX(), this.getY() + (double)vec3i2.getY(), this.getZ()); + } + + this.applySlowdown(); + Vec3d vec3d4 = this.snapPositionToRail(this.getX(), this.getY(), this.getZ()); + Vec3d vec3d7; + double af; + if (vec3d4 != null && vec3d != null) { + double aa = (vec3d.y - vec3d4.y) * 0.05D; + vec3d7 = this.getVelocity(); + af = vec3d7.horizontalLength(); + if (af > 0.0D) { + this.setVelocity(vec3d7.multiply((af + aa) / af, 1.0D, (af + aa) / af)); + } + + this.setPosition(this.getX(), vec3d4.y, this.getZ()); + } + + int ac = MathHelper.floor(this.getX()); + int ad = MathHelper.floor(this.getZ()); + if (ac != pos.getX() || ad != pos.getZ()) { + vec3d7 = this.getVelocity(); + af = vec3d7.horizontalLength(); + this.setVelocity(af * (double)(ac - pos.getX()), vec3d7.y, af * (double)(ad - pos.getZ())); + } + + if (onPoweredRail) { + vec3d7 = this.getVelocity(); + af = vec3d7.horizontalLength(); + if (af > 0.01D) { + this.setVelocity(vec3d7.add(vec3d7.x / af * 0.06D, 0.0D, vec3d7.z / af * 0.06D)); + } else { + Vec3d vec3d8 = this.getVelocity(); + double ah = vec3d8.x; + double ai = vec3d8.z; + if (railShape == RailShape.EAST_WEST) { + if (this.willHitBlockAt(pos.west())) { + ah = 0.02D; + } else if (this.willHitBlockAt(pos.east())) { + ah = -0.02D; + } + } else { + if (railShape != RailShape.NORTH_SOUTH) { + return; + } + + if (this.willHitBlockAt(pos.north())) { + ai = 0.02D; + } else if (this.willHitBlockAt(pos.south())) { + ai = -0.02D; + } + } + + this.setVelocity(ah, vec3d8.y, ai); + } + } + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3430c17..6cabb1f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -28,11 +28,7 @@ "depends": { "fabricloader": ">=0.11.3", - "fabric": "*", "minecraft": "1.17.x", "java": ">=16" - }, - "suggests": { - "another-mod": "*" } }