490 lines
14 KiB
Java
490 lines
14 KiB
Java
package net.minecraft.world.entity.item;
|
|
|
|
import java.util.Objects;
|
|
import java.util.UUID;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.network.syncher.SynchedEntityData.Builder;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.stats.Stats;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.tags.ItemTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.MoverType;
|
|
import net.minecraft.world.entity.SlotAccess;
|
|
import net.minecraft.world.entity.TraceableEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.Explosion;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.portal.TeleportTransition;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class ItemEntity extends Entity implements TraceableEntity {
|
|
private static final EntityDataAccessor<ItemStack> DATA_ITEM = SynchedEntityData.defineId(ItemEntity.class, EntityDataSerializers.ITEM_STACK);
|
|
private static final float FLOAT_HEIGHT = 0.1F;
|
|
public static final float EYE_HEIGHT = 0.2125F;
|
|
private static final int LIFETIME = 6000;
|
|
private static final int INFINITE_PICKUP_DELAY = 32767;
|
|
private static final int INFINITE_LIFETIME = -32768;
|
|
private int age;
|
|
private int pickupDelay;
|
|
private int health = 5;
|
|
@Nullable
|
|
private UUID thrower;
|
|
@Nullable
|
|
private Entity cachedThrower;
|
|
@Nullable
|
|
private UUID target;
|
|
public final float bobOffs;
|
|
|
|
public ItemEntity(EntityType<? extends ItemEntity> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.bobOffs = this.random.nextFloat() * (float) Math.PI * 2.0F;
|
|
this.setYRot(this.random.nextFloat() * 360.0F);
|
|
}
|
|
|
|
public ItemEntity(Level level, double posX, double posY, double posZ, ItemStack itemStack) {
|
|
this(level, posX, posY, posZ, itemStack, level.random.nextDouble() * 0.2 - 0.1, 0.2, level.random.nextDouble() * 0.2 - 0.1);
|
|
}
|
|
|
|
public ItemEntity(Level level, double posX, double posY, double posZ, ItemStack itemStack, double deltaX, double deltaY, double deltaZ) {
|
|
this(EntityType.ITEM, level);
|
|
this.setPos(posX, posY, posZ);
|
|
this.setDeltaMovement(deltaX, deltaY, deltaZ);
|
|
this.setItem(itemStack);
|
|
}
|
|
|
|
private ItemEntity(ItemEntity other) {
|
|
super(other.getType(), other.level());
|
|
this.setItem(other.getItem().copy());
|
|
this.copyPosition(other);
|
|
this.age = other.age;
|
|
this.bobOffs = other.bobOffs;
|
|
}
|
|
|
|
@Override
|
|
public boolean dampensVibrations() {
|
|
return this.getItem().is(ItemTags.DAMPENS_VIBRATIONS);
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Entity getOwner() {
|
|
if (this.cachedThrower != null && !this.cachedThrower.isRemoved()) {
|
|
return this.cachedThrower;
|
|
} else if (this.thrower != null && this.level() instanceof ServerLevel serverLevel) {
|
|
this.cachedThrower = serverLevel.getEntity(this.thrower);
|
|
return this.cachedThrower;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void restoreFrom(Entity entity) {
|
|
super.restoreFrom(entity);
|
|
if (entity instanceof ItemEntity itemEntity) {
|
|
this.cachedThrower = itemEntity.cachedThrower;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected Entity.MovementEmission getMovementEmission() {
|
|
return Entity.MovementEmission.NONE;
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(Builder builder) {
|
|
builder.define(DATA_ITEM, ItemStack.EMPTY);
|
|
}
|
|
|
|
@Override
|
|
protected double getDefaultGravity() {
|
|
return 0.04;
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (this.getItem().isEmpty()) {
|
|
this.discard();
|
|
} else {
|
|
super.tick();
|
|
if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
|
|
this.pickupDelay--;
|
|
}
|
|
|
|
this.xo = this.getX();
|
|
this.yo = this.getY();
|
|
this.zo = this.getZ();
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
if (this.isInWater() && this.getFluidHeight(FluidTags.WATER) > 0.1F) {
|
|
this.setUnderwaterMovement();
|
|
} else if (this.isInLava() && this.getFluidHeight(FluidTags.LAVA) > 0.1F) {
|
|
this.setUnderLavaMovement();
|
|
} else {
|
|
this.applyGravity();
|
|
}
|
|
|
|
if (this.level().isClientSide) {
|
|
this.noPhysics = false;
|
|
} else {
|
|
this.noPhysics = !this.level().noCollision(this, this.getBoundingBox().deflate(1.0E-7));
|
|
if (this.noPhysics) {
|
|
this.moveTowardsClosestSpace(this.getX(), (this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0, this.getZ());
|
|
}
|
|
}
|
|
|
|
if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-5F || (this.tickCount + this.getId()) % 4 == 0) {
|
|
this.move(MoverType.SELF, this.getDeltaMovement());
|
|
this.applyEffectsFromBlocks();
|
|
float f = 0.98F;
|
|
if (this.onGround()) {
|
|
f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F;
|
|
}
|
|
|
|
this.setDeltaMovement(this.getDeltaMovement().multiply(f, 0.98, f));
|
|
if (this.onGround()) {
|
|
Vec3 vec32 = this.getDeltaMovement();
|
|
if (vec32.y < 0.0) {
|
|
this.setDeltaMovement(vec32.multiply(1.0, -0.5, 1.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
boolean bl = Mth.floor(this.xo) != Mth.floor(this.getX()) || Mth.floor(this.yo) != Mth.floor(this.getY()) || Mth.floor(this.zo) != Mth.floor(this.getZ());
|
|
int i = bl ? 2 : 40;
|
|
if (this.tickCount % i == 0 && !this.level().isClientSide && this.isMergable()) {
|
|
this.mergeWithNeighbours();
|
|
}
|
|
|
|
if (this.age != -32768) {
|
|
this.age++;
|
|
}
|
|
|
|
this.hasImpulse = this.hasImpulse | this.updateInWaterStateAndDoFluidPushing();
|
|
if (!this.level().isClientSide) {
|
|
double d = this.getDeltaMovement().subtract(vec3).lengthSqr();
|
|
if (d > 0.01) {
|
|
this.hasImpulse = true;
|
|
}
|
|
}
|
|
|
|
if (!this.level().isClientSide && this.age >= 6000) {
|
|
this.discard();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public BlockPos getBlockPosBelowThatAffectsMyMovement() {
|
|
return this.getOnPos(0.999999F);
|
|
}
|
|
|
|
private void setUnderwaterMovement() {
|
|
this.setFluidMovement(0.99F);
|
|
}
|
|
|
|
private void setUnderLavaMovement() {
|
|
this.setFluidMovement(0.95F);
|
|
}
|
|
|
|
private void setFluidMovement(double multiplier) {
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
this.setDeltaMovement(vec3.x * multiplier, vec3.y + (vec3.y < 0.06F ? 5.0E-4F : 0.0F), vec3.z * multiplier);
|
|
}
|
|
|
|
/**
|
|
* Looks for other itemstacks nearby and tries to stack them together
|
|
*/
|
|
private void mergeWithNeighbours() {
|
|
if (this.isMergable()) {
|
|
for (ItemEntity itemEntity : this.level()
|
|
.getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.5, 0.0, 0.5), itemEntityx -> itemEntityx != this && itemEntityx.isMergable())) {
|
|
if (itemEntity.isMergable()) {
|
|
this.tryToMerge(itemEntity);
|
|
if (this.isRemoved()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean isMergable() {
|
|
ItemStack itemStack = this.getItem();
|
|
return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemStack.getCount() < itemStack.getMaxStackSize();
|
|
}
|
|
|
|
private void tryToMerge(ItemEntity itemEntity) {
|
|
ItemStack itemStack = this.getItem();
|
|
ItemStack itemStack2 = itemEntity.getItem();
|
|
if (Objects.equals(this.target, itemEntity.target) && areMergable(itemStack, itemStack2)) {
|
|
if (itemStack2.getCount() < itemStack.getCount()) {
|
|
merge(this, itemStack, itemEntity, itemStack2);
|
|
} else {
|
|
merge(itemEntity, itemStack2, this, itemStack);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static boolean areMergable(ItemStack destinationStack, ItemStack originStack) {
|
|
return originStack.getCount() + destinationStack.getCount() > originStack.getMaxStackSize()
|
|
? false
|
|
: ItemStack.isSameItemSameComponents(destinationStack, originStack);
|
|
}
|
|
|
|
public static ItemStack merge(ItemStack destinationStack, ItemStack originStack, int amount) {
|
|
int i = Math.min(Math.min(destinationStack.getMaxStackSize(), amount) - destinationStack.getCount(), originStack.getCount());
|
|
ItemStack itemStack = destinationStack.copyWithCount(destinationStack.getCount() + i);
|
|
originStack.shrink(i);
|
|
return itemStack;
|
|
}
|
|
|
|
private static void merge(ItemEntity destinationEntity, ItemStack destinationStack, ItemStack originStack) {
|
|
ItemStack itemStack = merge(destinationStack, originStack, 64);
|
|
destinationEntity.setItem(itemStack);
|
|
}
|
|
|
|
private static void merge(ItemEntity destinationEntity, ItemStack destinationStack, ItemEntity originEntity, ItemStack originStack) {
|
|
merge(destinationEntity, destinationStack, originStack);
|
|
destinationEntity.pickupDelay = Math.max(destinationEntity.pickupDelay, originEntity.pickupDelay);
|
|
destinationEntity.age = Math.min(destinationEntity.age, originEntity.age);
|
|
if (originStack.isEmpty()) {
|
|
originEntity.discard();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean fireImmune() {
|
|
return !this.getItem().canBeHurtBy(this.damageSources().inFire()) || super.fireImmune();
|
|
}
|
|
|
|
@Override
|
|
protected boolean shouldPlayLavaHurtSound() {
|
|
return this.health <= 0 ? true : this.tickCount % 10 == 0;
|
|
}
|
|
|
|
@Override
|
|
public final boolean hurtClient(DamageSource damageSource) {
|
|
return this.isInvulnerableToBase(damageSource) ? false : this.getItem().canBeHurtBy(damageSource);
|
|
}
|
|
|
|
@Override
|
|
public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
|
|
if (this.isInvulnerableToBase(damageSource)) {
|
|
return false;
|
|
} else if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && damageSource.getEntity() instanceof Mob) {
|
|
return false;
|
|
} else if (!this.getItem().canBeHurtBy(damageSource)) {
|
|
return false;
|
|
} else {
|
|
this.markHurt();
|
|
this.health = (int)(this.health - amount);
|
|
this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
|
|
if (this.health <= 0) {
|
|
this.getItem().onDestroyed(this);
|
|
this.discard();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean ignoreExplosion(Explosion explosion) {
|
|
return explosion.shouldAffectBlocklikeEntities() ? super.ignoreExplosion(explosion) : true;
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag tag) {
|
|
tag.putShort("Health", (short)this.health);
|
|
tag.putShort("Age", (short)this.age);
|
|
tag.putShort("PickupDelay", (short)this.pickupDelay);
|
|
if (this.thrower != null) {
|
|
tag.putUUID("Thrower", this.thrower);
|
|
}
|
|
|
|
if (this.target != null) {
|
|
tag.putUUID("Owner", this.target);
|
|
}
|
|
|
|
if (!this.getItem().isEmpty()) {
|
|
tag.put("Item", this.getItem().save(this.registryAccess()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag tag) {
|
|
this.health = tag.getShort("Health");
|
|
this.age = tag.getShort("Age");
|
|
if (tag.contains("PickupDelay")) {
|
|
this.pickupDelay = tag.getShort("PickupDelay");
|
|
}
|
|
|
|
if (tag.hasUUID("Owner")) {
|
|
this.target = tag.getUUID("Owner");
|
|
}
|
|
|
|
if (tag.hasUUID("Thrower")) {
|
|
this.thrower = tag.getUUID("Thrower");
|
|
this.cachedThrower = null;
|
|
}
|
|
|
|
if (tag.contains("Item", 10)) {
|
|
CompoundTag compoundTag = tag.getCompound("Item");
|
|
this.setItem((ItemStack)ItemStack.parse(this.registryAccess(), compoundTag).orElse(ItemStack.EMPTY));
|
|
} else {
|
|
this.setItem(ItemStack.EMPTY);
|
|
}
|
|
|
|
if (this.getItem().isEmpty()) {
|
|
this.discard();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void playerTouch(Player player) {
|
|
if (!this.level().isClientSide) {
|
|
ItemStack itemStack = this.getItem();
|
|
Item item = itemStack.getItem();
|
|
int i = itemStack.getCount();
|
|
if (this.pickupDelay == 0 && (this.target == null || this.target.equals(player.getUUID())) && player.getInventory().add(itemStack)) {
|
|
player.take(this, i);
|
|
if (itemStack.isEmpty()) {
|
|
this.discard();
|
|
itemStack.setCount(i);
|
|
}
|
|
|
|
player.awardStat(Stats.ITEM_PICKED_UP.get(item), i);
|
|
player.onItemPickup(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Component getName() {
|
|
Component component = this.getCustomName();
|
|
return component != null ? component : this.getItem().getItemName();
|
|
}
|
|
|
|
@Override
|
|
public boolean isAttackable() {
|
|
return false;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Entity teleport(TeleportTransition teleportTransition) {
|
|
Entity entity = super.teleport(teleportTransition);
|
|
if (!this.level().isClientSide && entity instanceof ItemEntity itemEntity) {
|
|
itemEntity.mergeWithNeighbours();
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
/**
|
|
* Gets the item that this entity represents.
|
|
*/
|
|
public ItemStack getItem() {
|
|
return this.getEntityData().get(DATA_ITEM);
|
|
}
|
|
|
|
/**
|
|
* Sets the item that this entity represents.
|
|
*/
|
|
public void setItem(ItemStack stack) {
|
|
this.getEntityData().set(DATA_ITEM, stack);
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
if (DATA_ITEM.equals(dataAccessor)) {
|
|
this.getItem().setEntityRepresentation(this);
|
|
}
|
|
}
|
|
|
|
public void setTarget(@Nullable UUID target) {
|
|
this.target = target;
|
|
}
|
|
|
|
public void setThrower(Entity thrower) {
|
|
this.thrower = thrower.getUUID();
|
|
this.cachedThrower = thrower;
|
|
}
|
|
|
|
public int getAge() {
|
|
return this.age;
|
|
}
|
|
|
|
public void setDefaultPickUpDelay() {
|
|
this.pickupDelay = 10;
|
|
}
|
|
|
|
public void setNoPickUpDelay() {
|
|
this.pickupDelay = 0;
|
|
}
|
|
|
|
public void setNeverPickUp() {
|
|
this.pickupDelay = 32767;
|
|
}
|
|
|
|
public void setPickUpDelay(int pickupDelay) {
|
|
this.pickupDelay = pickupDelay;
|
|
}
|
|
|
|
public boolean hasPickUpDelay() {
|
|
return this.pickupDelay > 0;
|
|
}
|
|
|
|
public void setUnlimitedLifetime() {
|
|
this.age = -32768;
|
|
}
|
|
|
|
public void setExtendedLifetime() {
|
|
this.age = -6000;
|
|
}
|
|
|
|
public void makeFakeItem() {
|
|
this.setNeverPickUp();
|
|
this.age = 5999;
|
|
}
|
|
|
|
public static float getSpin(float age, float bobOffset) {
|
|
return age / 20.0F + bobOffset;
|
|
}
|
|
|
|
public ItemEntity copy() {
|
|
return new ItemEntity(this);
|
|
}
|
|
|
|
@Override
|
|
public SoundSource getSoundSource() {
|
|
return SoundSource.AMBIENT;
|
|
}
|
|
|
|
@Override
|
|
public float getVisualRotationYInDegrees() {
|
|
return 180.0F - getSpin(this.getAge() + 0.5F, this.bobOffs) / (float) (Math.PI * 2) * 360.0F;
|
|
}
|
|
|
|
@Override
|
|
public SlotAccess getSlot(int slot) {
|
|
return slot == 0 ? SlotAccess.of(this::getItem, this::setItem) : super.getSlot(slot);
|
|
}
|
|
}
|