Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

accurate placement protocol V3 support (For Crafter Rotations) #323

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions src/main/java/carpetextra/CarpetExtraSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public Integer validate(ServerCommandSource source, CarpetRule<Integer> currentR
)
public static boolean accurateBlockPlacement = false;

@Rule(
categories = {EXTRA, SURVIVAL}
)
public static boolean accurateBlockPlacementLegacy = false;

@Rule(
categories = {EXTRA, EXPERIMENTAL, FEATURE, DISPENSER}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ public class BlockItemMixin_accurateBlockPlacement
))
private BlockState getAlternatePlacement(Block block, ItemPlacementContext context)
{
if (CarpetExtraSettings.accurateBlockPlacement)
if (CarpetExtraSettings.accurateBlockPlacement || CarpetExtraSettings.accurateBlockPlacementLegacy)
{
BlockState tryAlternative = BlockPlacer.alternativeBlockPlacement(block, context);
BlockState tryAlternative = BlockPlacer.applyAlternativeBlockPlacement(block.getPlacementState(context), BlockPlacer.UseContext.from(context, context.getHand()));
if (tryAlternative != null)
return tryAlternative;
}
return block.getPlacementState(context);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public abstract class ServerPlayNetworkHandlerMixin
require = 0)
private Vec3d carpetextra_removeHitPosCheck(Vec3d hitVec, Vec3d blockCenter)
{
if (CarpetExtraSettings.accurateBlockPlacement)
if (CarpetExtraSettings.accurateBlockPlacement || CarpetExtraSettings.accurateBlockPlacementLegacy)
{
return Vec3d.ZERO;
}
Expand Down
289 changes: 269 additions & 20 deletions src/main/java/carpetextra/utils/BlockPlacer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package carpetextra.utils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.ImmutableSet;
import carpetextra.CarpetExtraSettings;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
Expand All @@ -9,34 +15,174 @@
import net.minecraft.block.enums.BlockHalf;
import net.minecraft.block.enums.ComparatorMode;
import net.minecraft.block.enums.SlabType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.state.property.Property;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

public class BlockPlacer
{
public static BlockState alternativeBlockPlacement(Block block, ItemPlacementContext context)
public static final ImmutableSet<Property<?>> WHITELISTED_PROPERTIES = ImmutableSet.of(
Properties.INVERTED,
Properties.OPEN,
Properties.POWERED,
Properties.LOCKED,
Properties.ATTACHMENT,
Properties.AXIS,
Properties.BLOCK_HALF,
Properties.BLOCK_FACE,
Properties.CHEST_TYPE,
Properties.COMPARATOR_MODE,
Properties.DOOR_HINGE,
Properties.FACING,
Properties.HOPPER_FACING,
Properties.HORIZONTAL_FACING,
Properties.ORIENTATION,
Properties.RAIL_SHAPE,
Properties.STRAIGHT_RAIL_SHAPE,
Properties.SLAB_TYPE,
Properties.STAIR_SHAPE,
Properties.BITES,
Properties.DELAY,
Properties.NOTE,
Properties.ROTATION
);

public static BlockState applyAlternativeBlockPlacement(BlockState state, UseContext context)
{
if (CarpetExtraSettings.accurateBlockPlacement)
{
return alternativeBlockPlacementV3(state, context);
}
else if (CarpetExtraSettings.accurateBlockPlacementLegacy)
{
return alternativeBlockPlacementV2(state.getBlock(), context);
}
else
{
return state;
}
}

private static <T extends Comparable<T>> BlockState alternativeBlockPlacementV3(BlockState state, UseContext context)
{
Vec3d hitPos = context.getHitPos();
BlockPos blockPos = context.getBlockPos();
int protocolValue = (int) (context.getHitVec().x - (double) context.getPos().getX()) - 2;
BlockState oldState = state;

if (protocolValue < 0)
{
return oldState;
}

Optional<EnumProperty<Direction>> property = getFirstDirectionProperty(state);

if (property.isPresent() && property.get() != Properties.VERTICAL_DIRECTION)
{
state = applyDirectionProperty(state, context, property.get(), protocolValue);

if (state == null)
{
return null;
}

if (state.canPlaceAt(context.getWorld(), context.getPos()))
{
oldState = state;
}
else
{
state = oldState;
}

// Consume the bits used for the facing
protocolValue >>>= 3;
}
// Consume the lowest unused bit
protocolValue >>>= 1;

List<Property<?>> propList = new ArrayList<>(state.getBlock().getStateManager().getProperties());
propList.sort(Comparator.comparing(Property::getName));

try
{
for (Property<?> p : propList)
{
if (property.isPresent() && !property.get().equals(p) &&
WHITELISTED_PROPERTIES.contains(p))
{
@SuppressWarnings("unchecked")
Property<T> prop = (Property<T>) p;
List<T> list = new ArrayList<>(prop.getValues());
list.sort(Comparable::compareTo);

int requiredBits = MathHelper.floorLog2(MathHelper.smallestEncompassingPowerOfTwo(list.size()));
int bitMask = ~(0xFFFFFFFF << requiredBits);
int valueIndex = protocolValue & bitMask;

if (valueIndex >= 0 && valueIndex < list.size())
{
T value = list.get(valueIndex);

if (state.get(prop).equals(value) == false &&
value != SlabType.DOUBLE) // don't allow duping slabs by forcing a double slab via the protocol
{
state = state.with(prop, value);

if (state.canPlaceAt(context.getWorld(), context.getPos()))
{
oldState = state;
}
else
{
state = oldState;
}
}

protocolValue >>>= requiredBits;
}
}
}
}
catch (Exception e)
{
// Exception
}

if (state.canPlaceAt(context.getWorld(), context.getPos()))
{
return state;
}
else
{
return null;
}
}

private static BlockState alternativeBlockPlacementV2(Block block, UseContext context)
{
Vec3d hitPos = context.getHitVec();
BlockPos blockPos = context.getPos();
double relativeHitX = hitPos.x - blockPos.getX();
BlockState state = block.getPlacementState(context);
BlockState state = block.getPlacementState(context.getItemPlacementContext());

if (relativeHitX < 2 || state == null) // vanilla handling
return null;

// It would be nice if relativeHitX was adjusted in context to original range from 0.0 to 1.0,
// since some blocks are actually using it.
EnumProperty<Direction> directionProp = getFirstDirectionProperty(state);
Optional<EnumProperty<Direction>> directionProp = getFirstDirectionProperty(state);
int protocolValue = ((int) relativeHitX - 2) / 2;

if (directionProp != null)
if (directionProp.isPresent())
{
Direction origFacing = state.get(directionProp);
Direction origFacing = state.get(directionProp.get());
Direction facing = origFacing;
int facingIndex = protocolValue & 0xF;

Expand All @@ -49,24 +195,24 @@ else if (facingIndex >= 0 && facingIndex <= 5)
facing = Direction.byId(facingIndex);
}

if (directionProp.getValues().contains(facing) == false)
if (directionProp.get().getValues().contains(facing) == false)
{
facing = context.getPlayer().getHorizontalFacing().getOpposite();
facing = context.getEntity().getHorizontalFacing().getOpposite();
}

if (facing != origFacing && directionProp.getValues().contains(facing))
if (facing != origFacing && directionProp.get().getValues().contains(facing))
{
if (state.getBlock() instanceof BedBlock)
{
BlockPos headPos = blockPos.offset(facing);

if (context.getWorld().getBlockState(headPos).canReplace(context) == false)
if (context.getWorld().getBlockState(headPos).canReplace(context.getItemPlacementContext()) == false)
{
return null;
}
}

state = state.with(directionProp, facing);
state = state.with(directionProp.get(), facing);
}
}
else if (state.contains(Properties.AXIS))
Expand Down Expand Up @@ -110,21 +256,124 @@ else if (state.contains(Properties.SLAB_TYPE) &&
return state;
}

@SuppressWarnings("unchecked")
@Nullable
public static EnumProperty<Direction> getFirstDirectionProperty(BlockState state)
private static BlockState applyDirectionProperty(BlockState state, UseContext context,
EnumProperty<Direction> property, int protocolValue)
{
for (Property<?> prop : state.getProperties())
Direction facingOrig = state.get(property);
Direction facing = facingOrig;
int decodedFacingIndex = (protocolValue & 0xF) >> 1;

if (decodedFacingIndex == 6) // the opposite of the normal facing requested
{
facing = facing.getOpposite();
}
else if (decodedFacingIndex >= 0 && decodedFacingIndex <= 5)
{
facing = Direction.byId(decodedFacingIndex);

if (property.getValues().contains(facing) == false)
{
facing = context.getEntity().getHorizontalFacing().getOpposite();
}
}

if (facing != facingOrig && property.getValues().contains(facing))
{
if (prop instanceof EnumProperty<?> enumProperty)
if (state.getBlock() instanceof BedBlock)
{
if (enumProperty.getType().equals(Direction.class))
BlockPos headPos = context.getPos().offset(facing);
ItemPlacementContext ctx = context.getItemPlacementContext();

if (context.getWorld().getBlockState(headPos).canReplace(ctx) == false)
{
return (EnumProperty<Direction>) enumProperty;
return null;
}
}

state = state.with(property, facing);
}

return null;
return state;
}

@SuppressWarnings("unchecked")
public static Optional<EnumProperty<Direction>> getFirstDirectionProperty(BlockState state)
{
for (Property<?> prop : state.getProperties())
{
if (prop instanceof EnumProperty<?> ep && ep.getType().equals(Direction.class))
{
return Optional.of((EnumProperty<Direction>) ep);
}
}

return Optional.empty();
}

public static class UseContext
{
private final World world;
private final BlockPos pos;
private final Direction side;
private final Vec3d hitVec;
private final LivingEntity entity;
private final Hand hand;
@Nullable
private final ItemPlacementContext itemPlacementContext;

private UseContext(World world, BlockPos pos, Direction side, Vec3d hitVec,
LivingEntity entity, Hand hand, @Nullable ItemPlacementContext itemPlacementContext)
{
this.world = world;
this.pos = pos;
this.side = side;
this.hitVec = hitVec;
this.entity = entity;
this.hand = hand;
this.itemPlacementContext = itemPlacementContext;
}

public static UseContext from(ItemPlacementContext ctx, Hand hand)
{
Vec3d pos = ctx.getHitPos();
return new UseContext(ctx.getWorld(), ctx.getBlockPos(), ctx.getSide(), new Vec3d(pos.x, pos.y, pos.z),
ctx.getPlayer(), hand, ctx);
}

public World getWorld()
{
return this.world;
}

public BlockPos getPos()
{
return this.pos;
}

public Direction getSide()
{
return this.side;
}

public Vec3d getHitVec()
{
return this.hitVec;
}

public LivingEntity getEntity()
{
return this.entity;
}

public Hand getHand()
{
return this.hand;
}

@Nullable
public ItemPlacementContext getItemPlacementContext()
{
return this.itemPlacementContext;
}
}
}
Loading
Loading