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 baritone.api.utils.accessor.ILootTable;
022import com.google.common.collect.ImmutableMap;
023import com.google.common.collect.ImmutableSet;
024import net.minecraft.client.Minecraft;
025import net.minecraft.core.HolderLookup;
026import net.minecraft.core.LayeredRegistryAccess;
027import net.minecraft.core.Registry;
028import net.minecraft.core.RegistryAccess;
029import net.minecraft.resources.RegistryDataLoader;
030import net.minecraft.resources.ResourceKey;
031import net.minecraft.server.MinecraftServer;
032import net.minecraft.server.RegistryLayer;
033import net.minecraft.server.ReloadableServerRegistries;
034import net.minecraft.server.level.ServerLevel;
035import net.minecraft.server.level.progress.ChunkProgressListener;
036import net.minecraft.server.packs.PackType;
037import net.minecraft.server.packs.VanillaPackResources;
038import net.minecraft.server.packs.repository.ServerPacksSource;
039import net.minecraft.server.packs.resources.CloseableResourceManager;
040import net.minecraft.server.packs.resources.MultiPackResourceManager;
041import net.minecraft.tags.TagLoader;
042import net.minecraft.world.RandomSequences;
043import net.minecraft.world.flag.FeatureFlagSet;
044import net.minecraft.world.item.Item;
045import net.minecraft.world.item.ItemStack;
046import net.minecraft.world.item.Items;
047import net.minecraft.world.level.CustomSpawner;
048import net.minecraft.world.level.Level;
049import net.minecraft.world.level.block.Block;
050import net.minecraft.world.level.block.state.BlockState;
051import net.minecraft.world.level.block.state.properties.Property;
052import net.minecraft.world.level.dimension.LevelStem;
053import net.minecraft.world.level.storage.LevelStorageSource;
054import net.minecraft.world.level.storage.ServerLevelData;
055import net.minecraft.world.level.storage.loot.LootContext;
056import net.minecraft.world.level.storage.loot.LootParams;
057import net.minecraft.world.level.storage.loot.LootTable;
058import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
059import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
060import net.minecraft.world.phys.Vec3;
061import sun.misc.Unsafe;
062
063import javax.annotation.Nonnull;
064import javax.annotation.Nullable;
065import java.lang.reflect.Field;
066import java.lang.reflect.Method;
067import java.util.*;
068import java.util.concurrent.CompletableFuture;
069import java.util.concurrent.Executor;
070import java.util.regex.Matcher;
071import java.util.regex.Pattern;
072import java.util.stream.Collectors;
073
074public final class BlockOptionalMeta {
075    // id or id[] or id[properties] where id and properties are any text with at least one character
076    private static final Pattern PATTERN = Pattern.compile("^(?<id>.+?)(?:\\[(?<properties>.+?)?\\])?$");
077
078    private final Block block;
079    private final String propertiesDescription; // exists so toString() can return something more useful than a list of all blockstates
080    private final Set<BlockState> blockstates;
081    private final ImmutableSet<Integer> stateHashes;
082    private final ImmutableSet<Integer> stackHashes;
083    private static Map<Block, List<Item>> drops = new HashMap<>();
084
085    public BlockOptionalMeta(@Nonnull Block block) {
086        this.block = block;
087        this.propertiesDescription = "{}";
088        this.blockstates = getStates(block, Collections.emptyMap());
089        this.stateHashes = getStateHashes(blockstates);
090        this.stackHashes = getStackHashes(blockstates);
091    }
092
093    public BlockOptionalMeta(@Nonnull String selector) {
094        Matcher matcher = PATTERN.matcher(selector);
095
096        if (!matcher.find()) {
097            throw new IllegalArgumentException("invalid block selector");
098        }
099
100        block = BlockUtils.stringToBlockRequired(matcher.group("id"));
101
102        String props = matcher.group("properties");
103        Map<Property<?>, ?> properties = props == null || props.equals("") ? Collections.emptyMap() : parseProperties(block, props);
104
105        propertiesDescription = props == null ? "{}" : "{" + props.replace("=", ":") + "}";
106        blockstates = getStates(block, properties);
107        stateHashes = getStateHashes(blockstates);
108        stackHashes = getStackHashes(blockstates);
109    }
110
111    private static <C extends Comparable<C>, P extends Property<C>> P castToIProperty(Object value) {
112        //noinspection unchecked
113        return (P) value;
114    }
115
116    private static Map<Property<?>, ?> parseProperties(Block block, String raw) {
117        ImmutableMap.Builder<Property<?>, Object> builder = ImmutableMap.builder();
118        for (String pair : raw.split(",")) {
119            String[] parts = pair.split("=");
120            if (parts.length != 2) {
121                throw new IllegalArgumentException(String.format("\"%s\" is not a valid property-value pair", pair));
122            }
123            String rawKey = parts[0];
124            String rawValue = parts[1];
125            Property<?> key = block.getStateDefinition().getProperty(rawKey);
126            Comparable<?> value = castToIProperty(key).getValue(rawValue)
127                    .orElseThrow(() -> new IllegalArgumentException(String.format(
128                            "\"%s\" is not a valid value for %s on %s",
129                            rawValue, key, block
130                    )));
131            builder.put(key, value);
132        }
133        return builder.build();
134    }
135
136    private static Set<BlockState> getStates(@Nonnull Block block, @Nonnull Map<Property<?>, ?> properties) {
137        return block.getStateDefinition().getPossibleStates().stream()
138                .filter(blockstate -> properties.entrySet().stream().allMatch(entry ->
139                        blockstate.getValue(entry.getKey()) == entry.getValue()
140                ))
141                .collect(Collectors.toSet());
142    }
143
144    private static ImmutableSet<Integer> getStateHashes(Set<BlockState> blockstates) {
145        return ImmutableSet.copyOf(
146                blockstates.stream()
147                        .map(BlockState::hashCode)
148                        .toArray(Integer[]::new)
149        );
150    }
151
152    private static ImmutableSet<Integer> getStackHashes(Set<BlockState> blockstates) {
153        //noinspection ConstantConditions
154        return ImmutableSet.copyOf(
155                blockstates.stream()
156                        .flatMap(state -> drops(state.getBlock())
157                                .stream()
158                                .map(item -> new ItemStack(item, 1))
159                        )
160                        .map(stack -> ((IItemStack) (Object) stack).getBaritoneHash())
161                        .toArray(Integer[]::new)
162        );
163    }
164
165    public Block getBlock() {
166        return block;
167    }
168
169    public boolean matches(@Nonnull Block block) {
170        return block == this.block;
171    }
172
173    public boolean matches(@Nonnull BlockState blockstate) {
174        Block block = blockstate.getBlock();
175        return block == this.block && stateHashes.contains(blockstate.hashCode());
176    }
177
178    public boolean matches(ItemStack stack) {
179        //noinspection ConstantConditions
180        int hash = ((IItemStack) (Object) stack).getBaritoneHash();
181
182        hash -= stack.getDamageValue();
183
184        return stackHashes.contains(hash);
185    }
186
187    @Override
188    public String toString() {
189        return String.format("BlockOptionalMeta{block=%s,properties=%s}", block, propertiesDescription);
190    }
191
192    public BlockState getAnyBlockState() {
193        if (blockstates.size() > 0) {
194            return blockstates.iterator().next();
195        }
196
197        return null;
198    }
199
200    public Set<BlockState> getAllBlockStates() {
201        return blockstates;
202    }
203
204    public Set<Integer> stackHashes() {
205        return stackHashes;
206    }
207
208    private static Method getVanillaServerPack;
209
210    private static VanillaPackResources getVanillaServerPack() {
211        if (getVanillaServerPack == null) {
212            getVanillaServerPack = Arrays.stream(ServerPacksSource.class.getDeclaredMethods()).filter(field -> field.getReturnType() == VanillaPackResources.class).findFirst().orElseThrow();
213            getVanillaServerPack.setAccessible(true);
214        }
215
216        try {
217            return (VanillaPackResources) getVanillaServerPack.invoke(null);
218        } catch (Exception e) {
219            e.printStackTrace();
220        }
221
222        return null;
223    }
224
225    private static synchronized List<Item> drops(Block b) {
226        return drops.computeIfAbsent(b, block -> {
227            Optional<ResourceKey<LootTable>> optionalLootTableKey = block.getLootTable();
228            if (optionalLootTableKey.isEmpty()) {
229                return Collections.emptyList();
230            } else {
231                List<Item> items = new ArrayList<>();
232                try {
233                    ServerLevel lv2 = ServerLevelStub.fastCreate();
234
235                    LootParams.Builder lv5 = new LootParams.Builder(lv2)
236                        .withParameter(LootContextParams.ORIGIN, Vec3.ZERO)
237                        .withParameter(LootContextParams.BLOCK_STATE, b.defaultBlockState())
238                        .withParameter(LootContextParams.TOOL, new ItemStack(Items.NETHERITE_PICKAXE, 1));
239                    getDrops(block, lv5).stream().map(ItemStack::getItem).forEach(items::add);
240                } catch (Exception e) {
241                    e.printStackTrace();
242                }
243                return items;
244            }
245        });
246    }
247
248    private static List<ItemStack> getDrops(Block state, LootParams.Builder params) {
249        Optional<ResourceKey<LootTable>> lv = state.getLootTable();
250        if (lv.isEmpty()) {
251            return Collections.emptyList();
252        } else {
253            LootParams lv2 = params.withParameter(LootContextParams.BLOCK_STATE, state.defaultBlockState()).create(LootContextParamSets.BLOCK);
254            ServerLevelStub lv3 = (ServerLevelStub) lv2.getLevel();
255            LootTable lv4 = lv3.holder().getLootTable(lv.get());
256            return((ILootTable) lv4).invokeGetRandomItems(new LootContext.Builder(lv2).withOptionalRandomSeed(1).create(null));
257        }
258    }
259
260    public static class ServerLevelStub extends ServerLevel {
261        private static Minecraft client = Minecraft.getInstance();
262        private static Unsafe unsafe = getUnsafe();
263        private static CompletableFuture<RegistryAccess> registryAccess = load();
264
265        public ServerLevelStub(MinecraftServer $$0, Executor $$1, LevelStorageSource.LevelStorageAccess $$2, ServerLevelData $$3, ResourceKey<Level> $$4, LevelStem $$5, ChunkProgressListener $$6, boolean $$7, long $$8, List<CustomSpawner> $$9, boolean $$10, @Nullable RandomSequences $$11) {
266            super($$0, $$1, $$2, $$3, $$4, $$5, $$6, $$7, $$8, $$9, $$10, $$11);
267        }
268
269        @Override
270        public FeatureFlagSet enabledFeatures() {
271            assert client.level != null;
272            return client.level.enabledFeatures();
273        }
274
275        public static ServerLevelStub fastCreate() {
276            try {
277                return (ServerLevelStub) unsafe.allocateInstance(ServerLevelStub.class);
278            } catch (InstantiationException e) {
279                throw new RuntimeException(e);
280            }
281        }
282
283        @Override
284        public RegistryAccess registryAccess() {
285            return registryAccess.join();
286        }
287
288        public ReloadableServerRegistries.Holder holder() {
289            return new ReloadableServerRegistries.Holder(registryAccess().freeze());
290        }
291
292        public static Unsafe getUnsafe() {
293            try {
294                Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
295                theUnsafe.setAccessible(true);
296                return (Unsafe) theUnsafe.get(null);
297            } catch (Exception e) {
298                throw new RuntimeException(e);
299            }
300        }
301
302        public static CompletableFuture<RegistryAccess> load() {
303            // Simplified from {@link net.minecraft.server.WorldLoader#load()}
304            CloseableResourceManager closeableResourceManager = new MultiPackResourceManager(
305                PackType.SERVER_DATA,
306                List.of(ServerPacksSource.createVanillaPackSource())
307            );
308            LayeredRegistryAccess<RegistryLayer> baseLayeredRegistry = RegistryLayer.createRegistryAccess();
309            List<Registry.PendingTags<?>> pendingTags = TagLoader.loadTagsForExistingRegistries(
310                closeableResourceManager, baseLayeredRegistry.getLayer(RegistryLayer.STATIC)
311            );
312            List<HolderLookup.RegistryLookup<?>> worldGenRegistryLookupList = TagLoader.buildUpdatedLookups(
313                baseLayeredRegistry.getAccessForLoading(RegistryLayer.WORLDGEN),
314                pendingTags
315            );
316            LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = baseLayeredRegistry.replaceFrom(
317                RegistryLayer.WORLDGEN,
318                RegistryDataLoader.load(
319                    closeableResourceManager,
320                    worldGenRegistryLookupList,
321                    RegistryDataLoader.WORLDGEN_REGISTRIES
322                )
323            );
324            return ReloadableServerRegistries.reload(
325                layeredRegistryAccess,
326                pendingTags,
327                closeableResourceManager,
328                Minecraft.getInstance()
329            ).thenApply(r -> r.layers().compositeAccess());
330        }
331    }
332}