minecraft-src/net/minecraft/world/entity/projectile/Projectile.java
2025-07-04 01:41:11 +03:00

308 lines
10 KiB
Java

package net.minecraft.world.entity.projectile;
import com.google.common.base.MoreObjects;
import it.unimi.dsi.fastutil.doubles.DoubleDoubleImmutablePair;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
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.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.TraceableEntity;
import net.minecraft.world.entity.player.Player;
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.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 {
@Nullable
private UUID ownerUUID;
@Nullable
private Entity cachedOwner;
private boolean leftOwner;
private boolean hasBeenShot;
@Nullable
private Entity lastDeflectedBy;
Projectile(EntityType<? extends Projectile> entityType, Level level) {
super(entityType, level);
}
public void setOwner(@Nullable Entity owner) {
if (owner != null) {
this.ownerUUID = owner.getUUID();
this.cachedOwner = owner;
}
}
@Nullable
@Override
public Entity getOwner() {
if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) {
return this.cachedOwner;
} else if (this.ownerUUID != null && this.level() instanceof ServerLevel serverLevel) {
this.cachedOwner = serverLevel.getEntity(this.ownerUUID);
return this.cachedOwner;
} else {
return null;
}
}
public Entity getEffectSource() {
return MoreObjects.firstNonNull(this.getOwner(), this);
}
@Override
protected void addAdditionalSaveData(CompoundTag compound) {
if (this.ownerUUID != null) {
compound.putUUID("Owner", this.ownerUUID);
}
if (this.leftOwner) {
compound.putBoolean("LeftOwner", true);
}
compound.putBoolean("HasBeenShot", this.hasBeenShot);
}
protected boolean ownedBy(Entity entity) {
return entity.getUUID().equals(this.ownerUUID);
}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {
if (compound.hasUUID("Owner")) {
this.ownerUUID = compound.getUUID("Owner");
this.cachedOwner = null;
}
this.leftOwner = compound.getBoolean("LeftOwner");
this.hasBeenShot = compound.getBoolean("HasBeenShot");
}
@Override
public void restoreFrom(Entity entity) {
super.restoreFrom(entity);
if (entity instanceof Projectile projectile) {
this.cachedOwner = projectile.cachedOwner;
}
}
@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) {
for (Entity entity2 : this.level()
.getEntities(this, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0), entityx -> !entityx.isSpectator() && entityx.isPickable())) {
if (entity2.getRootVehicle() == entity.getRootVehicle()) {
return false;
}
}
}
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));
}
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;
}
}
this.onHit(hitResult);
return ProjectileDeflection.NONE;
}
public boolean deflect(ProjectileDeflection deflection, @Nullable Entity entity, @Nullable Entity owner, boolean deflectedByPlayer) {
if (!this.level().isClientSide) {
deflection.deflect(this, entity, this.random);
this.setOwner(owner);
this.onDeflection(entity, deflectedByPlayer);
}
return true;
}
protected void onDeflection(@Nullable Entity entity, boolean deflectedByPlayer) {
}
/**
* 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);
}
@Override
public void lerpMotion(double x, double y, double z) {
this.setDeltaMovement(x, y, z);
if (this.xRotO == 0.0F && this.yRotO == 0.0F) {
double d = Math.sqrt(x * x + z * z);
this.setXRot((float)(Mth.atan2(y, d) * 180.0F / (float)Math.PI));
this.setYRot((float)(Mth.atan2(x, z) * 180.0F / (float)Math.PI));
this.xRotO = this.getXRot();
this.yRotO = this.getYRot();
this.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
}
}
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(Level 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(Level 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);
}
}