package net.minecraft.world.entity.animal.allay; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Dynamic; import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; import net.minecraft.core.Holder; import net.minecraft.core.Vec3i; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.game.DebugPackets; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.resources.RegistryOps; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.GameEventTags; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.SimpleContainer; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.ai.Brain; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.behavior.BehaviorUtils; import net.minecraft.world.entity.ai.control.FlyingMoveControl; import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.memory.MemoryStatus; import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.ai.sensing.Sensor; import net.minecraft.world.entity.ai.sensing.SensorType; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.npc.InventoryCarrier; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.alchemy.PotionContents; import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.DynamicGameEventListener; import net.minecraft.world.level.gameevent.EntityPositionSource; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEventListener; import net.minecraft.world.level.gameevent.PositionSource; import net.minecraft.world.level.gameevent.vibrations.VibrationSystem; import net.minecraft.world.phys.Vec3; public class Allay extends PathfinderMob implements InventoryCarrier, VibrationSystem { private static final Vec3i ITEM_PICKUP_REACH = new Vec3i(1, 1, 1); private static final int LIFTING_ITEM_ANIMATION_DURATION = 5; private static final float DANCING_LOOP_DURATION = 55.0F; private static final float SPINNING_ANIMATION_DURATION = 15.0F; private static final int DEFAULT_DUPLICATION_COOLDOWN = 0; private static final int DUPLICATION_COOLDOWN_TICKS = 6000; private static final int NUM_OF_DUPLICATION_HEARTS = 3; public static final int MAX_NOTEBLOCK_DISTANCE = 1024; private static final EntityDataAccessor DATA_DANCING = SynchedEntityData.defineId(Allay.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor DATA_CAN_DUPLICATE = SynchedEntityData.defineId(Allay.class, EntityDataSerializers.BOOLEAN); protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of( SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.NEAREST_ITEMS ); protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of( MemoryModuleType.PATH, MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.HURT_BY, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.LIKED_PLAYER, MemoryModuleType.LIKED_NOTEBLOCK_POSITION, MemoryModuleType.LIKED_NOTEBLOCK_COOLDOWN_TICKS, MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS, MemoryModuleType.IS_PANICKING ); public static final ImmutableList THROW_SOUND_PITCHES = ImmutableList.of( 0.5625F, 0.625F, 0.75F, 0.9375F, 1.0F, 1.0F, 1.125F, 1.25F, 1.5F, 1.875F, 2.0F, 2.25F, 2.5F, 3.0F, 3.75F, 4.0F ); private final DynamicGameEventListener dynamicVibrationListener; private VibrationSystem.Data vibrationData; private final VibrationSystem.User vibrationUser; private final DynamicGameEventListener dynamicJukeboxListener; private final SimpleContainer inventory = new SimpleContainer(1); @Nullable private BlockPos jukeboxPos; private long duplicationCooldown = 0L; private float holdingItemAnimationTicks; private float holdingItemAnimationTicks0; private float dancingAnimationTicks; private float spinningAnimationTicks; private float spinningAnimationTicks0; public Allay(EntityType p_218310_, Level p_218311_) { super(p_218310_, p_218311_); this.moveControl = new FlyingMoveControl(this, 20, true); this.setCanPickUpLoot(this.canPickUpLoot()); this.vibrationUser = new Allay.VibrationUser(); this.vibrationData = new VibrationSystem.Data(); this.dynamicVibrationListener = new DynamicGameEventListener<>(new VibrationSystem.Listener(this)); this.dynamicJukeboxListener = new DynamicGameEventListener<>(new Allay.JukeboxListener(this.vibrationUser.getPositionSource(), GameEvent.JUKEBOX_PLAY.value().notificationRadius())); } @Override protected Brain.Provider brainProvider() { return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); } @Override protected Brain makeBrain(Dynamic p_218344_) { return AllayAi.makeBrain(this.brainProvider().makeBrain(p_218344_)); } @Override public Brain getBrain() { return (Brain)super.getBrain(); } public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.FLYING_SPEED, 0.1F) .add(Attributes.MOVEMENT_SPEED, 0.1F) .add(Attributes.ATTACK_DAMAGE, 2.0); } @Override protected PathNavigation createNavigation(Level p_218342_) { FlyingPathNavigation flyingpathnavigation = new FlyingPathNavigation(this, p_218342_); flyingpathnavigation.setCanOpenDoors(false); flyingpathnavigation.setCanFloat(true); flyingpathnavigation.setRequiredPathLength(48.0F); return flyingpathnavigation; } @Override protected void defineSynchedData(SynchedEntityData.Builder p_332593_) { super.defineSynchedData(p_332593_); p_332593_.define(DATA_DANCING, false); p_332593_.define(DATA_CAN_DUPLICATE, true); } @Override public void travel(Vec3 p_218382_) { if (this.isInWater()) { this.moveRelative(0.02F, p_218382_); this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.8F)); } else if (this.isInLava()) { this.moveRelative(0.02F, p_218382_); this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.5)); } else { this.moveRelative(this.getSpeed(), p_218382_); this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.91F)); } } @Override public boolean hurtServer(ServerLevel p_365948_, DamageSource p_369485_, float p_365274_) { return this.isLikedPlayer(p_369485_.getEntity()) ? false : super.hurtServer(p_365948_, p_369485_, p_365274_); } @Override protected boolean considersEntityAsAlly(Entity p_376126_) { return this.isLikedPlayer(p_376126_) || super.considersEntityAsAlly(p_376126_); } private boolean isLikedPlayer(@Nullable Entity p_377947_) { if (!(p_377947_ instanceof Player player)) { return false; } else { Optional optional = this.getBrain().getMemory(MemoryModuleType.LIKED_PLAYER); return optional.isPresent() && player.getUUID().equals(optional.get()); } } @Override protected void playStepSound(BlockPos p_218364_, BlockState p_218365_) { } @Override protected void checkFallDamage(double p_218316_, boolean p_218317_, BlockState p_218318_, BlockPos p_218319_) { } @Override protected SoundEvent getAmbientSound() { return this.hasItemInSlot(EquipmentSlot.MAINHAND) ? SoundEvents.ALLAY_AMBIENT_WITH_ITEM : SoundEvents.ALLAY_AMBIENT_WITHOUT_ITEM; } @Override protected SoundEvent getHurtSound(DamageSource p_218369_) { return SoundEvents.ALLAY_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.ALLAY_DEATH; } @Override protected float getSoundVolume() { return 0.4F; } @Override protected void customServerAiStep(ServerLevel p_367271_) { ProfilerFiller profilerfiller = Profiler.get(); profilerfiller.push("allayBrain"); this.getBrain().tick(p_367271_, this); profilerfiller.pop(); profilerfiller.push("allayActivityUpdate"); AllayAi.updateActivity(this); profilerfiller.pop(); super.customServerAiStep(p_367271_); } @Override public void aiStep() { super.aiStep(); if (!this.level().isClientSide && this.isAlive() && this.tickCount % 10 == 0) { this.heal(1.0F); } if (this.isDancing() && this.shouldStopDancing() && this.tickCount % 20 == 0) { this.setDancing(false); this.jukeboxPos = null; } this.updateDuplicationCooldown(); } @Override public void tick() { super.tick(); if (this.level().isClientSide) { this.holdingItemAnimationTicks0 = this.holdingItemAnimationTicks; if (this.hasItemInHand()) { this.holdingItemAnimationTicks = Mth.clamp(this.holdingItemAnimationTicks + 1.0F, 0.0F, 5.0F); } else { this.holdingItemAnimationTicks = Mth.clamp(this.holdingItemAnimationTicks - 1.0F, 0.0F, 5.0F); } if (this.isDancing()) { this.dancingAnimationTicks++; this.spinningAnimationTicks0 = this.spinningAnimationTicks; if (this.isSpinning()) { this.spinningAnimationTicks++; } else { this.spinningAnimationTicks--; } this.spinningAnimationTicks = Mth.clamp(this.spinningAnimationTicks, 0.0F, 15.0F); } else { this.dancingAnimationTicks = 0.0F; this.spinningAnimationTicks = 0.0F; this.spinningAnimationTicks0 = 0.0F; } } else { VibrationSystem.Ticker.tick(this.level(), this.vibrationData, this.vibrationUser); if (this.isPanicking()) { this.setDancing(false); } } } @Override public boolean canPickUpLoot() { return !this.isOnPickupCooldown() && this.hasItemInHand(); } public boolean hasItemInHand() { return !this.getItemInHand(InteractionHand.MAIN_HAND).isEmpty(); } @Override protected boolean canDispenserEquipIntoSlot(EquipmentSlot p_360851_) { return false; } private boolean isOnPickupCooldown() { return this.getBrain().checkMemory(MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS, MemoryStatus.VALUE_PRESENT); } @Override protected InteractionResult mobInteract(Player p_218361_, InteractionHand p_218362_) { ItemStack itemstack = p_218361_.getItemInHand(p_218362_); ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND); if (this.isDancing() && itemstack.is(ItemTags.DUPLICATES_ALLAYS) && this.canDuplicate()) { this.duplicateAllay(); this.level().broadcastEntityEvent(this, (byte)18); this.level().playSound(p_218361_, this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F); this.removeInteractionItem(p_218361_, itemstack); return InteractionResult.SUCCESS; } else if (itemstack1.isEmpty() && !itemstack.isEmpty()) { ItemStack itemstack3 = itemstack.copyWithCount(1); this.setItemInHand(InteractionHand.MAIN_HAND, itemstack3); this.removeInteractionItem(p_218361_, itemstack); this.level().playSound(p_218361_, this, SoundEvents.ALLAY_ITEM_GIVEN, SoundSource.NEUTRAL, 2.0F, 1.0F); this.getBrain().setMemory(MemoryModuleType.LIKED_PLAYER, p_218361_.getUUID()); return InteractionResult.SUCCESS; } else if (!itemstack1.isEmpty() && p_218362_ == InteractionHand.MAIN_HAND && itemstack.isEmpty()) { this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); this.level().playSound(p_218361_, this, SoundEvents.ALLAY_ITEM_TAKEN, SoundSource.NEUTRAL, 2.0F, 1.0F); this.swing(InteractionHand.MAIN_HAND); for (ItemStack itemstack2 : this.getInventory().removeAllItems()) { BehaviorUtils.throwItem(this, itemstack2, this.position()); } this.getBrain().eraseMemory(MemoryModuleType.LIKED_PLAYER); p_218361_.addItem(itemstack1); return InteractionResult.SUCCESS; } else { return super.mobInteract(p_218361_, p_218362_); } } public void setJukeboxPlaying(BlockPos p_240102_, boolean p_240103_) { if (p_240103_) { if (!this.isDancing()) { this.jukeboxPos = p_240102_; this.setDancing(true); } } else if (p_240102_.equals(this.jukeboxPos) || this.jukeboxPos == null) { this.jukeboxPos = null; this.setDancing(false); } } @Override public SimpleContainer getInventory() { return this.inventory; } @Override protected Vec3i getPickupReach() { return ITEM_PICKUP_REACH; } @Override public boolean wantsToPickUp(ServerLevel p_363882_, ItemStack p_218387_) { ItemStack itemstack = this.getItemInHand(InteractionHand.MAIN_HAND); return !itemstack.isEmpty() && p_363882_.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.inventory.canAddItem(p_218387_) && this.allayConsidersItemEqual(itemstack, p_218387_); } private boolean allayConsidersItemEqual(ItemStack p_252278_, ItemStack p_250405_) { return ItemStack.isSameItem(p_252278_, p_250405_) && !this.hasNonMatchingPotion(p_252278_, p_250405_); } private boolean hasNonMatchingPotion(ItemStack p_248762_, ItemStack p_250839_) { PotionContents potioncontents = p_248762_.get(DataComponents.POTION_CONTENTS); PotionContents potioncontents1 = p_250839_.get(DataComponents.POTION_CONTENTS); return !Objects.equals(potioncontents, potioncontents1); } @Override protected void pickUpItem(ServerLevel p_363025_, ItemEntity p_218359_) { InventoryCarrier.pickUpItem(p_363025_, this, this, p_218359_); } @Override protected void sendDebugPackets() { super.sendDebugPackets(); DebugPackets.sendEntityBrain(this); } @Override public boolean isFlapping() { return !this.onGround(); } @Override public void updateDynamicGameEventListener(BiConsumer, ServerLevel> p_218348_) { if (this.level() instanceof ServerLevel serverlevel) { p_218348_.accept(this.dynamicVibrationListener, serverlevel); p_218348_.accept(this.dynamicJukeboxListener, serverlevel); } } public boolean isDancing() { return this.entityData.get(DATA_DANCING); } public void setDancing(boolean p_240178_) { if (!this.level().isClientSide && this.isEffectiveAi() && (!p_240178_ || !this.isPanicking())) { this.entityData.set(DATA_DANCING, p_240178_); } } private boolean shouldStopDancing() { return this.jukeboxPos == null || !this.jukeboxPos.closerToCenterThan(this.position(), GameEvent.JUKEBOX_PLAY.value().notificationRadius()) || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX); } public float getHoldingItemAnimationProgress(float p_218395_) { return Mth.lerp(p_218395_, this.holdingItemAnimationTicks0, this.holdingItemAnimationTicks) / 5.0F; } public boolean isSpinning() { float f = this.dancingAnimationTicks % 55.0F; return f < 15.0F; } public float getSpinningProgress(float p_240057_) { return Mth.lerp(p_240057_, this.spinningAnimationTicks0, this.spinningAnimationTicks) / 15.0F; } @Override public boolean equipmentHasChanged(ItemStack p_249825_, ItemStack p_251595_) { return !this.allayConsidersItemEqual(p_249825_, p_251595_); } @Override protected void dropEquipment(ServerLevel p_362296_) { super.dropEquipment(p_362296_); this.inventory.removeAllItems().forEach(p_359201_ -> this.spawnAtLocation(p_362296_, p_359201_)); ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); if (!itemstack.isEmpty() && !EnchantmentHelper.has(itemstack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { this.spawnAtLocation(p_362296_, itemstack); this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); } } @Override public boolean removeWhenFarAway(double p_218384_) { return false; } @Override public void addAdditionalSaveData(CompoundTag p_218367_) { super.addAdditionalSaveData(p_218367_); this.writeInventoryToTag(p_218367_, this.registryAccess()); RegistryOps registryops = this.registryAccess().createSerializationContext(NbtOps.INSTANCE); p_218367_.store("listener", VibrationSystem.Data.CODEC, registryops, this.vibrationData); p_218367_.putLong("DuplicationCooldown", this.duplicationCooldown); } @Override public void readAdditionalSaveData(CompoundTag p_218350_) { super.readAdditionalSaveData(p_218350_); this.readInventoryFromTag(p_218350_, this.registryAccess()); RegistryOps registryops = this.registryAccess().createSerializationContext(NbtOps.INSTANCE); this.vibrationData = p_218350_.read("listener", VibrationSystem.Data.CODEC, registryops).orElseGet(VibrationSystem.Data::new); this.setDuplicationCooldown(p_218350_.getIntOr("DuplicationCooldown", 0)); } @Override protected boolean shouldStayCloseToLeashHolder() { return false; } private void updateDuplicationCooldown() { if (!this.level().isClientSide() && this.duplicationCooldown > 0L) { this.setDuplicationCooldown(this.duplicationCooldown - 1L); } } private void setDuplicationCooldown(long p_396336_) { this.duplicationCooldown = p_396336_; this.entityData.set(DATA_CAN_DUPLICATE, p_396336_ == 0L); } private void duplicateAllay() { Allay allay = EntityType.ALLAY.create(this.level(), EntitySpawnReason.BREEDING); if (allay != null) { allay.snapTo(this.position()); allay.setPersistenceRequired(); allay.resetDuplicationCooldown(); this.resetDuplicationCooldown(); this.level().addFreshEntity(allay); } } private void resetDuplicationCooldown() { this.setDuplicationCooldown(6000L); } private boolean canDuplicate() { return this.entityData.get(DATA_CAN_DUPLICATE); } private void removeInteractionItem(Player p_239359_, ItemStack p_239360_) { p_239360_.consume(1, p_239359_); } @Override public Vec3 getLeashOffset() { return new Vec3(0.0, this.getEyeHeight() * 0.6, this.getBbWidth() * 0.1); } @Override public void handleEntityEvent(byte p_239347_) { if (p_239347_ == 18) { for (int i = 0; i < 3; i++) { this.spawnHeartParticle(); } } else { super.handleEntityEvent(p_239347_); } } private void spawnHeartParticle() { double d0 = this.random.nextGaussian() * 0.02; double d1 = this.random.nextGaussian() * 0.02; double d2 = this.random.nextGaussian() * 0.02; this.level().addParticle(ParticleTypes.HEART, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), d0, d1, d2); } @Override public VibrationSystem.Data getVibrationData() { return this.vibrationData; } @Override public VibrationSystem.User getVibrationUser() { return this.vibrationUser; } class JukeboxListener implements GameEventListener { private final PositionSource listenerSource; private final int listenerRadius; public JukeboxListener(final PositionSource p_239448_, final int p_239449_) { this.listenerSource = p_239448_; this.listenerRadius = p_239449_; } @Override public PositionSource getListenerSource() { return this.listenerSource; } @Override public int getListenerRadius() { return this.listenerRadius; } @Override public boolean handleGameEvent(ServerLevel p_250009_, Holder p_333132_, GameEvent.Context p_249478_, Vec3 p_250852_) { if (p_333132_.is(GameEvent.JUKEBOX_PLAY)) { Allay.this.setJukeboxPlaying(BlockPos.containing(p_250852_), true); return true; } else if (p_333132_.is(GameEvent.JUKEBOX_STOP_PLAY)) { Allay.this.setJukeboxPlaying(BlockPos.containing(p_250852_), false); return true; } else { return false; } } } class VibrationUser implements VibrationSystem.User { private static final int VIBRATION_EVENT_LISTENER_RANGE = 16; private final PositionSource positionSource = new EntityPositionSource(Allay.this, Allay.this.getEyeHeight()); @Override public int getListenerRadius() { return 16; } @Override public PositionSource getPositionSource() { return this.positionSource; } @Override public boolean canReceiveVibration(ServerLevel p_282038_, BlockPos p_283385_, Holder p_334911_, GameEvent.Context p_282208_) { if (Allay.this.isNoAi()) { return false; } else { Optional optional = Allay.this.getBrain().getMemory(MemoryModuleType.LIKED_NOTEBLOCK_POSITION); if (optional.isEmpty()) { return true; } else { GlobalPos globalpos = optional.get(); return globalpos.isCloseEnough(p_282038_.dimension(), Allay.this.blockPosition(), 1024) && globalpos.pos().equals(p_283385_); } } } @Override public void onReceiveVibration( ServerLevel p_281422_, BlockPos p_281449_, Holder p_333452_, @Nullable Entity p_281794_, @Nullable Entity p_281864_, float p_281642_ ) { if (p_333452_.is(GameEvent.NOTE_BLOCK_PLAY)) { AllayAi.hearNoteblock(Allay.this, new BlockPos(p_281449_)); } } @Override public TagKey getListenableEvents() { return GameEventTags.ALLAY_CAN_LISTEN; } } }