diff --git a/build.gradle b/build.gradle index 82dafc3d..32637436 100644 --- a/build.gradle +++ b/build.gradle @@ -2,9 +2,11 @@ buildscript { repositories { jcenter() maven { url = "http://files.minecraftforge.net/maven" } + maven { url = "http://repo.spongepowered.org/maven" } } dependencies { classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT' } } @@ -15,13 +17,20 @@ plugins { apply plugin: 'java' apply plugin: 'net.minecraftforge.gradle.forge' +apply plugin: 'org.spongepowered.mixin' repositories { - maven { url "https://jitpack.io" } + maven { url = "https://jitpack.io" } + maven { + name = 'sonatype' + url = 'http://oss.sonatype.org/content/repositories/public/' + } + maven { url = "http://repo.spongepowered.org/maven" } } configurations { embed + coreShadow compile.extendsFrom(embed) } @@ -30,7 +39,8 @@ dependencies { embed 'org.jgrapht:jgrapht-core:1.1.0' embed 'com.github.DimensionalDevelopment:poly2tri.java:master-SNAPSHOT' compileOnly 'com.github.DimensionalDevelopment:AnnotatedNBT:-SNAPSHOT' - compileOnly 'com.github.OpenCubicChunks:CubicChunks:MC_1.12-SNAPSHOT' + compile("org.spongepowered:mixin:0.7.8-SNAPSHOT") { transitive = false } + deobfProvided 'io.github.opencubicchunks:cubicchunks:1.12.2-0.0.819.0-SNAPSHOT' } // Mod version @@ -63,6 +73,19 @@ minecraft { mappings = mcpversion replace '${version}', fullVersion makeObfSourceJar = false + //noinspection GroovyUnusedAssignment + def args = [ // TODO: https://github.com/SpongePowered/Mixin/issues/140 + "-Dfml.noGrab=false", + "-Dfml.coreMods.load=org.dimdev.vanillafix.VanillaFixCoreMod", + "-Dmixin.env.compatLevel=JAVA_8", + //"-Dmixin.debug.verbose=true", + //"-Dmixin.debug.export=true", + "-Dmixin.checks.interfaces=true" + ] +} + +mixin { + add sourceSets.main, "org.dimdev.vanillafix.mixins.refmap.json" } // Tasks @@ -73,6 +96,15 @@ compileJava { jar { archiveName = archivesBaseName + "-" + jarVersion + ".jar" from configurations.embed.collect { it.isDirectory() ? it : zipTree(it) } + manifest { + attributes( + "TweakClass": "org.spongepowered.asm.launch.MixinTweaker", + "FMLCorePlugin": "org.dimdev.vanillafix.VanillaFixCoreMod", + "TweakOrder": 0, + "MixinConfigs": "org.dimdev.vanillafix.mixins.json", + "ForceLoadAsMod": "true" + ) + } } task sourcesJar(type: Jar, dependsOn: classes) { diff --git a/src/main/java/org/dimdev/vanillafix/VanillaFixCoreContainer.java b/src/main/java/org/dimdev/vanillafix/VanillaFixCoreContainer.java new file mode 100644 index 00000000..1c4adebb --- /dev/null +++ b/src/main/java/org/dimdev/vanillafix/VanillaFixCoreContainer.java @@ -0,0 +1,24 @@ +package org.dimdev.vanillafix; + +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.ModMetadata; +import net.minecraftforge.fml.common.versioning.ArtifactVersion; + +import java.util.Collections; +import java.util.List; + +public class VanillaFixCoreContainer extends DummyModContainer { + + public VanillaFixCoreContainer() { + super(new ModMetadata()); + ModMetadata meta = getMetadata(); + meta.modId = "cubicchunkscore"; + meta.name = "VanillaFix"; + meta.version = "${version}"; + } + + @Override + public List<ArtifactVersion> getDependencies() { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/src/main/java/org/dimdev/vanillafix/VanillaFixCoreMod.java b/src/main/java/org/dimdev/vanillafix/VanillaFixCoreMod.java new file mode 100644 index 00000000..7157e2af --- /dev/null +++ b/src/main/java/org/dimdev/vanillafix/VanillaFixCoreMod.java @@ -0,0 +1,38 @@ +package org.dimdev.vanillafix; + +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.Mixins; + +import javax.annotation.Nullable; +import java.util.Map; + +@IFMLLoadingPlugin.MCVersion(ForgeVersion.mcVersion) +@IFMLLoadingPlugin.SortingIndex(5000) +@IFMLLoadingPlugin.TransformerExclusions("org.dimdev.vanillafix.") +public class VanillaFixCoreMod implements IFMLLoadingPlugin { + + public VanillaFixCoreMod() { + MixinBootstrap.init(); + Mixins.addConfiguration("org.dimdev.vanillafix.mixins.json"); + } + + @Override public String[] getASMTransformerClass() { + return new String[0]; + } + + @Override public String getModContainerClass() { + return "org.dimdev.vanillafix.VanillaFixCoreContainer"; + } + + @Nullable @Override public String getSetupClass() { + return null; + } + + @Override public void injectData(Map<String, Object> data) {} + + @Override public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/main/java/org/dimdev/vanillafix/mixins/MixinNetHandlerPlayServer.java b/src/main/java/org/dimdev/vanillafix/mixins/MixinNetHandlerPlayServer.java new file mode 100644 index 00000000..95cf1e24 --- /dev/null +++ b/src/main/java/org/dimdev/vanillafix/mixins/MixinNetHandlerPlayServer.java @@ -0,0 +1,173 @@ +package org.dimdev.vanillafix.mixins; + +import net.minecraft.entity.MoverType; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.MobEffects; +import net.minecraft.network.NetHandlerPlayServer; +import net.minecraft.network.PacketThreadUtil; +import net.minecraft.network.play.INetHandlerPlayServer; +import net.minecraft.network.play.client.CPacketPlayer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldServer; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +@SuppressWarnings("unused") // Shadow +@Mixin(NetHandlerPlayServer.class) +public abstract class MixinNetHandlerPlayServer implements INetHandlerPlayServer { + + @Shadow public EntityPlayerMP player; + @Shadow private /*final*/ MinecraftServer server; + @Shadow private int networkTickCount; + @Shadow private double firstGoodX; + @Shadow private double firstGoodY; + @Shadow private double firstGoodZ; + @Shadow private double lastGoodX; + @Shadow private double lastGoodY; + @Shadow private double lastGoodZ; + @Shadow private int lastPositionUpdate; + @Shadow private boolean floating; + @Shadow private Vec3d targetPos; + @Shadow private static final Logger LOGGER = null; + @Shadow private int movePacketCounter; + @Shadow private int lastMovePacketCounter; + + @Shadow public void disconnect(final ITextComponent textComponent) {} + + @Shadow private static boolean isMovePlayerPacketInvalid(CPacketPlayer packetIn) { return false; } + + @Shadow private void captureCurrentPosition() {} + + @Shadow public void setPlayerLocation(double x, double y, double z, float yaw, float pitch) {} + + @Overwrite + @Override + public void processPlayer(CPacketPlayer packet) { + PacketThreadUtil.checkThreadAndEnqueue(packet, this, player.getServerWorld()); + + if (isMovePlayerPacketInvalid(packet)) { + disconnect(new TextComponentTranslation("multiplayer.disconnect.invalid_player_movement")); + } else { + WorldServer world = server.getWorld(player.dimension); + + if (player.queuedEndExit) return; + + if (networkTickCount == 0) { + captureCurrentPosition(); + } + + if (targetPos != null) { + if (networkTickCount - lastPositionUpdate > 20) { + lastPositionUpdate = networkTickCount; + setPlayerLocation(targetPos.x, targetPos.y, targetPos.z, player.rotationYaw, player.rotationPitch); + } + } else { + lastPositionUpdate = networkTickCount; + + if (player.isRiding()) { + player.setPositionAndRotation(player.posX, player.posY, player.posZ, packet.getYaw(player.rotationYaw), packet.getPitch(player.rotationPitch)); + server.getPlayerList().serverUpdateMovingPlayer(player); + } else { + double oldX = player.posX; + double oldY = player.posY; + double oldZ = player.posZ; + double oldY2 = player.posY; + + double packetX = packet.getX(player.posX); + double packetY = packet.getY(player.posY); + double packetZ = packet.getZ(player.posZ); + float packetYaw = packet.getYaw(player.rotationYaw); + float packetPitch = packet.getPitch(player.rotationPitch); + + double xDiff = packetX - firstGoodX; + double yDiff = packetY - firstGoodY; + double zDiff = packetZ - firstGoodZ; + double speedSq = player.motionX * player.motionX + player.motionY * player.motionY + player.motionZ * player.motionZ; + double distanceSq = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff; + + if (player.isPlayerSleeping()) { + if (distanceSq > 1.0D) { + setPlayerLocation(player.posX, player.posY, player.posZ, packet.getYaw(player.rotationYaw), packet.getPitch(player.rotationPitch)); + } + } else { + ++movePacketCounter; + int packetCount = movePacketCounter - lastMovePacketCounter; + + if (packetCount > 5) { + LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", player.getName(), packetCount); + packetCount = 1; + } + + if (!player.isInvulnerableDimensionChange() && (!player.getServerWorld().getGameRules().getBoolean("disableElytraMovementCheck") || !player.isElytraFlying())) { + float maxDistancePerTic = player.isElytraFlying() ? 300.0F : 100.0F; + + if (distanceSq - speedSq > maxDistancePerTic * packetCount && (!server.isSinglePlayer() || !server.getServerOwner().equals(player.getName()))) { + LOGGER.warn("{} moved too quickly! {},{},{}", player.getName(), xDiff, yDiff, zDiff); + setPlayerLocation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch); + return; + } + } + + boolean notInsideBlock = world.getCollisionBoxes(player, player.getEntityBoundingBox().shrink(0.0625D)).isEmpty(); + xDiff = packetX - lastGoodX; + yDiff = packetY - lastGoodY; + zDiff = packetZ - lastGoodZ; + + if (player.onGround && !packet.isOnGround() && yDiff > 0.0D) { + player.jump(); + } + + player.move(MoverType.PLAYER, xDiff, yDiff, zDiff); + player.onGround = packet.isOnGround(); + double oldYDiff = yDiff; + + xDiff = packetX - player.posX; + yDiff = packetY - player.posY; + if (yDiff > -0.5D || yDiff < 0.5D) { // TODO: But why? + yDiff = 0.0D; + } + zDiff = packetZ - player.posZ; + distanceSq = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff; + + boolean movedWrongly = false; + if (!player.isInvulnerableDimensionChange() && distanceSq > 0.0625D && !player.isPlayerSleeping() && !player.interactionManager.isCreative() && player.interactionManager.getGameType() != GameType.SPECTATOR) { + movedWrongly = true; + LOGGER.warn("{} moved wrongly!", player.getName()); + } + + // Fix https://bugs.mojang.com/browse/MC-98153 + //player.setPositionAndRotation(packetX, packetY, packetZ, packetYaw, packetPitch); + //player.addMovementStat(player.posX - oldX, player.posY - oldY, player.posZ - oldZ); + player.addMovementStat(packetX - oldX, packetY - oldY, packetZ - oldZ); + + // Fix https://bugs.mojang.com/browse/MC-123364 (partially, players can still cheat to teleport into a portal) + if (!player.isInvulnerableDimensionChange() && !player.noClip && !player.isPlayerSleeping()) { + boolean oldPositionEmpty = world.getCollisionBoxes(player, player.getEntityBoundingBox().shrink(0.0625D)).isEmpty(); + + if (notInsideBlock && (movedWrongly || !oldPositionEmpty)) { + setPlayerLocation(oldX, oldY, oldZ, packetYaw, packetPitch); + return; + } + } + + floating = oldYDiff >= -0.03125D; + floating &= !server.isFlightAllowed() && !player.capabilities.allowFlying; + floating &= !player.isPotionActive(MobEffects.LEVITATION) && !player.isElytraFlying() && !world.checkBlockCollision(player.getEntityBoundingBox().grow(0.0625D).expand(0.0D, -0.55D, 0.0D)); + player.onGround = packet.isOnGround(); + server.getPlayerList().serverUpdateMovingPlayer(player); + player.handleFalling(player.posY - oldY2, packet.isOnGround()); + lastGoodX = player.posX; + lastGoodY = player.posY; + lastGoodZ = player.posZ; + } + } + } + } + } +} diff --git a/src/main/resources/org.dimdev.vanillafix.mixins.json b/src/main/resources/org.dimdev.vanillafix.mixins.json new file mode 100644 index 00000000..341185f8 --- /dev/null +++ b/src/main/resources/org.dimdev.vanillafix.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "package": "org.dimdev.vanillafix.mixins", + "refmap": "org.dimdev.vanillafix.mixins.refmap.json", + "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.15-SNAPSHOT", + "mixins": [ + "MixinNetHandlerPlayServer" + ], + "client": [], + "server": [], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "conformVisibility": true + } +}