735 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			735 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.entity.projectile;
 | |
| 
 | |
| import com.google.common.collect.Lists;
 | |
| import com.mojang.serialization.Codec;
 | |
| import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 | |
| import java.util.List;
 | |
| import java.util.Objects;
 | |
| import net.minecraft.advancements.CriteriaTriggers;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.core.component.DataComponents;
 | |
| import net.minecraft.core.particles.ParticleTypes;
 | |
| import net.minecraft.core.registries.BuiltInRegistries;
 | |
| import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
 | |
| 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.server.level.ServerPlayer;
 | |
| import net.minecraft.sounds.SoundEvent;
 | |
| import net.minecraft.sounds.SoundEvents;
 | |
| import net.minecraft.tags.EntityTypeTags;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.util.Unit;
 | |
| import net.minecraft.world.damagesource.DamageSource;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| import net.minecraft.world.entity.EntityType;
 | |
| import net.minecraft.world.entity.LivingEntity;
 | |
| import net.minecraft.world.entity.MoverType;
 | |
| import net.minecraft.world.entity.OminousItemSpawner;
 | |
| import net.minecraft.world.entity.SlotAccess;
 | |
| import net.minecraft.world.entity.ai.attributes.Attributes;
 | |
| 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.ClipContext;
 | |
| import net.minecraft.world.level.Level;
 | |
| import net.minecraft.world.level.block.state.BlockState;
 | |
