001/*
002 * This file is part of Baritone.
003 *
004 * Baritone is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU Lesser General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * Baritone is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License
015 * along with Baritone.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package baritone.api.utils;
019
020import baritone.api.utils.accessor.IItemStack;
021import com.google.common.collect.ImmutableSet;
022import net.minecraft.block.*;
023import net.minecraft.block.properties.IProperty;
024import net.minecraft.block.state.IBlockState;
025import net.minecraft.item.ItemStack;
026import net.minecraft.util.EnumFacing;
027import net.minecraft.util.ResourceLocation;
028
029import javax.annotation.Nonnull;
030import javax.annotation.Nullable;
031import java.util.*;
032import java.util.function.Consumer;
033import java.util.regex.MatchResult;
034import java.util.regex.Matcher;
035import java.util.regex.Pattern;
036import java.util.stream.Collectors;
037
038public final class BlockOptionalMeta {
039
040    private final Block block;
041    private final int meta;
042    private final boolean noMeta;
043    private final Set<IBlockState> blockstates;
044    private final ImmutableSet<Integer> stateHashes;
045    private final ImmutableSet<Integer> stackHashes;
046    private static final Pattern pattern = Pattern.compile("^(.+?)(?::(\\d+))?$");
047    private static final Map<Object, Object> normalizations;
048
049    public BlockOptionalMeta(@Nonnull Block block, @Nullable Integer meta) {
050        this.block = block;
051        this.noMeta = meta == null;
052        this.meta = noMeta ? 0 : meta;
053        this.blockstates = getStates(block, meta);
054        this.stateHashes = getStateHashes(blockstates);
055        this.stackHashes = getStackHashes(blockstates);
056    }
057
058    public BlockOptionalMeta(@Nonnull Block block) {
059        this(block, null);
060    }
061
062    public BlockOptionalMeta(@Nonnull String selector) {
063        Matcher matcher = pattern.matcher(selector);
064
065        if (!matcher.find()) {
066            throw new IllegalArgumentException("invalid block selector");
067        }
068
069        MatchResult matchResult = matcher.toMatchResult();
070        noMeta = matchResult.group(2) == null;
071
072        ResourceLocation id = new ResourceLocation(matchResult.group(1));
073
074        if (!Block.REGISTRY.containsKey(id)) {
075            throw new IllegalArgumentException("Invalid block ID");
076        }
077
078        block = Block.REGISTRY.getObject(id);
079        meta = noMeta ? 0 : Integer.parseInt(matchResult.group(2));
080        blockstates = getStates(block, getMeta());
081        stateHashes = getStateHashes(blockstates);
082        stackHashes = getStackHashes(blockstates);
083    }
084
085    static {
086        Map<Object, Object> _normalizations = new HashMap<>();
087        Consumer<Enum> put = instance -> _normalizations.put(instance.getClass(), instance);
088        put.accept(EnumFacing.NORTH);
089        put.accept(EnumFacing.Axis.Y);
090        put.accept(BlockLog.EnumAxis.Y);
091        put.accept(BlockStairs.EnumHalf.BOTTOM);
092        put.accept(BlockStairs.EnumShape.STRAIGHT);
093        put.accept(BlockLever.EnumOrientation.DOWN_X);
094        put.accept(BlockDoublePlant.EnumBlockHalf.LOWER);
095        put.accept(BlockSlab.EnumBlockHalf.BOTTOM);
096        put.accept(BlockDoor.EnumDoorHalf.LOWER);
097        put.accept(BlockDoor.EnumHingePosition.LEFT);
098        put.accept(BlockBed.EnumPartType.HEAD);
099        put.accept(BlockRailBase.EnumRailDirection.NORTH_SOUTH);
100        put.accept(BlockTrapDoor.DoorHalf.BOTTOM);
101        _normalizations.put(BlockBanner.ROTATION, 0);
102        _normalizations.put(BlockBed.OCCUPIED, false);
103        _normalizations.put(BlockBrewingStand.HAS_BOTTLE[0], false);
104        _normalizations.put(BlockBrewingStand.HAS_BOTTLE[1], false);
105        _normalizations.put(BlockBrewingStand.HAS_BOTTLE[2], false);
106        _normalizations.put(BlockButton.POWERED, false);
107        // _normalizations.put(BlockCactus.AGE, 0);
108        // _normalizations.put(BlockCauldron.LEVEL, 0);
109        // _normalizations.put(BlockChorusFlower.AGE, 0);
110        _normalizations.put(BlockChorusPlant.NORTH, false);
111        _normalizations.put(BlockChorusPlant.EAST, false);
112        _normalizations.put(BlockChorusPlant.SOUTH, false);
113        _normalizations.put(BlockChorusPlant.WEST, false);
114        _normalizations.put(BlockChorusPlant.UP, false);
115        _normalizations.put(BlockChorusPlant.DOWN, false);
116        // _normalizations.put(BlockCocoa.AGE, 0);
117        // _normalizations.put(BlockCrops.AGE, 0);
118        _normalizations.put(BlockDirt.SNOWY, false);
119        _normalizations.put(BlockDoor.OPEN, false);
120        _normalizations.put(BlockDoor.POWERED, false);
121        // _normalizations.put(BlockFarmland.MOISTURE, 0);
122        _normalizations.put(BlockFence.NORTH, false);
123        _normalizations.put(BlockFence.EAST, false);
124        _normalizations.put(BlockFence.WEST, false);
125        _normalizations.put(BlockFence.SOUTH, false);
126        // _normalizations.put(BlockFenceGate.POWERED, false);
127        // _normalizations.put(BlockFenceGate.IN_WALL, false);
128        _normalizations.put(BlockFire.AGE, 0);
129        _normalizations.put(BlockFire.NORTH, false);
130        _normalizations.put(BlockFire.EAST, false);
131        _normalizations.put(BlockFire.SOUTH, false);
132        _normalizations.put(BlockFire.WEST, false);
133        _normalizations.put(BlockFire.UPPER, false);
134        // _normalizations.put(BlockFrostedIce.AGE, 0);
135        _normalizations.put(BlockGrass.SNOWY, false);
136        // _normalizations.put(BlockHopper.ENABLED, true);
137        // _normalizations.put(BlockLever.POWERED, false);
138        // _normalizations.put(BlockLiquid.LEVEL, 0);
139        // _normalizations.put(BlockMycelium.SNOWY, false);
140        // _normalizations.put(BlockNetherWart.AGE, false);
141        _normalizations.put(BlockLeaves.CHECK_DECAY, false);
142        // _normalizations.put(BlockLeaves.DECAYABLE, false);
143        // _normalizations.put(BlockObserver.POWERED, false);
144        _normalizations.put(BlockPane.NORTH, false);
145        _normalizations.put(BlockPane.EAST, false);
146        _normalizations.put(BlockPane.WEST, false);
147        _normalizations.put(BlockPane.SOUTH, false);
148        // _normalizations.put(BlockPistonBase.EXTENDED, false);
149        // _normalizations.put(BlockPressurePlate.POWERED, false);
150        // _normalizations.put(BlockPressurePlateWeighted.POWER, false);
151        _normalizations.put(BlockQuartz.EnumType.LINES_X, BlockQuartz.EnumType.LINES_Y);
152        _normalizations.put(BlockQuartz.EnumType.LINES_Z, BlockQuartz.EnumType.LINES_Y);
153        // _normalizations.put(BlockRailDetector.POWERED, false);
154        // _normalizations.put(BlockRailPowered.POWERED, false);
155        _normalizations.put(BlockRedstoneWire.NORTH, false);
156        _normalizations.put(BlockRedstoneWire.EAST, false);
157        _normalizations.put(BlockRedstoneWire.SOUTH, false);
158        _normalizations.put(BlockRedstoneWire.WEST, false);
159        // _normalizations.put(BlockReed.AGE, false);
160        _normalizations.put(BlockSapling.STAGE, 0);
161        _normalizations.put(BlockSkull.NODROP, false);
162        _normalizations.put(BlockStandingSign.ROTATION, 0);
163        _normalizations.put(BlockStem.AGE, 0);
164        _normalizations.put(BlockTripWire.NORTH, false);
165        _normalizations.put(BlockTripWire.EAST, false);
166        _normalizations.put(BlockTripWire.WEST, false);
167        _normalizations.put(BlockTripWire.SOUTH, false);
168        _normalizations.put(BlockVine.NORTH, false);
169        _normalizations.put(BlockVine.EAST, false);
170        _normalizations.put(BlockVine.SOUTH, false);
171        _normalizations.put(BlockVine.WEST, false);
172        _normalizations.put(BlockVine.UP, false);
173        _normalizations.put(BlockWall.UP, false);
174        _normalizations.put(BlockWall.NORTH, false);
175        _normalizations.put(BlockWall.EAST, false);
176        _normalizations.put(BlockWall.WEST, false);
177        _normalizations.put(BlockWall.SOUTH, false);
178        normalizations = Collections.unmodifiableMap(_normalizations);
179    }
180
181    public static <C extends Comparable<C>, P extends IProperty<C>> P castToIProperty(Object value) {
182        //noinspection unchecked
183        return (P) value;
184    }
185
186    public static <C extends Comparable<C>, P extends IProperty<C>> C castToIPropertyValue(P iproperty, Object value) {
187        //noinspection unchecked
188        return (C) value;
189    }
190
191    /**
192     * Normalizes the specified blockstate by setting meta-affecting properties which
193     * are not being targeted by the meta parameter to their default values.
194     * <p>
195     * For example, block variant/color is the primary target for the meta value, so properties
196     * such as rotation/facing direction will be set to default values in order to nullify
197     * the effect that they have on the state's meta value.
198     *
199     * @param state The state to normalize
200     * @return The normalized block state
201     */
202    public static IBlockState normalize(IBlockState state) {
203        IBlockState newState = state;
204
205        for (IProperty<?> property : state.getProperties().keySet()) {
206            Class<?> valueClass = property.getValueClass();
207            if (normalizations.containsKey(property)) {
208                try {
209                    newState = newState.withProperty(
210                            castToIProperty(property),
211                            castToIPropertyValue(property, normalizations.get(property))
212                    );
213                } catch (IllegalArgumentException ignored) {}
214            } else if (normalizations.containsKey(state.getValue(property))) {
215                try {
216                    newState = newState.withProperty(
217                            castToIProperty(property),
218                            castToIPropertyValue(property, normalizations.get(state.getValue(property)))
219                    );
220                } catch (IllegalArgumentException ignored) {}
221            } else if (normalizations.containsKey(valueClass)) {
222                try {
223                    newState = newState.withProperty(
224                            castToIProperty(property),
225                            castToIPropertyValue(property, normalizations.get(valueClass))
226                    );
227                } catch (IllegalArgumentException ignored) {}
228            }
229        }
230
231        return newState;
232    }
233
234    /**
235     * Evaluate the target meta value for the specified state. The target meta value is
236     * most often that which is influenced by the variant/color property of the block state.
237     *
238     * @param state The state to check
239     * @return The target meta of the state
240     * @see #normalize(IBlockState)
241     */
242    public static int stateMeta(IBlockState state) {
243        return state.getBlock().getMetaFromState(normalize(state));
244    }
245
246    private static Set<IBlockState> getStates(@Nonnull Block block, @Nullable Integer meta) {
247        return block.getBlockState().getValidStates().stream()
248                .filter(blockstate -> meta == null || stateMeta(blockstate) == meta)
249                .collect(Collectors.toSet());
250    }
251
252    private static ImmutableSet<Integer> getStateHashes(Set<IBlockState> blockstates) {
253        return ImmutableSet.copyOf(
254                blockstates.stream()
255                        .map(IBlockState::hashCode)
256                        .toArray(Integer[]::new)
257        );
258    }
259
260    private static ImmutableSet<Integer> getStackHashes(Set<IBlockState> blockstates) {
261        //noinspection ConstantConditions
262        return ImmutableSet.copyOf(
263                blockstates.stream()
264                        .map(state -> new ItemStack(
265                                state.getBlock().getItemDropped(state, new Random(), 0),
266                                state.getBlock().damageDropped(state)
267                        ))
268                        .map(stack -> ((IItemStack) (Object) stack).getBaritoneHash())
269                        .toArray(Integer[]::new)
270        );
271    }
272
273    public Block getBlock() {
274        return block;
275    }
276
277    public Integer getMeta() {
278        return noMeta ? null : meta;
279    }
280
281    public boolean matches(@Nonnull Block block) {
282        return block == this.block;
283    }
284
285    public boolean matches(@Nonnull IBlockState blockstate) {
286        Block block = blockstate.getBlock();
287        return block == this.block && stateHashes.contains(blockstate.hashCode());
288    }
289
290    public boolean matches(ItemStack stack) {
291        //noinspection ConstantConditions
292        int hash = ((IItemStack) (Object) stack).getBaritoneHash();
293
294        if (noMeta) {
295            hash -= stack.getItemDamage();
296        }
297
298        return stackHashes.contains(hash);
299    }
300
301    @Override
302    public String toString() {
303        return String.format("BlockOptionalMeta{block=%s,meta=%s}", block, getMeta());
304    }
305
306    public static IBlockState blockStateFromStack(ItemStack stack) {
307        //noinspection deprecation
308        return Block.getBlockFromItem(stack.getItem()).getStateFromMeta(stack.getMetadata());
309    }
310
311    public IBlockState getAnyBlockState() {
312        if (blockstates.size() > 0) {
313            return blockstates.iterator().next();
314        }
315
316        return null;
317    }
318}