diff --git a/README.md b/README.md
index a88464bb..e1a445cc 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ To set this up just add the latest litematica version to your mods folder as wel
`easyPlaceModeBreakBlocks`: "Automatically breaks blocks."
`easyPlaceModeDelay`: "Delay between printing blocks.Do not set to 0 if you are playing on a server."
`easyPlaceModePaper`: "Enable this feature to bypass the built-in papers anti-cheat. This will make the range stricter, delay lower and only pick blocks from the hotbar."
+`easyPlaceModeFluids`: "Enable this feature for placing fluid(water/lava) sources or waterlogged blocks. Be aware, this functions uses \"Fake rotations\", this can be seen as hacking!"
### Handy litematica settings:
`easyPlaceMode`: "When enabled, then simply trying to use an item/place a block on schematic blocks will place that block in that position."
diff --git a/src/main/java/io/github/eatmyvenom/litematicin/LitematicaMixinMod.java b/src/main/java/io/github/eatmyvenom/litematicin/LitematicaMixinMod.java
index ec13aa65..b4d4efc1 100644
--- a/src/main/java/io/github/eatmyvenom/litematicin/LitematicaMixinMod.java
+++ b/src/main/java/io/github/eatmyvenom/litematicin/LitematicaMixinMod.java
@@ -18,6 +18,7 @@ public class LitematicaMixinMod implements ModInitializer {
public static final ConfigBoolean EASY_PLACE_MODE_BREAK_BLOCKS = new ConfigBoolean("easyPlaceModeBreakBlocks", false, "Automatically breaks blocks.");
public static final ConfigDouble EASY_PLACE_MODE_DELAY = new ConfigDouble( "easyPlaceModeDelay", 0.2, 0.0, 1.0, "Delay between printing blocks.\nDo not set to 0 if you are playing on a server.");
public static final ConfigBoolean EASY_PLACE_MODE_PAPER = new ConfigBoolean("easyPlaceModePaper", false, "Enable this feature to bypass the built-in papers anti-cheat. This will make the range stricter, delay lower and only pick blocks from the hotbar.");
+ public static final ConfigBoolean EASY_PLACE_MODE_FLUIDS = new ConfigBoolean("easyPlaceModeFluids", false, "Enable for placing fluid(water/lava) sources or waterlogged blocks. Be aware, this functions uses \"Fake rotations\", this can be seen as hacking!");
public static final ImmutableList betterList = ImmutableList.builder()
.addAll(Configs.Generic.OPTIONS)
@@ -28,6 +29,7 @@ public class LitematicaMixinMod implements ModInitializer {
.add(EASY_PLACE_MODE_BREAK_BLOCKS)
.add(EASY_PLACE_MODE_DELAY)
.add(EASY_PLACE_MODE_PAPER)
+ .add(EASY_PLACE_MODE_FLUIDS)
.build();
@Override
diff --git a/src/main/java/io/github/eatmyvenom/litematicin/utils/InteractionUtils.java b/src/main/java/io/github/eatmyvenom/litematicin/utils/InteractionUtils.java
index 8c259ee9..1819e2d9 100644
--- a/src/main/java/io/github/eatmyvenom/litematicin/utils/InteractionUtils.java
+++ b/src/main/java/io/github/eatmyvenom/litematicin/utils/InteractionUtils.java
@@ -1,17 +1,11 @@
package io.github.eatmyvenom.litematicin.utils;
+import java.util.function.Predicate;
+
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.item.ItemStack;
-import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket;
-import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
-import net.minecraft.state.property.Properties;
-import net.minecraft.util.ActionResult;
-import net.minecraft.util.Hand;
-import net.minecraft.util.TypedActionResult;
-import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
@@ -19,18 +13,17 @@
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
-import net.minecraft.world.GameMode;
import net.minecraft.world.RaycastContext;
public class InteractionUtils {
/**
- * Check if an item (bucket) can be used at the given {@code BlockPos}.
+ * Check if an item can be used at the given {@code BlockPos}.
* @param pos
* @param mc
* @return
*/
- public static ViewResult canSeeAndInteractWithBlock(BlockPos pos, MinecraftClient mc) {
+ public static ViewResult canSeeAndInteractWithBlock(BlockPos pos, MinecraftClient mc, Predicate statesToAccept) {
Direction[] possibleDirections = Direction.values();
for (int i = 0; i < possibleDirections.length; i++) {
@@ -38,7 +31,7 @@ public static ViewResult canSeeAndInteractWithBlock(BlockPos pos, MinecraftClien
BlockState state = mc.world.getBlockState(pos.add(vec));
// You can't place water on air or a waterloggen block
- if (state.isAir() || state.contains(Properties.WATERLOGGED)) continue;
+ if (!statesToAccept.test(state)) continue;
ViewResult result = isVisible(mc, pos, possibleDirections[i]);
@@ -73,12 +66,13 @@ private static Rotation getNeededRotation(PlayerEntity me, BlockPos pos, Directi
double pitch = Math.asin(diry);
double yaw = Math.atan2(dirz, dirx);
-
+
//to degree
- pitch = pitch * 180.0 / Math.PI;
- yaw = yaw * 180.0 / Math.PI;
+ pitch = pitch * 180.0d / Math.PI;
+ yaw = yaw * 180.0d / Math.PI;
- yaw += 90f;
+ yaw += 90d;
+
return new Rotation((float)yaw, (float)pitch, (float)len);
}
@@ -104,8 +98,7 @@ private static ViewResult isVisible(MinecraftClient mc, BlockPos toSee, Directio
RaycastContext.FluidHandling.ANY, player));
if (result.getType() == Type.BLOCK
- && !(result.getPos().squaredDistanceTo(player.getX(), player.getEyeY(), player.getZ()) < rotation.maxDist * rotation.maxDist) // If there's a block between the player and the location
- && !mc.world.getBlockState(((BlockHitResult)result).getBlockPos()).contains(Properties.WATERLOGGED)) { // Don't place water on top of waterloggable blocks
+ && !(result.getPos().squaredDistanceTo(player.getX(), player.getEyeY(), player.getZ()) < rotation.maxDist * rotation.maxDist)) { // If there's a block between the player and the location
ViewResult viewResult = ViewResult.VISIBLE;
viewResult.pitch = rotation.pitch;
viewResult.yaw = rotation.yaw;
@@ -116,7 +109,7 @@ private static ViewResult isVisible(MinecraftClient mc, BlockPos toSee, Directio
}
/**
- * An overriden function from Minecraft. This original function was protected, so we couldn't use it.
+ * An overridden function from Minecraft. This original function was protected, so we couldn't use it.
* @param pitch
* @param yaw
* @return
@@ -144,44 +137,5 @@ public Rotation(float yaw, float pitch, float maxDist) {
this.maxDist = maxDist;
}
}
- // TODO something wrong with the rotation
-
- /**
- * A slightly modified version of {@code ClientPlayerInteractionManager}.{@code interactItem(PlayerEntity player, World world, Hand hand)}
- * that allows us to change the players rotation (yaw & pitch).
- * @param mc
- * @param hand
- * @param result This contains the rotation
- * @return Whether or not the interact succeeded
- */
- public static ActionResult interactItem(MinecraftClient mc, Hand hand, ViewResult result) {
- if (mc.interactionManager.getCurrentGameMode() == GameMode.SPECTATOR) {
- System.out.println("Spectator");
- return ActionResult.PASS;
- } else {
- /* mc.interactionManager.syncSelectedSlot() in inaccessible
- * To workaround this, we call interactBlock() and give a blockpos outside the worlborder,
- * this will imediately return but the syncSelectedSlot is called.
- */
- mc.interactionManager.interactBlock(null, null, null, new BlockHitResult(null, null,
- new BlockPos(mc.world.getWorldBorder().getBoundEast()+3, 0, mc.world.getWorldBorder().getBoundSouth() + 3), false));
- // TODO Bug: when the player move's the block is placed at the wrong place
- mc.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.Full(mc.player.getX(), mc.player.getEyeY(),
- mc.player.getZ(), result.yaw, result.pitch, mc.player.isOnGround()));
- mc.getNetworkHandler().sendPacket(new PlayerInteractItemC2SPacket(hand));
- ItemStack itemStack = mc.player.getStackInHand(hand);
- if (mc.player.getItemCooldownManager().isCoolingDown(itemStack.getItem())) {
- return ActionResult.PASS;
- } else {
- TypedActionResult typedActionResult = itemStack.use(mc.world, mc.player, hand);
- ItemStack itemStack2 = (ItemStack)typedActionResult.getValue();
- if (itemStack2 != itemStack) {
- mc.player.setStackInHand(hand, itemStack2);
- }
-
- return typedActionResult.getResult();
- }
- }
- }
}
diff --git a/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java b/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java
index 9f69d303..eee41020 100644
--- a/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java
+++ b/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java
@@ -2,6 +2,7 @@
import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.EASY_PLACE_MODE_BREAK_BLOCKS;
import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.EASY_PLACE_MODE_DELAY;
+import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.EASY_PLACE_MODE_FLUIDS;
import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.EASY_PLACE_MODE_MAX_BLOCKS;
import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.EASY_PLACE_MODE_PAPER;
import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.EASY_PLACE_MODE_RANGE_X;
@@ -59,6 +60,7 @@
import net.minecraft.block.WallRedstoneTorchBlock;
import net.minecraft.block.WallSignBlock;
import net.minecraft.block.WallTorchBlock;
+import net.minecraft.block.Waterloggable;
import net.minecraft.block.enums.BedPart;
import net.minecraft.block.enums.BlockHalf;
import net.minecraft.block.enums.DoubleBlockHalf;
@@ -69,6 +71,8 @@
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.state.property.Properties;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
@@ -95,11 +99,7 @@ public class Printer {
*/
@Environment(EnvType.CLIENT)
public static boolean doSchematicWorldPickBlock(boolean closest, MinecraftClient mc, BlockState preference,
- BlockPos pos) {
-
- World world = SchematicWorldHandler.getSchematicWorld();
-
- ItemStack stack = MaterialCache.getInstance().getRequiredBuildItemForState(preference, world, pos);
+ BlockPos pos, ItemStack stack) {
if (stack.isEmpty() == false) {
PlayerInventory inv = mc.player.getInventory();
@@ -119,6 +119,7 @@ public static boolean doSchematicWorldPickBlock(boolean closest, MinecraftClient
// NOTE: I dont know why we have to pick block in creative mode. You can simply
// just set the block
+
mc.interactionManager.clickCreativeStack(stack, 36 + inv.selectedSlot);
return true;
@@ -126,11 +127,14 @@ public static boolean doSchematicWorldPickBlock(boolean closest, MinecraftClient
int slot = inv.getSlotWithStack(stack);
boolean shouldPick = inv.selectedSlot != slot;
- boolean canPick = (slot != -1) && slot < 36 && (slot < maxSlotId && EASY_PLACE_MODE_PAPER.getBooleanValue());
+ boolean canPick = (slot != -1) && slot < 36 && (EASY_PLACE_MODE_PAPER.getBooleanValue() ? slot < maxSlotId : true);
if (shouldPick && canPick) {
InventoryUtils.setPickedItemToHand(stack, mc);
return true;
+ //return InteractionUtils.setPickedItemToHand(stack, mc);
+ } else if (!shouldPick) {
+ return true;
} else if (slot == -1 && Configs.Generic.PICK_BLOCK_SHULKERS.getBooleanValue()) {
slot = InventoryUtils.findSlotWithBoxWithItem(mc.player.playerScreenHandler, stack, false);
if (slot != -1) {
@@ -139,11 +143,10 @@ public static boolean doSchematicWorldPickBlock(boolean closest, MinecraftClient
return true;
}
}
- return !shouldPick;
}
}
- return true;
+ return false;
}
/**
@@ -320,13 +323,20 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
for (int x = fromX; x <= toX; x++) {
for (int y = fromY; y <= toY; y++) {
for (int z = fromZ; z <= toZ; z++) {
-
+ BlockPos pos = new BlockPos(x, y, z);
+
+ BlockState stateSchematic = world.getBlockState(pos);
+ BlockState stateClient = mc.world.getBlockState(pos);
+
+ if (stateSchematic == stateClient)
+ continue;
+
// Offset to player
double dx = mc.player.getX() - x - 0.5;
double dy = mc.player.getY() - y - 0.5;
double dz = mc.player.getZ() - z - 0.5;
- // Another check if its within reach, this time its not checked in a square but in a circle, hard to do this in 3 dimensions
+ // Another check if its within reach
if (dx * dx + dy * dy + dz * dz > maxReach * maxReach)
continue;
@@ -341,14 +351,9 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
continue;
}
- BlockPos pos = new BlockPos(x, y, z);
-
if (range.isPositionWithinRange(pos) == false) // Check if block is rendered
continue;
-
- BlockState stateSchematic = world.getBlockState(pos);
- BlockState stateClient = mc.world.getBlockState(pos);
-
+
// Block breaking
if (breakBlocks && stateSchematic != null && !stateClient.isAir() && !(stateClient.getBlock() instanceof FluidBlock)) {
if (!stateClient.getBlock().getName().equals(stateSchematic.getBlock().getName()) && dx * dx + Math.pow(dy + 1.5,2) + dz * dz <= maxReach * maxReach) {
@@ -498,7 +503,12 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
if (isPositionCached(pos, false)) continue;
// If the player has the required item in his inventory or is in creative
- ItemStack stack = ((MaterialCache) MaterialCache.getInstance()).getRequiredBuildItemForState((BlockState)stateSchematic);
+ ItemStack stack = MaterialCache.getInstance().getRequiredBuildItemForState(stateSchematic, world, pos);
+
+ // The function above dus not take waterloggable blocks in account
+ if (stateSchematic.getBlock() instanceof Waterloggable && stateSchematic.get(Properties.WATERLOGGED) && stateClient.getBlock() == stateSchematic.getBlock())
+ stack = new ItemStack(Items.WATER_BUCKET);
+
if (stack.isEmpty() == false && (mc.player.getAbilities().creativeMode || mc.player.getInventory().getSlotWithStack(stack) != -1)) {
Block sBlock = stateSchematic.getBlock();
@@ -508,6 +518,10 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
// If the item is a block
if (stack.getItem() instanceof BlockItem) {
+ // Block placing, when the correct block is already placed, but the state is incorrect, continue. (e.g. a powered rail that is not powered, we can't do anything about it, or the schematic is incomplete or the redstone isn't placed yet.
+ if (stateClient.getBlock() == stateSchematic.getBlock())
+ continue;
+
// When gravity block, check if there's a block underneath
if (sBlock instanceof FallingBlock) {
BlockPos Offsetpos = new BlockPos(x, y-1, z);
@@ -553,7 +567,7 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
// This should prevent the printer from placing torches and ... in water
if (!blockSchematic.canPlaceAt(stateSchematic, mc.world, pos)) continue;
-
+
if (blockSchematic instanceof WallMountedBlock || blockSchematic instanceof TorchBlock
|| blockSchematic instanceof LadderBlock || blockSchematic instanceof TrapdoorBlock
|| blockSchematic instanceof TripwireHookBlock || blockSchematic instanceof SignBlock
@@ -631,7 +645,7 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
// If player hasn't the correct item in his hand yet
// Depending on the maxInteracts, it tries to place the same block types in one function call
if (!hasPicked) {
- if (doSchematicWorldPickBlock(true, mc, stateSchematic, pos) == false) // When wrong item in hand
+ if (doSchematicWorldPickBlock(true, mc, stateSchematic, pos, stack) == false) // When wrong item in hand
return ActionResult.FAIL;
hasPicked = true;
pickedBlock = stateSchematic.getBlock().getName();
@@ -655,22 +669,18 @@ public static ActionResult doPrinterAction(MinecraftClient mc) {
// pos, side, hitPos
ActionResult actionResult = mc.interactionManager.interactBlock(mc.player, mc.world, hand, hitResult);
-
- // Test if the blockPlace failed, in this case we need to use it as an item
- if (!actionResult.isAccepted() && actionResult != ActionResult.FAIL) {
- ViewResult result = InteractionUtils.canSeeAndInteractWithBlock(pos, mc);
- if (result == ViewResult.INVISIBLE)
- continue;
- // An overrided minecraft function so we can use a fake rotation
- actionResult = InteractionUtils.interactItem(mc, hand, result);
- }
- // Placement failed
- if (actionResult != ActionResult.SUCCESS)
+ if (!actionResult.isAccepted())
continue;
- // Mark that this position has been handled (use the non-offset position that is checked above)
- cacheEasyPlacePosition(pos, false);
+ if (actionResult.shouldSwingHand())
+ mc.player.swingHand(hand);
+
+ // Ugly workaround, only cache when the block doesn't need to be waterlogged
+ if (!(stateSchematic.getBlock() instanceof Waterloggable && stateSchematic.get(Properties.WATERLOGGED))) {
+ // Mark that this position has been handled (use the non-offset position that is checked above)
+ cacheEasyPlacePosition(pos, false);
+ }
interact++;
// Place multiple slabs/pickles at once, since this is one block
@@ -699,14 +709,23 @@ else if (stateSchematic.getBlock() instanceof SeaPickleBlock
}
}
- } else { // If its an item
+ } else if (EASY_PLACE_MODE_FLUIDS.getBooleanValue()) { // If its an item
// TODO remove some of the duplicate code
// TODO support more items
ViewResult result = ViewResult.INVISIBLE;
// Currently only water/lava blocks placement is supported
if (stateSchematic.getBlock() instanceof FluidBlock) {
- result = InteractionUtils.canSeeAndInteractWithBlock(pos, mc);
+
+ // Water can only be placed if the neighbor is a solid block -> not air and not a waterloggable block
+ result = InteractionUtils.canSeeAndInteractWithBlock(pos, mc,
+ (state) -> !state.isAir() && !state.contains(Properties.WATERLOGGED));
+
+ }else if (stateSchematic.getBlock() instanceof Waterloggable) {
+
+ // Waterloggable block only visible when neighbor is air.
+ result = InteractionUtils.canSeeAndInteractWithBlock(pos, mc,
+ (state) -> state.isAir());
}
if (result == ViewResult.INVISIBLE)
@@ -715,7 +734,7 @@ else if (stateSchematic.getBlock() instanceof SeaPickleBlock
// If player hasn't the correct item in his hand yet
// Depending on the maxInteracts, it tries to place the same block types in one function call
if (!hasPicked) {
- if (doSchematicWorldPickBlock(true, mc, stateSchematic, pos) == false) // When wrong item in hand
+ if (doSchematicWorldPickBlock(true, mc, stateSchematic, pos, stack) == false) // When wrong item in hand
return ActionResult.FAIL;
hasPicked = true;
pickedBlock = stateSchematic.getBlock().getName();
@@ -723,19 +742,31 @@ else if (stateSchematic.getBlock() instanceof SeaPickleBlock
continue;
Hand hand = EntityUtils.getUsedHandForItem(mc.player, stack);
-
+
// Go to next block if a wrong item is in the player's hand
// It will place the same block per function call
if (hand == null)
continue;
- // An overrided minecraft function so we can use a fake rotation
- ActionResult actionResult = InteractionUtils.interactItem(mc, hand, result);
+ // Set player's rotation to fake rotation
+ float previousYaw = mc.player.getYaw();
+ float previousPitch = mc.player.getPitch();
- // Placement failed
- if (actionResult != ActionResult.SUCCESS)
- continue;
+ mc.player.setYaw(result.yaw);
+ mc.player.setPitch(result.pitch);
+
+ ActionResult actionResult = mc.interactionManager.interactItem(mc.player, mc.world, hand);
+ // Set rotation back to original
+ mc.player.setYaw(previousYaw);
+ mc.player.setPitch(previousPitch);
+
+ if (!actionResult.isAccepted())
+ continue;
+
+ if (actionResult.shouldSwingHand())
+ mc.player.swingHand(hand);
+
// Mark that this position has been handled (use the non-offset position that is checked above)
cacheEasyPlacePosition(pos, false);
interact++;
@@ -803,6 +834,7 @@ private static boolean canPlaceFace(FacingData facedata, BlockState stateSchemat
private static boolean printerCheckCancel(BlockState stateSchematic, BlockState stateClient,
PlayerEntity player) {
Block blockSchematic = stateSchematic.getBlock();
+ // TODO fully implement pickels, here it just check if it can be clicked
if (blockSchematic instanceof SeaPickleBlock && stateSchematic.get(SeaPickleBlock.PICKLES) >1) {
Block blockClient = stateClient.getBlock();
@@ -810,22 +842,24 @@ private static boolean printerCheckCancel(BlockState stateSchematic, BlockState
return blockSchematic != blockClient;
}
}
- if (blockSchematic instanceof SlabBlock && stateSchematic.get(SlabBlock.TYPE) == SlabType.DOUBLE) {
+ else if (blockSchematic instanceof SlabBlock && stateSchematic.get(SlabBlock.TYPE) == SlabType.DOUBLE) {
Block blockClient = stateClient.getBlock();
if (blockClient instanceof SlabBlock && stateClient.get(SlabBlock.TYPE) != SlabType.DOUBLE) {
return blockSchematic != blockClient;
}
}
-
+
Block blockClient = stateClient.getBlock();
if (blockClient instanceof SnowBlock && stateClient.get(SnowBlock.LAYERS) <3) {
return false;
}
// If its air, the block doesn't need to be clicked again
// This is a lot simpler than below. But slightly lacks functionality.
- if (stateClient.isAir() || stateClient.getBlock() instanceof FluidBlock)
+ if (stateClient.isAir() || stateClient.getBlock() instanceof FluidBlock
+ || (stateSchematic.contains(Properties.WATERLOGGED) && stateClient.contains(Properties.WATERLOGGED)))
return false;
+
/*
* if (trace.getType() != HitResult.Type.BLOCK) { return false; }
*/