Path: blob/1.21.x/src/main/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java
7456 views
/*1* Copyright (c) Forge Development LLC and contributors2* SPDX-License-Identifier: LGPL-2.1-only3*/45package net.minecraftforge.client.model.generators;67import java.util.ArrayList;8import java.util.Arrays;9import java.util.Collection;10import java.util.List;11import java.util.Map.Entry;1213import com.google.common.base.Preconditions;14import com.google.common.collect.Multimap;15import com.google.common.collect.MultimapBuilder;16import com.google.gson.JsonArray;17import com.google.gson.JsonElement;18import com.google.gson.JsonObject;1920import net.minecraft.world.level.block.Block;21import net.minecraft.world.level.block.state.properties.Property;2223/**24* In 1.21.4 Mojang exposed their data generators for their models. So it should be feasible to just use theirs.25* If you find something lacking feel free to open a PR so that we can extend it.26* @deprecated Use Vanilla's providers {@link net.minecraft.client.data.models.ModelProvider}27*/28@Deprecated(since = "1.21.4", forRemoval = true)29public final class MultiPartBlockStateBuilder implements IGeneratedBlockState {30private final List<PartBuilder> parts = new ArrayList<>();31private final Block owner;3233public MultiPartBlockStateBuilder(Block owner) {34this.owner = owner;35}3637/**38* Creates a builder for models to assign to a {@link PartBuilder}, which when39* completed via {@link ConfiguredModel.Builder#addModel()} will assign the40* resultant set of models to the part and return it for further processing.41*42* @return the model builder43* @see ConfiguredModel.Builder44*/45public ConfiguredModel.Builder<PartBuilder> part() {46return ConfiguredModel.builder(this);47}4849MultiPartBlockStateBuilder addPart(PartBuilder part) {50this.parts.add(part);51return this;52}5354@Override55public JsonObject toJson() {56JsonArray variants = new JsonArray();57for (PartBuilder part : parts) {58variants.add(part.toJson());59}60JsonObject main = new JsonObject();61main.add("multipart", variants);62return main;63}6465public class PartBuilder {66public BlockStateProvider.ConfiguredModelList models;67public boolean useOr;68public final Multimap<Property<?>, Comparable<?>> conditions = MultimapBuilder.linkedHashKeys().arrayListValues().build();69public final List<ConditionGroup> nestedConditionGroups = new ArrayList<>();7071PartBuilder(BlockStateProvider.ConfiguredModelList models) {72this.models = models;73}7475/**76* Makes this part get applied if any of the conditions/condition groups are true, instead of all of them needing to be true.77*/78public PartBuilder useOr() {79this.useOr = true;80return this;81}8283/**84* Set a condition for this part, which consists of a property and a set of85* valid values. Can be called multiple times for multiple different properties.86*87* @param <T> the type of the property value88* @param prop the property89* @param values a set of valid values90* @return this builder91* @throws NullPointerException if {@code prop} is {@code null}92* @throws NullPointerException if {@code values} is {@code null}93* @throws IllegalArgumentException if {@code values} is empty94* @throws IllegalArgumentException if {@code prop} has already been configured95* @throws IllegalArgumentException if {@code prop} is not applicable to the96* current block's state97* @throws IllegalStateException if {@code !nestedConditionGroups.isEmpty()}98*/99@SafeVarargs100public final <T extends Comparable<T>> PartBuilder condition(Property<T> prop, T... values) {101Preconditions.checkNotNull(prop, "Property must not be null");102Preconditions.checkNotNull(values, "Value list must not be null");103Preconditions.checkArgument(values.length > 0, "Value list must not be empty");104Preconditions.checkArgument(!conditions.containsKey(prop), "Cannot set condition for property \"%s\" more than once", prop.getName());105Preconditions.checkArgument(canApplyTo(owner), "IProperty %s is not valid for the block %s", prop, owner);106Preconditions.checkState(nestedConditionGroups.isEmpty(), "Can't have normal conditions if there are already nested condition groups");107this.conditions.putAll(prop, Arrays.asList(values));108return this;109}110111/**112* Allows having nested groups of conditions if there are not any normal conditions.113* @throws IllegalStateException if {@code !conditions.isEmpty()}114*/115public final ConditionGroup nestedGroup() {116Preconditions.checkState(conditions.isEmpty(), "Can't have nested condition groups if there are already normal conditions");117ConditionGroup group = new ConditionGroup();118this.nestedConditionGroups.add(group);119return group;120}121122public MultiPartBlockStateBuilder end() { return MultiPartBlockStateBuilder.this; }123124JsonObject toJson() {125JsonObject out = new JsonObject();126if (!conditions.isEmpty()) {127out.add("when", MultiPartBlockStateBuilder.toJson(this.conditions, this.useOr));128} else if (!nestedConditionGroups.isEmpty()) {129out.add("when", MultiPartBlockStateBuilder.toJson(this.nestedConditionGroups, this.useOr));130}131out.add("apply", models.toJSON());132return out;133}134135public boolean canApplyTo(Block b) {136return b.getStateDefinition().getProperties().containsAll(conditions.keySet());137}138139public class ConditionGroup {140public final Multimap<Property<?>, Comparable<?>> conditions = MultimapBuilder.linkedHashKeys().arrayListValues().build();141public final List<ConditionGroup> nestedConditionGroups = new ArrayList<>();142private ConditionGroup parent = null;143public boolean useOr;144145/**146* Set a condition for this part, which consists of a property and a set of147* valid values. Can be called multiple times for multiple different properties.148*149* @param <T> the type of the property value150* @param prop the property151* @param values a set of valid values152* @return this builder153* @throws NullPointerException if {@code prop} is {@code null}154* @throws NullPointerException if {@code values} is {@code null}155* @throws IllegalArgumentException if {@code values} is empty156* @throws IllegalArgumentException if {@code prop} has already been configured157* @throws IllegalArgumentException if {@code prop} is not applicable to the158* current block's state159* @throws IllegalStateException if {@code !nestedConditionGroups.isEmpty()}160*/161@SafeVarargs162public final <T extends Comparable<T>> ConditionGroup condition(Property<T> prop, T... values) {163Preconditions.checkNotNull(prop, "Property must not be null");164Preconditions.checkNotNull(values, "Value list must not be null");165Preconditions.checkArgument(values.length > 0, "Value list must not be empty");166Preconditions.checkArgument(!conditions.containsKey(prop), "Cannot set condition for property \"%s\" more than once", prop.getName());167Preconditions.checkArgument(canApplyTo(owner), "IProperty %s is not valid for the block %s", prop, owner);168Preconditions.checkState(nestedConditionGroups.isEmpty(), "Can't have normal conditions if there are already nested condition groups");169this.conditions.putAll(prop, Arrays.asList(values));170return this;171}172173/**174* Allows having nested groups of conditions if there are not any normal conditions.175* @throws IllegalStateException if {@code !conditions.isEmpty()}176*/177public ConditionGroup nestedGroup() {178Preconditions.checkState(conditions.isEmpty(), "Can't have nested condition groups if there are already normal conditions");179ConditionGroup group = new ConditionGroup();180group.parent = this;181this.nestedConditionGroups.add(group);182return group;183}184185/**186* Ends this nested condition group and returns the parent condition group187*188* @throws IllegalStateException If this is not a nested condition group189*/190public ConditionGroup endNestedGroup() {191if (parent == null)192throw new IllegalStateException("This condition group is not nested, use end() instead");193return parent;194}195196/**197* Ends this condition group and returns the part builder198*199* @throws IllegalStateException If this is a nested condition group200*/201public MultiPartBlockStateBuilder.PartBuilder end() {202if (this.parent != null)203throw new IllegalStateException("This is a nested condition group, use endNestedGroup() instead");204return MultiPartBlockStateBuilder.PartBuilder.this;205}206207/**208* Makes this part get applied if any of the conditions/condition groups are true, instead of all of them needing to be true.209*/210public ConditionGroup useOr() {211this.useOr = true;212return this;213}214215JsonObject toJson() {216if (!this.conditions.isEmpty())217return MultiPartBlockStateBuilder.toJson(this.conditions, this.useOr);218else if (!this.nestedConditionGroups.isEmpty())219return MultiPartBlockStateBuilder.toJson(this.nestedConditionGroups, this.useOr);220return new JsonObject();221}222}223}224225private static JsonObject toJson(List<PartBuilder.ConditionGroup> conditions, boolean useOr) {226JsonObject groupJson = new JsonObject();227JsonArray innerGroupJson = new JsonArray();228groupJson.add(useOr ? "OR" : "AND", innerGroupJson);229for (PartBuilder.ConditionGroup group : conditions)230innerGroupJson.add(group.toJson());231return groupJson;232}233234private static JsonObject toJson(Multimap<Property<?>, Comparable<?>> conditions, boolean useOr) {235JsonObject groupJson = new JsonObject();236for (Entry<Property<?>, Collection<Comparable<?>>> e : conditions.asMap().entrySet()) {237StringBuilder activeString = new StringBuilder();238for (Comparable<?> val : e.getValue()) {239if (!activeString.isEmpty())240activeString.append("|");241activeString.append(getName(e.getKey(), val));242}243groupJson.addProperty(e.getKey().getName(), activeString.toString());244}245246if (useOr) {247JsonArray innerWhen = new JsonArray();248for (Entry<String, JsonElement> entry : groupJson.entrySet()) {249JsonObject obj = new JsonObject();250obj.add(entry.getKey(), entry.getValue());251innerWhen.add(obj);252}253groupJson = new JsonObject();254groupJson.add("OR", innerWhen);255}256return groupJson;257}258259@SuppressWarnings({ "unchecked", "rawtypes" })260private static <T> String getName(Property<?> key, Comparable<?> value) {261return ((Property)key).getName((Comparable)value);262}263}264265266