From e950aa268feb23bb24f8ef80766a5dbbe57cbf5e Mon Sep 17 00:00:00 2001 From: Zelophed Date: Wed, 16 Dec 2020 17:36:22 +0100 Subject: [PATCH] Assisted Placement, Part I - refactor existing placement helpers - add placement helpers for cogs and shafts --- .../structureMovement/bearing/SailBlock.java | 105 ++++++---- .../bearing/SailBlockPlacementHelper.java | 101 ---------- .../piston/PistonContraption.java | 4 +- .../piston/PistonExtensionPoleBlock.java | 42 +++- .../piston/PistonPolePlacementHelper.java | 129 ------------ .../goggles/GoggleOverlayRenderer.java | 4 +- .../relays/elementary/CogWheelBlock.java | 15 +- .../relays/elementary/CogwheelBlockItem.java | 185 +++++++++++++++++- .../relays/elementary/ShaftBlock.java | 61 +++++- .../simibubi/create/events/ClientEvents.java | 21 +- .../create/foundation/utility/Iterate.java | 2 + .../utility/placement/IPlacementHelper.java | 111 +++++++++++ .../utility/placement/PlacementHelpers.java | 74 +++++++ .../utility/placement/PlacementOffset.java | 43 ++++ .../utility/placement/util/PoleHelper.java | 77 ++++++++ 15 files changed, 666 insertions(+), 308 deletions(-) delete mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlockPlacementHelper.java delete mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonPolePlacementHelper.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java index 03b2ca366..d6b021dea 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlock.java @@ -1,28 +1,21 @@ package com.simibubi.create.content.contraptions.components.structureMovement.bearing; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.annotation.Nullable; - import com.simibubi.create.AllBlocks; import com.simibubi.create.AllShapes; import com.simibubi.create.foundation.block.ProperDirectionalBlock; import com.simibubi.create.foundation.utility.DyeHelper; import com.simibubi.create.foundation.utility.Iterate; - +import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.foundation.utility.placement.IPlacementHelper; +import com.simibubi.create.foundation.utility.placement.PlacementHelpers; +import com.simibubi.create.foundation.utility.placement.PlacementOffset; +import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.DyeColor; -import net.minecraft.item.ItemStack; -import net.minecraft.item.ShearsItem; +import net.minecraft.item.*; import net.minecraft.util.ActionResultType; import net.minecraft.util.Direction; import net.minecraft.util.Hand; @@ -35,6 +28,13 @@ import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + public class SailBlock extends ProperDirectionalBlock { public static SailBlock frame(Properties properties) { @@ -45,7 +45,9 @@ public class SailBlock extends ProperDirectionalBlock { return new SailBlock(properties, false); } - private boolean frame; + private static final int placementHelperId = PlacementHelpers.register(new PlacementHelper()); + + private final boolean frame; protected SailBlock(Properties p_i48415_1_, boolean frame) { super(p_i48415_1_); @@ -55,27 +57,27 @@ public class SailBlock extends ProperDirectionalBlock { @Override public BlockState getStateForPlacement(BlockItemUseContext context) { BlockState state = super.getStateForPlacement(context); - return state.with(FACING, state.get(FACING) - .getOpposite()); + return state.with(FACING, state.get(FACING).getOpposite()); } @Override - public ActionResultType onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, - BlockRayTraceResult ray) { + public ActionResultType onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult ray) { ItemStack heldItem = player.getHeldItem(hand); if (AllBlocks.SAIL.isIn(heldItem) || AllBlocks.SAIL_FRAME.isIn(heldItem)) { - Direction offset = - SailBlockPlacementHelper.getPlacementOffset(world, state.get(FACING), pos, ray.getHitVec()); - if (offset == null) - return ActionResultType.SUCCESS; + IPlacementHelper placementHelper = PlacementHelpers.get(placementHelperId); + PlacementOffset offset = placementHelper.getOffset(world, state, pos, ray); + + if (!offset.isSuccessful()) + return ActionResultType.PASS; + BlockState blockState = ((BlockItem) heldItem.getItem()).getBlock() - .getDefaultState() - .with(FACING, state.get(FACING)); - BlockPos offsetPos = pos.offset(offset); + .getDefaultState() + .with(FACING, state.get(FACING)); + BlockPos offsetPos = new BlockPos(offset.getPos()); if (!world.isRemote && world.getBlockState(offsetPos) - .getMaterial() - .isReplaceable()) { + .getMaterial() + .isReplaceable()) { world.setBlockState(offsetPos, blockState); if (!player.isCreative()) heldItem.shrink(1); @@ -94,7 +96,7 @@ public class SailBlock extends ProperDirectionalBlock { for (DyeColor color : DyeColor.values()) { if (!heldItem.getItem() - .isIn(DyeHelper.getTagOfDye(color))) + .isIn(DyeHelper.getTagOfDye(color))) continue; if (!world.isRemote) applyDye(state, world, pos, color); @@ -106,8 +108,8 @@ public class SailBlock extends ProperDirectionalBlock { protected void applyDye(BlockState state, World world, BlockPos pos, @Nullable DyeColor color) { BlockState newState = - (color == null ? AllBlocks.SAIL_FRAME : AllBlocks.DYED_SAILS[color.ordinal()]).getDefaultState() - .with(FACING, state.get(FACING)); + (color == null ? AllBlocks.SAIL_FRAME : AllBlocks.DYED_SAILS[color.ordinal()]).getDefaultState() + .with(FACING, state.get(FACING)); // Dye the block itself if (state != newState) { @@ -118,7 +120,7 @@ public class SailBlock extends ProperDirectionalBlock { // Dye all adjacent for (Direction d : Iterate.directions) { if (d.getAxis() == state.get(FACING) - .getAxis()) + .getAxis()) continue; BlockPos offset = pos.offset(d); BlockState adjacentState = world.getBlockState(offset); @@ -145,7 +147,7 @@ public class SailBlock extends ProperDirectionalBlock { for (Direction d : Iterate.directions) { if (d.getAxis() == state.get(FACING) - .getAxis()) + .getAxis()) continue; BlockPos offset = currentPos.offset(d); if (visited.contains(offset)) @@ -163,26 +165,23 @@ public class SailBlock extends ProperDirectionalBlock { } @Override - public VoxelShape getShape(BlockState state, IBlockReader p_220053_2_, BlockPos p_220053_3_, - ISelectionContext p_220053_4_) { + public VoxelShape getShape(BlockState state, IBlockReader p_220053_2_, BlockPos p_220053_3_, ISelectionContext p_220053_4_) { return (frame ? AllShapes.SAIL_FRAME : AllShapes.SAIL).get(state.get(FACING)); } @Override - public VoxelShape getCollisionShape(BlockState state, IBlockReader p_220071_2_, BlockPos p_220071_3_, - ISelectionContext p_220071_4_) { + public VoxelShape getCollisionShape(BlockState state, IBlockReader p_220071_2_, BlockPos p_220071_3_, ISelectionContext p_220071_4_) { if (frame) return AllShapes.SAIL_FRAME_COLLISION.get(state.get(FACING)); return getShape(state, p_220071_2_, p_220071_3_, p_220071_4_); } @Override - public ItemStack getPickBlock(BlockState state, RayTraceResult target, IBlockReader world, BlockPos pos, - PlayerEntity player) { + public ItemStack getPickBlock(BlockState state, RayTraceResult target, IBlockReader world, BlockPos pos, PlayerEntity player) { ItemStack pickBlock = super.getPickBlock(state, target, world, pos, player); if (pickBlock.isEmpty()) return AllBlocks.SAIL.get() - .getPickBlock(state, target, world, pos, player); + .getPickBlock(state, target, world, pos, player); return pickBlock; } @@ -209,4 +208,32 @@ public class SailBlock extends ProperDirectionalBlock { } + @MethodsReturnNonnullByDefault + private static class PlacementHelper implements IPlacementHelper { + @Override + public Predicate getItemPredicate() { + return i -> AllBlocks.SAIL.isIn(i) || AllBlocks.SAIL_FRAME.isIn(i); + } + + @Override + public Predicate getStatePredicate() { + return s -> s.getBlock() instanceof SailBlock; + } + + @Override + public PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray) { + List directions = IPlacementHelper.orderedByDistanceExceptAxis(pos, ray.getHitVec(), state.get(SailBlock.FACING).getAxis(), dir -> world.getBlockState(pos.offset(dir)).getMaterial().isReplaceable()); + + if (directions.isEmpty()) + return PlacementOffset.fail(); + else { + return PlacementOffset.success(pos.offset(directions.get(0))); + } + } + + @Override + public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { + IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), state.get(FACING)); + } + } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlockPlacementHelper.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlockPlacementHelper.java deleted file mode 100644 index 462b8bdef..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/bearing/SailBlockPlacementHelper.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.simibubi.create.content.contraptions.components.structureMovement.bearing; - -import com.simibubi.create.AllBlocks; -import com.simibubi.create.CreateClient; -import com.simibubi.create.foundation.utility.Iterate; -import com.simibubi.create.foundation.utility.VecHelper; - -import net.minecraft.block.BlockState; -import net.minecraft.client.Minecraft; -import net.minecraft.client.entity.player.ClientPlayerEntity; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -public class SailBlockPlacementHelper { - - @OnlyIn(Dist.CLIENT) - public static void tick() { - Minecraft mc = Minecraft.getInstance(); - RayTraceResult objectMouseOver = mc.objectMouseOver; - ClientWorld world = mc.world; - ClientPlayerEntity player = mc.player; - if (!(objectMouseOver instanceof BlockRayTraceResult)) - return; - BlockRayTraceResult ray = (BlockRayTraceResult) objectMouseOver; - if (!isHoldingSail(player)) - return; - BlockPos pos = ray.getPos(); - BlockState blockState = world.getBlockState(pos); - if (!(blockState.getBlock() instanceof SailBlock)) - return; - - Direction sailFacing = blockState.get(SailBlock.FACING); - Direction offset = getPlacementOffset(world, sailFacing, pos, ray.getHitVec()); - if (offset == null) - return; - - Vec3d centerOf = VecHelper.getCenterOf(pos); - Vec3d offsetVec = new Vec3d(offset.getDirectionVec()); - - if (!world.getBlockState(pos.offset(offset)) - .getMaterial() - .isReplaceable()) - return; - - for (Direction caretDirection : Iterate.directions) { - if (caretDirection.getAxis() == offset.getAxis()) - continue; - if (caretDirection.getAxis() == sailFacing.getAxis()) - continue; - - Vec3d otherOffset = new Vec3d(caretDirection.getDirectionVec()).scale(.25f); - Vec3d start = offsetVec.scale(.75f) - .add(otherOffset); - Vec3d target = centerOf.add(offsetVec); - CreateClient.outliner.showLine("sailHelp" + caretDirection, centerOf.add(start), target) - .lineWidth(1 / 16f); - } - - return; - } - - public static boolean isHoldingSail(PlayerEntity player) { - for (Hand hand : Hand.values()) { - ItemStack heldItem = player.getHeldItem(hand); - if (AllBlocks.SAIL.isIn(heldItem) || AllBlocks.SAIL_FRAME.isIn(heldItem)) - return true; - } - return false; - } - - public static Direction getPlacementOffset(World world, Direction sailDirection, BlockPos pos, Vec3d hit) { - Direction argMin = null; - float min = Float.MAX_VALUE; - Vec3d diffFromCentre = hit.subtract(VecHelper.getCenterOf(pos)); - for (Direction side : Iterate.directions) { - if (side.getAxis() == sailDirection.getAxis()) - continue; - if (!world.getBlockState(pos.offset(side)) - .getMaterial() - .isReplaceable()) - continue; - float distance = (float) new Vec3d(side.getDirectionVec()).distanceTo(diffFromCentre); - if (distance > min) - continue; - min = distance; - argMin = side; - } - return argMin; - } - -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonContraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonContraption.java index 33041f493..2895fb9bd 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonContraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonContraption.java @@ -77,7 +77,7 @@ public class PistonContraption extends TranslatingContraption { return false; if (blockState.get(MechanicalPistonBlock.STATE) == PistonState.EXTENDED) { - while (PistonPolePlacementHelper.matchesAxis(nextBlock, direction.getAxis()) || isPistonHead(nextBlock) && nextBlock.get(FACING) == direction) { + while (PistonExtensionPoleBlock.PlacementHelper.get().matchesAxis(nextBlock, direction.getAxis()) || isPistonHead(nextBlock) && nextBlock.get(FACING) == direction) { actualStart = actualStart.offset(direction); poles.add(new BlockInfo(actualStart, nextBlock.with(FACING, direction), null)); @@ -104,7 +104,7 @@ public class PistonContraption extends TranslatingContraption { nextBlock = world.getBlockState(end.offset(direction.getOpposite())); int extensionsInBack = 0; - while (PistonPolePlacementHelper.matchesAxis(nextBlock, direction.getAxis())) { + while (PistonExtensionPoleBlock.PlacementHelper.get().matchesAxis(nextBlock, direction.getAxis())) { end = end.offset(direction.getOpposite()); poles.add(new BlockInfo(end, nextBlock.with(FACING, direction), null)); extensionsInBack++; diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonExtensionPoleBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonExtensionPoleBlock.java index 83d38e1c5..10248461e 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonExtensionPoleBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonExtensionPoleBlock.java @@ -5,7 +5,11 @@ import com.simibubi.create.AllShapes; import com.simibubi.create.content.contraptions.components.structureMovement.piston.MechanicalPistonBlock.*; import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.foundation.block.ProperDirectionalBlock; -import com.simibubi.create.foundation.utility.Pair; +import com.simibubi.create.foundation.utility.placement.IPlacementHelper; +import com.simibubi.create.foundation.utility.placement.PlacementHelpers; +import com.simibubi.create.foundation.utility.placement.PlacementOffset; +import com.simibubi.create.foundation.utility.placement.util.PoleHelper; +import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.IWaterLoggable; @@ -30,10 +34,14 @@ import net.minecraft.world.IBlockReader; import net.minecraft.world.IWorld; import net.minecraft.world.World; +import java.util.function.Predicate; + import static com.simibubi.create.content.contraptions.components.structureMovement.piston.MechanicalPistonBlock.*; public class PistonExtensionPoleBlock extends ProperDirectionalBlock implements IWrenchable, IWaterLoggable { + private static final int placementHelperId = PlacementHelpers.register(PlacementHelper.get()); + public PistonExtensionPoleBlock(Properties properties) { super(properties); setDefaultState(getDefaultState().with(FACING, Direction.UP).with(BlockStateProperties.WATERLOGGED, false)); @@ -107,12 +115,13 @@ public class PistonExtensionPoleBlock extends ProperDirectionalBlock implements ItemStack heldItem = player.getHeldItem(hand); if (AllBlocks.PISTON_EXTENSION_POLE.isIn(heldItem) && !player.isSneaking()) { - Pair offset = PistonPolePlacementHelper.getPlacementOffset(world, state.get(FACING).getAxis(), pos, ray.getHitVec()); + IPlacementHelper placementHelper = PlacementHelpers.get(placementHelperId); + PlacementOffset offset = placementHelper.getOffset(world, state, pos, ray); - if (offset == null || offset.getSecond() == 0) + if (!offset.isSuccessful()) return ActionResultType.PASS; - BlockPos newPos = pos.offset(offset.getFirst(), offset.getSecond()); + BlockPos newPos = new BlockPos(offset.getPos()); if (!world.getBlockState(newPos).getMaterial().isReplaceable()) return ActionResultType.PASS; @@ -120,7 +129,7 @@ public class PistonExtensionPoleBlock extends ProperDirectionalBlock implements if (world.isRemote) return ActionResultType.SUCCESS; - world.setBlockState(newPos, AllBlocks.PISTON_EXTENSION_POLE.getDefaultState().with(FACING, state.get(FACING))); + world.setBlockState(newPos, offset.getTransform().apply(AllBlocks.PISTON_EXTENSION_POLE.getDefaultState())); if (!player.isCreative()) heldItem.shrink(1); @@ -149,4 +158,27 @@ public class PistonExtensionPoleBlock extends ProperDirectionalBlock implements } return state; } + + @MethodsReturnNonnullByDefault + public static class PlacementHelper extends PoleHelper { + + private static final PlacementHelper instance = new PlacementHelper(); + + public static PlacementHelper get() { + return instance; + } + + private PlacementHelper(){ + super( + AllBlocks.PISTON_EXTENSION_POLE::has, + state -> state.get(FACING).getAxis(), + FACING + ); + } + + @Override + public Predicate getItemPredicate() { + return AllBlocks.PISTON_EXTENSION_POLE::isIn; + } + } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonPolePlacementHelper.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonPolePlacementHelper.java deleted file mode 100644 index 43a32fd45..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/piston/PistonPolePlacementHelper.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.simibubi.create.content.contraptions.components.structureMovement.piston; - -import com.simibubi.create.AllBlocks; -import com.simibubi.create.CreateClient; -import com.simibubi.create.foundation.utility.Iterate; -import com.simibubi.create.foundation.utility.Pair; -import com.simibubi.create.foundation.utility.VecHelper; -import net.minecraft.block.BlockState; -import net.minecraft.client.Minecraft; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.Direction; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import java.util.Arrays; - -public class PistonPolePlacementHelper { - - @OnlyIn(Dist.CLIENT) - public static void tick() { - Minecraft mc = Minecraft.getInstance(); - ClientWorld world = mc.world; - - if (!(mc.objectMouseOver instanceof BlockRayTraceResult)) - return; - - BlockRayTraceResult ray = (BlockRayTraceResult) mc.objectMouseOver; - - if (mc.player != null && !isHoldingPole(mc.player)) - return; - - if (mc.player.isSneaking()) - return; - - BlockPos pos = ray.getPos(); - BlockState state = world.getBlockState(pos); - if (!(state.getBlock() instanceof PistonExtensionPoleBlock)) - return; - - Pair offset = getPlacementOffset(world, state.get(PistonExtensionPoleBlock.FACING).getAxis(), pos, ray.getHitVec()); - if (offset == null || offset.getSecond() == 0) - return; - - Direction hitFace = ray.getFace(); - - if (hitFace.getAxis() == offset.getFirst().getAxis()) - return; - - Vec3d hitCenter = VecHelper.getCenterOf(pos).add(new Vec3d(hitFace.getDirectionVec()).scale(0.3)); - - //get the two perpendicular directions to form the arrow - Direction[] directions = Arrays.stream(Direction.Axis.values()).filter(axis -> axis != hitFace.getAxis() && axis != offset.getFirst().getAxis()).map(Iterate::directionsInAxis).findFirst().orElse(new Direction[]{}); - Vec3d startOffset = new Vec3d(offset.getFirst().getDirectionVec()); - Vec3d start = hitCenter.add(startOffset); - for (Direction dir : directions) { - Vec3d arrowOffset = new Vec3d(dir.getDirectionVec()).scale(.25); - Vec3d target = hitCenter.add(startOffset.scale(0.75)).add(arrowOffset); - CreateClient.outliner.showLine("poleHelp" + offset.getFirst() + dir, start, target).lineWidth(1/16f); - } - } - - // first indicates the direction that the position needs to be offset into - // second indicates by how many blocks the position needs to be offset by; is 0 if there was no valid position on either end of the pole - public static Pair getPlacementOffset(World world, Direction.Axis poleAxis, BlockPos pos, Vec3d hit) { - Pair offset = null; - double min = Double.MAX_VALUE; - Vec3d localPos = hit.subtract(VecHelper.getCenterOf(pos)); - - //find target direction - for (Direction dir : Iterate.directionsInAxis(poleAxis)) { - double distance = new Vec3d(dir.getDirectionVec()).distanceTo(localPos); - if (distance > min) - continue; - min = distance; - offset = Pair.of(dir, 0); - } - - if (offset == null)//?? - return null; - - //check for space at the end of the pole - int poles = attachedPoles(world, pos, offset.getFirst()); - BlockState state = world.getBlockState(pos.offset(offset.getFirst(), poles + 1)); - - if (state.getMaterial().isReplaceable()) { - offset.setSecond(poles + 1); - return offset; - } - - //check the other end of the pole - offset.setFirst(offset.getFirst().getOpposite()); - poles = attachedPoles(world, pos, offset.getFirst()); - state = world.getBlockState(pos.offset(offset.getFirst(), poles + 1)); - - if (state.getMaterial().isReplaceable()) { - offset.setSecond(poles + 1); - } - - return offset; - } - - public static int attachedPoles(World world, BlockPos pos, Direction direction) { - BlockPos checkPos = pos.offset(direction); - BlockState state = world.getBlockState(checkPos); - int count = 0; - while (matchesAxis(state, direction.getAxis())) { - count++; - checkPos = checkPos.offset(direction); - state = world.getBlockState(checkPos); - } - return count; - } - - //checks if the given state is a piston pole on the given axis - public static boolean matchesAxis(BlockState state, Direction.Axis axis) { - return AllBlocks.PISTON_EXTENSION_POLE.has(state) && state.get(PistonExtensionPoleBlock.FACING).getAxis() == axis; - } - - public static boolean isHoldingPole(PlayerEntity player) { - return Arrays.stream(Hand.values()).anyMatch(hand -> AllBlocks.PISTON_EXTENSION_POLE.isIn(player.getHeldItem(hand))); - } - -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/goggles/GoggleOverlayRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/goggles/GoggleOverlayRenderer.java index 882dedcd9..07b9dff3a 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/goggles/GoggleOverlayRenderer.java +++ b/src/main/java/com/simibubi/create/content/contraptions/goggles/GoggleOverlayRenderer.java @@ -6,7 +6,6 @@ import com.simibubi.create.AllItems; import com.simibubi.create.CreateClient; import com.simibubi.create.content.contraptions.components.structureMovement.piston.MechanicalPistonBlock; import com.simibubi.create.content.contraptions.components.structureMovement.piston.PistonExtensionPoleBlock; -import com.simibubi.create.content.contraptions.components.structureMovement.piston.PistonPolePlacementHelper; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.gui.GuiGameElement; import com.simibubi.create.foundation.tileEntity.behaviour.ValueBox; @@ -14,7 +13,6 @@ import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.outliner.Outline; import com.simibubi.create.foundation.utility.outliner.Outliner.OutlineEntry; - import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.Screen; @@ -106,7 +104,7 @@ public class GoggleOverlayRenderer { int poles = 1; boolean pistonFound = false; for (Direction dir : directions) { - int attachedPoles = PistonPolePlacementHelper.attachedPoles(world, pos, dir); + int attachedPoles = PistonExtensionPoleBlock.PlacementHelper.get().attachedPoles(world, pos, dir); poles += attachedPoles; pistonFound |= world.getBlockState(pos.offset(dir, attachedPoles + 1)) .getBlock() instanceof MechanicalPistonBlock; diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java index 8e2d906c3..4b62167c9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java @@ -5,7 +5,6 @@ import com.simibubi.create.AllShapes; import com.simibubi.create.content.contraptions.base.IRotate; import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerBlock; import com.simibubi.create.foundation.utility.Iterate; - import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.fluid.Fluids; @@ -69,9 +68,7 @@ public class CogWheelBlock extends AbstractShaftBlock { @Override public BlockState getStateForPlacement(BlockItemUseContext context) { - BlockPos placedOnPos = context.getPos() - .offset(context.getFace() - .getOpposite()); + BlockPos placedOnPos = context.getPos().offset(context.getFace().getOpposite()); World world = context.getWorld(); BlockState placedAgainst = world.getBlockState(placedOnPos); Block block = placedAgainst.getBlock(); @@ -80,7 +77,8 @@ public class CogWheelBlock extends AbstractShaftBlock { .down()); IFluidState ifluidstate = context.getWorld().getFluidState(context.getPos()); if (AllBlocks.ROTATION_SPEED_CONTROLLER.has(stateBelow) && isLarge) { - return this.getDefaultState().with(BlockStateProperties.WATERLOGGED, Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER)) + return this.getDefaultState() + .with(BlockStateProperties.WATERLOGGED, ifluidstate.getFluid() == Fluids.WATER) .with(AXIS, stateBelow.get(SpeedControllerBlock.HORIZONTAL_AXIS) == Axis.X ? Axis.Z : Axis.X); } @@ -89,10 +87,11 @@ public class CogWheelBlock extends AbstractShaftBlock { Axis preferredAxis = getPreferredAxis(context); if (preferredAxis != null) return this.getDefaultState() - .with(AXIS, preferredAxis).with(BlockStateProperties.WATERLOGGED, Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER)); + .with(AXIS, preferredAxis) + .with(BlockStateProperties.WATERLOGGED, ifluidstate.getFluid() == Fluids.WATER); return this.getDefaultState() - .with(AXIS, context.getFace() - .getAxis()).with(BlockStateProperties.WATERLOGGED, Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER)); + .with(AXIS, context.getFace().getAxis()) + .with(BlockStateProperties.WATERLOGGED, ifluidstate.getFluid() == Fluids.WATER); } return getDefaultState().with(AXIS, ((IRotate) block).getRotationAxis(placedAgainst)); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java index c468242d2..568238b4c 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogwheelBlockItem.java @@ -1,38 +1,76 @@ package com.simibubi.create.content.contraptions.relays.elementary; import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllShapes; import com.simibubi.create.foundation.advancement.AllTriggers; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.VecHelper; - +import com.simibubi.create.foundation.utility.placement.IPlacementHelper; +import com.simibubi.create.foundation.utility.placement.PlacementHelpers; +import com.simibubi.create.foundation.utility.placement.PlacementOffset; +import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResultType; import net.minecraft.util.Direction; import net.minecraft.util.Direction.Axis; import net.minecraft.util.Direction.AxisDirection; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; +import java.util.List; +import java.util.function.Predicate; + +import static com.simibubi.create.content.contraptions.relays.elementary.CogWheelBlock.AXIS; + public class CogwheelBlockItem extends BlockItem { boolean large; + private final int placementHelperId; + public CogwheelBlockItem(CogWheelBlock block, Properties builder) { super(block, builder); large = block.isLarge; + + placementHelperId = PlacementHelpers.register(large ? new LargeCogHelper() : new SmallCogHelper()); } @Override public ActionResultType tryPlace(BlockItemUseContext context) { - Direction face = context.getFace(); - BlockPos placedOnPos = context.getPos().offset(face.getOpposite()); - BlockState placedOnState = context.getWorld().getBlockState(placedOnPos); + IPlacementHelper helper = PlacementHelpers.get(placementHelperId); + World world = context.getWorld(); + BlockPos pos = context.getPos().offset(context.getFace().getOpposite()); + BlockState state = world.getBlockState(pos); - if (!(placedOnState.getBlock() instanceof CogWheelBlock)) + if (!helper.getStatePredicate().test(state)) + return super.tryPlace(context); + + PlacementOffset offset = helper.getOffset(world, state, pos, new BlockRayTraceResult(context.getHitVec(), context.getFace(), pos, true)); + + if (!offset.isSuccessful()) + return super.tryPlace(context); + + BlockPos newPos = new BlockPos(offset.getPos()); + + if (!world.getBlockState(newPos).getMaterial().isReplaceable()) + return ActionResultType.PASS; + + if (world.isRemote) + return ActionResultType.SUCCESS; + + world.setBlockState(newPos, offset.getTransform().apply(this.getBlock().getDefaultState())); + if (context.getPlayer() != null && !context.getPlayer().isCreative()) { + context.getItem().shrink(1); + } + + return ActionResultType.SUCCESS; + + /*if (!(placedOnState.getBlock() instanceof CogWheelBlock)) return super.tryPlace(context); if (face.getAxis() == placedOnState.get(CogWheelBlock.AXIS)) return super.tryPlace(context); @@ -67,7 +105,7 @@ public class CogwheelBlockItem extends BlockItem { return ActionResultType.FAIL; } - return super.tryPlace(context); + return super.tryPlace(context);*/ } @Override @@ -107,4 +145,137 @@ public class CogwheelBlockItem extends BlockItem { return super.placeBlock(context, state); } + @MethodsReturnNonnullByDefault + private static class SmallCogHelper extends DiagonalCogHelper { + + @Override + public Predicate getItemPredicate() { + return AllBlocks.COGWHEEL::isIn; + } + + @Override + public PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray) { + if (hitOnShaft(state, ray)) + return PlacementOffset.fail(); + + if (!((CogWheelBlock) state.getBlock()).isLarge) { + List directions = IPlacementHelper.orderedByDistanceExceptAxis(pos, ray.getHitVec(), state.get(AXIS)); + + for (Direction dir : directions) { + BlockPos newPos = pos.offset(dir); + + if (hasLargeCogwheelNeighbor(world, newPos, state.get(AXIS))) + continue; + + if (!world.getBlockState(newPos).getMaterial().isReplaceable()) + continue; + + return PlacementOffset.success(newPos, s -> s.with(AXIS, state.get(AXIS))); + + } + + return PlacementOffset.fail(); + } + + return super.getOffset(world, state, pos, ray); + } + + @Override + public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { + IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, state.get(AXIS)), ((CogWheelBlock) state.getBlock()).isLarge ? 1.5D : 0.75D); + } + } + + @MethodsReturnNonnullByDefault + private static class LargeCogHelper extends DiagonalCogHelper { + + @Override + public Predicate getItemPredicate() { + return AllBlocks.LARGE_COGWHEEL::isIn; + } + + @Override + public PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray) { + if (hitOnShaft(state, ray)) + return PlacementOffset.fail(); + + if (((CogWheelBlock) state.getBlock()).isLarge) { + Direction side = IPlacementHelper.orderedByDistanceOnlyAxis(pos, ray.getHitVec(), state.get(AXIS)).get(0); + List directions = IPlacementHelper.orderedByDistanceExceptAxis(pos, ray.getHitVec(), state.get(AXIS)); + for (Direction dir : directions) { + BlockPos newPos = pos.offset(dir).offset(side); + if (!world.getBlockState(newPos).getMaterial().isReplaceable()) + continue; + + return PlacementOffset.success(newPos, s -> s.with(AXIS, dir.getAxis())); + } + + return PlacementOffset.fail(); + } + + return super.getOffset(world, state, pos, ray); + } + } + + @MethodsReturnNonnullByDefault + private abstract static class DiagonalCogHelper implements IPlacementHelper { + + @Override + public Predicate getStatePredicate() { + return s -> s.getBlock() instanceof CogWheelBlock; + } + + @Override + public PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray) { + //diagonal gears of different size + Direction closest = IPlacementHelper.orderedByDistanceExceptAxis(pos, ray.getHitVec(), state.get(AXIS)).get(0); + List directions = IPlacementHelper.orderedByDistanceExceptAxis(pos, ray.getHitVec(), state.get(AXIS), d -> d.getAxis() != closest.getAxis()); + + for (Direction dir : directions) { + BlockPos newPos = pos.offset(dir).offset(closest); + if (!world.getBlockState(newPos).getMaterial().isReplaceable()) + continue; + + if (AllBlocks.COGWHEEL.has(state) && hasSmallCogwheelNeighbor(world, newPos, state.get(AXIS))) + continue; + + return PlacementOffset.success(newPos, s -> s.with(AXIS, state.get(AXIS))); + } + + return PlacementOffset.fail(); + } + + @Override + public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { + IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), Direction.getFacingFromAxis(Direction.AxisDirection.POSITIVE, state.get(AXIS)), 1D); + } + + protected boolean hitOnShaft(BlockState state, BlockRayTraceResult ray) { + return AllShapes.SIX_VOXEL_POLE.get(state.get(AXIS)).getBoundingBox().grow(0.001).contains(ray.getHitVec().subtract(ray.getHitVec().align(Iterate.axisSet))); + } + + protected boolean hasLargeCogwheelNeighbor(World world, BlockPos pos, Direction.Axis axis) { + for (Direction dir : Iterate.directions) { + if (dir.getAxis() == axis) + continue; + + if (AllBlocks.LARGE_COGWHEEL.has(world.getBlockState(pos.offset(dir)))) + return true; + } + + return false; + } + + protected boolean hasSmallCogwheelNeighbor(World world, BlockPos pos, Direction.Axis axis) { + for (Direction dir : Iterate.directions) { + if (dir.getAxis() == axis) + continue; + + if (AllBlocks.COGWHEEL.has(world.getBlockState(pos.offset(dir)))) + return true; + } + + return false; + } + } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java index 92ab2fad7..b4584c8ee 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java @@ -5,11 +5,18 @@ import com.simibubi.create.AllShapes; import com.simibubi.create.content.contraptions.base.KineticTileEntity; import com.simibubi.create.content.contraptions.relays.encased.EncasedShaftBlock; import com.simibubi.create.foundation.advancement.AllTriggers; - +import com.simibubi.create.foundation.utility.placement.IPlacementHelper; +import com.simibubi.create.foundation.utility.placement.PlacementHelpers; +import com.simibubi.create.foundation.utility.placement.PlacementOffset; +import com.simibubi.create.foundation.utility.placement.util.PoleHelper; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResultType; +import net.minecraft.util.Direction; import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; @@ -18,8 +25,12 @@ import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; +import java.util.function.Predicate; + public class ShaftBlock extends AbstractShaftBlock { + private static final int placementHelperId = PlacementHelpers.register(new PlacementHelper()); + public ShaftBlock(Properties properties) { super(properties); } @@ -45,7 +56,7 @@ public class ShaftBlock extends AbstractShaftBlock { @Override public ActionResultType onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, - BlockRayTraceResult p_225533_6_) { + BlockRayTraceResult ray) { if (player.isSneaking() || !player.isAllowEdit()) return ActionResultType.PASS; @@ -66,6 +77,52 @@ public class ShaftBlock extends AbstractShaftBlock { return ActionResultType.SUCCESS; } + IPlacementHelper helper = PlacementHelpers.get(placementHelperId); + if (helper.getItemPredicate().test(heldItem)) { + PlacementOffset offset = helper.getOffset(world, state, pos, ray); + + if (!offset.isSuccessful()) + return ActionResultType.PASS; + + BlockPos newPos = new BlockPos(offset.getPos()); + + if (!world.getBlockState(newPos).getMaterial().isReplaceable()) + return ActionResultType.PASS; + + if (world.isRemote) + return ActionResultType.SUCCESS; + + Block block = ((BlockItem) heldItem.getItem()).getBlock(); + world.setBlockState(newPos, offset.getTransform().apply(block.getDefaultState())); + if (!player.isCreative()) + heldItem.shrink(1); + + return ActionResultType.SUCCESS; + } + return ActionResultType.PASS; } + + @MethodsReturnNonnullByDefault + private static class PlacementHelper extends PoleHelper { + //used for extending a shaft in its axis, like the piston poles. works with shafts and cogs + + private PlacementHelper(){ + super( + state -> state.getBlock() instanceof AbstractShaftBlock, + state -> state.get(AXIS), + AXIS + ); + } + + @Override + public Predicate getItemPredicate() { + return i -> i.getItem() instanceof BlockItem && ((BlockItem) i.getItem()).getBlock() instanceof AbstractShaftBlock; + } + + @Override + public Predicate getStatePredicate() { + return AllBlocks.SHAFT::has; + } + } } diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java index a609e7955..6ab2abeaf 100644 --- a/src/main/java/com/simibubi/create/events/ClientEvents.java +++ b/src/main/java/com/simibubi/create/events/ClientEvents.java @@ -1,17 +1,12 @@ package com.simibubi.create.events; -import java.util.ArrayList; -import java.util.List; - import com.mojang.blaze3d.matrix.MatrixStack; import com.simibubi.create.AllFluids; import com.simibubi.create.Create; import com.simibubi.create.CreateClient; import com.simibubi.create.content.contraptions.KineticDebugger; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler; -import com.simibubi.create.content.contraptions.components.structureMovement.bearing.SailBlockPlacementHelper; import com.simibubi.create.content.contraptions.components.structureMovement.chassis.ChassisRangeDisplay; -import com.simibubi.create.content.contraptions.components.structureMovement.piston.PistonPolePlacementHelper; import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingHandlerClient; import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingPhysics; import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingRenderer; @@ -36,7 +31,7 @@ import com.simibubi.create.foundation.tileEntity.behaviour.linked.LinkRenderer; import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueRenderer; import com.simibubi.create.foundation.utility.AnimationTickHolder; import com.simibubi.create.foundation.utility.ServerSpeedProvider; - +import com.simibubi.create.foundation.utility.placement.PlacementHelpers; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.client.renderer.IRenderTypeBuffer; @@ -61,6 +56,9 @@ import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import java.util.ArrayList; +import java.util.List; + @EventBusSubscriber(value = Dist.CLIENT) public class ClientEvents { @@ -103,8 +101,7 @@ public class ClientEvents { ExtendoGripRenderHandler.tick(); // CollisionDebugger.tick(); ArmInteractionPointHandler.tick(); - SailBlockPlacementHelper.tick(); - PistonPolePlacementHelper.tick(); + PlacementHelpers.tick(); CreateClient.outliner.tickOutlines(); } @@ -137,8 +134,8 @@ public class ClientEvents { return; onRenderHotbar(new MatrixStack(), Minecraft.getInstance() - .getBufferBuilders() - .getEntityVertexConsumers(), 0xF000F0, OverlayTexture.DEFAULT_UV); + .getBufferBuilders() + .getEntityVertexConsumers(), 0xF000F0, OverlayTexture.DEFAULT_UV); } public static void onRenderHotbar(MatrixStack ms, IRenderTypeBuffer buffer, int light, int overlay) { @@ -154,7 +151,7 @@ public class ClientEvents { ItemStack stack = event.getItemStack(); String translationKey = stack.getItem() - .getTranslationKey(stack); + .getTranslationKey(stack); if (!translationKey.startsWith(itemPrefix) && !translationKey.startsWith(blockPrefix)) return; @@ -163,7 +160,7 @@ public class ClientEvents { List toolTip = new ArrayList<>(); toolTip.add(itemTooltip.remove(0)); TooltipHelper.getTooltip(stack) - .addInformation(toolTip); + .addInformation(toolTip); itemTooltip.addAll(0, toolTip); } diff --git a/src/main/java/com/simibubi/create/foundation/utility/Iterate.java b/src/main/java/com/simibubi/create/foundation/utility/Iterate.java index 70206344d..693231353 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/Iterate.java +++ b/src/main/java/com/simibubi/create/foundation/utility/Iterate.java @@ -5,6 +5,7 @@ import net.minecraft.util.Direction.Axis; import net.minecraft.util.math.BlockPos; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; public class Iterate { @@ -15,6 +16,7 @@ public class Iterate { public static final Direction[] directions = Direction.values(); public static final Direction[] horizontalDirections = getHorizontals(); public static final Axis[] axes = Axis.values(); + public static final EnumSet axisSet = EnumSet.allOf(Direction.Axis.class); private static Direction[] getHorizontals() { Direction[] directions = new Direction[4]; diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java b/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java new file mode 100644 index 000000000..685323076 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/IPlacementHelper.java @@ -0,0 +1,111 @@ +package com.simibubi.create.foundation.utility.placement; + +import com.simibubi.create.CreateClient; +import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.Pair; +import com.simibubi.create.foundation.utility.VecHelper; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@MethodsReturnNonnullByDefault +public interface IPlacementHelper { + + /** + * @return a predicate that gets tested with the items held in the players hands, + * should return true if this placement helper is active with the given item + */ + Predicate getItemPredicate(); + + /** + * @return a predicate that gets tested with the blockstate the player is looking at + * should return true if this placement helper is active with the given blockstate + */ + Predicate getStatePredicate(); + + /** + * @return PlacementOffset.fail() if no valid offset could be found. + * PlacementOffset.success(newPos) with newPos being the new position the block should be placed at + */ + PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray); + + //only gets called when placementOffset is successful + default void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { + IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos), VecHelper.getCenterOf(offset.getPos()), ray.getFace()); + } + + static void renderArrow(Vec3d center, Vec3d target, Direction arrowPlane) { + renderArrow(center, target, arrowPlane, 1D); + } + static void renderArrow(Vec3d center, Vec3d target, Direction arrowPlane, double distanceFromCenter) { + Vec3d direction = target.subtract(center).normalize(); + Vec3d facing = new Vec3d(arrowPlane.getDirectionVec()); + Vec3d start = center.add(direction); + Vec3d offset = direction.scale(distanceFromCenter-1); + Vec3d offsetA = direction.crossProduct(facing).normalize().scale(.25); + Vec3d offsetB = facing.crossProduct(direction).normalize().scale(.25); + Vec3d endA = center.add(direction.scale(.75)).add(offsetA); + Vec3d endB = center.add(direction.scale(.75)).add(offsetB); + CreateClient.outliner.showLine("placementArrowA" + center + target, start.add(offset), endA.add(offset)).lineWidth(1/16f); + CreateClient.outliner.showLine("placementArrowB" + center + target, start.add(offset), endB.add(offset)).lineWidth(1/16f); + } + + /*@OnlyIn(Dist.CLIENT) + static void renderArrow(Vec3d center, Direction towards, BlockRayTraceResult ray) { + Direction hitFace = ray.getFace(); + + if (hitFace.getAxis() == towards.getAxis()) + return; + + //get the two perpendicular directions to form the arrow + Direction[] directions = Arrays.stream(Direction.Axis.values()).filter(axis -> axis != hitFace.getAxis() && axis != towards.getAxis()).map(Iterate::directionsInAxis).findFirst().orElse(new Direction[]{}); + Vec3d startOffset = new Vec3d(towards.getDirectionVec()); + Vec3d start = center.add(startOffset); + for (Direction dir : directions) { + Vec3d arrowOffset = new Vec3d(dir.getDirectionVec()).scale(.25); + Vec3d target = center.add(startOffset.scale(0.75)).add(arrowOffset); + CreateClient.outliner.showLine("placementArrow" + towards + dir, start, target).lineWidth(1/16f); + } + }*/ + + static List orderedByDistanceOnlyAxis(BlockPos pos, Vec3d hit, Direction.Axis axis) { + return orderedByDistance(pos, hit, dir -> dir.getAxis() == axis); + } + + static List orderedByDistanceOnlyAxis(BlockPos pos, Vec3d hit, Direction.Axis axis, Predicate includeDirection) { + return orderedByDistance(pos, hit, ((Predicate) dir -> dir.getAxis() == axis).and(includeDirection)); + } + + static List orderedByDistanceExceptAxis(BlockPos pos, Vec3d hit, Direction.Axis axis) { + return orderedByDistance(pos, hit, dir -> dir.getAxis() != axis); + } + + static List orderedByDistanceExceptAxis(BlockPos pos, Vec3d hit, Direction.Axis axis, Predicate includeDirection) { + return orderedByDistance(pos, hit, ((Predicate) dir -> dir.getAxis() != axis).and(includeDirection)); + } + + static List orderedByDistance(BlockPos pos, Vec3d hit) { + return orderedByDistance(pos, hit, _$ -> true); + } + + static List orderedByDistance(BlockPos pos, Vec3d hit, Predicate includeDirection) { + Vec3d centerToHit = hit.subtract(VecHelper.getCenterOf(pos)); + return Arrays.stream(Iterate.directions) + .filter(includeDirection) + .map(dir -> Pair.of(dir, new Vec3d(dir.getDirectionVec()).distanceTo(centerToHit))) + .sorted(Comparator.comparingDouble(Pair::getSecond)) + .map(Pair::getFirst) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java new file mode 100644 index 000000000..7645ef75f --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java @@ -0,0 +1,74 @@ +package com.simibubi.create.foundation.utility.placement; + +import net.minecraft.block.BlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class PlacementHelpers { + + private static final List helpers = new ArrayList<>(); + + public static int register(IPlacementHelper helper) { + helpers.add(helper); + return helpers.size() - 1; + } + + public static IPlacementHelper get(int id) { + if (id < 0 || id >= helpers.size()) + throw new ArrayIndexOutOfBoundsException("id " + id + " for placement helper not known"); + + return helpers.get(id); + } + + @OnlyIn(Dist.CLIENT) + public static void tick() { + Minecraft mc = Minecraft.getInstance(); + ClientWorld world = mc.world; + + if (world == null) + return; + + if (!(mc.objectMouseOver instanceof BlockRayTraceResult)) + return; + + BlockRayTraceResult ray = (BlockRayTraceResult) mc.objectMouseOver; + + if (mc.player == null) + return; + + List filteredForHeldItem = helpers.stream().filter(helper -> Arrays.stream(Hand.values()).anyMatch(hand -> helper.getItemPredicate().test(mc.player.getHeldItem(hand)))).collect(Collectors.toList()); + if (filteredForHeldItem.isEmpty()) + return; + + if (mc.player.isSneaking())//for now, disable all helpers when sneaking TODO add helpers that respect sneaking but still show position + return; + + BlockPos pos = ray.getPos(); + BlockState state = world.getBlockState(pos); + + List filteredForState = filteredForHeldItem.stream().filter(helper -> helper.getStatePredicate().test(state)).collect(Collectors.toList()); + + if (filteredForState.isEmpty()) + return; + + for (IPlacementHelper h : filteredForState) { + PlacementOffset offset = h.getOffset(world, state, pos, ray); + + if (offset.isSuccessful()) { + h.renderAt(pos, state, ray, offset); + break; + } + + } + } +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java new file mode 100644 index 000000000..4d8f28faf --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java @@ -0,0 +1,43 @@ +package com.simibubi.create.foundation.utility.placement; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.Vec3i; + +import java.util.function.Function; + +public class PlacementOffset { + + private final boolean success; + private final Vec3i pos; + private final Function stateTransform; + + private PlacementOffset(boolean success, Vec3i pos, Function transform) { + this.success = success; + this.pos = pos; + this.stateTransform = transform == null ? Function.identity() : transform; + } + + public static PlacementOffset fail() { + return new PlacementOffset(false, Vec3i.NULL_VECTOR, null); + } + + public static PlacementOffset success(Vec3i pos) { + return new PlacementOffset(true, pos, null); + } + + public static PlacementOffset success(Vec3i pos, Function transform) { + return new PlacementOffset(true, pos, transform); + } + + public boolean isSuccessful() { + return success; + } + + public Vec3i getPos() { + return pos; + } + + public Function getTransform() { + return stateTransform; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java b/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java new file mode 100644 index 000000000..f074a8d5c --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/util/PoleHelper.java @@ -0,0 +1,77 @@ +package com.simibubi.create.foundation.utility.placement.util; + +import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.foundation.utility.placement.IPlacementHelper; +import com.simibubi.create.foundation.utility.placement.PlacementOffset; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.BlockState; +import net.minecraft.state.IProperty; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +@MethodsReturnNonnullByDefault +public abstract class PoleHelper> implements IPlacementHelper { + + protected final Predicate statePredicate; + protected final IProperty property; + protected final Function axisFunction; + + public PoleHelper(Predicate statePredicate, Function axisFunction, IProperty property) { + this.statePredicate = statePredicate; + this.axisFunction = axisFunction; + this.property = property; + } + + public boolean matchesAxis(BlockState state, Direction.Axis axis) { + if (!statePredicate.test(state)) + return false; + + return axisFunction.apply(state) == axis; + } + + public int attachedPoles(World world, BlockPos pos, Direction direction) { + BlockPos checkPos = pos.offset(direction); + BlockState state = world.getBlockState(checkPos); + int count = 0; + while (matchesAxis(state, direction.getAxis())) { + count++; + checkPos = checkPos.offset(direction); + state = world.getBlockState(checkPos); + } + return count; + } + + @Override + public Predicate getStatePredicate() { + return this.statePredicate; + } + + @Override + public PlacementOffset getOffset(World world, BlockState state, BlockPos pos, BlockRayTraceResult ray) { + List directions = IPlacementHelper.orderedByDistance(pos, ray.getHitVec(), dir -> dir.getAxis() == axisFunction.apply(state)); + for (Direction dir : directions) { + int poles = attachedPoles(world, pos, dir); + BlockPos newPos = pos.offset(dir, poles + 1); + BlockState newState = world.getBlockState(newPos); + + if (newState.getMaterial().isReplaceable()) + return PlacementOffset.success(newPos, bState -> bState.with(property, state.get(property))); + + } + + return PlacementOffset.fail(); + } + + @Override + public void renderAt(BlockPos pos, BlockState state, BlockRayTraceResult ray, PlacementOffset offset) { + Vec3d centerOffset = new Vec3d(ray.getFace().getDirectionVec()).scale(.3); + IPlacementHelper.renderArrow(VecHelper.getCenterOf(pos).add(centerOffset), VecHelper.getCenterOf(offset.getPos()).add(centerOffset), ray.getFace(), 0.75D); + } +}