package net.minecraft.world.entity.projectile; import com.google.common.base.MoreObjects; import com.google.common.collect.Lists; import java.util.List; import java.util.UUID; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; import net.minecraft.network.syncher.SynchedEntityData.Builder; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.world.Difficulty; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.Level; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent.Context; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.HitResult.Type; import org.jetbrains.annotations.Nullable; public class ShulkerBullet extends Projectile { private static final double SPEED = 0.15; @Nullable private Entity finalTarget; @Nullable private Direction currentMoveDirection; private int flightSteps; private double targetDeltaX; private double targetDeltaY; private double targetDeltaZ; @Nullable private UUID targetId; public ShulkerBullet(EntityType entityType, Level level) { super(entityType, level); this.noPhysics = true; } public ShulkerBullet(Level level, LivingEntity shooter, Entity finalTarget, Direction.Axis axis) { this(EntityType.SHULKER_BULLET, level); this.setOwner(shooter); Vec3 vec3 = shooter.getBoundingBox().getCenter(); this.moveTo(vec3.x, vec3.y, vec3.z, this.getYRot(), this.getXRot()); this.finalTarget = finalTarget; this.currentMoveDirection = Direction.UP; this.selectNextMoveDirection(axis); } @Override public SoundSource getSoundSource() { return SoundSource.HOSTILE; } @Override protected void addAdditionalSaveData(CompoundTag compound) { super.addAdditionalSaveData(compound); if (this.finalTarget != null) { compound.putUUID("Target", this.finalTarget.getUUID()); } if (this.currentMoveDirection != null) { compound.putInt("Dir", this.currentMoveDirection.get3DDataValue()); } compound.putInt("Steps", this.flightSteps); compound.putDouble("TXD", this.targetDeltaX); compound.putDouble("TYD", this.targetDeltaY); compound.putDouble("TZD", this.targetDeltaZ); } @Override protected void readAdditionalSaveData(CompoundTag compound) { super.readAdditionalSaveData(compound); this.flightSteps = compound.getInt("Steps"); this.targetDeltaX = compound.getDouble("TXD"); this.targetDeltaY = compound.getDouble("TYD"); this.targetDeltaZ = compound.getDouble("TZD"); if (compound.contains("Dir", 99)) { this.currentMoveDirection = Direction.from3DDataValue(compound.getInt("Dir")); } if (compound.hasUUID("Target")) { this.targetId = compound.getUUID("Target"); } } @Override protected void defineSynchedData(Builder builder) { } @Nullable private Direction getMoveDirection() { return this.currentMoveDirection; } private void setMoveDirection(@Nullable Direction direction) { this.currentMoveDirection = direction; } private void selectNextMoveDirection(@Nullable Direction.Axis axis) { double d = 0.5; BlockPos blockPos; if (this.finalTarget == null) { blockPos = this.blockPosition().below(); } else { d = this.finalTarget.getBbHeight() * 0.5; blockPos = BlockPos.containing(this.finalTarget.getX(), this.finalTarget.getY() + d, this.finalTarget.getZ()); } double e = blockPos.getX() + 0.5; double f = blockPos.getY() + d; double g = blockPos.getZ() + 0.5; Direction direction = null; if (!blockPos.closerToCenterThan(this.position(), 2.0)) { BlockPos blockPos2 = this.blockPosition(); List list = Lists.newArrayList(); if (axis != Direction.Axis.X) { if (blockPos2.getX() < blockPos.getX() && this.level().isEmptyBlock(blockPos2.east())) { list.add(Direction.EAST); } else if (blockPos2.getX() > blockPos.getX() && this.level().isEmptyBlock(blockPos2.west())) { list.add(Direction.WEST); } } if (axis != Direction.Axis.Y) { if (blockPos2.getY() < blockPos.getY() && this.level().isEmptyBlock(blockPos2.above())) { list.add(Direction.UP); } else if (blockPos2.getY() > blockPos.getY() && this.level().isEmptyBlock(blockPos2.below())) { list.add(Direction.DOWN); } } if (axis != Direction.Axis.Z) { if (blockPos2.getZ() < blockPos.getZ() && this.level().isEmptyBlock(blockPos2.south())) { list.add(Direction.SOUTH); } else if (blockPos2.getZ() > blockPos.getZ() && this.level().isEmptyBlock(blockPos2.north())) { list.add(Direction.NORTH); } } direction = Direction.getRandom(this.random); if (list.isEmpty()) { for (int i = 5; !this.level().isEmptyBlock(blockPos2.relative(direction)) && i > 0; i--) { direction = Direction.getRandom(this.random); } } else { direction = (Direction)list.get(this.random.nextInt(list.size())); } e = this.getX() + direction.getStepX(); f = this.getY() + direction.getStepY(); g = this.getZ() + direction.getStepZ(); } this.setMoveDirection(direction); double h = e - this.getX(); double j = f - this.getY(); double k = g - this.getZ(); double l = Math.sqrt(h * h + j * j + k * k); if (l == 0.0) { this.targetDeltaX = 0.0; this.targetDeltaY = 0.0; this.targetDeltaZ = 0.0; } else { this.targetDeltaX = h / l * 0.15; this.targetDeltaY = j / l * 0.15; this.targetDeltaZ = k / l * 0.15; } this.hasImpulse = true; this.flightSteps = 10 + this.random.nextInt(5) * 10; } @Override public void checkDespawn() { if (this.level().getDifficulty() == Difficulty.PEACEFUL) { this.discard(); } } @Override protected double getDefaultGravity() { return 0.04; } @Override public void tick() { super.tick(); HitResult hitResult = null; if (!this.level().isClientSide) { if (this.finalTarget == null && this.targetId != null) { this.finalTarget = ((ServerLevel)this.level()).getEntity(this.targetId); if (this.finalTarget == null) { this.targetId = null; } } if (this.finalTarget == null || !this.finalTarget.isAlive() || this.finalTarget instanceof Player && this.finalTarget.isSpectator()) { this.applyGravity(); } else { this.targetDeltaX = Mth.clamp(this.targetDeltaX * 1.025, -1.0, 1.0); this.targetDeltaY = Mth.clamp(this.targetDeltaY * 1.025, -1.0, 1.0); this.targetDeltaZ = Mth.clamp(this.targetDeltaZ * 1.025, -1.0, 1.0); Vec3 vec3 = this.getDeltaMovement(); this.setDeltaMovement(vec3.add((this.targetDeltaX - vec3.x) * 0.2, (this.targetDeltaY - vec3.y) * 0.2, (this.targetDeltaZ - vec3.z) * 0.2)); } hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); } Vec3 vec3 = this.getDeltaMovement(); this.setPos(this.position().add(vec3)); this.applyEffectsFromBlocks(); if (this.portalProcess != null && this.portalProcess.isInsidePortalThisTick()) { this.handlePortal(); } if (hitResult != null && this.isAlive() && hitResult.getType() != Type.MISS) { this.hitTargetOrDeflectSelf(hitResult); } ProjectileUtil.rotateTowardsMovement(this, 0.5F); if (this.level().isClientSide) { this.level().addParticle(ParticleTypes.END_ROD, this.getX() - vec3.x, this.getY() - vec3.y + 0.15, this.getZ() - vec3.z, 0.0, 0.0, 0.0); } else if (this.finalTarget != null && !this.finalTarget.isRemoved()) { if (this.flightSteps > 0) { this.flightSteps--; if (this.flightSteps == 0) { this.selectNextMoveDirection(this.currentMoveDirection == null ? null : this.currentMoveDirection.getAxis()); } } if (this.currentMoveDirection != null) { BlockPos blockPos = this.blockPosition(); Direction.Axis axis = this.currentMoveDirection.getAxis(); if (this.level().loadedAndEntityCanStandOn(blockPos.relative(this.currentMoveDirection), this)) { this.selectNextMoveDirection(axis); } else { BlockPos blockPos2 = this.finalTarget.blockPosition(); if (axis == Direction.Axis.X && blockPos.getX() == blockPos2.getX() || axis == Direction.Axis.Z && blockPos.getZ() == blockPos2.getZ() || axis == Direction.Axis.Y && blockPos.getY() == blockPos2.getY()) { this.selectNextMoveDirection(axis); } } } } } @Override protected boolean canHitEntity(Entity target) { return super.canHitEntity(target) && !target.noPhysics; } @Override public boolean isOnFire() { return false; } @Override public boolean shouldRenderAtSqrDistance(double distance) { return distance < 16384.0; } @Override public float getLightLevelDependentMagicValue() { return 1.0F; } @Override protected void onHitEntity(EntityHitResult result) { super.onHitEntity(result); Entity entity = result.getEntity(); Entity entity2 = this.getOwner(); LivingEntity livingEntity = entity2 instanceof LivingEntity ? (LivingEntity)entity2 : null; DamageSource damageSource = this.damageSources().mobProjectile(this, livingEntity); boolean bl = entity.hurtOrSimulate(damageSource, 4.0F); if (bl) { if (this.level() instanceof ServerLevel serverLevel) { EnchantmentHelper.doPostAttackEffects(serverLevel, entity, damageSource); } if (entity instanceof LivingEntity livingEntity2) { livingEntity2.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), MoreObjects.firstNonNull(entity2, this)); } } } @Override protected void onHitBlock(BlockHitResult result) { super.onHitBlock(result); ((ServerLevel)this.level()).sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(), this.getZ(), 2, 0.2, 0.2, 0.2, 0.0); this.playSound(SoundEvents.SHULKER_BULLET_HIT, 1.0F, 1.0F); } private void destroy() { this.discard(); this.level().gameEvent(GameEvent.ENTITY_DAMAGE, this.position(), Context.of(this)); } @Override protected void onHit(HitResult result) { super.onHit(result); this.destroy(); } @Override public boolean isPickable() { return true; } @Override public boolean hurtClient(DamageSource damageSource) { return true; } @Override public boolean hurtServer(ServerLevel serverLevel, DamageSource damageSource, float f) { this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0F, 1.0F); serverLevel.sendParticles(ParticleTypes.CRIT, this.getX(), this.getY(), this.getZ(), 15, 0.2, 0.2, 0.2, 0.0); this.destroy(); return true; } @Override public void recreateFromPacket(ClientboundAddEntityPacket packet) { super.recreateFromPacket(packet); double d = packet.getXa(); double e = packet.getYa(); double f = packet.getZa(); this.setDeltaMovement(d, e, f); } }