package net.minecraft.world.attribute; import com.google.common.annotations.VisibleForTesting; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; import net.minecraft.SharedConstants; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.world.clock.ClockManager; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.phys.Vec3; import net.minecraft.world.timeline.Timeline; import org.jspecify.annotations.Nullable; public class EnvironmentAttributeSystem implements EnvironmentAttributeReader { private final Map, EnvironmentAttributeSystem.ValueSampler> attributeSamplers = new Reference2ObjectOpenHashMap<>(); private EnvironmentAttributeSystem(final Map, List>> layersByAttribute) { layersByAttribute.forEach((attribute, layers) -> this.attributeSamplers.put(attribute, this.bakeLayerSampler(attribute, layers))); } private EnvironmentAttributeSystem.ValueSampler bakeLayerSampler( final EnvironmentAttribute attribute, final List> untypedLayers ) { List> layers = new ArrayList(untypedLayers); Value constantBaseValue = attribute.defaultValue(); while (!layers.isEmpty()) { if (!(layers.getFirst() instanceof EnvironmentAttributeLayer.Constant constantLayer)) { break; } constantBaseValue = constantLayer.applyConstant(constantBaseValue); layers.removeFirst(); } boolean isAffectedByPosition = layers.stream().anyMatch(layer -> layer instanceof EnvironmentAttributeLayer.Positional); return new EnvironmentAttributeSystem.ValueSampler<>(attribute, constantBaseValue, List.copyOf(layers), isAffectedByPosition); } public static EnvironmentAttributeSystem.Builder builder() { return new EnvironmentAttributeSystem.Builder(); } private static void addDefaultLayers(final EnvironmentAttributeSystem.Builder builder, final Level level) { RegistryAccess registries = level.registryAccess(); BiomeManager biomeManager = level.getBiomeManager(); ClockManager clockManager = level.clockManager(); addDimensionLayer(builder, level.dimensionType()); addBiomeLayer(builder, registries.lookupOrThrow(Registries.BIOME), biomeManager); level.dimensionType().timelines().forEach(timeline -> builder.addTimelineLayer(timeline, clockManager)); if (level.canHaveWeather()) { WeatherAttributes.addBuiltinLayers(builder, WeatherAttributes.WeatherAccess.from(level)); } } private static void addDimensionLayer(final EnvironmentAttributeSystem.Builder builder, final DimensionType dimensionType) { builder.addConstantLayer(dimensionType.attributes()); } private static void addBiomeLayer(final EnvironmentAttributeSystem.Builder builder, final HolderLookup biomes, final BiomeManager biomeManager) { Stream> attributesProvidedByBiomes = biomes.listElements() .flatMap(biome -> ((Biome)biome.value()).getAttributes().keySet().stream()) .distinct(); attributesProvidedByBiomes.forEach(attribute -> addBiomeLayerForAttribute(builder, attribute, biomeManager)); } private static void addBiomeLayerForAttribute( final EnvironmentAttributeSystem.Builder builder, final EnvironmentAttribute attribute, final BiomeManager biomeManager ) { builder.addPositionalLayer(attribute, (baseValue, pos, biomeWeights) -> { if (biomeWeights != null && attribute.isSpatiallyInterpolated()) { return biomeWeights.applyAttributeLayer(attribute, baseValue); } else { Holder biome = biomeManager.getNoiseBiomeAtPosition(pos.x, pos.y, pos.z); return biome.value().getAttributes().applyModifier(attribute, baseValue); } }); } public void invalidateTickCache() { this.attributeSamplers.values().forEach(EnvironmentAttributeSystem.ValueSampler::invalidateTickCache); } @Nullable private EnvironmentAttributeSystem.ValueSampler getValueSampler(final EnvironmentAttribute attribute) { return (EnvironmentAttributeSystem.ValueSampler)this.attributeSamplers.get(attribute); } @Override public Value getDimensionValue(final EnvironmentAttribute attribute) { if (SharedConstants.IS_RUNNING_IN_IDE && attribute.isPositional()) { throw new IllegalStateException("Position must always be provided for positional attribute " + attribute); } else { EnvironmentAttributeSystem.ValueSampler sampler = this.getValueSampler(attribute); return sampler == null ? attribute.defaultValue() : sampler.getDimensionValue(); } } @Override public Value getValue(final EnvironmentAttribute attribute, final Vec3 pos, @Nullable final SpatialAttributeInterpolator biomeInterpolator) { EnvironmentAttributeSystem.ValueSampler sampler = this.getValueSampler(attribute); return sampler == null ? attribute.defaultValue() : sampler.getValue(pos, biomeInterpolator); } @VisibleForTesting Value getConstantBaseValue(final EnvironmentAttribute attribute) { EnvironmentAttributeSystem.ValueSampler sampler = this.getValueSampler(attribute); return sampler != null ? sampler.baseValue : attribute.defaultValue(); } @VisibleForTesting boolean isAffectedByPosition(final EnvironmentAttribute attribute) { EnvironmentAttributeSystem.ValueSampler sampler = this.getValueSampler(attribute); return sampler != null && sampler.isAffectedByPosition; } public static class Builder { private final Map, List>> layersByAttribute = new HashMap(); private Builder() { } public EnvironmentAttributeSystem.Builder addDefaultLayers(final Level level) { EnvironmentAttributeSystem.addDefaultLayers(this, level); return this; } public EnvironmentAttributeSystem.Builder addConstantLayer(final EnvironmentAttributeMap attributeMap) { for (EnvironmentAttribute attribute : attributeMap.keySet()) { this.addConstantEntry(attribute, attributeMap); } return this; } private EnvironmentAttributeSystem.Builder addConstantEntry(final EnvironmentAttribute attribute, final EnvironmentAttributeMap attributeMap) { EnvironmentAttributeMap.Entry entry = attributeMap.get(attribute); if (entry == null) { throw new IllegalArgumentException("Missing attribute " + attribute); } else { return this.addConstantLayer(attribute, entry::applyModifier); } } public EnvironmentAttributeSystem.Builder addConstantLayer( final EnvironmentAttribute attribute, final EnvironmentAttributeLayer.Constant layer ) { return this.addLayer(attribute, layer); } public EnvironmentAttributeSystem.Builder addTimeBasedLayer( final EnvironmentAttribute attribute, final EnvironmentAttributeLayer.TimeBased layer ) { return this.addLayer(attribute, layer); } public EnvironmentAttributeSystem.Builder addPositionalLayer( final EnvironmentAttribute attribute, final EnvironmentAttributeLayer.Positional layer ) { return this.addLayer(attribute, layer); } private EnvironmentAttributeSystem.Builder addLayer(final EnvironmentAttribute attribute, final EnvironmentAttributeLayer layer) { ((List)this.layersByAttribute.computeIfAbsent(attribute, t -> new ArrayList())).add(layer); return this; } public EnvironmentAttributeSystem.Builder addTimelineLayer(final Holder timeline, final ClockManager clockManager) { for (EnvironmentAttribute attribute : timeline.value().attributes()) { this.addTimelineLayerForAttribute(timeline, attribute, clockManager); } return this; } private void addTimelineLayerForAttribute( final Holder timeline, final EnvironmentAttribute attribute, final ClockManager clockManager ) { this.addTimeBasedLayer(attribute, timeline.value().createTrackSampler(attribute, clockManager)); } public EnvironmentAttributeSystem build() { return new EnvironmentAttributeSystem(this.layersByAttribute); } } private static class ValueSampler { private final EnvironmentAttribute attribute; private final Value baseValue; private final List> layers; private final boolean isAffectedByPosition; @Nullable private Value cachedTickValue; private int cacheTickId; private ValueSampler( final EnvironmentAttribute attribute, final Value baseValue, final List> layers, final boolean isAffectedByPosition ) { this.attribute = attribute; this.baseValue = baseValue; this.layers = layers; this.isAffectedByPosition = isAffectedByPosition; } public void invalidateTickCache() { this.cachedTickValue = null; this.cacheTickId++; } public Value getDimensionValue() { if (this.cachedTickValue != null) { return this.cachedTickValue; } else { Value result = this.computeValueNotPositional(); this.cachedTickValue = result; return result; } } public Value getValue(final Vec3 pos, @Nullable final SpatialAttributeInterpolator biomeInterpolator) { return !this.isAffectedByPosition ? this.getDimensionValue() : this.computeValuePositional(pos, biomeInterpolator); } private Value computeValuePositional(final Vec3 pos, @Nullable final SpatialAttributeInterpolator biomeInterpolator) { Value result = this.baseValue; for (EnvironmentAttributeLayer layer : this.layers) { result = (Value)(switch (layer) { case EnvironmentAttributeLayer.Constant constantLayer -> constantLayer.applyConstant(result); case EnvironmentAttributeLayer.TimeBased timeBasedLayer -> timeBasedLayer.applyTimeBased(result, this.cacheTickId); case EnvironmentAttributeLayer.Positional positionalLayer -> positionalLayer.applyPositional( result, (Vec3)Objects.requireNonNull(pos), biomeInterpolator ); default -> throw new MatchException(null, null); }); } return this.attribute.sanitizeValue(result); } private Value computeValueNotPositional() { Value result = this.baseValue; for (EnvironmentAttributeLayer layer : this.layers) { result = (Value)(switch (layer) { case EnvironmentAttributeLayer.Constant constantLayer -> constantLayer.applyConstant(result); case EnvironmentAttributeLayer.TimeBased timeBasedLayer -> timeBasedLayer.applyTimeBased(result, this.cacheTickId); case EnvironmentAttributeLayer.Positional ignored -> result; default -> throw new MatchException(null, null); }); } return this.attribute.sanitizeValue(result); } } }