package net.minecraft.world.entity; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.DependantName; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.util.ProblemReporter; import net.minecraft.util.Util; import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.Difficulty; import net.minecraft.world.entity.player.Player; import net.minecraft.world.flag.FeatureElement; import net.minecraft.world.flag.FeatureFlag; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.TypedEntityData; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.pathfinder.NodeEvaluator; import net.minecraft.world.level.storage.TagValueInput; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class EntityType implements EntityTypeTest, FeatureElement { private static final Logger LOGGER = LogUtils.getLogger(); private final Holder.Reference> builtInRegistryHolder = BuiltInRegistries.ENTITY_TYPE.createIntrusiveHolder(this); public static final Codec> CODEC = BuiltInRegistries.ENTITY_TYPE.byNameCodec(); public static final StreamCodec> STREAM_CODEC = ByteBufCodecs.registry(Registries.ENTITY_TYPE); private final EntityType.EntityFactory factory; private final MobCategory category; private final TagKey immuneTo; private final boolean serialize; private final boolean summon; private final boolean fireImmune; private final boolean canSpawnFarFromPlayer; private final int clientTrackingRange; private final int updateInterval; private final String descriptionId; @Nullable private Component description; private final Optional> lootTable; private final EntityDimensions dimensions; private final float spawnDimensionsScale; private final FeatureFlagSet requiredFeatures; private final boolean allowedInPeaceful; public static Identifier getKey(final EntityType type) { return BuiltInRegistries.ENTITY_TYPE.getKey(type); } public EntityType( final EntityType.EntityFactory factory, final MobCategory category, final boolean serialize, final boolean summon, final boolean fireImmune, final boolean canSpawnFarFromPlayer, final TagKey immuneTo, final EntityDimensions dimensions, final float spawnDimensionsScale, final int clientTrackingRange, final int updateInterval, final String descriptionId, final Optional> lootTable, final FeatureFlagSet requiredFeatures, final boolean allowedInPeaceful ) { this.factory = factory; this.category = category; this.canSpawnFarFromPlayer = canSpawnFarFromPlayer; this.serialize = serialize; this.summon = summon; this.fireImmune = fireImmune; this.immuneTo = immuneTo; this.dimensions = dimensions; this.spawnDimensionsScale = spawnDimensionsScale; this.clientTrackingRange = clientTrackingRange; this.updateInterval = updateInterval; this.descriptionId = descriptionId; this.lootTable = lootTable; this.requiredFeatures = requiredFeatures; this.allowedInPeaceful = allowedInPeaceful; } @Nullable public T spawn( final ServerLevel level, @Nullable final ItemStack itemStack, @Nullable final LivingEntity user, final BlockPos spawnPos, final EntitySpawnReason spawnReason, final boolean tryMoveDown, final boolean movedUp ) { Consumer postSpawnConfig; if (itemStack != null) { postSpawnConfig = createDefaultStackConfig(level, itemStack, user); } else { postSpawnConfig = entity -> {}; } return this.spawn(level, postSpawnConfig, spawnPos, spawnReason, tryMoveDown, movedUp); } public static Consumer createDefaultStackConfig(final Level level, final ItemStack itemStack, @Nullable final LivingEntity user) { return appendDefaultStackConfig(entity -> {}, level, itemStack, user); } public static Consumer appendDefaultStackConfig( final Consumer initialConfig, final Level level, final ItemStack itemStack, @Nullable final LivingEntity user ) { return appendCustomEntityStackConfig(appendComponentsConfig(initialConfig, itemStack), level, itemStack, user); } public static Consumer appendComponentsConfig(final Consumer initialConfig, final ItemStack itemStack) { return initialConfig.andThen(entity -> entity.applyComponentsFromItemStack(itemStack)); } public static Consumer appendCustomEntityStackConfig( final Consumer initialConfig, final Level level, final ItemStack itemStack, @Nullable final LivingEntity user ) { TypedEntityData> entityData = itemStack.get(DataComponents.ENTITY_DATA); return entityData != null ? initialConfig.andThen(entity -> updateCustomEntityTag(level, user, entity, entityData)) : initialConfig; } @Nullable public T spawn(final ServerLevel level, final BlockPos spawnPos, final EntitySpawnReason spawnReason) { return this.spawn(level, null, spawnPos, spawnReason, false, false); } @Nullable public T spawn( final ServerLevel level, @Nullable final Consumer postSpawnConfig, final BlockPos spawnPos, final EntitySpawnReason spawnReason, final boolean tryMoveDown, final boolean movedUp ) { T entity = this.create(level, postSpawnConfig, spawnPos, spawnReason, tryMoveDown, movedUp); if (entity != null) { level.addFreshEntityWithPassengers(entity); if (entity instanceof Mob mob) { mob.playAmbientSound(); } } return entity; } @Nullable public T create( final ServerLevel level, @Nullable final Consumer postSpawnConfig, final BlockPos spawnPos, final EntitySpawnReason spawnReason, final boolean tryMoveDown, final boolean movedUp ) { T entity = this.create(level, spawnReason); if (entity == null) { return null; } else { double yOff; if (tryMoveDown) { entity.setPos(spawnPos.getX() + 0.5, spawnPos.getY() + 1, spawnPos.getZ() + 0.5); yOff = getYOffset(level, spawnPos, movedUp, entity.getBoundingBox()); } else { yOff = 0.0; } entity.snapTo(spawnPos.getX() + 0.5, spawnPos.getY() + yOff, spawnPos.getZ() + 0.5, Mth.wrapDegrees(level.getRandom().nextFloat() * 360.0F), 0.0F); if (entity instanceof Mob mob) { mob.yHeadRot = mob.getYRot(); mob.yBodyRot = mob.getYRot(); mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), spawnReason, null); } if (postSpawnConfig != null) { postSpawnConfig.accept(entity); } return entity; } } protected static double getYOffset(final LevelReader level, final BlockPos spawnPos, final boolean movedUp, final AABB entityBox) { AABB aabb = new AABB(spawnPos); if (movedUp) { aabb = aabb.expandTowards(0.0, -1.0, 0.0); } Iterable shapes = level.getCollisions(null, aabb); return 1.0 + Shapes.collide(Direction.Axis.Y, entityBox, shapes, movedUp ? -2.0 : -1.0); } public static void updateCustomEntityTag( final Level level, @Nullable final LivingEntity user, @Nullable final Entity entity, final TypedEntityData> entityData ) { MinecraftServer server = level.getServer(); if (server != null && entity != null) { if (entity.getType() == entityData.type()) { if (level.isClientSide() || !entity.getType().onlyOpCanSetNbt() || user instanceof Player player && server.getPlayerList().isOp(player.nameAndId())) { entityData.loadInto(entity); } } } } public boolean canSerialize() { return this.serialize; } public boolean canSummon() { return this.summon; } public boolean fireImmune() { return this.fireImmune; } public boolean canSpawnFarFromPlayer() { return this.canSpawnFarFromPlayer; } public MobCategory getCategory() { return this.category; } public String getDescriptionId() { return this.descriptionId; } public Component getDescription() { if (this.description == null) { this.description = Component.translatable(this.getDescriptionId()); } return this.description; } public String toString() { return this.getDescriptionId(); } public String toShortString() { int dot = this.getDescriptionId().lastIndexOf(46); return dot == -1 ? this.getDescriptionId() : this.getDescriptionId().substring(dot + 1); } public Optional> getDefaultLootTable() { return this.lootTable; } public float getWidth() { return this.dimensions.width(); } public float getHeight() { return this.dimensions.height(); } @Override public FeatureFlagSet requiredFeatures() { return this.requiredFeatures; } public boolean canSpawn(final Level level) { return !this.isEnabled(level.enabledFeatures()) ? false : this.isAllowedInPeaceful() || level.getDifficulty() != Difficulty.PEACEFUL; } @Nullable public T create(final Level level, final EntitySpawnReason reason) { return !this.canSpawn(level) ? null : this.factory.create(this, level); } public static Optional create(final ValueInput input, final Level level, final EntitySpawnReason reason) { return Util.ifElse( by(input).map(type -> type.create(level, reason)), entity -> entity.load(input), () -> LOGGER.warn("Skipping Entity with id {}", input.getStringOr("id", "[invalid]")) ); } public static Optional create(final EntityType type, final ValueInput input, final Level level, final EntitySpawnReason reason) { Optional entity = Optional.ofNullable(type.create(level, reason)); entity.ifPresent(e -> e.load(input)); return entity; } public AABB getSpawnAABB(final double x, final double y, final double z) { float halfWidth = this.spawnDimensionsScale * this.getWidth() / 2.0F; float height = this.spawnDimensionsScale * this.getHeight(); return new AABB(x - halfWidth, y, z - halfWidth, x + halfWidth, y + height, z + halfWidth); } public boolean isBlockDangerous(final BlockState state) { if (state.is(this.immuneTo)) { return false; } else { return !this.fireImmune && NodeEvaluator.isBurningBlock(state) ? true : state.is(Blocks.WITHER_ROSE) || state.is(Blocks.SWEET_BERRY_BUSH) || state.is(Blocks.CACTUS) || state.is(Blocks.POWDER_SNOW); } } public EntityDimensions getDimensions() { return this.dimensions; } public static Optional> by(final ValueInput input) { return input.read("id", CODEC); } @Nullable public static Entity loadEntityRecursive(final CompoundTag tag, final Level level, final EntitySpawnReason reason, final EntityProcessor postLoad) { Entity var5; try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(LOGGER)) { var5 = loadEntityRecursive(TagValueInput.create(reporter, level.registryAccess(), tag), level, reason, postLoad); } return var5; } @Nullable public static Entity loadEntityRecursive( final EntityType type, final CompoundTag tag, final Level level, final EntitySpawnReason reason, final EntityProcessor postLoad ) { Entity var6; try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(LOGGER)) { var6 = loadEntityRecursive(type, TagValueInput.create(reporter, level.registryAccess(), tag), level, reason, postLoad); } return var6; } @Nullable public static Entity loadEntityRecursive(final ValueInput input, final Level level, final EntitySpawnReason reason, final EntityProcessor postLoad) { return (Entity)loadStaticEntity(input, level, reason) .map(postLoad::process) .map(entity -> loadPassengersRecursive(entity, input, level, reason, postLoad)) .orElse(null); } @Nullable public static Entity loadEntityRecursive( final EntityType type, final ValueInput input, final Level level, final EntitySpawnReason reason, final EntityProcessor postLoad ) { return (Entity)loadStaticEntity(type, input, level, reason) .map(postLoad::process) .map(entity -> loadPassengersRecursive(entity, input, level, reason, postLoad)) .orElse(null); } private static Entity loadPassengersRecursive( final Entity entity, final ValueInput input, final Level level, final EntitySpawnReason reason, final EntityProcessor postLoad ) { for (ValueInput passengerTag : input.childrenListOrEmpty("Passengers")) { Entity passenger = loadEntityRecursive(passengerTag, level, reason, postLoad); if (passenger != null) { passenger.startRiding(entity, true, false); } } return entity; } public static Stream loadEntitiesRecursive(final ValueInput.ValueInputList entities, final Level level, final EntitySpawnReason reason) { return entities.stream().mapMulti((tag, output) -> loadEntityRecursive(tag, level, reason, entity -> { output.accept(entity); return entity; })); } private static Optional loadStaticEntity(final ValueInput input, final Level level, final EntitySpawnReason reason) { try { return create(input, level, reason); } catch (RuntimeException var4) { LOGGER.warn("Exception loading entity: ", (Throwable)var4); return Optional.empty(); } } private static Optional loadStaticEntity(final EntityType type, final ValueInput input, final Level level, final EntitySpawnReason reason) { try { return create(type, input, level, reason); } catch (RuntimeException var5) { LOGGER.warn("Exception loading entity: ", (Throwable)var5); return Optional.empty(); } } public int clientTrackingRange() { return this.clientTrackingRange; } public int updateInterval() { return this.updateInterval; } public boolean trackDeltas() { return this != EntityTypes.PLAYER && this != EntityTypes.LLAMA_SPIT && this != EntityTypes.WITHER && this != EntityTypes.BAT && this != EntityTypes.ITEM_FRAME && this != EntityTypes.GLOW_ITEM_FRAME && this != EntityTypes.LEASH_KNOT && this != EntityTypes.PAINTING && this != EntityTypes.END_CRYSTAL && this != EntityTypes.EVOKER_FANGS; } @Nullable public T tryCast(final Entity entity) { return (T)(entity.getType() == this ? entity : null); } @Override public Class getBaseClass() { return Entity.class; } @Deprecated public Holder.Reference> builtInRegistryHolder() { return this.builtInRegistryHolder; } public boolean isAllowedInPeaceful() { return this.allowedInPeaceful; } public boolean onlyOpCanSetNbt() { return EntityTypes.OP_ONLY_CUSTOM_DATA.contains(this); } public static class Builder implements net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityType.Builder { private final EntityType.EntityFactory factory; private final MobCategory category; private TagKey immuneTo = BlockTags.DEFAULT_IMMUNE_TO; private boolean serialize = true; private boolean summon = true; private boolean fireImmune; private boolean canSpawnFarFromPlayer; private int clientTrackingRange = 5; private int updateInterval = 3; private EntityDimensions dimensions = EntityDimensions.scalable(0.6F, 1.8F); private float spawnDimensionsScale = 1.0F; private EntityAttachments.Builder attachments = EntityAttachments.builder(); private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; private DependantName, Optional>> lootTable = id -> Optional.of( ResourceKey.create(Registries.LOOT_TABLE, id.identifier().withPrefix("entities/")) ); private final DependantName, String> descriptionId = id -> Util.makeDescriptionId("entity", id.identifier()); private boolean allowedInPeaceful = true; private Builder(final EntityType.EntityFactory factory, final MobCategory category) { this.factory = factory; this.category = category; this.canSpawnFarFromPlayer = category == MobCategory.CREATURE || category == MobCategory.MISC; } public static EntityType.Builder of(final EntityType.EntityFactory factory, final MobCategory category) { return new EntityType.Builder<>(factory, category); } public static EntityType.Builder createNothing(final MobCategory category) { return new EntityType.Builder<>((t, l) -> null, category); } public EntityType.Builder sized(final float width, final float height) { this.dimensions = EntityDimensions.scalable(width, height); return this; } public EntityType.Builder spawnDimensionsScale(final float scale) { this.spawnDimensionsScale = scale; return this; } public EntityType.Builder eyeHeight(final float eyeHeight) { this.dimensions = this.dimensions.withEyeHeight(eyeHeight); return this; } public EntityType.Builder passengerAttachments(final float... offsetYs) { for (float offsetY : offsetYs) { this.attachments = this.attachments.attach(EntityAttachment.PASSENGER, 0.0F, offsetY, 0.0F); } return this; } public EntityType.Builder passengerAttachments(final Vec3... points) { for (Vec3 point : points) { this.attachments = this.attachments.attach(EntityAttachment.PASSENGER, point); } return this; } public EntityType.Builder vehicleAttachment(final Vec3 point) { return this.attach(EntityAttachment.VEHICLE, point); } public EntityType.Builder ridingOffset(final float ridingOffset) { return this.attach(EntityAttachment.VEHICLE, 0.0F, -ridingOffset, 0.0F); } public EntityType.Builder nameTagOffset(final float nameTagOffset) { return this.attach(EntityAttachment.NAME_TAG, 0.0F, nameTagOffset, 0.0F); } public EntityType.Builder attach(final EntityAttachment attachment, final float x, final float y, final float z) { this.attachments = this.attachments.attach(attachment, x, y, z); return this; } public EntityType.Builder attach(final EntityAttachment attachment, final Vec3 point) { this.attachments = this.attachments.attach(attachment, point); return this; } public EntityType.Builder noSummon() { this.summon = false; return this; } public EntityType.Builder noSave() { this.serialize = false; return this; } public EntityType.Builder fireImmune() { this.fireImmune = true; return this; } public EntityType.Builder immuneTo(final TagKey tag) { this.immuneTo = tag; return this; } public EntityType.Builder canSpawnFarFromPlayer() { this.canSpawnFarFromPlayer = true; return this; } public EntityType.Builder clientTrackingRange(final int clientChunkRange) { this.clientTrackingRange = clientChunkRange; return this; } public EntityType.Builder updateInterval(final int updateInterval) { this.updateInterval = updateInterval; return this; } public EntityType.Builder requiredFeatures(final FeatureFlag... flags) { this.requiredFeatures = FeatureFlags.REGISTRY.subset(flags); return this; } public EntityType.Builder noLootTable() { this.lootTable = DependantName.fixed(Optional.empty()); return this; } public EntityType.Builder notInPeaceful() { this.allowedInPeaceful = false; return this; } public EntityType build(final ResourceKey> name) { if (this.serialize) { Util.fetchChoiceType(References.ENTITY_TREE, name.identifier().toString()); } return new EntityType<>( this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions.withAttachments(this.attachments), this.spawnDimensionsScale, this.clientTrackingRange, this.updateInterval, this.descriptionId.get(name), this.lootTable.get(name), this.requiredFeatures, this.allowedInPeaceful ); } } @FunctionalInterface public interface EntityFactory { @Nullable T create(final EntityType entityType, final Level level); } }