package net.minecraft.world.entity.animal; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.ItemTags; import net.minecraft.util.Mth; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; import net.minecraft.world.entity.ai.goal.BreedGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.FollowParentGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.TemptGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.variant.SpawnContext; import net.minecraft.world.entity.variant.VariantUtils; import net.minecraft.world.item.EitherHolder; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class Chicken extends Animal { private static final EntityDimensions BABY_DIMENSIONS = EntityType.CHICKEN.getDimensions().scale(0.5F).withEyeHeight(0.2975F); private static final EntityDataAccessor> DATA_VARIANT_ID = SynchedEntityData.defineId( Chicken.class, EntityDataSerializers.CHICKEN_VARIANT ); private static final boolean DEFAULT_CHICKEN_JOCKEY = false; public float flap; public float flapSpeed; public float oFlapSpeed; public float oFlap; public float flapping = 1.0F; private float nextFlap = 1.0F; public int eggTime; public boolean isChickenJockey = false; public Chicken(EntityType entityType, Level level) { super(entityType, level); this.eggTime = this.random.nextInt(6000) + 6000; this.setPathfindingMalus(PathType.WATER, 0.0F); } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(1, new PanicGoal(this, 1.4)); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, itemStack -> itemStack.is(ItemTags.CHICKEN_FOOD), false)); this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); } @Override public EntityDimensions getDefaultDimensions(Pose pose) { return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose); } public static Builder createAttributes() { return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0).add(Attributes.MOVEMENT_SPEED, 0.25); } @Override public void aiStep() { super.aiStep(); this.oFlap = this.flap; this.oFlapSpeed = this.flapSpeed; this.flapSpeed = this.flapSpeed + (this.onGround() ? -1.0F : 4.0F) * 0.3F; this.flapSpeed = Mth.clamp(this.flapSpeed, 0.0F, 1.0F); if (!this.onGround() && this.flapping < 1.0F) { this.flapping = 1.0F; } this.flapping *= 0.9F; Vec3 vec3 = this.getDeltaMovement(); if (!this.onGround() && vec3.y < 0.0) { this.setDeltaMovement(vec3.multiply(1.0, 0.6, 1.0)); } this.flap = this.flap + this.flapping * 2.0F; if (this.level() instanceof ServerLevel serverLevel && this.isAlive() && !this.isBaby() && !this.isChickenJockey() && --this.eggTime <= 0) { if (this.dropFromGiftLootTable(serverLevel, BuiltInLootTables.CHICKEN_LAY, this::spawnAtLocation)) { this.playSound(SoundEvents.CHICKEN_EGG, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); this.gameEvent(GameEvent.ENTITY_PLACE); } this.eggTime = this.random.nextInt(6000) + 6000; } } @Override protected boolean isFlapping() { return this.flyDist > this.nextFlap; } @Override protected void onFlap() { this.nextFlap = this.flyDist + this.flapSpeed / 2.0F; } @Override protected SoundEvent getAmbientSound() { return SoundEvents.CHICKEN_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource damageSource) { return SoundEvents.CHICKEN_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.CHICKEN_DEATH; } @Override protected void playStepSound(BlockPos pos, BlockState state) { this.playSound(SoundEvents.CHICKEN_STEP, 0.15F, 1.0F); } @Nullable public Chicken getBreedOffspring(ServerLevel serverLevel, AgeableMob ageableMob) { Chicken chicken = EntityType.CHICKEN.create(serverLevel, EntitySpawnReason.BREEDING); if (chicken != null && ageableMob instanceof Chicken chicken2) { chicken.setVariant(this.random.nextBoolean() ? this.getVariant() : chicken2.getVariant()); } return chicken; } @Override public SpawnGroupData finalizeSpawn( ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData ) { ChickenVariants.selectVariantToSpawn(this.random, this.registryAccess(), SpawnContext.create(level, this.blockPosition())).ifPresent(this::setVariant); return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } @Override public boolean isFood(ItemStack stack) { return stack.is(ItemTags.CHICKEN_FOOD); } @Override protected int getBaseExperienceReward(ServerLevel level) { return this.isChickenJockey() ? 10 : super.getBaseExperienceReward(level); } @Override protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) { super.defineSynchedData(builder); builder.define(DATA_VARIANT_ID, VariantUtils.getDefaultOrAny(this.registryAccess(), ChickenVariants.TEMPERATE)); } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.isChickenJockey = tag.getBooleanOr("IsChickenJockey", false); tag.getInt("EggLayTime").ifPresent(integer -> this.eggTime = integer); VariantUtils.readVariant(tag, this.registryAccess(), Registries.CHICKEN_VARIANT).ifPresent(this::setVariant); } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); tag.putBoolean("IsChickenJockey", this.isChickenJockey); tag.putInt("EggLayTime", this.eggTime); VariantUtils.writeVariant(tag, this.getVariant()); } public void setVariant(Holder variant) { this.entityData.set(DATA_VARIANT_ID, variant); } public Holder getVariant() { return this.entityData.get(DATA_VARIANT_ID); } @Nullable @Override public T get(DataComponentType component) { return component == DataComponents.CHICKEN_VARIANT ? castComponentValue((DataComponentType)component, new EitherHolder<>(this.getVariant())) : super.get(component); } @Override protected void applyImplicitComponents(DataComponentGetter componentGetter) { this.applyImplicitComponentIfPresent(componentGetter, DataComponents.CHICKEN_VARIANT); super.applyImplicitComponents(componentGetter); } @Override protected boolean applyImplicitComponent(DataComponentType component, T value) { if (component == DataComponents.CHICKEN_VARIANT) { Optional> optional = castComponentValue(DataComponents.CHICKEN_VARIANT, value).unwrap(this.registryAccess()); if (optional.isPresent()) { this.setVariant((Holder)optional.get()); return true; } else { return false; } } else { return super.applyImplicitComponent(component, value); } } @Override public boolean removeWhenFarAway(double distanceToClosestPlayer) { return this.isChickenJockey(); } @Override protected void positionRider(Entity passenger, Entity.MoveFunction callback) { super.positionRider(passenger, callback); if (passenger instanceof LivingEntity) { ((LivingEntity)passenger).yBodyRot = this.yBodyRot; } } /** * Determines if this chicken is a jokey with a zombie riding it. */ public boolean isChickenJockey() { return this.isChickenJockey; } /** * Sets whether this chicken is a jockey or not. */ public void setChickenJockey(boolean isChickenJockey) { this.isChickenJockey = isChickenJockey; } }