256 lines
11 KiB
Java
256 lines
11 KiB
Java
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<PlayerPredicate.StatMatcher<?>> stats,
|
|
Object2BooleanMap<ResourceKey<Recipe<?>>> recipes,
|
|
Map<ResourceLocation, PlayerPredicate.AdvancementPredicate> advancements,
|
|
Optional<EntityPredicate> lookingAt,
|
|
Optional<InputPredicate> input
|
|
) implements EntitySubPredicate {
|
|
public static final int LOOKING_AT_RANGE = 100;
|
|
public static final MapCodec<PlayerPredicate> 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<ResourceKey<Recipe<?>>> entry : this.recipes.object2BooleanEntrySet()) {
|
|
if (serverRecipeBook.contains((ResourceKey<Recipe<?>>)entry.getKey()) != entry.getBooleanValue()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!this.advancements.isEmpty()) {
|
|
PlayerAdvancements playerAdvancements = serverPlayer.getAdvancements();
|
|
ServerAdvancementManager serverAdvancementManager = serverPlayer.getServer().getAdvancements();
|
|
|
|
for (java.util.Map.Entry<ResourceLocation, PlayerPredicate.AdvancementPredicate> 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<PlayerPredicate> codec() {
|
|
return EntitySubPredicates.PLAYER;
|
|
}
|
|
|
|
record AdvancementCriterionsPredicate(Object2BooleanMap<String> criterions) implements PlayerPredicate.AdvancementPredicate {
|
|
public static final Codec<PlayerPredicate.AdvancementCriterionsPredicate> CODEC = ExtraCodecs.object2BooleanMap(Codec.STRING)
|
|
.xmap(PlayerPredicate.AdvancementCriterionsPredicate::new, PlayerPredicate.AdvancementCriterionsPredicate::criterions);
|
|
|
|
public boolean test(AdvancementProgress progress) {
|
|
for (Entry<String> 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<PlayerPredicate.AdvancementDonePredicate> CODEC = Codec.BOOL
|
|
.xmap(PlayerPredicate.AdvancementDonePredicate::new, PlayerPredicate.AdvancementDonePredicate::state);
|
|
|
|
public boolean test(AdvancementProgress progress) {
|
|
return progress.isDone() == this.state;
|
|
}
|
|
}
|
|
|
|
interface AdvancementPredicate extends Predicate<AdvancementProgress> {
|
|
Codec<PlayerPredicate.AdvancementPredicate> 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<PlayerPredicate.StatMatcher<?>> stats = ImmutableList.builder();
|
|
private final Object2BooleanMap<ResourceKey<Recipe<?>>> recipes = new Object2BooleanOpenHashMap<>();
|
|
private final Map<ResourceLocation, PlayerPredicate.AdvancementPredicate> advancements = Maps.<ResourceLocation, PlayerPredicate.AdvancementPredicate>newHashMap();
|
|
private Optional<EntityPredicate> lookingAt = Optional.empty();
|
|
private Optional<InputPredicate> 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 <T> PlayerPredicate.Builder addStat(StatType<T> type, Reference<T> value, MinMaxBounds.Ints range) {
|
|
this.stats.add(new PlayerPredicate.StatMatcher<>(type, value, range));
|
|
return this;
|
|
}
|
|
|
|
public PlayerPredicate.Builder addRecipe(ResourceKey<Recipe<?>> 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<String, Boolean> 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<T>(StatType<T> type, Holder<T> value, MinMaxBounds.Ints range, Supplier<Stat<T>> stat) {
|
|
public static final Codec<PlayerPredicate.StatMatcher<?>> CODEC = BuiltInRegistries.STAT_TYPE
|
|
.byNameCodec()
|
|
.dispatch(PlayerPredicate.StatMatcher::type, PlayerPredicate.StatMatcher::createTypedCodec);
|
|
|
|
public StatMatcher(StatType<T> type, Holder<T> value, MinMaxBounds.Ints range) {
|
|
this(type, value, range, Suppliers.memoize(() -> type.get(value.value())));
|
|
}
|
|
|
|
private static <T> MapCodec<PlayerPredicate.StatMatcher<T>> createTypedCodec(StatType<T> 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()));
|
|
}
|
|
}
|
|
}
|