705 lines
22 KiB
Java
705 lines
22 KiB
Java
package net.minecraft.world.entity.projectile;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
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.core.registries.Registries;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtUtils;
|
|
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.resources.ResourceLocation;
|
|
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.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
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.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.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 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 int FLAG_CRIT = 1;
|
|
private static final int FLAG_NOPHYSICS = 2;
|
|
@Nullable
|
|
private BlockState lastState;
|
|
protected boolean inGround;
|
|
protected int inGroundTime;
|
|
public AbstractArrow.Pickup pickup = AbstractArrow.Pickup.DISALLOWED;
|
|
public int shakeTime;
|
|
private int life;
|
|
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.setCustomName(pickupItemStack.get(DataComponents.CUSTOM_NAME));
|
|
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);
|
|
}
|
|
|
|
EnchantmentHelper.onProjectileSpawned(serverLevel, firedFromWeapon, this, item -> this.firedFromWeapon = null);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
@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 lerpTo(double x, double y, double z, float yRot, float xRot, int steps) {
|
|
this.setPos(x, y, z);
|
|
this.setRot(yRot, xRot);
|
|
}
|
|
|
|
@Override
|
|
public void lerpMotion(double x, double y, double z) {
|
|
super.lerpMotion(x, y, z);
|
|
this.life = 0;
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
boolean bl = this.isNoPhysics();
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
if (this.xRotO == 0.0F && this.yRotO == 0.0F) {
|
|
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();
|
|
}
|
|
|
|
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.inGround = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.shakeTime > 0) {
|
|
this.shakeTime--;
|
|
}
|
|
|
|
if (this.isInWaterOrRain() || blockState.is(Blocks.POWDER_SNOW)) {
|
|
this.clearFire();
|
|
}
|
|
|
|
if (this.inGround && !bl) {
|
|
if (this.lastState != blockState && this.shouldFall()) {
|
|
this.startFalling();
|
|
} else if (!this.level().isClientSide) {
|
|
this.tickDespawn();
|
|
}
|
|
|
|
this.inGroundTime++;
|
|
} else {
|
|
this.inGroundTime = 0;
|
|
Vec3 vec33 = this.position();
|
|
Vec3 vec32 = vec33.add(vec3);
|
|
HitResult hitResult = this.level().clip(new ClipContext(vec33, vec32, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
|
|
if (hitResult.getType() != HitResult.Type.MISS) {
|
|
vec32 = hitResult.getLocation();
|
|
}
|
|
|
|
while (!this.isRemoved()) {
|
|
EntityHitResult entityHitResult = this.findHitEntity(vec33, vec32);
|
|
if (entityHitResult != null) {
|
|
hitResult = entityHitResult;
|
|
}
|
|
|
|
if (hitResult != null && hitResult.getType() == HitResult.Type.ENTITY) {
|
|
Entity entity = ((EntityHitResult)hitResult).getEntity();
|
|
Entity entity2 = this.getOwner();
|
|
if (entity instanceof Player && entity2 instanceof Player && !((Player)entity2).canHarmPlayer((Player)entity)) {
|
|
hitResult = null;
|
|
entityHitResult = null;
|
|
}
|
|
}
|
|
|
|
if (hitResult != null && !bl) {
|
|
ProjectileDeflection projectileDeflection = this.hitTargetOrDeflectSelf(hitResult);
|
|
this.hasImpulse = true;
|
|
if (projectileDeflection != ProjectileDeflection.NONE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entityHitResult == null || this.getPierceLevel() <= 0) {
|
|
break;
|
|
}
|
|
|
|
hitResult = null;
|
|
}
|
|
|
|
vec3 = this.getDeltaMovement();
|
|
double e = vec3.x;
|
|
double f = vec3.y;
|
|
double g = vec3.z;
|
|
if (this.isCritArrow()) {
|
|
for (int i = 0; i < 4; i++) {
|
|
this.level().addParticle(ParticleTypes.CRIT, this.getX() + e * i / 4.0, this.getY() + f * i / 4.0, this.getZ() + g * i / 4.0, -e, -f + 0.2, -g);
|
|
}
|
|
}
|
|
|
|
double h = this.getX() + e;
|
|
double j = this.getY() + f;
|
|
double k = this.getZ() + g;
|
|
double l = vec3.horizontalDistance();
|
|
if (bl) {
|
|
this.setYRot((float)(Mth.atan2(-e, -g) * 180.0F / (float)Math.PI));
|
|
} else {
|
|
this.setYRot((float)(Mth.atan2(e, g) * 180.0F / (float)Math.PI));
|
|
}
|
|
|
|
this.setXRot((float)(Mth.atan2(f, l) * 180.0F / (float)Math.PI));
|
|
this.setXRot(lerpRotation(this.xRotO, this.getXRot()));
|
|
this.setYRot(lerpRotation(this.yRotO, this.getYRot()));
|
|
float m = 0.99F;
|
|
if (this.isInWater()) {
|
|
for (int n = 0; n < 4; n++) {
|
|
float o = 0.25F;
|
|
this.level().addParticle(ParticleTypes.BUBBLE, h - e * 0.25, j - f * 0.25, k - g * 0.25, e, f, g);
|
|
}
|
|
|
|
m = this.getWaterInertia();
|
|
}
|
|
|
|
this.setDeltaMovement(vec3.scale(m));
|
|
if (!bl) {
|
|
this.applyGravity();
|
|
}
|
|
|
|
this.setPos(h, j, k);
|
|
this.checkInsideBlocks();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected double getDefaultGravity() {
|
|
return 0.05;
|
|
}
|
|
|
|
private boolean shouldFall() {
|
|
return this.inGround && this.level().noCollision(new AABB(this.position(), this.position()).inflate(0.06));
|
|
}
|
|
|
|
private void startFalling() {
|
|
this.inGround = 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;
|
|
}
|
|
|
|
@Override
|
|
public void move(MoverType type, Vec3 pos) {
|
|
super.move(type, pos);
|
|
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 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.hurt(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 != entity2 && livingEntity2 instanceof Player && entity2 instanceof ServerPlayer && !this.isSilent()) {
|
|
((ServerPlayer)entity2).connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.ARROW_HIT_PLAYER, 0.0F));
|
|
}
|
|
|
|
if (!entity.isAlive() && this.piercedAndKilledEntities != null) {
|
|
this.piercedAndKilledEntities.add(livingEntity2);
|
|
}
|
|
|
|
if (!this.level().isClientSide && entity2 instanceof ServerPlayer serverPlayer) {
|
|
if (this.piercedAndKilledEntities != null && this.shotFromCrossbow()) {
|
|
CriteriaTriggers.KILLED_BY_CROSSBOW.trigger(serverPlayer, this.piercedAndKilledEntities);
|
|
} else if (!entity.isAlive() && this.shotFromCrossbow()) {
|
|
CriteriaTriggers.KILLED_BY_CROSSBOW.trigger(serverPlayer, Arrays.asList(entity));
|
|
}
|
|
}
|
|
}
|
|
|
|
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().isClientSide && this.getDeltaMovement().lengthSqr() < 1.0E-7) {
|
|
if (this.pickup == AbstractArrow.Pickup.ALLOWED) {
|
|
this.spawnAtLocation(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);
|
|
Vec3 vec3 = result.getLocation().subtract(this.getX(), this.getY(), this.getZ());
|
|
this.setDeltaMovement(vec3);
|
|
ItemStack itemStack = this.getWeaponItem();
|
|
if (this.level() instanceof ServerLevel serverLevel && itemStack != null) {
|
|
this.hitBlockEnchantmentEffects(serverLevel, result, itemStack);
|
|
}
|
|
|
|
Vec3 vec32 = vec3.normalize().scale(0.05F);
|
|
this.setPosRaw(this.getX() - vec32.x, this.getY() - vec32.y, this.getZ() - vec32.z);
|
|
this.playSound(this.getHitGroundSoundEvent(), 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
|
|
this.inGround = 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 super.canHitEntity(target) && (this.piercingIgnoreEntityIds == null || !this.piercingIgnoreEntityIds.contains(target.getId()));
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag compound) {
|
|
super.addAdditionalSaveData(compound);
|
|
compound.putShort("life", (short)this.life);
|
|
if (this.lastState != null) {
|
|
compound.put("inBlockState", NbtUtils.writeBlockState(this.lastState));
|
|
}
|
|
|
|
compound.putByte("shake", (byte)this.shakeTime);
|
|
compound.putBoolean("inGround", this.inGround);
|
|
compound.putByte("pickup", (byte)this.pickup.ordinal());
|
|
compound.putDouble("damage", this.baseDamage);
|
|
compound.putBoolean("crit", this.isCritArrow());
|
|
compound.putByte("PierceLevel", this.getPierceLevel());
|
|
compound.putString("SoundEvent", BuiltInRegistries.SOUND_EVENT.getKey(this.soundEvent).toString());
|
|
compound.put("item", this.pickupItemStack.save(this.registryAccess()));
|
|
if (this.firedFromWeapon != null) {
|
|
compound.put("weapon", this.firedFromWeapon.save(this.registryAccess(), new CompoundTag()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag compound) {
|
|
super.readAdditionalSaveData(compound);
|
|
this.life = compound.getShort("life");
|
|
if (compound.contains("inBlockState", 10)) {
|
|
this.lastState = NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), compound.getCompound("inBlockState"));
|
|
}
|
|
|
|
this.shakeTime = compound.getByte("shake") & 255;
|
|
this.inGround = compound.getBoolean("inGround");
|
|
if (compound.contains("damage", 99)) {
|
|
this.baseDamage = compound.getDouble("damage");
|
|
}
|
|
|
|
this.pickup = AbstractArrow.Pickup.byOrdinal(compound.getByte("pickup"));
|
|
this.setCritArrow(compound.getBoolean("crit"));
|
|
this.setPierceLevel(compound.getByte("PierceLevel"));
|
|
if (compound.contains("SoundEvent", 8)) {
|
|
this.soundEvent = (SoundEvent)BuiltInRegistries.SOUND_EVENT
|
|
.getOptional(ResourceLocation.parse(compound.getString("SoundEvent")))
|
|
.orElse(this.getDefaultHitGroundSoundEvent());
|
|
}
|
|
|
|
if (compound.contains("item", 10)) {
|
|
this.setPickupItemStack((ItemStack)ItemStack.parse(this.registryAccess(), compound.getCompound("item")).orElse(this.getDefaultPickupItem()));
|
|
} else {
|
|
this.setPickupItemStack(this.getDefaultPickupItem());
|
|
}
|
|
|
|
if (compound.contains("weapon", 10)) {
|
|
this.firedFromWeapon = (ItemStack)ItemStack.parse(this.registryAccess(), compound.getCompound("weapon")).orElse(null);
|
|
} else {
|
|
this.firedFromWeapon = 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.inGround || 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;
|
|
}
|
|
|
|
public double getBaseDamage() {
|
|
return this.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;
|
|
}
|
|
|
|
/**
|
|
* Whether the arrow was shot from a crossbow.
|
|
*/
|
|
public boolean shotFromCrossbow() {
|
|
return this.firedFromWeapon != null && this.firedFromWeapon.is(Items.CROSSBOW);
|
|
}
|
|
|
|
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.inGround;
|
|
}
|
|
|
|
@Override
|
|
public SlotAccess getSlot(int slot) {
|
|
return slot == 0 ? SlotAccess.of(this::getPickupItemStackOrigin, this::setPickupItemStack) : super.getSlot(slot);
|
|
}
|
|
|
|
public static enum Pickup {
|
|
DISALLOWED,
|
|
ALLOWED,
|
|
CREATIVE_ONLY;
|
|
|
|
public static AbstractArrow.Pickup byOrdinal(int ordinal) {
|
|
if (ordinal < 0 || ordinal > values().length) {
|
|
ordinal = 0;
|
|
}
|
|
|
|
return values()[ordinal];
|
|
}
|
|
}
|
|
}
|