minecraft-src/net/minecraft/world/entity/projectile/AbstractArrow.java
2025-07-04 03:45:38 +03:00

743 lines
24 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.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
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.network.syncher.SynchedEntityData.Builder;
import net.minecraft.resources.RegistryOps;
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.ClipContext.Block;
import net.minecraft.world.level.ClipContext.Fluid;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
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.HitResult.Type;
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(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() || blockState.is(Blocks.POWDER_SNOW)) {
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), Block.COLLIDER, 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() != 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 intertia) {
Vec3 vec3 = this.getDeltaMovement();
this.setDeltaMovement(vec3.scale(intertia));
}
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
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
tag.putShort("life", (short)this.life);
tag.storeNullable("inBlockState", BlockState.CODEC, registryOps, this.lastState);
tag.putByte("shake", (byte)this.shakeTime);
tag.putBoolean("inGround", this.isInGround());
tag.store("pickup", AbstractArrow.Pickup.LEGACY_CODEC, this.pickup);
tag.putDouble("damage", this.baseDamage);
tag.putBoolean("crit", this.isCritArrow());
tag.putByte("PierceLevel", this.getPierceLevel());
tag.store("SoundEvent", BuiltInRegistries.SOUND_EVENT.byNameCodec(), this.soundEvent);
tag.store("item", ItemStack.CODEC, registryOps, this.pickupItemStack);
tag.storeNullable("weapon", ItemStack.CODEC, registryOps, this.firedFromWeapon);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
this.life = tag.getShortOr("life", (short)0);
this.lastState = (BlockState)tag.read("inBlockState", BlockState.CODEC, registryOps).orElse(null);
this.shakeTime = tag.getByteOr("shake", (byte)0) & 255;
this.setInGround(tag.getBooleanOr("inGround", false));
this.baseDamage = tag.getDoubleOr("damage", 2.0);
this.pickup = (AbstractArrow.Pickup)tag.read("pickup", AbstractArrow.Pickup.LEGACY_CODEC).orElse(AbstractArrow.Pickup.DISALLOWED);
this.setCritArrow(tag.getBooleanOr("crit", false));
this.setPierceLevel(tag.getByteOr("PierceLevel", (byte)0));
this.soundEvent = (SoundEvent)tag.read("SoundEvent", BuiltInRegistries.SOUND_EVENT.byNameCodec()).orElse(this.getDefaultHitGroundSoundEvent());
this.setPickupItemStack((ItemStack)tag.read("item", ItemStack.CODEC, registryOps).orElse(this.getDefaultPickupItem()));
this.firedFromWeapon = (ItemStack)tag.read("weapon", ItemStack.CODEC, registryOps).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];
}
}
}