package net.minecraft.client.resources.model; import com.google.common.collect.Maps; import com.google.gson.JsonObject; import com.mojang.logging.LogUtils; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Stream; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.renderer.block.BlockModelShaper; import net.minecraft.client.renderer.block.model.BlockModelDefinition; import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.GsonHelper; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateDefinition.Builder; import net.minecraft.world.level.block.state.properties.BooleanProperty; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class BlockStateModelLoader { private static final Logger LOGGER = LogUtils.getLogger(); private static final FileToIdConverter BLOCKSTATE_LISTER = FileToIdConverter.json("blockstates"); private static final String FRAME_MAP_PROPERTY = "map"; private static final String FRAME_MAP_PROPERTY_TRUE = "map=true"; private static final String FRAME_MAP_PROPERTY_FALSE = "map=false"; private static final StateDefinition ITEM_FRAME_FAKE_DEFINITION = new Builder(Blocks.AIR) .add(BooleanProperty.create("map")) .create(Block::defaultBlockState, BlockState::new); private static final ResourceLocation GLOW_ITEM_FRAME_LOCATION = ResourceLocation.withDefaultNamespace("glow_item_frame"); private static final ResourceLocation ITEM_FRAME_LOCATION = ResourceLocation.withDefaultNamespace("item_frame"); private static final Map> STATIC_DEFINITIONS = Map.of( ITEM_FRAME_LOCATION, ITEM_FRAME_FAKE_DEFINITION, GLOW_ITEM_FRAME_LOCATION, ITEM_FRAME_FAKE_DEFINITION ); public static final ModelResourceLocation GLOW_MAP_FRAME_LOCATION = new ModelResourceLocation(GLOW_ITEM_FRAME_LOCATION, "map=true"); public static final ModelResourceLocation GLOW_FRAME_LOCATION = new ModelResourceLocation(GLOW_ITEM_FRAME_LOCATION, "map=false"); public static final ModelResourceLocation MAP_FRAME_LOCATION = new ModelResourceLocation(ITEM_FRAME_LOCATION, "map=true"); public static final ModelResourceLocation FRAME_LOCATION = new ModelResourceLocation(ITEM_FRAME_LOCATION, "map=false"); private static Function> definitionLocationToBlockMapper() { Map> map = new HashMap(STATIC_DEFINITIONS); for (Block block : BuiltInRegistries.BLOCK) { map.put(block.builtInRegistryHolder().key().location(), block.getStateDefinition()); } return map::get; } public static CompletableFuture loadBlockStates(UnbakedModel model, ResourceManager resourceManager, Executor executor) { Function> function = definitionLocationToBlockMapper(); return CompletableFuture.supplyAsync(() -> BLOCKSTATE_LISTER.listMatchingResourceStacks(resourceManager), executor).thenCompose(map -> { List> list = new ArrayList(map.size()); for (Entry> entry : map.entrySet()) { list.add(CompletableFuture.supplyAsync(() -> { ResourceLocation resourceLocation = BLOCKSTATE_LISTER.fileToId((ResourceLocation)entry.getKey()); StateDefinition stateDefinition = (StateDefinition)function.apply(resourceLocation); if (stateDefinition == null) { LOGGER.debug("Discovered unknown block state definition {}, ignoring", resourceLocation); return null; } else { List listx = (List)entry.getValue(); List list2 = new ArrayList(listx.size()); for (Resource resource : listx) { try { Reader reader = resource.openAsReader(); try { JsonObject jsonObject = GsonHelper.parse(reader); BlockModelDefinition blockModelDefinition = BlockModelDefinition.fromJsonElement(jsonObject); list2.add(new BlockStateModelLoader.LoadedBlockModelDefinition(resource.sourcePackId(), blockModelDefinition)); } catch (Throwable var14) { if (reader != null) { try { reader.close(); } catch (Throwable var13) { var14.addSuppressed(var13); } } throw var14; } if (reader != null) { reader.close(); } } catch (Exception var15) { LOGGER.error("Failed to load blockstate definition {} from pack {}", resourceLocation, resource.sourcePackId(), var15); } } try { return loadBlockStateDefinitionStack(resourceLocation, stateDefinition, list2, model); } catch (Exception var12) { LOGGER.error("Failed to load blockstate definition {}", resourceLocation, var12); return null; } } }, executor)); } return Util.sequence(list).thenApply(listx -> { Map mapx = new HashMap(); for (BlockStateModelLoader.LoadedModels loadedModels : listx) { if (loadedModels != null) { mapx.putAll(loadedModels.models()); } } return new BlockStateModelLoader.LoadedModels(mapx); }); }); } private static BlockStateModelLoader.LoadedModels loadBlockStateDefinitionStack( ResourceLocation id, StateDefinition stateDefinition, List modelDefinitions, UnbakedModel model ) { Map map = new HashMap(); for (BlockStateModelLoader.LoadedBlockModelDefinition loadedBlockModelDefinition : modelDefinitions) { loadedBlockModelDefinition.contents .instantiate(stateDefinition, id + "/" + loadedBlockModelDefinition.source) .forEach((blockState, unbakedBlockStateModel) -> { ModelResourceLocation modelResourceLocation = BlockModelShaper.stateToModelLocation(id, blockState); map.put(modelResourceLocation, new BlockStateModelLoader.LoadedModel(blockState, unbakedBlockStateModel)); }); } return new BlockStateModelLoader.LoadedModels(map); } @Environment(EnvType.CLIENT) record LoadedBlockModelDefinition(String source, BlockModelDefinition contents) { } @Environment(EnvType.CLIENT) public record LoadedModel(BlockState state, UnbakedBlockStateModel model) { } @Environment(EnvType.CLIENT) public record LoadedModels(Map models) { public Stream forResolving() { return this.models.values().stream().map(BlockStateModelLoader.LoadedModel::model); } public Map plainModels() { return Maps.transformValues(this.models, BlockStateModelLoader.LoadedModel::model); } } }