/* * Minecraft Forge * Copyright (c) 2016. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.client.model; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import net.minecraft.client.renderer.block.model.ModelBlockDefinition; import net.minecraft.client.renderer.block.model.ModelRotation; import net.minecraft.client.renderer.block.model.Variant; import net.minecraft.client.renderer.block.model.VariantList; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.TRSRTransformation; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.Pair; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class BlockStateLoader { private static final Gson GSON = (new GsonBuilder()) .registerTypeAdapter(ForgeBlockStateV1.class, ForgeBlockStateV1.Deserializer.INSTANCE) .registerTypeAdapter(ForgeBlockStateV1.Variant.class, ForgeBlockStateV1.Variant.Deserializer.INSTANCE) .registerTypeAdapter(TRSRTransformation.class, ForgeBlockStateV1.TRSRDeserializer.INSTANCE) .create(); /** * Loads a BlockStates json file. * Will attempt to parse it as a Forge Enhanced version if possible. * Will fall back to standard loading if marker is not present. * * Note: This method is NOT thread safe * * @param reader json read * @param vanillaGSON ModelBlockDefinition's GSON reader. * * @return Model definition including variants for all known combinations. */ public static ModelBlockDefinition load(Reader reader, final Gson vanillaGSON) { try { byte[] data = IOUtils.toByteArray(reader); reader = new InputStreamReader(new ByteArrayInputStream(data), Charsets.UTF_8); Marker marker = GSON.fromJson(new String(data), Marker.class); // Read "forge_marker" to determine what to load. switch (marker.forge_marker) { case 1: // Version 1 ForgeBlockStateV1 v1 = GSON.fromJson(reader, ForgeBlockStateV1.class); Map<String, VariantList> variants = Maps.newHashMap(); for (Entry<String, Collection<ForgeBlockStateV1.Variant>> entry : v1.variants.asMap().entrySet()) { // Convert Version1 variants into vanilla variants for the ModelBlockDefinition. List<Variant> mcVars = Lists.newArrayList(); for (ForgeBlockStateV1.Variant var : entry.getValue()) { boolean uvLock = var.getUvLock().or(false); boolean smooth = var.getSmooth().or(true); boolean gui3d = var.getGui3d().or(true); int weight = var.getWeight().or(1); if (var.getModel() != null && var.getSubmodels().size() == 0 && var.getTextures().size() == 0 && var.getCustomData().size() == 0 && var.getState().orNull() instanceof ModelRotation) mcVars.add(new Variant(var.getModel(), (ModelRotation)var.getState().get(), uvLock, weight)); else mcVars.add(new ForgeVariant(var.getModel(), var.getState().or(TRSRTransformation.identity()), uvLock, smooth, gui3d, weight, var.getTextures(), var.getOnlyPartsVariant(), var.getCustomData())); } variants.put(entry.getKey(), new VariantList(mcVars)); } return new ModelBlockDefinition(variants, null); default: //Unknown version.. try loading it as normal. return vanillaGSON.fromJson(reader, ModelBlockDefinition.class); } } catch (IOException e) { Throwables.propagate(e); } return null; } public static class Marker { public int forge_marker = -1; } //This is here specifically so that we do not have a hard reference to ForgeBlockStateV1.Variant in ForgeVariant public static class SubModel { private final IModelState state; private final boolean uvLock; private final boolean smooth; private final boolean gui3d; private final ImmutableMap<String, String> textures; private final ResourceLocation model; private final ImmutableMap<String, String> customData; public SubModel(IModelState state, boolean uvLock, boolean smooth, boolean gui3d, ImmutableMap<String, String> textures, ResourceLocation model, ImmutableMap<String, String> customData) { this.state = state; this.uvLock = uvLock; this.smooth = smooth; this.gui3d = gui3d; this.textures = textures; this.model = model; this.customData = customData; } public IModelState getState() { return state; } public boolean isUVLock() { return uvLock; } public ImmutableMap<String, String> getTextures() { return textures; } public ResourceLocation getModelLocation() { return model; } public ImmutableMap<String, String> getCustomData() { return customData; } } private static class ForgeVariant extends Variant implements ISmartVariant { private final ImmutableMap<String, String> textures; private final ImmutableMap<String, SubModel> parts; private final ImmutableMap<String, String> customData; private final boolean smooth; private final boolean gui3d; private final IModelState state; public ForgeVariant(ResourceLocation model, IModelState state, boolean uvLock, boolean smooth, boolean gui3d, int weight, ImmutableMap<String, String> textures, ImmutableMap<String, SubModel> parts, ImmutableMap<String, String> customData) { super(model == null ? new ResourceLocation("builtin/missing") : model, state instanceof ModelRotation ? (ModelRotation)state : ModelRotation.X0_Y0, uvLock, weight); this.textures = textures; this.parts = parts; this.customData = customData; this.state = state; this.smooth = smooth; this.gui3d = gui3d; } private IModel runModelHooks(IModel base, boolean smooth, boolean gui3d, boolean uvlock, ImmutableMap<String, String> textureMap, ImmutableMap<String, String> customData) { base = ModelProcessingHelper.customData(base, customData); base = ModelProcessingHelper.retexture(base, textureMap); base = ModelProcessingHelper.smoothLighting(base, smooth); base = ModelProcessingHelper.gui3d(base, gui3d); base = ModelProcessingHelper.uvlock(base, uvlock); return base; } /** * Used to replace the base model with a re-textured model containing sub-models. */ @Override public IModel process(IModel base) { int size = parts.size(); // FIXME: should missing base be handled this way? boolean hasBase = base != ModelLoaderRegistry.getMissingModel(); if (hasBase) { base = runModelHooks(base, smooth, gui3d, this.isUvLock(), textures, customData); if (size <= 0) return base; } // Apply rotation of base model to sub-models. // If baseRot is non-null, then that rotation will be applied instead of the base model's rotation. // This is used to allow replacing base model with a sub-model when there is no base model for a variant. IModelState baseTr = getState(); ImmutableMap.Builder<String, Pair<IModel, IModelState>> models = ImmutableMap.builder(); for (Entry<String, SubModel> entry : parts.entrySet()) { SubModel part = entry.getValue(); IModel model = ModelLoaderRegistry.getModelOrLogError(part.getModelLocation(), "Unable to load block sub-model: \'" + part.getModelLocation()); models.put(entry.getKey(), Pair.<IModel, IModelState>of(runModelHooks(model, part.smooth, part.gui3d, part.uvLock, part.getTextures(), part.getCustomData()), part.getState())); } return new MultiModel(getModelLocation(), hasBase ? base : null, baseTr, models.build()); } @Override public IModelState getState() { return state; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("TexturedVariant:"); for (Entry<String, String> e: this.textures.entrySet()) buf.append(" ").append(e.getKey()).append(" = ").append(e.getValue()); return buf.toString(); } } }