WIP UndoRedo overhaul continued.

More validation in ServerBlockPlacer.
Added BlockPlacerHelper.
This commit is contained in:
Christian Knaapen
2023-02-05 13:22:27 +01:00
parent 23b8c0c9fc
commit df1efef63b
7 changed files with 332 additions and 146 deletions

View File

@@ -31,7 +31,7 @@ public class ClientConfig {
maxBlockPreviews = builder maxBlockPreviews = builder
.comment("Don't show block previews when placing more than this many blocks. " + .comment("Don't show block previews when placing more than this many blocks. " +
"The outline will always be rendered.") "The outline will always be rendered.")
.defineInRange("maxBlockPreviews", 500, 0, 5000); .defineInRange("maxBlockPreviews", 400, 0, 5000);
appearAnimationLength = builder appearAnimationLength = builder
.comment("How long it takes for a block to appear when placed in ticks.", .comment("How long it takes for a block to appear when placed in ticks.",

View File

@@ -17,6 +17,7 @@ public class ServerConfig {
public final BooleanValue allowInSurvival; public final BooleanValue allowInSurvival;
public final BooleanValue useWhitelist; public final BooleanValue useWhitelist;
public final ConfigValue<List<? extends String>> whitelist; public final ConfigValue<List<? extends String>> whitelist;
public final IntValue maxBlocksPlacedAtOnce;
public Validation(Builder builder) { public Validation(Builder builder) {
builder.push("Validation"); builder.push("Validation");
@@ -33,6 +34,10 @@ public class ServerConfig {
.comment("List of player names that can use the mod.") .comment("List of player names that can use the mod.")
.defineList("whitelist", Arrays.asList("Player1", "Player2"), o -> true); .defineList("whitelist", Arrays.asList("Player1", "Player2"), o -> true);
maxBlocksPlacedAtOnce = builder
.comment("Maximum number of blocks that can be placed at once.")
.defineInRange("maxBlocksPlacedAtOnce", 1000, 1, 10000);
builder.pop(); builder.pop();
} }
} }

View File

@@ -262,6 +262,7 @@ public class BuilderChain {
var clickedFace = lookingAt.getDirection(); var clickedFace = lookingAt.getDirection();
Vec3 relativeHitVec = lookingAt.getLocation().subtract(Vec3.atLowerCornerOf(lookingAt.getBlockPos())); Vec3 relativeHitVec = lookingAt.getLocation().subtract(Vec3.atLowerCornerOf(lookingAt.getBlockPos()));
//TODO keep track of count and find different itemstack if necessary
if (itemStack.getItem() instanceof BlockItem) { if (itemStack.getItem() instanceof BlockItem) {
for (BlockEntry blockEntry : blocks) { for (BlockEntry blockEntry : blocks) {

View File

@@ -10,6 +10,7 @@ import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.ServerConfig; import nl.requios.effortlessbuilding.ServerConfig;
import nl.requios.effortlessbuilding.create.foundation.utility.BlockHelper; import nl.requios.effortlessbuilding.create.foundation.utility.BlockHelper;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockPlacerHelper;
import nl.requios.effortlessbuilding.utilities.BlockSet; import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.BlockUtilities; import nl.requios.effortlessbuilding.utilities.BlockUtilities;
@@ -17,12 +18,15 @@ import java.util.*;
// Receives block placement requests from the client and places them // Receives block placement requests from the client and places them
public class ServerBlockPlacer { public class ServerBlockPlacer {
private boolean isPlacingOrBreakingBlocks = false;
//region Delays
private final Set<DelayedEntry> delayedEntries = Collections.synchronizedSet(new HashSet<>()); private final Set<DelayedEntry> delayedEntries = Collections.synchronizedSet(new HashSet<>());
private final Set<DelayedEntry> delayedEntriesView = Collections.unmodifiableSet(delayedEntries); private final Set<DelayedEntry> delayedEntriesView = Collections.unmodifiableSet(delayedEntries);
private boolean isPlacingOrBreakingBlocks = false;
public void placeBlocksDelayed(Player player, BlockSet blocks, long placeTime) { public void placeBlocksDelayed(Player player, BlockSet blocks, long placeTime) {
if (!checkAndNotifyAllowedToUseMod(player)) return; if (!checkAndNotifyAllowedToUseMod(player)) return;
if (!validateBlockSet(player, blocks)) return;
delayedEntries.add(new DelayedEntry(player, blocks, placeTime)); delayedEntries.add(new DelayedEntry(player, blocks, placeTime));
} }
@@ -33,71 +37,103 @@ public class ServerBlockPlacer {
DelayedEntry entry = iterator.next(); DelayedEntry entry = iterator.next();
long gameTime = entry.player.level.getGameTime(); long gameTime = entry.player.level.getGameTime();
if (gameTime >= entry.placeTime) { if (gameTime >= entry.placeTime) {
placeBlocks(entry.player, entry.blocks); applyBlockSet(entry.player, entry.blocks);
iterator.remove(); iterator.remove();
} }
} }
} }
public void placeBlocks(Player player, BlockSet blocks) { public Set<DelayedEntry> getDelayedEntries() {
if (!checkAndNotifyAllowedToUseMod(player)) return; return delayedEntriesView;
// EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks");
var undoSet = new BlockSet();
for (BlockEntry block : blocks) {
if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue;
if (placeBlock(player, block)) {
undoSet.add(block);
}
}
EffortlessBuilding.UNDO_REDO.addUndo(player, undoSet);
} }
private boolean placeBlock(Player player, BlockEntry block) { public record DelayedEntry(Player player, BlockSet blocks, long placeTime) {}
Level world = player.level; //endregion
if (!world.isLoaded(block.blockPos)) return false;
isPlacingOrBreakingBlocks = true;
boolean placedBlock = BlockUtilities.placeBlockEntry(player, block) == InteractionResult.SUCCESS;
isPlacingOrBreakingBlocks = false;
return placedBlock;
}
public void breakBlocks(Player player, BlockSet blocks) { public void breakBlocks(Player player, BlockSet blocks) {
applyBlockSet(player, blocks);
}
public void applyBlockSet(Player player, BlockSet blocks) {
if (!checkAndNotifyAllowedToUseMod(player)) return; if (!checkAndNotifyAllowedToUseMod(player)) return;
// EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks"); if (!validateBlockSet(player, blocks)) return;
var undoSet = new BlockSet(); var undoSet = new BlockSet();
for (BlockEntry block : blocks) { for (BlockEntry block : blocks) {
if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue; if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue;
if (breakBlock(player, block)) {
if (applyBlockEntry(player, block)) {
undoSet.add(block); undoSet.add(block);
} }
} }
EffortlessBuilding.UNDO_REDO.addUndo(player, undoSet); EffortlessBuilding.UNDO_REDO.addUndo(player, undoSet);
} }
private boolean breakBlock(Player player, BlockEntry block) { public void undoBlockSet(Player player, BlockSet blocks) {
ServerLevel world = (ServerLevel) player.level; if (!isAllowedToUndo(player)) return;
if (!world.isLoaded(block.blockPos) || world.isEmptyBlock(block.blockPos)) return false;
isPlacingOrBreakingBlocks = true; var redoSet = new BlockSet();
boolean brokeBlock = BlockHelper.destroyBlockAs(world, block.blockPos, player, player.getMainHandItem(), 0f, stack -> { for (BlockEntry block : blocks) {
if (!player.isCreative()) { if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue;
ItemHandlerHelper.giveItemToPlayer(player, stack);
if (undoBlockEntry(player, block)) {
redoSet.add(block);
} }
}); }
isPlacingOrBreakingBlocks = false; EffortlessBuilding.UNDO_REDO.addRedo(player, redoSet);
return brokeBlock;
} }
public boolean checkAndNotifyAllowedToUseMod(Player player) { private boolean applyBlockEntry(Player player, BlockEntry block) {
//TODO TEMP block.existingBlockState = player.level.getBlockState(block.blockPos);
boolean breaking = BlockUtilities.isNullOrAir(block.newBlockState);
if (!validateBlockEntry(player, block, breaking)) return false;
boolean success;
isPlacingOrBreakingBlocks = true;
if (breaking) {
success = BlockPlacerHelper.breakBlock(player, block);
} else {
success = BlockPlacerHelper.placeBlock(player, block);
}
isPlacingOrBreakingBlocks = false;
return success;
}
private boolean undoBlockEntry(Player player, BlockEntry block) {
//Update newBlockState for future redo's
block.newBlockState = player.level.getBlockState(block.blockPos);
boolean breaking = BlockUtilities.isNullOrAir(block.existingBlockState);
var tempBlockEntry = new BlockEntry(block.blockPos);
var newBlockState = block.existingBlockState;
tempBlockEntry.existingBlockState = block.newBlockState;
tempBlockEntry.newBlockState = newBlockState;
if (!validateBlockEntry(player, tempBlockEntry, breaking)) return false;
boolean success;
isPlacingOrBreakingBlocks = true;
if (breaking) {
success = BlockPlacerHelper.placeBlock(player, tempBlockEntry);
} else {
success = BlockPlacerHelper.breakBlock(player, tempBlockEntry);
}
isPlacingOrBreakingBlocks = false;
return success;
}
private boolean checkAndNotifyAllowedToUseMod(Player player) {
//TODO temp no survival allowed
if (!player.isCreative()) { if (!player.isCreative()) {
EffortlessBuilding.log(player, ChatFormatting.RED + "Effortless Building is not yet supported in survival mode."); EffortlessBuilding.log(player, ChatFormatting.RED + "Effortless Building is not yet supported in survival mode.");
return false; return false;
} }
if (!player.getAbilities().mayBuild) {
EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to build.");
return false;
}
if (!isAllowedToUseMod(player)) { if (!isAllowedToUseMod(player)) {
EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to use Effortless Building."); EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to use Effortless Building.");
return false; return false;
@@ -105,7 +141,7 @@ public class ServerBlockPlacer {
return true; return true;
} }
public boolean isAllowedToUseMod(Player player) { private boolean isAllowedToUseMod(Player player) {
if (!ServerConfig.validation.allowInSurvival.get() && !player.isCreative()) return false; if (!ServerConfig.validation.allowInSurvival.get() && !player.isCreative()) return false;
if (ServerConfig.validation.useWhitelist.get()) { if (ServerConfig.validation.useWhitelist.get()) {
@@ -115,14 +151,52 @@ public class ServerBlockPlacer {
return true; return true;
} }
public Set<DelayedEntry> getDelayedEntries() { private boolean isAllowedToUndo(Player player) {
return delayedEntriesView; if (!player.isCreative()) {
EffortlessBuilding.log(player, ChatFormatting.RED + "Undo is not supported in survival mode.");
return false;
}
return true;
}
private boolean validateBlockSet(Player player, BlockSet blocks) {
if (blocks.size() > ServerConfig.validation.maxBlocksPlacedAtOnce.get()) {
EffortlessBuilding.log(player, ChatFormatting.RED + "Too many blocks to place. Max: " + ServerConfig.validation.maxBlocksPlacedAtOnce.get());
return false;
}
//Dont allow mixing breaking and placing blocks
//TODO fix if skipping first block
boolean breaking = blocks.getFirstBlockEntry().newBlockState == null || blocks.getFirstBlockEntry().newBlockState.isAir();
for (var iterator = blocks.iterator(); iterator.hasNext(); ) {
var block = iterator.next();
if (block.newBlockState == null || block.newBlockState.isAir()) {
if (!breaking) {
EffortlessBuilding.log(player, ChatFormatting.RED + "Cannot mix breaking and placing blocks.");
return false;
}
} else {
if (breaking) {
EffortlessBuilding.log(player, ChatFormatting.RED + "Cannot mix breaking and placing blocks.");
return false;
}
}
}
return true;
}
private boolean validateBlockEntry(Player player, BlockEntry block, boolean breaking) {
if (!player.level.isLoaded(block.blockPos)) return false;
if (breaking && BlockUtilities.isNullOrAir(block.existingBlockState)) return false;
return true;
} }
public boolean isPlacingOrBreakingBlocks() { public boolean isPlacingOrBreakingBlocks() {
return isPlacingOrBreakingBlocks; return isPlacingOrBreakingBlocks;
} }
public record DelayedEntry(Player player, BlockSet blocks, long placeTime) {}
} }

View File

@@ -31,7 +31,7 @@ public class UndoRedo {
undoStacks.get(player.getUUID()).push(blockSet); undoStacks.get(player.getUUID()).push(blockSet);
} }
private void addRedo(Player player, BlockSet blockSet) { public void addRedo(Player player, BlockSet blockSet) {
if (blockSet.isEmpty()) return; if (blockSet.isEmpty()) return;
//If no stack exists, make one //If no stack exists, make one
@@ -49,10 +49,7 @@ public class UndoRedo {
if (undoStack.isEmpty()) return false; if (undoStack.isEmpty()) return false;
BlockSet blockSet = undoStack.pop(); BlockSet blockSet = undoStack.pop();
// blockSet.undo(player.level); EffortlessBuilding.SERVER_BLOCK_PLACER.undoBlockSet(player, blockSet);
BlockSet redoSet = new BlockSet();
addRedo(player, redoSet);
return true; return true;
} }
@@ -64,8 +61,7 @@ public class UndoRedo {
if (redoStack.isEmpty()) return false; if (redoStack.isEmpty()) return false;
BlockSet blockSet = redoStack.pop(); BlockSet blockSet = redoStack.pop();
// blockSet.redo(player.level); EffortlessBuilding.SERVER_BLOCK_PLACER.applyBlockSet(player, blockSet);
addUndo(player, blockSet);
return true; return true;
} }

View File

@@ -0,0 +1,198 @@
package nl.requios.effortlessbuilding.utilities;
import com.google.common.collect.Lists;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BucketItem;
import net.minecraft.world.item.DiggerItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.items.ItemHandlerHelper;
import nl.requios.effortlessbuilding.create.foundation.utility.BlockHelper;
import java.util.List;
//Server only
public class BlockPlacerHelper {
public static boolean breakBlock(Player player, BlockEntry blockEntry) {
ItemStack usedTool = player.getMainHandItem();
if (usedTool.isEmpty() || !(usedTool.getItem() instanceof DiggerItem)) {
ItemStack offhand = player.getOffhandItem();
if (!offhand.isEmpty() && offhand.getItem() instanceof DiggerItem) {
usedTool = offhand;
}
}
boolean brokeBlock = BlockHelper.destroyBlockAs(player.level, blockEntry.blockPos, player, usedTool, 0f, stack -> {
if (!player.isCreative()) {
ItemHandlerHelper.giveItemToPlayer(player, stack);
}
});
return brokeBlock;
}
public static boolean placeBlock(Player player, BlockEntry blockEntry) {
if (blockEntry.itemStack == null) {
return placeBlockWithoutItem(player, blockEntry);
} else {
var interactionResult = placeItem(player, blockEntry);
interactionResult.shouldSwing()
return interactionResult == InteractionResult.SUCCESS;
}
}
private static boolean placeBlockWithoutItem(Player player, BlockEntry blockEntry) {
Level level = player.level;
level.captureBlockSnapshots = true;
BlockHelper.placeSchematicBlock(level, player, blockEntry.newBlockState, blockEntry.blockPos, blockEntry.itemStack, null);
level.captureBlockSnapshots = false;
//Find out if we get to keep the placed block by sending a forge event
@SuppressWarnings("unchecked")
List<BlockSnapshot> blockSnapshots = (List<BlockSnapshot>)level.capturedBlockSnapshots.clone();
level.capturedBlockSnapshots.clear();
Direction side = Direction.UP;
boolean eventResult = false;
if (blockSnapshots.size() > 1)
{
eventResult = ForgeEventFactory.onMultiBlockPlace(player, blockSnapshots, side);
}
else if (blockSnapshots.size() == 1)
{
eventResult = ForgeEventFactory.onBlockPlace(player, blockSnapshots.get(0), side);
}
if (eventResult)
{
// revert back all captured blocks
for (BlockSnapshot blocksnapshot : Lists.reverse(blockSnapshots))
{
level.restoringBlockSnapshots = true;
blocksnapshot.restore(true, false);
level.restoringBlockSnapshots = false;
}
}
else
{
for (BlockSnapshot snap : blockSnapshots)
{
int updateFlag = snap.getFlag();
BlockState oldBlock = snap.getReplacedBlock();
BlockState newBlock = level.getBlockState(snap.getPos());
newBlock.onPlace(level, snap.getPos(), oldBlock, false);
level.markAndNotifyBlock(snap.getPos(), level.getChunkAt(snap.getPos()), oldBlock, newBlock, updateFlag, 512);
}
}
level.capturedBlockSnapshots.clear();
return !eventResult;
}
//ForgeHooks::onPlaceItemIntoWorld
private static InteractionResult placeItem(Player player, BlockEntry block) {
ItemStack itemstack = block.itemStack;
Level level = player.level;
if (player != null && !player.getAbilities().mayBuild)
return InteractionResult.PASS;
if (itemstack != null && !itemstack.hasAdventureModePlaceTagForBlock(level.registryAccess().registryOrThrow(Registry.BLOCK_REGISTRY), new BlockInWorld(level, block.blockPos, false)))
return InteractionResult.PASS;
// handle all placement events here
Item item = itemstack.getItem();
int size = itemstack.getCount();
CompoundTag nbt = null;
if (itemstack.getTag() != null)
nbt = itemstack.getTag().copy();
if (!(itemstack.getItem() instanceof BucketItem)) // if not bucket
level.captureBlockSnapshots = true;
ItemStack copy = itemstack.copy();
////
BlockHelper.placeSchematicBlock(level, player, block.newBlockState, block.blockPos, block.itemStack, null);
////
InteractionResult ret = InteractionResult.SUCCESS;
if (itemstack.isEmpty())
ForgeEventFactory.onPlayerDestroyItem(player, copy, InteractionHand.MAIN_HAND);
level.captureBlockSnapshots = false;
if (ret.consumesAction())
{
// save new item data
int newSize = itemstack.getCount();
CompoundTag newNBT = null;
if (itemstack.getTag() != null)
{
newNBT = itemstack.getTag().copy();
}
@SuppressWarnings("unchecked")
List<BlockSnapshot> blockSnapshots = (List<BlockSnapshot>)level.capturedBlockSnapshots.clone();
level.capturedBlockSnapshots.clear();
// make sure to set pre-placement item data for event
itemstack.setCount(size);
itemstack.setTag(nbt);
Direction side = Direction.UP;
boolean eventResult = false;
if (blockSnapshots.size() > 1)
{
eventResult = ForgeEventFactory.onMultiBlockPlace(player, blockSnapshots, side);
}
else if (blockSnapshots.size() == 1)
{
eventResult = ForgeEventFactory.onBlockPlace(player, blockSnapshots.get(0), side);
}
if (eventResult)
{
ret = InteractionResult.FAIL; // cancel placement
// revert back all captured blocks
for (BlockSnapshot blocksnapshot : Lists.reverse(blockSnapshots))
{
level.restoringBlockSnapshots = true;
blocksnapshot.restore(true, false);
level.restoringBlockSnapshots = false;
}
}
else
{
// Change the stack to its new content
itemstack.setCount(newSize);
itemstack.setTag(newNBT);
for (BlockSnapshot snap : blockSnapshots)
{
int updateFlag = snap.getFlag();
BlockState oldBlock = snap.getReplacedBlock();
BlockState newBlock = level.getBlockState(snap.getPos());
newBlock.onPlace(level, snap.getPos(), oldBlock, false);
level.markAndNotifyBlock(snap.getPos(), level.getChunkAt(snap.getPos()), oldBlock, newBlock, updateFlag, 512);
}
if (player != null)
player.awardStat(Stats.ITEM_USED.get(item));
}
}
level.capturedBlockSnapshots.clear();
return ret;
}
}

View File

@@ -34,6 +34,10 @@ import java.util.List;
//Common //Common
public class BlockUtilities { public class BlockUtilities {
public static boolean isNullOrAir(BlockState blockState) {
return blockState == null || blockState.isAir();
}
@Deprecated //Use BlockEntry.setItemStackAndFindNewBlockState instead @Deprecated //Use BlockEntry.setItemStackAndFindNewBlockState instead
public static BlockState getBlockState(Player player, InteractionHand hand, ItemStack blockItemStack, BlockEntry blockEntry, Vec3 relativeHitVec, Direction sideHit) { public static BlockState getBlockState(Player player, InteractionHand hand, ItemStack blockItemStack, BlockEntry blockEntry, Vec3 relativeHitVec, Direction sideHit) {
Block block = Block.byItem(blockItemStack.getItem()); Block block = Block.byItem(blockItemStack.getItem());
@@ -60,98 +64,6 @@ public class BlockUtilities {
return result; return result;
} }
//ForgeHooks::onPlaceItemIntoWorld
public static InteractionResult placeBlockEntry(Player player, BlockEntry block) {
ItemStack itemstack = block.itemStack;
Level level = player.level;
if (player != null && !player.getAbilities().mayBuild && !itemstack.hasAdventureModePlaceTagForBlock(level.registryAccess().registryOrThrow(Registry.BLOCK_REGISTRY), new BlockInWorld(level, block.blockPos, false)))
return InteractionResult.PASS;
// handle all placement events here
Item item = itemstack.getItem();
int size = itemstack.getCount();
CompoundTag nbt = null;
if (itemstack.getTag() != null)
nbt = itemstack.getTag().copy();
if (!(itemstack.getItem() instanceof BucketItem)) // if not bucket
level.captureBlockSnapshots = true;
ItemStack copy = itemstack.copy();
////
BlockHelper.placeSchematicBlock(level, player, block.newBlockState, block.blockPos, block.itemStack, null);
////
InteractionResult ret = InteractionResult.SUCCESS;
if (itemstack.isEmpty())
ForgeEventFactory.onPlayerDestroyItem(player, copy, InteractionHand.MAIN_HAND);
level.captureBlockSnapshots = false;
if (ret.consumesAction())
{
// save new item data
int newSize = itemstack.getCount();
CompoundTag newNBT = null;
if (itemstack.getTag() != null)
{
newNBT = itemstack.getTag().copy();
}
@SuppressWarnings("unchecked")
List<BlockSnapshot> blockSnapshots = (List<BlockSnapshot>)level.capturedBlockSnapshots.clone();
level.capturedBlockSnapshots.clear();
// make sure to set pre-placement item data for event
itemstack.setCount(size);
itemstack.setTag(nbt);
Direction side = Direction.UP;
boolean eventResult = false;
if (blockSnapshots.size() > 1)
{
eventResult = ForgeEventFactory.onMultiBlockPlace(player, blockSnapshots, side);
}
else if (blockSnapshots.size() == 1)
{
eventResult = ForgeEventFactory.onBlockPlace(player, blockSnapshots.get(0), side);
}
if (eventResult)
{
ret = InteractionResult.FAIL; // cancel placement
// revert back all captured blocks
for (BlockSnapshot blocksnapshot : Lists.reverse(blockSnapshots))
{
level.restoringBlockSnapshots = true;
blocksnapshot.restore(true, false);
level.restoringBlockSnapshots = false;
}
}
else
{
// Change the stack to its new content
itemstack.setCount(newSize);
itemstack.setTag(newNBT);
for (BlockSnapshot snap : blockSnapshots)
{
int updateFlag = snap.getFlag();
BlockState oldBlock = snap.getReplacedBlock();
BlockState newBlock = level.getBlockState(snap.getPos());
newBlock.onPlace(level, snap.getPos(), oldBlock, false);
level.markAndNotifyBlock(snap.getPos(), level.getChunkAt(snap.getPos()), oldBlock, newBlock, updateFlag, 512);
}
if (player != null)
player.awardStat(Stats.ITEM_USED.get(item));
}
}
level.capturedBlockSnapshots.clear();
return ret;
}
public static void playSoundIfFurtherThanNormal(Player player, BlockEntry blockEntry, boolean breaking) { public static void playSoundIfFurtherThanNormal(Player player, BlockEntry blockEntry, boolean breaking) {
if (Minecraft.getInstance().hitResult != null && Minecraft.getInstance().hitResult.getType() == HitResult.Type.BLOCK) if (Minecraft.getInstance().hitResult != null && Minecraft.getInstance().hitResult.getType() == HitResult.Type.BLOCK)