All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.block.BlockRail Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.block;

import cn.nukkit.Player;
import cn.nukkit.api.DeprecationDetails;
import cn.nukkit.api.PowerNukkitDifference;
import cn.nukkit.api.PowerNukkitOnly;
import cn.nukkit.api.Since;
import cn.nukkit.blockproperty.ArrayBlockProperty;
import cn.nukkit.blockproperty.BlockProperties;
import cn.nukkit.blockproperty.BlockProperty;
import cn.nukkit.blockproperty.BooleanBlockProperty;
import cn.nukkit.item.Item;
import cn.nukkit.item.ItemBlock;
import cn.nukkit.item.ItemTool;
import cn.nukkit.level.Level;
import cn.nukkit.math.AxisAlignedBB;
import cn.nukkit.math.BlockFace;
import cn.nukkit.math.Vector3;
import cn.nukkit.utils.BlockColor;
import cn.nukkit.utils.Faceable;
import cn.nukkit.utils.OptionalBoolean;
import cn.nukkit.utils.Rail;
import cn.nukkit.utils.Rail.Orientation;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.nukkit.math.BlockFace.*;
import static cn.nukkit.utils.Rail.Orientation.*;

/**
 * @author Snake1999
 * @since 2016/1/11
 */
public class BlockRail extends BlockFlowable implements Faceable {
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final BooleanBlockProperty ACTIVE = new BooleanBlockProperty("rail_data_bit", false);

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final BlockProperty UNCURVED_RAIL_DIRECTION = new ArrayBlockProperty<>("rail_direction", false, new Rail.Orientation[]{
            STRAIGHT_NORTH_SOUTH, STRAIGHT_EAST_WEST,
            ASCENDING_EAST, ASCENDING_WEST,
            ASCENDING_NORTH, ASCENDING_SOUTH
    }).ordinal(true);

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final BlockProperty CURVED_RAIL_DIRECTION = new ArrayBlockProperty<>("rail_direction", false, new Rail.Orientation[]{
            STRAIGHT_NORTH_SOUTH, STRAIGHT_EAST_WEST,
            ASCENDING_EAST, ASCENDING_WEST,
            ASCENDING_NORTH, ASCENDING_SOUTH,
            CURVED_SOUTH_EAST, CURVED_SOUTH_WEST,
            CURVED_NORTH_WEST, CURVED_NORTH_EAST
    }).ordinal(true);

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final BlockProperties ACTIVABLE_PROPERTIES = new BlockProperties(UNCURVED_RAIL_DIRECTION, ACTIVE);

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final BlockProperties PROPERTIES = new BlockProperties(CURVED_RAIL_DIRECTION);

    // 0x8: Set the block active
    // 0x7: Reset the block to normal
    // If the rail can be powered. So its a complex rail!
    protected boolean canBePowered = false;

    public BlockRail() {
        this(0);
    }

    public BlockRail(int meta) {
        super(meta);
    }

    @Override
    public String getName() {
        return "Rail";
    }

    @Override
    public int getId() {
        return RAIL;
    }

    @Since("1.4.0.0-PN")
    @PowerNukkitOnly
    @Nonnull
    @Override
    public BlockProperties getProperties() {
        return PROPERTIES;
    }

    @Override
    public double getHardness() {
        return 0.7;
    }

    @Override
    public double getResistance() {
        return 3.5;
    }

    @Override
    public boolean canPassThrough() {
        return true;
    }

    @Override
    public int getToolType() {
        return ItemTool.TYPE_PICKAXE;
    }

    @Override
    public boolean sticksToPiston() {
        return true;
    }

    @Override
    public int onUpdate(int type) {
        if (type == Level.BLOCK_UPDATE_NORMAL) {
            Optional ascendingDirection = this.getOrientation().ascendingDirection();
            if (!checkCanBePlace(this.down()) || (ascendingDirection.isPresent() && !checkCanBePlace(this.getSide(ascendingDirection.get())))) {
                this.getLevel().useBreakOn(this);
                return Level.BLOCK_UPDATE_NORMAL;
            }
        }
        if (type == Level.BLOCK_UPDATE_REDSTONE && this.getRailDirection().isCurved()) {
            var connect = checkRailsConnected().values();
            List railFace = new ArrayList<>();
            for (BlockFace face : connect) {
                if (this.getSide(face.getOpposite()).getId() == BlockID.RAIL) {
                    railFace.add(face.getOpposite());
                } else {
                    railFace.add(face);
                }
            }
            Orientation orient;
            if (railFace.contains(SOUTH)) {
                if (railFace.contains(EAST)) {
                    orient = CURVED_SOUTH_EAST;
                } else orient = CURVED_SOUTH_WEST;
            } else {
                if (railFace.contains(EAST)) {
                    orient = CURVED_NORTH_EAST;
                } else orient = CURVED_NORTH_WEST;
            }
            setOrientation(orient);
        }
        return 0;
    }

