package net.minecraft.advancements.critereon; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import net.minecraft.core.HolderGetter; import net.minecraft.core.component.DataComponentGetter; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.TagKey; import net.minecraft.util.ExtraCodecs; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; import net.minecraft.world.level.storage.loot.predicates.LootItemEntityPropertyCondition; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.Team; import org.jetbrains.annotations.Nullable; public record EntityPredicate( Optional entityType, Optional distanceToPlayer, Optional movement, EntityPredicate.LocationWrapper location, Optional effects, Optional nbt, Optional flags, Optional equipment, Optional subPredicate, Optional periodicTick, Optional vehicle, Optional passenger, Optional targetedEntity, Optional team, Optional slots, DataComponentMatchers components ) { public static final Codec CODEC = Codec.recursive( "EntityPredicate", codec -> RecordCodecBuilder.create( instance -> instance.group( EntityTypePredicate.CODEC.optionalFieldOf("type").forGetter(EntityPredicate::entityType), DistancePredicate.CODEC.optionalFieldOf("distance").forGetter(EntityPredicate::distanceToPlayer), MovementPredicate.CODEC.optionalFieldOf("movement").forGetter(EntityPredicate::movement), EntityPredicate.LocationWrapper.CODEC.forGetter(EntityPredicate::location), MobEffectsPredicate.CODEC.optionalFieldOf("effects").forGetter(EntityPredicate::effects), NbtPredicate.CODEC.optionalFieldOf("nbt").forGetter(EntityPredicate::nbt), EntityFlagsPredicate.CODEC.optionalFieldOf("flags").forGetter(EntityPredicate::flags), EntityEquipmentPredicate.CODEC.optionalFieldOf("equipment").forGetter(EntityPredicate::equipment), EntitySubPredicate.CODEC.optionalFieldOf("type_specific").forGetter(EntityPredicate::subPredicate), ExtraCodecs.POSITIVE_INT.optionalFieldOf("periodic_tick").forGetter(EntityPredicate::periodicTick), codec.optionalFieldOf("vehicle").forGetter(EntityPredicate::vehicle), codec.optionalFieldOf("passenger").forGetter(EntityPredicate::passenger), codec.optionalFieldOf("targeted_entity").forGetter(EntityPredicate::targetedEntity), Codec.STRING.optionalFieldOf("team").forGetter(EntityPredicate::team), SlotsPredicate.CODEC.optionalFieldOf("slots").forGetter(EntityPredicate::slots), DataComponentMatchers.CODEC.forGetter(EntityPredicate::components) ) .apply(instance, EntityPredicate::new) ) ); public static final Codec ADVANCEMENT_CODEC = Codec.withAlternative(ContextAwarePredicate.CODEC, CODEC, EntityPredicate::wrap); public static ContextAwarePredicate wrap(EntityPredicate.Builder builder) { return wrap(builder.build()); } public static Optional wrap(Optional predicate) { return predicate.map(EntityPredicate::wrap); } public static List wrap(EntityPredicate.Builder... builders) { return Stream.of(builders).map(EntityPredicate::wrap).toList(); } public static ContextAwarePredicate wrap(EntityPredicate predicate) { LootItemCondition lootItemCondition = LootItemEntityPropertyCondition.hasProperties(LootContext.EntityTarget.THIS, predicate).build(); return new ContextAwarePredicate(List.of(lootItemCondition)); } public boolean matches(ServerPlayer player, @Nullable Entity entity) { return this.matches(player.serverLevel(), player.position(), entity); } public boolean matches(ServerLevel level, @Nullable Vec3 position, @Nullable Entity entity) { if (entity == null) { return false; } else if (this.entityType.isPresent() && !((EntityTypePredicate)this.entityType.get()).matches(entity.getType())) { return false; } else { if (position == null) { if (this.distanceToPlayer.isPresent()) { return false; } } else if (this.distanceToPlayer.isPresent() && !((DistancePredicate)this.distanceToPlayer.get()).matches(position.x, position.y, position.z, entity.getX(), entity.getY(), entity.getZ())) { return false; } if (this.movement.isPresent()) { Vec3 vec3 = entity.getKnownMovement(); Vec3 vec32 = vec3.scale(20.0); if (!((MovementPredicate)this.movement.get()).matches(vec32.x, vec32.y, vec32.z, entity.fallDistance)) { return false; } } if (this.location.located.isPresent() && !((LocationPredicate)this.location.located.get()).matches(level, entity.getX(), entity.getY(), entity.getZ())) { return false; } else { if (this.location.steppingOn.isPresent()) { Vec3 vec3 = Vec3.atCenterOf(entity.getOnPos()); if (!entity.onGround() || !((LocationPredicate)this.location.steppingOn.get()).matches(level, vec3.x(), vec3.y(), vec3.z())) { return false; } } if (this.location.affectsMovement.isPresent()) { Vec3 vec3 = Vec3.atCenterOf(entity.getBlockPosBelowThatAffectsMyMovement()); if (!((LocationPredicate)this.location.affectsMovement.get()).matches(level, vec3.x(), vec3.y(), vec3.z())) { return false; } } if (this.effects.isPresent() && !((MobEffectsPredicate)this.effects.get()).matches(entity)) { return false; } else if (this.flags.isPresent() && !((EntityFlagsPredicate)this.flags.get()).matches(entity)) { return false; } else if (this.equipment.isPresent() && !((EntityEquipmentPredicate)this.equipment.get()).matches(entity)) { return false; } else if (this.subPredicate.isPresent() && !((EntitySubPredicate)this.subPredicate.get()).matches(entity, level, position)) { return false; } else if (this.vehicle.isPresent() && !((EntityPredicate)this.vehicle.get()).matches(level, position, entity.getVehicle())) { return false; } else if (this.passenger.isPresent() && entity.getPassengers().stream().noneMatch(entityx -> ((EntityPredicate)this.passenger.get()).matches(level, position, entityx))) { return false; } else if (this.targetedEntity.isPresent() && !((EntityPredicate)this.targetedEntity.get()).matches(level, position, entity instanceof Mob ? ((Mob)entity).getTarget() : null)) { return false; } else if (this.periodicTick.isPresent() && entity.tickCount % (Integer)this.periodicTick.get() != 0) { return false; } else { if (this.team.isPresent()) { Team team = entity.getTeam(); if (team == null || !((String)this.team.get()).equals(team.getName())) { return false; } } if (this.slots.isPresent() && !((SlotsPredicate)this.slots.get()).matches(entity)) { return false; } else { return !this.components.test((DataComponentGetter)entity) ? false : this.nbt.isEmpty() || ((NbtPredicate)this.nbt.get()).matches(entity); } } } } } public static LootContext createContext(ServerPlayer player, Entity entity) { LootParams lootParams = new LootParams.Builder(player.serverLevel()) .withParameter(LootContextParams.THIS_ENTITY, entity) .withParameter(LootContextParams.ORIGIN, player.position()) .create(LootContextParamSets.ADVANCEMENT_ENTITY); return new LootContext.Builder(lootParams).create(Optional.empty()); } public static class Builder { private Optional entityType = Optional.empty(); private Optional distanceToPlayer = Optional.empty(); private Optional movement = Optional.empty(); private Optional located = Optional.empty(); private Optional steppingOnLocation = Optional.empty(); private Optional movementAffectedBy = Optional.empty(); private Optional effects = Optional.empty(); private Optional nbt = Optional.empty(); private Optional flags = Optional.empty(); private Optional equipment = Optional.empty(); private Optional subPredicate = Optional.empty(); private Optional periodicTick = Optional.empty(); private Optional vehicle = Optional.empty(); private Optional passenger = Optional.empty(); private Optional targetedEntity = Optional.empty(); private Optional team = Optional.empty(); private Optional slots = Optional.empty(); private DataComponentMatchers components = DataComponentMatchers.ANY; public static EntityPredicate.Builder entity() { return new EntityPredicate.Builder(); } public EntityPredicate.Builder of(HolderGetter> entityTypeRegistry, EntityType entityType) { this.entityType = Optional.of(EntityTypePredicate.of(entityTypeRegistry, entityType)); return this; } public EntityPredicate.Builder of(HolderGetter> entityTypeRegistry, TagKey> entityTypeTag) { this.entityType = Optional.of(EntityTypePredicate.of(entityTypeRegistry, entityTypeTag)); return this; } public EntityPredicate.Builder entityType(EntityTypePredicate entityType) { this.entityType = Optional.of(entityType); return this; } public EntityPredicate.Builder distance(DistancePredicate distanceToPlayer) { this.distanceToPlayer = Optional.of(distanceToPlayer); return this; } public EntityPredicate.Builder moving(MovementPredicate movement) { this.movement = Optional.of(movement); return this; } public EntityPredicate.Builder located(LocationPredicate.Builder location) { this.located = Optional.of(location.build()); return this; } public EntityPredicate.Builder steppingOn(LocationPredicate.Builder steppingOnLocation) { this.steppingOnLocation = Optional.of(steppingOnLocation.build()); return this; } public EntityPredicate.Builder movementAffectedBy(LocationPredicate.Builder movementAffectedBy) { this.movementAffectedBy = Optional.of(movementAffectedBy.build()); return this; } public EntityPredicate.Builder effects(MobEffectsPredicate.Builder effects) { this.effects = effects.build(); return this; } public EntityPredicate.Builder nbt(NbtPredicate nbt) { this.nbt = Optional.of(nbt); return this; } public EntityPredicate.Builder flags(EntityFlagsPredicate.Builder flags) { this.flags = Optional.of(flags.build()); return this; } public EntityPredicate.Builder equipment(EntityEquipmentPredicate.Builder equipment) { this.equipment = Optional.of(equipment.build()); return this; } public EntityPredicate.Builder equipment(EntityEquipmentPredicate equipment) { this.equipment = Optional.of(equipment); return this; } public EntityPredicate.Builder subPredicate(EntitySubPredicate subPredicate) { this.subPredicate = Optional.of(subPredicate); return this; } public EntityPredicate.Builder periodicTick(int periodicTick) { this.periodicTick = Optional.of(periodicTick); return this; } public EntityPredicate.Builder vehicle(EntityPredicate.Builder vehicle) { this.vehicle = Optional.of(vehicle.build()); return this; } public EntityPredicate.Builder passenger(EntityPredicate.Builder passenger) { this.passenger = Optional.of(passenger.build()); return this; } public EntityPredicate.Builder targetedEntity(EntityPredicate.Builder targetedEntity) { this.targetedEntity = Optional.of(targetedEntity.build()); return this; } public EntityPredicate.Builder team(String team) { this.team = Optional.of(team); return this; } public EntityPredicate.Builder slots(SlotsPredicate slots) { this.slots = Optional.of(slots); return this; } public EntityPredicate.Builder components(DataComponentMatchers components) { this.components = components; return this; } public EntityPredicate build() { return new EntityPredicate( this.entityType, this.distanceToPlayer, this.movement, new EntityPredicate.LocationWrapper(this.located, this.steppingOnLocation, this.movementAffectedBy), this.effects, this.nbt, this.flags, this.equipment, this.subPredicate, this.periodicTick, this.vehicle, this.passenger, this.targetedEntity, this.team, this.slots, this.components ); } } public record LocationWrapper(Optional located, Optional steppingOn, Optional affectsMovement) { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( LocationPredicate.CODEC.optionalFieldOf("location").forGetter(EntityPredicate.LocationWrapper::located), LocationPredicate.CODEC.optionalFieldOf("stepping_on").forGetter(EntityPredicate.LocationWrapper::steppingOn), LocationPredicate.CODEC.optionalFieldOf("movement_affected_by").forGetter(EntityPredicate.LocationWrapper::affectsMovement) ) .apply(instance, EntityPredicate.LocationWrapper::new) ); } }