diff --git a/src/main/java/codechicken/multipart/api/ItemMultipart.java b/src/main/java/codechicken/multipart/api/ItemMultipart.java index 45fc3b3..31d10e9 100644 --- a/src/main/java/codechicken/multipart/api/ItemMultipart.java +++ b/src/main/java/codechicken/multipart/api/ItemMultipart.java @@ -12,6 +12,9 @@ import net.minecraft.world.level.block.SoundType; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.Collections; + /** * Created by covers1624 on 1/1/21. */ @@ -24,6 +27,10 @@ public ItemMultipart(Properties properties) { @Nullable public abstract MultiPart newPart(MultipartPlaceContext context); + protected Collection newParts(MultipartPlaceContext context) { + return Collections.singletonList(newPart(context)); + } + @Override public InteractionResult useOn(UseOnContext context) { @@ -39,18 +46,21 @@ private boolean place(MultipartPlaceContext context) { Level world = context.getLevel(); BlockPos pos = context.getClickedPos(); - MultiPart part = newPart(context); - if (part == null || !TileMultipart.canPlacePart(context, part)) return false; + Collection parts = newParts(context); + if (parts.isEmpty() || !TileMultipart.canPlaceParts(context, parts)) return false; if (!world.isClientSide) { - TileMultipart.addPart(world, pos, part); - SoundType sound = part.getPlacementSound(context); - if (sound != null) { + for (MultiPart part : parts) { + TileMultipart.addPart(world, pos, part); + + SoundType sound = part.getPlacementSound(context); + if (sound == null) continue; + world.playSound(null, pos, sound.getPlaceSound(), SoundSource.BLOCKS, (sound.getVolume() + 1.0F) / 2.0F, sound.getPitch() * 0.8F); } } - if (!context.getPlayer().getAbilities().instabuild) { + if (!context.getPlayer().isCreative()) { context.getItemInHand().shrink(1); } return true; diff --git a/src/main/java/codechicken/multipart/block/TileMultipart.java b/src/main/java/codechicken/multipart/block/TileMultipart.java index 19848f9..6b677e0 100644 --- a/src/main/java/codechicken/multipart/block/TileMultipart.java +++ b/src/main/java/codechicken/multipart/block/TileMultipart.java @@ -15,7 +15,6 @@ import codechicken.multipart.network.MultiPartSPH; import codechicken.multipart.util.*; import com.google.common.base.Preconditions; -import net.covers1624.quack.collection.ColUtils; import net.covers1624.quack.collection.FastStream; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -46,6 +45,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -234,8 +234,15 @@ public void writeDesc(MCDataOutput packet) { //region *** Adding/Removing parts *** - public boolean canAddPart(MultiPart part) { - return !partList.contains(part) && occlusionTest(partList, part); + public final boolean canAddPart(MultiPart part) { + return canAddParts(Collections.singletonList(part)); + } + + public boolean canAddParts(Iterable parts) { + for (MultiPart part : parts) { + if (partList.contains(part)) return false; + } + return occlusionTest(partList, parts); } /** @@ -251,11 +258,18 @@ public boolean canReplacePart(MultiPart oPart, MultiPart nPart) { if (oPart != nPart && partList.contains(nPart)) { return false; } - return occlusionTest(FastStream.of(partList).filter(e -> e != oPart), nPart); + return occlusionTest(FastStream.of(partList).filter(e -> e != oPart), Collections.singletonList(nPart)); } - public boolean occlusionTest(Iterable parts, MultiPart nPart) { - return ColUtils.allMatch(parts, part -> part.occlusionTest(nPart) && nPart.occlusionTest(part)); + public boolean occlusionTest(Iterable parts, Iterable newParts) { + for (MultiPart oldPart : parts) { + for (MultiPart newPart : newParts) { + if (!oldPart.occlusionTest(newPart) || !newPart.occlusionTest(oldPart)) { + return false; + } + } + } + return true; } public void addPart_impl(MultiPart part) { @@ -607,16 +621,22 @@ public CapabilityCache getCapCache() { //endregion public static boolean canPlacePart(UseOnContext context, MultiPart part) { + return canPlaceParts(context, Collections.singletonList(part)); + } + + public static boolean canPlaceParts(UseOnContext context, Iterable parts) { Level level = context.getLevel(); BlockPos pos = context.getClickedPos(); - if (!isUnobstructed(level, pos, part)) { - return false; + for (MultiPart p : parts) { + if (!isUnobstructed(level, pos, p)) { + return false; + } } TileMultipart tile = MultipartHelper.getOrConvertTile(level, pos); if (tile != null) { - return tile.canAddPart(part); + return tile.canAddParts(parts); } if (!replaceable(level, pos, context)) return false; diff --git a/src/main/java/codechicken/multipart/trait/TPartialOcclusionTile.java b/src/main/java/codechicken/multipart/trait/TPartialOcclusionTile.java index 817c160..e371f78 100644 --- a/src/main/java/codechicken/multipart/trait/TPartialOcclusionTile.java +++ b/src/main/java/codechicken/multipart/trait/TPartialOcclusionTile.java @@ -3,6 +3,8 @@ import codechicken.multipart.api.part.MultiPart; import codechicken.multipart.api.part.PartialOcclusionPart; import codechicken.multipart.block.TileMultipart; +import net.covers1624.quack.collection.ColUtils; +import net.covers1624.quack.collection.FastStream; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.BooleanOp; @@ -10,8 +12,8 @@ import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; -import java.util.LinkedList; import java.util.List; +import java.util.Set; /** * Implementation for the partial occlusion test. @@ -24,7 +26,7 @@ public class TPartialOcclusionTile extends TileMultipart { @Nullable private static TPartialOcclusionTile lastOcclusionTestedTile; @Nullable - private static VoxelShape lastOcclusionTestedShape; + private static Set lastOcclusionTestedShape; private static boolean lastOcclusionTestedResult; public TPartialOcclusionTile(BlockPos pos, BlockState state) { @@ -50,30 +52,31 @@ public void partRemoved(MultiPart remPart, int p) { } @Override - public boolean occlusionTest(Iterable parts, MultiPart npart) { - if (npart instanceof PartialOcclusionPart newPart) { - VoxelShape newShape = newPart.getPartialOcclusionShape(); - if (lastOcclusionTestedTile != this || lastOcclusionTestedShape != newShape) { + public boolean occlusionTest(Iterable parts, Iterable newParts) { + + if (ColUtils.anyMatch(newParts, e -> e instanceof PartialOcclusionPart)) { + Set newPartialShapes = FastStream.of(newParts) + .filter(e -> e instanceof PartialOcclusionPart) + .map(e -> ((PartialOcclusionPart) e).getPartialOcclusionShape()) + .toImmutableSet(); + if (lastOcclusionTestedTile != this || newPartialShapes.equals(lastOcclusionTestedShape)) { lastOcclusionTestedTile = this; - lastOcclusionTestedShape = newShape; - lastOcclusionTestedResult = partialOcclusionTest(parts, newPart); + lastOcclusionTestedShape = newPartialShapes; + lastOcclusionTestedResult = partialOcclusionTest(FastStream.concat(parts, newParts)); } if (!lastOcclusionTestedResult) { return false; } } - return super.occlusionTest(parts, npart); - } + return super.occlusionTest(parts, newParts); } - private static boolean partialOcclusionTest(Iterable allParts, PartialOcclusionPart newPart) { - List parts = new LinkedList<>(); - for (MultiPart part : allParts) { - if (part instanceof PartialOcclusionPart) { - parts.add((PartialOcclusionPart) part); - } - } - parts.add(newPart); + // Returns true if part list satisfies partial occlusion rules + private static boolean partialOcclusionTest(FastStream allParts) { + List parts = allParts + .filter(e -> e instanceof PartialOcclusionPart) + .map(e -> ((PartialOcclusionPart) e)) + .toList(); for (PartialOcclusionPart part1 : parts) { if (part1.allowCompleteOcclusion()) { diff --git a/src/main/java/codechicken/multipart/trait/TSlottedTile.java b/src/main/java/codechicken/multipart/trait/TSlottedTile.java index 2468b82..4c780dc 100644 --- a/src/main/java/codechicken/multipart/trait/TSlottedTile.java +++ b/src/main/java/codechicken/multipart/trait/TSlottedTile.java @@ -51,17 +51,25 @@ public void partRemoved(MultiPart part, int p) { } @Override - public boolean canAddPart(MultiPart part) { - if (part instanceof SlottedPart) { - int mask = ((SlottedPart) part).getSlotMask(); - for (int i = 0; i < v_partMap.length; i++) { - if ((mask & 1 << i) != 0 && getSlottedPart(i) != null) { - return false; - } + public boolean canAddParts(Iterable parts) { + // Check collisions among new parts + int mask = 0; + for (var p : parts) { + if (!(p instanceof SlottedPart sp)) continue; + + int partMask = sp.getSlotMask(); + if ((mask & partMask) != 0) return false; + mask |= partMask; + } + + // Check collisions of new mask with existing parts + for (int i = 0; i < v_partMap.length; i++) { + if ((mask & 1 << i) != 0 && getSlottedPart(i) != null) { + return false; } } - return super.canAddPart(part); + return super.canAddParts(parts); } @Override