| 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 net.minecraft.world.phys.shapes.VoxelShape;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| public abstract class AbstractArrow extends Projectile {
 | |
| 	private static final double ARROW_BASE_DAMAGE = 2.0;
 | |
| 	private static final int SHAKE_TIME = 7;
 | |
| 	private static final float WATER_INERTIA = 0.6F;
 | |
| 	private static final float INERTIA = 0.99F;
 | |
| 	private static final short DEFAULT_LIFE = 0;
 | |
| 	private static final byte DEFAULT_SHAKE = 0;
 | |
| 	private static final boolean DEFAULT_IN_GROUND = false;
 | |
| 	private static final boolean DEFAULT_CRIT = false;
 | |
| 	private static final byte DEFAULT_PIERCE_LEVEL = 0;
 | |
| 	private static final EntityDataAccessor<Byte> ID_FLAGS = SynchedEntityData.defineId(AbstractArrow.class, EntityDataSerializers.BYTE);
 | |
| 	private static final EntityDataAccessor<Byte> PIERCE_LEVEL = SynchedEntityData.defineId(AbstractArrow.class, EntityDataSerializers.BYTE);
 | |
| 	private static final EntityDataAccessor<Boolean> IN_GROUND = SynchedEntityData.defineId(AbstractArrow.class, EntityDataSerializers.BOOLEAN);
 | |
| 	private static final int FLAG_CRIT = 1;
 | |
| 	private static final int FLAG_NOPHYSICS = 2;
 | |
| 	@Nullable
 | |
| 	private BlockState lastState;
 | |
| 	protected int inGroundTime;
 | |
| 	public AbstractArrow.Pickup pickup = AbstractArrow.Pickup.DISALLOWED;
 | |
| 	public int shakeTime = 0;
 | |
| 	private int life = 0;
 | |
| 	private double baseDamage = 2.0;
 | |
| 	private SoundEvent soundEvent = this.getDefaultHitGroundSoundEvent();
 | |
| 	@Nullable
 | |
| 	private IntOpenHashSet piercingIgnoreEntityIds;
 | |
| 	@Nullable
 | |
| 	private List<Entity> piercedAndKilledEntities;
 | |
| 	private ItemStack pickupItemStack = this.getDefaultPickupItem();
 | |
| 	@Nullable
 | |
| 	private ItemStack firedFromWeapon = null;
 | |
| 
 | |
| 	protected AbstractArrow(EntityType<? extends AbstractArrow> entityType, Level level) {
 | |
| 		super(entityType, level);
 | |
| 	}
 | |
| 
 | |
| 	protected AbstractArrow(
 | |
| 		EntityType<? extends AbstractArrow> entityType, double x, double y, double z, Level level, ItemStack pickupItemStack, @Nullable ItemStack firedFromWeapon
 | |
| 	) {
 | |
| 		this(entityType, level);
 | |
| 		this.pickupItemStack = pickupItemStack.copy();
 | |
| 		this.applyComponentsFromItemStack(pickupItemStack);
 | |
| 		Unit unit = pickupItemStack.remove(DataComponents.INTANGIBLE_PROJECTILE);
 | |
| 		if (unit != null) {
 | |
| 			this.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
 | |
| 		}
 | |
| 
 | |
| 		this.setPos(x, y, z);
 | |
| 		if (firedFromWeapon != null && level instanceof ServerLevel serverLevel) {
 | |
| 			if (firedFromWeapon.isEmpty()) {
 | |
| 				throw new IllegalArgumentException("Invalid weapon firing an arrow");
 | |
| 			}
 | |
| 
 | |
| 			this.firedFromWeapon = firedFromWeapon.copy();
 | |
| 			int i = EnchantmentHelper.getPiercingCount(serverLevel, firedFromWeapon, this.pickupItemStack);
 | |
| 			if (i > 0) {
 | |
| 				this.setPierceLevel((byte)i);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected AbstractArrow(
 | |
| 		EntityType<? extends AbstractArrow> entityType, LivingEntity owner, Level level, ItemStack pickupItemStack, @Nullable ItemStack firedFromWeapon
 | |
| 	) {
 | |
| 		this(entityType, owner.getX(), owner.getEyeY() - 0.1F, owner.getZ(), level, pickupItemStack, firedFromWeapon);
 | |
| 		this.setOwner(owner);
 | |
| 	}
 | |
| 
 | |
| 	public void setSoundEvent(SoundEvent soundEvent) {
 | |
| 		this.soundEvent = soundEvent;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean shouldRenderAtSqrDistance(double distance) {
 | |
| 		double d = this.getBoundingBox().getSize() * 10.0;
 | |
| 		if (Double.isNaN(d)) {
 | |
| 			d = 1.0;
 | |
| 		}
 | |
| 
 | |
| 		d *= 64.0 * getViewScale();
 | |
| 		return distance < d * d;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void defineSynchedData(SynchedEntityData.Builder builder) {
 | |
| 		builder.define(ID_FLAGS, (byte)0);
 | |
| 		builder.define(PIERCE_LEVEL, (byte)0);
 | |
| 		builder.define(IN_GROUND, false);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void shoot(double x, double y, double z, float velocity, float inaccuracy) {
 | |
| 		super.shoot(x, y, z, velocity, inaccuracy);
 | |
| 		this.life = 0;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void lerpMotion(double x, double y, double z) {
 | |
| 		super.lerpMotion(x, y, z);
 | |
| 		this.life = 0;
 | |
| 		if (this.isInGround() && Mth.lengthSquared(x, y, z) > 0.0) {
 | |
| 			this.setInGround(false);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
 | |
| 		super.onSyncedDataUpdated(dataAccessor);
 | |
| 		if (!this.firstTick && this.shakeTime <= 0 && dataAccessor.equals(IN_GROUND) && this.isInGround()) {
 | |
| 			this.shakeTime = 7;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void tick() {
 | |
| 		boolean bl = !this.isNoPhysics();
 | |
| 		Vec3 vec3 = this.getDeltaMovement();
 | |
| 		BlockPos blockPos = this.blockPosition();
 | |
| 		BlockState blockState = this.level().getBlockState(blockPos);
 | |
| 		if (!blockState.isAir() && bl) {
 | |
| 			VoxelShape voxelShape = blockState.getCollisionShape(this.level(), blockPos);
 | |
| 			if (!voxelShape.isEmpty()) {
 | |
| 				Vec3 vec32 = this.position();
 | |
| 
 | |
| 				for (AABB aABB : voxelShape.toAabbs()) {
 | |
| 					if (aABB.move(blockPos).contains(vec32)) {
 | |
| 						this.setDeltaMovement(Vec3.ZERO);
 | |
| 						this.setInGround(true);
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (this.shakeTime > 0) {
 | |
| 			this.shakeTime--;
 | |
| 		}
 | |
| 
 | |
| 		if (this.isInWaterOrRain()) {
 | |
| 			this.clearFire();
 | |
| 		}
 | |
| 
 | |
| 		if (this.isInGround() && bl) {
 | |
| 			if (!this.level().isClientSide()) {
 | |
| 				if (this.lastState != blockState && this.shouldFall()) {
 | |
| 					this.startFalling();
 | |
| 				} else {
 | |
| 					this.tickDespawn();
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			this.inGroundTime++;
 | |
| 			if (this.isAlive()) {
 | |
| 				this.applyEffectsFromBlocks();
 | |
| 			}
 | |
| 
 | |
| 			if (!this.level().isClientSide) {
 | |
| 				this.setSharedFlagOnFire(this.getRemainingFireTicks() > 0);
 | |
| 			}
 | |
| 		} else {
 | |
| 			this.inGroundTime = 0;
 | |
| 			Vec3 vec33 = this.position();
 | |
| 			if (this.isInWater()) {
 | |
| 				this.applyInertia(this.getWaterInertia());
 | |
| 				this.addBubbleParticles(vec33);
 | |
| 			}
 | |
| 
 | |
| 			if (this.isCritArrow()) {
 | |
| 				for (int i = 0; i < 4; i++) {
 | |
| 					this.level()
 | |
| 						.addParticle(ParticleTypes.CRIT, vec33.x + vec3.x * i / 4.0, vec33.y + vec3.y * i / 4.0, vec33.z + vec3.z * i / 4.0, -vec3.x, -vec3.y + 0.2, -vec3.z);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			float f;
 | |
| 			if (!bl) {
 | |
| 				f = (float)(Mth.atan2(-vec3.x, -vec3.z) * 180.0F / (float)Math.PI);
 | |
| 			} else {
 | |
| 				f = (float)(Mth.atan2(vec3.x, vec3.z) * 180.0F / (float)Math.PI);
 | |
| 			}
 | |
| 
 | |
| 			float g = (float)(Mth.atan2(vec3.y, vec3.horizontalDistance()) * 180.0F / (float)Math.PI);
 | |
| 			this.setXRot(lerpRotation(this.getXRot(), g));
 | |
| 			this.setYRot(lerpRotation(this.getYRot(), f));
 | |
| 			if (bl) {
 | |
| 				BlockHitResult blockHitResult = this.level()
 | |
| 					.clipIncludingBorder(new ClipContext(vec33, vec33.add(vec3), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
 | |
| 				this.stepMoveAndHit(blockHitResult);
 | |
| 			} else {
 | |
| 				this.setPos(vec33.add(vec3));
 | |
| 				this.applyEffectsFromBlocks();
 | |
| 			}
 | |
| 
 | |
| 			if (!this.isInWater()) {
 | |
| 				this.applyInertia(0.99F);
 | |
| 			}
 | |
| 
 | |
| 			if (bl && !this.isInGround()) {
 | |
| 				this.applyGravity();
 | |
| 			}
 | |
| 
 | |
| 			super.tick();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void stepMoveAndHit(BlockHitResult hitResult) {
 | |
| 		while (this.isAlive()) {
 | |
| 			Vec3 vec3 = this.position();
 | |
| 			EntityHitResult entityHitResult = this.findHitEntity(vec3, hitResult.getLocation());
 | |
| 			Vec3 vec32 = ((HitResult)Objects.requireNonNullElse(entityHitResult, hitResult)).getLocation();
 | |
| 			this.setPos(vec32);
 | |
| 			this.applyEffectsFromBlocks(vec3, vec32);
 | |
| 			if (this.portalProcess != null && this.portalProcess.isInsidePortalThisTick()) {
 | |
| 				this.handlePortal();
 | |
| 			}
 | |
| 
 | |
| 			if (entityHitResult == null) {
 | |
| 				if (this.isAlive() && hitResult.getType() != HitResult.Type.MISS) {
 | |
| 					this.hitTargetOrDeflectSelf(hitResult);
 | |
| 					this.hasImpulse = true;
 | |
| 				}
 | |
| 				break;
 | |
| 			} else if (this.isAlive() && !this.noPhysics) {
 | |
| 				ProjectileDeflection projectileDeflection = this.hitTargetOrDeflectSelf(entityHitResult);
 | |
| 				this.hasImpulse = true;
 | |
| 				if (this.getPierceLevel() > 0 && projectileDeflection == ProjectileDeflection.NONE) {
 | |
| 					continue;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void applyInertia(float inertia) {
 | |
| 		Vec3 vec3 = this.getDeltaMovement();
 | |
| 		this.setDeltaMovement(vec3.scale(inertia));
 | |
| 	}
 | |
| 
 | |
| 	private void addBubbleParticles(Vec3 pos) {
 | |
| 		Vec3 vec3 = this.getDeltaMovement();
 | |
| 
 | |
| 		for (int i = 0; i < 4; i++) {
 | |
| 			float f = 0.25F;
 | |
| 			this.level().addParticle(ParticleTypes.BUBBLE, pos.x - vec3.x * 0.25, pos.y - vec3.y * 0.25, pos.z - vec3.z * 0.25, vec3.x, vec3.y, vec3.z);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected double getDefaultGravity() {
 | |
| 		return 0.05;
 | |
| 	}
 | |
| 
 | |
| 	private boolean shouldFall() {
 | |
| 		return this.isInGround() && this.level().noCollision(new AABB(this.position(), this.position()).inflate(0.06));
 | |
| 	}
 | |
| 
 | |
| 	private void startFalling() {
 | |
| 		this.setInGround(false);
 | |
| 		Vec3 vec3 = this.getDeltaMovement();
 | |
| 		this.setDeltaMovement(vec3.multiply(this.random.nextFloat() * 0.2F, this.random.nextFloat() * 0.2F, this.random.nextFloat() * 0.2F));
 | |
| 		this.life = 0;
 | |
| 	}
 | |
| 
 | |
| 	protected boolean isInGround() {
 | |
| 		return this.entityData.get(IN_GROUND);
 | |
| 	}
 | |
| 
 | |
| 	protected void setInGround(boolean inGround) {
 | |
| 		this.entityData.set(IN_GROUND, inGround);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean isPushedByFluid() {
 | |
| 		return !this.isInGround();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void move(MoverType type, Vec3 movement) {
 | |
| 		super.move(type, movement);
 | |
| 		if (type != MoverType.SELF && this.shouldFall()) {
 | |
| 			this.startFalling();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void tickDespawn() {
 | |
| 		this.life++;
 | |
| 		if (this.life >= 1200) {
 | |
| 			this.discard();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void resetPiercedEntities() {
 | |
| 		if (this.piercedAndKilledEntities != null) {
 | |
| 			this.piercedAndKilledEntities.clear();
 | |
| 		}
 | |
| 
 | |
| 		if (this.piercingIgnoreEntityIds != null) {
 | |
| 			this.piercingIgnoreEntityIds.clear();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void onItemBreak(Item item) {
 | |
| 		this.firedFromWeapon = null;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onAboveBubbleColumn(boolean downwards, BlockPos pos) {
 | |
| 		if (!this.isInGround()) {
 | |
| 			super.onAboveBubbleColumn(downwards, pos);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onInsideBubbleColumn(boolean downwards) {
 | |
| 		if (!this.isInGround()) {
 | |
| 			super.onInsideBubbleColumn(downwards);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void push(double x, double y, double z) {
 | |
| 		if (!this.isInGround()) {
 | |
| 			super.push(x, y, z);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void onHitEntity(EntityHitResult result) {
 | |
| 		super.onHitEntity(result);
 | |
| 		Entity entity = result.getEntity();
 | |
| 		float f = (float)this.getDeltaMovement().length();
 | |
| 		double d = this.baseDamage;
 | |
| 		Entity entity2 = this.getOwner();
 | |
| 		DamageSource damageSource = this.damageSources().arrow(this, (Entity)(entity2 != null ? entity2 : this));
 | |
| 		if (this.getWeaponItem() != null && this.level() instanceof ServerLevel serverLevel) {
 | |
| 			d = EnchantmentHelper.modifyDamage(serverLevel, this.getWeaponItem(), entity, damageSource, (float)d);
 | |
| 		}
 | |
| 
 | |
| 		int i = Mth.ceil(Mth.clamp(f * d, 0.0, 2.147483647E9));
 | |
| 		if (this.getPierceLevel() > 0) {
 | |
| 			if (this.piercingIgnoreEntityIds == null) {
 | |
| 				this.piercingIgnoreEntityIds = new IntOpenHashSet(5);
 | |
| 			}
 | |
| 
 | |
| 			if (this.piercedAndKilledEntities == null) {
 | |
| 				this.piercedAndKilledEntities = Lists.<Entity>newArrayListWithCapacity(5);
 | |
| 			}
 | |
| 
 | |
| 			if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) {
 | |
| 				this.discard();
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			this.piercingIgnoreEntityIds.add(entity.getId());
 | |
| 		}
 | |
| 
 | |
| 		if (this.isCritArrow()) {
 | |
| 			long l = this.random.nextInt(i / 2 + 2);
 | |
| 			i = (int)Math.min(l + i, 2147483647L);
 | |
| 		}
 | |
| 
 | |
| 		if (entity2 instanceof LivingEntity livingEntity) {
 | |
| 			livingEntity.setLastHurtMob(entity);
 | |
| 		}
 | |
| 
 | |
| 		boolean bl = entity.getType() == EntityType.ENDERMAN;
 | |
| 		int j = entity.getRemainingFireTicks();
 | |
| 		if (this.isOnFire() && !bl) {
 | |
| 			entity.igniteForSeconds(5.0F);
 | |
| 		}
 | |
| 
 | |
| 		if (entity.hurtOrSimulate(damageSource, i)) {
 | |
| 			if (bl) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			if (entity instanceof LivingEntity livingEntity2) {
 | |
| 				if (!this.level().isClientSide && this.getPierceLevel() <= 0) {
 | |
| 					livingEntity2.setArrowCount(livingEntity2.getArrowCount() + 1);
 | |
| 				}
 | |
| 
 | |
| 				this.doKnockback(livingEntity2, damageSource);
 | |
| 				if (this.level() instanceof ServerLevel serverLevel2) {
 | |
| 					EnchantmentHelper.doPostAttackEffectsWithItemSource(serverLevel2, livingEntity2, damageSource, this.getWeaponItem());
 | |
| 				}
 | |
| 
 | |
| 				this.doPostHurtEffects(livingEntity2);
 | |
| 				if (livingEntity2 instanceof Player && entity2 instanceof ServerPlayer serverPlayer && !this.isSilent() && livingEntity2 != serverPlayer) {
 | |
| 					serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.PLAY_ARROW_HIT_SOUND, 0.0F));
 | |
| 				}
 | |
| 
 | |
| 				if (!entity.isAlive() && this.piercedAndKilledEntities != null) {
 | |
| 					this.piercedAndKilledEntities.add(livingEntity2);
 | |
| 				}
 | |
| 
 | |
| 				if (!this.level().isClientSide && entity2 instanceof ServerPlayer serverPlayer) {
 | |
| 					if (this.piercedAndKilledEntities != null) {
 | |
| 						CriteriaTriggers.KILLED_BY_ARROW.trigger(serverPlayer, this.piercedAndKilledEntities, this.firedFromWeapon);
 | |
| 					} else if (!entity.isAlive()) {
 | |
| 						CriteriaTriggers.KILLED_BY_ARROW.trigger(serverPlayer, List.of(entity), this.firedFromWeapon);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			this.playSound(this.soundEvent, 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
 | |
| 			if (this.getPierceLevel() <= 0) {
 | |
| 				this.discard();
 | |
| 			}
 | |
| 		} else {
 | |
| 			entity.setRemainingFireTicks(j);
 | |
| 			this.deflect(ProjectileDeflection.REVERSE, entity, this.getOwner(), false);
 | |
| 			this.setDeltaMovement(this.getDeltaMovement().scale(0.2));
 | |
| 			if (this.level() instanceof ServerLevel serverLevel3 && this.getDeltaMovement().lengthSqr() < 1.0E-7) {
 | |
| 				if (this.pickup == AbstractArrow.Pickup.ALLOWED) {
 | |
| 					this.spawnAtLocation(serverLevel3, this.getPickupItem(), 0.1F);
 | |
| 				}
 | |
| 
 | |
| 				this.discard();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void doKnockback(LivingEntity entity, DamageSource damageSource) {
 | |
| 		double d = this.firedFromWeapon != null && this.level() instanceof ServerLevel serverLevel
 | |
| 			? EnchantmentHelper.modifyKnockback(serverLevel, this.firedFromWeapon, entity, damageSource, 0.0F)
 | |
| 			: 0.0F;
 | |
| 		if (d > 0.0) {
 | |
| 			double e = Math.max(0.0, 1.0 - entity.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
 | |
| 			Vec3 vec3 = this.getDeltaMovement().multiply(1.0, 0.0, 1.0).normalize().scale(d * 0.6 * e);
 | |
| 			if (vec3.lengthSqr() > 0.0) {
 | |
| 				entity.push(vec3.x, 0.1, vec3.z);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void onHitBlock(BlockHitResult result) {
 | |
| 		this.lastState = this.level().getBlockState(result.getBlockPos());
 | |
| 		super.onHitBlock(result);
 | |
| 		ItemStack itemStack = this.getWeaponItem();
 | |
| 		if (this.level() instanceof ServerLevel serverLevel && itemStack != null) {
 | |
| 			this.hitBlockEnchantmentEffects(serverLevel, result, itemStack);
 | |
| 		}
 | |
| 
 | |
| 		Vec3 vec3 = this.getDeltaMovement();
 | |
| 		Vec3 vec32 = new Vec3(Math.signum(vec3.x), Math.signum(vec3.y), Math.signum(vec3.z));
 | |
| 		Vec3 vec33 = vec32.scale(0.05F);
 | |
| 		this.setPos(this.position().subtract(vec33));
 | |
| 		this.setDeltaMovement(Vec3.ZERO);
 | |
| 		this.playSound(this.getHitGroundSoundEvent(), 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
 | |
| 		this.setInGround(true);
 | |
| 		this.shakeTime = 7;
 | |
| 		this.setCritArrow(false);
 | |
| 		this.setPierceLevel((byte)0);
 | |
| 		this.setSoundEvent(SoundEvents.ARROW_HIT);
 | |
| 		this.resetPiercedEntities();
 | |
| 	}
 | |
| 
 | |
| 	protected void hitBlockEnchantmentEffects(ServerLevel level, BlockHitResult hitResult, ItemStack stack) {
 | |
| 		Vec3 vec3 = hitResult.getBlockPos().clampLocationWithin(hitResult.getLocation());
 | |
| 		EnchantmentHelper.onHitBlock(
 | |
| 			level,
 | |
| 			stack,
 | |
| 			this.getOwner() instanceof LivingEntity livingEntity ? livingEntity : null,
 | |
| 			this,
 | |
| 			null,
 | |
| 			vec3,
 | |
| 			level.getBlockState(hitResult.getBlockPos()),
 | |
| 			item -> this.firedFromWeapon = null
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public ItemStack getWeaponItem() {
 | |
| 		return this.firedFromWeapon;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * The sound made when an entity is hit by this projectile
 | |
| 	 */
 | |
| 	protected SoundEvent getDefaultHitGroundSoundEvent() {
 | |
| 		return SoundEvents.ARROW_HIT;
 | |
| 	}
 | |
| 
 | |
| 	protected final SoundEvent getHitGroundSoundEvent() {
 | |
| 		return this.soundEvent;
 | |
| 	}
 | |
| 
 | |
| 	protected void doPostHurtEffects(LivingEntity target) {
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets the EntityRayTraceResult representing the entity hit
 | |
| 	 */
 | |
| 	@Nullable
 | |
| 	protected EntityHitResult findHitEntity(Vec3 startVec, Vec3 endVec) {
 | |
| 		return ProjectileUtil.getEntityHitResult(
 | |
| 			this.level(), this, startVec, endVec, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0), this::canHitEntity
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected boolean canHitEntity(Entity target) {
 | |
| 		return target instanceof Player && this.getOwner() instanceof Player player && !player.canHarmPlayer((Player)target)
 | |
| 			? false
 | |
| 			: super.canHitEntity(target) && (this.piercingIgnoreEntityIds == null || !this.piercingIgnoreEntityIds.contains(target.getId()));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void addAdditionalSaveData(ValueOutput output) {
 | |
| 		super.addAdditionalSaveData(output);
 | |
| 		output.putShort("life", (short)this.life);
 | |
| 		output.storeNullable("inBlockState", BlockState.CODEC, this.lastState);
 | |
| 		output.putByte("shake", (byte)this.shakeTime);
 | |
| 		output.putBoolean("inGround", this.isInGround());
 | |
| 		output.store("pickup", AbstractArrow.Pickup.LEGACY_CODEC, this.pickup);
 | |
| 		output.putDouble("damage", this.baseDamage);
 | |
| 		output.putBoolean("crit", this.isCritArrow());
 | |
| 		output.putByte("PierceLevel", this.getPierceLevel());
 | |
| 		output.store("SoundEvent", BuiltInRegistries.SOUND_EVENT.byNameCodec(), this.soundEvent);
 | |
| 		output.store("item", ItemStack.CODEC, this.pickupItemStack);
 | |
| 		output.storeNullable("weapon", ItemStack.CODEC, this.firedFromWeapon);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void readAdditionalSaveData(ValueInput input) {
 | |
| 		super.readAdditionalSaveData(input);
 | |
| 		this.life = input.getShortOr("life", (short)0);
 | |
| 		this.lastState = (BlockState)input.read("inBlockState", BlockState.CODEC).orElse(null);
 | |
| 		this.shakeTime = input.getByteOr("shake", (byte)0) & 255;
 | |
| 		this.setInGround(input.getBooleanOr("inGround", false));
 | |
| 		this.baseDamage = input.getDoubleOr("damage", 2.0);
 | |
| 		this.pickup = (AbstractArrow.Pickup)input.read("pickup", AbstractArrow.Pickup.LEGACY_CODEC).orElse(AbstractArrow.Pickup.DISALLOWED);
 | |
| 		this.setCritArrow(input.getBooleanOr("crit", false));
 | |
| 		this.setPierceLevel(input.getByteOr("PierceLevel", (byte)0));
 | |
| 		this.soundEvent = (SoundEvent)input.read("SoundEvent", BuiltInRegistries.SOUND_EVENT.byNameCodec()).orElse(this.getDefaultHitGroundSoundEvent());
 | |
| 		this.setPickupItemStack((ItemStack)input.read("item", ItemStack.CODEC).orElse(this.getDefaultPickupItem()));
 | |
| 		this.firedFromWeapon = (ItemStack)input.read("weapon", ItemStack.CODEC).orElse(null);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setOwner(@Nullable Entity owner) {
 | |
| 		super.setOwner(owner);
 | |
| 
 | |
| 		this.pickup = switch (owner) {
 | |
| 			case Player player when this.pickup == AbstractArrow.Pickup.DISALLOWED -> AbstractArrow.Pickup.ALLOWED;
 | |
| 			case OminousItemSpawner ominousItemSpawner -> AbstractArrow.Pickup.DISALLOWED;
 | |
| 			case null, default -> this.pickup;
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void playerTouch(Player player) {
 | |
| 		if (!this.level().isClientSide && (this.isInGround() || this.isNoPhysics()) && this.shakeTime <= 0) {
 | |
| 			if (this.tryPickup(player)) {
 | |
| 				player.take(this, 1);
 | |
| 				this.discard();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected boolean tryPickup(Player player) {
 | |
| 		return switch (this.pickup) {
 | |
| 			case DISALLOWED -> false;
 | |
| 			case ALLOWED -> player.getInventory().add(this.getPickupItem());
 | |
| 			case CREATIVE_ONLY -> player.hasInfiniteMaterials();
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	protected ItemStack getPickupItem() {
 | |
| 		return this.pickupItemStack.copy();
 | |
| 	}
 | |
| 
 | |
| 	protected abstract ItemStack getDefaultPickupItem();
 | |
| 
 | |
| 	@Override
 | |
| 	protected Entity.MovementEmission getMovementEmission() {
 | |
| 		return Entity.MovementEmission.NONE;
 | |
| 	}
 | |
| 
 | |
| 	public ItemStack getPickupItemStackOrigin() {
 | |
| 		return this.pickupItemStack;
 | |
| 	}
 | |
| 
 | |
| 	public void setBaseDamage(double baseDamage) {
 | |
| 		this.baseDamage = baseDamage;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean isAttackable() {
 | |
| 		return this.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Whether the arrow has a stream of critical hit particles flying behind it.
 | |
| 	 */
 | |
| 	public void setCritArrow(boolean critArrow) {
 | |
| 		this.setFlag(1, critArrow);
 | |
| 	}
 | |
| 
 | |
| 	private void setPierceLevel(byte pierceLevel) {
 | |
| 		this.entityData.set(PIERCE_LEVEL, pierceLevel);
 | |
| 	}
 | |
| 
 | |
| 	private void setFlag(int id, boolean value) {
 | |
| 		byte b = this.entityData.get(ID_FLAGS);
 | |
| 		if (value) {
 | |
| 			this.entityData.set(ID_FLAGS, (byte)(b | id));
 | |
| 		} else {
 | |
| 			this.entityData.set(ID_FLAGS, (byte)(b & ~id));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void setPickupItemStack(ItemStack pickupItemStack) {
 | |
| 		if (!pickupItemStack.isEmpty()) {
 | |
| 			this.pickupItemStack = pickupItemStack;
 | |
| 		} else {
 | |
| 			this.pickupItemStack = this.getDefaultPickupItem();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Whether the arrow has a stream of critical hit particles flying behind it.
 | |
| 	 */
 | |
| 	public boolean isCritArrow() {
 | |
| 		byte b = this.entityData.get(ID_FLAGS);
 | |
| 		return (b & 1) != 0;
 | |
| 	}
 | |
| 
 | |
| 	public byte getPierceLevel() {
 | |
| 		return this.entityData.get(PIERCE_LEVEL);
 | |
| 	}
 | |
| 
 | |
| 	public void setBaseDamageFromMob(float velocity) {
 | |
| 		this.setBaseDamage(velocity * 2.0F + this.random.triangle(this.level().getDifficulty().getId() * 0.11, 0.57425));
 | |
| 	}
 | |
| 
 | |
| 	protected float getWaterInertia() {
 | |
| 		return 0.6F;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Sets if this arrow can noClip
 | |
| 	 */
 | |
| 	public void setNoPhysics(boolean noPhysics) {
 | |
| 		this.noPhysics = noPhysics;
 | |
| 		this.setFlag(2, noPhysics);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Whether the arrow can noClip
 | |
| 	 */
 | |
| 	public boolean isNoPhysics() {
 | |
| 		return !this.level().isClientSide ? this.noPhysics : (this.entityData.get(ID_FLAGS) & 2) != 0;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean isPickable() {
 | |
| 		return super.isPickable() && !this.isInGround();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public SlotAccess getSlot(int slot) {
 | |
| 		return slot == 0 ? SlotAccess.of(this::getPickupItemStackOrigin, this::setPickupItemStack) : super.getSlot(slot);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected boolean shouldBounceOnWorldBorder() {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	public static enum Pickup {
 | |
| 		DISALLOWED,
 | |
| 		ALLOWED,
 | |
| 		CREATIVE_ONLY;
 | |
| 
 | |
| 		public static final Codec<AbstractArrow.Pickup> LEGACY_CODEC = Codec.BYTE.xmap(AbstractArrow.Pickup::byOrdinal, pickup -> (byte)pickup.ordinal());
 | |
| 
 | |
| 		public static AbstractArrow.Pickup byOrdinal(int ordinal) {
 | |
| 			if (ordinal < 0 || ordinal > values().length) {
 | |
| 				ordinal = 0;
 | |
| 			}
 | |
| 
 | |
| 			return values()[ordinal];
 | |
| 		}
 | |
| 	}
 | |
| }
 |