package net.minecraft.advancements.critereon; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMaps; import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap.Entry; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; import net.minecraft.advancements.AdvancementHolder; import net.minecraft.advancements.AdvancementProgress; import net.minecraft.advancements.CriterionProgress; import net.minecraft.core.Holder; import net.minecraft.core.Holder.Reference; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.ServerAdvancementManager; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.stats.ServerRecipeBook; import net.minecraft.stats.Stat; import net.minecraft.stats.StatType; import net.minecraft.stats.StatsCounter; import net.minecraft.util.ExtraCodecs; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.projectile.ProjectileUtil; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.HitResult.Type; import org.jetbrains.annotations.Nullable; public record PlayerPredicate( MinMaxBounds.Ints level, GameTypePredicate gameType, List> stats, Object2BooleanMap>> recipes, Map advancements, Optional lookingAt, Optional input ) implements EntitySubPredicate { public static final int LOOKING_AT_RANGE = 100; public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( MinMaxBounds.Ints.CODEC.optionalFieldOf("level", MinMaxBounds.Ints.ANY).forGetter(PlayerPredicate::level), GameTypePredicate.CODEC.optionalFieldOf("gamemode", GameTypePredicate.ANY).forGetter(PlayerPredicate::gameType), PlayerPredicate.StatMatcher.CODEC.listOf().optionalFieldOf("stats", List.of()).forGetter(PlayerPredicate::stats), ExtraCodecs.object2BooleanMap(Recipe.KEY_CODEC).optionalFieldOf("recipes", Object2BooleanMaps.emptyMap()).forGetter(PlayerPredicate::recipes), Codec.unboundedMap(ResourceLocation.CODEC, PlayerPredicate.AdvancementPredicate.CODEC) .optionalFieldOf("advancements", Map.of()) .forGetter(PlayerPredicate::advancements), EntityPredicate.CODEC.optionalFieldOf("looking_at").forGetter(PlayerPredicate::lookingAt), InputPredicate.CODEC.optionalFieldOf("input").forGetter(PlayerPredicate::input) ) .apply(instance, PlayerPredicate::new) ); @Override public boolean matches(Entity entity, ServerLevel level, @Nullable Vec3 position) { if (!(entity instanceof ServerPlayer serverPlayer)) { return false; } else if (!this.level.matches(serverPlayer.experienceLevel)) { return false; } else if (!this.gameType.matches(serverPlayer.gameMode())) { return false; } else { StatsCounter statsCounter = serverPlayer.getStats(); for (PlayerPredicate.StatMatcher statMatcher : this.stats) { if (!statMatcher.matches(statsCounter)) { return false; } } ServerRecipeBook serverRecipeBook = serverPlayer.getRecipeBook(); for (Entry>> entry : this.recipes.object2BooleanEntrySet()) { if (serverRecipeBook.contains((ResourceKey>)entry.getKey()) != entry.getBooleanValue()) { return false; } } if (!this.advancements.isEmpty()) { PlayerAdvancements playerAdvancements = serverPlayer.getAdvancements(); ServerAdvancementManager serverAdvancementManager = serverPlayer.getServer().getAdvancements(); for (java.util.Map.Entry entry2 : this.advancements.entrySet()) { AdvancementHolder advancementHolder = serverAdvancementManager.get((ResourceLocation)entry2.getKey()); if (advancementHolder == null || !((PlayerPredicate.AdvancementPredicate)entry2.getValue()).test(playerAdvancements.getOrStartProgress(advancementHolder)) ) { return false; } } } if (this.lookingAt.isPresent()) { Vec3 vec3 = serverPlayer.getEyePosition(); Vec3 vec32 = serverPlayer.getViewVector(1.0F); Vec3 vec33 = vec3.add(vec32.x * 100.0, vec32.y * 100.0, vec32.z * 100.0); EntityHitResult entityHitResult = ProjectileUtil.getEntityHitResult( serverPlayer.level(), serverPlayer, vec3, vec33, new AABB(vec3, vec33).inflate(1.0), entityx -> !entityx.isSpectator(), 0.0F ); if (entityHitResult == null || entityHitResult.getType() != Type.ENTITY) { return false; } Entity entity2 = entityHitResult.getEntity(); if (!((EntityPredicate)this.lookingAt.get()).matches(serverPlayer, entity2) || !serverPlayer.hasLineOfSight(entity2)) { return false; } } return !this.input.isPresent() || ((InputPredicate)this.input.get()).matches(serverPlayer.getLastClientInput()); } } @Override public MapCodec codec() { return EntitySubPredicates.PLAYER; } record AdvancementCriterionsPredicate(Object2BooleanMap criterions) implements PlayerPredicate.AdvancementPredicate { public static final Codec CODEC = ExtraCodecs.object2BooleanMap(Codec.STRING) .xmap(PlayerPredicate.AdvancementCriterionsPredicate::new, PlayerPredicate.AdvancementCriterionsPredicate::criterions); public boolean test(AdvancementProgress progress) { for (Entry entry : this.criterions.object2BooleanEntrySet()) { CriterionProgress criterionProgress = progress.getCriterion((String)entry.getKey()); if (criterionProgress == null || criterionProgress.isDone() != entry.getBooleanValue()) { return false; } } return true; } } record AdvancementDonePredicate(boolean state) implements PlayerPredicate.AdvancementPredicate { public static final Codec CODEC = Codec.BOOL .xmap(PlayerPredicate.AdvancementDonePredicate::new, PlayerPredicate.AdvancementDonePredicate::state); public boolean test(AdvancementProgress progress) { return progress.isDone() == this.state; } } interface AdvancementPredicate extends Predicate { Codec CODEC = Codec.either( PlayerPredicate.AdvancementDonePredicate.CODEC, PlayerPredicate.AdvancementCriterionsPredicate.CODEC ) .xmap(Either::unwrap, advancementPredicate -> { if (advancementPredicate instanceof PlayerPredicate.AdvancementDonePredicate advancementDonePredicate) { return Either.left(advancementDonePredicate); } else if (advancementPredicate instanceof PlayerPredicate.AdvancementCriterionsPredicate advancementCriterionsPredicate) { return Either.right(advancementCriterionsPredicate); } else { throw new UnsupportedOperationException(); } }); } public static class Builder { private MinMaxBounds.Ints level = MinMaxBounds.Ints.ANY; private GameTypePredicate gameType = GameTypePredicate.ANY; private final ImmutableList.Builder> stats = ImmutableList.builder(); private final Object2BooleanMap>> recipes = new Object2BooleanOpenHashMap<>(); private final Map advancements = Maps.newHashMap(); private Optional lookingAt = Optional.empty(); private Optional input = Optional.empty(); public static PlayerPredicate.Builder player() { return new PlayerPredicate.Builder(); } public PlayerPredicate.Builder setLevel(MinMaxBounds.Ints level) { this.level = level; return this; } public PlayerPredicate.Builder addStat(StatType type, Reference value, MinMaxBounds.Ints range) { this.stats.add(new PlayerPredicate.StatMatcher<>(type, value, range)); return this; } public PlayerPredicate.Builder addRecipe(ResourceKey> recipe, boolean unlocked) { this.recipes.put(recipe, unlocked); return this; } public PlayerPredicate.Builder setGameType(GameTypePredicate gameType) { this.gameType = gameType; return this; } public PlayerPredicate.Builder setLookingAt(EntityPredicate.Builder lookingAt) { this.lookingAt = Optional.of(lookingAt.build()); return this; } public PlayerPredicate.Builder checkAdvancementDone(ResourceLocation advancement, boolean done) { this.advancements.put(advancement, new PlayerPredicate.AdvancementDonePredicate(done)); return this; } public PlayerPredicate.Builder checkAdvancementCriterions(ResourceLocation advancement, Map criterions) { this.advancements.put(advancement, new PlayerPredicate.AdvancementCriterionsPredicate(new Object2BooleanOpenHashMap<>(criterions))); return this; } public PlayerPredicate.Builder hasInput(InputPredicate input) { this.input = Optional.of(input); return this; } public PlayerPredicate build() { return new PlayerPredicate(this.level, this.gameType, this.stats.build(), this.recipes, this.advancements, this.lookingAt, this.input); } } record StatMatcher(StatType type, Holder value, MinMaxBounds.Ints range, Supplier> stat) { public static final Codec> CODEC = BuiltInRegistries.STAT_TYPE .byNameCodec() .dispatch(PlayerPredicate.StatMatcher::type, PlayerPredicate.StatMatcher::createTypedCodec); public StatMatcher(StatType type, Holder value, MinMaxBounds.Ints range) { this(type, value, range, Suppliers.memoize(() -> type.get(value.value()))); } private static MapCodec> createTypedCodec(StatType statType) { return RecordCodecBuilder.mapCodec( instance -> instance.group( statType.getRegistry().holderByNameCodec().fieldOf("stat").forGetter(PlayerPredicate.StatMatcher::value), MinMaxBounds.Ints.CODEC.optionalFieldOf("value", MinMaxBounds.Ints.ANY).forGetter(PlayerPredicate.StatMatcher::range) ) .apply(instance, (holder, ints) -> new PlayerPredicate.StatMatcher<>(statType, holder, ints)) ); } public boolean matches(StatsCounter statsCounter) { return this.range.matches(statsCounter.getValue((Stat)this.stat.get())); } } }