package net.minecraft.advancements.critereon; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import io.netty.buffer.ByteBuf; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.StringRepresentable; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.material.FluidState; public record StatePropertiesPredicate(List properties) { private static final Codec> PROPERTIES_CODEC = Codec.unboundedMap( Codec.STRING, StatePropertiesPredicate.ValueMatcher.CODEC ) .xmap( map -> map.entrySet() .stream() .map(entry -> new StatePropertiesPredicate.PropertyMatcher((String)entry.getKey(), (StatePropertiesPredicate.ValueMatcher)entry.getValue())) .toList(), list -> (Map)list.stream().collect(Collectors.toMap(StatePropertiesPredicate.PropertyMatcher::name, StatePropertiesPredicate.PropertyMatcher::valueMatcher)) ); public static final Codec CODEC = PROPERTIES_CODEC.xmap(StatePropertiesPredicate::new, StatePropertiesPredicate::properties); public static final StreamCodec STREAM_CODEC = StatePropertiesPredicate.PropertyMatcher.STREAM_CODEC .apply(ByteBufCodecs.list()) .map(StatePropertiesPredicate::new, StatePropertiesPredicate::properties); public > boolean matches(StateDefinition properties, S targetProperty) { for (StatePropertiesPredicate.PropertyMatcher propertyMatcher : this.properties) { if (!propertyMatcher.match(properties, targetProperty)) { return false; } } return true; } public boolean matches(BlockState state) { return this.matches(state.getBlock().getStateDefinition(), state); } public boolean matches(FluidState state) { return this.matches(state.getType().getStateDefinition(), state); } public Optional checkState(StateDefinition state) { for (StatePropertiesPredicate.PropertyMatcher propertyMatcher : this.properties) { Optional optional = propertyMatcher.checkState(state); if (optional.isPresent()) { return optional; } } return Optional.empty(); } public static class Builder { private final ImmutableList.Builder matchers = ImmutableList.builder(); private Builder() { } public static StatePropertiesPredicate.Builder properties() { return new StatePropertiesPredicate.Builder(); } public StatePropertiesPredicate.Builder hasProperty(Property property, String value) { this.matchers.add(new StatePropertiesPredicate.PropertyMatcher(property.getName(), new StatePropertiesPredicate.ExactMatcher(value))); return this; } public StatePropertiesPredicate.Builder hasProperty(Property property, int value) { return this.hasProperty(property, Integer.toString(value)); } public StatePropertiesPredicate.Builder hasProperty(Property property, boolean value) { return this.hasProperty(property, Boolean.toString(value)); } public & StringRepresentable> StatePropertiesPredicate.Builder hasProperty(Property property, T value) { return this.hasProperty(property, value.getSerializedName()); } public Optional build() { return Optional.of(new StatePropertiesPredicate(this.matchers.build())); } } record ExactMatcher(String value) implements StatePropertiesPredicate.ValueMatcher { public static final Codec CODEC = Codec.STRING .xmap(StatePropertiesPredicate.ExactMatcher::new, StatePropertiesPredicate.ExactMatcher::value); public static final StreamCodec STREAM_CODEC = ByteBufCodecs.STRING_UTF8 .map(StatePropertiesPredicate.ExactMatcher::new, StatePropertiesPredicate.ExactMatcher::value); @Override public > boolean match(StateHolder stateHolder, Property property) { T comparable = stateHolder.getValue(property); Optional optional = property.getValue(this.value); return optional.isPresent() && comparable.compareTo((Comparable)optional.get()) == 0; } } record PropertyMatcher(String name, StatePropertiesPredicate.ValueMatcher valueMatcher) { public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.STRING_UTF8, StatePropertiesPredicate.PropertyMatcher::name, StatePropertiesPredicate.ValueMatcher.STREAM_CODEC, StatePropertiesPredicate.PropertyMatcher::valueMatcher, StatePropertiesPredicate.PropertyMatcher::new ); public > boolean match(StateDefinition properties, S propertyToMatch) { Property property = properties.getProperty(this.name); return property != null && this.valueMatcher.match(propertyToMatch, property); } public Optional checkState(StateDefinition state) { Property property = state.getProperty(this.name); return property != null ? Optional.empty() : Optional.of(this.name); } } record RangedMatcher(Optional minValue, Optional maxValue) implements StatePropertiesPredicate.ValueMatcher { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( Codec.STRING.optionalFieldOf("min").forGetter(StatePropertiesPredicate.RangedMatcher::minValue), Codec.STRING.optionalFieldOf("max").forGetter(StatePropertiesPredicate.RangedMatcher::maxValue) ) .apply(instance, StatePropertiesPredicate.RangedMatcher::new) ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), StatePropertiesPredicate.RangedMatcher::minValue, ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), StatePropertiesPredicate.RangedMatcher::maxValue, StatePropertiesPredicate.RangedMatcher::new ); @Override public > boolean match(StateHolder stateHolder, Property property) { T comparable = stateHolder.getValue(property); if (this.minValue.isPresent()) { Optional optional = property.getValue((String)this.minValue.get()); if (optional.isEmpty() || comparable.compareTo((Comparable)optional.get()) < 0) { return false; } } if (this.maxValue.isPresent()) { Optional optional = property.getValue((String)this.maxValue.get()); if (optional.isEmpty() || comparable.compareTo((Comparable)optional.get()) > 0) { return false; } } return true; } } interface ValueMatcher { Codec CODEC = Codec.either(StatePropertiesPredicate.ExactMatcher.CODEC, StatePropertiesPredicate.RangedMatcher.CODEC) .xmap(Either::unwrap, valueMatcher -> { if (valueMatcher instanceof StatePropertiesPredicate.ExactMatcher exactMatcher) { return Either.left(exactMatcher); } else if (valueMatcher instanceof StatePropertiesPredicate.RangedMatcher rangedMatcher) { return Either.right(rangedMatcher); } else { throw new UnsupportedOperationException(); } }); StreamCodec STREAM_CODEC = ByteBufCodecs.either( StatePropertiesPredicate.ExactMatcher.STREAM_CODEC, StatePropertiesPredicate.RangedMatcher.STREAM_CODEC ) .map(Either::unwrap, valueMatcher -> { if (valueMatcher instanceof StatePropertiesPredicate.ExactMatcher exactMatcher) { return Either.left(exactMatcher); } else if (valueMatcher instanceof StatePropertiesPredicate.RangedMatcher rangedMatcher) { return Either.right(rangedMatcher); } else { throw new UnsupportedOperationException(); } }); > boolean match(StateHolder stateHolder, Property property); } }