    @Override
    public double getMaxY() {
        return this.y + 0.125;
    }

    @Override
    public AxisAlignedBB recalculateBoundingBox() {
        return this;
    }

    @Override
    public BlockColor getColor() {
        return BlockColor.AIR_BLOCK_COLOR;
    }

    //Information from http://minecraft.gamepedia.com/Rail
    @Override
    public boolean place(@Nonnull Item item, @Nonnull Block block, @Nonnull Block target, @Nonnull BlockFace face, double fx, double fy, double fz, Player player) {
        Block down = this.down();
        if (!checkCanBePlace(down)) {
            return false;
        }
        Map railsAround = this.checkRailsAroundAffected();
        List rails = new ArrayList<>(railsAround.keySet());
        List faces = new ArrayList<>(railsAround.values());
        if (railsAround.size() == 1) {
            BlockRail other = rails.get(0);
            this.setRailDirection(this.connect(other, railsAround.get(other)));
        } else if (railsAround.size() == 4) {
            if (this.isAbstract()) {
                this.setRailDirection(this.connect(rails.get(faces.indexOf(SOUTH)), SOUTH, rails.get(faces.indexOf(EAST)), EAST));
            } else {
                this.setRailDirection(this.connect(rails.get(faces.indexOf(EAST)), EAST, rails.get(faces.indexOf(WEST)), WEST));
            }
        } else if (!railsAround.isEmpty()) {
            if (this.isAbstract()) {
                if (railsAround.size() == 2) {
                    BlockRail rail1 = rails.get(0);
                    BlockRail rail2 = rails.get(1);
                    this.setRailDirection(this.connect(rail1, railsAround.get(rail1), rail2, railsAround.get(rail2)));
                } else {
                    List cd = Stream.of(CURVED_SOUTH_EAST, CURVED_NORTH_EAST, CURVED_SOUTH_WEST)
                            .filter(o -> faces.containsAll(o.connectingDirections()))
                            .findFirst().get().connectingDirections();
                    BlockFace f1 = cd.get(0);
                    BlockFace f2 = cd.get(1);
                    this.setRailDirection(this.connect(rails.get(faces.indexOf(f1)), f1, rails.get(faces.indexOf(f2)), f2));
                }
            } else {
                BlockFace f = faces.stream().min((f1, f2) -> (f1.getIndex() < f2.getIndex()) ? 1 : ((x == y) ? 0 : -1)).get();
                BlockFace fo = f.getOpposite();
                if (faces.contains(fo)) { //Opposite connectable
                    this.setRailDirection(this.connect(rails.get(faces.indexOf(f)), f, rails.get(faces.indexOf(fo)), fo));
                } else {
                    this.setRailDirection(this.connect(rails.get(faces.indexOf(f)), f));
                }
            }
        }
        this.level.setBlock(this, this, true, true);
        if (!isAbstract()) {
            level.scheduleUpdate(this, this, 0);
        }

        return true;
    }

    @PowerNukkitDifference(since = "1.4.0.0-PN", info = "Fixed support logic")
    private boolean checkCanBePlace(Block check) {
        if (check == null) {
            return false;
        }
        return check.isSolid(UP) || check instanceof BlockCauldron;
    }

    private Orientation connect(BlockRail rail1, BlockFace face1, BlockRail rail2, BlockFace face2) {
        this.connect(rail1, face1);
        this.connect(rail2, face2);

        if (face1.getOpposite() == face2) {
            int delta1 = (int) (this.y - rail1.y);
            int delta2 = (int) (this.y - rail2.y);

            if (delta1 == -1) {
                return Orientation.ascending(face1);
            } else if (delta2 == -1) {
                return Orientation.ascending(face2);
            }
        }
        return straightOrCurved(face1, face2);
    }

