package net.minecraft.world.level.block.state; import com.google.common.collect.ArrayTable; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; 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.Iterator; import java.util.Map; 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 Table, Comparable, 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(Collection collection, T value) { Iterator iterator = collection.iterator(); while (iterator.hasNext()) { if (iterator.next().equals(value)) { if (iterator.hasNext()) { return (T)iterator.next(); } return (T)collection.iterator().next(); } } return (T)iterator.next(); } 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) { Comparable comparable = this.values.get(property); return comparable == null ? Optional.empty() : Optional.of((Comparable)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 if (comparable.equals(value)) { return (S)this; } else { S object = this.neighbours.get(property, value); if (object == null) { throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); } else { return object; } } } public , V extends T> S trySetValue(Property property, V value) { Comparable comparable = this.values.get(property); if (comparable != null && !comparable.equals(value)) { S object = this.neighbours.get(property, value); if (object == null) { throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); } else { return object; } } else { return (S)this; } } public void populateNeighbours(Map, Comparable>, S> possibleStateMap) { if (this.neighbours != null) { throw new IllegalStateException(); } else { Table, Comparable, S> table = HashBasedTable.create(); for (Entry, Comparable> entry : this.values.entrySet()) { Property property = (Property)entry.getKey(); for (Comparable comparable : property.getPossibleValues()) { if (!comparable.equals(entry.getValue())) { table.put(property, comparable, (S)possibleStateMap.get(this.makeNeighbourValues(property, comparable))); } } } this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); } } 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); } ); } }