/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Lists * it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.animal; import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; 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.BlockTags; import net.minecraft.tags.ItemTags; import net.minecraft.tags.PoiTypeTags; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.util.TimeUtil; import net.minecraft.util.VisibleForDebug; import net.minecraft.util.debug.DebugBeeInfo; import net.minecraft.util.debug.DebugSubscriptions; import net.minecraft.util.debug.DebugValueSource; import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.world.Difficulty; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityReference; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.NeutralMob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.FlyingMoveControl; import net.minecraft.world.entity.ai.control.LookControl; 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.Goal; import net.minecraft.world.entity.ai.goal.GoalSelector; import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; import net.minecraft.world.entity.ai.goal.TemptGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal; import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.ai.util.AirAndWaterRandomPos; import net.minecraft.world.entity.ai.util.AirRandomPos; import net.minecraft.world.entity.ai.util.HoverRandomPos; import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.entity.ai.village.poi.PoiRecord; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.animal.FlyingAnimal; import net.minecraft.world.entity.player.Player; import net.minecraft.world.flag.FeatureElement; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.EnchantmentHelper; 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.BonemealableBlock; import net.minecraft.world.level.block.CropBlock; import net.minecraft.world.level.block.DoublePlantBlock; import net.minecraft.world.level.block.FlowerBlock; import net.minecraft.world.level.block.StemBlock; import net.minecraft.world.level.block.SweetBerryBushBlock; import net.minecraft.world.level.block.entity.BeehiveBlockEntity; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; public class Bee extends Animal implements FlyingAnimal, NeutralMob { public static final float FLAP_DEGREES_PER_TICK = 120.32113f; public static final int TICKS_PER_FLAP = Mth.ceil(1.4959966f); private static final EntityDataAccessor DATA_FLAGS_ID = SynchedEntityData.defineId(Bee.class, EntityDataSerializers.BYTE); private static final EntityDataAccessor DATA_ANGER_END_TIME = SynchedEntityData.defineId(Bee.class, EntityDataSerializers.LONG); private static final int FLAG_ROLL = 2; private static final int FLAG_HAS_STUNG = 4; private static final int FLAG_HAS_NECTAR = 8; private static final int STING_DEATH_COUNTDOWN = 1200; private static final int TICKS_BEFORE_GOING_TO_KNOWN_FLOWER = 600; private static final int TICKS_WITHOUT_NECTAR_BEFORE_GOING_HOME = 3600; private static final int MIN_ATTACK_DIST = 4; private static final int MAX_CROPS_GROWABLE = 10; private static final int POISON_SECONDS_NORMAL = 10; private static final int POISON_SECONDS_HARD = 18; private static final int TOO_FAR_DISTANCE = 48; private static final int HIVE_CLOSE_ENOUGH_DISTANCE = 2; private static final int RESTRICTED_WANDER_DISTANCE_REDUCTION = 24; private static final int DEFAULT_WANDER_DISTANCE_REDUCTION = 16; private static final int PATHFIND_TO_HIVE_WHEN_CLOSER_THAN = 16; private static final int HIVE_SEARCH_DISTANCE = 20; public static final String TAG_CROPS_GROWN_SINCE_POLLINATION = "CropsGrownSincePollination"; public static final String TAG_CANNOT_ENTER_HIVE_TICKS = "CannotEnterHiveTicks"; public static final String TAG_TICKS_SINCE_POLLINATION = "TicksSincePollination"; public static final String TAG_HAS_STUNG = "HasStung"; public static final String TAG_HAS_NECTAR = "HasNectar"; public static final String TAG_FLOWER_POS = "flower_pos"; public static final String TAG_HIVE_POS = "hive_pos"; public static final boolean DEFAULT_HAS_NECTAR = false; private static final boolean DEFAULT_HAS_STUNG = false; private static final int DEFAULT_TICKS_SINCE_POLLINATION = 0; private static final int DEFAULT_CANNOT_ENTER_HIVE_TICKS = 0; private static final int DEFAULT_CROPS_GROWN_SINCE_POLLINATION = 0; private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39); private @Nullable EntityReference persistentAngerTarget; private float rollAmount; private float rollAmountO; private int timeSinceSting; private int ticksWithoutNectarSinceExitingHive = 0; private int stayOutOfHiveCountdown = 0; private int numCropsGrownSincePollination = 0; private static final int COOLDOWN_BEFORE_LOCATING_NEW_HIVE = 200; private int remainingCooldownBeforeLocatingNewHive; private static final int COOLDOWN_BEFORE_LOCATING_NEW_FLOWER = 200; private static final int MIN_FIND_FLOWER_RETRY_COOLDOWN = 20; private static final int MAX_FIND_FLOWER_RETRY_COOLDOWN = 60; private int remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); private @Nullable BlockPos savedFlowerPos; private @Nullable BlockPos hivePos; private BeePollinateGoal beePollinateGoal; private BeeGoToHiveGoal goToHiveGoal; private BeeGoToKnownFlowerGoal goToKnownFlowerGoal; private int underWaterTicks; public Bee(EntityType type, Level level) { super((EntityType)type, level); this.moveControl = new FlyingMoveControl(this, 20, true); this.lookControl = new BeeLookControl(this); this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0f); this.setPathfindingMalus(PathType.WATER, -1.0f); this.setPathfindingMalus(PathType.WATER_BORDER, 16.0f); this.setPathfindingMalus(PathType.COCOA, -1.0f); this.setPathfindingMalus(PathType.FENCE, -1.0f); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(DATA_FLAGS_ID, (byte)0); entityData.define(DATA_ANGER_END_TIME, -1L); } @Override public float getWalkTargetValue(BlockPos pos, LevelReader level) { if (level.getBlockState(pos).isAir()) { return 10.0f; } return 0.0f; } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new BeeAttackGoal(this, 1.4f, true)); this.goalSelector.addGoal(1, new BeeEnterHiveGoal()); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, i -> i.is(ItemTags.BEE_FOOD), false)); this.goalSelector.addGoal(3, new ValidateHiveGoal()); this.goalSelector.addGoal(3, new ValidateFlowerGoal()); this.beePollinateGoal = new BeePollinateGoal(); this.goalSelector.addGoal(4, this.beePollinateGoal); this.goalSelector.addGoal(5, new FollowParentGoal(this, 1.25)); this.goalSelector.addGoal(5, new BeeLocateHiveGoal()); this.goToHiveGoal = new BeeGoToHiveGoal(); this.goalSelector.addGoal(5, this.goToHiveGoal); this.goToKnownFlowerGoal = new BeeGoToKnownFlowerGoal(); this.goalSelector.addGoal(6, this.goToKnownFlowerGoal); this.goalSelector.addGoal(7, new BeeGrowCropGoal()); this.goalSelector.addGoal(8, new BeeWanderGoal()); this.goalSelector.addGoal(9, new FloatGoal(this)); this.targetSelector.addGoal(1, new BeeHurtByOtherGoal(this).setAlertOthers(new Class[0])); this.targetSelector.addGoal(2, new BeeBecomeAngryTargetGoal(this)); this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal(this, true)); } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.storeNullable(TAG_HIVE_POS, BlockPos.CODEC, this.hivePos); output.storeNullable(TAG_FLOWER_POS, BlockPos.CODEC, this.savedFlowerPos); output.putBoolean(TAG_HAS_NECTAR, this.hasNectar()); output.putBoolean(TAG_HAS_STUNG, this.hasStung()); output.putInt(TAG_TICKS_SINCE_POLLINATION, this.ticksWithoutNectarSinceExitingHive); output.putInt(TAG_CANNOT_ENTER_HIVE_TICKS, this.stayOutOfHiveCountdown); output.putInt(TAG_CROPS_GROWN_SINCE_POLLINATION, this.numCropsGrownSincePollination); this.addPersistentAngerSaveData(output); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); this.setHasNectar(input.getBooleanOr(TAG_HAS_NECTAR, false)); this.setHasStung(input.getBooleanOr(TAG_HAS_STUNG, false)); this.ticksWithoutNectarSinceExitingHive = input.getIntOr(TAG_TICKS_SINCE_POLLINATION, 0); this.stayOutOfHiveCountdown = input.getIntOr(TAG_CANNOT_ENTER_HIVE_TICKS, 0); this.numCropsGrownSincePollination = input.getIntOr(TAG_CROPS_GROWN_SINCE_POLLINATION, 0); this.hivePos = input.read(TAG_HIVE_POS, BlockPos.CODEC).orElse(null); this.savedFlowerPos = input.read(TAG_FLOWER_POS, BlockPos.CODEC).orElse(null); this.readPersistentAngerSaveData(this.level(), input); } @Override public boolean doHurtTarget(ServerLevel level, Entity target) { DamageSource damageSource = this.damageSources().sting(this); boolean wasHurt = target.hurtServer(level, damageSource, (int)this.getAttributeValue(Attributes.ATTACK_DAMAGE)); if (wasHurt) { EnchantmentHelper.doPostAttackEffects(level, target, damageSource); if (target instanceof LivingEntity) { LivingEntity livingTarget = (LivingEntity)target; livingTarget.setStingerCount(livingTarget.getStingerCount() + 1); int poisonTime = 0; if (this.level().getDifficulty() == Difficulty.NORMAL) { poisonTime = 10; } else if (this.level().getDifficulty() == Difficulty.HARD) { poisonTime = 18; } if (poisonTime > 0) { livingTarget.addEffect(new MobEffectInstance(MobEffects.POISON, poisonTime * 20, 0), this); } } this.setHasStung(true); this.stopBeingAngry(); this.playSound(SoundEvents.BEE_STING, 1.0f, 1.0f); } return wasHurt; } @Override public void tick() { super.tick(); if (this.hasNectar() && this.getCropsGrownSincePollination() < 10 && this.random.nextFloat() < 0.05f) { for (int i = 0; i < this.random.nextInt(2) + 1; ++i) { this.spawnFluidParticle(this.level(), this.getX() - (double)0.3f, this.getX() + (double)0.3f, this.getZ() - (double)0.3f, this.getZ() + (double)0.3f, this.getY(0.5), ParticleTypes.FALLING_NECTAR); } } this.updateRollAmount(); } private void spawnFluidParticle(Level level, double x1, double x2, double z1, double z2, double y, ParticleOptions dripParticle) { level.addParticle(dripParticle, Mth.lerp(level.random.nextDouble(), x1, x2), y, Mth.lerp(level.random.nextDouble(), z1, z2), 0.0, 0.0, 0.0); } private void pathfindRandomlyTowards(BlockPos targetPos) { Vec3 nextPosTowards; Vec3 targetVec = Vec3.atBottomCenterOf(targetPos); int yAdjust = 0; BlockPos beePos = this.blockPosition(); int yDelta = (int)targetVec.y - beePos.getY(); if (yDelta > 2) { yAdjust = 4; } else if (yDelta < -2) { yAdjust = -4; } int xzDist = 6; int yDist = 8; int dist = beePos.distManhattan(targetPos); if (dist < 15) { xzDist = dist / 2; yDist = dist / 2; } if ((nextPosTowards = AirRandomPos.getPosTowards(this, xzDist, yDist, yAdjust, targetVec, 0.3141592741012573)) == null) { return; } this.navigation.setMaxVisitedNodesMultiplier(0.5f); this.navigation.moveTo(nextPosTowards.x, nextPosTowards.y, nextPosTowards.z, 1.0); } public @Nullable BlockPos getSavedFlowerPos() { return this.savedFlowerPos; } public boolean hasSavedFlowerPos() { return this.savedFlowerPos != null; } public void setSavedFlowerPos(BlockPos savedFlowerPos) { this.savedFlowerPos = savedFlowerPos; } @VisibleForDebug public int getTravellingTicks() { return Math.max(this.goToHiveGoal.travellingTicks, this.goToKnownFlowerGoal.travellingTicks); } @VisibleForDebug public List getBlacklistedHives() { return this.goToHiveGoal.blacklistedTargets; } private boolean isTiredOfLookingForNectar() { return this.ticksWithoutNectarSinceExitingHive > 3600; } private void dropHive() { this.hivePos = null; this.remainingCooldownBeforeLocatingNewHive = 200; } private void dropFlower() { this.savedFlowerPos = null; this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); } private boolean wantsToEnterHive() { if (this.stayOutOfHiveCountdown > 0 || this.beePollinateGoal.isPollinating() || this.hasStung() || this.getTarget() != null) { return false; } boolean wantsToEnterHive = this.hasNectar() || this.isTiredOfLookingForNectar() || this.level().environmentAttributes().getValue(EnvironmentAttributes.BEES_STAY_IN_HIVE, this.position()) != false; return wantsToEnterHive && !this.isHiveNearFire(); } public void setStayOutOfHiveCountdown(int ticks) { this.stayOutOfHiveCountdown = ticks; } public float getRollAmount(float a) { return Mth.lerp(a, this.rollAmountO, this.rollAmount); } private void updateRollAmount() { this.rollAmountO = this.rollAmount; this.rollAmount = this.isRolling() ? Math.min(1.0f, this.rollAmount + 0.2f) : Math.max(0.0f, this.rollAmount - 0.24f); } @Override protected void customServerAiStep(ServerLevel level) { boolean hasStung = this.hasStung(); this.underWaterTicks = this.isInWater() ? ++this.underWaterTicks : 0; if (this.underWaterTicks > 20) { this.hurtServer(level, this.damageSources().drown(), 1.0f); } if (hasStung) { ++this.timeSinceSting; if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) { this.hurtServer(level, this.damageSources().generic(), this.getHealth()); } } if (!this.hasNectar()) { ++this.ticksWithoutNectarSinceExitingHive; } this.updatePersistentAnger(level, false); } public void resetTicksWithoutNectarSinceExitingHive() { this.ticksWithoutNectarSinceExitingHive = 0; } private boolean isHiveNearFire() { BeehiveBlockEntity beehiveBlockEntity = this.getBeehiveBlockEntity(); return beehiveBlockEntity != null && beehiveBlockEntity.isFireNearby(); } @Override public long getPersistentAngerEndTime() { return this.entityData.get(DATA_ANGER_END_TIME); } @Override public void setPersistentAngerEndTime(long endTime) { this.entityData.set(DATA_ANGER_END_TIME, endTime); } @Override public @Nullable EntityReference getPersistentAngerTarget() { return this.persistentAngerTarget; } @Override public void setPersistentAngerTarget(@Nullable EntityReference persistentAngerTarget) { this.persistentAngerTarget = persistentAngerTarget; } @Override public void startPersistentAngerTimer() { this.setTimeToRemainAngry(PERSISTENT_ANGER_TIME.sample(this.random)); } private boolean doesHiveHaveSpace(BlockPos hivePos) { BlockEntity blockEntity = this.level().getBlockEntity(hivePos); if (blockEntity instanceof BeehiveBlockEntity) { return !((BeehiveBlockEntity)blockEntity).isFull(); } return false; } @VisibleForDebug public boolean hasHive() { return this.hivePos != null; } @VisibleForDebug public @Nullable BlockPos getHivePos() { return this.hivePos; } @VisibleForDebug public GoalSelector getGoalSelector() { return this.goalSelector; } private int getCropsGrownSincePollination() { return this.numCropsGrownSincePollination; } private void resetNumCropsGrownSincePollination() { this.numCropsGrownSincePollination = 0; } private void incrementNumCropsGrownSincePollination() { ++this.numCropsGrownSincePollination; } @Override public void aiStep() { super.aiStep(); if (!this.level().isClientSide()) { if (this.stayOutOfHiveCountdown > 0) { --this.stayOutOfHiveCountdown; } if (this.remainingCooldownBeforeLocatingNewHive > 0) { --this.remainingCooldownBeforeLocatingNewHive; } if (this.remainingCooldownBeforeLocatingNewFlower > 0) { --this.remainingCooldownBeforeLocatingNewFlower; } boolean shouldRoll = this.isAngry() && !this.hasStung() && this.getTarget() != null && this.getTarget().distanceToSqr(this) < 4.0; this.setRolling(shouldRoll); if (this.tickCount % 20 == 0 && !this.isHiveValid()) { this.hivePos = null; } } } private @Nullable BeehiveBlockEntity getBeehiveBlockEntity() { if (this.hivePos == null) { return null; } if (this.isTooFarAway(this.hivePos)) { return null; } return this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null); } private boolean isHiveValid() { return this.getBeehiveBlockEntity() != null; } public boolean hasNectar() { return this.getFlag(8); } private void setHasNectar(boolean hasNectar) { if (hasNectar) { this.resetTicksWithoutNectarSinceExitingHive(); } this.setFlag(8, hasNectar); } public boolean hasStung() { return this.getFlag(4); } private void setHasStung(boolean hasStung) { this.setFlag(4, hasStung); } private boolean isRolling() { return this.getFlag(2); } private void setRolling(boolean rolling) { this.setFlag(2, rolling); } private boolean isTooFarAway(BlockPos targetPos) { return !this.closerThan(targetPos, 48); } private void setFlag(int flag, boolean value) { if (value) { this.entityData.set(DATA_FLAGS_ID, (byte)(this.entityData.get(DATA_FLAGS_ID) | flag)); } else { this.entityData.set(DATA_FLAGS_ID, (byte)(this.entityData.get(DATA_FLAGS_ID) & ~flag)); } } private boolean getFlag(int flag) { return (this.entityData.get(DATA_FLAGS_ID) & flag) != 0; } public static AttributeSupplier.Builder createAttributes() { return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.FLYING_SPEED, 0.6f).add(Attributes.MOVEMENT_SPEED, 0.3f).add(Attributes.ATTACK_DAMAGE, 2.0); } @Override protected PathNavigation createNavigation(Level level) { FlyingPathNavigation flyingPathNavigation = new FlyingPathNavigation(this, level){ @Override public boolean isStableDestination(BlockPos pos) { return !this.level.getBlockState(pos.below()).isAir(); } @Override public void tick() { if (Bee.this.beePollinateGoal.isPollinating()) { return; } super.tick(); } }; flyingPathNavigation.setCanOpenDoors(false); flyingPathNavigation.setCanFloat(false); flyingPathNavigation.setRequiredPathLength(48.0f); return flyingPathNavigation; } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { FlowerBlock flower; MobEffectInstance effect; BlockItem blockItem; FeatureElement featureElement; ItemStack heldItem = player.getItemInHand(hand); if (this.isFood(heldItem) && (featureElement = heldItem.getItem()) instanceof BlockItem && (featureElement = (blockItem = (BlockItem)featureElement).getBlock()) instanceof FlowerBlock && (effect = (flower = (FlowerBlock)featureElement).getBeeInteractionEffect()) != null) { this.usePlayerItem(player, hand, heldItem); if (!this.level().isClientSide()) { this.addEffect(effect); } return InteractionResult.SUCCESS; } return super.mobInteract(player, hand); } @Override public boolean isFood(ItemStack itemStack) { return itemStack.is(ItemTags.BEE_FOOD); } @Override protected void playStepSound(BlockPos pos, BlockState blockState) { } @Override protected SoundEvent getAmbientSound() { return null; } @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.BEE_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.BEE_DEATH; } @Override protected float getSoundVolume() { return 0.4f; } @Override public @Nullable Bee getBreedOffspring(ServerLevel level, AgeableMob partner) { return EntityType.BEE.create(level, EntitySpawnReason.BREEDING); } @Override protected void checkFallDamage(double ya, boolean onGround, BlockState onState, BlockPos pos) { } @Override public boolean isFlapping() { return this.isFlying() && this.tickCount % TICKS_PER_FLAP == 0; } @Override public boolean isFlying() { return !this.onGround(); } public void dropOffNectar() { this.setHasNectar(false); this.resetNumCropsGrownSincePollination(); } @Override public boolean hurtServer(ServerLevel level, DamageSource source, float damage) { if (this.isInvulnerableTo(level, source)) { return false; } this.beePollinateGoal.stopPollinating(); return super.hurtServer(level, source, damage); } @Override protected void jumpInLiquid(TagKey type) { this.setDeltaMovement(this.getDeltaMovement().add(0.0, 0.01, 0.0)); } @Override public Vec3 getLeashOffset() { return new Vec3(0.0, 0.5f * this.getEyeHeight(), this.getBbWidth() * 0.2f); } private boolean closerThan(BlockPos targetPos, int distance) { return targetPos.closerThan(this.blockPosition(), distance); } public void setHivePos(BlockPos hivePos) { this.hivePos = hivePos; } public static boolean attractsBees(BlockState state) { if (state.is(BlockTags.BEE_ATTRACTIVE)) { if (state.getValueOrElse(BlockStateProperties.WATERLOGGED, false).booleanValue()) { return false; } if (state.is(Blocks.SUNFLOWER)) { return state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.UPPER; } return true; } return false; } @Override public void registerDebugValues(ServerLevel level, DebugValueSource.Registration registration) { super.registerDebugValues(level, registration); registration.register(DebugSubscriptions.BEES, () -> new DebugBeeInfo(Optional.ofNullable(this.getHivePos()), Optional.ofNullable(this.getSavedFlowerPos()), this.getTravellingTicks(), this.getBlacklistedHives())); } private class BeeLookControl extends LookControl { BeeLookControl(Mob mob) { super(mob); } @Override public void tick() { if (Bee.this.isAngry()) { return; } super.tick(); } @Override protected boolean resetXRotOnTick() { return !Bee.this.beePollinateGoal.isPollinating(); } } private class BeeAttackGoal extends MeleeAttackGoal { BeeAttackGoal(PathfinderMob mob, double speedModifier, boolean trackTarget) { super(mob, speedModifier, trackTarget); } @Override public boolean canUse() { return super.canUse() && Bee.this.isAngry() && !Bee.this.hasStung(); } @Override public boolean canContinueToUse() { return super.canContinueToUse() && Bee.this.isAngry() && !Bee.this.hasStung(); } } private class BeeEnterHiveGoal extends BaseBeeGoal { private BeeEnterHiveGoal() { } @Override public boolean canBeeUse() { BeehiveBlockEntity beehiveBlockEntity; if (Bee.this.hivePos != null && Bee.this.wantsToEnterHive() && Bee.this.hivePos.closerToCenterThan(Bee.this.position(), 2.0) && (beehiveBlockEntity = Bee.this.getBeehiveBlockEntity()) != null) { if (beehiveBlockEntity.isFull()) { Bee.this.hivePos = null; } else { return true; } } return false; } @Override public boolean canBeeContinueToUse() { return false; } @Override public void start() { BeehiveBlockEntity beehiveBlockEntity = Bee.this.getBeehiveBlockEntity(); if (beehiveBlockEntity != null) { beehiveBlockEntity.addOccupant(Bee.this); } } } private class ValidateHiveGoal extends BaseBeeGoal { private final int VALIDATE_HIVE_COOLDOWN; private long lastValidateTick; private ValidateHiveGoal() { this.VALIDATE_HIVE_COOLDOWN = Mth.nextInt(Bee.this.random, 20, 40); this.lastValidateTick = -1L; } @Override public void start() { if (Bee.this.hivePos != null && Bee.this.level().isLoaded(Bee.this.hivePos) && !Bee.this.isHiveValid()) { Bee.this.dropHive(); } this.lastValidateTick = Bee.this.level().getGameTime(); } @Override public boolean canBeeUse() { return Bee.this.level().getGameTime() > this.lastValidateTick + (long)this.VALIDATE_HIVE_COOLDOWN; } @Override public boolean canBeeContinueToUse() { return false; } } private class ValidateFlowerGoal extends BaseBeeGoal { private final int validateFlowerCooldown; private long lastValidateTick; private ValidateFlowerGoal() { this.validateFlowerCooldown = Mth.nextInt(Bee.this.random, 20, 40); this.lastValidateTick = -1L; } @Override public void start() { if (Bee.this.savedFlowerPos != null && Bee.this.level().isLoaded(Bee.this.savedFlowerPos) && !this.isFlower(Bee.this.savedFlowerPos)) { Bee.this.dropFlower(); } this.lastValidateTick = Bee.this.level().getGameTime(); } @Override public boolean canBeeUse() { return Bee.this.level().getGameTime() > this.lastValidateTick + (long)this.validateFlowerCooldown; } @Override public boolean canBeeContinueToUse() { return false; } private boolean isFlower(BlockPos flowerPos) { return Bee.attractsBees(Bee.this.level().getBlockState(flowerPos)); } } private class BeePollinateGoal extends BaseBeeGoal { private static final int MIN_POLLINATION_TICKS = 400; private static final double ARRIVAL_THRESHOLD = 0.1; private static final int POSITION_CHANGE_CHANCE = 25; private static final float SPEED_MODIFIER = 0.35f; private static final float HOVER_HEIGHT_WITHIN_FLOWER = 0.6f; private static final float HOVER_POS_OFFSET = 0.33333334f; private static final int FLOWER_SEARCH_RADIUS = 5; private int successfulPollinatingTicks; private int lastSoundPlayedTick; private boolean pollinating; private @Nullable Vec3 hoverPos; private int pollinatingTicks; private static final int MAX_POLLINATING_TICKS = 600; private Long2LongOpenHashMap unreachableFlowerCache; BeePollinateGoal() { this.unreachableFlowerCache = new Long2LongOpenHashMap(); this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canBeeUse() { if (Bee.this.remainingCooldownBeforeLocatingNewFlower > 0) { return false; } if (Bee.this.hasNectar()) { return false; } if (Bee.this.level().isRaining()) { return false; } Optional nearbyPos = this.findNearbyFlower(); if (nearbyPos.isPresent()) { Bee.this.savedFlowerPos = nearbyPos.get(); Bee.this.navigation.moveTo((double)Bee.this.savedFlowerPos.getX() + 0.5, (double)Bee.this.savedFlowerPos.getY() + 0.5, (double)Bee.this.savedFlowerPos.getZ() + 0.5, 1.2f); return true; } Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60); return false; } @Override public boolean canBeeContinueToUse() { if (!this.pollinating) { return false; } if (!Bee.this.hasSavedFlowerPos()) { return false; } if (Bee.this.level().isRaining()) { return false; } if (this.hasPollinatedLongEnough()) { return Bee.this.random.nextFloat() < 0.2f; } return true; } private boolean hasPollinatedLongEnough() { return this.successfulPollinatingTicks > 400; } private boolean isPollinating() { return this.pollinating; } private void stopPollinating() { this.pollinating = false; } @Override public void start() { this.successfulPollinatingTicks = 0; this.pollinatingTicks = 0; this.lastSoundPlayedTick = 0; this.pollinating = true; Bee.this.resetTicksWithoutNectarSinceExitingHive(); } @Override public void stop() { if (this.hasPollinatedLongEnough()) { Bee.this.setHasNectar(true); } this.pollinating = false; Bee.this.navigation.stop(); Bee.this.remainingCooldownBeforeLocatingNewFlower = 200; } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { if (!Bee.this.hasSavedFlowerPos()) { return; } ++this.pollinatingTicks; if (this.pollinatingTicks > 600) { Bee.this.dropFlower(); this.pollinating = false; Bee.this.remainingCooldownBeforeLocatingNewFlower = 200; return; } Vec3 flowerPos = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0, 0.6f, 0.0); if (flowerPos.distanceTo(Bee.this.position()) > 1.0) { this.hoverPos = flowerPos; this.setWantedPos(); return; } if (this.hoverPos == null) { this.hoverPos = flowerPos; } boolean arrivedAtHoverPos = Bee.this.position().distanceTo(this.hoverPos) <= 0.1; boolean shouldSetWantedPos = true; if (!arrivedAtHoverPos && this.pollinatingTicks > 600) { Bee.this.dropFlower(); return; } if (arrivedAtHoverPos) { boolean shouldChangeHoverPositions; boolean bl = shouldChangeHoverPositions = Bee.this.random.nextInt(25) == 0; if (shouldChangeHoverPositions) { this.hoverPos = new Vec3(flowerPos.x() + (double)this.getOffset(), flowerPos.y(), flowerPos.z() + (double)this.getOffset()); Bee.this.navigation.stop(); } else { shouldSetWantedPos = false; } Bee.this.getLookControl().setLookAt(flowerPos.x(), flowerPos.y(), flowerPos.z()); } if (shouldSetWantedPos) { this.setWantedPos(); } ++this.successfulPollinatingTicks; if (Bee.this.random.nextFloat() < 0.05f && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) { this.lastSoundPlayedTick = this.successfulPollinatingTicks; Bee.this.playSound(SoundEvents.BEE_POLLINATE, 1.0f, 1.0f); } } private void setWantedPos() { Bee.this.getMoveControl().setWantedPosition(this.hoverPos.x(), this.hoverPos.y(), this.hoverPos.z(), 0.35f); } private float getOffset() { return (Bee.this.random.nextFloat() * 2.0f - 1.0f) * 0.33333334f; } private Optional findNearbyFlower() { Iterable closestNearbyFlowers = BlockPos.withinManhattan(Bee.this.blockPosition(), 5, 5, 5); Long2LongOpenHashMap tempCache = new Long2LongOpenHashMap(); for (BlockPos pos : closestNearbyFlowers) { long unreachableUntilTime = this.unreachableFlowerCache.getOrDefault(pos.asLong(), Long.MIN_VALUE); if (Bee.this.level().getGameTime() < unreachableUntilTime) { tempCache.put(pos.asLong(), unreachableUntilTime); continue; } if (!Bee.attractsBees(Bee.this.level().getBlockState(pos))) continue; Path path = Bee.this.navigation.createPath(pos, 1); if (path != null && path.canReach()) { return Optional.of(pos); } tempCache.put(pos.asLong(), Bee.this.level().getGameTime() + 600L); } this.unreachableFlowerCache = tempCache; return Optional.empty(); } } private class BeeLocateHiveGoal extends BaseBeeGoal { private BeeLocateHiveGoal() { } @Override public boolean canBeeUse() { return Bee.this.remainingCooldownBeforeLocatingNewHive == 0 && !Bee.this.hasHive() && Bee.this.wantsToEnterHive(); } @Override public boolean canBeeContinueToUse() { return false; } @Override public void start() { Bee.this.remainingCooldownBeforeLocatingNewHive = 200; List hivesWithSpace = this.findNearbyHivesWithSpace(); if (hivesWithSpace.isEmpty()) { return; } for (BlockPos posToCheck : hivesWithSpace) { if (Bee.this.goToHiveGoal.isTargetBlacklisted(posToCheck)) continue; Bee.this.hivePos = posToCheck; return; } Bee.this.goToHiveGoal.clearBlacklist(); Bee.this.hivePos = hivesWithSpace.get(0); } private List findNearbyHivesWithSpace() { BlockPos beePos = Bee.this.blockPosition(); PoiManager poiManager = ((ServerLevel)Bee.this.level()).getPoiManager(); Stream nearbyHives = poiManager.getInRange(p -> p.is(PoiTypeTags.BEE_HOME), beePos, 20, PoiManager.Occupancy.ANY); return nearbyHives.map(PoiRecord::getPos).filter(Bee.this::doesHiveHaveSpace).sorted(Comparator.comparingDouble(pos -> pos.distSqr(beePos))).collect(Collectors.toList()); } } @VisibleForDebug public class BeeGoToHiveGoal extends BaseBeeGoal { public static final int MAX_TRAVELLING_TICKS = 2400; private int travellingTicks; private static final int MAX_BLACKLISTED_TARGETS = 3; private final List blacklistedTargets; private @Nullable Path lastPath; private static final int TICKS_BEFORE_HIVE_DROP = 60; private int ticksStuck; BeeGoToHiveGoal() { this.blacklistedTargets = Lists.newArrayList(); this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canBeeUse() { return Bee.this.hivePos != null && !Bee.this.isTooFarAway(Bee.this.hivePos) && !Bee.this.hasHome() && Bee.this.wantsToEnterHive() && !this.hasReachedTarget(Bee.this.hivePos) && Bee.this.level().getBlockState(Bee.this.hivePos).is(BlockTags.BEEHIVES); } @Override public boolean canBeeContinueToUse() { return this.canBeeUse(); } @Override public void start() { this.travellingTicks = 0; this.ticksStuck = 0; super.start(); } @Override public void stop() { this.travellingTicks = 0; this.ticksStuck = 0; Bee.this.navigation.stop(); Bee.this.navigation.resetMaxVisitedNodesMultiplier(); } @Override public void tick() { if (Bee.this.hivePos == null) { return; } ++this.travellingTicks; if (this.travellingTicks > this.adjustedTickDelay(2400)) { this.dropAndBlacklistHive(); return; } if (Bee.this.navigation.isInProgress()) { return; } if (Bee.this.closerThan(Bee.this.hivePos, 16)) { boolean canReachAllTheWayToTarget = this.pathfindDirectlyTowards(Bee.this.hivePos); if (!canReachAllTheWayToTarget) { this.dropAndBlacklistHive(); } else if (this.lastPath != null && Bee.this.navigation.getPath().sameAs(this.lastPath)) { ++this.ticksStuck; if (this.ticksStuck > 60) { Bee.this.dropHive(); this.ticksStuck = 0; } } else { this.lastPath = Bee.this.navigation.getPath(); } return; } if (Bee.this.isTooFarAway(Bee.this.hivePos)) { Bee.this.dropHive(); return; } Bee.this.pathfindRandomlyTowards(Bee.this.hivePos); } private boolean pathfindDirectlyTowards(BlockPos targetPos) { int closeEnough = Bee.this.closerThan(targetPos, 3) ? 1 : 2; Bee.this.navigation.setMaxVisitedNodesMultiplier(10.0f); Bee.this.navigation.moveTo(targetPos.getX(), targetPos.getY(), targetPos.getZ(), closeEnough, 1.0); return Bee.this.navigation.getPath() != null && Bee.this.navigation.getPath().canReach(); } private boolean isTargetBlacklisted(BlockPos targetPos) { return this.blacklistedTargets.contains(targetPos); } private void blacklistTarget(BlockPos targetPos) { this.blacklistedTargets.add(targetPos); while (this.blacklistedTargets.size() > 3) { this.blacklistedTargets.remove(0); } } private void clearBlacklist() { this.blacklistedTargets.clear(); } private void dropAndBlacklistHive() { if (Bee.this.hivePos != null) { this.blacklistTarget(Bee.this.hivePos); } Bee.this.dropHive(); } private boolean hasReachedTarget(BlockPos targetPos) { if (Bee.this.closerThan(targetPos, 2)) { return true; } Path path = Bee.this.navigation.getPath(); return path != null && path.getTarget().equals(targetPos) && path.canReach() && path.isDone(); } } public class BeeGoToKnownFlowerGoal extends BaseBeeGoal { private static final int MAX_TRAVELLING_TICKS = 2400; private int travellingTicks; BeeGoToKnownFlowerGoal() { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canBeeUse() { return Bee.this.savedFlowerPos != null && !Bee.this.hasHome() && this.wantsToGoToKnownFlower() && !Bee.this.closerThan(Bee.this.savedFlowerPos, 2); } @Override public boolean canBeeContinueToUse() { return this.canBeeUse(); } @Override public void start() { this.travellingTicks = 0; super.start(); } @Override public void stop() { this.travellingTicks = 0; Bee.this.navigation.stop(); Bee.this.navigation.resetMaxVisitedNodesMultiplier(); } @Override public void tick() { if (Bee.this.savedFlowerPos == null) { return; } ++this.travellingTicks; if (this.travellingTicks > this.adjustedTickDelay(2400)) { Bee.this.dropFlower(); return; } if (Bee.this.navigation.isInProgress()) { return; } if (Bee.this.isTooFarAway(Bee.this.savedFlowerPos)) { Bee.this.dropFlower(); return; } Bee.this.pathfindRandomlyTowards(Bee.this.savedFlowerPos); } private boolean wantsToGoToKnownFlower() { return Bee.this.ticksWithoutNectarSinceExitingHive > 600; } } private class BeeGrowCropGoal extends BaseBeeGoal { static final int GROW_CHANCE = 30; private BeeGrowCropGoal() { } @Override public boolean canBeeUse() { if (Bee.this.getCropsGrownSincePollination() >= 10) { return false; } if (Bee.this.random.nextFloat() < 0.3f) { return false; } return Bee.this.hasNectar() && Bee.this.isHiveValid(); } @Override public boolean canBeeContinueToUse() { return this.canBeeUse(); } @Override public void tick() { if (Bee.this.random.nextInt(this.adjustedTickDelay(30)) != 0) { return; } for (int i = 1; i <= 2; ++i) { BonemealableBlock bonemealableBlock; BlockPos belowPos = Bee.this.blockPosition().below(i); BlockState belowState = Bee.this.level().getBlockState(belowPos); Block belowBlock = belowState.getBlock(); BlockState growState = null; if (!belowState.is(BlockTags.BEE_GROWABLES)) continue; if (belowBlock instanceof CropBlock) { CropBlock cropBlockBelow = (CropBlock)belowBlock; if (!cropBlockBelow.isMaxAge(belowState)) { growState = cropBlockBelow.getStateForAge(cropBlockBelow.getAge(belowState) + 1); } } else if (belowBlock instanceof StemBlock) { int age = belowState.getValue(StemBlock.AGE); if (age < 7) { growState = (BlockState)belowState.setValue(StemBlock.AGE, age + 1); } } else if (belowState.is(Blocks.SWEET_BERRY_BUSH)) { int age = belowState.getValue(SweetBerryBushBlock.AGE); if (age < 3) { growState = (BlockState)belowState.setValue(SweetBerryBushBlock.AGE, age + 1); } } else if ((belowState.is(Blocks.CAVE_VINES) || belowState.is(Blocks.CAVE_VINES_PLANT)) && (bonemealableBlock = (BonemealableBlock)((Object)belowState.getBlock())).isValidBonemealTarget(Bee.this.level(), belowPos, belowState)) { bonemealableBlock.performBonemeal((ServerLevel)Bee.this.level(), Bee.this.random, belowPos, belowState); growState = Bee.this.level().getBlockState(belowPos); } if (growState == null) continue; Bee.this.level().levelEvent(2011, belowPos, 15); Bee.this.level().setBlockAndUpdate(belowPos, growState); Bee.this.incrementNumCropsGrownSincePollination(); } } } private class BeeWanderGoal extends Goal { BeeWanderGoal() { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canUse() { return Bee.this.navigation.isDone() && Bee.this.random.nextInt(10) == 0; } @Override public boolean canContinueToUse() { return Bee.this.navigation.isInProgress(); } @Override public void start() { Vec3 targetPos = this.findPos(); if (targetPos != null) { Bee.this.navigation.moveTo(Bee.this.navigation.createPath(BlockPos.containing(targetPos), 1), 1.0); } } private @Nullable Vec3 findPos() { Vec3 wanderDirection; if (Bee.this.isHiveValid() && !Bee.this.closerThan(Bee.this.hivePos, this.getWanderThreshold())) { Vec3 hivePosVec = Vec3.atCenterOf(Bee.this.hivePos); wanderDirection = hivePosVec.subtract(Bee.this.position()).normalize(); } else { wanderDirection = Bee.this.getViewVector(0.0f); } int xzDist = 8; Vec3 groundBasedPosition = HoverRandomPos.getPos(Bee.this, 8, 7, wanderDirection.x, wanderDirection.z, 1.5707964f, 3, 1); if (groundBasedPosition != null) { return groundBasedPosition; } return AirAndWaterRandomPos.getPos(Bee.this, 8, 4, -2, wanderDirection.x, wanderDirection.z, 1.5707963705062866); } private int getWanderThreshold() { int distanceReduction = Bee.this.hasHive() || Bee.this.hasSavedFlowerPos() ? 24 : 16; return 48 - distanceReduction; } } private class BeeHurtByOtherGoal extends HurtByTargetGoal { BeeHurtByOtherGoal(Bee bee2) { super(bee2, new Class[0]); } @Override public boolean canContinueToUse() { return Bee.this.isAngry() && super.canContinueToUse(); } @Override protected void alertOther(Mob other, LivingEntity hurtByMob) { if (other instanceof Bee && this.mob.hasLineOfSight(hurtByMob)) { other.setTarget(hurtByMob); } } } private static class BeeBecomeAngryTargetGoal extends NearestAttackableTargetGoal { BeeBecomeAngryTargetGoal(Bee bee) { super(bee, Player.class, 10, true, false, bee::isAngryAt); } @Override public boolean canUse() { return this.beeCanTarget() && super.canUse(); } @Override public boolean canContinueToUse() { boolean beeCanTarget = this.beeCanTarget(); if (!beeCanTarget || this.mob.getTarget() == null) { this.targetMob = null; return false; } return super.canContinueToUse(); } private boolean beeCanTarget() { Bee bee = (Bee)this.mob; return bee.isAngry() && !bee.hasStung(); } } private abstract class BaseBeeGoal extends Goal { private BaseBeeGoal() { } public abstract boolean canBeeUse(); public abstract boolean canBeeContinueToUse(); @Override public boolean canUse() { return this.canBeeUse() && !Bee.this.isAngry(); } @Override public boolean canContinueToUse() { return this.canBeeContinueToUse() && !Bee.this.isAngry(); } } }