diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/AbstractContraptionEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/AbstractContraptionEntity.java index 00af0d452..205d94255 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/AbstractContraptionEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/AbstractContraptionEntity.java @@ -1,6 +1,5 @@ package com.simibubi.create.content.contraptions.components.structureMovement; -import java.io.IOException; import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; @@ -11,11 +10,11 @@ import java.util.UUID; import javax.annotation.Nullable; +import com.simibubi.create.foundation.utility.ContraptionData; + import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.MutablePair; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.AllItems; import com.simibubi.create.AllMovementBehaviours; @@ -44,7 +43,6 @@ import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.Tag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; @@ -96,7 +94,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit /* * staleTicks are a band-aid to prevent a frame or two of missing blocks between * contraption discard and off-thread block placement on disassembly - * + * * FIXME this timeout should be longer but then also cancelled early based on a * chunk rebuild listener */ @@ -116,7 +114,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit return; contraption.onEntityCreated(this); } - + @Override public void move(MoverType pType, Vec3 pPos) { if (pType == MoverType.SHULKER) @@ -140,7 +138,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit public boolean collisionEnabled() { return true; } - + public void registerColliding(Entity collidingEntity) { collidingEntities.put(collidingEntity, new MutableInt()); } @@ -319,7 +317,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit public Vec3 toGlobalVector(Vec3 localVec, float partialTicks) { return toGlobalVector(localVec, partialTicks, false); } - + public Vec3 toGlobalVector(Vec3 localVec, float partialTicks, boolean prevAnchor) { Vec3 anchor = prevAnchor ? getPrevAnchorVec() : getAnchorVec(); Vec3 rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO); @@ -329,7 +327,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit .add(anchor); return localVec; } - + public Vec3 toLocalVector(Vec3 localVec, float partialTicks) { return toLocalVector(localVec, partialTicks, false); } @@ -546,7 +544,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit public Vec3 getAnchorVec() { return position(); } - + public Vec3 getPrevAnchorVec() { return getPrevPositionVec(); } @@ -598,22 +596,10 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit CompoundTag compound = new CompoundTag(); writeAdditional(compound, true); - try { - ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput(); - NbtIo.write(compound, dataOutput); - byte[] byteArray = dataOutput.toByteArray(); - int estimatedPacketSize = byteArray.length; - if (estimatedPacketSize > 2_000_000) { - Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): " - + getContraption().getType().id + " @" + position() + " (" + getUUID().toString() + ")"); - buffer.writeNbt(new CompoundTag()); - return; - } - - } catch (IOException e) { - e.printStackTrace(); - buffer.writeNbt(new CompoundTag()); - return; + if (ContraptionData.isTooLargeForSync(compound)) { + String info = getContraption().getType().id + " @" + position() + " (" + getStringUUID() + ")"; + Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): " + info); + compound = null; } buffer.writeNbt(compound); @@ -633,7 +619,10 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit @Override public void readSpawnData(FriendlyByteBuf additionalData) { - readAdditional(additionalData.readNbt(), true); + CompoundTag nbt = additionalData.readAnySizeNbt(); + if (nbt != null) { + readAdditional(nbt, true); + } } @Override @@ -823,7 +812,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit public Vec3 getContactPointMotion(Vec3 globalContactPoint) { if (prevPosInvalid) return Vec3.ZERO; - + Vec3 contactPoint = toGlobalVector(toLocalVector(globalContactPoint, 0, true), 1, true); Vec3 contraptionLocalMovement = contactPoint.subtract(globalContactPoint); Vec3 contraptionAnchorMovement = position().subtract(getPrevPositionVec()); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/mounted/MinecartContraptionItem.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/mounted/MinecartContraptionItem.java index 71123c191..5b0d02320 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/mounted/MinecartContraptionItem.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/mounted/MinecartContraptionItem.java @@ -1,14 +1,15 @@ package com.simibubi.create.content.contraptions.components.structureMovement.mounted; -import java.io.IOException; import java.util.List; import javax.annotation.Nullable; +import com.simibubi.create.foundation.utility.ContraptionData; + +import net.minecraft.network.chat.MutableComponent; + import org.apache.commons.lang3.tuple.MutablePair; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; import com.simibubi.create.AllItems; import com.simibubi.create.AllMovementBehaviours; import com.simibubi.create.content.contraptions.components.actors.PortableStorageInterfaceMovement; @@ -31,7 +32,6 @@ import net.minecraft.core.NonNullList; import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; import net.minecraft.core.dispenser.DispenseItemBehavior; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.tags.BlockTags; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; @@ -251,18 +251,10 @@ public class MinecartContraptionItem extends Item { ItemStack generatedStack = create(type, oce).setHoverName(entity.getCustomName()); - try { - ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput(); - NbtIo.write(generatedStack.serializeNBT(), dataOutput); - int estimatedPacketSize = dataOutput.toByteArray().length; - if (estimatedPacketSize > 2_000_000) { - player.displayClientMessage(Lang.translateDirect("contraption.minecart_contraption_too_big") - .withStyle(ChatFormatting.RED), true); - return; - } - - } catch (IOException e) { - e.printStackTrace(); + if (ContraptionData.isTooLargeForPickup(generatedStack.serializeNBT())) { + MutableComponent message = Lang.translateDirect("contraption.minecart_contraption_too_big") + .withStyle(ChatFormatting.RED); + player.displayClientMessage(message, true); return; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderingWorld.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderingWorld.java index 072b8842d..f09549fa5 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderingWorld.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderingWorld.java @@ -64,6 +64,7 @@ public abstract class ContraptionRenderingWorld .map(Reference::get) .filter(Objects::nonNull) .map(AbstractContraptionEntity::getContraption) + .filter(Objects::nonNull) // contraptions that are too large will not be synced, and un-synced contraptions will be null .forEach(this::getRenderInfo); } diff --git a/src/main/java/com/simibubi/create/foundation/config/CKinetics.java b/src/main/java/com/simibubi/create/foundation/config/CKinetics.java index 3e02181f1..d473b2777 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CKinetics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CKinetics.java @@ -1,6 +1,7 @@ package com.simibubi.create.foundation.config; import com.simibubi.create.foundation.config.ui.ConfigAnnotations; +import com.simibubi.create.foundation.utility.ContraptionData; public class CKinetics extends ConfigBase { @@ -30,6 +31,8 @@ public class CKinetics extends ConfigBase { public final ConfigGroup contraptions = group(1, "contraptions", "Moving Contraptions"); public final ConfigInt maxBlocksMoved = i(2048, 1, "maxBlocksMoved", Comments.maxBlocksMoved); + public final ConfigInt maxDataSize = + i(ContraptionData.DEFAULT_MAX, 0, "maxDataSize", Comments.bytes, Comments.maxDataDisable, Comments.maxDataSize, Comments.maxDataSize2); public final ConfigInt maxChassisRange = i(16, 1, "maxChassisRange", Comments.maxChassisRange); public final ConfigInt maxPistonPoles = i(64, 1, "maxPistonPoles", Comments.maxPistonPoles); public final ConfigInt maxRopeLength = i(256, 1, "maxRopeLength", Comments.maxRopeLength); @@ -75,6 +78,9 @@ public class CKinetics extends ConfigBase { "multiplier used for calculating exhaustion from speed when a crank is turned."; static String maxBlocksMoved = "Maximum amount of blocks in a structure movable by Pistons, Bearings or other means."; + static String maxDataSize = "Maximum amount of data a contraption can have before it can't be synced with players."; + static String maxDataSize2 = "Un-synced contraptions will not be visible and will not have collision."; + static String maxDataDisable = "[0 to disable this limit]"; static String maxChassisRange = "Maximum value of a chassis attachment range."; static String maxPistonPoles = "Maximum amount of extension poles behind a Mechanical Piston."; static String maxRopeLength = "Max length of rope available off a Rope Pulley."; @@ -86,6 +92,7 @@ public class CKinetics extends ConfigBase { static String stats = "Configure speed/capacity levels for requirements and indicators."; static String rpm = "[in Revolutions per Minute]"; static String su = "[in Stress Units]"; + static String bytes = "[in Bytes]"; static String mediumSpeed = "Minimum speed of rotation to be considered 'medium'"; static String fastSpeed = "Minimum speed of rotation to be considered 'fast'"; static String mediumStressImpact = "Minimum stress impact to be considered 'medium'"; diff --git a/src/main/java/com/simibubi/create/foundation/mixin/accessor/NbtAccounterAccessor.java b/src/main/java/com/simibubi/create/foundation/mixin/accessor/NbtAccounterAccessor.java new file mode 100644 index 000000000..c466f13fa --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/accessor/NbtAccounterAccessor.java @@ -0,0 +1,12 @@ +package com.simibubi.create.foundation.mixin.accessor; + +import net.minecraft.nbt.NbtAccounter; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(NbtAccounter.class) +public interface NbtAccounterAccessor { + @Accessor("usage") + long create$getUsage(); +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/ContraptionData.java b/src/main/java/com/simibubi/create/foundation/utility/ContraptionData.java new file mode 100644 index 000000000..782afe380 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/ContraptionData.java @@ -0,0 +1,56 @@ +package com.simibubi.create.foundation.utility; + +import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.mixin.accessor.NbtAccounterAccessor; + +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; +import net.minecraftforge.fml.ModList; + +public class ContraptionData { + /** + * A sane, default maximum for contraption data size. + */ + public static final int DEFAULT_MAX = 2_000_000; + /** + * XL Packets expands the NBT packet limit to 2 GB. + */ + public static final int EXPANDED_MAX = 2_000_000_000; + /** + * Minecart item sizes are limited by the vanilla slot change packet ({@link ClientboundContainerSetSlotPacket}). + * {@link ContraptionData#DEFAULT_MAX} is used as the default. + * XL Packets expands the size limit to ~2 GB. If the mod is loaded, we take advantage of it and use the higher limit. + */ + public static final int PICKUP_MAX = ModList.get().isLoaded("xlpackets") ? EXPANDED_MAX : DEFAULT_MAX; + + /** + * @return true if the given NBT is too large for a contraption to be synced to clients. + */ + public static boolean isTooLargeForSync(CompoundTag data) { + int max = AllConfigs.SERVER.kinetics.maxDataSize.get(); + return max != 0 && packetSize(data) > max; + } + + /** + * @return true if the given NBT is too large for a contraption to be picked up with a wrench. + */ + public static boolean isTooLargeForPickup(CompoundTag data) { + return packetSize(data) > PICKUP_MAX; + } + + /** + * @return the size of the given NBT when put through a packet, in bytes. + */ + public static long packetSize(CompoundTag data) { + FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer()); + test.writeNbt(data); + NbtAccounter sizeTracker = new NbtAccounter(Long.MAX_VALUE); + test.readNbt(sizeTracker); + long size = ((NbtAccounterAccessor) sizeTracker).create$getUsage(); + test.release(); + return size; + } +} diff --git a/src/main/resources/create.mixins.json b/src/main/resources/create.mixins.json index af3452d15..dc5b8e9a8 100644 --- a/src/main/resources/create.mixins.json +++ b/src/main/resources/create.mixins.json @@ -13,6 +13,7 @@ "accessor.DispenserBlockAccessor", "accessor.FallingBlockEntityAccessor", "accessor.LivingEntityAccessor", + "accessor.NbtAccounterAccessor", "accessor.ServerLevelAccessor" ], "client": [