From 410d1542766fd46301d5542e495600fd201d6a14 Mon Sep 17 00:00:00 2001 From: Christian Knaapen Date: Thu, 18 May 2023 18:46:23 +0200 Subject: [PATCH] Enabled for survival. Calculating missing items. Showing itemstacks at cursor. Showing preview blocks in red. Increased max blocks placed at once. Filtering coordinates that are not loaded or outside of world border. Sending items instead of itemstacks to server. --- .../effortlessbuilding/CommonEvents.java | 10 +- .../EffortlessBuildingClient.java | 4 + .../effortlessbuilding/ServerConfig.java | 2 +- .../render/BlockPreviews.java | 3 +- .../render/RenderHandler.java | 57 +++++++++- .../systems/BuilderChain.java | 72 ++++++++---- .../systems/BuilderFilter.java | 29 +++-- .../systems/ItemUsageTracker.java | 73 ++++++++++++ .../systems/ServerBlockPlacer.java | 67 ++++++----- .../utilities/BlockEntry.java | 16 ++- .../utilities/BlockPlacerHelper.java | 105 +----------------- .../utilities/BlockSet.java | 2 +- .../utilities/InventoryHelper.java | 11 ++ .../utilities/PlaceChecker.java | 22 ++-- 14 files changed, 282 insertions(+), 191 deletions(-) create mode 100644 src/main/java/nl/requios/effortlessbuilding/systems/ItemUsageTracker.java 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