    private Orientation connect(BlockRail other, BlockFace face) {
        int delta = (int) (this.y - other.y);
        Map rails = other.checkRailsConnected();
        if (rails.isEmpty()) { //Only one
            other.setOrientation(delta == 1 ? ascending(face.getOpposite()) : straight(face));
            return delta == -1 ? ascending(face) : straight(face);
        } else if (rails.size() == 1) { //Already connected
            BlockFace faceConnected = rails.values().iterator().next();

            if (other.isAbstract() && faceConnected != face) { //Curve!
                other.setOrientation(curved(face.getOpposite(), faceConnected));
                return delta == -1 ? ascending(face) : straight(face);
            } else if (faceConnected == face) { //Turn!
                if (!other.getOrientation().isAscending()) {
                    other.setOrientation(delta == 1 ? ascending(face.getOpposite()) : straight(face));
                }
                return delta == -1 ? ascending(face) : straight(face);
            } else if (other.getOrientation().hasConnectingDirections(NORTH, SOUTH)) { //North-south
                other.setOrientation(delta == 1 ? ascending(face.getOpposite()) : straight(face));
                return delta == -1 ? ascending(face) : straight(face);
            }
        }
        return STRAIGHT_NORTH_SOUTH;
    }

    private Map checkRailsAroundAffected() {
        Map railsAround = this.checkRailsAround(Arrays.asList(SOUTH, EAST, WEST, NORTH));
        return railsAround.keySet().stream()
                .filter(r -> r.checkRailsConnected().size() != 2)
                .collect(Collectors.toMap(r -> r, railsAround::get));
    }

    private Map checkRailsAround(Collection faces) {
        Map result = new HashMap<>();
        faces.forEach(f -> {
            Block b = this.getSide(f);
            Stream.of(b, b.up(), b.down())
                    .filter(Rail::isRailBlock)
                    .forEach(block -> result.put((BlockRail) block, f));
        });
        return result;
    }

    protected Map checkRailsConnected() {
        Map railsAround = this.checkRailsAround(this.getOrientation().connectingDirections());
        return railsAround.keySet().stream()
                .filter(r -> r.getOrientation().hasConnectingDirections(railsAround.get(r).getOpposite()))
                .collect(Collectors.toMap(r -> r, railsAround::get));
    }

    public boolean isAbstract() {
        return this.getId() == RAIL;
    }

    public boolean canPowered() {
        return this.canBePowered;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public final Orientation getRailDirection() {
        return getOrientation();
    }

    /**
     * Changes the rail direction without changing anything else.
     *
     * @param orientation The new orientation
     */
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void setRailDirection(Orientation orientation) {
        setPropertyValue(CURVED_RAIL_DIRECTION.getName(), orientation);
    }

    public Orientation getOrientation() {
        return (Orientation) getPropertyValue(CURVED_RAIL_DIRECTION.getName());
    }

    /**
     * Changes the rail direction and update the state in the world if the orientation changed in a single call.
     * 

* Note that the level block won't change if the current block has already the given orientation. * * @see #setRailDirection(Orientation) * @see Level#setBlock(Vector3, int, Block, boolean, boolean) */ public void setOrientation(Orientation o) { if (o != getOrientation()) { setRailDirection(o); this.level.setBlock(this, this, true, true); } } @Deprecated @DeprecationDetails(since = "1.4.0.0-PN", by = "PowerNukkit", reason = "This hack is no longer needed after the block state implementation and is no longer maintained") public int getRealMeta() { // Check if this can be powered // Avoid modifying the value from meta (The rail orientation may be false) // Reason: When the rail is curved, the meta will return STRAIGHT_NORTH_SOUTH. // OR Null Pointer Exception if (!isAbstract()) { return getDamage() & 0x7; } // Return the default: This meta return getDamage(); } public boolean isActive() { return getProperties().contains(ACTIVE) && getBooleanValue(ACTIVE); } /** * Changes the active flag and update the state in the world in a single call. *

* The active flag will not change if the block state don't have the {@link #ACTIVE} property, * and it will not throw exceptions related to missing block properties. *

* The level block will always update. * * @see #setRailDirection(Orientation) * @see Level#setBlock(Vector3, int, Block, boolean, boolean) */ public void setActive(boolean active) { if (getProperties().contains(ACTIVE)) { setRailActive(active); } level.setBlock(this, this, true, true); } @PowerNukkitOnly @Since("1.4.0.0-PN") public OptionalBoolean isRailActive() { return getProperties().contains(ACTIVE) ? OptionalBoolean.of(getBooleanValue(ACTIVE)) : OptionalBoolean.empty(); } /** * @throws NoSuchElementException If attempt to set the rail to active but it don't have the {@link #ACTIVE} property. */ @PowerNukkitOnly @Since("1.4.0.0-PN") public void setRailActive(boolean active) throws NoSuchElementException { if (!active && !getProperties().contains(ACTIVE)) { return; } setBooleanValue(ACTIVE, active); } @Override public Item toItem() { return new ItemBlock(this, 0); } @Override public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); } @Override public boolean canBePushed() { return true; } @Override @PowerNukkitOnly public boolean canBePulled() { return true; } @PowerNukkitOnly @Override public boolean breaksWhenMoved() { return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy