diff --git a/dependencies.gradle b/dependencies.gradle index 4f9733dc..8da8697c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -45,6 +45,8 @@ dependencies { compileOnly('com.github.GTNewHorizons:Angelica:1.0.0-beta68a:dev') compileOnly("com.github.GTNewHorizons:FindIt:1.4.0:dev") compileOnly('com.github.GTNewHorizons:TinkersConstruct:1.14.14-GTNH:dev') + compileOnly("com.github.GTNewHorizons:Backhand:1.8.2:dev") + compileOnly('com.github.GTNewHorizons:GT5-Unofficial:5.09.52.296:dev') devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.4-GTNH:dev") @@ -57,4 +59,8 @@ dependencies { // For testing glove // runtimeOnlyNonPublishable("com.github.GTNewHorizons:Translocators:1.4.0:dev") // runtimeOnlyNonPublishable("com.github.GTNewHorizons:Backhand:1.8.2:dev") + + // For testing trowel + wand + // runtimeOnlyNonPublishable("com.github.GTNewHorizons:Backhand:1.8.2:dev") + // runtimeOnlyNonPublishable('com.github.GTNewHorizons:GT5-Unofficial:5.09.52.296:dev') } diff --git a/src/main/java/com/fouristhenumber/utilitiesinexcess/client/IMCForNEI.java b/src/main/java/com/fouristhenumber/utilitiesinexcess/client/IMCForNEI.java index 1466643d..fa70b32d 100644 --- a/src/main/java/com/fouristhenumber/utilitiesinexcess/client/IMCForNEI.java +++ b/src/main/java/com/fouristhenumber/utilitiesinexcess/client/IMCForNEI.java @@ -37,10 +37,19 @@ public static void IMCSender() { sendInfoPage("", "nei.infopage.uie.precision_shears.1"); sendInfoPage("", "nei.infopage.uie.etheric_sword.1"); sendInfoPage("", "nei.infopage.uie.reversing_hoe.1"); + sendInfoPage( ",", "nei.infopage.uie.architects_wand.1"); + if (Mods.Backhand.isLoaded()) { + sendInfoPage( + ",", + "nei.infopage.uie.architects_wand.2"); + if (Mods.GT.isLoaded()) sendInfoPage( + ",", + "nei.infopage.uie.architects_wand.3"); + } sendInfoPage("", "nei.infopage.uie.mob_jar.1"); sendInfoPage("utilitiesinexcess:glove", "nei.infopage.uie.glove.1"); diff --git a/src/main/java/com/fouristhenumber/utilitiesinexcess/common/items/ItemArchitectsWand.java b/src/main/java/com/fouristhenumber/utilitiesinexcess/common/items/ItemArchitectsWand.java index 21ae871a..efed2778 100644 --- a/src/main/java/com/fouristhenumber/utilitiesinexcess/common/items/ItemArchitectsWand.java +++ b/src/main/java/com/fouristhenumber/utilitiesinexcess/common/items/ItemArchitectsWand.java @@ -1,16 +1,14 @@ package com.fouristhenumber.utilitiesinexcess.common.items; -import static com.fouristhenumber.utilitiesinexcess.utils.ArchitectsWandUtils.getItemStackToPlace; - import java.util.List; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; -import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.MovingObjectPosition; @@ -19,8 +17,11 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; +import org.jetbrains.annotations.NotNull; + import com.fouristhenumber.utilitiesinexcess.UtilitiesInExcess; import com.fouristhenumber.utilitiesinexcess.common.renderers.WireframeRenderer; +import com.fouristhenumber.utilitiesinexcess.utils.ArchitectsSelection; import com.fouristhenumber.utilitiesinexcess.utils.ArchitectsWandUtils; import com.gtnewhorizon.gtnhlib.api.ITranslucentItem; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; @@ -85,24 +86,60 @@ public void onUpdate(ItemStack stack, World world, Entity entity, int slot, bool } // 4. Target block to place - ItemStack itemStackToPlace = getItemStackToPlace(world, target, movingObjectPosition, player); - if (itemStackToPlace == null || !(itemStackToPlace.getItem() instanceof ItemBlock)) { - WireframeRenderer.clearCandidatePositions(); - return; - } + ArchitectsSelection selection = new ArchitectsSelection(player, world, movingObjectPosition); + List itemStackToPlace = selection.blockToPlace(player); // 3. Total amount to place - int placeCount = player.capabilities.isCreativeMode ? this.buildLimit - : Math.min(ArchitectsWandUtils.countItemInInventory(player, itemStackToPlace), this.buildLimit); - - Set blocksToPlace = ArchitectsWandUtils - .findAdjacentBlocks(world, itemStackToPlace, placeCount, forgeSide, target, movingObjectPosition, player); + int placeCount = selection.maxPlaceCount(player, buildLimit); + + Set blocksToPlace = ArchitectsWandUtils.findAdjacentBlocks( + world, + itemStackToPlace, + placeCount, + forgeSide, + target, + movingObjectPosition, + player, + selection); WireframeRenderer.clearCandidatePositions(); for (BlockPos pos : blocksToPlace) { WireframeRenderer.addCandidatePosition(pos.offset(forgeSide.offsetX, forgeSide.offsetY, forgeSide.offsetZ)); } } + private void placeBlock(World world, EntityPlayer player, @NotNull ItemStack itemStack, BlockPos pos, int side, + float hitX, float hitY, float hitZ, ForgeDirection forgeSide) { + // This block is here because some mods want to use TEs to + ItemStack itemCopy = itemStack.copy(); + itemCopy.stackSize = 1; + Block comparisonBlock = world.getBlock(pos.x, pos.y, pos.z); + int comparisonMeta = world.getBlockMetadata(pos.x, pos.y, pos.z); + ItemStack comparisonItemStack = new ItemStack(comparisonBlock, 1, comparisonMeta); + + boolean useCompatPlacement = !ItemStack.areItemStacksEqual(itemCopy, comparisonItemStack); + if (useCompatPlacement) { + itemStack.getItem() + .onItemUse(itemCopy, player, world, pos.x, pos.y, pos.z, side, hitX, hitY, hitZ); + } else { + Block block = Block.getBlockFromItem(itemCopy.getItem()); + world.setBlock( + pos.x + forgeSide.offsetX, + pos.y + forgeSide.offsetY, + pos.z + forgeSide.offsetZ, + block, + comparisonMeta, + 3); + + world.playSoundEffect( + pos.x + forgeSide.offsetX, + pos.y + forgeSide.offsetY, + pos.z + forgeSide.offsetZ, + block.stepSound.func_150496_b(), + (block.stepSound.getVolume() + 1.0F) / 2.0F, + block.stepSound.getPitch() * 0.8F); + } + } + @Override public boolean onItemUse(ItemStack itemstack, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ) { @@ -116,42 +153,28 @@ public boolean onItemUse(ItemStack itemstack, EntityPlayer player, World world, BlockPos target = new BlockPos(x, y, z); MovingObjectPosition mop = new MovingObjectPosition(x, y, z, side, Vec3.createVectorHelper(hitX, hitY, hitZ)); - ItemStack itemStackToPlace = getItemStackToPlace(world, target, mop, player); - if (itemStackToPlace == null || !(itemStackToPlace.getItem() instanceof ItemBlock)) return false; + ArchitectsSelection selection = new ArchitectsSelection(player, world, mop); - // This block is here because some mods want to use TEs to - Block comparisonBlock = world.getBlock(x, y, z); - int comparisonMeta = world.getBlockMetadata(x, y, z); - ItemStack comparisonItemStack = new ItemStack(comparisonBlock, 1, comparisonMeta); - - boolean useCompatPlacement = !ItemStack.areItemStacksEqual(itemStackToPlace, comparisonItemStack); + List itemStackToPlace = selection.blockToPlace(player); - int inventoryBlockCount = ArchitectsWandUtils.countItemInInventory(player, itemStackToPlace); - if (!player.capabilities.isCreativeMode && inventoryBlockCount == 0) return false; - int placeCount = player.capabilities.isCreativeMode ? this.buildLimit - : Math.min(inventoryBlockCount, this.buildLimit); + int placeCount = selection.maxPlaceCount(player, buildLimit); Set blocksToPlace = ArchitectsWandUtils - .findAdjacentBlocks(world, itemStackToPlace, placeCount, forgeSide, target, mop, player); - itemStackToPlace.stackSize = blocksToPlace.size(); // Since now, we actually create a stack we have to set the - // size. Strange kinda... + .findAdjacentBlocks(world, itemStackToPlace, placeCount, forgeSide, target, mop, player, selection); + + ItemStack nowPlacing; for (BlockPos pos : blocksToPlace) { - // TODO: Group these by a bigger number instead of decreasing by 1 every time. - if (player.capabilities.isCreativeMode - || ArchitectsWandUtils.decreaseFromInventory(player, itemStackToPlace)) { - if (useCompatPlacement) { - itemStackToPlace.getItem() - .onItemUse(itemStackToPlace, player, world, pos.x, pos.y, pos.z, side, hitX, hitY, hitZ); - } else { - world.setBlock( - pos.x + forgeSide.offsetX, - pos.y + forgeSide.offsetY, - pos.z + forgeSide.offsetZ, - comparisonBlock, - comparisonMeta, - 3); - } + List candidates = selection.blockToPlace(player); + if (candidates.size() == 1) { + nowPlacing = candidates.get(0); + } else { + nowPlacing = candidates.get( + ThreadLocalRandom.current() + .nextInt(candidates.size())); + } + if (player.capabilities.isCreativeMode || ArchitectsWandUtils.decreaseFromInventory(player, nowPlacing)) { + placeBlock(world, player, nowPlacing, pos, side, hitX, hitY, hitZ, forgeSide); } } return true; diff --git a/src/main/java/com/fouristhenumber/utilitiesinexcess/compat/Mods.java b/src/main/java/com/fouristhenumber/utilitiesinexcess/compat/Mods.java index 6d9235cc..c2d13056 100644 --- a/src/main/java/com/fouristhenumber/utilitiesinexcess/compat/Mods.java +++ b/src/main/java/com/fouristhenumber/utilitiesinexcess/compat/Mods.java @@ -13,7 +13,9 @@ public enum Mods { NEI("NotEnoughItems"), CraftTweaker("MineTweaker3"), FindIt("findit"), - Tinkers("TConstruct") + Tinkers("TConstruct"), + Backhand("backhand"), + GT("gregtech_nh") ; // spotless:on diff --git a/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsSelection.java b/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsSelection.java new file mode 100644 index 00000000..e34265f0 --- /dev/null +++ b/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsSelection.java @@ -0,0 +1,135 @@ +package com.fouristhenumber.utilitiesinexcess.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.world.World; + +import org.jetbrains.annotations.Nullable; + +import com.fouristhenumber.utilitiesinexcess.compat.Mods; +import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; + +import gregtech.api.items.MetaGeneratedTool; +import gregtech.common.tools.ToolTrowel; +import xonin.backhand.api.core.BackhandUtils; + +public class ArchitectsSelection { + + private final Set validBlocks; + private final ItemStack backhand; + private final ItemStack lookAtBlock; + + public ArchitectsSelection(EntityPlayer player, World world, MovingObjectPosition movingObjectPosition) { + this.validBlocks = new HashSet<>(); + backhand = Mods.Backhand.isLoaded() ? BackhandUtils.getOffhandItem(player) : null; + lookAtBlock = getBlockByLocation(world, movingObjectPosition, player); + + // No logic is executed if we don't look at any block, no need to bother checking other cases + if (lookAtBlock == null) { + return; + } + + this.validBlocks.add(lookAtBlock); // Clicked block is always valid + + if (backhand == null) { + return; + } + if (isValidBlock(backhand)) { + this.validBlocks.add(backhand.copy()); + return; + } + if (isTrowel(backhand)) { + this.validBlocks.addAll(hotbarBlocks(player)); + } + } + + /** + * + * @param player + * @return Always a valid block list or null + */ + public List blockToPlace(EntityPlayer player) { + + if (backhand == null) { + return Collections.singletonList((lookAtBlock)); + } + if (isValidBlock(backhand)) { + return Collections.singletonList(backhand); + } else if (isTrowel(backhand)) { + return hotbarBlocks(player); + } else { + return Collections.singletonList(lookAtBlock); + } + } + + public int maxPlaceCount(EntityPlayer player, int wandLimit) { + if (player.capabilities.isCreativeMode) return wandLimit; + int count = 0; + for (ItemStack block : blockToPlace(player)) { + count += ArchitectsWandUtils.countItemInInventory(player, block); + } + return Math.min(count, wandLimit); + } + + public boolean matches(ItemStack other) { + if (other == null) return false; + return this.validBlocks.stream() + .anyMatch( + validBlock -> validBlock.getItem() == other.getItem() + && ItemStack.areItemStackTagsEqual(validBlock, other)); + } + + private static boolean isTrowel(@Nullable ItemStack stack) { + if (stack == null) { + return false; + } + if (Mods.GT.isLoaded() && stack.getItem() instanceof MetaGeneratedTool metaGeneratedTool) { + return metaGeneratedTool.getToolStats(stack) instanceof ToolTrowel; + } + return false; + } + + private static boolean isValidBlock(@Nullable ItemStack stack) { + return (stack != null && stack.getItem() instanceof ItemBlock); + } + + private static List hotbarBlocks(EntityPlayer player) { + List candidates = new ArrayList<>(); + + for (int i = 0; i < 9; i++) { + if (i == player.inventory.currentItem) { + continue; + } + ItemStack item = player.inventory.mainInventory[i]; + if (!isValidBlock(item)) { + continue; + } + candidates.add(item); + } + return candidates; + } + + public static ItemStack getBlockByLocation(World world, MovingObjectPosition movingObjectPosition, + EntityPlayer player) { + + BlockPos blockPos = new BlockPos( + movingObjectPosition.blockX, + movingObjectPosition.blockY, + movingObjectPosition.blockZ); + + Block block = world.getBlock(blockPos.x, blockPos.y, blockPos.z); + if (block == null) { + return null; + } + return block.getPickBlock(movingObjectPosition, world, blockPos.x, blockPos.y, blockPos.z, player); + } +} diff --git a/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsWandUtils.java b/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsWandUtils.java index dde01397..752ccd25 100644 --- a/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsWandUtils.java +++ b/src/main/java/com/fouristhenumber/utilitiesinexcess/utils/ArchitectsWandUtils.java @@ -1,9 +1,11 @@ package com.fouristhenumber.utilitiesinexcess.utils; +import static com.fouristhenumber.utilitiesinexcess.utils.ArchitectsSelection.getBlockByLocation; import static com.fouristhenumber.utilitiesinexcess.utils.MovingObjectPositionUtil.TranslateMovingObjectPoistionToLocation; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Queue; import java.util.Set; @@ -18,7 +20,9 @@ public class ArchitectsWandUtils { - public ArchitectsWandUtils() {}; + public ArchitectsWandUtils() {} + + ; /** * Counts the items of a certain type in a player's inventory @@ -66,15 +70,16 @@ public static boolean decreaseFromInventory(EntityPlayer player, ItemStack itemS * Finds the blocks adjacent to the start position that are connected cardinally, or diagonally * and have air in front of them relative to the side clicked on. * - * @param world The world in which to place - * @param - * @param findCount The maximum amount of blocks it should search - * @param clickedSide The side of the block that was clicked - * @param startPos The position to start + * @param world The world in which to place + * @param findCount The maximum amount of blocks it should search + * @param clickedSide The side of the block that was clicked + * @param startPos The position to start + * @param architectsSelection The pattern used to search adjacent blocks * @return The set of 1<=x<=findCount adjacent blocks with air on their face */ - public static Set findAdjacentBlocks(World world, ItemStack itemStackToPlace, int findCount, - ForgeDirection clickedSide, BlockPos startPos, MovingObjectPosition mop, EntityPlayer player) { + public static Set findAdjacentBlocks(World world, List possiblePlacements, int findCount, + ForgeDirection clickedSide, BlockPos startPos, MovingObjectPosition mop, EntityPlayer player, + ArchitectsSelection architectsSelection) { Set region = new HashSet<>(); if (findCount <= 0) { return region; @@ -103,7 +108,7 @@ public static Set findAdjacentBlocks(World world, ItemStack itemStackT TranslateMovingObjectPoistionToLocation(mop, startPos); // Base case - if (IsValidForWireFrame(world, itemStackToPlace, startPos, mop, player, clickedSide)) { + if (IsValidForWireFrame(world, possiblePlacements, startPos, mop, player, clickedSide, architectsSelection)) { region.add(startPos); queue.add(startPos); visited.add(startPos); @@ -128,7 +133,14 @@ public static Set findAdjacentBlocks(World world, ItemStack itemStackT // translate the mop TranslateMovingObjectPoistionToLocation(mop, key); - if (IsValidForWireFrame(world, itemStackToPlace, key, mop, player, clickedSide)) { + if (IsValidForWireFrame( + world, + possiblePlacements, + key, + mop, + player, + clickedSide, + architectsSelection)) { region.add(key); queue.add(key); } @@ -138,41 +150,37 @@ public static Set findAdjacentBlocks(World world, ItemStack itemStackT return region; } - private static boolean IsValidForWireFrame(World world, ItemStack itemStackToPlace, BlockPos targetLocation, - MovingObjectPosition movingObjectPosition, EntityPlayer player, ForgeDirection clickedSide) { - ItemStack stack = getItemStackToPlace(world, targetLocation, movingObjectPosition, player); - if (stack != null) { - Block block = Block.getBlockFromItem(stack.getItem()); - return ItemStack.areItemStacksEqual(stack, itemStackToPlace) - && world.isAirBlock( - targetLocation.x + clickedSide.offsetX, - targetLocation.y + clickedSide.offsetY, - targetLocation.z + clickedSide.offsetZ) - && block.canPlaceBlockOnSide( - world, - targetLocation.x + clickedSide.offsetX, - targetLocation.y + clickedSide.offsetY, - targetLocation.z + clickedSide.offsetZ, - clickedSide.ordinal()) - && world.canPlaceEntityOnSide( - block, - targetLocation.x + clickedSide.offsetX, - targetLocation.y + clickedSide.offsetY, - targetLocation.z + clickedSide.offsetZ, - false, - clickedSide.ordinal(), - null, - stack); - } - return false; - } - - public static ItemStack getItemStackToPlace(World world, BlockPos pos, MovingObjectPosition movingObjectPosition, - EntityPlayer player) { - Block block = world.getBlock(pos.x, pos.y, pos.z); - if (block != null) { - return block.getPickBlock(movingObjectPosition, world, pos.x, pos.y, pos.z, player); - } - return null; + private static boolean IsValidForWireFrame(World world, List possibleBlocks, BlockPos targetLocation, + MovingObjectPosition mop, EntityPlayer player, ForgeDirection clickedSide, ArchitectsSelection selection) { + ItemStack currentBlock = getBlockByLocation(world, mop, player); + + if (currentBlock == null) return false; + + return possibleBlocks.stream() + .allMatch(itemStackToPlace -> { + if (itemStackToPlace == null) return false; + + Block block = Block.getBlockFromItem(itemStackToPlace.getItem()); + return selection.matches(currentBlock) + && world.isAirBlock( + targetLocation.x + clickedSide.offsetX, + targetLocation.y + clickedSide.offsetY, + targetLocation.z + clickedSide.offsetZ) + && block.canPlaceBlockOnSide( + world, + targetLocation.x + clickedSide.offsetX, + targetLocation.y + clickedSide.offsetY, + targetLocation.z + clickedSide.offsetZ, + clickedSide.ordinal()) + && world.canPlaceEntityOnSide( + block, + targetLocation.x + clickedSide.offsetX, + targetLocation.y + clickedSide.offsetY, + targetLocation.z + clickedSide.offsetZ, + false, + clickedSide.ordinal(), + null, + itemStackToPlace); + }); } } diff --git a/src/main/resources/assets/utilitiesinexcess/lang/en_US.lang b/src/main/resources/assets/utilitiesinexcess/lang/en_US.lang index dde98b55..c643960c 100644 --- a/src/main/resources/assets/utilitiesinexcess/lang/en_US.lang +++ b/src/main/resources/assets/utilitiesinexcess/lang/en_US.lang @@ -171,6 +171,8 @@ item.architects_wand.name=Architect's Wand tooltip.architects_wand.1=Can place up to %s blocks at once nei.infopage.uie.architects_wand.1=This wand is designed to aid the bearer in construction by placing many blocks at the same time. While looking at a block, you will see a wireframe indicating how a set of blocks will be extended by the wand. +nei.infopage.uie.architects_wand.2=When holding a building block in your off-hand, the wand will try to place it instead. +nei.infopage.uie.architects_wand.3=If, instead of holding a block, you hold a trowel, random blocks from your hotbar will be placed. item.gourmands_axe.name=Gourmand's Axe