2021-11-23 18:50:33 +11:00

597 lines
20 KiB
Java

package org.bukkit.craftbukkit.block;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import net.minecraft.core.BlockPosition;
import net.minecraft.core.EnumDirection;
import net.minecraft.core.IRegistry;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.EnumHand;
import net.minecraft.world.EnumInteractionResult;
import net.minecraft.world.item.ItemBoneMeal;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.ItemActionContext;
import net.minecraft.world.level.EnumSkyBlock;
import net.minecraft.world.level.GeneratorAccess;
import net.minecraft.world.level.RayTrace;
import net.minecraft.world.level.biome.BiomeBase;
import net.minecraft.world.level.block.BlockRedstoneWire;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.phys.AxisAlignedBB;
import net.minecraft.world.phys.MovingObjectPosition;
import net.minecraft.world.phys.MovingObjectPositionBlock;
import net.minecraft.world.phys.Vec3D;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.apache.commons.lang.Validate;
import org.bukkit.Chunk;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.CraftFluidCollisionMode;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.CraftRayTraceResult;
import org.bukkit.craftbukkit.util.CraftVoxelShape;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.BlockVector;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
public class CraftBlock implements Block {
private final net.minecraft.world.level.GeneratorAccess world;
private final BlockPosition position;
public CraftBlock(GeneratorAccess world, BlockPosition position) {
this.world = world;
this.position = position.immutable();
}
public static CraftBlock at(GeneratorAccess world, BlockPosition position) {
return new CraftBlock(world, position);
}
public net.minecraft.world.level.block.state.IBlockData getNMS() {
return world.getBlockState(position);
}
public BlockPosition getPosition() {
return position;
}
public GeneratorAccess getHandle() {
return world;
}
@Override
public World getWorld() {
return world.getMinecraftWorld().getWorld();
}
public CraftWorld getCraftWorld() {
return (CraftWorld) getWorld();
}
@Override
public Location getLocation() {
return new Location(getWorld(), position.getX(), position.getY(), position.getZ());
}
@Override
public Location getLocation(Location loc) {
if (loc != null) {
loc.setWorld(getWorld());
loc.setX(position.getX());
loc.setY(position.getY());
loc.setZ(position.getZ());
loc.setYaw(0);
loc.setPitch(0);
}
return loc;
}
public BlockVector getVector() {
return new BlockVector(getX(), getY(), getZ());
}
@Override
public int getX() {
return position.getX();
}
@Override
public int getY() {
return position.getY();
}
@Override
public int getZ() {
return position.getZ();
}
@Override
public Chunk getChunk() {
return getWorld().getChunkAt(this);
}
public void setData(final byte data) {
setData(data, 3);
}
public void setData(final byte data, boolean applyPhysics) {
if (applyPhysics) {
setData(data, 3);
} else {
setData(data, 2);
}
}
private void setData(final byte data, int flag) {
world.setBlock(position, CraftMagicNumbers.getBlock(getType(), data), flag);
}
@Override
public byte getData() {
IBlockData blockData = world.getBlockState(position);
return CraftMagicNumbers.toLegacyData(blockData);
}
@Override
public BlockData getBlockData() {
return CraftBlockData.fromData(getNMS());
}
@Override
public void setType(final Material type) {
setType(type, true);
}
@Override
public void setType(Material type, boolean applyPhysics) {
Preconditions.checkArgument(type != null, "Material cannot be null");
setBlockData(type.createBlockData(), applyPhysics);
}
@Override
public void setBlockData(BlockData data) {
setBlockData(data, true);
}
@Override
public void setBlockData(BlockData data, boolean applyPhysics) {
Preconditions.checkArgument(data != null, "BlockData cannot be null");
setTypeAndData(((CraftBlockData) data).getState(), applyPhysics);
}
public boolean setTypeAndData(final IBlockData blockData, final boolean applyPhysics) {
IBlockData old = getNMS();
// SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup
if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes
// SPIGOT-4612: faster - just clear tile
if (world instanceof net.minecraft.world.level.World) {
((net.minecraft.world.level.World) world).removeBlockEntity(position);
} else {
world.setBlock(position, Blocks.AIR.defaultBlockState(), 0);
}
}
if (applyPhysics) {
return world.setBlock(position, blockData, 3);
} else {
boolean success = world.setBlock(position, blockData, 2 | 16 | 1024); // NOTIFY | NO_OBSERVER | NO_PLACE (custom)
if (success && world instanceof net.minecraft.world.level.World) {
world.getMinecraftWorld().sendBlockUpdated(
position,
old,
blockData,
3
);
}
return success;
}
}
@Override
public Material getType() {
return CraftMagicNumbers.getMaterial(world.getBlockState(position).getBlock());
}
@Override
public byte getLightLevel() {
return (byte) world.getMinecraftWorld().getMaxLocalRawBrightness(position);
}
@Override
public byte getLightFromSky() {
return (byte) world.getBrightness(EnumSkyBlock.SKY, position);
}
@Override
public byte getLightFromBlocks() {
return (byte) world.getBrightness(EnumSkyBlock.BLOCK, position);
}
public Block getFace(final BlockFace face) {
return getRelative(face, 1);
}
public Block getFace(final BlockFace face, final int distance) {
return getRelative(face, distance);
}
@Override
public Block getRelative(final int modX, final int modY, final int modZ) {
return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ);
}
@Override
public Block getRelative(BlockFace face) {
return getRelative(face, 1);
}
@Override
public Block getRelative(BlockFace face, int distance) {
return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance);
}
@Override
public BlockFace getFace(final Block block) {
BlockFace[] values = BlockFace.values();
for (BlockFace face : values) {
if ((this.getX() + face.getModX() == block.getX()) && (this.getY() + face.getModY() == block.getY()) && (this.getZ() + face.getModZ() == block.getZ())) {
return face;
}
}
return null;
}
@Override
public String toString() {
return "CraftBlock{pos=" + position + ",type=" + getType() + ",data=" + getNMS() + ",fluid=" + world.getFluidState(position) + '}';
}
public static BlockFace notchToBlockFace(EnumDirection notch) {
if (notch == null) {
return BlockFace.SELF;
}
switch (notch) {
case DOWN:
return BlockFace.DOWN;
case UP:
return BlockFace.UP;
case NORTH:
return BlockFace.NORTH;
case SOUTH:
return BlockFace.SOUTH;
case WEST:
return BlockFace.WEST;
case EAST:
return BlockFace.EAST;
default:
return BlockFace.SELF;
}
}
public static EnumDirection blockFaceToNotch(BlockFace face) {
switch (face) {
case DOWN:
return EnumDirection.DOWN;
case UP:
return EnumDirection.UP;
case NORTH:
return EnumDirection.NORTH;
case SOUTH:
return EnumDirection.SOUTH;
case WEST:
return EnumDirection.WEST;
case EAST:
return EnumDirection.EAST;
default:
return null;
}
}
@Override
public BlockState getState() {
return CraftBlockStates.getBlockState(this);
}
@Override
public Biome getBiome() {
return getWorld().getBiome(getX(), getY(), getZ());
}
@Override
public void setBiome(Biome bio) {
getWorld().setBiome(getX(), getY(), getZ(), bio);
}
public static Biome biomeBaseToBiome(IRegistry<BiomeBase> registry, BiomeBase base) {
if (base == null) {
return null;
}
Biome biome = Registry.BIOME.get(CraftNamespacedKey.fromMinecraft(registry.getKey(base)));
return (biome == null) ? Biome.CUSTOM : biome;
}
public static BiomeBase biomeToBiomeBase(IRegistry<BiomeBase> registry, Biome bio) {
if (bio == null || bio == Biome.CUSTOM) {
return null;
}
return registry.get(CraftNamespacedKey.toMinecraft(bio.getKey()));
}
@Override
public double getTemperature() {
return world.getBiome(position).getTemperature(position);
}
@Override
public double getHumidity() {
return getWorld().getHumidity(getX(), getY(), getZ());
}
@Override
public boolean isBlockPowered() {
return world.getMinecraftWorld().getDirectSignalTo(position) > 0;
}
@Override
public boolean isBlockIndirectlyPowered() {
return world.getMinecraftWorld().hasNeighborSignal(position);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof CraftBlock)) {
return false;
}
CraftBlock other = (CraftBlock) o;
return this.position.equals(other.position) && this.getWorld().equals(other.getWorld());
}
@Override
public int hashCode() {
return this.position.hashCode() ^ this.getWorld().hashCode();
}
@Override
public boolean isBlockFacePowered(BlockFace face) {
return world.getMinecraftWorld().hasSignal(position, blockFaceToNotch(face));
}
@Override
public boolean isBlockFaceIndirectlyPowered(BlockFace face) {
int power = world.getMinecraftWorld().getSignal(position, blockFaceToNotch(face));
Block relative = getRelative(face);
if (relative.getType() == Material.REDSTONE_WIRE) {
return Math.max(power, relative.getData()) > 0;
}
return power > 0;
}
@Override
public int getBlockPower(BlockFace face) {
int power = 0;
net.minecraft.world.level.World world = this.world.getMinecraftWorld();
int x = getX();
int y = getY();
int z = getZ();
if ((face == BlockFace.DOWN || face == BlockFace.SELF) && world.hasSignal(new BlockPosition(x, y - 1, z), EnumDirection.DOWN)) power = getPower(power, world.getBlockState(new BlockPosition(x, y - 1, z)));
if ((face == BlockFace.UP || face == BlockFace.SELF) && world.hasSignal(new BlockPosition(x, y + 1, z), EnumDirection.UP)) power = getPower(power, world.getBlockState(new BlockPosition(x, y + 1, z)));
if ((face == BlockFace.EAST || face == BlockFace.SELF) && world.hasSignal(new BlockPosition(x + 1, y, z), EnumDirection.EAST)) power = getPower(power, world.getBlockState(new BlockPosition(x + 1, y, z)));
if ((face == BlockFace.WEST || face == BlockFace.SELF) && world.hasSignal(new BlockPosition(x - 1, y, z), EnumDirection.WEST)) power = getPower(power, world.getBlockState(new BlockPosition(x - 1, y, z)));
if ((face == BlockFace.NORTH || face == BlockFace.SELF) && world.hasSignal(new BlockPosition(x, y, z - 1), EnumDirection.NORTH)) power = getPower(power, world.getBlockState(new BlockPosition(x, y, z - 1)));
if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && world.hasSignal(new BlockPosition(x, y, z + 1), EnumDirection.SOUTH)) power = getPower(power, world.getBlockState(new BlockPosition(x, y, z + 1)));
return power > 0 ? power : (face == BlockFace.SELF ? isBlockIndirectlyPowered() : isBlockFaceIndirectlyPowered(face)) ? 15 : 0;
}
private static int getPower(int i, IBlockData iblockdata) {
if (!iblockdata.is(Blocks.REDSTONE_WIRE)) {
return i;
} else {
int j = iblockdata.getValue(BlockRedstoneWire.POWER);
return j > i ? j : i;
}
}
@Override
public int getBlockPower() {
return getBlockPower(BlockFace.SELF);
}
@Override
public boolean isEmpty() {
return getNMS().isAir();
}
@Override
public boolean isLiquid() {
return getNMS().getMaterial().isLiquid();
}
@Override
public PistonMoveReaction getPistonMoveReaction() {
return PistonMoveReaction.getById(getNMS().getPistonPushReaction().ordinal());
}
@Override
public boolean breakNaturally() {
return breakNaturally(null);
}
@Override
public boolean breakNaturally(ItemStack item) {
// Order matters here, need to drop before setting to air so skulls can get their data
net.minecraft.world.level.block.state.IBlockData iblockdata = this.getNMS();
net.minecraft.world.level.block.Block block = iblockdata.getBlock();
net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
boolean result = false;
// Modelled off EntityHuman#hasBlock
if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) {
net.minecraft.world.level.block.Block.dropResources(iblockdata, world.getMinecraftWorld(), position, world.getBlockEntity(position), null, nmsItem);
result = true;
}
return setTypeAndData(Blocks.AIR.defaultBlockState(), true) && result;
}
@Override
public boolean applyBoneMeal(BlockFace face) {
EnumDirection direction = blockFaceToNotch(face);
ItemActionContext context = new ItemActionContext(getCraftWorld().getHandle(), null, EnumHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new MovingObjectPositionBlock(Vec3D.ZERO, direction, getPosition(), false));
return ItemBoneMeal.applyBonemeal(context) == EnumInteractionResult.SUCCESS;
}
@Override
public Collection<ItemStack> getDrops() {
return getDrops(null);
}
@Override
public Collection<ItemStack> getDrops(ItemStack item) {
return getDrops(item, null);
}
@Override
public Collection<ItemStack> getDrops(ItemStack item, Entity entity) {
IBlockData iblockdata = getNMS();
net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item);
// Modelled off EntityHuman#hasBlock
if (item == null || isPreferredTool(iblockdata, nms)) {
return net.minecraft.world.level.block.Block.getDrops(iblockdata, (WorldServer) world.getMinecraftWorld(), position, world.getBlockEntity(position), entity == null ? null : ((CraftEntity) entity).getHandle(), nms)
.stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public boolean isPreferredTool(ItemStack item) {
IBlockData iblockdata = getNMS();
net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item);
return isPreferredTool(iblockdata, nms);
}
@Override
public float getBreakSpeed(Player player) {
Preconditions.checkArgument(player != null, "player cannot be null");
return getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), world, position);
}
private boolean isPreferredTool(IBlockData iblockdata, net.minecraft.world.item.ItemStack nmsItem) {
return !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata);
}
@Override
public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
getCraftWorld().getBlockMetadata().setMetadata(this, metadataKey, newMetadataValue);
}
@Override
public List<MetadataValue> getMetadata(String metadataKey) {
return getCraftWorld().getBlockMetadata().getMetadata(this, metadataKey);
}
@Override
public boolean hasMetadata(String metadataKey) {
return getCraftWorld().getBlockMetadata().hasMetadata(this, metadataKey);
}
@Override
public void removeMetadata(String metadataKey, Plugin owningPlugin) {
getCraftWorld().getBlockMetadata().removeMetadata(this, metadataKey, owningPlugin);
}
@Override
public boolean isPassable() {
return this.getNMS().getCollisionShape(world, position).isEmpty();
}
@Override
public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) {
Validate.notNull(start, "Start location is null!");
Validate.isTrue(this.getWorld().equals(start.getWorld()), "Start location is from different world!");
start.checkFinite();
Validate.notNull(direction, "Direction is null!");
direction.checkFinite();
Validate.isTrue(direction.lengthSquared() > 0, "Direction's magnitude is 0!");
Validate.notNull(fluidCollisionMode, "Fluid collision mode is null!");
if (maxDistance < 0.0D) {
return null;
}
Vector dir = direction.clone().normalize().multiply(maxDistance);
Vec3D startPos = new Vec3D(start.getX(), start.getY(), start.getZ());
Vec3D endPos = new Vec3D(start.getX() + dir.getX(), start.getY() + dir.getY(), start.getZ() + dir.getZ());
MovingObjectPosition nmsHitResult = world.clip(new RayTrace(startPos, endPos, RayTrace.BlockCollisionOption.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), null), position);
return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult);
}
@Override
public BoundingBox getBoundingBox() {
VoxelShape shape = getNMS().getShape(world, position);
if (shape.isEmpty()) {
return new BoundingBox(); // Return an empty bounding box if the block has no dimension
}
AxisAlignedBB aabb = shape.bounds();
return new BoundingBox(getX() + aabb.minX, getY() + aabb.minY, getZ() + aabb.minZ, getX() + aabb.maxX, getY() + aabb.maxY, getZ() + aabb.maxZ);
}
@Override
public org.bukkit.util.VoxelShape getCollisionShape() {
VoxelShape shape = getNMS().getCollisionShape(world, position);
return new CraftVoxelShape(shape);
}
@Override
public boolean canPlace(BlockData data) {
Preconditions.checkArgument(data != null, "Provided block data is null!");
net.minecraft.world.level.block.state.IBlockData iblockdata = ((CraftBlockData) data).getState();
net.minecraft.world.level.World world = this.world.getMinecraftWorld();
return iblockdata.canSurvive(world, this.position);
}
}