3996 lines
123 KiB
Java
3996 lines
123 KiB
Java
package net.minecraft.world.entity;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.common.collect.ImmutableList.Builder;
|
|
import com.mojang.serialization.Codec;
|
|
import it.unimi.dsi.fastutil.floats.FloatArraySet;
|
|
import it.unimi.dsi.fastutil.floats.FloatArrays;
|
|
import it.unimi.dsi.fastutil.floats.FloatSet;
|
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
|
import it.unimi.dsi.fastutil.objects.Object2DoubleArrayMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.BiConsumer;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.CrashReport;
|
|
import net.minecraft.CrashReportCategory;
|
|
import net.minecraft.CrashReportDetail;
|
|
import net.minecraft.ReportedException;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.BlockUtil.FoundRectangle;
|
|
import net.minecraft.advancements.CriteriaTriggers;
|
|
import net.minecraft.commands.CommandSource;
|
|
import net.minecraft.commands.CommandSourceStack;
|
|
import net.minecraft.commands.arguments.EntityAnchorArgument.Anchor;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.RegistryAccess;
|
|
import net.minecraft.core.SectionPos;
|
|
import net.minecraft.core.UUIDUtil;
|
|
import net.minecraft.core.component.DataComponentGetter;
|
|
import net.minecraft.core.component.DataComponentType;
|
|
import net.minecraft.core.component.DataComponents;
|
|
import net.minecraft.core.particles.BlockParticleOption;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.ListTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.ComponentSerialization;
|
|
import net.minecraft.network.chat.HoverEvent;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
|
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
|
|
import net.minecraft.network.protocol.game.VecDeltaCodec;
|
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.network.syncher.SyncedDataHolder;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.network.syncher.SynchedEntityData.DataValue;
|
|
import net.minecraft.resources.RegistryOps;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.level.ServerEntity;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.level.TicketType;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.tags.DamageTypeTags;
|
|
import net.minecraft.tags.EntityTypeTags;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.tags.TagKey;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.Nameable;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.damagesource.DamageSources;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.entity.projectile.Projectile;
|
|
import net.minecraft.world.entity.projectile.ProjectileDeflection;
|
|
import net.minecraft.world.entity.vehicle.AbstractBoat;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
import net.minecraft.world.item.component.CustomData;
|
|
import net.minecraft.world.level.BlockGetter;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.ClipContext;
|
|
import net.minecraft.world.level.Explosion;
|
|
import net.minecraft.world.level.ItemLike;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.ClipContext.Block;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.FenceGateBlock;
|
|
import net.minecraft.world.level.block.HoneyBlock;
|
|
import net.minecraft.world.level.block.Mirror;
|
|
import net.minecraft.world.level.block.Portal;
|
|
import net.minecraft.world.level.block.RenderShape;
|
|
import net.minecraft.world.level.block.Rotation;
|
|
import net.minecraft.world.level.block.SoundType;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.border.WorldBorder;
|
|
import net.minecraft.world.level.entity.EntityAccess;
|
|
import net.minecraft.world.level.entity.EntityInLevelCallback;
|
|
import net.minecraft.world.level.gameevent.DynamicGameEventListener;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.gameevent.GameEvent.Context;
|
|
import net.minecraft.world.level.levelgen.Heightmap;
|
|
import net.minecraft.world.level.material.Fluid;
|
|
import net.minecraft.world.level.material.FluidState;
|
|
import net.minecraft.world.level.material.PushReaction;
|
|
import net.minecraft.world.level.portal.PortalShape;
|
|
import net.minecraft.world.level.portal.TeleportTransition;
|
|
import net.minecraft.world.level.storage.loot.LootTable;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.HitResult;
|
|
import net.minecraft.world.phys.Vec2;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.phys.HitResult.Type;
|
|
import net.minecraft.world.phys.shapes.BooleanOp;
|
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
|
import net.minecraft.world.phys.shapes.Shapes;
|
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
|
import net.minecraft.world.scores.PlayerTeam;
|
|
import net.minecraft.world.scores.ScoreHolder;
|
|
import net.minecraft.world.scores.Team;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, DataComponentGetter {
|
|
public static final String ID_TAG = "id";
|
|
public static final String PASSENGERS_TAG = "Passengers";
|
|
private static final String DATA_TAG = "data";
|
|
private static final AtomicInteger ENTITY_COUNTER = new AtomicInteger();
|
|
public static final int CONTENTS_SLOT_INDEX = 0;
|
|
public static final int BOARDING_COOLDOWN = 60;
|
|
public static final int TOTAL_AIR_SUPPLY = 300;
|
|
public static final int MAX_ENTITY_TAG_COUNT = 1024;
|
|
private static final Codec<List<String>> TAG_LIST_CODEC = Codec.STRING.sizeLimitedListOf(1024);
|
|
public static final float DELTA_AFFECTED_BY_BLOCKS_BELOW_0_2 = 0.2F;
|
|
public static final double DELTA_AFFECTED_BY_BLOCKS_BELOW_0_5 = 0.500001;
|
|
public static final double DELTA_AFFECTED_BY_BLOCKS_BELOW_1_0 = 0.999999;
|
|
public static final int BASE_TICKS_REQUIRED_TO_FREEZE = 140;
|
|
public static final int FREEZE_HURT_FREQUENCY = 40;
|
|
public static final int BASE_SAFE_FALL_DISTANCE = 3;
|
|
private static final ImmutableList<Direction.Axis> YXZ_AXIS_ORDER = ImmutableList.of(Direction.Axis.Y, Direction.Axis.X, Direction.Axis.Z);
|
|
private static final ImmutableList<Direction.Axis> YZX_AXIS_ORDER = ImmutableList.of(Direction.Axis.Y, Direction.Axis.Z, Direction.Axis.X);
|
|
private static final AABB INITIAL_AABB = new AABB(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
|
private static final double WATER_FLOW_SCALE = 0.014;
|
|
private static final double LAVA_FAST_FLOW_SCALE = 0.007;
|
|
private static final double LAVA_SLOW_FLOW_SCALE = 0.0023333333333333335;
|
|
public static final String UUID_TAG = "UUID";
|
|
private static double viewScale = 1.0;
|
|
private final EntityType<?> type;
|
|
private int id = ENTITY_COUNTER.incrementAndGet();
|
|
public boolean blocksBuilding;
|
|
private ImmutableList<Entity> passengers = ImmutableList.of();
|
|
protected int boardingCooldown;
|
|
@Nullable
|
|
private Entity vehicle;
|
|
private Level level;
|
|
public double xo;
|
|
public double yo;
|
|
public double zo;
|
|
private Vec3 position;
|
|
private BlockPos blockPosition;
|
|
private ChunkPos chunkPosition;
|
|
private Vec3 deltaMovement = Vec3.ZERO;
|
|
private float yRot;
|
|
private float xRot;
|
|
public float yRotO;
|
|
public float xRotO;
|
|
private AABB bb = INITIAL_AABB;
|
|
private boolean onGround;
|
|
public boolean horizontalCollision;
|
|
public boolean verticalCollision;
|
|
public boolean verticalCollisionBelow;
|
|
public boolean minorHorizontalCollision;
|
|
public boolean hurtMarked;
|
|
protected Vec3 stuckSpeedMultiplier = Vec3.ZERO;
|
|
@Nullable
|
|
private Entity.RemovalReason removalReason;
|
|
public static final float DEFAULT_BB_WIDTH = 0.6F;
|
|
public static final float DEFAULT_BB_HEIGHT = 1.8F;
|
|
public float moveDist;
|
|
public float flyDist;
|
|
public double fallDistance;
|
|
private float nextStep = 1.0F;
|
|
public double xOld;
|
|
public double yOld;
|
|
public double zOld;
|
|
public boolean noPhysics;
|
|
protected final RandomSource random = RandomSource.create();
|
|
public int tickCount;
|
|
private int remainingFireTicks = -this.getFireImmuneTicks();
|
|
protected boolean wasTouchingWater;
|
|
protected Object2DoubleMap<TagKey<Fluid>> fluidHeight = new Object2DoubleArrayMap<>(2);
|
|
protected boolean wasEyeInWater;
|
|
private final Set<TagKey<Fluid>> fluidOnEyes = new HashSet();
|
|
public int invulnerableTime;
|
|
protected boolean firstTick = true;
|
|
protected final SynchedEntityData entityData;
|
|
protected static final EntityDataAccessor<Byte> DATA_SHARED_FLAGS_ID = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BYTE);
|
|
protected static final int FLAG_ONFIRE = 0;
|
|
private static final int FLAG_SHIFT_KEY_DOWN = 1;
|
|
private static final int FLAG_SPRINTING = 3;
|
|
private static final int FLAG_SWIMMING = 4;
|
|
private static final int FLAG_INVISIBLE = 5;
|
|
protected static final int FLAG_GLOWING = 6;
|
|
protected static final int FLAG_FALL_FLYING = 7;
|
|
private static final EntityDataAccessor<Integer> DATA_AIR_SUPPLY_ID = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Optional<Component>> DATA_CUSTOM_NAME = SynchedEntityData.defineId(
|
|
Entity.class, EntityDataSerializers.OPTIONAL_COMPONENT
|
|
);
|
|
private static final EntityDataAccessor<Boolean> DATA_CUSTOM_NAME_VISIBLE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
|
|
private static final EntityDataAccessor<Boolean> DATA_SILENT = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
|
|
private static final EntityDataAccessor<Boolean> DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
|
|
protected static final EntityDataAccessor<Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE);
|
|
private static final EntityDataAccessor<Integer> DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT);
|
|
private EntityInLevelCallback levelCallback = EntityInLevelCallback.NULL;
|
|
private final VecDeltaCodec packetPositionCodec = new VecDeltaCodec();
|
|
public boolean hasImpulse;
|
|
@Nullable
|
|
public PortalProcessor portalProcess;
|
|
private int portalCooldown;
|
|
private boolean invulnerable;
|
|
protected UUID uuid = Mth.createInsecureUUID(this.random);
|
|
protected String stringUUID = this.uuid.toString();
|
|
private boolean hasGlowingTag;
|
|
private final Set<String> tags = Sets.<String>newHashSet();
|
|
private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
|
|
private long pistonDeltasGameTime;
|
|
private EntityDimensions dimensions;
|
|
private float eyeHeight;
|
|
public boolean isInPowderSnow;
|
|
public boolean wasInPowderSnow;
|
|
public Optional<BlockPos> mainSupportingBlockPos = Optional.empty();
|
|
private boolean onGroundNoBlocks = false;
|
|
private float crystalSoundIntensity;
|
|
private int lastCrystalSoundPlayTick;
|
|
private boolean hasVisualFire;
|
|
@Nullable
|
|
private BlockState inBlockState = null;
|
|
private final List<List<Entity.Movement>> movementThisTick = new ObjectArrayList<>();
|
|
private final List<Entity.Movement> finalMovementsThisTick = new ObjectArrayList<>();
|
|
private final LongSet visitedBlocks = new LongOpenHashSet();
|
|
private final InsideBlockEffectApplier.StepBasedCollector insideEffectCollector = new InsideBlockEffectApplier.StepBasedCollector();
|
|
private CustomData customData = CustomData.EMPTY;
|
|
|
|
public Entity(EntityType<?> entityType, Level level) {
|
|
this.type = entityType;
|
|
this.level = level;
|
|
this.dimensions = entityType.getDimensions();
|
|
this.position = Vec3.ZERO;
|
|
this.blockPosition = BlockPos.ZERO;
|
|
this.chunkPosition = ChunkPos.ZERO;
|
|
net.minecraft.network.syncher.SynchedEntityData.Builder builder = new net.minecraft.network.syncher.SynchedEntityData.Builder(this);
|
|
builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
|
|
builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
|
|
builder.define(DATA_CUSTOM_NAME_VISIBLE, false);
|
|
builder.define(DATA_CUSTOM_NAME, Optional.empty());
|
|
builder.define(DATA_SILENT, false);
|
|
builder.define(DATA_NO_GRAVITY, false);
|
|
builder.define(DATA_POSE, Pose.STANDING);
|
|
builder.define(DATA_TICKS_FROZEN, 0);
|
|
this.defineSynchedData(builder);
|
|
this.entityData = builder.build();
|
|
this.setPos(0.0, 0.0, 0.0);
|
|
this.eyeHeight = this.dimensions.eyeHeight();
|
|
}
|
|
|
|
public boolean isColliding(BlockPos pos, BlockState state) {
|
|
VoxelShape voxelShape = state.getCollisionShape(this.level(), pos, CollisionContext.of(this)).move(pos);
|
|
return Shapes.joinIsNotEmpty(voxelShape, Shapes.create(this.getBoundingBox()), BooleanOp.AND);
|
|
}
|
|
|
|
public int getTeamColor() {
|
|
Team team = this.getTeam();
|
|
return team != null && team.getColor().getColor() != null ? team.getColor().getColor() : 16777215;
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the player is in spectator mode.
|
|
*/
|
|
public boolean isSpectator() {
|
|
return false;
|
|
}
|
|
|
|
public final void unRide() {
|
|
if (this.isVehicle()) {
|
|
this.ejectPassengers();
|
|
}
|
|
|
|
if (this.isPassenger()) {
|
|
this.stopRiding();
|
|
}
|
|
}
|
|
|
|
public void syncPacketPositionCodec(double x, double y, double z) {
|
|
this.packetPositionCodec.setBase(new Vec3(x, y, z));
|
|
}
|
|
|
|
public VecDeltaCodec getPositionCodec() {
|
|
return this.packetPositionCodec;
|
|
}
|
|
|
|
public EntityType<?> getType() {
|
|
return this.type;
|
|
}
|
|
|
|
@Override
|
|
public int getId() {
|
|
return this.id;
|
|
}
|
|
|
|
public void setId(int id) {
|
|
this.id = id;
|
|
}
|
|
|
|
public Set<String> getTags() {
|
|
return this.tags;
|
|
}
|
|
|
|
public boolean addTag(String tag) {
|
|
return this.tags.size() >= 1024 ? false : this.tags.add(tag);
|
|
}
|
|
|
|
public boolean removeTag(String tag) {
|
|
return this.tags.remove(tag);
|
|
}
|
|
|
|
public void kill(ServerLevel level) {
|
|
this.remove(Entity.RemovalReason.KILLED);
|
|
this.gameEvent(GameEvent.ENTITY_DIE);
|
|
}
|
|
|
|
public final void discard() {
|
|
this.remove(Entity.RemovalReason.DISCARDED);
|
|
}
|
|
|
|
protected abstract void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder);
|
|
|
|
public SynchedEntityData getEntityData() {
|
|
return this.entityData;
|
|
}
|
|
|
|
public boolean equals(Object object) {
|
|
return object instanceof Entity ? ((Entity)object).id == this.id : false;
|
|
}
|
|
|
|
public int hashCode() {
|
|
return this.id;
|
|
}
|
|
|
|
public void remove(Entity.RemovalReason reason) {
|
|
this.setRemoved(reason);
|
|
}
|
|
|
|
public void onClientRemoval() {
|
|
}
|
|
|
|
public void onRemoval(Entity.RemovalReason reason) {
|
|
}
|
|
|
|
public void setPose(Pose pose) {
|
|
this.entityData.set(DATA_POSE, pose);
|
|
}
|
|
|
|
public Pose getPose() {
|
|
return this.entityData.get(DATA_POSE);
|
|
}
|
|
|
|
public boolean hasPose(Pose pose) {
|
|
return this.getPose() == pose;
|
|
}
|
|
|
|
public boolean closerThan(Entity entity, double distance) {
|
|
return this.position().closerThan(entity.position(), distance);
|
|
}
|
|
|
|
public boolean closerThan(Entity entity, double horizontalDistance, double verticalDistance) {
|
|
double d = entity.getX() - this.getX();
|
|
double e = entity.getY() - this.getY();
|
|
double f = entity.getZ() - this.getZ();
|
|
return Mth.lengthSquared(d, f) < Mth.square(horizontalDistance) && Mth.square(e) < Mth.square(verticalDistance);
|
|
}
|
|
|
|
/**
|
|
* Sets the rotation of the entity.
|
|
*/
|
|
protected void setRot(float yRot, float xRot) {
|
|
this.setYRot(yRot % 360.0F);
|
|
this.setXRot(xRot % 360.0F);
|
|
}
|
|
|
|
public final void setPos(Vec3 pos) {
|
|
this.setPos(pos.x(), pos.y(), pos.z());
|
|
}
|
|
|
|
/**
|
|
* Sets the x,y,z of the entity from the given parameters. Also seems to set up a bounding box.
|
|
*/
|
|
public void setPos(double x, double y, double z) {
|
|
this.setPosRaw(x, y, z);
|
|
this.setBoundingBox(this.makeBoundingBox());
|
|
}
|
|
|
|
protected final AABB makeBoundingBox() {
|
|
return this.makeBoundingBox(this.position);
|
|
}
|
|
|
|
protected AABB makeBoundingBox(Vec3 position) {
|
|
return this.dimensions.makeBoundingBox(position);
|
|
}
|
|
|
|
/**
|
|
* Recomputes this entity's bounding box so that it is positioned at this entity's X/Y/Z.
|
|
*/
|
|
protected void reapplyPosition() {
|
|
this.setPos(this.position.x, this.position.y, this.position.z);
|
|
}
|
|
|
|
public void turn(double yRot, double xRot) {
|
|
float f = (float)xRot * 0.15F;
|
|
float g = (float)yRot * 0.15F;
|
|
this.setXRot(this.getXRot() + f);
|
|
this.setYRot(this.getYRot() + g);
|
|
this.setXRot(Mth.clamp(this.getXRot(), -90.0F, 90.0F));
|
|
this.xRotO += f;
|
|
this.yRotO += g;
|
|
this.xRotO = Mth.clamp(this.xRotO, -90.0F, 90.0F);
|
|
if (this.vehicle != null) {
|
|
this.vehicle.onPassengerTurned(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called to update the entity's position/logic.
|
|
*/
|
|
public void tick() {
|
|
this.baseTick();
|
|
}
|
|
|
|
/**
|
|
* Gets called every tick from main Entity class
|
|
*/
|
|
public void baseTick() {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("entityBaseTick");
|
|
this.inBlockState = null;
|
|
if (this.isPassenger() && this.getVehicle().isRemoved()) {
|
|
this.stopRiding();
|
|
}
|
|
|
|
if (this.boardingCooldown > 0) {
|
|
this.boardingCooldown--;
|
|
}
|
|
|
|
this.handlePortal();
|
|
if (this.canSpawnSprintParticle()) {
|
|
this.spawnSprintParticle();
|
|
}
|
|
|
|
this.wasInPowderSnow = this.isInPowderSnow;
|
|
this.isInPowderSnow = false;
|
|
this.updateInWaterStateAndDoFluidPushing();
|
|
this.updateFluidOnEyes();
|
|
this.updateSwimming();
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
if (this.remainingFireTicks > 0) {
|
|
if (this.fireImmune()) {
|
|
this.setRemainingFireTicks(this.remainingFireTicks - 4);
|
|
if (this.remainingFireTicks < 0) {
|
|
this.clearFire();
|
|
}
|
|
} else {
|
|
if (this.remainingFireTicks % 20 == 0 && !this.isInLava()) {
|
|
this.hurtServer(serverLevel, this.damageSources().onFire(), 1.0F);
|
|
}
|
|
|
|
this.setRemainingFireTicks(this.remainingFireTicks - 1);
|
|
}
|
|
}
|
|
} else {
|
|
this.clearFire();
|
|
}
|
|
|
|
if (this.isInLava()) {
|
|
this.fallDistance *= 0.5;
|
|
}
|
|
|
|
this.checkBelowWorld();
|
|
if (!this.level().isClientSide) {
|
|
this.setSharedFlagOnFire(this.remainingFireTicks > 0);
|
|
}
|
|
|
|
this.firstTick = false;
|
|
if (this.level() instanceof ServerLevel serverLevelx && this instanceof Leashable) {
|
|
Leashable.tickLeash(serverLevelx, (Entity & Leashable)this);
|
|
}
|
|
|
|
profilerFiller.pop();
|
|
}
|
|
|
|
public void setSharedFlagOnFire(boolean isOnFire) {
|
|
this.setSharedFlag(0, isOnFire || this.hasVisualFire);
|
|
}
|
|
|
|
public void checkBelowWorld() {
|
|
if (this.getY() < this.level().getMinY() - 64) {
|
|
this.onBelowWorld();
|
|
}
|
|
}
|
|
|
|
public void setPortalCooldown() {
|
|
this.portalCooldown = this.getDimensionChangingDelay();
|
|
}
|
|
|
|
public void setPortalCooldown(int portalCooldown) {
|
|
this.portalCooldown = portalCooldown;
|
|
}
|
|
|
|
public int getPortalCooldown() {
|
|
return this.portalCooldown;
|
|
}
|
|
|
|
public boolean isOnPortalCooldown() {
|
|
return this.portalCooldown > 0;
|
|
}
|
|
|
|
/**
|
|
* Decrements the counter for the remaining time until the entity may use a portal again.
|
|
*/
|
|
protected void processPortalCooldown() {
|
|
if (this.isOnPortalCooldown()) {
|
|
this.portalCooldown--;
|
|
}
|
|
}
|
|
|
|
public void lavaIgnite() {
|
|
if (!this.fireImmune()) {
|
|
this.igniteForSeconds(15.0F);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called whenever the entity is walking inside of lava.
|
|
*/
|
|
public void lavaHurt() {
|
|
if (!this.fireImmune()) {
|
|
if (this.level() instanceof ServerLevel serverLevel
|
|
&& this.hurtServer(serverLevel, this.damageSources().lava(), 4.0F)
|
|
&& this.shouldPlayLavaHurtSound()
|
|
&& !this.isSilent()) {
|
|
serverLevel.playSound(
|
|
null, this.getX(), this.getY(), this.getZ(), SoundEvents.GENERIC_BURN, this.getSoundSource(), 0.4F, 2.0F + this.random.nextFloat() * 0.4F
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean shouldPlayLavaHurtSound() {
|
|
return true;
|
|
}
|
|
|
|
public final void igniteForSeconds(float seconds) {
|
|
this.igniteForTicks(Mth.floor(seconds * 20.0F));
|
|
}
|
|
|
|
public void igniteForTicks(int ticks) {
|
|
if (this.remainingFireTicks < ticks) {
|
|
this.setRemainingFireTicks(ticks);
|
|
}
|
|
|
|
this.clearFreeze();
|
|
}
|
|
|
|
public void setRemainingFireTicks(int remainingFireTicks) {
|
|
this.remainingFireTicks = remainingFireTicks;
|
|
}
|
|
|
|
public int getRemainingFireTicks() {
|
|
return this.remainingFireTicks;
|
|
}
|
|
|
|
/**
|
|
* Removes fire from entity.
|
|
*/
|
|
public void clearFire() {
|
|
this.setRemainingFireTicks(0);
|
|
}
|
|
|
|
protected void onBelowWorld() {
|
|
this.discard();
|
|
}
|
|
|
|
/**
|
|
* Checks if the offset position from the entity's current position has a collision with a block or a liquid.
|
|
*/
|
|
public boolean isFree(double x, double y, double z) {
|
|
return this.isFree(this.getBoundingBox().move(x, y, z));
|
|
}
|
|
|
|
/**
|
|
* Determines if the entity has no collision with a block or a liquid within the specified bounding box.
|
|
*/
|
|
private boolean isFree(AABB box) {
|
|
return this.level().noCollision(this, box) && !this.level().containsAnyLiquid(box);
|
|
}
|
|
|
|
public void setOnGround(boolean onGround) {
|
|
this.onGround = onGround;
|
|
this.checkSupportingBlock(onGround, null);
|
|
}
|
|
|
|
public void setOnGroundWithMovement(boolean onGround, Vec3 movement) {
|
|
this.setOnGroundWithMovement(onGround, this.horizontalCollision, movement);
|
|
}
|
|
|
|
public void setOnGroundWithMovement(boolean onGround, boolean horizontalCollision, Vec3 movement) {
|
|
this.onGround = onGround;
|
|
this.horizontalCollision = horizontalCollision;
|
|
this.checkSupportingBlock(onGround, movement);
|
|
}
|
|
|
|
public boolean isSupportedBy(BlockPos pos) {
|
|
return this.mainSupportingBlockPos.isPresent() && ((BlockPos)this.mainSupportingBlockPos.get()).equals(pos);
|
|
}
|
|
|
|
protected void checkSupportingBlock(boolean onGround, @Nullable Vec3 movement) {
|
|
if (onGround) {
|
|
AABB aABB = this.getBoundingBox();
|
|
AABB aABB2 = new AABB(aABB.minX, aABB.minY - 1.0E-6, aABB.minZ, aABB.maxX, aABB.minY, aABB.maxZ);
|
|
Optional<BlockPos> optional = this.level.findSupportingBlock(this, aABB2);
|
|
if (optional.isPresent() || this.onGroundNoBlocks) {
|
|
this.mainSupportingBlockPos = optional;
|
|
} else if (movement != null) {
|
|
AABB aABB3 = aABB2.move(-movement.x, 0.0, -movement.z);
|
|
optional = this.level.findSupportingBlock(this, aABB3);
|
|
this.mainSupportingBlockPos = optional;
|
|
}
|
|
|
|
this.onGroundNoBlocks = optional.isEmpty();
|
|
} else {
|
|
this.onGroundNoBlocks = false;
|
|
if (this.mainSupportingBlockPos.isPresent()) {
|
|
this.mainSupportingBlockPos = Optional.empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean onGround() {
|
|
return this.onGround;
|
|
}
|
|
|
|
public void move(MoverType type, Vec3 movement) {
|
|
if (this.noPhysics) {
|
|
this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
|
|
} else {
|
|
if (type == MoverType.PISTON) {
|
|
movement = this.limitPistonMovement(movement);
|
|
if (movement.equals(Vec3.ZERO)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("move");
|
|
if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7) {
|
|
movement = movement.multiply(this.stuckSpeedMultiplier);
|
|
this.stuckSpeedMultiplier = Vec3.ZERO;
|
|
this.setDeltaMovement(Vec3.ZERO);
|
|
}
|
|
|
|
movement = this.maybeBackOffFromEdge(movement, type);
|
|
Vec3 vec3 = this.collide(movement);
|
|
double d = vec3.lengthSqr();
|
|
if (d > 1.0E-7 || movement.lengthSqr() - d < 1.0E-7) {
|
|
if (this.fallDistance != 0.0 && d >= 1.0) {
|
|
BlockHitResult blockHitResult = this.level()
|
|
.clip(new ClipContext(this.position(), this.position().add(vec3), Block.FALLDAMAGE_RESETTING, net.minecraft.world.level.ClipContext.Fluid.WATER, this));
|
|
if (blockHitResult.getType() != Type.MISS) {
|
|
this.resetFallDistance();
|
|
}
|
|
}
|
|
|
|
Vec3 vec32 = this.position();
|
|
List<Entity.Movement> list = new ObjectArrayList<>();
|
|
|
|
for (Direction.Axis axis : axisStepOrder(vec3)) {
|
|
double e = vec3.get(axis);
|
|
if (e != 0.0) {
|
|
Vec3 vec33 = vec32.relative(axis.getPositive(), e);
|
|
list.add(new Entity.Movement(vec32, vec33));
|
|
vec32 = vec33;
|
|
}
|
|
}
|
|
|
|
this.movementThisTick.add(list);
|
|
this.setPos(vec32);
|
|
}
|
|
|
|
profilerFiller.pop();
|
|
profilerFiller.push("rest");
|
|
boolean bl = !Mth.equal(movement.x, vec3.x);
|
|
boolean bl2 = !Mth.equal(movement.z, vec3.z);
|
|
this.horizontalCollision = bl || bl2;
|
|
if (Math.abs(movement.y) > 0.0 || this.isLocalInstanceAuthoritative()) {
|
|
this.verticalCollision = movement.y != vec3.y;
|
|
this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0;
|
|
this.setOnGroundWithMovement(this.verticalCollisionBelow, this.horizontalCollision, vec3);
|
|
}
|
|
|
|
if (this.horizontalCollision) {
|
|
this.minorHorizontalCollision = this.isHorizontalCollisionMinor(vec3);
|
|
} else {
|
|
this.minorHorizontalCollision = false;
|
|
}
|
|
|
|
BlockPos blockPos = this.getOnPosLegacy();
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
if (this.isLocalInstanceAuthoritative()) {
|
|
this.checkFallDamage(vec3.y, this.onGround(), blockState, blockPos);
|
|
}
|
|
|
|
if (this.isRemoved()) {
|
|
profilerFiller.pop();
|
|
} else {
|
|
if (this.horizontalCollision) {
|
|
Vec3 vec34 = this.getDeltaMovement();
|
|
this.setDeltaMovement(bl ? 0.0 : vec34.x, vec34.y, bl2 ? 0.0 : vec34.z);
|
|
}
|
|
|
|
if (this.canSimulateMovement()) {
|
|
net.minecraft.world.level.block.Block block = blockState.getBlock();
|
|
if (movement.y != vec3.y) {
|
|
block.updateEntityMovementAfterFallOn(this.level(), this);
|
|
}
|
|
}
|
|
|
|
if (!this.level().isClientSide() || this.isLocalInstanceAuthoritative()) {
|
|
Entity.MovementEmission movementEmission = this.getMovementEmission();
|
|
if (movementEmission.emitsAnything() && !this.isPassenger()) {
|
|
this.applyMovementEmissionAndPlaySound(movementEmission, vec3, blockPos, blockState);
|
|
}
|
|
}
|
|
|
|
float f = this.getBlockSpeedFactor();
|
|
this.setDeltaMovement(this.getDeltaMovement().multiply(f, 1.0, f));
|
|
profilerFiller.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) {
|
|
float f = 0.6F;
|
|
float g = (float)(movement.length() * 0.6F);
|
|
float h = (float)(movement.horizontalDistance() * 0.6F);
|
|
BlockPos blockPos = this.getOnPos();
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
boolean bl = this.isStateClimbable(blockState);
|
|
this.moveDist += bl ? g : h;
|
|
this.flyDist += g;
|
|
if (this.moveDist > this.nextStep && !blockState.isAir()) {
|
|
boolean bl2 = blockPos.equals(pos);
|
|
boolean bl3 = this.vibrationAndSoundEffectsFromBlock(pos, state, movementEmission.emitsSounds(), bl2, movement);
|
|
if (!bl2) {
|
|
bl3 |= this.vibrationAndSoundEffectsFromBlock(blockPos, blockState, false, movementEmission.emitsEvents(), movement);
|
|
}
|
|
|
|
if (bl3) {
|
|
this.nextStep = this.nextStep();
|
|
} else if (this.isInWater()) {
|
|
this.nextStep = this.nextStep();
|
|
if (movementEmission.emitsSounds()) {
|
|
this.waterSwimSound();
|
|
}
|
|
|
|
if (movementEmission.emitsEvents()) {
|
|
this.gameEvent(GameEvent.SWIM);
|
|
}
|
|
}
|
|
} else if (blockState.isAir()) {
|
|
this.processFlappingMovement();
|
|
}
|
|
}
|
|
|
|
protected void applyEffectsFromBlocks() {
|
|
this.finalMovementsThisTick.clear();
|
|
this.movementThisTick.forEach(this.finalMovementsThisTick::addAll);
|
|
this.movementThisTick.clear();
|
|
if (this.finalMovementsThisTick.isEmpty()) {
|
|
this.finalMovementsThisTick.add(new Entity.Movement(this.oldPosition(), this.position()));
|
|
} else if (((Entity.Movement)this.finalMovementsThisTick.getLast()).to.distanceToSqr(this.position()) > 9.9999994E-11F) {
|
|
this.finalMovementsThisTick.add(new Entity.Movement(((Entity.Movement)this.finalMovementsThisTick.getLast()).to, this.position()));
|
|
}
|
|
|
|
this.applyEffectsFromBlocks(this.finalMovementsThisTick);
|
|
}
|
|
|
|
public void removeLatestMovementRecordingBatch() {
|
|
if (!this.movementThisTick.isEmpty()) {
|
|
this.movementThisTick.removeLast();
|
|
}
|
|
}
|
|
|
|
public void applyEffectsFromBlocks(Vec3 oldPosition, Vec3 position) {
|
|
this.applyEffectsFromBlocks(List.of(new Entity.Movement(oldPosition, position)));
|
|
}
|
|
|
|
private void applyEffectsFromBlocks(List<Entity.Movement> movements) {
|
|
if (this.isAffectedByBlocks()) {
|
|
if (this.onGround()) {
|
|
BlockPos blockPos = this.getOnPosLegacy();
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
blockState.getBlock().stepOn(this.level(), blockPos, blockState, this);
|
|
}
|
|
|
|
boolean bl = this.isOnFire();
|
|
boolean bl2 = this.isFreezing();
|
|
this.checkInsideBlocks(movements, this.insideEffectCollector);
|
|
this.insideEffectCollector.applyAndClear(this);
|
|
if (this.isInRain()) {
|
|
this.clearFire();
|
|
}
|
|
|
|
if (bl && !this.isOnFire() || bl2 && !this.isFreezing()) {
|
|
this.playEntityOnFireExtinguishedSound();
|
|
}
|
|
|
|
if (bl && !this.isOnFire() && this.remainingFireTicks <= 0) {
|
|
this.setRemainingFireTicks(-this.getFireImmuneTicks());
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean isAffectedByBlocks() {
|
|
return !this.isRemoved() && !this.noPhysics;
|
|
}
|
|
|
|
private boolean isStateClimbable(BlockState state) {
|
|
return state.is(BlockTags.CLIMBABLE) || state.is(Blocks.POWDER_SNOW);
|
|
}
|
|
|
|
private boolean vibrationAndSoundEffectsFromBlock(BlockPos pos, BlockState state, boolean playStepSound, boolean broadcastGameEvent, Vec3 entityPos) {
|
|
if (state.isAir()) {
|
|
return false;
|
|
} else {
|
|
boolean bl = this.isStateClimbable(state);
|
|
if ((this.onGround() || bl || this.isCrouching() && entityPos.y == 0.0 || this.isOnRails()) && !this.isSwimming()) {
|
|
if (playStepSound) {
|
|
this.walkingStepSound(pos, state);
|
|
}
|
|
|
|
if (broadcastGameEvent) {
|
|
this.level().gameEvent(GameEvent.STEP, this.position(), Context.of(this, state));
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean isHorizontalCollisionMinor(Vec3 deltaMovement) {
|
|
return false;
|
|
}
|
|
|
|
protected void playEntityOnFireExtinguishedSound() {
|
|
if (!this.level.isClientSide()) {
|
|
this.level()
|
|
.playSound(
|
|
null,
|
|
this.getX(),
|
|
this.getY(),
|
|
this.getZ(),
|
|
SoundEvents.GENERIC_EXTINGUISH_FIRE,
|
|
this.getSoundSource(),
|
|
0.7F,
|
|
1.6F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F
|
|
);
|
|
}
|
|
}
|
|
|
|
public void extinguishFire() {
|
|
if (this.isOnFire()) {
|
|
this.playEntityOnFireExtinguishedSound();
|
|
}
|
|
|
|
this.clearFire();
|
|
}
|
|
|
|
protected void processFlappingMovement() {
|
|
if (this.isFlapping()) {
|
|
this.onFlap();
|
|
if (this.getMovementEmission().emitsEvents()) {
|
|
this.gameEvent(GameEvent.FLAP);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public BlockPos getOnPosLegacy() {
|
|
return this.getOnPos(0.2F);
|
|
}
|
|
|
|
public BlockPos getBlockPosBelowThatAffectsMyMovement() {
|
|
return this.getOnPos(0.500001F);
|
|
}
|
|
|
|
public BlockPos getOnPos() {
|
|
return this.getOnPos(1.0E-5F);
|
|
}
|
|
|
|
protected BlockPos getOnPos(float yOffset) {
|
|
if (this.mainSupportingBlockPos.isPresent()) {
|
|
BlockPos blockPos = (BlockPos)this.mainSupportingBlockPos.get();
|
|
if (!(yOffset > 1.0E-5F)) {
|
|
return blockPos;
|
|
} else {
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
return (!(yOffset <= 0.5) || !blockState.is(BlockTags.FENCES)) && !blockState.is(BlockTags.WALLS) && !(blockState.getBlock() instanceof FenceGateBlock)
|
|
? blockPos.atY(Mth.floor(this.position.y - yOffset))
|
|
: blockPos;
|
|
}
|
|
} else {
|
|
int i = Mth.floor(this.position.x);
|
|
int j = Mth.floor(this.position.y - yOffset);
|
|
int k = Mth.floor(this.position.z);
|
|
return new BlockPos(i, j, k);
|
|
}
|
|
}
|
|
|
|
protected float getBlockJumpFactor() {
|
|
float f = this.level().getBlockState(this.blockPosition()).getBlock().getJumpFactor();
|
|
float g = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getJumpFactor();
|
|
return f == 1.0 ? g : f;
|
|
}
|
|
|
|
protected float getBlockSpeedFactor() {
|
|
BlockState blockState = this.level().getBlockState(this.blockPosition());
|
|
float f = blockState.getBlock().getSpeedFactor();
|
|
if (!blockState.is(Blocks.WATER) && !blockState.is(Blocks.BUBBLE_COLUMN)) {
|
|
return f == 1.0 ? this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getSpeedFactor() : f;
|
|
} else {
|
|
return f;
|
|
}
|
|
}
|
|
|
|
protected Vec3 maybeBackOffFromEdge(Vec3 vec, MoverType mover) {
|
|
return vec;
|
|
}
|
|
|
|
protected Vec3 limitPistonMovement(Vec3 pos) {
|
|
if (pos.lengthSqr() <= 1.0E-7) {
|
|
return pos;
|
|
} else {
|
|
long l = this.level().getGameTime();
|
|
if (l != this.pistonDeltasGameTime) {
|
|
Arrays.fill(this.pistonDeltas, 0.0);
|
|
this.pistonDeltasGameTime = l;
|
|
}
|
|
|
|
if (pos.x != 0.0) {
|
|
double d = this.applyPistonMovementRestriction(Direction.Axis.X, pos.x);
|
|
return Math.abs(d) <= 1.0E-5F ? Vec3.ZERO : new Vec3(d, 0.0, 0.0);
|
|
} else if (pos.y != 0.0) {
|
|
double d = this.applyPistonMovementRestriction(Direction.Axis.Y, pos.y);
|
|
return Math.abs(d) <= 1.0E-5F ? Vec3.ZERO : new Vec3(0.0, d, 0.0);
|
|
} else if (pos.z != 0.0) {
|
|
double d = this.applyPistonMovementRestriction(Direction.Axis.Z, pos.z);
|
|
return Math.abs(d) <= 1.0E-5F ? Vec3.ZERO : new Vec3(0.0, 0.0, d);
|
|
} else {
|
|
return Vec3.ZERO;
|
|
}
|
|
}
|
|
}
|
|
|
|
private double applyPistonMovementRestriction(Direction.Axis axis, double distance) {
|
|
int i = axis.ordinal();
|
|
double d = Mth.clamp(distance + this.pistonDeltas[i], -0.51, 0.51);
|
|
distance = d - this.pistonDeltas[i];
|
|
this.pistonDeltas[i] = d;
|
|
return distance;
|
|
}
|
|
|
|
/**
|
|
* Given a motion vector, return an updated vector that takes into account restrictions such as collisions (from all directions) and step-up from stepHeight
|
|
*/
|
|
private Vec3 collide(Vec3 vec) {
|
|
AABB aABB = this.getBoundingBox();
|
|
List<VoxelShape> list = this.level().getEntityCollisions(this, aABB.expandTowards(vec));
|
|
Vec3 vec3 = vec.lengthSqr() == 0.0 ? vec : collideBoundingBox(this, vec, aABB, this.level(), list);
|
|
boolean bl = vec.x != vec3.x;
|
|
boolean bl2 = vec.y != vec3.y;
|
|
boolean bl3 = vec.z != vec3.z;
|
|
boolean bl4 = bl2 && vec.y < 0.0;
|
|
if (this.maxUpStep() > 0.0F && (bl4 || this.onGround()) && (bl || bl3)) {
|
|
AABB aABB2 = bl4 ? aABB.move(0.0, vec3.y, 0.0) : aABB;
|
|
AABB aABB3 = aABB2.expandTowards(vec.x, this.maxUpStep(), vec.z);
|
|
if (!bl4) {
|
|
aABB3 = aABB3.expandTowards(0.0, -1.0E-5F, 0.0);
|
|
}
|
|
|
|
List<VoxelShape> list2 = collectColliders(this, this.level, list, aABB3);
|
|
float f = (float)vec3.y;
|
|
float[] fs = collectCandidateStepUpHeights(aABB2, list2, this.maxUpStep(), f);
|
|
|
|
for (float g : fs) {
|
|
Vec3 vec32 = collideWithShapes(new Vec3(vec.x, g, vec.z), aABB2, list2);
|
|
if (vec32.horizontalDistanceSqr() > vec3.horizontalDistanceSqr()) {
|
|
double d = aABB.minY - aABB2.minY;
|
|
return vec32.subtract(0.0, d, 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return vec3;
|
|
}
|
|
|
|
private static float[] collectCandidateStepUpHeights(AABB box, List<VoxelShape> colliders, float deltaY, float maxUpStep) {
|
|
FloatSet floatSet = new FloatArraySet(4);
|
|
|
|
for (VoxelShape voxelShape : colliders) {
|
|
for (double d : voxelShape.getCoords(Direction.Axis.Y)) {
|
|
float f = (float)(d - box.minY);
|
|
if (!(f < 0.0F) && f != maxUpStep) {
|
|
if (f > deltaY) {
|
|
break;
|
|
}
|
|
|
|
floatSet.add(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
float[] fs = floatSet.toFloatArray();
|
|
FloatArrays.unstableSort(fs);
|
|
return fs;
|
|
}
|
|
|
|
public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 vec, AABB collisionBox, Level level, List<VoxelShape> potentialHits) {
|
|
List<VoxelShape> list = collectColliders(entity, level, potentialHits, collisionBox.expandTowards(vec));
|
|
return collideWithShapes(vec, collisionBox, list);
|
|
}
|
|
|
|
private static List<VoxelShape> collectColliders(@Nullable Entity entity, Level level, List<VoxelShape> collisions, AABB boundingBox) {
|
|
Builder<VoxelShape> builder = ImmutableList.builderWithExpectedSize(collisions.size() + 1);
|
|
if (!collisions.isEmpty()) {
|
|
builder.addAll(collisions);
|
|
}
|
|
|
|
WorldBorder worldBorder = level.getWorldBorder();
|
|
boolean bl = entity != null && worldBorder.isInsideCloseToBorder(entity, boundingBox);
|
|
if (bl) {
|
|
builder.add(worldBorder.getCollisionShape());
|
|
}
|
|
|
|
builder.addAll(level.getBlockCollisions(entity, boundingBox));
|
|
return builder.build();
|
|
}
|
|
|
|
private static Vec3 collideWithShapes(Vec3 deltaMovement, AABB entityBB, List<VoxelShape> shapes) {
|
|
if (shapes.isEmpty()) {
|
|
return deltaMovement;
|
|
} else {
|
|
Vec3 vec3 = Vec3.ZERO;
|
|
|
|
for (Direction.Axis axis : axisStepOrder(deltaMovement)) {
|
|
double d = deltaMovement.get(axis);
|
|
if (d != 0.0) {
|
|
double e = Shapes.collide(axis, entityBB.move(vec3), shapes, d);
|
|
vec3 = vec3.with(axis, e);
|
|
}
|
|
}
|
|
|
|
return vec3;
|
|
}
|
|
}
|
|
|
|
private static Iterable<Direction.Axis> axisStepOrder(Vec3 deltaMovement) {
|
|
return Math.abs(deltaMovement.x) < Math.abs(deltaMovement.z) ? YZX_AXIS_ORDER : YXZ_AXIS_ORDER;
|
|
}
|
|
|
|
protected float nextStep() {
|
|
return (int)this.moveDist + 1;
|
|
}
|
|
|
|
protected SoundEvent getSwimSound() {
|
|
return SoundEvents.GENERIC_SWIM;
|
|
}
|
|
|
|
protected SoundEvent getSwimSplashSound() {
|
|
return SoundEvents.GENERIC_SPLASH;
|
|
}
|
|
|
|
protected SoundEvent getSwimHighSpeedSplashSound() {
|
|
return SoundEvents.GENERIC_SPLASH;
|
|
}
|
|
|
|
private void checkInsideBlocks(List<Entity.Movement> movements, InsideBlockEffectApplier.StepBasedCollector stepBasedCollector) {
|
|
if (this.isAffectedByBlocks()) {
|
|
LongSet longSet = this.visitedBlocks;
|
|
|
|
for (Entity.Movement movement : movements) {
|
|
Vec3 vec3 = movement.from();
|
|
Vec3 vec32 = movement.to();
|
|
AABB aABB = this.makeBoundingBox(vec32).deflate(1.0E-5F);
|
|
BlockGetter.forEachBlockIntersectedBetween(vec3, vec32, aABB, (blockPos, i) -> {
|
|
if (this.isAlive()) {
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
if (!blockState.isAir()) {
|
|
if (longSet.add(blockPos.asLong())) {
|
|
VoxelShape voxelShape = blockState.getEntityInsideCollisionShape(this.level(), blockPos, this);
|
|
boolean bl = voxelShape == Shapes.block() || this.collidedWithShapeMovingFrom(vec3, vec32, voxelShape.move(new Vec3(blockPos)).toAabbs());
|
|
if (bl) {
|
|
try {
|
|
stepBasedCollector.advanceStep(i);
|
|
blockState.entityInside(this.level(), blockPos, this, stepBasedCollector);
|
|
this.onInsideBlock(blockState);
|
|
} catch (Throwable var14) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var14, "Colliding entity with block");
|
|
CrashReportCategory crashReportCategory = crashReport.addCategory("Block being collided with");
|
|
CrashReportCategory.populateBlockDetails(crashReportCategory, this.level(), blockPos, blockState);
|
|
CrashReportCategory crashReportCategory2 = crashReport.addCategory("Entity being checked for collision");
|
|
this.fillCrashReportCategory(crashReportCategory2);
|
|
throw new ReportedException(crashReport);
|
|
}
|
|
}
|
|
|
|
boolean bl2 = this.collidedWithFluid(blockState.getFluidState(), blockPos, vec3, vec32);
|
|
if (bl2) {
|
|
stepBasedCollector.advanceStep(i);
|
|
blockState.getFluidState().entityInside(this.level(), blockPos, this, stepBasedCollector);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
longSet.clear();
|
|
}
|
|
}
|
|
|
|
private boolean collidedWithFluid(FluidState fluid, BlockPos pos, Vec3 from, Vec3 to) {
|
|
AABB aABB = fluid.getAABB(this.level(), pos);
|
|
return aABB != null && this.collidedWithShapeMovingFrom(from, to, List.of(aABB));
|
|
}
|
|
|
|
private boolean collidedWithShapeMovingFrom(Vec3 from, Vec3 to, List<AABB> boxes) {
|
|
AABB aABB = this.makeBoundingBox(from);
|
|
Vec3 vec3 = to.subtract(from);
|
|
return aABB.collidedAlongVector(vec3, boxes);
|
|
}
|
|
|
|
protected void onInsideBlock(BlockState state) {
|
|
}
|
|
|
|
public BlockPos adjustSpawnLocation(ServerLevel level, BlockPos pos) {
|
|
BlockPos blockPos = level.getSharedSpawnPos();
|
|
Vec3 vec3 = blockPos.getCenter();
|
|
int i = level.getChunkAt(blockPos).getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos.getX(), blockPos.getZ()) + 1;
|
|
return BlockPos.containing(vec3.x, i, vec3.z);
|
|
}
|
|
|
|
public void gameEvent(Holder<GameEvent> gameEvent, @Nullable Entity entity) {
|
|
this.level().gameEvent(entity, gameEvent, this.position);
|
|
}
|
|
|
|
public void gameEvent(Holder<GameEvent> gameEvent) {
|
|
this.gameEvent(gameEvent, this);
|
|
}
|
|
|
|
private void walkingStepSound(BlockPos pos, BlockState state) {
|
|
this.playStepSound(pos, state);
|
|
if (this.shouldPlayAmethystStepSound(state)) {
|
|
this.playAmethystStepSound();
|
|
}
|
|
}
|
|
|
|
protected void waterSwimSound() {
|
|
Entity entity = (Entity)Objects.requireNonNullElse(this.getControllingPassenger(), this);
|
|
float f = entity == this ? 0.35F : 0.4F;
|
|
Vec3 vec3 = entity.getDeltaMovement();
|
|
float g = Math.min(1.0F, (float)Math.sqrt(vec3.x * vec3.x * 0.2F + vec3.y * vec3.y + vec3.z * vec3.z * 0.2F) * f);
|
|
this.playSwimSound(g);
|
|
}
|
|
|
|
protected BlockPos getPrimaryStepSoundBlockPos(BlockPos pos) {
|
|
BlockPos blockPos = pos.above();
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
return !blockState.is(BlockTags.INSIDE_STEP_SOUND_BLOCKS) && !blockState.is(BlockTags.COMBINATION_STEP_SOUND_BLOCKS) ? pos : blockPos;
|
|
}
|
|
|
|
protected void playCombinationStepSounds(BlockState primaryState, BlockState secondaryState) {
|
|
SoundType soundType = primaryState.getSoundType();
|
|
this.playSound(soundType.getStepSound(), soundType.getVolume() * 0.15F, soundType.getPitch());
|
|
this.playMuffledStepSound(secondaryState);
|
|
}
|
|
|
|
protected void playMuffledStepSound(BlockState state) {
|
|
SoundType soundType = state.getSoundType();
|
|
this.playSound(soundType.getStepSound(), soundType.getVolume() * 0.05F, soundType.getPitch() * 0.8F);
|
|
}
|
|
|
|
protected void playStepSound(BlockPos pos, BlockState state) {
|
|
SoundType soundType = state.getSoundType();
|
|
this.playSound(soundType.getStepSound(), soundType.getVolume() * 0.15F, soundType.getPitch());
|
|
}
|
|
|
|
private boolean shouldPlayAmethystStepSound(BlockState state) {
|
|
return state.is(BlockTags.CRYSTAL_SOUND_BLOCKS) && this.tickCount >= this.lastCrystalSoundPlayTick + 20;
|
|
}
|
|
|
|
private void playAmethystStepSound() {
|
|
this.crystalSoundIntensity = this.crystalSoundIntensity * (float)Math.pow(0.997, this.tickCount - this.lastCrystalSoundPlayTick);
|
|
this.crystalSoundIntensity = Math.min(1.0F, this.crystalSoundIntensity + 0.07F);
|
|
float f = 0.5F + this.crystalSoundIntensity * this.random.nextFloat() * 1.2F;
|
|
float g = 0.1F + this.crystalSoundIntensity * 1.2F;
|
|
this.playSound(SoundEvents.AMETHYST_BLOCK_CHIME, g, f);
|
|
this.lastCrystalSoundPlayTick = this.tickCount;
|
|
}
|
|
|
|
protected void playSwimSound(float volume) {
|
|
this.playSound(this.getSwimSound(), volume, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
|
|
}
|
|
|
|
protected void onFlap() {
|
|
}
|
|
|
|
protected boolean isFlapping() {
|
|
return false;
|
|
}
|
|
|
|
public void playSound(SoundEvent sound, float volume, float pitch) {
|
|
if (!this.isSilent()) {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), sound, this.getSoundSource(), volume, pitch);
|
|
}
|
|
}
|
|
|
|
public void playSound(SoundEvent sound) {
|
|
if (!this.isSilent()) {
|
|
this.playSound(sound, 1.0F, 1.0F);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return True if this entity will not play sounds
|
|
*/
|
|
public boolean isSilent() {
|
|
return this.entityData.get(DATA_SILENT);
|
|
}
|
|
|
|
/**
|
|
* When set to true the entity will not play sounds.
|
|
*/
|
|
public void setSilent(boolean isSilent) {
|
|
this.entityData.set(DATA_SILENT, isSilent);
|
|
}
|
|
|
|
public boolean isNoGravity() {
|
|
return this.entityData.get(DATA_NO_GRAVITY);
|
|
}
|
|
|
|
public void setNoGravity(boolean noGravity) {
|
|
this.entityData.set(DATA_NO_GRAVITY, noGravity);
|
|
}
|
|
|
|
protected double getDefaultGravity() {
|
|
return 0.0;
|
|
}
|
|
|
|
public final double getGravity() {
|
|
return this.isNoGravity() ? 0.0 : this.getDefaultGravity();
|
|
}
|
|
|
|
protected void applyGravity() {
|
|
double d = this.getGravity();
|
|
if (d != 0.0) {
|
|
this.setDeltaMovement(this.getDeltaMovement().add(0.0, -d, 0.0));
|
|
}
|
|
}
|
|
|
|
protected Entity.MovementEmission getMovementEmission() {
|
|
return Entity.MovementEmission.ALL;
|
|
}
|
|
|
|
public boolean dampensVibrations() {
|
|
return false;
|
|
}
|
|
|
|
public final void doCheckFallDamage(double x, double y, double z, boolean onGround) {
|
|
if (!this.touchingUnloadedChunk()) {
|
|
this.checkSupportingBlock(onGround, new Vec3(x, y, z));
|
|
BlockPos blockPos = this.getOnPosLegacy();
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
this.checkFallDamage(y, onGround, blockState, blockPos);
|
|
}
|
|
}
|
|
|
|
protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
|
|
if (!this.isInWater() && y < 0.0) {
|
|
this.fallDistance -= (float)y;
|
|
}
|
|
|
|
if (onGround) {
|
|
if (this.fallDistance > 0.0) {
|
|
state.getBlock().fallOn(this.level(), state, pos, this, this.fallDistance);
|
|
this.level()
|
|
.gameEvent(
|
|
GameEvent.HIT_GROUND,
|
|
this.position,
|
|
Context.of(this, (BlockState)this.mainSupportingBlockPos.map(blockPos -> this.level().getBlockState(blockPos)).orElse(state))
|
|
);
|
|
}
|
|
|
|
this.resetFallDistance();
|
|
}
|
|
}
|
|
|
|
public boolean fireImmune() {
|
|
return this.getType().fireImmune();
|
|
}
|
|
|
|
public boolean causeFallDamage(double fallDistance, float damageMultiplier, DamageSource damageSource) {
|
|
if (this.type.is(EntityTypeTags.FALL_DAMAGE_IMMUNE)) {
|
|
return false;
|
|
} else {
|
|
this.propagateFallToPassengers(fallDistance, damageMultiplier, damageSource);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected void propagateFallToPassengers(double fallDistance, float damageMultiplier, DamageSource damageSource) {
|
|
if (this.isVehicle()) {
|
|
for (Entity entity : this.getPassengers()) {
|
|
entity.causeFallDamage(fallDistance, damageMultiplier, damageSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if this entity is inside water (if inWater field is true as a result of handleWaterMovement() returning true)
|
|
*/
|
|
public boolean isInWater() {
|
|
return this.wasTouchingWater;
|
|
}
|
|
|
|
boolean isInRain() {
|
|
BlockPos blockPos = this.blockPosition();
|
|
return this.level().isRainingAt(blockPos) || this.level().isRainingAt(BlockPos.containing(blockPos.getX(), this.getBoundingBox().maxY, blockPos.getZ()));
|
|
}
|
|
|
|
/**
|
|
* Checks if this entity is either in water or on an open air block in rain (used in wolves).
|
|
*/
|
|
public boolean isInWaterOrRain() {
|
|
return this.isInWater() || this.isInRain();
|
|
}
|
|
|
|
public boolean isInLiquid() {
|
|
return this.isInWater() || this.isInLava();
|
|
}
|
|
|
|
public boolean isUnderWater() {
|
|
return this.wasEyeInWater && this.isInWater();
|
|
}
|
|
|
|
public void updateSwimming() {
|
|
if (this.isSwimming()) {
|
|
this.setSwimming(this.isSprinting() && this.isInWater() && !this.isPassenger());
|
|
} else {
|
|
this.setSwimming(this.isSprinting() && this.isUnderWater() && !this.isPassenger() && this.level().getFluidState(this.blockPosition).is(FluidTags.WATER));
|
|
}
|
|
}
|
|
|
|
protected boolean updateInWaterStateAndDoFluidPushing() {
|
|
this.fluidHeight.clear();
|
|
this.updateInWaterStateAndDoWaterCurrentPushing();
|
|
double d = this.level().dimensionType().ultraWarm() ? 0.007 : 0.0023333333333333335;
|
|
boolean bl = this.updateFluidHeightAndDoFluidPushing(FluidTags.LAVA, d);
|
|
return this.isInWater() || bl;
|
|
}
|
|
|
|
void updateInWaterStateAndDoWaterCurrentPushing() {
|
|
if (this.getVehicle() instanceof AbstractBoat abstractBoat && !abstractBoat.isUnderWater()) {
|
|
this.wasTouchingWater = false;
|
|
} else if (this.updateFluidHeightAndDoFluidPushing(FluidTags.WATER, 0.014)) {
|
|
if (!this.wasTouchingWater && !this.firstTick) {
|
|
this.doWaterSplashEffect();
|
|
}
|
|
|
|
this.resetFallDistance();
|
|
this.wasTouchingWater = true;
|
|
} else {
|
|
this.wasTouchingWater = false;
|
|
}
|
|
}
|
|
|
|
private void updateFluidOnEyes() {
|
|
this.wasEyeInWater = this.isEyeInFluid(FluidTags.WATER);
|
|
this.fluidOnEyes.clear();
|
|
double d = this.getEyeY();
|
|
if (!(
|
|
this.getVehicle() instanceof AbstractBoat abstractBoat
|
|
&& !abstractBoat.isUnderWater()
|
|
&& abstractBoat.getBoundingBox().maxY >= d
|
|
&& abstractBoat.getBoundingBox().minY <= d
|
|
)) {
|
|
BlockPos blockPos = BlockPos.containing(this.getX(), d, this.getZ());
|
|
FluidState fluidState = this.level().getFluidState(blockPos);
|
|
double e = blockPos.getY() + fluidState.getHeight(this.level(), blockPos);
|
|
if (e > d) {
|
|
fluidState.getTags().forEach(this.fluidOnEyes::add);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plays the {@link #getSplashSound() splash sound}, and the {@link ParticleType#WATER_BUBBLE} and {@link ParticleType#WATER_SPLASH} particles.
|
|
*/
|
|
protected void doWaterSplashEffect() {
|
|
Entity entity = (Entity)Objects.requireNonNullElse(this.getControllingPassenger(), this);
|
|
float f = entity == this ? 0.2F : 0.9F;
|
|
Vec3 vec3 = entity.getDeltaMovement();
|
|
float g = Math.min(1.0F, (float)Math.sqrt(vec3.x * vec3.x * 0.2F + vec3.y * vec3.y + vec3.z * vec3.z * 0.2F) * f);
|
|
if (g < 0.25F) {
|
|
this.playSound(this.getSwimSplashSound(), g, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
|
|
} else {
|
|
this.playSound(this.getSwimHighSpeedSplashSound(), g, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
|
|
}
|
|
|
|
float h = Mth.floor(this.getY());
|
|
|
|
for (int i = 0; i < 1.0F + this.dimensions.width() * 20.0F; i++) {
|
|
double d = (this.random.nextDouble() * 2.0 - 1.0) * this.dimensions.width();
|
|
double e = (this.random.nextDouble() * 2.0 - 1.0) * this.dimensions.width();
|
|
this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d, h + 1.0F, this.getZ() + e, vec3.x, vec3.y - this.random.nextDouble() * 0.2F, vec3.z);
|
|
}
|
|
|
|
for (int i = 0; i < 1.0F + this.dimensions.width() * 20.0F; i++) {
|
|
double d = (this.random.nextDouble() * 2.0 - 1.0) * this.dimensions.width();
|
|
double e = (this.random.nextDouble() * 2.0 - 1.0) * this.dimensions.width();
|
|
this.level().addParticle(ParticleTypes.SPLASH, this.getX() + d, h + 1.0F, this.getZ() + e, vec3.x, vec3.y, vec3.z);
|
|
}
|
|
|
|
this.gameEvent(GameEvent.SPLASH);
|
|
}
|
|
|
|
@Deprecated
|
|
protected BlockState getBlockStateOnLegacy() {
|
|
return this.level().getBlockState(this.getOnPosLegacy());
|
|
}
|
|
|
|
public BlockState getBlockStateOn() {
|
|
return this.level().getBlockState(this.getOnPos());
|
|
}
|
|
|
|
public boolean canSpawnSprintParticle() {
|
|
return this.isSprinting() && !this.isInWater() && !this.isSpectator() && !this.isCrouching() && !this.isInLava() && this.isAlive();
|
|
}
|
|
|
|
protected void spawnSprintParticle() {
|
|
BlockPos blockPos = this.getOnPosLegacy();
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
if (blockState.getRenderShape() != RenderShape.INVISIBLE) {
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
BlockPos blockPos2 = this.blockPosition();
|
|
double d = this.getX() + (this.random.nextDouble() - 0.5) * this.dimensions.width();
|
|
double e = this.getZ() + (this.random.nextDouble() - 0.5) * this.dimensions.width();
|
|
if (blockPos2.getX() != blockPos.getX()) {
|
|
d = Mth.clamp(d, (double)blockPos.getX(), blockPos.getX() + 1.0);
|
|
}
|
|
|
|
if (blockPos2.getZ() != blockPos.getZ()) {
|
|
e = Mth.clamp(e, (double)blockPos.getZ(), blockPos.getZ() + 1.0);
|
|
}
|
|
|
|
this.level().addParticle(new BlockParticleOption(ParticleTypes.BLOCK, blockState), d, this.getY() + 0.1, e, vec3.x * -4.0, 1.5, vec3.z * -4.0);
|
|
}
|
|
}
|
|
|
|
public boolean isEyeInFluid(TagKey<Fluid> fluidTag) {
|
|
return this.fluidOnEyes.contains(fluidTag);
|
|
}
|
|
|
|
public boolean isInLava() {
|
|
return !this.firstTick && this.fluidHeight.getDouble(FluidTags.LAVA) > 0.0;
|
|
}
|
|
|
|
public void moveRelative(float amount, Vec3 relative) {
|
|
Vec3 vec3 = getInputVector(relative, amount, this.getYRot());
|
|
this.setDeltaMovement(this.getDeltaMovement().add(vec3));
|
|
}
|
|
|
|
protected static Vec3 getInputVector(Vec3 relative, float motionScaler, float facing) {
|
|
double d = relative.lengthSqr();
|
|
if (d < 1.0E-7) {
|
|
return Vec3.ZERO;
|
|
} else {
|
|
Vec3 vec3 = (d > 1.0 ? relative.normalize() : relative).scale(motionScaler);
|
|
float f = Mth.sin(facing * (float) (Math.PI / 180.0));
|
|
float g = Mth.cos(facing * (float) (Math.PI / 180.0));
|
|
return new Vec3(vec3.x * g - vec3.z * f, vec3.y, vec3.z * g + vec3.x * f);
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public float getLightLevelDependentMagicValue() {
|
|
return this.level().hasChunkAt(this.getBlockX(), this.getBlockZ())
|
|
? this.level().getLightLevelDependentMagicValue(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()))
|
|
: 0.0F;
|
|
}
|
|
|
|
public void absSnapTo(double x, double y, double z, float yRot, float xRot) {
|
|
this.absSnapTo(x, y, z);
|
|
this.absSnapRotationTo(yRot, xRot);
|
|
}
|
|
|
|
public void absSnapRotationTo(float yRot, float xRot) {
|
|
this.setYRot(yRot % 360.0F);
|
|
this.setXRot(Mth.clamp(xRot, -90.0F, 90.0F) % 360.0F);
|
|
this.yRotO = this.getYRot();
|
|
this.xRotO = this.getXRot();
|
|
}
|
|
|
|
public void absSnapTo(double x, double y, double z) {
|
|
double d = Mth.clamp(x, -3.0E7, 3.0E7);
|
|
double e = Mth.clamp(z, -3.0E7, 3.0E7);
|
|
this.xo = d;
|
|
this.yo = y;
|
|
this.zo = e;
|
|
this.setPos(d, y, e);
|
|
}
|
|
|
|
public void snapTo(Vec3 pos) {
|
|
this.snapTo(pos.x, pos.y, pos.z);
|
|
}
|
|
|
|
public void snapTo(double x, double y, double z) {
|
|
this.snapTo(x, y, z, this.getYRot(), this.getXRot());
|
|
}
|
|
|
|
public void snapTo(BlockPos pos, float yRot, float xRot) {
|
|
this.snapTo(pos.getBottomCenter(), yRot, xRot);
|
|
}
|
|
|
|
public void snapTo(Vec3 pos, float yRot, float xRot) {
|
|
this.snapTo(pos.x, pos.y, pos.z, yRot, xRot);
|
|
}
|
|
|
|
public void snapTo(double x, double y, double z, float yRot, float xRot) {
|
|
this.setPosRaw(x, y, z);
|
|
this.setYRot(yRot);
|
|
this.setXRot(xRot);
|
|
this.setOldPosAndRot();
|
|
this.reapplyPosition();
|
|
}
|
|
|
|
public final void setOldPosAndRot() {
|
|
this.setOldPos();
|
|
this.setOldRot();
|
|
}
|
|
|
|
public final void setOldPosAndRot(Vec3 pos, float yRot, float xRot) {
|
|
this.setOldPos(pos);
|
|
this.setOldRot(yRot, xRot);
|
|
}
|
|
|
|
protected void setOldPos() {
|
|
this.setOldPos(this.position);
|
|
}
|
|
|
|
public void setOldRot() {
|
|
this.setOldRot(this.getYRot(), this.getXRot());
|
|
}
|
|
|
|
private void setOldPos(Vec3 pos) {
|
|
this.xo = this.xOld = pos.x;
|
|
this.yo = this.yOld = pos.y;
|
|
this.zo = this.zOld = pos.z;
|
|
}
|
|
|
|
private void setOldRot(float yRot, float xRot) {
|
|
this.yRotO = yRot;
|
|
this.xRotO = xRot;
|
|
}
|
|
|
|
public final Vec3 oldPosition() {
|
|
return new Vec3(this.xOld, this.yOld, this.zOld);
|
|
}
|
|
|
|
/**
|
|
* Returns the distance to the entity.
|
|
*/
|
|
public float distanceTo(Entity entity) {
|
|
float f = (float)(this.getX() - entity.getX());
|
|
float g = (float)(this.getY() - entity.getY());
|
|
float h = (float)(this.getZ() - entity.getZ());
|
|
return Mth.sqrt(f * f + g * g + h * h);
|
|
}
|
|
|
|
/**
|
|
* Gets the squared distance to the position.
|
|
*/
|
|
public double distanceToSqr(double x, double y, double z) {
|
|
double d = this.getX() - x;
|
|
double e = this.getY() - y;
|
|
double f = this.getZ() - z;
|
|
return d * d + e * e + f * f;
|
|
}
|
|
|
|
/**
|
|
* Returns the squared distance to the entity.
|
|
*/
|
|
public double distanceToSqr(Entity entity) {
|
|
return this.distanceToSqr(entity.position());
|
|
}
|
|
|
|
public double distanceToSqr(Vec3 vec) {
|
|
double d = this.getX() - vec.x;
|
|
double e = this.getY() - vec.y;
|
|
double f = this.getZ() - vec.z;
|
|
return d * d + e * e + f * f;
|
|
}
|
|
|
|
/**
|
|
* Called by a player entity when they collide with an entity
|
|
*/
|
|
public void playerTouch(Player player) {
|
|
}
|
|
|
|
/**
|
|
* Applies a velocity to the entities, to push them away from each other.
|
|
*/
|
|
public void push(Entity entity) {
|
|
if (!this.isPassengerOfSameVehicle(entity)) {
|
|
if (!entity.noPhysics && !this.noPhysics) {
|
|
double d = entity.getX() - this.getX();
|
|
double e = entity.getZ() - this.getZ();
|
|
double f = Mth.absMax(d, e);
|
|
if (f >= 0.01F) {
|
|
f = Math.sqrt(f);
|
|
d /= f;
|
|
e /= f;
|
|
double g = 1.0 / f;
|
|
if (g > 1.0) {
|
|
g = 1.0;
|
|
}
|
|
|
|
d *= g;
|
|
e *= g;
|
|
d *= 0.05F;
|
|
e *= 0.05F;
|
|
if (!this.isVehicle() && this.isPushable()) {
|
|
this.push(-d, 0.0, -e);
|
|
}
|
|
|
|
if (!entity.isVehicle() && entity.isPushable()) {
|
|
entity.push(d, 0.0, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void push(Vec3 vector) {
|
|
this.push(vector.x, vector.y, vector.z);
|
|
}
|
|
|
|
/**
|
|
* Adds to the current velocity of the entity, and sets {@link #isAirBorne} to true.
|
|
*/
|
|
public void push(double x, double y, double z) {
|
|
this.setDeltaMovement(this.getDeltaMovement().add(x, y, z));
|
|
this.hasImpulse = true;
|
|
}
|
|
|
|
/**
|
|
* Marks this entity's velocity as changed, so that it can be re-synced with the client later
|
|
*/
|
|
protected void markHurt() {
|
|
this.hurtMarked = true;
|
|
}
|
|
|
|
@Deprecated
|
|
public final void hurt(DamageSource damageSource, float amount) {
|
|
if (this.level instanceof ServerLevel serverLevel) {
|
|
this.hurtServer(serverLevel, damageSource, amount);
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public final boolean hurtOrSimulate(DamageSource damageSource, float amount) {
|
|
return this.level instanceof ServerLevel serverLevel ? this.hurtServer(serverLevel, damageSource, amount) : this.hurtClient(damageSource);
|
|
}
|
|
|
|
public abstract boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount);
|
|
|
|
public boolean hurtClient(DamageSource damageSource) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the interpolated look vector.
|
|
*/
|
|
public final Vec3 getViewVector(float partialTicks) {
|
|
return this.calculateViewVector(this.getViewXRot(partialTicks), this.getViewYRot(partialTicks));
|
|
}
|
|
|
|
public Direction getNearestViewDirection() {
|
|
return Direction.getApproximateNearest(this.getViewVector(1.0F));
|
|
}
|
|
|
|
/**
|
|
* Returns the current X rotation of the entity.
|
|
*/
|
|
public float getViewXRot(float partialTicks) {
|
|
return this.getXRot(partialTicks);
|
|
}
|
|
|
|
/**
|
|
* Returns the current Y rotation of the entity.
|
|
*/
|
|
public float getViewYRot(float partialTick) {
|
|
return this.getYRot(partialTick);
|
|
}
|
|
|
|
public float getXRot(float partialTick) {
|
|
return partialTick == 1.0F ? this.getXRot() : Mth.lerp(partialTick, this.xRotO, this.getXRot());
|
|
}
|
|
|
|
public float getYRot(float partialTick) {
|
|
return partialTick == 1.0F ? this.getYRot() : Mth.rotLerp(partialTick, this.yRotO, this.getYRot());
|
|
}
|
|
|
|
/**
|
|
* Calculates the view vector using the X and Y rotation of an entity.
|
|
*/
|
|
public final Vec3 calculateViewVector(float xRot, float yRot) {
|
|
float f = xRot * (float) (Math.PI / 180.0);
|
|
float g = -yRot * (float) (Math.PI / 180.0);
|
|
float h = Mth.cos(g);
|
|
float i = Mth.sin(g);
|
|
float j = Mth.cos(f);
|
|
float k = Mth.sin(f);
|
|
return new Vec3(i * j, -k, h * j);
|
|
}
|
|
|
|
public final Vec3 getUpVector(float partialTick) {
|
|
return this.calculateUpVector(this.getViewXRot(partialTick), this.getViewYRot(partialTick));
|
|
}
|
|
|
|
protected final Vec3 calculateUpVector(float xRot, float yRot) {
|
|
return this.calculateViewVector(xRot - 90.0F, yRot);
|
|
}
|
|
|
|
public final Vec3 getEyePosition() {
|
|
return new Vec3(this.getX(), this.getEyeY(), this.getZ());
|
|
}
|
|
|
|
public final Vec3 getEyePosition(float partialTick) {
|
|
double d = Mth.lerp((double)partialTick, this.xo, this.getX());
|
|
double e = Mth.lerp((double)partialTick, this.yo, this.getY()) + this.getEyeHeight();
|
|
double f = Mth.lerp((double)partialTick, this.zo, this.getZ());
|
|
return new Vec3(d, e, f);
|
|
}
|
|
|
|
public Vec3 getLightProbePosition(float partialTicks) {
|
|
return this.getEyePosition(partialTicks);
|
|
}
|
|
|
|
public final Vec3 getPosition(float partialTicks) {
|
|
double d = Mth.lerp((double)partialTicks, this.xo, this.getX());
|
|
double e = Mth.lerp((double)partialTicks, this.yo, this.getY());
|
|
double f = Mth.lerp((double)partialTicks, this.zo, this.getZ());
|
|
return new Vec3(d, e, f);
|
|
}
|
|
|
|
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
|
|
Vec3 vec3 = this.getEyePosition(partialTicks);
|
|
Vec3 vec32 = this.getViewVector(partialTicks);
|
|
Vec3 vec33 = vec3.add(vec32.x * hitDistance, vec32.y * hitDistance, vec32.z * hitDistance);
|
|
return this.level()
|
|
.clip(
|
|
new ClipContext(
|
|
vec3, vec33, Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
|
|
)
|
|
);
|
|
}
|
|
|
|
public boolean canBeHitByProjectile() {
|
|
return this.isAlive() && this.isPickable();
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if other Entities should be prevented from moving through this Entity.
|
|
*/
|
|
public boolean isPickable() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if this entity should push and be pushed by other entities when colliding.
|
|
*/
|
|
public boolean isPushable() {
|
|
return false;
|
|
}
|
|
|
|
public void awardKillScore(Entity entity, DamageSource damageSource) {
|
|
if (entity instanceof ServerPlayer) {
|
|
CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer)entity, this, damageSource);
|
|
}
|
|
}
|
|
|
|
public boolean shouldRender(double x, double y, double z) {
|
|
double d = this.getX() - x;
|
|
double e = this.getY() - y;
|
|
double f = this.getZ() - z;
|
|
double g = d * d + e * e + f * f;
|
|
return this.shouldRenderAtSqrDistance(g);
|
|
}
|
|
|
|
/**
|
|
* Checks if the entity is in range to render.
|
|
*/
|
|
public boolean shouldRenderAtSqrDistance(double distance) {
|
|
double d = this.getBoundingBox().getSize();
|
|
if (Double.isNaN(d)) {
|
|
d = 1.0;
|
|
}
|
|
|
|
d *= 64.0 * viewScale;
|
|
return distance < d * d;
|
|
}
|
|
|
|
/**
|
|
* Writes this entity to NBT, unless it has been removed. Also writes this entity's passengers, and the entity type ID (so the produced NBT is sufficient to recreate the entity).
|
|
*
|
|
* Generally, {@link #writeUnlessPassenger} or {@link #writeWithoutTypeId} should be used instead of this method.
|
|
*
|
|
* @return True if the entity was written (and the passed compound should be saved)" false if the entity was not written.
|
|
*/
|
|
public boolean saveAsPassenger(CompoundTag compound) {
|
|
if (this.removalReason != null && !this.removalReason.shouldSave()) {
|
|
return false;
|
|
} else {
|
|
String string = this.getEncodeId();
|
|
if (string == null) {
|
|
return false;
|
|
} else {
|
|
compound.putString("id", string);
|
|
this.saveWithoutId(compound);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes this entity to NBT, unless it has been removed or it is a passenger. Also writes this entity's passengers, and the entity type ID (so the produced NBT is sufficient to recreate the entity).
|
|
* To always write the entity, use {@link #writeWithoutTypeId}.
|
|
*
|
|
* @return True if the entity was written (and the passed compound should be saved)" false if the entity was not written.
|
|
*/
|
|
public boolean save(CompoundTag compound) {
|
|
return this.isPassenger() ? false : this.saveAsPassenger(compound);
|
|
}
|
|
|
|
/**
|
|
* Writes this entity, including passengers, to NBT, regardless as to whether it is removed or a passenger. Does <b>not</b> include the entity's type ID, so the NBT is insufficient to recreate the entity using {@link AnvilChunkLoader#readWorldEntity}. Use {@link #writeUnlessPassenger} for that purpose.
|
|
*/
|
|
public CompoundTag saveWithoutId(CompoundTag compound) {
|
|
try {
|
|
if (this.vehicle != null) {
|
|
compound.store("Pos", Vec3.CODEC, new Vec3(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
|
|
} else {
|
|
compound.store("Pos", Vec3.CODEC, this.position());
|
|
}
|
|
|
|
compound.store("Motion", Vec3.CODEC, this.getDeltaMovement());
|
|
compound.store("Rotation", Vec2.CODEC, new Vec2(this.getYRot(), this.getXRot()));
|
|
compound.putDouble("fall_distance", this.fallDistance);
|
|
compound.putShort("Fire", (short)this.remainingFireTicks);
|
|
compound.putShort("Air", (short)this.getAirSupply());
|
|
compound.putBoolean("OnGround", this.onGround());
|
|
compound.putBoolean("Invulnerable", this.invulnerable);
|
|
compound.putInt("PortalCooldown", this.portalCooldown);
|
|
compound.store("UUID", UUIDUtil.CODEC, this.getUUID());
|
|
Component component = this.getCustomName();
|
|
if (component != null) {
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
compound.store("CustomName", ComponentSerialization.CODEC, registryOps, component);
|
|
}
|
|
|
|
if (this.isCustomNameVisible()) {
|
|
compound.putBoolean("CustomNameVisible", this.isCustomNameVisible());
|
|
}
|
|
|
|
if (this.isSilent()) {
|
|
compound.putBoolean("Silent", this.isSilent());
|
|
}
|
|
|
|
if (this.isNoGravity()) {
|
|
compound.putBoolean("NoGravity", this.isNoGravity());
|
|
}
|
|
|
|
if (this.hasGlowingTag) {
|
|
compound.putBoolean("Glowing", true);
|
|
}
|
|
|
|
int i = this.getTicksFrozen();
|
|
if (i > 0) {
|
|
compound.putInt("TicksFrozen", this.getTicksFrozen());
|
|
}
|
|
|
|
if (this.hasVisualFire) {
|
|
compound.putBoolean("HasVisualFire", this.hasVisualFire);
|
|
}
|
|
|
|
if (!this.tags.isEmpty()) {
|
|
compound.store("Tags", TAG_LIST_CODEC, List.copyOf(this.tags));
|
|
}
|
|
|
|
if (!this.customData.isEmpty()) {
|
|
compound.store("data", CustomData.CODEC, this.customData);
|
|
}
|
|
|
|
this.addAdditionalSaveData(compound);
|
|
if (this.isVehicle()) {
|
|
ListTag listTag = new ListTag();
|
|
|
|
for (Entity entity : this.getPassengers()) {
|
|
CompoundTag compoundTag = new CompoundTag();
|
|
if (entity.saveAsPassenger(compoundTag)) {
|
|
listTag.add(compoundTag);
|
|
}
|
|
}
|
|
|
|
if (!listTag.isEmpty()) {
|
|
compound.put("Passengers", listTag);
|
|
}
|
|
}
|
|
|
|
return compound;
|
|
} catch (Throwable var8) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var8, "Saving entity NBT");
|
|
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being saved");
|
|
this.fillCrashReportCategory(crashReportCategory);
|
|
throw new ReportedException(crashReport);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the entity from NBT (calls an abstract helper method to read specialized data)
|
|
*/
|
|
public void load(CompoundTag compound) {
|
|
try {
|
|
Vec3 vec3 = (Vec3)compound.read("Pos", Vec3.CODEC).orElse(Vec3.ZERO);
|
|
Vec3 vec32 = (Vec3)compound.read("Motion", Vec3.CODEC).orElse(Vec3.ZERO);
|
|
Vec2 vec2 = (Vec2)compound.read("Rotation", Vec2.CODEC).orElse(Vec2.ZERO);
|
|
this.setDeltaMovement(Math.abs(vec32.x) > 10.0 ? 0.0 : vec32.x, Math.abs(vec32.y) > 10.0 ? 0.0 : vec32.y, Math.abs(vec32.z) > 10.0 ? 0.0 : vec32.z);
|
|
this.hasImpulse = true;
|
|
double d = 3.0000512E7;
|
|
this.setPosRaw(Mth.clamp(vec3.x, -3.0000512E7, 3.0000512E7), Mth.clamp(vec3.y, -2.0E7, 2.0E7), Mth.clamp(vec3.z, -3.0000512E7, 3.0000512E7));
|
|
this.setYRot(vec2.x);
|
|
this.setXRot(vec2.y);
|
|
this.setOldPosAndRot();
|
|
this.setYHeadRot(this.getYRot());
|
|
this.setYBodyRot(this.getYRot());
|
|
this.fallDistance = compound.getDoubleOr("fall_distance", 0.0);
|
|
this.remainingFireTicks = compound.getShortOr("Fire", (short)0);
|
|
this.setAirSupply(compound.getIntOr("Air", this.getMaxAirSupply()));
|
|
this.onGround = compound.getBooleanOr("OnGround", false);
|
|
this.invulnerable = compound.getBooleanOr("Invulnerable", false);
|
|
this.portalCooldown = compound.getIntOr("PortalCooldown", 0);
|
|
compound.read("UUID", UUIDUtil.CODEC).ifPresent(uUID -> {
|
|
this.uuid = uUID;
|
|
this.stringUUID = this.uuid.toString();
|
|
});
|
|
if (!Double.isFinite(this.getX()) || !Double.isFinite(this.getY()) || !Double.isFinite(this.getZ())) {
|
|
throw new IllegalStateException("Entity has invalid position");
|
|
} else if (Double.isFinite(this.getYRot()) && Double.isFinite(this.getXRot())) {
|
|
this.reapplyPosition();
|
|
this.setRot(this.getYRot(), this.getXRot());
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
this.setCustomName((Component)compound.read("CustomName", ComponentSerialization.CODEC, registryOps).orElse(null));
|
|
this.setCustomNameVisible(compound.getBooleanOr("CustomNameVisible", false));
|
|
this.setSilent(compound.getBooleanOr("Silent", false));
|
|
this.setNoGravity(compound.getBooleanOr("NoGravity", false));
|
|
this.setGlowingTag(compound.getBooleanOr("Glowing", false));
|
|
this.setTicksFrozen(compound.getIntOr("TicksFrozen", 0));
|
|
this.hasVisualFire = compound.getBooleanOr("HasVisualFire", false);
|
|
this.customData = (CustomData)compound.read("data", CustomData.CODEC).orElse(CustomData.EMPTY);
|
|
this.tags.clear();
|
|
compound.read("Tags", TAG_LIST_CODEC).ifPresent(this.tags::addAll);
|
|
this.readAdditionalSaveData(compound);
|
|
if (this.repositionEntityAfterLoad()) {
|
|
this.reapplyPosition();
|
|
}
|
|
} else {
|
|
throw new IllegalStateException("Entity has invalid rotation");
|
|
}
|
|
} catch (Throwable var8) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var8, "Loading entity NBT");
|
|
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
|
|
this.fillCrashReportCategory(crashReportCategory);
|
|
throw new ReportedException(crashReport);
|
|
}
|
|
}
|
|
|
|
protected boolean repositionEntityAfterLoad() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the string that identifies this Entity's class
|
|
*/
|
|
@Nullable
|
|
protected final String getEncodeId() {
|
|
EntityType<?> entityType = this.getType();
|
|
ResourceLocation resourceLocation = EntityType.getKey(entityType);
|
|
return entityType.canSerialize() && resourceLocation != null ? resourceLocation.toString() : null;
|
|
}
|
|
|
|
/**
|
|
* (abstract) Protected helper method to read subclass entity data from NBT.
|
|
*/
|
|
protected abstract void readAdditionalSaveData(CompoundTag tag);
|
|
|
|
protected abstract void addAdditionalSaveData(CompoundTag tag);
|
|
|
|
@Nullable
|
|
public ItemEntity spawnAtLocation(ServerLevel level, ItemLike item) {
|
|
return this.spawnAtLocation(level, item, 0);
|
|
}
|
|
|
|
@Nullable
|
|
public ItemEntity spawnAtLocation(ServerLevel level, ItemLike item, int yOffset) {
|
|
return this.spawnAtLocation(level, new ItemStack(item), (float)yOffset);
|
|
}
|
|
|
|
@Nullable
|
|
public ItemEntity spawnAtLocation(ServerLevel level, ItemStack stack) {
|
|
return this.spawnAtLocation(level, stack, 0.0F);
|
|
}
|
|
|
|
@Nullable
|
|
public ItemEntity spawnAtLocation(ServerLevel level, ItemStack stack, float yOffset) {
|
|
if (stack.isEmpty()) {
|
|
return null;
|
|
} else {
|
|
ItemEntity itemEntity = new ItemEntity(level, this.getX(), this.getY() + yOffset, this.getZ(), stack);
|
|
itemEntity.setDefaultPickUpDelay();
|
|
level.addFreshEntity(itemEntity);
|
|
return itemEntity;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the entity has not been {@link #removed}.
|
|
*/
|
|
public boolean isAlive() {
|
|
return !this.isRemoved();
|
|
}
|
|
|
|
/**
|
|
* Checks if this entity is inside an opaque block.
|
|
*/
|
|
public boolean isInWall() {
|
|
if (this.noPhysics) {
|
|
return false;
|
|
} else {
|
|
float f = this.dimensions.width() * 0.8F;
|
|
AABB aABB = AABB.ofSize(this.getEyePosition(), f, 1.0E-6, f);
|
|
return BlockPos.betweenClosedStream(aABB)
|
|
.anyMatch(
|
|
blockPos -> {
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
return !blockState.isAir()
|
|
&& blockState.isSuffocating(this.level(), blockPos)
|
|
&& Shapes.joinIsNotEmpty(blockState.getCollisionShape(this.level(), blockPos).move(blockPos), Shapes.create(aABB), BooleanOp.AND);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
public InteractionResult interact(Player player, InteractionHand hand) {
|
|
if (this.isAlive() && this instanceof Leashable leashable) {
|
|
if (leashable.getLeashHolder() == player) {
|
|
if (!this.level().isClientSide()) {
|
|
if (player.hasInfiniteMaterials()) {
|
|
leashable.removeLeash();
|
|
} else {
|
|
leashable.dropLeash();
|
|
}
|
|
|
|
this.gameEvent(GameEvent.ENTITY_INTERACT, player);
|
|
}
|
|
|
|
return InteractionResult.SUCCESS.withoutItem();
|
|
}
|
|
|
|
ItemStack itemStack = player.getItemInHand(hand);
|
|
if (itemStack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
|
|
if (!this.level().isClientSide()) {
|
|
leashable.setLeashedTo(player, true);
|
|
}
|
|
|
|
itemStack.shrink(1);
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
}
|
|
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
public boolean canCollideWith(Entity entity) {
|
|
return entity.canBeCollidedWith() && !this.isPassengerOfSameVehicle(entity);
|
|
}
|
|
|
|
public boolean canBeCollidedWith() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handles updating while riding another entity
|
|
*/
|
|
public void rideTick() {
|
|
this.setDeltaMovement(Vec3.ZERO);
|
|
this.tick();
|
|
if (this.isPassenger()) {
|
|
this.getVehicle().positionRider(this);
|
|
}
|
|
}
|
|
|
|
public final void positionRider(Entity passenger) {
|
|
if (this.hasPassenger(passenger)) {
|
|
this.positionRider(passenger, Entity::setPos);
|
|
}
|
|
}
|
|
|
|
protected void positionRider(Entity passenger, Entity.MoveFunction callback) {
|
|
Vec3 vec3 = this.getPassengerRidingPosition(passenger);
|
|
Vec3 vec32 = passenger.getVehicleAttachmentPoint(this);
|
|
callback.accept(passenger, vec3.x - vec32.x, vec3.y - vec32.y, vec3.z - vec32.z);
|
|
}
|
|
|
|
/**
|
|
* Applies this entity's orientation to another entity. Used to update passenger orientation.
|
|
*/
|
|
public void onPassengerTurned(Entity entityToUpdate) {
|
|
}
|
|
|
|
public Vec3 getVehicleAttachmentPoint(Entity entity) {
|
|
return this.getAttachments().get(EntityAttachment.VEHICLE, 0, this.yRot);
|
|
}
|
|
|
|
public Vec3 getPassengerRidingPosition(Entity entity) {
|
|
return this.position().add(this.getPassengerAttachmentPoint(entity, this.dimensions, 1.0F));
|
|
}
|
|
|
|
protected Vec3 getPassengerAttachmentPoint(Entity entity, EntityDimensions dimensions, float partialTick) {
|
|
return getDefaultPassengerAttachmentPoint(this, entity, dimensions.attachments());
|
|
}
|
|
|
|
protected static Vec3 getDefaultPassengerAttachmentPoint(Entity vehicle, Entity passenger, EntityAttachments attachments) {
|
|
int i = vehicle.getPassengers().indexOf(passenger);
|
|
return attachments.getClamped(EntityAttachment.PASSENGER, i, vehicle.yRot);
|
|
}
|
|
|
|
public boolean startRiding(Entity vehicle) {
|
|
return this.startRiding(vehicle, false);
|
|
}
|
|
|
|
public boolean showVehicleHealth() {
|
|
return this instanceof LivingEntity;
|
|
}
|
|
|
|
public boolean startRiding(Entity vehicle, boolean force) {
|
|
if (vehicle == this.vehicle) {
|
|
return false;
|
|
} else if (!vehicle.couldAcceptPassenger()) {
|
|
return false;
|
|
} else if (!this.level().isClientSide() && !vehicle.type.canSerialize()) {
|
|
return false;
|
|
} else {
|
|
for (Entity entity = vehicle; entity.vehicle != null; entity = entity.vehicle) {
|
|
if (entity.vehicle == this) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (force || this.canRide(vehicle) && vehicle.canAddPassenger(this)) {
|
|
if (this.isPassenger()) {
|
|
this.stopRiding();
|
|
}
|
|
|
|
this.setPose(Pose.STANDING);
|
|
this.vehicle = vehicle;
|
|
this.vehicle.addPassenger(this);
|
|
vehicle.getIndirectPassengersStream()
|
|
.filter(entityx -> entityx instanceof ServerPlayer)
|
|
.forEach(entityx -> CriteriaTriggers.START_RIDING_TRIGGER.trigger((ServerPlayer)entityx));
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean canRide(Entity vehicle) {
|
|
return !this.isShiftKeyDown() && this.boardingCooldown <= 0;
|
|
}
|
|
|
|
/**
|
|
* Dismounts all entities riding this entity from this entity.
|
|
*/
|
|
public void ejectPassengers() {
|
|
for (int i = this.passengers.size() - 1; i >= 0; i--) {
|
|
((Entity)this.passengers.get(i)).stopRiding();
|
|
}
|
|
}
|
|
|
|
public void removeVehicle() {
|
|
if (this.vehicle != null) {
|
|
Entity entity = this.vehicle;
|
|
this.vehicle = null;
|
|
entity.removePassenger(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dismounts this entity from the entity it is riding.
|
|
*/
|
|
public void stopRiding() {
|
|
this.removeVehicle();
|
|
}
|
|
|
|
protected void addPassenger(Entity passenger) {
|
|
if (passenger.getVehicle() != this) {
|
|
throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)");
|
|
} else {
|
|
if (this.passengers.isEmpty()) {
|
|
this.passengers = ImmutableList.of(passenger);
|
|
} else {
|
|
List<Entity> list = Lists.<Entity>newArrayList(this.passengers);
|
|
if (!this.level().isClientSide && passenger instanceof Player && !(this.getFirstPassenger() instanceof Player)) {
|
|
list.add(0, passenger);
|
|
} else {
|
|
list.add(passenger);
|
|
}
|
|
|
|
this.passengers = ImmutableList.copyOf(list);
|
|
}
|
|
|
|
this.gameEvent(GameEvent.ENTITY_MOUNT, passenger);
|
|
}
|
|
}
|
|
|
|
protected void removePassenger(Entity passenger) {
|
|
if (passenger.getVehicle() == this) {
|
|
throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
|
|
} else {
|
|
if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
|
|
this.passengers = ImmutableList.of();
|
|
} else {
|
|
this.passengers = (ImmutableList<Entity>)this.passengers.stream().filter(entity2 -> entity2 != passenger).collect(ImmutableList.toImmutableList());
|
|
}
|
|
|
|
passenger.boardingCooldown = 60;
|
|
this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger);
|
|
}
|
|
}
|
|
|
|
protected boolean canAddPassenger(Entity passenger) {
|
|
return this.passengers.isEmpty();
|
|
}
|
|
|
|
protected boolean couldAcceptPassenger() {
|
|
return true;
|
|
}
|
|
|
|
public final boolean isInterpolating() {
|
|
return this.getInterpolation() != null && this.getInterpolation().hasActiveInterpolation();
|
|
}
|
|
|
|
public final void moveOrInterpolateTo(Vec3 pos, float yRot, float xRot) {
|
|
InterpolationHandler interpolationHandler = this.getInterpolation();
|
|
if (interpolationHandler != null) {
|
|
interpolationHandler.interpolateTo(pos, yRot, xRot);
|
|
} else {
|
|
this.setPos(pos);
|
|
this.setRot(yRot, xRot);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public InterpolationHandler getInterpolation() {
|
|
return null;
|
|
}
|
|
|
|
public void lerpHeadTo(float yaw, int pitch) {
|
|
this.setYHeadRot(yaw);
|
|
}
|
|
|
|
public float getPickRadius() {
|
|
return 0.0F;
|
|
}
|
|
|
|
/**
|
|
* Returns a (normalized) vector of where this entity is looking.
|
|
*/
|
|
public Vec3 getLookAngle() {
|
|
return this.calculateViewVector(this.getXRot(), this.getYRot());
|
|
}
|
|
|
|
public Vec3 getHandHoldingItemAngle(Item item) {
|
|
if (!(this instanceof Player player)) {
|
|
return Vec3.ZERO;
|
|
} else {
|
|
boolean bl = player.getOffhandItem().is(item) && !player.getMainHandItem().is(item);
|
|
HumanoidArm humanoidArm = bl ? player.getMainArm().getOpposite() : player.getMainArm();
|
|
return this.calculateViewVector(0.0F, this.getYRot() + (humanoidArm == HumanoidArm.RIGHT ? 80 : -80)).scale(0.5);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Entity's pitch and yaw as a {@link net.minecraft.world.phys.Vec2}.
|
|
*/
|
|
public Vec2 getRotationVector() {
|
|
return new Vec2(this.getXRot(), this.getYRot());
|
|
}
|
|
|
|
public Vec3 getForward() {
|
|
return Vec3.directionFromRotation(this.getRotationVector());
|
|
}
|
|
|
|
public void setAsInsidePortal(Portal portal, BlockPos pos) {
|
|
if (this.isOnPortalCooldown()) {
|
|
this.setPortalCooldown();
|
|
} else {
|
|
if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) {
|
|
this.portalProcess = new PortalProcessor(portal, pos.immutable());
|
|
} else if (!this.portalProcess.isInsidePortalThisTick()) {
|
|
this.portalProcess.updateEntryPosition(pos.immutable());
|
|
this.portalProcess.setAsInsidePortalThisTick(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void handlePortal() {
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
this.processPortalCooldown();
|
|
if (this.portalProcess != null) {
|
|
if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("portal");
|
|
this.setPortalCooldown();
|
|
TeleportTransition teleportTransition = this.portalProcess.getPortalDestination(serverLevel, this);
|
|
if (teleportTransition != null) {
|
|
ServerLevel serverLevel2 = teleportTransition.newLevel();
|
|
if (serverLevel.getServer().isLevelEnabled(serverLevel2)
|
|
&& (serverLevel2.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, serverLevel2))) {
|
|
this.teleport(teleportTransition);
|
|
}
|
|
}
|
|
|
|
profilerFiller.pop();
|
|
} else if (this.portalProcess.hasExpired()) {
|
|
this.portalProcess = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the amount of cooldown before this entity can use a portal again.
|
|
*/
|
|
public int getDimensionChangingDelay() {
|
|
Entity entity = this.getFirstPassenger();
|
|
return entity instanceof ServerPlayer ? entity.getDimensionChangingDelay() : 300;
|
|
}
|
|
|
|
/**
|
|
* Updates the entity motion clientside, called by packets from the server
|
|
*/
|
|
public void lerpMotion(double x, double y, double z) {
|
|
this.setDeltaMovement(x, y, z);
|
|
}
|
|
|
|
public void handleDamageEvent(DamageSource damageSource) {
|
|
}
|
|
|
|
/**
|
|
* Handles an entity event received from a {@link net.minecraft.network.protocol.game.ClientboundEntityEventPacket}.
|
|
*/
|
|
public void handleEntityEvent(byte id) {
|
|
switch (id) {
|
|
case 53:
|
|
HoneyBlock.showSlideParticles(this);
|
|
}
|
|
}
|
|
|
|
public void animateHurt(float yaw) {
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the entity is on fire. Used by render to add the fire effect on rendering.
|
|
*/
|
|
public boolean isOnFire() {
|
|
boolean bl = this.level() != null && this.level().isClientSide;
|
|
return !this.fireImmune() && (this.remainingFireTicks > 0 || bl && this.getSharedFlag(0));
|
|
}
|
|
|
|
public boolean isPassenger() {
|
|
return this.getVehicle() != null;
|
|
}
|
|
|
|
/**
|
|
* If at least 1 entity is riding this one
|
|
*/
|
|
public boolean isVehicle() {
|
|
return !this.passengers.isEmpty();
|
|
}
|
|
|
|
public boolean dismountsUnderwater() {
|
|
return this.getType().is(EntityTypeTags.DISMOUNTS_UNDERWATER);
|
|
}
|
|
|
|
public boolean canControlVehicle() {
|
|
return !this.getType().is(EntityTypeTags.NON_CONTROLLING_RIDER);
|
|
}
|
|
|
|
public void setShiftKeyDown(boolean keyDown) {
|
|
this.setSharedFlag(1, keyDown);
|
|
}
|
|
|
|
public boolean isShiftKeyDown() {
|
|
return this.getSharedFlag(1);
|
|
}
|
|
|
|
public boolean isSteppingCarefully() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
public boolean isSuppressingBounce() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
public boolean isDiscrete() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
public boolean isDescending() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
public boolean isCrouching() {
|
|
return this.hasPose(Pose.CROUCHING);
|
|
}
|
|
|
|
/**
|
|
* Get if the Entity is sprinting.
|
|
*/
|
|
public boolean isSprinting() {
|
|
return this.getSharedFlag(3);
|
|
}
|
|
|
|
/**
|
|
* Set sprinting switch for Entity.
|
|
*/
|
|
public void setSprinting(boolean sprinting) {
|
|
this.setSharedFlag(3, sprinting);
|
|
}
|
|
|
|
public boolean isSwimming() {
|
|
return this.getSharedFlag(4);
|
|
}
|
|
|
|
public boolean isVisuallySwimming() {
|
|
return this.hasPose(Pose.SWIMMING);
|
|
}
|
|
|
|
public boolean isVisuallyCrawling() {
|
|
return this.isVisuallySwimming() && !this.isInWater();
|
|
}
|
|
|
|
public void setSwimming(boolean swimming) {
|
|
this.setSharedFlag(4, swimming);
|
|
}
|
|
|
|
public final boolean hasGlowingTag() {
|
|
return this.hasGlowingTag;
|
|
}
|
|
|
|
public final void setGlowingTag(boolean hasGlowingTag) {
|
|
this.hasGlowingTag = hasGlowingTag;
|
|
this.setSharedFlag(6, this.isCurrentlyGlowing());
|
|
}
|
|
|
|
public boolean isCurrentlyGlowing() {
|
|
return this.level().isClientSide() ? this.getSharedFlag(6) : this.hasGlowingTag;
|
|
}
|
|
|
|
public boolean isInvisible() {
|
|
return this.getSharedFlag(5);
|
|
}
|
|
|
|
/**
|
|
* Only used by renderer in EntityLivingBase subclasses.
|
|
* Determines if an entity is visible or not to a specific player, if the entity is normally invisible.
|
|
* For EntityLivingBase subclasses, returning false when invisible will render the entity semi-transparent.
|
|
*/
|
|
public boolean isInvisibleTo(Player player) {
|
|
if (player.isSpectator()) {
|
|
return false;
|
|
} else {
|
|
Team team = this.getTeam();
|
|
return team != null && player != null && player.getTeam() == team && team.canSeeFriendlyInvisibles() ? false : this.isInvisible();
|
|
}
|
|
}
|
|
|
|
public boolean isOnRails() {
|
|
return false;
|
|
}
|
|
|
|
public void updateDynamicGameEventListener(BiConsumer<DynamicGameEventListener<?>, ServerLevel> listenerConsumer) {
|
|
}
|
|
|
|
@Nullable
|
|
public PlayerTeam getTeam() {
|
|
return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName());
|
|
}
|
|
|
|
/**
|
|
* Returns whether this Entity is on the same team as the given Entity.
|
|
*/
|
|
public final boolean isAlliedTo(@Nullable Entity entity) {
|
|
return entity == null ? false : this == entity || this.considersEntityAsAlly(entity) || entity.considersEntityAsAlly(this);
|
|
}
|
|
|
|
protected boolean considersEntityAsAlly(Entity entity) {
|
|
return this.isAlliedTo(entity.getTeam());
|
|
}
|
|
|
|
/**
|
|
* Returns whether this Entity is on the given scoreboard team.
|
|
*/
|
|
public boolean isAlliedTo(@Nullable Team team) {
|
|
return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false;
|
|
}
|
|
|
|
public void setInvisible(boolean invisible) {
|
|
this.setSharedFlag(5, invisible);
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if the flag is active for the entity. Known flags: 0: burning 1: sneaking 2: unused 3: sprinting 4: swimming 5: invisible 6: glowing 7: elytra flying
|
|
*/
|
|
protected boolean getSharedFlag(int flag) {
|
|
return (this.entityData.get(DATA_SHARED_FLAGS_ID) & 1 << flag) != 0;
|
|
}
|
|
|
|
/**
|
|
* Enable or disable an entity flag, see {@link #getEntityFlag} to read the known flags.
|
|
*/
|
|
protected void setSharedFlag(int flag, boolean set) {
|
|
byte b = this.entityData.get(DATA_SHARED_FLAGS_ID);
|
|
if (set) {
|
|
this.entityData.set(DATA_SHARED_FLAGS_ID, (byte)(b | 1 << flag));
|
|
} else {
|
|
this.entityData.set(DATA_SHARED_FLAGS_ID, (byte)(b & ~(1 << flag)));
|
|
}
|
|
}
|
|
|
|
public int getMaxAirSupply() {
|
|
return 300;
|
|
}
|
|
|
|
public int getAirSupply() {
|
|
return this.entityData.get(DATA_AIR_SUPPLY_ID);
|
|
}
|
|
|
|
public void setAirSupply(int air) {
|
|
this.entityData.set(DATA_AIR_SUPPLY_ID, air);
|
|
}
|
|
|
|
public void clearFreeze() {
|
|
this.setTicksFrozen(0);
|
|
}
|
|
|
|
public int getTicksFrozen() {
|
|
return this.entityData.get(DATA_TICKS_FROZEN);
|
|
}
|
|
|
|
public void setTicksFrozen(int ticksFrozen) {
|
|
this.entityData.set(DATA_TICKS_FROZEN, ticksFrozen);
|
|
}
|
|
|
|
public float getPercentFrozen() {
|
|
int i = this.getTicksRequiredToFreeze();
|
|
return (float)Math.min(this.getTicksFrozen(), i) / i;
|
|
}
|
|
|
|
public boolean isFullyFrozen() {
|
|
return this.getTicksFrozen() >= this.getTicksRequiredToFreeze();
|
|
}
|
|
|
|
public int getTicksRequiredToFreeze() {
|
|
return 140;
|
|
}
|
|
|
|
public void thunderHit(ServerLevel level, LightningBolt lightning) {
|
|
this.setRemainingFireTicks(this.remainingFireTicks + 1);
|
|
if (this.remainingFireTicks == 0) {
|
|
this.igniteForSeconds(8.0F);
|
|
}
|
|
|
|
this.hurtServer(level, this.damageSources().lightningBolt(), 5.0F);
|
|
}
|
|
|
|
public void onAboveBubbleColumn(boolean downwards, BlockPos pos) {
|
|
handleOnAboveBubbleColumn(this, downwards, pos);
|
|
}
|
|
|
|
protected static void handleOnAboveBubbleColumn(Entity entity, boolean downwards, BlockPos pos) {
|
|
Vec3 vec3 = entity.getDeltaMovement();
|
|
double d;
|
|
if (downwards) {
|
|
d = Math.max(-0.9, vec3.y - 0.03);
|
|
} else {
|
|
d = Math.min(1.8, vec3.y + 0.1);
|
|
}
|
|
|
|
entity.setDeltaMovement(vec3.x, d, vec3.z);
|
|
sendBubbleColumnParticles(entity.level, pos);
|
|
}
|
|
|
|
protected static void sendBubbleColumnParticles(Level level, BlockPos pos) {
|
|
if (level instanceof ServerLevel serverLevel) {
|
|
for (int i = 0; i < 2; i++) {
|
|
serverLevel.sendParticles(
|
|
ParticleTypes.SPLASH, pos.getX() + level.random.nextDouble(), pos.getY() + 1, pos.getZ() + level.random.nextDouble(), 1, 0.0, 0.0, 0.0, 1.0
|
|
);
|
|
serverLevel.sendParticles(
|
|
ParticleTypes.BUBBLE, pos.getX() + level.random.nextDouble(), pos.getY() + 1, pos.getZ() + level.random.nextDouble(), 1, 0.0, 0.01, 0.0, 0.2
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onInsideBubbleColumn(boolean downwards) {
|
|
handleOnInsideBubbleColumn(this, downwards);
|
|
}
|
|
|
|
protected static void handleOnInsideBubbleColumn(Entity entity, boolean downwards) {
|
|
Vec3 vec3 = entity.getDeltaMovement();
|
|
double d;
|
|
if (downwards) {
|
|
d = Math.max(-0.3, vec3.y - 0.03);
|
|
} else {
|
|
d = Math.min(0.7, vec3.y + 0.06);
|
|
}
|
|
|
|
entity.setDeltaMovement(vec3.x, d, vec3.z);
|
|
entity.resetFallDistance();
|
|
}
|
|
|
|
public boolean killedEntity(ServerLevel level, LivingEntity entity) {
|
|
return true;
|
|
}
|
|
|
|
public void checkSlowFallDistance() {
|
|
if (this.getDeltaMovement().y() > -0.5 && this.fallDistance > 1.0) {
|
|
this.fallDistance = 1.0;
|
|
}
|
|
}
|
|
|
|
public void resetFallDistance() {
|
|
this.fallDistance = 0.0;
|
|
}
|
|
|
|
protected void moveTowardsClosestSpace(double x, double y, double z) {
|
|
BlockPos blockPos = BlockPos.containing(x, y, z);
|
|
Vec3 vec3 = new Vec3(x - blockPos.getX(), y - blockPos.getY(), z - blockPos.getZ());
|
|
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
|
Direction direction = Direction.UP;
|
|
double d = Double.MAX_VALUE;
|
|
|
|
for (Direction direction2 : new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, Direction.UP}) {
|
|
mutableBlockPos.setWithOffset(blockPos, direction2);
|
|
if (!this.level().getBlockState(mutableBlockPos).isCollisionShapeFullBlock(this.level(), mutableBlockPos)) {
|
|
double e = vec3.get(direction2.getAxis());
|
|
double f = direction2.getAxisDirection() == Direction.AxisDirection.POSITIVE ? 1.0 - e : e;
|
|
if (f < d) {
|
|
d = f;
|
|
direction = direction2;
|
|
}
|
|
}
|
|
}
|
|
|
|
float g = this.random.nextFloat() * 0.2F + 0.1F;
|
|
float h = direction.getAxisDirection().getStep();
|
|
Vec3 vec32 = this.getDeltaMovement().scale(0.75);
|
|
if (direction.getAxis() == Direction.Axis.X) {
|
|
this.setDeltaMovement(h * g, vec32.y, vec32.z);
|
|
} else if (direction.getAxis() == Direction.Axis.Y) {
|
|
this.setDeltaMovement(vec32.x, h * g, vec32.z);
|
|
} else if (direction.getAxis() == Direction.Axis.Z) {
|
|
this.setDeltaMovement(vec32.x, vec32.y, h * g);
|
|
}
|
|
}
|
|
|
|
public void makeStuckInBlock(BlockState state, Vec3 motionMultiplier) {
|
|
this.resetFallDistance();
|
|
this.stuckSpeedMultiplier = motionMultiplier;
|
|
}
|
|
|
|
private static Component removeAction(Component name) {
|
|
MutableComponent mutableComponent = name.plainCopy().setStyle(name.getStyle().withClickEvent(null));
|
|
|
|
for (Component component : name.getSiblings()) {
|
|
mutableComponent.append(removeAction(component));
|
|
}
|
|
|
|
return mutableComponent;
|
|
}
|
|
|
|
@Override
|
|
public Component getName() {
|
|
Component component = this.getCustomName();
|
|
return component != null ? removeAction(component) : this.getTypeName();
|
|
}
|
|
|
|
protected Component getTypeName() {
|
|
return this.type.getDescription();
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if Entity argument is equal to this Entity
|
|
*/
|
|
public boolean is(Entity entity) {
|
|
return this == entity;
|
|
}
|
|
|
|
public float getYHeadRot() {
|
|
return 0.0F;
|
|
}
|
|
|
|
/**
|
|
* Sets the head's Y rotation of the entity.
|
|
*/
|
|
public void setYHeadRot(float yHeadRot) {
|
|
}
|
|
|
|
/**
|
|
* Set the body Y rotation of the entity.
|
|
*/
|
|
public void setYBodyRot(float yBodyRot) {
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if it's possible to attack this entity with an item.
|
|
*/
|
|
public boolean isAttackable() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called when a player attacks an entity. If this returns true the attack will not happen.
|
|
*/
|
|
public boolean skipAttackInteraction(Entity entity) {
|
|
return false;
|
|
}
|
|
|
|
public String toString() {
|
|
String string = this.level() == null ? "~NULL~" : this.level().toString();
|
|
return this.removalReason != null
|
|
? String.format(
|
|
Locale.ROOT,
|
|
"%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]",
|
|
this.getClass().getSimpleName(),
|
|
this.getName().getString(),
|
|
this.id,
|
|
string,
|
|
this.getX(),
|
|
this.getY(),
|
|
this.getZ(),
|
|
this.removalReason
|
|
)
|
|
: String.format(
|
|
Locale.ROOT,
|
|
"%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]",
|
|
this.getClass().getSimpleName(),
|
|
this.getName().getString(),
|
|
this.id,
|
|
string,
|
|
this.getX(),
|
|
this.getY(),
|
|
this.getZ()
|
|
);
|
|
}
|
|
|
|
protected final boolean isInvulnerableToBase(DamageSource damageSource) {
|
|
return this.isRemoved()
|
|
|| this.invulnerable && !damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY) && !damageSource.isCreativePlayer()
|
|
|| damageSource.is(DamageTypeTags.IS_FIRE) && this.fireImmune()
|
|
|| damageSource.is(DamageTypeTags.IS_FALL) && this.getType().is(EntityTypeTags.FALL_DAMAGE_IMMUNE);
|
|
}
|
|
|
|
public boolean isInvulnerable() {
|
|
return this.invulnerable;
|
|
}
|
|
|
|
/**
|
|
* Sets whether this Entity is invulnerable.
|
|
*/
|
|
public void setInvulnerable(boolean isInvulnerable) {
|
|
this.invulnerable = isInvulnerable;
|
|
}
|
|
|
|
/**
|
|
* Sets this entity's location and angles to the location and angles of the passed in entity.
|
|
*/
|
|
public void copyPosition(Entity entity) {
|
|
this.snapTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
|
|
}
|
|
|
|
/**
|
|
* Prepares this entity in new dimension by copying NBT data from entity in old dimension
|
|
*/
|
|
public void restoreFrom(Entity entity) {
|
|
CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
|
|
compoundTag.remove("Dimension");
|
|
this.load(compoundTag);
|
|
this.portalCooldown = entity.portalCooldown;
|
|
this.portalProcess = entity.portalProcess;
|
|
}
|
|
|
|
@Nullable
|
|
public Entity teleport(TeleportTransition teleportTransition) {
|
|
if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved()) {
|
|
ServerLevel serverLevel2 = teleportTransition.newLevel();
|
|
boolean bl = serverLevel2.dimension() != serverLevel.dimension();
|
|
if (!teleportTransition.asPassenger()) {
|
|
this.stopRiding();
|
|
}
|
|
|
|
return bl ? this.teleportCrossDimension(serverLevel2, teleportTransition) : this.teleportSameDimension(serverLevel, teleportTransition);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private Entity teleportSameDimension(ServerLevel level, TeleportTransition teleportTransition) {
|
|
for (Entity entity : this.getPassengers()) {
|
|
entity.teleport(this.calculatePassengerTransition(teleportTransition, entity));
|
|
}
|
|
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("teleportSameDimension");
|
|
this.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
|
|
if (!teleportTransition.asPassenger()) {
|
|
this.sendTeleportTransitionToRidingPlayers(teleportTransition);
|
|
}
|
|
|
|
teleportTransition.postTeleportTransition().onTransition(this);
|
|
profilerFiller.pop();
|
|
return this;
|
|
}
|
|
|
|
private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) {
|
|
List<Entity> list = this.getPassengers();
|
|
List<Entity> list2 = new ArrayList(list.size());
|
|
this.ejectPassengers();
|
|
|
|
for (Entity entity : list) {
|
|
Entity entity2 = entity.teleport(this.calculatePassengerTransition(teleportTransition, entity));
|
|
if (entity2 != null) {
|
|
list2.add(entity2);
|
|
}
|
|
}
|
|
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("teleportCrossDimension");
|
|
Entity entityx = this.getType().create(level, EntitySpawnReason.DIMENSION_TRAVEL);
|
|
if (entityx == null) {
|
|
profilerFiller.pop();
|
|
return null;
|
|
} else {
|
|
entityx.restoreFrom(this);
|
|
this.removeAfterChangingDimensions();
|
|
entityx.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
|
|
level.addDuringTeleport(entityx);
|
|
|
|
for (Entity entity3 : list2) {
|
|
entity3.startRiding(entityx, true);
|
|
}
|
|
|
|
level.resetEmptyTime();
|
|
teleportTransition.postTeleportTransition().onTransition(entityx);
|
|
profilerFiller.pop();
|
|
return entityx;
|
|
}
|
|
}
|
|
|
|
private TeleportTransition calculatePassengerTransition(TeleportTransition teleportTransition, Entity entity) {
|
|
float f = teleportTransition.yRot() + (teleportTransition.relatives().contains(Relative.Y_ROT) ? 0.0F : entity.getYRot() - this.getYRot());
|
|
float g = teleportTransition.xRot() + (teleportTransition.relatives().contains(Relative.X_ROT) ? 0.0F : entity.getXRot() - this.getXRot());
|
|
Vec3 vec3 = entity.position().subtract(this.position());
|
|
Vec3 vec32 = teleportTransition.position()
|
|
.add(
|
|
teleportTransition.relatives().contains(Relative.X) ? 0.0 : vec3.x(),
|
|
teleportTransition.relatives().contains(Relative.Y) ? 0.0 : vec3.y(),
|
|
teleportTransition.relatives().contains(Relative.Z) ? 0.0 : vec3.z()
|
|
);
|
|
return teleportTransition.withPosition(vec32).withRotation(f, g).transitionAsPassenger();
|
|
}
|
|
|
|
private void sendTeleportTransitionToRidingPlayers(TeleportTransition teleportTransition) {
|
|
Entity entity = this.getControllingPassenger();
|
|
|
|
for (Entity entity2 : this.getIndirectPassengers()) {
|
|
if (entity2 instanceof ServerPlayer serverPlayer) {
|
|
if (entity != null && serverPlayer.getId() == entity.getId()) {
|
|
serverPlayer.connection
|
|
.send(ClientboundTeleportEntityPacket.teleport(this.getId(), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives(), this.onGround));
|
|
} else {
|
|
serverPlayer.connection.send(ClientboundTeleportEntityPacket.teleport(this.getId(), PositionMoveRotation.of(this), Set.of(), this.onGround));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void teleportSetPosition(PositionMoveRotation positionMovementRotation, Set<Relative> relatives) {
|
|
PositionMoveRotation positionMoveRotation = PositionMoveRotation.of(this);
|
|
PositionMoveRotation positionMoveRotation2 = PositionMoveRotation.calculateAbsolute(positionMoveRotation, positionMovementRotation, relatives);
|
|
this.setPosRaw(positionMoveRotation2.position().x, positionMoveRotation2.position().y, positionMoveRotation2.position().z);
|
|
this.setYRot(positionMoveRotation2.yRot());
|
|
this.setYHeadRot(positionMoveRotation2.yRot());
|
|
this.setXRot(positionMoveRotation2.xRot());
|
|
this.reapplyPosition();
|
|
this.setOldPosAndRot();
|
|
this.setDeltaMovement(positionMoveRotation2.deltaMovement());
|
|
this.movementThisTick.clear();
|
|
}
|
|
|
|
public void forceSetRotation(float yRot, float xRot) {
|
|
this.setYRot(yRot);
|
|
this.setYHeadRot(yRot);
|
|
this.setXRot(xRot);
|
|
this.setOldRot();
|
|
}
|
|
|
|
public void placePortalTicket(BlockPos pos) {
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
serverLevel.getChunkSource().addTicketWithRadius(TicketType.PORTAL, new ChunkPos(pos), 3);
|
|
}
|
|
}
|
|
|
|
protected void removeAfterChangingDimensions() {
|
|
this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
|
|
if (this instanceof Leashable leashable) {
|
|
leashable.removeLeash();
|
|
}
|
|
}
|
|
|
|
public Vec3 getRelativePortalPosition(Direction.Axis axis, FoundRectangle portal) {
|
|
return PortalShape.getRelativePosition(portal, axis, this.position(), this.getDimensions(this.getPose()));
|
|
}
|
|
|
|
public boolean canUsePortal(boolean allowPassengers) {
|
|
return (allowPassengers || !this.isPassenger()) && this.isAlive();
|
|
}
|
|
|
|
public boolean canTeleport(Level fromLevel, Level toLevel) {
|
|
if (fromLevel.dimension() == Level.END && toLevel.dimension() == Level.OVERWORLD) {
|
|
for (Entity entity : this.getPassengers()) {
|
|
if (entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Explosion resistance of a block relative to this entity
|
|
*/
|
|
public float getBlockExplosionResistance(
|
|
Explosion explosion, BlockGetter level, BlockPos pos, BlockState blockState, FluidState fluidState, float explosionPower
|
|
) {
|
|
return explosionPower;
|
|
}
|
|
|
|
public boolean shouldBlockExplode(Explosion explosion, BlockGetter level, BlockPos pos, BlockState blockState, float explosionPower) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The maximum height from where the entity is allowed to jump (used in pathfinder)
|
|
*/
|
|
public int getMaxFallDistance() {
|
|
return 3;
|
|
}
|
|
|
|
/**
|
|
* Return whether this entity should NOT trigger a pressure plate or a tripwire.
|
|
*/
|
|
public boolean isIgnoringBlockTriggers() {
|
|
return false;
|
|
}
|
|
|
|
public void fillCrashReportCategory(CrashReportCategory category) {
|
|
category.setDetail("Entity Type", (CrashReportDetail<String>)(() -> EntityType.getKey(this.getType()) + " (" + this.getClass().getCanonicalName() + ")"));
|
|
category.setDetail("Entity ID", this.id);
|
|
category.setDetail("Entity Name", (CrashReportDetail<String>)(() -> this.getName().getString()));
|
|
category.setDetail("Entity's Exact location", String.format(Locale.ROOT, "%.2f, %.2f, %.2f", this.getX(), this.getY(), this.getZ()));
|
|
category.setDetail(
|
|
"Entity's Block location", CrashReportCategory.formatLocation(this.level(), Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()))
|
|
);
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
category.setDetail("Entity's Momentum", String.format(Locale.ROOT, "%.2f, %.2f, %.2f", vec3.x, vec3.y, vec3.z));
|
|
category.setDetail("Entity's Passengers", (CrashReportDetail<String>)(() -> this.getPassengers().toString()));
|
|
category.setDetail("Entity's Vehicle", (CrashReportDetail<String>)(() -> String.valueOf(this.getVehicle())));
|
|
}
|
|
|
|
/**
|
|
* Return whether this entity should be rendered as on fire.
|
|
*/
|
|
public boolean displayFireAnimation() {
|
|
return this.isOnFire() && !this.isSpectator();
|
|
}
|
|
|
|
public void setUUID(UUID uniqueId) {
|
|
this.uuid = uniqueId;
|
|
this.stringUUID = this.uuid.toString();
|
|
}
|
|
|
|
@Override
|
|
public UUID getUUID() {
|
|
return this.uuid;
|
|
}
|
|
|
|
public String getStringUUID() {
|
|
return this.stringUUID;
|
|
}
|
|
|
|
@Override
|
|
public String getScoreboardName() {
|
|
return this.stringUUID;
|
|
}
|
|
|
|
public boolean isPushedByFluid() {
|
|
return true;
|
|
}
|
|
|
|
public static double getViewScale() {
|
|
return viewScale;
|
|
}
|
|
|
|
public static void setViewScale(double renderDistWeight) {
|
|
viewScale = renderDistWeight;
|
|
}
|
|
|
|
@Override
|
|
public Component getDisplayName() {
|
|
return PlayerTeam.formatNameForTeam(this.getTeam(), this.getName())
|
|
.withStyle(style -> style.withHoverEvent(this.createHoverEvent()).withInsertion(this.getStringUUID()));
|
|
}
|
|
|
|
public void setCustomName(@Nullable Component name) {
|
|
this.entityData.set(DATA_CUSTOM_NAME, Optional.ofNullable(name));
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Component getCustomName() {
|
|
return (Component)this.entityData.get(DATA_CUSTOM_NAME).orElse(null);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCustomName() {
|
|
return this.entityData.get(DATA_CUSTOM_NAME).isPresent();
|
|
}
|
|
|
|
public void setCustomNameVisible(boolean alwaysRenderNameTag) {
|
|
this.entityData.set(DATA_CUSTOM_NAME_VISIBLE, alwaysRenderNameTag);
|
|
}
|
|
|
|
public boolean isCustomNameVisible() {
|
|
return this.entityData.get(DATA_CUSTOM_NAME_VISIBLE);
|
|
}
|
|
|
|
public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
|
|
Entity entity = this.teleport(new TeleportTransition(level, new Vec3(x, y, z), Vec3.ZERO, yaw, pitch, relativeMovements, TeleportTransition.DO_NOTHING));
|
|
return entity != null;
|
|
}
|
|
|
|
public void dismountTo(double x, double y, double z) {
|
|
this.teleportTo(x, y, z);
|
|
}
|
|
|
|
/**
|
|
* Sets the position of the entity and updates the 'last' variables
|
|
*/
|
|
public void teleportTo(double x, double y, double z) {
|
|
if (this.level() instanceof ServerLevel) {
|
|
this.snapTo(x, y, z, this.getYRot(), this.getXRot());
|
|
this.teleportPassengers();
|
|
}
|
|
}
|
|
|
|
private void teleportPassengers() {
|
|
this.getSelfAndPassengers().forEach(entity -> {
|
|
for (Entity entity2 : entity.passengers) {
|
|
entity.positionRider(entity2, Entity::snapTo);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void teleportRelative(double dx, double dy, double dz) {
|
|
this.teleportTo(this.getX() + dx, this.getY() + dy, this.getZ() + dz);
|
|
}
|
|
|
|
public boolean shouldShowName() {
|
|
return this.isCustomNameVisible();
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(List<DataValue<?>> newData) {
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
if (DATA_POSE.equals(dataAccessor)) {
|
|
this.refreshDimensions();
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
protected void fixupDimensions() {
|
|
Pose pose = this.getPose();
|
|
EntityDimensions entityDimensions = this.getDimensions(pose);
|
|
this.dimensions = entityDimensions;
|
|
this.eyeHeight = entityDimensions.eyeHeight();
|
|
}
|
|
|
|
public void refreshDimensions() {
|
|
EntityDimensions entityDimensions = this.dimensions;
|
|
Pose pose = this.getPose();
|
|
EntityDimensions entityDimensions2 = this.getDimensions(pose);
|
|
this.dimensions = entityDimensions2;
|
|
this.eyeHeight = entityDimensions2.eyeHeight();
|
|
this.reapplyPosition();
|
|
boolean bl = entityDimensions2.width() <= 4.0F && entityDimensions2.height() <= 4.0F;
|
|
if (!this.level.isClientSide
|
|
&& !this.firstTick
|
|
&& !this.noPhysics
|
|
&& bl
|
|
&& (entityDimensions2.width() > entityDimensions.width() || entityDimensions2.height() > entityDimensions.height())
|
|
&& !(this instanceof Player)) {
|
|
this.fudgePositionAfterSizeChange(entityDimensions);
|
|
}
|
|
}
|
|
|
|
public boolean fudgePositionAfterSizeChange(EntityDimensions dimensions) {
|
|
EntityDimensions entityDimensions = this.getDimensions(this.getPose());
|
|
Vec3 vec3 = this.position().add(0.0, dimensions.height() / 2.0, 0.0);
|
|
double d = Math.max(0.0F, entityDimensions.width() - dimensions.width()) + 1.0E-6;
|
|
double e = Math.max(0.0F, entityDimensions.height() - dimensions.height()) + 1.0E-6;
|
|
VoxelShape voxelShape = Shapes.create(AABB.ofSize(vec3, d, e, d));
|
|
Optional<Vec3> optional = this.level.findFreePosition(this, voxelShape, vec3, entityDimensions.width(), entityDimensions.height(), entityDimensions.width());
|
|
if (optional.isPresent()) {
|
|
this.setPos(((Vec3)optional.get()).add(0.0, -entityDimensions.height() / 2.0, 0.0));
|
|
return true;
|
|
} else {
|
|
if (entityDimensions.width() > dimensions.width() && entityDimensions.height() > dimensions.height()) {
|
|
VoxelShape voxelShape2 = Shapes.create(AABB.ofSize(vec3, d, 1.0E-6, d));
|
|
Optional<Vec3> optional2 = this.level.findFreePosition(this, voxelShape2, vec3, entityDimensions.width(), dimensions.height(), entityDimensions.width());
|
|
if (optional2.isPresent()) {
|
|
this.setPos(((Vec3)optional2.get()).add(0.0, -dimensions.height() / 2.0 + 1.0E-6, 0.0));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the horizontal facing direction of this Entity.
|
|
*/
|
|
public Direction getDirection() {
|
|
return Direction.fromYRot(this.getYRot());
|
|
}
|
|
|
|
/**
|
|
* Gets the horizontal facing direction of this Entity, adjusted to take specially-treated entity types into account.
|
|
*/
|
|
public Direction getMotionDirection() {
|
|
return this.getDirection();
|
|
}
|
|
|
|
protected HoverEvent createHoverEvent() {
|
|
return new HoverEvent.ShowEntity(new HoverEvent.EntityTooltipInfo(this.getType(), this.getUUID(), this.getName()));
|
|
}
|
|
|
|
public boolean broadcastToPlayer(ServerPlayer player) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public final AABB getBoundingBox() {
|
|
return this.bb;
|
|
}
|
|
|
|
public final void setBoundingBox(AABB bb) {
|
|
this.bb = bb;
|
|
}
|
|
|
|
public final float getEyeHeight(Pose pose) {
|
|
return this.getDimensions(pose).eyeHeight();
|
|
}
|
|
|
|
public final float getEyeHeight() {
|
|
return this.eyeHeight;
|
|
}
|
|
|
|
public Vec3 getLeashOffset(float partialTick) {
|
|
return this.getLeashOffset();
|
|
}
|
|
|
|
protected Vec3 getLeashOffset() {
|
|
return new Vec3(0.0, this.getEyeHeight(), this.getBbWidth() * 0.4F);
|
|
}
|
|
|
|
public SlotAccess getSlot(int slot) {
|
|
return SlotAccess.NULL;
|
|
}
|
|
|
|
/**
|
|
* Get the world, if available. <b>{@code null} is not allowed!</b> If you are not an entity in the world, return the overworld
|
|
*/
|
|
public Level getCommandSenderWorld() {
|
|
return this.level();
|
|
}
|
|
|
|
/**
|
|
* Get the Minecraft server instance
|
|
*/
|
|
@Nullable
|
|
public MinecraftServer getServer() {
|
|
return this.level().getServer();
|
|
}
|
|
|
|
/**
|
|
* Applies the given player interaction to this Entity.
|
|
*/
|
|
public InteractionResult interactAt(Player player, Vec3 vec, InteractionHand hand) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
public boolean ignoreExplosion(Explosion explosion) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add the given player to the list of players tracking this entity. For instance, a player may track a boss in order to view its associated boss bar.
|
|
*/
|
|
public void startSeenByPlayer(ServerPlayer serverPlayer) {
|
|
}
|
|
|
|
/**
|
|
* Removes the given player from the list of players tracking this entity. See {@link Entity#addTrackingPlayer} for more information on tracking.
|
|
*/
|
|
public void stopSeenByPlayer(ServerPlayer serverPlayer) {
|
|
}
|
|
|
|
/**
|
|
* Transforms the entity's current yaw with the given Rotation and returns it. This does not have a side-effect.
|
|
*/
|
|
public float rotate(Rotation transformRotation) {
|
|
float f = Mth.wrapDegrees(this.getYRot());
|
|
switch (transformRotation) {
|
|
case CLOCKWISE_180:
|
|
return f + 180.0F;
|
|
case COUNTERCLOCKWISE_90:
|
|
return f + 270.0F;
|
|
case CLOCKWISE_90:
|
|
return f + 90.0F;
|
|
default:
|
|
return f;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transforms the entity's current yaw with the given Mirror and returns it. This does not have a side-effect.
|
|
*/
|
|
public float mirror(Mirror transformMirror) {
|
|
float f = Mth.wrapDegrees(this.getYRot());
|
|
switch (transformMirror) {
|
|
case FRONT_BACK:
|
|
return -f;
|
|
case LEFT_RIGHT:
|
|
return 180.0F - f;
|
|
default:
|
|
return f;
|
|
}
|
|
}
|
|
|
|
public ProjectileDeflection deflection(Projectile projectile) {
|
|
return this.getType().is(EntityTypeTags.DEFLECTS_PROJECTILES) ? ProjectileDeflection.REVERSE : ProjectileDeflection.NONE;
|
|
}
|
|
|
|
@Nullable
|
|
public LivingEntity getControllingPassenger() {
|
|
return null;
|
|
}
|
|
|
|
public final boolean hasControllingPassenger() {
|
|
return this.getControllingPassenger() != null;
|
|
}
|
|
|
|
public final List<Entity> getPassengers() {
|
|
return this.passengers;
|
|
}
|
|
|
|
@Nullable
|
|
public Entity getFirstPassenger() {
|
|
return this.passengers.isEmpty() ? null : (Entity)this.passengers.get(0);
|
|
}
|
|
|
|
public boolean hasPassenger(Entity entity) {
|
|
return this.passengers.contains(entity);
|
|
}
|
|
|
|
public boolean hasPassenger(Predicate<Entity> predicate) {
|
|
for (Entity entity : this.passengers) {
|
|
if (predicate.test(entity)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private Stream<Entity> getIndirectPassengersStream() {
|
|
return this.passengers.stream().flatMap(Entity::getSelfAndPassengers);
|
|
}
|
|
|
|
@Override
|
|
public Stream<Entity> getSelfAndPassengers() {
|
|
return Stream.concat(Stream.of(this), this.getIndirectPassengersStream());
|
|
}
|
|
|
|
@Override
|
|
public Stream<Entity> getPassengersAndSelf() {
|
|
return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this));
|
|
}
|
|
|
|
public Iterable<Entity> getIndirectPassengers() {
|
|
return () -> this.getIndirectPassengersStream().iterator();
|
|
}
|
|
|
|
public int countPlayerPassengers() {
|
|
return (int)this.getIndirectPassengersStream().filter(entity -> entity instanceof Player).count();
|
|
}
|
|
|
|
public boolean hasExactlyOnePlayerPassenger() {
|
|
return this.countPlayerPassengers() == 1;
|
|
}
|
|
|
|
public Entity getRootVehicle() {
|
|
Entity entity = this;
|
|
|
|
while (entity.isPassenger()) {
|
|
entity = entity.getVehicle();
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
public boolean isPassengerOfSameVehicle(Entity entity) {
|
|
return this.getRootVehicle() == entity.getRootVehicle();
|
|
}
|
|
|
|
public boolean hasIndirectPassenger(Entity entity) {
|
|
if (!entity.isPassenger()) {
|
|
return false;
|
|
} else {
|
|
Entity entity2 = entity.getVehicle();
|
|
return entity2 == this ? true : this.hasIndirectPassenger(entity2);
|
|
}
|
|
}
|
|
|
|
public final boolean isLocalInstanceAuthoritative() {
|
|
return this.level.isClientSide() ? this.isLocalClientAuthoritative() : !this.isClientAuthoritative();
|
|
}
|
|
|
|
protected boolean isLocalClientAuthoritative() {
|
|
LivingEntity livingEntity = this.getControllingPassenger();
|
|
return livingEntity != null && livingEntity.isLocalClientAuthoritative();
|
|
}
|
|
|
|
public boolean isClientAuthoritative() {
|
|
LivingEntity livingEntity = this.getControllingPassenger();
|
|
return livingEntity != null && livingEntity.isClientAuthoritative();
|
|
}
|
|
|
|
public boolean canSimulateMovement() {
|
|
return this.isLocalInstanceAuthoritative();
|
|
}
|
|
|
|
public boolean isEffectiveAi() {
|
|
return this.isLocalInstanceAuthoritative();
|
|
}
|
|
|
|
protected static Vec3 getCollisionHorizontalEscapeVector(double vehicleWidth, double passengerWidth, float yRot) {
|
|
double d = (vehicleWidth + passengerWidth + 1.0E-5F) / 2.0;
|
|
float f = -Mth.sin(yRot * (float) (Math.PI / 180.0));
|
|
float g = Mth.cos(yRot * (float) (Math.PI / 180.0));
|
|
float h = Math.max(Math.abs(f), Math.abs(g));
|
|
return new Vec3(f * d / h, 0.0, g * d / h);
|
|
}
|
|
|
|
public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
|
|
return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ());
|
|
}
|
|
|
|
/**
|
|
* Get entity this is riding
|
|
*/
|
|
@Nullable
|
|
public Entity getVehicle() {
|
|
return this.vehicle;
|
|
}
|
|
|
|
@Nullable
|
|
public Entity getControlledVehicle() {
|
|
return this.vehicle != null && this.vehicle.getControllingPassenger() == this ? this.vehicle : null;
|
|
}
|
|
|
|
public PushReaction getPistonPushReaction() {
|
|
return PushReaction.NORMAL;
|
|
}
|
|
|
|
public SoundSource getSoundSource() {
|
|
return SoundSource.NEUTRAL;
|
|
}
|
|
|
|
protected int getFireImmuneTicks() {
|
|
return 1;
|
|
}
|
|
|
|
public CommandSourceStack createCommandSourceStackForNameResolution(ServerLevel level) {
|
|
return new CommandSourceStack(
|
|
CommandSource.NULL, this.position(), this.getRotationVector(), level, 0, this.getName().getString(), this.getDisplayName(), level.getServer(), this
|
|
);
|
|
}
|
|
|
|
public void lookAt(Anchor anchor, Vec3 target) {
|
|
Vec3 vec3 = anchor.apply(this);
|
|
double d = target.x - vec3.x;
|
|
double e = target.y - vec3.y;
|
|
double f = target.z - vec3.z;
|
|
double g = Math.sqrt(d * d + f * f);
|
|
this.setXRot(Mth.wrapDegrees((float)(-(Mth.atan2(e, g) * 180.0F / (float)Math.PI))));
|
|
this.setYRot(Mth.wrapDegrees((float)(Mth.atan2(f, d) * 180.0F / (float)Math.PI) - 90.0F));
|
|
this.setYHeadRot(this.getYRot());
|
|
this.xRotO = this.getXRot();
|
|
this.yRotO = this.getYRot();
|
|
}
|
|
|
|
public float getPreciseBodyRotation(float partialTick) {
|
|
return Mth.lerp(partialTick, this.yRotO, this.yRot);
|
|
}
|
|
|
|
public boolean updateFluidHeightAndDoFluidPushing(TagKey<Fluid> fluidTag, double motionScale) {
|
|
if (this.touchingUnloadedChunk()) {
|
|
return false;
|
|
} else {
|
|
AABB aABB = this.getBoundingBox().deflate(0.001);
|
|
int i = Mth.floor(aABB.minX);
|
|
int j = Mth.ceil(aABB.maxX);
|
|
int k = Mth.floor(aABB.minY);
|
|
int l = Mth.ceil(aABB.maxY);
|
|
int m = Mth.floor(aABB.minZ);
|
|
int n = Mth.ceil(aABB.maxZ);
|
|
double d = 0.0;
|
|
boolean bl = this.isPushedByFluid();
|
|
boolean bl2 = false;
|
|
Vec3 vec3 = Vec3.ZERO;
|
|
int o = 0;
|
|
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
|
|
|
for (int p = i; p < j; p++) {
|
|
for (int q = k; q < l; q++) {
|
|
for (int r = m; r < n; r++) {
|
|
mutableBlockPos.set(p, q, r);
|
|
FluidState fluidState = this.level().getFluidState(mutableBlockPos);
|
|
if (fluidState.is(fluidTag)) {
|
|
double e = q + fluidState.getHeight(this.level(), mutableBlockPos);
|
|
if (e >= aABB.minY) {
|
|
bl2 = true;
|
|
d = Math.max(e - aABB.minY, d);
|
|
if (bl) {
|
|
Vec3 vec32 = fluidState.getFlow(this.level(), mutableBlockPos);
|
|
if (d < 0.4) {
|
|
vec32 = vec32.scale(d);
|
|
}
|
|
|
|
vec3 = vec3.add(vec32);
|
|
o++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vec3.length() > 0.0) {
|
|
if (o > 0) {
|
|
vec3 = vec3.scale(1.0 / o);
|
|
}
|
|
|
|
if (!(this instanceof Player)) {
|
|
vec3 = vec3.normalize();
|
|
}
|
|
|
|
Vec3 vec33 = this.getDeltaMovement();
|
|
vec3 = vec3.scale(motionScale);
|
|
double f = 0.003;
|
|
if (Math.abs(vec33.x) < 0.003 && Math.abs(vec33.z) < 0.003 && vec3.length() < 0.0045000000000000005) {
|
|
vec3 = vec3.normalize().scale(0.0045000000000000005);
|
|
}
|
|
|
|
this.setDeltaMovement(this.getDeltaMovement().add(vec3));
|
|
}
|
|
|
|
this.fluidHeight.put(fluidTag, d);
|
|
return bl2;
|
|
}
|
|
}
|
|
|
|
public boolean touchingUnloadedChunk() {
|
|
AABB aABB = this.getBoundingBox().inflate(1.0);
|
|
int i = Mth.floor(aABB.minX);
|
|
int j = Mth.ceil(aABB.maxX);
|
|
int k = Mth.floor(aABB.minZ);
|
|
int l = Mth.ceil(aABB.maxZ);
|
|
return !this.level().hasChunksAt(i, k, j, l);
|
|
}
|
|
|
|
public double getFluidHeight(TagKey<Fluid> fluidTag) {
|
|
return this.fluidHeight.getDouble(fluidTag);
|
|
}
|
|
|
|
public double getFluidJumpThreshold() {
|
|
return this.getEyeHeight() < 0.4 ? 0.0 : 0.4;
|
|
}
|
|
|
|
public final float getBbWidth() {
|
|
return this.dimensions.width();
|
|
}
|
|
|
|
public final float getBbHeight() {
|
|
return this.dimensions.height();
|
|
}
|
|
|
|
public Packet<ClientGamePacketListener> getAddEntityPacket(ServerEntity entity) {
|
|
return new ClientboundAddEntityPacket(this, entity);
|
|
}
|
|
|
|
public EntityDimensions getDimensions(Pose pose) {
|
|
return this.type.getDimensions();
|
|
}
|
|
|
|
public final EntityAttachments getAttachments() {
|
|
return this.dimensions.attachments();
|
|
}
|
|
|
|
public Vec3 position() {
|
|
return this.position;
|
|
}
|
|
|
|
public Vec3 trackingPosition() {
|
|
return this.position();
|
|
}
|
|
|
|
@Override
|
|
public BlockPos blockPosition() {
|
|
return this.blockPosition;
|
|
}
|
|
|
|
public BlockState getInBlockState() {
|
|
if (this.inBlockState == null) {
|
|
this.inBlockState = this.level().getBlockState(this.blockPosition());
|
|
}
|
|
|
|
return this.inBlockState;
|
|
}
|
|
|
|
public ChunkPos chunkPosition() {
|
|
return this.chunkPosition;
|
|
}
|
|
|
|
public Vec3 getDeltaMovement() {
|
|
return this.deltaMovement;
|
|
}
|
|
|
|
public void setDeltaMovement(Vec3 deltaMovement) {
|
|
this.deltaMovement = deltaMovement;
|
|
}
|
|
|
|
public void addDeltaMovement(Vec3 addend) {
|
|
this.setDeltaMovement(this.getDeltaMovement().add(addend));
|
|
}
|
|
|
|
public void setDeltaMovement(double x, double y, double z) {
|
|
this.setDeltaMovement(new Vec3(x, y, z));
|
|
}
|
|
|
|
public final int getBlockX() {
|
|
return this.blockPosition.getX();
|
|
}
|
|
|
|
public final double getX() {
|
|
return this.position.x;
|
|
}
|
|
|
|
public double getX(double scale) {
|
|
return this.position.x + this.getBbWidth() * scale;
|
|
}
|
|
|
|
public double getRandomX(double scale) {
|
|
return this.getX((2.0 * this.random.nextDouble() - 1.0) * scale);
|
|
}
|
|
|
|
public final int getBlockY() {
|
|
return this.blockPosition.getY();
|
|
}
|
|
|
|
public final double getY() {
|
|
return this.position.y;
|
|
}
|
|
|
|
public double getY(double scale) {
|
|
return this.position.y + this.getBbHeight() * scale;
|
|
}
|
|
|
|
public double getRandomY() {
|
|
return this.getY(this.random.nextDouble());
|
|
}
|
|
|
|
public double getEyeY() {
|
|
return this.position.y + this.eyeHeight;
|
|
}
|
|
|
|
public final int getBlockZ() {
|
|
return this.blockPosition.getZ();
|
|
}
|
|
|
|
public final double getZ() {
|
|
return this.position.z;
|
|
}
|
|
|
|
public double getZ(double scale) {
|
|
return this.position.z + this.getBbWidth() * scale;
|
|
}
|
|
|
|
public double getRandomZ(double scale) {
|
|
return this.getZ((2.0 * this.random.nextDouble() - 1.0) * scale);
|
|
}
|
|
|
|
/**
|
|
* Directly updates the {@link #posX}, {@link posY}, and {@link posZ} fields, without performing any collision checks, updating the bounding box position, or sending any packets. In general, this is not what you want and {@link #setPosition} is better, as that handles the bounding box.
|
|
*/
|
|
public final void setPosRaw(double x, double y, double z) {
|
|
if (this.position.x != x || this.position.y != y || this.position.z != z) {
|
|
this.position = new Vec3(x, y, z);
|
|
int i = Mth.floor(x);
|
|
int j = Mth.floor(y);
|
|
int k = Mth.floor(z);
|
|
if (i != this.blockPosition.getX() || j != this.blockPosition.getY() || k != this.blockPosition.getZ()) {
|
|
this.blockPosition = new BlockPos(i, j, k);
|
|
this.inBlockState = null;
|
|
if (SectionPos.blockToSectionCoord(i) != this.chunkPosition.x || SectionPos.blockToSectionCoord(k) != this.chunkPosition.z) {
|
|
this.chunkPosition = new ChunkPos(this.blockPosition);
|
|
}
|
|
}
|
|
|
|
this.levelCallback.onMove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes the entity despawn if requirements are reached
|
|
*/
|
|
public void checkDespawn() {
|
|
}
|
|
|
|
public Vec3 getRopeHoldPosition(float partialTicks) {
|
|
return this.getPosition(partialTicks).add(0.0, this.eyeHeight * 0.7, 0.0);
|
|
}
|
|
|
|
public void recreateFromPacket(ClientboundAddEntityPacket packet) {
|
|
int i = packet.getId();
|
|
double d = packet.getX();
|
|
double e = packet.getY();
|
|
double f = packet.getZ();
|
|
this.syncPacketPositionCodec(d, e, f);
|
|
this.snapTo(d, e, f, packet.getYRot(), packet.getXRot());
|
|
this.setId(i);
|
|
this.setUUID(packet.getUUID());
|
|
Vec3 vec3 = new Vec3(packet.getXa(), packet.getYa(), packet.getZa());
|
|
this.setDeltaMovement(vec3);
|
|
}
|
|
|
|
@Nullable
|
|
public ItemStack getPickResult() {
|
|
return null;
|
|
}
|
|
|
|
public void setIsInPowderSnow(boolean isInPowderSnow) {
|
|
this.isInPowderSnow = isInPowderSnow;
|
|
}
|
|
|
|
public boolean canFreeze() {
|
|
return !this.getType().is(EntityTypeTags.FREEZE_IMMUNE_ENTITY_TYPES);
|
|
}
|
|
|
|
public boolean isFreezing() {
|
|
return this.getTicksFrozen() > 0;
|
|
}
|
|
|
|
public float getYRot() {
|
|
return this.yRot;
|
|
}
|
|
|
|
public float getVisualRotationYInDegrees() {
|
|
return this.getYRot();
|
|
}
|
|
|
|
public void setYRot(float yRot) {
|
|
if (!Float.isFinite(yRot)) {
|
|
Util.logAndPauseIfInIde("Invalid entity rotation: " + yRot + ", discarding.");
|
|
} else {
|
|
this.yRot = yRot;
|
|
}
|
|
}
|
|
|
|
public float getXRot() {
|
|
return this.xRot;
|
|
}
|
|
|
|
public void setXRot(float xRot) {
|
|
if (!Float.isFinite(xRot)) {
|
|
Util.logAndPauseIfInIde("Invalid entity rotation: " + xRot + ", discarding.");
|
|
} else {
|
|
this.xRot = Math.clamp(xRot % 360.0F, -90.0F, 90.0F);
|
|
}
|
|
}
|
|
|
|
public boolean canSprint() {
|
|
return false;
|
|
}
|
|
|
|
public float maxUpStep() {
|
|
return 0.0F;
|
|
}
|
|
|
|
public void onExplosionHit(@Nullable Entity entity) {
|
|
}
|
|
|
|
@Override
|
|
public final boolean isRemoved() {
|
|
return this.removalReason != null;
|
|
}
|
|
|
|
@Nullable
|
|
public Entity.RemovalReason getRemovalReason() {
|
|
return this.removalReason;
|
|
}
|
|
|
|
@Override
|
|
public final void setRemoved(Entity.RemovalReason removalReason) {
|
|
if (this.removalReason == null) {
|
|
this.removalReason = removalReason;
|
|
}
|
|
|
|
if (this.removalReason.shouldDestroy()) {
|
|
this.stopRiding();
|
|
}
|
|
|
|
this.getPassengers().forEach(Entity::stopRiding);
|
|
this.levelCallback.onRemove(removalReason);
|
|
this.onRemoval(removalReason);
|
|
}
|
|
|
|
protected void unsetRemoved() {
|
|
this.removalReason = null;
|
|
}
|
|
|
|
@Override
|
|
public void setLevelCallback(EntityInLevelCallback levelCallback) {
|
|
this.levelCallback = levelCallback;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldBeSaved() {
|
|
if (this.removalReason != null && !this.removalReason.shouldSave()) {
|
|
return false;
|
|
} else {
|
|
return this.isPassenger() ? false : !this.isVehicle() || !this.hasExactlyOnePlayerPassenger();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isAlwaysTicking() {
|
|
return false;
|
|
}
|
|
|
|
public boolean mayInteract(ServerLevel level, BlockPos pos) {
|
|
return true;
|
|
}
|
|
|
|
public Level level() {
|
|
return this.level;
|
|
}
|
|
|
|
protected void setLevel(Level level) {
|
|
this.level = level;
|
|
}
|
|
|
|
public DamageSources damageSources() {
|
|
return this.level().damageSources();
|
|
}
|
|
|
|
public RegistryAccess registryAccess() {
|
|
return this.level().registryAccess();
|
|
}
|
|
|
|
protected void lerpPositionAndRotationStep(int steps, double targetX, double targetY, double targetZ, double targetYRot, double targetXRot) {
|
|
double d = 1.0 / steps;
|
|
double e = Mth.lerp(d, this.getX(), targetX);
|
|
double f = Mth.lerp(d, this.getY(), targetY);
|
|
double g = Mth.lerp(d, this.getZ(), targetZ);
|
|
float h = (float)Mth.rotLerp(d, (double)this.getYRot(), targetYRot);
|
|
float i = (float)Mth.lerp(d, (double)this.getXRot(), targetXRot);
|
|
this.setPos(e, f, g);
|
|
this.setRot(h, i);
|
|
}
|
|
|
|
public RandomSource getRandom() {
|
|
return this.random;
|
|
}
|
|
|
|
public Vec3 getKnownMovement() {
|
|
return this.getControllingPassenger() instanceof Player player && this.isAlive() ? player.getKnownMovement() : this.getDeltaMovement();
|
|
}
|
|
|
|
@Nullable
|
|
public ItemStack getWeaponItem() {
|
|
return null;
|
|
}
|
|
|
|
public Optional<ResourceKey<LootTable>> getLootTable() {
|
|
return this.type.getDefaultLootTable();
|
|
}
|
|
|
|
protected void applyImplicitComponents(DataComponentGetter componentGetter) {
|
|
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.CUSTOM_NAME);
|
|
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.CUSTOM_DATA);
|
|
}
|
|
|
|
public final void applyComponentsFromItemStack(ItemStack stack) {
|
|
this.applyImplicitComponents(stack.getComponents());
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public <T> T get(DataComponentType<? extends T> component) {
|
|
if (component == DataComponents.CUSTOM_NAME) {
|
|
return castComponentValue((DataComponentType<T>)component, this.getCustomName());
|
|
} else {
|
|
return component == DataComponents.CUSTOM_DATA ? castComponentValue((DataComponentType<T>)component, this.customData) : null;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Contract("_,!null->!null;_,_->_")
|
|
protected static <T> T castComponentValue(DataComponentType<T> componentType, @Nullable Object value) {
|
|
return (T)value;
|
|
}
|
|
|
|
public <T> void setComponent(DataComponentType<T> component, T value) {
|
|
this.applyImplicitComponent(component, value);
|
|
}
|
|
|
|
protected <T> boolean applyImplicitComponent(DataComponentType<T> component, T value) {
|
|
if (component == DataComponents.CUSTOM_NAME) {
|
|
this.setCustomName(castComponentValue(DataComponents.CUSTOM_NAME, value));
|
|
return true;
|
|
} else if (component == DataComponents.CUSTOM_DATA) {
|
|
this.customData = castComponentValue(DataComponents.CUSTOM_DATA, value);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected <T> boolean applyImplicitComponentIfPresent(DataComponentGetter componentGetter, DataComponentType<T> component) {
|
|
T object = componentGetter.get(component);
|
|
return object != null ? this.applyImplicitComponent(component, object) : false;
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface MoveFunction {
|
|
void accept(Entity entity, double d, double e, double f);
|
|
}
|
|
|
|
record Movement(Vec3 from, Vec3 to) {
|
|
}
|
|
|
|
public static enum MovementEmission {
|
|
NONE(false, false),
|
|
SOUNDS(true, false),
|
|
EVENTS(false, true),
|
|
ALL(true, true);
|
|
|
|
final boolean sounds;
|
|
final boolean events;
|
|
|
|
private MovementEmission(final boolean sounds, final boolean events) {
|
|
this.sounds = sounds;
|
|
this.events = events;
|
|
}
|
|
|
|
public boolean emitsAnything() {
|
|
return this.events || this.sounds;
|
|
}
|
|
|
|
public boolean emitsEvents() {
|
|
return this.events;
|
|
}
|
|
|
|
public boolean emitsSounds() {
|
|
return this.sounds;
|
|
}
|
|
}
|
|
|
|
public static enum RemovalReason {
|
|
KILLED(true, false),
|
|
DISCARDED(true, false),
|
|
UNLOADED_TO_CHUNK(false, true),
|
|
UNLOADED_WITH_PLAYER(false, false),
|
|
CHANGED_DIMENSION(false, false);
|
|
|
|
private final boolean destroy;
|
|
private final boolean save;
|
|
|
|
private RemovalReason(final boolean destroy, final boolean save) {
|
|
this.destroy = destroy;
|
|
this.save = save;
|
|
}
|
|
|
|
public boolean shouldDestroy() {
|
|
return this.destroy;
|
|
}
|
|
|
|
public boolean shouldSave() {
|
|
return this.save;
|
|
}
|
|
}
|
|
}
|