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}