Skip to content

Commit

Permalink
Fixed crash when placing many connectable blocks
Browse files Browse the repository at this point in the history
(iron bars, glass panes, redstone)

The reason for this crash is that I precomputed the blockstates to be placed (which would be free standing poles when no blocks are present around them).
Placing all blocks at once then causes lots of block updates which may crash the game (especially in MC1.15).
So the solution is to calculate the blockstate directly before the placement.
  • Loading branch information
Theta-Dev committed Mar 14, 2021
1 parent 5b29ecc commit cdba987
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 61 deletions.
48 changes: 0 additions & 48 deletions src/main/java/thetadev/constructionwand/basics/WandUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,54 +235,6 @@ public static boolean entitiesCollidingWithBlock(World world, BlockState blockSt
return false;
}

/**
* Tests if a certain block can be placed by the wand.
* If it can, returns the blockstate to be placed.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
public static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult,
BlockPos pos, BlockItem item,
@Nullable BlockState supportingBlock, @Nullable WandOptions options) {
// Is block at pos replaceable?
BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item);
if(!ctx.canPlace()) return null;

// Can block be placed?
BlockState blockState = item.getBlock().getStateForPlacement(ctx);
if(blockState == null) return null;

// Forbidden Tile Entity?
if(!isTEAllowed(blockState)) return null;

// No entities colliding?
if(entitiesCollidingWithBlock(world, blockState, pos)) return null;

// Adjust blockstate to neighbors
blockState = Block.getValidBlockForPosition(blockState, world, pos);
if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null;

// Copy block properties from supporting block
if(options != null && supportingBlock != null && options.direction.get() == WandOptions.DIRECTION.TARGET) {
// Block properties to be copied (alignment/rotation properties)

for(Property property : new Property[]{
BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP,
BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) {
if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) {
blockState = blockState.with(property, supportingBlock.get(property));
}
}

// Dont dupe double slabs
if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) {
SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE);
if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType);
}
}
return blockState;
}

public static Direction fromVector(Vector3d vector) {
return Direction.getFacingFromVector(vector.x, vector.y, vector.z);
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/thetadev/constructionwand/wand/WandJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public boolean doIt() {
for(ISnapshot snapshot : placeSnapshots) {
if(wand.isEmpty() || wandItem.remainingDurability(wand) == 0) break;

if(snapshot.execute(world, player)) {
if(snapshot.execute(world, player, rayTraceResult)) {
// If the item cant be taken, undo the placement
if(wandSupplier.takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot);
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.World;
import thetadev.constructionwand.basics.WandUtil;

import javax.annotation.Nullable;

public class DestroySnapshot implements ISnapshot
{
public final BlockState block;
public final BlockPos pos;
private final BlockState block;
private final BlockPos pos;

public DestroySnapshot(BlockState block, BlockPos pos) {
this.pos = pos;
Expand Down Expand Up @@ -43,7 +44,7 @@ public ItemStack getRequiredItems() {
}

@Override
public boolean execute(World world, PlayerEntity player) {
public boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult) {
return WandUtil.removeBlock(world, player, block, pos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.World;

public interface ISnapshot
Expand All @@ -14,7 +15,7 @@ public interface ISnapshot

ItemStack getRequiredItems();

boolean execute(World world, PlayerEntity player);
boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult);

boolean canRestore(World world, PlayerEntity player);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
package thetadev.constructionwand.wand.undo;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.Property;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.state.properties.SlabType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.World;
import thetadev.constructionwand.basics.WandUtil;
import thetadev.constructionwand.basics.option.WandOptions;
import thetadev.constructionwand.wand.WandItemUseContext;

import javax.annotation.Nullable;

public class PlaceSnapshot implements ISnapshot
{
public final BlockState block;
public final BlockPos pos;
public final BlockItem item;
private BlockState block;
private final BlockPos pos;
private final BlockItem item;
private final BlockState supportingBlock;
private final boolean targetMode;

public PlaceSnapshot(BlockState block, BlockPos pos, BlockItem item) {
public PlaceSnapshot(BlockState block, BlockPos pos, BlockItem item, BlockState supportingBlock, boolean targetMode) {
this.block = block;
this.pos = pos;
this.item = item;
this.supportingBlock = supportingBlock;
this.targetMode = targetMode;
}

@Nullable
public static PlaceSnapshot get(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult,
BlockPos pos, BlockItem item,
@Nullable BlockState supportingBlock, @Nullable WandOptions options) {
BlockState blockState = WandUtil.getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, options);
boolean targetMode = options != null && supportingBlock != null && options.direction.get() == WandOptions.DIRECTION.TARGET;
BlockState blockState = getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, targetMode);
if(blockState == null) return null;
return new PlaceSnapshot(blockState, pos, item);

return new PlaceSnapshot(blockState, pos, item, supportingBlock, targetMode);
}

@Override
Expand All @@ -49,7 +61,12 @@ public ItemStack getRequiredItems() {
}

@Override
public boolean execute(World world, PlayerEntity player) {
public boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult) {
// Recalculate PlaceBlockState, because other blocks might be placed nearby
// Not doing this may cause game crashes (StackOverflowException) when placing lots of blocks
// with changing orientation like panes, iron bars or redstone.
block = getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, targetMode);
if(block == null) return false;
return WandUtil.placeBlock(world, player, block, pos, item);
}

Expand All @@ -67,4 +84,52 @@ public boolean restore(World world, PlayerEntity player) {
public void forceRestore(World world) {
world.removeBlock(pos, false);
}

/**
* Tests if a certain block can be placed by the wand.
* If it can, returns the blockstate to be placed.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
private static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult,
BlockPos pos, BlockItem item,
@Nullable BlockState supportingBlock, boolean targetMode) {
// Is block at pos replaceable?
BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item);
if(!ctx.canPlace()) return null;

// Can block be placed?
BlockState blockState = item.getBlock().getStateForPlacement(ctx);
if(blockState == null) return null;

// Forbidden Tile Entity?
if(!WandUtil.isTEAllowed(blockState)) return null;

// No entities colliding?
if(WandUtil.entitiesCollidingWithBlock(world, blockState, pos)) return null;

// Adjust blockstate to neighbors
blockState = Block.getValidBlockForPosition(blockState, world, pos);
if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null;

// Copy block properties from supporting block
if(targetMode) {
// Block properties to be copied (alignment/rotation properties)

for(Property property : new Property[]{
BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP,
BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) {
if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) {
blockState = blockState.with(property, supportingBlock.get(property));
}
}

// Dont dupe double slabs
if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) {
SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE);
if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType);
}
}
return blockState;
}
}

0 comments on commit cdba987

Please sign in to comment.