383 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			383 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.entity.projectile;
 | |
| 
 | |
| import com.google.common.base.MoreObjects;
 | |
| import it.unimi.dsi.fastutil.doubles.DoubleDoubleImmutablePair;
 | |
| import java.util.function.Consumer;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.network.protocol.Packet;
 | |
| import net.minecraft.network.protocol.game.ClientGamePacketListener;
 | |
| import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
 | |
| import net.minecraft.server.level.ServerEntity;
 | |
| import net.minecraft.server.level.ServerLevel;
 | |
| import net.minecraft.tags.EntityTypeTags;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.world.damagesource.DamageSource;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| import net.minecraft.world.entity.EntityReference;
 | |
| import net.minecraft.world.entity.EntitySelector;
 | |
| import net.minecraft.world.entity.EntityType;
 | |
| import net.minecraft.world.entity.LivingEntity;
 | |
| import net.minecraft.world.entity.TraceableEntity;
 | |
| import net.minecraft.world.entity.player.Player;
 | |
| import net.minecraft.world.item.Item;
 | |
| import net.minecraft.world.item.ItemStack;
 | |
| 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.state.BlockState;
 | |
| 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.AABB;
 | |
| 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 abstract class Projectile extends Entity implements TraceableEntity {
 | |
| 	private static final boolean DEFAULT_LEFT_OWNER = false;
 | |
| 	private static final boolean DEFAULT_HAS_BEEN_SHOT = false;
 | |
| 	@Nullable
 | |
| 	protected EntityReference<Entity> owner;
 | |
| 	private boolean leftOwner = false;
 | |
| 	private boolean hasBeenShot = false;
 | |
| 	@Nullable
 | |
| 	private Entity lastDeflectedBy;
 | |
| 
 | |
| 	Projectile(EntityType<? extends Projectile> entityType, Level level) {
 | |
| 		super(entityType, level);
 | |
| 	}
 | |
| 
 | |
| 	protected void setOwner(@Nullable EntityReference<Entity> owner) {
 | |
| 		this.owner = owner;
 | |
| 	}
 | |
| 
 | |
| 	public void setOwner(@Nullable Entity owner) {
 | |
| 		this.setOwner(owner != null ? new EntityReference<>(owner) : null);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public Entity getOwner() {
 | |
| 		return EntityReference.get(this.owner, this.level(), Entity.class);
 | |
| 	}
 | |
| 
 | |
| 	public Entity getEffectSource() {
 | |
| 		return MoreObjects.firstNonNull(this.getOwner(), this);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void addAdditionalSaveData(ValueOutput output) {
 | |
| 		EntityReference.store(this.owner, output, "Owner");
 | |
| 		if (this.leftOwner) {
 | |
| 			output.putBoolean("LeftOwner", true);
 | |
| 		}
 | |
| 
 | |
| 		output.putBoolean("HasBeenShot", this.hasBeenShot);
 | |
| 	}
 | |
| 
 | |
| 	protected boolean ownedBy(Entity entity) {
 | |
| 		return this.owner != null && this.owner.matches(entity);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void readAdditionalSaveData(ValueInput input) {
 | |
| 		this.setOwner(EntityReference.read(input, "Owner"));
 | |
| 		this.leftOwner = input.getBooleanOr("LeftOwner", false);
 | |
| 		this.hasBeenShot = input.getBooleanOr("HasBeenShot", false);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void restoreFrom(Entity entity) {
 | |
| 		super.restoreFrom(entity);
 | |
| 		if (entity instanceof Projectile projectile) {
 | |
| 			this.owner = projectile.owner;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void tick() {
 | |
| 		if (!this.hasBeenShot) {
 | |
| 			this.gameEvent(GameEvent.PROJECTILE_SHOOT, this.getOwner());
 | |
| 			this.hasBeenShot = true;
 | |
| 		}
 | |
| 
 | |
| 		if (!this.leftOwner) {
 | |
| 			this.leftOwner = this.checkLeftOwner();
 | |
| 		}
 | |
| 
 | |
| 		super.tick();
 | |
| 	}
 | |
| 
 | |
| 	private boolean checkLeftOwner() {
 | |
| 		Entity entity = this.getOwner();
 | |
| 		if (entity != null) {
 | |
| 			AABB aABB = this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0);
 | |
| 			return entity.getRootVehicle().getSelfAndPassengers().filter(EntitySelector.CAN_BE_PICKED).noneMatch(entityx -> aABB.intersects(entityx.getBoundingBox()));
 | |
| 		} else {
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public Vec3 getMovementToShoot(double x, double y, double z, float velocity, float inaccuracy) {
 | |
| 		return new Vec3(x, y, z)
 | |
| 			.normalize()
 | |
| 			.add(this.random.triangle(0.0, 0.0172275 * inaccuracy), this.random.triangle(0.0, 0.0172275 * inaccuracy), this.random.triangle(0.0, 0.0172275 * inaccuracy))
 | |
| 			.scale(velocity);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Similar to setArrowHeading, it's point the throwable entity to a x, y, z direction.
 | |
| 	 */
 | |
| 	public void shoot(double x, double y, double z, float velocity, float inaccuracy) {
 | |
| 		Vec3 vec3 = this.getMovementToShoot(x, y, z, velocity, inaccuracy);
 | |
| 		this.setDeltaMovement(vec3);
 | |
| 		this.hasImpulse = true;
 | |
| 		double d = vec3.horizontalDistance();
 | |
| 		this.setYRot((float)(Mth.atan2(vec3.x, vec3.z) * 180.0F / (float)Math.PI));
 | |
| 		this.setXRot((float)(Mth.atan2(vec3.y, d) * 180.0F / (float)Math.PI));
 | |
| 		this.yRotO = this.getYRot();
 | |
| 		this.xRotO = this.getXRot();
 | |
| 	}
 | |
| 
 | |
| 	public void shootFromRotation(Entity shooter, float x, float y, float z, float velocity, float inaccuracy) {
 | |
| 		float f = -Mth.sin(y * (float) (Math.PI / 180.0)) * Mth.cos(x * (float) (Math.PI / 180.0));
 | |
| 		float g = -Mth.sin((x + z) * (float) (Math.PI / 180.0));
 | |
| 		float h = Mth.cos(y * (float) (Math.PI / 180.0)) * Mth.cos(x * (float) (Math.PI / 180.0));
 | |
| 		this.shoot(f, g, h, velocity, inaccuracy);
 | |
| 		Vec3 vec3 = shooter.getKnownMovement();
 | |
| 		this.setDeltaMovement(this.getDeltaMovement().add(vec3.x, shooter.onGround() ? 0.0 : vec3.y, vec3.z));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onAboveBubbleColumn(boolean downwards, BlockPos pos) {
 | |
| 		double d = downwards ? -0.03 : 0.1;
 | |
| 		this.setDeltaMovement(this.getDeltaMovement().add(0.0, d, 0.0));
 | |
| 		sendBubbleColumnParticles(this.level(), pos);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onInsideBubbleColumn(boolean downwards) {
 | |
| 		double d = downwards ? -0.03 : 0.06;
 | |
| 		this.setDeltaMovement(this.getDeltaMovement().add(0.0, d, 0.0));
 | |
| 		this.resetFallDistance();
 | |
| 	}
 | |
| 
 | |
| 	public static <T extends Projectile> T spawnProjectileFromRotation(
 | |
| 		Projectile.ProjectileFactory<T> factory, ServerLevel level, ItemStack spawnedFrom, LivingEntity owner, float z, float velocity, float inaccuracy
 | |
| 	) {
 | |
| 		return spawnProjectile(
 | |
| 			factory.create(level, owner, spawnedFrom),
 | |
| 			level,
 | |
| 			spawnedFrom,
 | |
| 			projectile -> projectile.shootFromRotation(owner, owner.getXRot(), owner.getYRot(), z, velocity, inaccuracy)
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	public static <T extends Projectile> T spawnProjectileUsingShoot(
 | |
| 		Projectile.ProjectileFactory<T> factory,
 | |
| 		ServerLevel level,
 | |
| 		ItemStack spawnedFrom,
 | |
| 		LivingEntity owner,
 | |
| 		double x,
 | |
| 		double y,
 | |
| 		double z,
 | |
| 		float velocity,
 | |
| 		float inaccuracy
 | |
| 	) {
 | |
| 		return spawnProjectile(factory.create(level, owner, spawnedFrom), level, spawnedFrom, projectile -> projectile.shoot(x, y, z, velocity, inaccuracy));
 | |
| 	}
 | |
| 
 | |
| 	public static <T extends Projectile> T spawnProjectileUsingShoot(
 | |
| 		T projectile, ServerLevel level, ItemStack spawnedFrom, double x, double y, double z, float velocity, float inaccuracy
 | |
| 	) {
 | |
| 		return spawnProjectile(projectile, level, spawnedFrom, projectile2 -> projectile.shoot(x, y, z, velocity, inaccuracy));
 | |
| 	}
 | |
| 
 | |
| 	public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel level, ItemStack spawnedFrom) {
 | |
| 		return spawnProjectile(projectile, level, spawnedFrom, projectilex -> {});
 | |
| 	}
 | |
| 
 | |
| 	public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel level, ItemStack stack, Consumer<T> adapter) {
 | |
| 		adapter.accept(projectile);
 | |
| 		level.addFreshEntity(projectile);
 | |
| 		projectile.applyOnProjectileSpawned(level, stack);
 | |
| 		return projectile;
 | |
| 	}
 | |
| 
 | |
| 	public void applyOnProjectileSpawned(ServerLevel level, ItemStack spawnedFrom) {
 | |
| 		EnchantmentHelper.onProjectileSpawned(level, spawnedFrom, this, item -> {});
 | |
| 		if (this instanceof AbstractArrow abstractArrow) {
 | |
| 			ItemStack itemStack = abstractArrow.getWeaponItem();
 | |
| 			if (itemStack != null && !itemStack.isEmpty() && !spawnedFrom.getItem().equals(itemStack.getItem())) {
 | |
| 				EnchantmentHelper.onProjectileSpawned(level, itemStack, this, abstractArrow::onItemBreak);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected ProjectileDeflection hitTargetOrDeflectSelf(HitResult hitResult) {
 | |
| 		if (hitResult.getType() == HitResult.Type.ENTITY) {
 | |
| 			EntityHitResult entityHitResult = (EntityHitResult)hitResult;
 | |
| 			Entity entity = entityHitResult.getEntity();
 | |
| 			ProjectileDeflection projectileDeflection = entity.deflection(this);
 | |
| 			if (projectileDeflection != ProjectileDeflection.NONE) {
 | |
| 				if (entity != this.lastDeflectedBy && this.deflect(projectileDeflection, entity, this.getOwner(), false)) {
 | |
| 					this.lastDeflectedBy = entity;
 | |
| 				}
 | |
| 
 | |
| 				return projectileDeflection;
 | |
| 			}
 | |
| 		} else if (this.shouldBounceOnWorldBorder() && hitResult instanceof BlockHitResult blockHitResult && blockHitResult.isWorldBorderHit()) {
 | |
| 			ProjectileDeflection projectileDeflection2 = ProjectileDeflection.REVERSE;
 | |
| 			if (this.deflect(projectileDeflection2, null, this.getOwner(), false)) {
 | |
| 				this.setDeltaMovement(this.getDeltaMovement().scale(0.2));
 | |
| 				return projectileDeflection2;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.onHit(hitResult);
 | |
| 		return ProjectileDeflection.NONE;
 | |
| 	}
 | |
| 
 | |
| 	protected boolean shouldBounceOnWorldBorder() {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	public boolean deflect(ProjectileDeflection deflection, @Nullable Entity entity, @Nullable Entity owner, boolean deflectedByPlayer) {
 | |
| 		deflection.deflect(this, entity, this.random);
 | |
| 		if (!this.level().isClientSide) {
 | |
| 			this.setOwner(owner);
 | |
| 			this.onDeflection(entity, deflectedByPlayer);
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	protected void onDeflection(@Nullable Entity entity, boolean deflectedByPlayer) {
 | |
| 	}
 | |
| 
 | |
| 	protected void onItemBreak(Item item) {
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Called when this EntityFireball hits a block or entity.
 | |
| 	 */
 | |
| 	protected void onHit(HitResult result) {
 | |
| 		HitResult.Type type = result.getType();
 | |
| 		if (type == HitResult.Type.ENTITY) {
 | |
| 			EntityHitResult entityHitResult = (EntityHitResult)result;
 | |
| 			Entity entity = entityHitResult.getEntity();
 | |
| 			if (entity.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && entity instanceof Projectile projectile) {
 | |
| 				projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this.getOwner(), this.getOwner(), true);
 | |
| 			}
 | |
| 
 | |
| 			this.onHitEntity(entityHitResult);
 | |
| 			this.level().gameEvent(GameEvent.PROJECTILE_LAND, result.getLocation(), GameEvent.Context.of(this, null));
 | |
| 		} else if (type == HitResult.Type.BLOCK) {
 | |
| 			BlockHitResult blockHitResult = (BlockHitResult)result;
 | |
| 			this.onHitBlock(blockHitResult);
 | |
| 			BlockPos blockPos = blockHitResult.getBlockPos();
 | |
| 			this.level().gameEvent(GameEvent.PROJECTILE_LAND, blockPos, GameEvent.Context.of(this, this.level().getBlockState(blockPos)));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Called when the arrow hits an entity
 | |
| 	 */
 | |
| 	protected void onHitEntity(EntityHitResult result) {
 | |
| 	}
 | |
| 
 | |
| 	protected void onHitBlock(BlockHitResult result) {
 | |
| 		BlockState blockState = this.level().getBlockState(result.getBlockPos());
 | |
| 		blockState.onProjectileHit(this.level(), blockState, result, this);
 | |
| 	}
 | |
| 
 | |
| 	protected boolean canHitEntity(Entity target) {
 | |
| 		if (!target.canBeHitByProjectile()) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			Entity entity = this.getOwner();
 | |
| 			return entity == null || this.leftOwner || !entity.isPassengerOfSameVehicle(target);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void updateRotation() {
 | |
| 		Vec3 vec3 = this.getDeltaMovement();
 | |
| 		double d = vec3.horizontalDistance();
 | |
| 		this.setXRot(lerpRotation(this.xRotO, (float)(Mth.atan2(vec3.y, d) * 180.0F / (float)Math.PI)));
 | |
| 		this.setYRot(lerpRotation(this.yRotO, (float)(Mth.atan2(vec3.x, vec3.z) * 180.0F / (float)Math.PI)));
 | |
| 	}
 | |
| 
 | |
| 	protected static float lerpRotation(float currentRotation, float targetRotation) {
 | |
| 		while (targetRotation - currentRotation < -180.0F) {
 | |
| 			currentRotation -= 360.0F;
 | |
| 		}
 | |
| 
 | |
| 		while (targetRotation - currentRotation >= 180.0F) {
 | |
| 			currentRotation += 360.0F;
 | |
| 		}
 | |
| 
 | |
| 		return Mth.lerp(0.2F, currentRotation, targetRotation);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public Packet<ClientGamePacketListener> getAddEntityPacket(ServerEntity entity) {
 | |
| 		Entity entity2 = this.getOwner();
 | |
| 		return new ClientboundAddEntityPacket(this, entity, entity2 == null ? 0 : entity2.getId());
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void recreateFromPacket(ClientboundAddEntityPacket packet) {
 | |
| 		super.recreateFromPacket(packet);
 | |
| 		Entity entity = this.level().getEntity(packet.getData());
 | |
| 		if (entity != null) {
 | |
| 			this.setOwner(entity);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean mayInteract(ServerLevel level, BlockPos pos) {
 | |
| 		Entity entity = this.getOwner();
 | |
| 		return entity instanceof Player ? entity.mayInteract(level, pos) : entity == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
 | |
| 	}
 | |
| 
 | |
| 	public boolean mayBreak(ServerLevel level) {
 | |
| 		return this.getType().is(EntityTypeTags.IMPACT_PROJECTILES) && level.getGameRules().getBoolean(GameRules.RULE_PROJECTILESCANBREAKBLOCKS);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean isPickable() {
 | |
| 		return this.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public float getPickRadius() {
 | |
| 		return this.isPickable() ? 1.0F : 0.0F;
 | |
| 	}
 | |
| 
 | |
| 	public DoubleDoubleImmutablePair calculateHorizontalHurtKnockbackDirection(LivingEntity entity, DamageSource damageSource) {
 | |
| 		double d = this.getDeltaMovement().x;
 | |
| 		double e = this.getDeltaMovement().z;
 | |
| 		return DoubleDoubleImmutablePair.of(d, e);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public int getDimensionChangingDelay() {
 | |
| 		return 2;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
 | |
| 		if (!this.isInvulnerableToBase(damageSource)) {
 | |
| 			this.markHurt();
 | |
| 		}
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	@FunctionalInterface
 | |
| 	public interface ProjectileFactory<T extends Projectile> {
 | |
| 		T create(ServerLevel serverLevel, LivingEntity livingEntity, ItemStack itemStack);
 | |
| 	}
 | |
| }
 |