/* * WorldEdit, a Minecraft world manipulation toolkit * Copyright (C) sk89q * Copyright (C) WorldEdit team and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.sk89q.worldedit.world.block; import com.fastasyncworldedit.core.command.SuggestInputParseException; import com.fastasyncworldedit.core.function.mask.SingleBlockStateMask; import com.fastasyncworldedit.core.queue.ITileInput; import com.fastasyncworldedit.core.registry.state.PropertyKey; import com.fastasyncworldedit.core.util.MutableCharSequence; import com.fastasyncworldedit.core.util.StringMan; import com.fastasyncworldedit.core.world.block.BlanketBaseBlock; import com.fastasyncworldedit.core.world.block.CompoundInput; import com.google.common.base.Function; import com.google.common.collect.Maps; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.NullExtent; import com.sk89q.worldedit.extent.OutputExtent; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.state.AbstractProperty; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.concurrency.LazyReference; import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; import com.sk89q.worldedit.world.registry.BlockMaterial; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; /** * An immutable class that represents the state a block can be in. */ @SuppressWarnings("unchecked") public class BlockState implements BlockStateHolder, Pattern { //FAWE start private final int internalId; private final int ordinal; private final char ordinalChar; private BlockMaterial material; private final BaseBlock emptyBaseBlock; private CompoundInput compoundInput = CompoundInput.NULL; //FAWE end private final BlockType blockType; //FAWE start public BlockState(BlockType blockType, int internalId, int ordinal) { this.blockType = blockType; this.internalId = internalId; this.ordinal = ordinal; this.ordinalChar = (char) ordinal; this.emptyBaseBlock = new BlanketBaseBlock(this); } public BlockState(BlockType blockType, int internalId, int ordinal, @Nonnull CompoundTag tile) { this.blockType = blockType; this.internalId = internalId; this.ordinal = ordinal; this.ordinalChar = (char) ordinal; this.emptyBaseBlock = new BlanketBaseBlock(this, tile); } /** * Returns a temporary BlockState for a given internal id. * * @return BlockState * @deprecated Magic Numbers */ @Deprecated public static BlockState getFromInternalId(int combinedId) throws InputParseException { return BlockTypes.getFromStateId(combinedId).withStateId(combinedId); } @Deprecated public static BlockState getFromOrdinal(int ordinal) { return BlockTypesCache.states[ordinal]; } /** * Returns a temporary BlockState for a given type and string. * * @param state String e.g., minecraft:water[level=4] * @return BlockState */ public static BlockState get(String state) throws InputParseException { return get(null, state); } /** * Returns a temporary BlockState for a given type and string. * *

It's faster if a BlockType is provided compared to parsing the string.

* * @param type BlockType e.g., BlockTypes.STONE (or null) * @param state String e.g., minecraft:water[level=4] * @return BlockState */ public static BlockState get(@Nullable BlockType type, String state) throws InputParseException { return get(type, state, null); } /** * Returns a temporary BlockState for a given type and string. * *

It's faster if a BlockType is provided compared to parsing the string.

* * @param type BlockType e.g., BlockTypes.STONE (or null) * @param state String e.g., minecraft:water[level=4] * @return BlockState */ public static BlockState get(@Nullable BlockType type, String state, BlockState defaultState) throws InputParseException { int propStrStart = state.indexOf('['); if (type == null) { CharSequence key; if (propStrStart == -1) { key = state; } else { MutableCharSequence charSequence = MutableCharSequence.getTemporal(); charSequence.setString(state); charSequence.setSubstring(0, propStrStart); key = charSequence; } type = BlockTypes.get(key); if (type == null) { String input = key.toString(); throw new SuggestInputParseException("Does not match a valid block type: " + input, input, () -> Stream.of( BlockTypesCache.values) .filter(b -> StringMan.blockStateMatches(input, b.getId())) .map(BlockType::getId) .sorted(StringMan.blockStateComparator(input)) .collect(Collectors.toList()) ); } } if (propStrStart == -1) { return type.getDefaultState(); } List propList = type.getProperties(); if (state.charAt(state.length() - 1) != ']') { state = state + "]"; } MutableCharSequence charSequence = MutableCharSequence.getTemporal(); charSequence.setString(state); if (propList.size() == 1) { AbstractProperty property = (AbstractProperty) propList.get(0); String name = property.getName(); charSequence.setSubstring(propStrStart + name.length() + 2, state.length() - 1); int index = charSequence.length() <= 0 ? -1 : property.getIndexFor(charSequence); if (index != -1) { return type.withPropertyId(index); } } int stateId; if (defaultState != null) { stateId = defaultState.getInternalId(); } else { stateId = type.getInternalId(); } int length = state.length(); AbstractProperty property = null; int last = propStrStart + 1; for (int i = last; i < length; i++) { char c = state.charAt(i); switch (c) { case ']': case ',': { charSequence.setSubstring(last, i); if (property != null) { int index = property.getIndexFor(charSequence); if (index == -1) { throw SuggestInputParseException.of(charSequence.toString(), property.getValues()); } stateId = property.modifyIndex(stateId, index); } else { // suggest PropertyKey key = PropertyKey.getByName(charSequence); if (key == null || !type.hasProperty(key)) { // Suggest property String input = charSequence.toString(); BlockType finalType = type; throw new SuggestInputParseException( "Invalid property " + key + ":" + input + " for type " + type, input, () -> finalType.getProperties().stream() .map(Property::getName) .filter(p -> StringMan.blockStateMatches(input, p)) .sorted(StringMan.blockStateComparator(input)) .collect(Collectors.toList()) ); } else { throw new SuggestInputParseException( "No operator for " + state, "", () -> Collections.singletonList("=") ); } } property = null; last = i + 1; break; } case '=': { charSequence.setSubstring(last, i); property = (AbstractProperty) type.getPropertyMap().get(charSequence); last = i + 1; break; } default: continue; } } return type.withPropertyId(stateId >> BlockTypesCache.BIT_OFFSET); } @Override public BlockState withPropertyId(int propertyId) { return getBlockType().withPropertyId(propertyId); } @Override public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { return set.setBlock(extent, this); } @Override public BaseBlock applyBlock(BlockVector3 position) { return this.toBaseBlock(); } public Mask toMask() { return new SingleBlockStateMask(new NullExtent(), this); } @Override public void applyTileEntity(OutputExtent output, int x, int y, int z) { } /** * The internal id with no type information. */ @Deprecated @Override public final int getInternalPropertiesId() { return this.getInternalId() >> BlockTypesCache.BIT_OFFSET; } @Deprecated @Override public final int getInternalBlockTypeId() { return this.getInternalId() & BlockTypesCache.BIT_MASK; } @Override public BlockState with(final Property property, final V value) { try { BlockType type = getBlockType(); int newState = ((AbstractProperty) property).modify(this.getInternalId(), value); return newState != this.getInternalId() ? type.withStateId(newState) : this; } catch (ClassCastException e) { throw new IllegalArgumentException("Property not found: " + property); } } @Override public V getState(final Property property) { try { AbstractProperty ap = (AbstractProperty) property; return (V) ap.getValue(this.getInternalId()); } catch (ClassCastException e) { throw new IllegalArgumentException("Property not found: " + property); } } @Override public BlockState with(final PropertyKey property, final V value) { try { BlockType type = getBlockType(); int newState = ((AbstractProperty) type.getProperty(property)).modify(this.getInternalId(), value); return newState != this.getInternalId() ? type.withStateId(newState) : this; } catch (ClassCastException e) { throw new IllegalArgumentException("Property not found: " + property); } } public BlockState withProperties(final BlockState other) { BlockType ot = other.getBlockType(); if (ot == blockType) { return other; } if (ot.getProperties().isEmpty() || blockType.getProperties().isEmpty()) { return this; } BlockState newState = this; for (Property prop : ot.getProperties()) { PropertyKey key = prop.getKey(); if (blockType.hasProperty(key)) { newState = newState.with(key, other.getState(key)); } } return newState; } //FAWE end @Override public Map, Object> getStates() { //FAWE end BlockType type = this.getBlockType(); // Lazily initialize the map Map map = Maps.asMap(type.getPropertiesSet(), (Function) this::getState); //noinspection RedundantCast - This is required for compilation, etc. return Collections.unmodifiableMap((Map, Object>) map); //FAWE end } @Override public BlockType getBlockType() { return this.blockType; } @Override public boolean equalsFuzzy(BlockStateHolder o) { if (null == o) { return false; } if (this == o) { // Added a reference equality check for speediness return true; } //FAWE start if (o.getClass() == BlockState.class) { return o.getOrdinal() == this.getOrdinal(); } return o.equalsFuzzy(this); //FAWE end } @Override public BlockState toImmutableState() { return this; } @Override public BaseBlock toBaseBlock() { return this.emptyBaseBlock; } @Deprecated @Override public V getState(PropertyKey key) { return getState(getBlockType().getProperty(key)); } //FAWE start @Deprecated @Override public CompoundTag getNbtData() { return getBlockType().getMaterial().isTile() ? getBlockType().getMaterial().getDefaultTile() : null; } //FAWE end @Override public BaseBlock toBaseBlock(LazyReference compoundTag) { if (compoundTag == null) { return toBaseBlock(); } return new BaseBlock(this, compoundTag); } //FAWE start @Override public int getInternalId() { return internalId; } @Override public BlockMaterial getMaterial() { if (this.material == null) { if (blockType == BlockTypes.__RESERVED__) { return this.material = blockType.getMaterial(); } this.material = WorldEdit .getInstance() .getPlatformManager() .queryCapability(Capability.GAME_HOOKS) .getRegistries() .getBlockRegistry() .getMaterial(this); if (this.material.hasContainer()) { this.compoundInput = CompoundInput.CONTAINER; } } return material; } @Override public final int getOrdinal() { return this.ordinal; } @Override public final char getOrdinalChar() { return this.ordinalChar; } @Override public int hashCode() { return getOrdinal(); } public boolean isAir() { return blockType.getMaterial().isAir(); } @Override public BaseBlock toBaseBlock(ITileInput input, int x, int y, int z) { return compoundInput.get(this, input, x, y, z); } //FAWE end @Override public String toString() { return getAsString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof BlockState)) { return false; } return equalsFuzzy((BlockState) obj); } }