341 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			341 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.entity.projectile;
 | |
| 
 | |
| import com.google.common.base.MoreObjects;
 | |
| import com.google.common.collect.Lists;
 | |
| import java.util.List;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.core.Direction;
 | |
| import net.minecraft.core.UUIDUtil;
 | |
| import net.minecraft.core.particles.ParticleTypes;
 | |
| import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
 | |
| import net.minecraft.network.syncher.SynchedEntityData;
 | |
| 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.EntityReference;
 | |
| 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.storage.ValueInput;
 | |
| import net.minecraft.world.level.storage.ValueOutput;
 | |
| 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 org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| public class ShulkerBullet extends Projectile {
 | |
| 	private static final double SPEED = 0.15;
 | |
| 	@Nullable
 | |
| 	private EntityReference<Entity> finalTarget;
 | |
| 	@Nullable
 | |
| 	private Direction currentMoveDirection;
 | |
| 	private int flightSteps;
 | |
| 	private double targetDeltaX;
 | |
| 	private double targetDeltaY;
 | |
| 	private double targetDeltaZ;
 | |
| 
 | |
| 	public ShulkerBullet(EntityType<? extends ShulkerBullet> 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.snapTo(vec3.x, vec3.y, vec3.z, this.getYRot(), this.getXRot());
 | |
| 		this.finalTarget = new EntityReference<>(finalTarget);
 | |
| 		this.currentMoveDirection = Direction.UP;
 | |
| 		this.selectNextMoveDirection(axis, finalTarget);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public SoundSource getSoundSource() {
 | |
| 		return SoundSource.HOSTILE;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void addAdditionalSaveData(ValueOutput output) {
 | |
| 		super.addAdditionalSaveData(output);
 | |
| 		if (this.finalTarget != null) {
 | |
| 			output.store("Target", UUIDUtil.CODEC, this.finalTarget.getUUID());
 | |
| 		}
 | |
| 
 | |
| 		output.storeNullable("Dir", Direction.LEGACY_ID_CODEC, this.currentMoveDirection);
 | |
| 		output.putInt("Steps", this.flightSteps);
 | |
| 		output.putDouble("TXD", this.targetDeltaX);
 | |
| 		output.putDouble("TYD", this.targetDeltaY);
 | |
| 		output.putDouble("TZD", this.targetDeltaZ);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void readAdditionalSaveData(ValueInput input) {
 | |
| 		super.readAdditionalSaveData(input);
 | |
| 		this.flightSteps = input.getIntOr("Steps", 0);
 | |
| 		this.targetDeltaX = input.getDoubleOr("TXD", 0.0);
 | |
| 		this.targetDeltaY = input.getDoubleOr("TYD", 0.0);
 | |
| 		this.targetDeltaZ = input.getDoubleOr("TZD", 0.0);
 | |
| 		this.currentMoveDirection = (Direction)input.read("Dir", Direction.LEGACY_ID_CODEC).orElse(null);
 | |
| 		this.finalTarget = EntityReference.read(input, "Target");
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void defineSynchedData(SynchedEntityData.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, @Nullable Entity target) {
 | |
| 		double d = 0.5;
 | |
| 		BlockPos blockPos;
 | |
| 		if (target == null) {
 | |
| 			blockPos = this.blockPosition().below();
 | |
| 		} else {
 | |
| 			d = target.getBbHeight() * 0.5;
 | |
| 			blockPos = BlockPos.containing(target.getX(), target.getY() + d, target.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<Direction> list = Lists.<Direction>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();
 | |
| 		Entity entity = !this.level().isClientSide() ? EntityReference.get(this.finalTarget, this.level(), Entity.class) : null;
 | |
| 		HitResult hitResult = null;
 | |
| 		if (!this.level().isClientSide) {
 | |
| 			if (entity == null) {
 | |
| 				this.finalTarget = null;
 | |
| 			}
 | |
| 
 | |
| 			if (entity == null || !entity.isAlive() || entity instanceof Player && entity.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() != HitResult.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 (entity != null) {
 | |
| 			if (this.flightSteps > 0) {
 | |
| 				this.flightSteps--;
 | |
| 				if (this.flightSteps == 0) {
 | |
| 					this.selectNextMoveDirection(this.currentMoveDirection == null ? null : this.currentMoveDirection.getAxis(), entity);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			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, entity);
 | |
| 				} else {
 | |
| 					BlockPos blockPos2 = entity.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, entity);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected boolean isAffectedByBlocks() {
 | |
| 		return !this.isRemoved();
 | |
| 	}
 | |
| 
 | |
| 	@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(), GameEvent.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 level, DamageSource damageSource, float amount) {
 | |
| 		this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0F, 1.0F);
 | |
| 		level.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);
 | |
| 	}
 | |
| }
 |