diff --git a/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java index 5e32073..8df8d43 100644 --- a/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java @@ -2,14 +2,10 @@ package nl.requios.effortlessbuilding; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.common.util.FakePlayer; -import net.minecraftforge.event.AttachCapabilitiesEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.level.BlockEvent; @@ -17,10 +13,9 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.network.PacketDistributor; +import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.network.ModifierSettingsPacket; import nl.requios.effortlessbuilding.network.PacketHandler; -import nl.requios.effortlessbuilding.systems.UndoRedo; -import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.systems.ServerBuildState; import nl.requios.effortlessbuilding.utilities.ReachHelper; @@ -58,6 +53,7 @@ public class CommonEvents { //Fixed issue with e.g. Create Wrench shift-rightclick disassembling being cancelled. if (isPlayerHoldingBlock(player)) { event.setCanceled(true); + //TODO Notify client to not decrease itemstack } } } diff --git a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java index 0a48603..4e5db08 100644 --- a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java +++ b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java @@ -11,6 +11,8 @@ import nl.requios.effortlessbuilding.gui.RandomizerBagScreen; import nl.requios.effortlessbuilding.render.BlockPreviews; import nl.requios.effortlessbuilding.systems.BuilderChain; import nl.requios.effortlessbuilding.systems.BuildSettings; +import nl.requios.effortlessbuilding.systems.BuilderFilter; +import nl.requios.effortlessbuilding.systems.ItemUsageTracker; public class EffortlessBuildingClient { @@ -19,6 +21,8 @@ public class EffortlessBuildingClient { public static final BuildModifiers BUILD_MODIFIERS = new BuildModifiers(); public static final BuildSettings BUILD_SETTINGS = new BuildSettings(); public static final BlockPreviews BLOCK_PREVIEWS = new BlockPreviews(); + public static final BuilderFilter BUILDER_FILTER = new BuilderFilter(); + public static final ItemUsageTracker ITEM_USAGE_TRACKER = new ItemUsageTracker(); public static void onConstructorClient(IEventBus modEventBus, IEventBus forgeEventBus) { modEventBus.addListener(EffortlessBuildingClient::clientSetup); diff --git a/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java b/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java index b249933..eb013e4 100644 --- a/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java +++ b/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java @@ -36,7 +36,7 @@ public class ServerConfig { maxBlocksPlacedAtOnce = builder .comment("Maximum number of blocks that can be placed at once.") - .defineInRange("maxBlocksPlacedAtOnce", 1000, 1, 10000); + .defineInRange("maxBlocksPlacedAtOnce", 10000, 1, 100000); builder.pop(); } diff --git a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java index 402ddc0..947be04 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java @@ -211,7 +211,8 @@ public class BlockPreviews { CreateClient.GHOST_BLOCKS.showGhostState(blockPos.toShortString(), blockState) .at(blockPos) .alpha(alpha) - .scale(scale); + .scale(scale) + .colored(blockEntry.invalid ? Color.RED : Color.WHITE); } //k=1 is the identity curve, k<1 produces the classic gain() shape, and k>1 produces "s" shaped curves. The curves are symmetric (and inverse) for k=a and k=1/a. diff --git a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java index 1ec09db..c26b2c8 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java @@ -7,8 +7,10 @@ import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderGuiEvent; @@ -16,6 +18,7 @@ import net.minecraftforge.client.event.RenderLevelStageEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import nl.requios.effortlessbuilding.EffortlessBuildingClient; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; import nl.requios.effortlessbuilding.systems.BuilderChain; /*** @@ -47,6 +50,8 @@ public class RenderHandler { @SubscribeEvent public static void onRenderGuiEvent(RenderGuiEvent event) { renderSubText(event.getPoseStack()); + + drawStacks(event.getPoseStack()); } private static final ChatFormatting highlightColor = ChatFormatting.DARK_AQUA; @@ -70,7 +75,7 @@ public class RenderHandler { var font = Minecraft.getInstance().font; ms.pushPose(); - ms.translate((double)(screenWidth / 2), (double)(screenHeight - 54), 0.0D); + ms.translate(screenWidth / 2.0, screenHeight - 54, 0.0D); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); int l = font.width(text); @@ -79,6 +84,56 @@ public class RenderHandler { ms.popPose(); } + //Draw item stacks at cursor, showing what will be used and what is missing + private static void drawStacks(PoseStack ms) { + var state = EffortlessBuildingClient.BUILDER_CHAIN.getBuildingState(); + if (state != BuilderChain.BuildingState.PLACING) return; + + var stacks = EffortlessBuildingClient.ITEM_USAGE_TRACKER.total; + //Show if we are in survival or we are using multiple types of items + if (Minecraft.getInstance().player == null || (Minecraft.getInstance().player.isCreative() && stacks.size() <= 1)) { + return; + } + + int screenWidth = Minecraft.getInstance().getWindow().getGuiScaledWidth(); + int screenHeight = Minecraft.getInstance().getWindow().getGuiScaledHeight(); + + int x = screenWidth / 2 + 10; + int y = screenHeight / 2 - 8; + + //Draw item texture with count + int i = 0; + for (var stack : stacks.entrySet()) { + int total = stack.getValue(); + int missing = EffortlessBuildingClient.ITEM_USAGE_TRACKER.getMissingCount(stack.getKey()); + + if (total - missing > 0) { + drawItemStack(ms, new ItemStack(stack.getKey(), total - missing), x + i * 20, y, false); + i++; + } + + if (missing > 0) { + drawItemStack(ms, new ItemStack(stack.getKey(), missing), x + i * 20, y, true); + i++; + } + } + } + + private static void drawItemStack(PoseStack ms, ItemStack stack, int x, int y, boolean missing) { + Minecraft.getInstance().getItemRenderer().renderGuiItem(stack, x, y); + + //draw count text, red if missing + //from ItemRenderer#renderGuiItemDecorations + ms.pushPose(); + Font font = Minecraft.getInstance().font; + String text = String.valueOf(stack.getCount()); + ms.translate(0.0D, 0.0D, (double)(Minecraft.getInstance().getItemRenderer().blitOffset + 200.0F)); + MultiBufferSource.BufferSource multibuffersource$buffersource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); + font.drawInBatch(text, (float)(x + 19 - 2 - font.width(text)), (float)(y + 6 + 3), missing ? ChatFormatting.RED.getColor() : ChatFormatting.WHITE.getColor(), true, ms.last().pose(), multibuffersource$buffersource, false, 0, 15728880); + multibuffersource$buffersource.endBatch(); + ms.popPose(); + } + protected static VertexConsumer beginLines(MultiBufferSource.BufferSource renderTypeBuffer) { return renderTypeBuffer.getBuffer(BuildRenderTypes.LINES); } diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java index 847bf50..9c75079 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java @@ -65,6 +65,7 @@ public class BuilderChain { private AbilitiesState abilitiesState = AbilitiesState.CAN_PLACE_AND_BREAK; public void onRightClick() { + if (abilitiesState != AbilitiesState.CAN_PLACE_AND_BREAK || buildingState == BuildingState.BREAKING) { cancel(); return; @@ -95,6 +96,7 @@ public class BuilderChain { } public void onLeftClick() { + if (abilitiesState == AbilitiesState.NONE || buildingState == BuildingState.PLACING) { cancel(); return; @@ -109,9 +111,9 @@ public class BuilderChain { //Use new start position for breaking, because we assumed the player was gonna place blocks.setStartPos(new BlockEntry(startPosForBreaking)); EffortlessBuildingClient.BUILD_MODIFIERS.findCoordinates(blocks, player); - BuilderFilter.filterOnCoordinates(blocks, player); + EffortlessBuildingClient.BUILDER_FILTER.filterOnCoordinates(blocks, player); findExistingBlockStates(player.level); - BuilderFilter.filterOnExistingBlockStates(blocks, player); + EffortlessBuildingClient.BUILDER_FILTER.filterOnExistingBlockStates(blocks, player); } var buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); @@ -131,6 +133,7 @@ public class BuilderChain { } public void onTick() { + var previousCoordinates = new HashSet<>(blocks.getCoordinates()); blocks.clear(); startPosForPlacing = null; @@ -160,7 +163,7 @@ public class BuilderChain { EffortlessBuildingClient.BUILD_MODES.findCoordinates(blocks, player); EffortlessBuildingClient.BUILD_MODIFIERS.findCoordinates(blocks, player); - BuilderFilter.filterOnCoordinates(blocks, player); + EffortlessBuildingClient.BUILDER_FILTER.filterOnCoordinates(blocks, player); if (buildMode == BuildModeEnum.DISABLED && blocks.size() <= 1) { abilitiesState = AbilitiesState.NONE; @@ -168,18 +171,17 @@ public class BuilderChain { } findExistingBlockStates(world); - BuilderFilter.filterOnExistingBlockStates(blocks, player); + EffortlessBuildingClient.BUILDER_FILTER.filterOnExistingBlockStates(blocks, player); - var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND); - findNewBlockStates(player, itemStack); - BuilderFilter.filterOnNewBlockStates(blocks, player); + var heldItem = player.getItemInHand(InteractionHand.MAIN_HAND); + findNewBlockStates(player, heldItem); //includes filtering on new blockstates //Check if any changes are made - if (previousHeldItem != itemStack.getItem() || !previousCoordinates.equals(blocks.getCoordinates())) { + if (previousHeldItem != heldItem.getItem() || !previousCoordinates.equals(blocks.getCoordinates())) { onBlocksChanged(player); } - previousHeldItem = itemStack.getItem(); + previousHeldItem = heldItem.getItem(); } //Whether we can place or break blocks, determined by what we are looking at and what we are holding @@ -222,9 +224,6 @@ public class BuilderChain { if (player.blockPosition().distSqr(startPos) > maxReach * maxReach) return null; startPosForBreaking = startPos; - if (!shouldLookAtNear && !ReachHelper.canBreakFar(player)) { - startPosForBreaking = null; - } if (abilitiesState == AbilitiesState.CAN_PLACE_AND_BREAK) { //Calculate start position for placing @@ -241,7 +240,7 @@ public class BuilderChain { //We can only break //Do not break far if we are not allowed to - if (startPosForBreaking == null) return null; + if (!shouldLookAtNear && !ReachHelper.canBreakFar(player)) return null; } var blockEntry = new BlockEntry(startPos); @@ -255,29 +254,54 @@ public class BuilderChain { } } - private void findNewBlockStates(Player player, ItemStack itemStack) { + private void findNewBlockStates(Player player, ItemStack heldItem) { if (buildingState == BuildingState.BREAKING) return; var originalDirection = player.getDirection(); var clickedFace = lookingAt.getDirection(); Vec3 relativeHitVec = lookingAt.getLocation().subtract(Vec3.atLowerCornerOf(lookingAt.getBlockPos())); - //TODO keep track of count and find different itemstack if necessary - if (itemStack.getItem() instanceof BlockItem) { + //Keep track of itemstack usage + EffortlessBuildingClient.ITEM_USAGE_TRACKER.initialize(player, heldItem); - for (BlockEntry blockEntry : blocks) { - blockEntry.setItemStackAndFindNewBlockState(itemStack, player.level, originalDirection, clickedFace, relativeHitVec); + var iter = blocks.entrySet().iterator(); + while (iter.hasNext()) { + var blockEntry = iter.next().getValue(); + + //Determine itemstack + ItemStack itemStack = determineItemStack(player, heldItem); + if (itemStack == null || itemStack.isEmpty()) { + iter.remove(); + continue; } - } else if (CompatHelper.isItemBlockProxy(itemStack, false)) { + //Find new blockstate + blockEntry.setItemAndFindNewBlockState(itemStack, player.level, originalDirection, clickedFace, relativeHitVec); - AbstractRandomizerBagItem.resetRandomness(); - for (BlockEntry blockEntry : blocks) { - ItemStack itemBlockStack = CompatHelper.getItemBlockFromStack(itemStack); - if (itemBlockStack == null || itemBlockStack.isEmpty()) continue; - blockEntry.setItemStackAndFindNewBlockState(itemBlockStack, player.level, originalDirection, clickedFace, relativeHitVec); + //Filter on new blockstate + if (EffortlessBuildingClient.BUILDER_FILTER.filterOnNewBlockState(blockEntry, player)) { + iter.remove(); + continue; } + + //Increase itemstack usage if not filtered out + //Mark invalid if the player does not have enough of that item + blockEntry.invalid = !EffortlessBuildingClient.ITEM_USAGE_TRACKER.increaseUsageCount(itemStack.getItem(), 1, player); } + + EffortlessBuildingClient.ITEM_USAGE_TRACKER.calculateMissingItems(player); + } + + private ItemStack determineItemStack(Player player, ItemStack heldItem) { + if (heldItem.getItem() instanceof BlockItem) { + return heldItem; + } + + if (CompatHelper.isItemBlockProxy(heldItem, false)) { + return CompatHelper.getItemBlockFromStack(heldItem); + } + + return null; } private void onBlocksChanged(Player player) { diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java index 3b3ad27..424ed0c 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java @@ -6,17 +6,28 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.compatibility.CompatHelper; +import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockSet; import nl.requios.effortlessbuilding.utilities.PlaceChecker; import nl.requios.effortlessbuilding.utilities.SurvivalHelper; @OnlyIn(Dist.CLIENT) public class BuilderFilter { - public static void filterOnCoordinates(BlockSet blocks, Player player) { + public void filterOnCoordinates(BlockSet blocks, Player player) { + var world = player.level; + var iter = blocks.entrySet().iterator(); + while (iter.hasNext()) { + var pos = iter.next().getValue().blockPos; + boolean remove = false; + if (!world.isLoaded(pos)) remove = true; + if (!world.getWorldBorder().isWithinBounds(pos)) remove = true; + + if (remove) iter.remove(); + } } - public static void filterOnExistingBlockStates(BlockSet blocks, Player player) { + public void filterOnExistingBlockStates(BlockSet blocks, Player player) { var buildSettings = EffortlessBuildingClient.BUILD_SETTINGS; var buildingState = EffortlessBuildingClient.BUILDER_CHAIN.getPretendBuildingState(); boolean placing = buildingState == BuilderChain.BuildingState.PLACING; @@ -60,19 +71,15 @@ public class BuilderFilter { // } } - public static void filterOnNewBlockStates(BlockSet blocks, Player player) { - var buildSettings = EffortlessBuildingClient.BUILD_SETTINGS; + //Returns true if we should remove the entry + public boolean filterOnNewBlockState(BlockEntry blockEntry, Player player) { var buildingState = EffortlessBuildingClient.BUILDER_CHAIN.getPretendBuildingState(); boolean placing = buildingState == BuilderChain.BuildingState.PLACING; - var iter = blocks.entrySet().iterator(); - while (iter.hasNext()) { - var blockEntry = iter.next().getValue(); - boolean remove = false; + boolean remove = false; - if (placing && !PlaceChecker.shouldPlaceBlock(player.level, blockEntry)) remove = true; + if (placing && !PlaceChecker.shouldPlaceBlock(player.level, blockEntry)) remove = true; - if (remove) iter.remove(); - } + return remove; } } diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/ItemUsageTracker.java b/src/main/java/nl/requios/effortlessbuilding/systems/ItemUsageTracker.java new file mode 100644 index 0000000..d8b64e5 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/systems/ItemUsageTracker.java @@ -0,0 +1,73 @@ +package nl.requios.effortlessbuilding.systems; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import nl.requios.effortlessbuilding.compatibility.CompatHelper; +import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; +import nl.requios.effortlessbuilding.utilities.InventoryHelper; + +import java.util.HashMap; +import java.util.Map; + +@OnlyIn(Dist.CLIENT) +public class ItemUsageTracker { + + //How many blocks we want to place + public Map total = new HashMap<>(); + + //How many blocks we have in inventory in total + public Map inInventory = new HashMap<>(); + + //How many blocks are missing from our inventory + public Map missing = new HashMap<>(); + + public void initialize(Player player, ItemStack heldItem) { + total.clear(); + inInventory.clear(); + missing.clear(); + + if (CompatHelper.isItemBlockProxy(heldItem, false)) { + AbstractRandomizerBagItem.resetRandomness(); + } + } + + //returns if we have enough items in inventory to use count more + public boolean increaseUsageCount(Item item, int count, Player player) { + if (item == null) return true; + int newValue = total.getOrDefault(item, 0) + count; + total.put(item, newValue); + + if (player.isCreative()) return true; + int have = 0; + if (inInventory.containsKey(item)) { + have = inInventory.get(item); + } else { + have = InventoryHelper.findTotalItemsInInventory(player, item); + inInventory.put(item, have); + } + + return have >= newValue; + } + + public void calculateMissingItems(Player player) { + if (player.isCreative()) return; + for (Item item : total.keySet()) { + int used = total.get(item); + int have = inInventory.getOrDefault(item, 0); + if (used > have) { + missing.put(item, used - have); + } + } + } + + public int getValidCount(Item item) { + return total.getOrDefault(item, 0) - missing.getOrDefault(item, 0); + } + + public int getMissingCount(Item item) { + return missing.getOrDefault(item, 0); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java index e359650..2b52ccf 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java @@ -25,6 +25,7 @@ public class ServerBlockPlacer { private final Set delayedEntriesView = Collections.unmodifiableSet(delayedEntries); public void placeBlocksDelayed(Player player, BlockSet blocks, long placeTime) { + if (!checkAndNotifyAllowedToUseMod(player)) return; if (!validateBlockSet(player, blocks)) return; @@ -32,6 +33,7 @@ public class ServerBlockPlacer { } public void tick() { + //Iterator to prevent concurrent modification exception for (var iterator = delayedEntries.iterator(); iterator.hasNext(); ) { DelayedEntry entry = iterator.next(); @@ -55,6 +57,7 @@ public class ServerBlockPlacer { } public void applyBlockSet(Player player, BlockSet blocks) { + if (!checkAndNotifyAllowedToUseMod(player)) return; if (!validateBlockSet(player, blocks)) return; @@ -71,6 +74,7 @@ public class ServerBlockPlacer { } public void undoBlockSet(Player player, BlockSet blocks) { + if (!isAllowedToUndo(player, true)) return; var redoSet = new BlockSet(); @@ -85,6 +89,7 @@ public class ServerBlockPlacer { } private boolean applyBlockEntry(Player player, BlockEntry block) { + block.existingBlockState = player.level.getBlockState(block.blockPos); boolean breaking = BlockUtilities.isNullOrAir(block.newBlockState); if (!validateBlockEntry(player, block, breaking)) return false; @@ -101,6 +106,7 @@ public class ServerBlockPlacer { } private boolean undoBlockEntry(Player player, BlockEntry block) { + boolean breaking = BlockUtilities.isNullOrAir(block.existingBlockState); var tempBlockEntry = new BlockEntry(block.blockPos); @@ -126,11 +132,6 @@ public class ServerBlockPlacer { } private boolean checkAndNotifyAllowedToUseMod(Player player) { - //TODO temp no survival allowed - if (!player.isCreative()) { - EffortlessBuilding.log(player, ChatFormatting.RED + "Effortless Building is not yet supported in survival mode."); - return false; - } if (!player.getAbilities().mayBuild) { EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to build."); @@ -145,6 +146,7 @@ public class ServerBlockPlacer { } private boolean isAllowedToUseMod(Player player) { + if (!ServerConfig.validation.allowInSurvival.get() && !player.isCreative()) return false; if (ServerConfig.validation.useWhitelist.get()) { @@ -155,8 +157,9 @@ public class ServerBlockPlacer { } private boolean isAllowedToUndo(Player player, boolean log) { + if (!player.isCreative()) { - if (log) EffortlessBuilding.log(player, ChatFormatting.RED + "Undo is not supported in survival mode."); + if (log) EffortlessBuilding.log(player, ChatFormatting.RED + "Undo is not available in survival mode."); return false; } @@ -164,6 +167,7 @@ public class ServerBlockPlacer { } private boolean validateBlockSet(Player player, BlockSet blocks) { + if (blocks.isEmpty()) { EffortlessBuilding.log(player, ChatFormatting.RED + "No blocks to place."); return false; @@ -178,34 +182,41 @@ public class ServerBlockPlacer { } //Dont allow mixing breaking and placing blocks - //First determine if we are breaking or placing - var iterator = blocks.iterator(); - //Get any block from the set, skip first if we have to - var anyBlock = iterator.next(); - if (blocks.skipFirst && anyBlock.blockPos == blocks.firstPos) { - anyBlock = iterator.next(); - } - boolean breaking = anyBlock.newBlockState == null || anyBlock.newBlockState.isAir(); - - while (iterator.hasNext()) { - var block = iterator.next(); - if (block.newBlockState == null || block.newBlockState.isAir()) { - if (!breaking) { - EffortlessBuilding.log(player, ChatFormatting.RED + "Cannot mix breaking and placing blocks."); - return false; - } - } else { - if (breaking) { - EffortlessBuilding.log(player, ChatFormatting.RED + "Cannot mix breaking and placing blocks."); - return false; - } - } + if (isMixedPlacingAndBreaking(player, blocks)) { + EffortlessBuilding.log(player, ChatFormatting.RED + "Cannot mix breaking and placing blocks."); + return false; } return true; } + private boolean isMixedPlacingAndBreaking(Player player, BlockSet blocks) { + + //First determine if we are breaking or placing + var iterator = blocks.iterator(); + + //Get any block from the set, skip first if we have to + var anyBlock = iterator.next(); + if (blocks.skipFirst && anyBlock.blockPos == blocks.firstPos) { + anyBlock = iterator.next(); + } + + boolean breaking = anyBlock.newBlockState == null || anyBlock.newBlockState.isAir(); + + while (iterator.hasNext()) { + var block = iterator.next(); + if (block.newBlockState == null || block.newBlockState.isAir()) { + if (!breaking) return true; + } else { + if (breaking) return true; + } + } + + return false; + } + private boolean validateBlockEntry(Player player, BlockEntry block, boolean breaking) { + if (!player.level.isLoaded(block.blockPos)) return false; if (breaking && BlockUtilities.isNullOrAir(block.existingBlockState)) return false; diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockEntry.java b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockEntry.java index 22cf7d3..c69d628 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockEntry.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockEntry.java @@ -4,6 +4,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -22,7 +23,9 @@ public class BlockEntry { //BlockState that is currently in the world public BlockState existingBlockState; public BlockState newBlockState; - public ItemStack itemStack = ItemStack.EMPTY; + public Item item; + //Invalid block entries will be marked red and won't be sent to server + public boolean invalid = false; public BlockEntry(BlockPos blockPos) { this.blockPos = blockPos; @@ -35,10 +38,11 @@ public class BlockEntry { rotation = blockEntry.rotation; } - public void setItemStackAndFindNewBlockState(ItemStack itemStack, Level world, Direction originalDirection, Direction clickedFace, Vec3 relativeHitVec) { - this.itemStack = itemStack; + public void setItemAndFindNewBlockState(ItemStack itemStack, Level world, Direction originalDirection, Direction clickedFace, Vec3 relativeHitVec) { + this.item = itemStack.getItem(); - Block block = Block.byItem(itemStack.getItem()); + //Find new blockstate with right direction + Block block = Block.byItem(this.item); var direction = originalDirection; if (rotation != null) direction = rotation.rotate(direction); direction = applyMirror(direction); @@ -62,7 +66,7 @@ public class BlockEntry { public static void encode(FriendlyByteBuf buf, BlockEntry block) { buf.writeBlockPos(block.blockPos); buf.writeNullable(block.newBlockState, (buffer, blockState) -> buffer.writeNbt(NbtUtils.writeBlockState(blockState))); - buf.writeItem(block.itemStack); + buf.writeInt(Item.getId(block.item)); } public static BlockEntry decode(FriendlyByteBuf buf) { @@ -72,7 +76,7 @@ public class BlockEntry { if (nbt == null) return null; return NbtUtils.readBlockState(nbt); }); - block.itemStack = buf.readItem(); + block.item = Item.byId(buf.readInt()); return block; } } diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockPlacerHelper.java b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockPlacerHelper.java index 83efa7c..aff742f 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockPlacerHelper.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockPlacerHelper.java @@ -26,6 +26,7 @@ import java.util.List; public class BlockPlacerHelper { public static boolean breakBlock(Player player, BlockEntry blockEntry) { + ItemStack usedTool = player.getMainHandItem(); if (usedTool.isEmpty() || !(usedTool.getItem() instanceof DiggerItem)) { ItemStack offhand = player.getOffhandItem(); @@ -42,20 +43,14 @@ public class BlockPlacerHelper { return brokeBlock; } + //ForgeHooks::onPlaceItemIntoWorld, removed itemstack usage public static boolean placeBlock(Player player, BlockEntry blockEntry) { - if (blockEntry.itemStack == null) { - return placeBlockWithoutItem(player, blockEntry); - } else { - var interactionResult = placeItem(player, blockEntry); - return interactionResult == InteractionResult.SUCCESS; - } - } - private static boolean placeBlockWithoutItem(Player player, BlockEntry blockEntry) { Level level = player.level; + var itemStack = new ItemStack(blockEntry.item); level.captureBlockSnapshots = true; - BlockHelper.placeSchematicBlock(level, player, blockEntry.newBlockState, blockEntry.blockPos, blockEntry.itemStack, null); + BlockHelper.placeSchematicBlock(level, player, blockEntry.newBlockState, blockEntry.blockPos, itemStack, null); level.captureBlockSnapshots = false; //Find out if we get to keep the placed block by sending a forge event @@ -99,96 +94,4 @@ public class BlockPlacerHelper { level.capturedBlockSnapshots.clear(); return !eventResult; } - - //ForgeHooks::onPlaceItemIntoWorld - private static InteractionResult placeItem(Player player, BlockEntry block) { - ItemStack itemstack = block.itemStack; - Level level = player.level; - - if (player != null && !player.getAbilities().mayBuild && !itemstack.hasAdventureModePlaceTagForBlock(level.registryAccess().registryOrThrow(Registry.BLOCK_REGISTRY), new BlockInWorld(level, block.blockPos, false))) - return InteractionResult.PASS; - - // handle all placement events here - Item item = itemstack.getItem(); - int size = itemstack.getCount(); - CompoundTag nbt = null; - if (itemstack.getTag() != null) - nbt = itemstack.getTag().copy(); - - if (!(itemstack.getItem() instanceof BucketItem)) // if not bucket - level.captureBlockSnapshots = true; - - ItemStack copy = itemstack.copy(); - //// - BlockHelper.placeSchematicBlock(level, player, block.newBlockState, block.blockPos, block.itemStack, null); - //// - InteractionResult ret = InteractionResult.SUCCESS; - if (itemstack.isEmpty()) - ForgeEventFactory.onPlayerDestroyItem(player, copy, InteractionHand.MAIN_HAND); - - level.captureBlockSnapshots = false; - - if (ret.consumesAction()) - { - // save new item data - int newSize = itemstack.getCount(); - CompoundTag newNBT = null; - if (itemstack.getTag() != null) - { - newNBT = itemstack.getTag().copy(); - } - @SuppressWarnings("unchecked") - List blockSnapshots = (List)level.capturedBlockSnapshots.clone(); - level.capturedBlockSnapshots.clear(); - - // make sure to set pre-placement item data for event - itemstack.setCount(size); - itemstack.setTag(nbt); - - Direction side = Direction.UP; - - boolean eventResult = false; - if (blockSnapshots.size() > 1) - { - eventResult = ForgeEventFactory.onMultiBlockPlace(player, blockSnapshots, side); - } - else if (blockSnapshots.size() == 1) - { - eventResult = ForgeEventFactory.onBlockPlace(player, blockSnapshots.get(0), side); - } - - if (eventResult) - { - ret = InteractionResult.FAIL; // cancel placement - // revert back all captured blocks - for (BlockSnapshot blocksnapshot : Lists.reverse(blockSnapshots)) - { - level.restoringBlockSnapshots = true; - blocksnapshot.restore(true, false); - level.restoringBlockSnapshots = false; - } - } - else - { - // Change the stack to its new content - itemstack.setCount(newSize); - itemstack.setTag(newNBT); - - for (BlockSnapshot snap : blockSnapshots) - { - int updateFlag = snap.getFlag(); - BlockState oldBlock = snap.getReplacedBlock(); - BlockState newBlock = level.getBlockState(snap.getPos()); - newBlock.onPlace(level, snap.getPos(), oldBlock, false); - - level.markAndNotifyBlock(snap.getPos(), level.getChunkAt(snap.getPos()), oldBlock, newBlock, updateFlag, 512); - } - if (player != null) - player.awardStat(Stats.ITEM_USED.get(item)); - } - } - level.capturedBlockSnapshots.clear(); - - return ret; - } } diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java index 6408f64..765bd17 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java @@ -83,7 +83,7 @@ public class BlockSet extends HashMap implements Iterable< } public static void encode(FriendlyByteBuf buf, BlockSet block) { - buf.writeCollection(block.values(), BlockEntry::encode); + buf.writeCollection(block.values().stream().filter(be -> !be.invalid).toList(), BlockEntry::encode); buf.writeBlockPos(block.firstPos); buf.writeBlockPos(block.lastPos); buf.writeBoolean(block.skipFirst); diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/InventoryHelper.java b/src/main/java/nl/requios/effortlessbuilding/utilities/InventoryHelper.java index eb64215..f6fff27 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/InventoryHelper.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/InventoryHelper.java @@ -1,5 +1,6 @@ package nl.requios.effortlessbuilding.utilities; +import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; @@ -28,4 +29,14 @@ public class InventoryHelper { } return total; } + + public static int findTotalItemsInInventory(Player player, Item item) { + int total = 0; + for (ItemStack invStack : player.getInventory().items) { + if (!invStack.isEmpty() && invStack.getItem().equals(item)) { + total += invStack.getCount(); + } + } + return total; + } } diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java b/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java index 975fe93..2e9dfc9 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java @@ -11,6 +11,7 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import nl.requios.effortlessbuilding.EffortlessBuildingClient; @OnlyIn(Dist.CLIENT) public class PlaceChecker { @@ -38,15 +39,17 @@ public class PlaceChecker { && state.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.LOWER) toReplaceOther = world.getBlockState(pos.above()); - if (!world.isLoaded(pos)) - return false; - if (!world.getWorldBorder().isWithinBounds(pos)) - return false; +// if (!world.isLoaded(pos)) +// return false; +// if (!world.getWorldBorder().isWithinBounds(pos)) +// return false; if (toReplace == state) return false; if (toReplace.getDestroySpeed(world, pos) == -1 || (toReplaceOther != null && toReplaceOther.getDestroySpeed(world, pos) == -1)) return false; + if (EffortlessBuildingClient.BUILD_SETTINGS.shouldProtectTileEntities() && toReplaceOther != null && toReplaceOther.hasBlockEntity()) + return false; boolean isNormalCube = state.isRedstoneConductor(world, pos); return shouldPlace(world, pos, state, tileEntity, toReplace, toReplaceOther, isNormalCube); @@ -55,14 +58,13 @@ public class PlaceChecker { //SchematicannonTileEntity::shouldPlace private static boolean shouldPlace(Level level, BlockPos pos, BlockState state, BlockEntity tileEntity, BlockState toReplace, BlockState toReplaceOther, boolean isNormalCube) { - return true; // if (!replaceTileEntities // && (toReplace.hasBlockEntity() || (toReplaceOther != null && toReplaceOther.hasBlockEntity()))) // return false; -// -// if (shouldIgnoreBlockState(state)) -// return false; -// + + if (shouldIgnoreBlockState(state)) + return false; + // boolean placingAir = state.isAir(); // // if (replaceMode == 3) @@ -76,7 +78,7 @@ public class PlaceChecker { // && (toReplaceOther == null || !toReplaceOther.isRedstoneConductor(level, pos)) && !placingAir) // return true; // -// return false; + return true; } //SchematicannonTileEntity::shouldIgnoreBlockState