package com.hypixel.hytale.builtin.blockphysics; import com.hypixel.hytale.component.ComponentAccessor; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.shape.Box; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.math.vector.Vector3i; import com.hypixel.hytale.protocol.BlockMaterial; import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFaceSupport; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; import com.hypixel.hytale.server.core.asset.type.blocktype.config.RequiredBlockFaceSupport; import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.util.FillerBlockUtil; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class BlockPhysicsUtil { public static final int DOESNT_SATISFY = 0; public static final int IGNORE = -1; public static final int SATISFIES_SUPPORT = -2; public static final int WAITING_CHUNK = -3; @Nonnull public static BlockPhysicsUtil.Result applyBlockPhysics( @Nullable ComponentAccessor commandBuffer, @Nonnull Ref chunkReference, @Nonnull BlockPhysicsSystems.CachedAccessor chunkAccessor, BlockSection blockSection, @Nonnull BlockPhysics blockPhysics, @Nonnull FluidSection fluidSection, int blockX, int blockY, int blockZ, @Nonnull BlockType blockType, int rotation, int filler ) { if (filler != 0) { return BlockPhysicsUtil.Result.VALID; } else { int supportDistance = -1; if (blockType.getHitboxTypeIndex() == 0) { supportDistance = testBlockPhysics(chunkAccessor, blockSection, blockPhysics, fluidSection, blockX, blockY, blockZ, blockType, rotation, filler); } else { BlockBoundingBoxes boundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); if (!boundingBoxes.protrudesUnitBox()) { supportDistance = testBlockPhysics(chunkAccessor, blockSection, blockPhysics, fluidSection, blockX, blockY, blockZ, blockType, rotation, filler); } else { BlockBoundingBoxes.RotatedVariantBoxes rotatedBox = boundingBoxes.get(rotation); Box boundingBox = rotatedBox.getBoundingBox(); int minX = (int)boundingBox.min.x; int minY = (int)boundingBox.min.y; int minZ = (int)boundingBox.min.z; if (minX - boundingBox.min.x > 0.0) { minX--; } if (minY - boundingBox.min.y > 0.0) { minY--; } if (minZ - boundingBox.min.z > 0.0) { minZ--; } int maxX = (int)boundingBox.max.x; int maxY = (int)boundingBox.max.y; int maxZ = (int)boundingBox.max.z; if (boundingBox.max.x - maxX > 0.0) { maxX++; } if (boundingBox.max.y - maxY > 0.0) { maxY++; } if (boundingBox.max.z - maxZ > 0.0) { maxZ++; } int blockWidth = Math.max(maxX - minX, 1); int blockHeight = Math.max(maxY - minY, 1); int blockDepth = Math.max(maxZ - minZ, 1); label138: for (int x = 0; x < blockWidth; x++) { for (int y = 0; y < blockHeight; y++) { for (int z = 0; z < blockDepth; z++) { int fillerX = blockX + minX + x; int fillerY = blockY + minY + y; int fillerZ = blockZ + minZ + z; BlockSection neighbourBlockSection; FluidSection neighbourFluidSection; BlockPhysics neighbourBlockPhysics; if (ChunkUtil.isSameChunkSection(blockX, blockY, blockZ, fillerX, fillerY, fillerZ)) { neighbourBlockSection = blockSection; neighbourFluidSection = fluidSection; neighbourBlockPhysics = blockPhysics; } else { int nx = ChunkUtil.chunkCoordinate(fillerX); int ny = ChunkUtil.chunkCoordinate(fillerY); int nz = ChunkUtil.chunkCoordinate(fillerZ); neighbourBlockSection = chunkAccessor.getBlockSection(nx, ny, nz); neighbourFluidSection = chunkAccessor.getFluidSection(nx, ny, nz); neighbourBlockPhysics = chunkAccessor.getBlockPhysics(nx, ny, nz); } if (neighbourBlockSection == null || neighbourFluidSection == null) { return BlockPhysicsUtil.Result.WAITING_CHUNK; } int neighbourFiller = FillerBlockUtil.pack(minX + x, minY + y, minZ + z); int neighbourRotation = neighbourBlockSection.getRotationIndex(fillerX, fillerY, fillerZ); int fillerSupportDistance = testBlockPhysics( chunkAccessor, neighbourBlockSection, neighbourBlockPhysics, neighbourFluidSection, fillerX, fillerY, fillerZ, blockType, neighbourRotation, neighbourFiller ); if (fillerSupportDistance != -1) { switch (blockType.getBlockSupportsRequiredFor()) { case Any: if (fillerSupportDistance == -2) { supportDistance = -2; break label138; } if (fillerSupportDistance == 0) { supportDistance = 0; } else if (supportDistance < fillerSupportDistance) { supportDistance = fillerSupportDistance; } break; case All: if (fillerSupportDistance == 0) { supportDistance = 0; break label138; } if (fillerSupportDistance == -2) { supportDistance = -2; } else if (supportDistance == -1 && supportDistance < fillerSupportDistance) { supportDistance = fillerSupportDistance; } } } } } } } } if (supportDistance == 0) { World world = commandBuffer.getExternalData().getWorld(); Store chunkStore = world.getChunkStore().getStore(); switch (blockType.getSupportDropType()) { case BREAK: BlockHarvestUtils.naturallyRemoveBlockByPhysics( new Vector3i(blockX, blockY, blockZ), blockType, filler, 256, chunkReference, commandBuffer, chunkStore ); break; case DESTROY: BlockHarvestUtils.naturallyRemoveBlockByPhysics( new Vector3i(blockX, blockY, blockZ), blockType, filler, 2304, chunkReference, commandBuffer, chunkStore ); } return BlockPhysicsUtil.Result.INVALID; } else if (supportDistance == -1) { return BlockPhysicsUtil.Result.VALID; } else if (supportDistance == -3) { return BlockPhysicsUtil.Result.WAITING_CHUNK; } else { int currentSupport = blockPhysics.get(blockX, blockY, blockZ); if (supportDistance == -2) { if (currentSupport != 0) { blockPhysics.set(blockX, blockY, blockZ, 0); chunkAccessor.performBlockUpdate(blockX, blockY, blockZ); } return BlockPhysicsUtil.Result.VALID; } else { if (currentSupport == supportDistance) { chunkAccessor.performBlockUpdate(blockX, blockY, blockZ, supportDistance - 1); } else { blockPhysics.set(blockX, blockY, blockZ, supportDistance); chunkAccessor.performBlockUpdate(blockX, blockY, blockZ); } return BlockPhysicsUtil.Result.VALID; } } } } public static int testBlockPhysics( @Nonnull BlockPhysicsSystems.CachedAccessor chunkAccessor, BlockSection blockSection, @Nullable BlockPhysics blockPhysics, @Nonnull FluidSection fluidSection, int blockX, int blockY, int blockZ, @Nonnull BlockType blockType, int rotation, int filler ) { if (blockType.isUnknown()) { return -1; } else { Map requiredBlockFaceSupportMap = blockType.getSupport(rotation); if (requiredBlockFaceSupportMap != null && !requiredBlockFaceSupportMap.isEmpty()) { Vector3i blockFillerOffset = new Vector3i(FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackY(filler), FillerBlockUtil.unpackZ(filler)); Vector3i neighbourFillerOffset = new Vector3i(); Fluid fluid = Fluid.getAssetMap().getAsset(fluidSection.getFluidId(blockX, blockY, blockZ)); BlockBoundingBoxes hitbox = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); Box boundingBox = hitbox.get(rotation).getBoundingBox(); Vector3i origin = new Vector3i( blockX - FillerBlockUtil.unpackX(filler), blockY - FillerBlockUtil.unpackY(filler), blockZ - FillerBlockUtil.unpackZ(filler) ); boolean hasTestedForSupport = false; int requiredSupportDistance = blockType.getMaxSupportDistance(); int lowestSupportDistance = Integer.MAX_VALUE; for (BlockFace blockFace : BlockFace.VALUES) { RequiredBlockFaceSupport[] requiredBlockFaceSupports = requiredBlockFaceSupportMap.get(blockFace); if (requiredBlockFaceSupports != null && requiredBlockFaceSupports.length != 0) { BlockFace[] connectingFaces = blockFace.getConnectingFaces(); Vector3i[] connectingFaceOffsets = blockFace.getConnectingFaceOffsets(); for (int i = 0; i < connectingFaces.length; i++) { BlockFace neighbourBlockFace = connectingFaces[i]; Vector3i neighbourDirection = connectingFaceOffsets[i]; int neighbourX = blockX + neighbourDirection.x; int neighbourY = blockY + neighbourDirection.y; int neighbourZ = blockZ + neighbourDirection.z; if (!boundingBox.containsBlock(origin, neighbourX, neighbourY, neighbourZ)) { BlockSection neighbourBlockSection; FluidSection neighbourFluidSection; BlockPhysics neighbourBlockPhysics; if (ChunkUtil.isSameChunkSection(blockX, blockY, blockZ, neighbourX, neighbourY, neighbourZ)) { neighbourBlockSection = blockSection; neighbourFluidSection = fluidSection; neighbourBlockPhysics = blockPhysics; } else { int nx = ChunkUtil.chunkCoordinate(neighbourX); int ny = ChunkUtil.chunkCoordinate(neighbourY); int nz = ChunkUtil.chunkCoordinate(neighbourZ); neighbourBlockSection = chunkAccessor.getBlockSection(nx, ny, nz); neighbourFluidSection = chunkAccessor.getFluidSection(nx, ny, nz); neighbourBlockPhysics = chunkAccessor.getBlockPhysics(nx, ny, nz); } if (neighbourFluidSection == null || neighbourBlockSection == null) { return -3; } int neighbourFluidId = neighbourFluidSection.getFluidId(neighbourX, neighbourY, neighbourZ); int neighbourBlockId = neighbourBlockSection.get(neighbourX, neighbourY, neighbourZ); int neighbourFiller = neighbourBlockSection.getFiller(neighbourX, neighbourY, neighbourZ); int neighbourRotation = neighbourBlockSection.getRotationIndex(neighbourX, neighbourY, neighbourZ); BlockType neighbourBlockType = BlockType.getAssetMap().getAsset(neighbourBlockId); Fluid neighbourFluid = Fluid.getAssetMap().getAsset(neighbourFluidId); neighbourFillerOffset.assign( FillerBlockUtil.unpackX(neighbourFiller), FillerBlockUtil.unpackY(neighbourFiller), FillerBlockUtil.unpackZ(neighbourFiller) ); boolean doesSatisfySupport = false; boolean failedSatisfySupport = false; for (RequiredBlockFaceSupport requiredBlockFaceSupport : requiredBlockFaceSupports) { if (requiredBlockFaceSupport.isAppliedToFiller(blockFillerOffset)) { boolean doesSatisfyRequirements = doesSatisfyRequirements( blockType, fluid, blockFillerOffset, neighbourFillerOffset, blockFace, neighbourBlockFace, neighbourBlockId, neighbourBlockType, neighbourRotation, neighbourFluidId, neighbourFluid, requiredBlockFaceSupport ); if (doesSatisfyRequirements && requiredSupportDistance > 0 && requiredBlockFaceSupport.allowsSupportPropagation()) { int supportDistance = neighbourBlockPhysics != null ? neighbourBlockPhysics.get(neighbourX, neighbourY, neighbourZ) : 0; if (supportDistance == 15) { lowestSupportDistance = 1; } else if (supportDistance < lowestSupportDistance) { lowestSupportDistance = supportDistance; } } switch (requiredBlockFaceSupport.getSupport()) { case IGNORED: break; case REQUIRED: if (doesSatisfyRequirements) { doesSatisfySupport = true; } hasTestedForSupport = true; break; case DISALLOWED: if (doesSatisfyRequirements) { failedSatisfySupport = true; } hasTestedForSupport = true; break; default: throw new IllegalArgumentException("Unknown Support Match type: " + requiredBlockFaceSupport.getMatchSelf()); } } } if (!failedSatisfySupport && doesSatisfySupport) { return -2; } } } } } if (!hasTestedForSupport) { return -1; } else { if (lowestSupportDistance < Integer.MAX_VALUE && lowestSupportDistance >= 0) { int supportDistance = lowestSupportDistance + 1; if (requiredSupportDistance >= supportDistance) { return supportDistance; } } return 0; } } else { return -1; } } } public static boolean doesSatisfyRequirements( @Nonnull BlockType blockType, Fluid fluid, Vector3i blockFillerOffset, Vector3i neighbourFillerOffset, BlockFace blockFace, BlockFace neighbourBlockFace, int neighbourBlockId, @Nonnull BlockType neighbourBlockType, int neighbourRotation, int neighbourFluidId, @Nonnull Fluid neighbourFluid, @Nonnull RequiredBlockFaceSupport requiredBlockFaceSupport ) { String neighbourBlockTypeKey = neighbourBlockType.getId(); boolean hasSupport = true; int blockSetId = requiredBlockFaceSupport.getBlockSetIndex(); if (blockSetId >= 0 && !BlockSetModule.getInstance().blockInSet(blockSetId, neighbourBlockId)) { hasSupport = false; } String requiredBlockTypeId = requiredBlockFaceSupport.getBlockTypeId(); if (hasSupport && requiredBlockTypeId != null && !requiredBlockTypeId.equals(neighbourBlockTypeKey)) { hasSupport = false; } String fluidId = requiredBlockFaceSupport.getFluidId(); if (hasSupport && fluidId != null && (neighbourBlockType.getMaterial() != BlockMaterial.Empty || neighbourFluidId == 0 || !fluidId.equals(neighbourFluid.getId()))) { hasSupport = false; } int tagIndex = requiredBlockFaceSupport.getTagIndex(); if (tagIndex >= 0 && !BlockType.getAssetMap().getKeysForTag(tagIndex).contains(neighbourBlockTypeKey)) { hasSupport = false; } if (hasSupport && requiredBlockFaceSupport.getFaceType() != null) { hasSupport = doesMatchFaceType( neighbourFillerOffset, requiredBlockFaceSupport.getFaceType(), neighbourBlockFace, neighbourBlockType.getSupporting(neighbourRotation) ); } if (hasSupport && requiredBlockFaceSupport.getSelfFaceType() != null) { hasSupport = doesMatchFaceType(blockFillerOffset, requiredBlockFaceSupport.getSelfFaceType(), blockFace, blockType.getSupporting(neighbourRotation)); } return switch (requiredBlockFaceSupport.getMatchSelf()) { case IGNORED -> { } case REQUIRED -> { if (hasSupport) { yield blockType.getId().equals(neighbourBlockTypeKey); } } case DISALLOWED -> { if (hasSupport) { yield !blockType.getId().equals(neighbourBlockTypeKey); } } default -> throw new IllegalArgumentException("Unknown MatchSelf type: " + requiredBlockFaceSupport.getMatchSelf()); }; } public static boolean doesMatchFaceType( Vector3i fillerOffset, @Nonnull String faceType, BlockFace blockFace, @Nonnull Map supporting ) { boolean faceHasSupport = false; BlockFaceSupport[] blockFaceSupports = supporting.get(blockFace); if (blockFaceSupports != null) { for (BlockFaceSupport blockFaceSupport : blockFaceSupports) { if (blockFaceSupport.providesSupportFromFiller(fillerOffset) && faceType.equals(blockFaceSupport.getFaceType())) { faceHasSupport = true; break; } } } return faceHasSupport; } public static enum Result { INVALID, VALID, WAITING_CHUNK; } }