package net.minecraft.world.level.block.state; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; import net.minecraft.world.level.block.state.properties.Property; import org.jetbrains.annotations.Nullable; public abstract class StateHolder { public static final String NAME_TAG = "Name"; public static final String PROPERTIES_TAG = "Properties"; private static final Function, Comparable>, String> PROPERTY_ENTRY_TO_STRING_FUNCTION = new Function, Comparable>, String>() { public String apply(@Nullable Entry, Comparable> propertyEntry) { if (propertyEntry == null) { return ""; } else { Property property = (Property)propertyEntry.getKey(); return property.getName() + "=" + this.getName(property, (Comparable)propertyEntry.getValue()); } } private > String getName(Property property, Comparable value) { return property.getName((T)value); } }; protected final O owner; private final Reference2ObjectArrayMap, Comparable> values; private Map, S[]> neighbours; protected final MapCodec propertiesCodec; protected StateHolder(O owner, Reference2ObjectArrayMap, Comparable> values, MapCodec propertiesCodec) { this.owner = owner; this.values = values; this.propertiesCodec = propertiesCodec; } public > S cycle(Property property) { return this.setValue(property, findNextInCollection(property.getPossibleValues(), this.getValue(property))); } protected static T findNextInCollection(List list, T object) { int i = list.indexOf(object) + 1; return (T)(i == list.size() ? list.getFirst() : list.get(i)); } public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(this.owner); if (!this.getValues().isEmpty()) { stringBuilder.append('['); stringBuilder.append((String)this.getValues().entrySet().stream().map(PROPERTY_ENTRY_TO_STRING_FUNCTION).collect(Collectors.joining(","))); stringBuilder.append(']'); } return stringBuilder.toString(); } /** * @return an unmodifiable collection of all possible properties. */ public Collection> getProperties() { return Collections.unmodifiableCollection(this.values.keySet()); } public > boolean hasProperty(Property property) { return this.values.containsKey(property); } /** * @return the value of the given Property for this state */ public > T getValue(Property property) { Comparable comparable = this.values.get(property); if (comparable == null) { throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); } else { return (T)property.getValueClass().cast(comparable); } } public > Optional getOptionalValue(Property property) { return Optional.ofNullable(this.getNullableValue(property)); } public > T getValueOrElse(Property property, T comparable) { return (T)Objects.requireNonNullElse(this.getNullableValue(property), comparable); } @Nullable public > T getNullableValue(Property property) { Comparable comparable = this.values.get(property); return (T)(comparable == null ? null : property.getValueClass().cast(comparable)); } public , V extends T> S setValue(Property property, V value) { Comparable comparable = this.values.get(property); if (comparable == null) { throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); } else { return this.setValueInternal(property, value, comparable); } } public , V extends T> S trySetValue(Property property, V value) { Comparable comparable = this.values.get(property); return (S)(comparable == null ? this : this.setValueInternal(property, value, comparable)); } private , V extends T> S setValueInternal(Property property, V comparable, Comparable comparable2) { if (comparable2.equals(comparable)) { return (S)this; } else { int i = property.getInternalIndex((T)comparable); if (i < 0) { throw new IllegalArgumentException("Cannot set property " + property + " to " + comparable + " on " + this.owner + ", it is not an allowed value"); } else { return (S)this.neighbours.get(property)[i]; } } } public void populateNeighbours(Map, Comparable>, S> possibleStateMap) { if (this.neighbours != null) { throw new IllegalStateException(); } else { Map, S[]> map = new Reference2ObjectArrayMap<>(this.values.size()); for (Entry, Comparable> entry : this.values.entrySet()) { Property property = (Property)entry.getKey(); map.put(property, property.getPossibleValues().stream().map(comparable -> possibleStateMap.get(this.makeNeighbourValues(property, comparable))).toArray()); } this.neighbours = map; } } private Map, Comparable> makeNeighbourValues(Property property, Comparable value) { Map, Comparable> map = new Reference2ObjectArrayMap<>(this.values); map.put(property, value); return map; } public Map, Comparable> getValues() { return this.values; } protected static > Codec codec(Codec propertyMap, Function holderFunction) { return propertyMap.dispatch( "Name", stateHolder -> stateHolder.owner, object -> { S stateHolder = (S)holderFunction.apply(object); return stateHolder.getValues().isEmpty() ? MapCodec.unit(stateHolder) : stateHolder.propertiesCodec.codec().lenientOptionalFieldOf("Properties").xmap(optional -> (StateHolder)optional.orElse(stateHolder), Optional::of); } ); } }