package net.minecraft.world.entity; import java.util.List; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundAddExperienceOrbPacket; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.FluidTags; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.EnchantedItemInUse; import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.Level; import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; public class ExperienceOrb extends Entity { private static final int LIFETIME = 6000; private static final int ENTITY_SCAN_PERIOD = 20; private static final int MAX_FOLLOW_DIST = 8; private static final int ORB_GROUPS_PER_AREA = 40; private static final double ORB_MERGE_DISTANCE = 0.5; private int age; private int health = 5; private int value; private int count = 1; private Player followingPlayer; public ExperienceOrb(Level p_20776_, double p_20777_, double p_20778_, double p_20779_, int p_20780_) { this(EntityType.EXPERIENCE_ORB, p_20776_); this.setPos(p_20777_, p_20778_, p_20779_); this.setYRot((float)(this.random.nextDouble() * 360.0)); this.setDeltaMovement((this.random.nextDouble() * 0.2F - 0.1F) * 2.0, this.random.nextDouble() * 0.2 * 2.0, (this.random.nextDouble() * 0.2F - 0.1F) * 2.0); this.value = p_20780_; } public ExperienceOrb(EntityType p_20773_, Level p_20774_) { super(p_20773_, p_20774_); } @Override protected Entity.MovementEmission getMovementEmission() { return Entity.MovementEmission.NONE; } @Override protected void defineSynchedData(SynchedEntityData.Builder p_329424_) { } @Override protected double getDefaultGravity() { return 0.03; } @Override public void tick() { super.tick(); this.xo = this.getX(); this.yo = this.getY(); this.zo = this.getZ(); if (this.isEyeInFluid(FluidTags.WATER)) { this.setUnderwaterMovement(); } else { this.applyGravity(); } if (this.level().getFluidState(this.blockPosition()).is(FluidTags.LAVA)) { this.setDeltaMovement( (double)((this.random.nextFloat() - this.random.nextFloat()) * 0.2F), 0.2F, (double)((this.random.nextFloat() - this.random.nextFloat()) * 0.2F) ); } if (!this.level().noCollision(this.getBoundingBox())) { this.moveTowardsClosestSpace(this.getX(), (this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0, this.getZ()); } if (this.tickCount % 20 == 1) { this.scanForEntities(); } if (this.followingPlayer != null && (this.followingPlayer.isSpectator() || this.followingPlayer.isDeadOrDying())) { this.followingPlayer = null; } if (this.followingPlayer != null) { Vec3 vec3 = new Vec3( this.followingPlayer.getX() - this.getX(), this.followingPlayer.getY() + (double)this.followingPlayer.getEyeHeight() / 2.0 - this.getY(), this.followingPlayer.getZ() - this.getZ() ); double d0 = vec3.lengthSqr(); if (d0 < 64.0) { double d1 = 1.0 - Math.sqrt(d0) / 8.0; this.setDeltaMovement(this.getDeltaMovement().add(vec3.normalize().scale(d1 * d1 * 0.1))); } } double d2 = this.getDeltaMovement().y; 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((double)f, 0.98, (double)f)); if (this.onGround()) { this.setDeltaMovement(new Vec3(this.getDeltaMovement().x, -d2 * 0.4, this.getDeltaMovement().z)); } this.age++; if (this.age >= 6000) { this.discard(); } } @Override public BlockPos getBlockPosBelowThatAffectsMyMovement() { return this.getOnPos(0.999999F); } private void scanForEntities() { if (this.followingPlayer == null || this.followingPlayer.distanceToSqr(this) > 64.0) { this.followingPlayer = this.level().getNearestPlayer(this, 8.0); } if (this.level() instanceof ServerLevel) { for (ExperienceOrb experienceorb : this.level() .getEntities(EntityTypeTest.forClass(ExperienceOrb.class), this.getBoundingBox().inflate(0.5), this::canMerge)) { this.merge(experienceorb); } } } public static void award(ServerLevel p_147083_, Vec3 p_147084_, int p_147085_) { while (p_147085_ > 0) { int i = getExperienceValue(p_147085_); p_147085_ -= i; if (!tryMergeToExisting(p_147083_, p_147084_, i)) { p_147083_.addFreshEntity(new ExperienceOrb(p_147083_, p_147084_.x(), p_147084_.y(), p_147084_.z(), i)); } } } private static boolean tryMergeToExisting(ServerLevel p_147097_, Vec3 p_147098_, int p_147099_) { AABB aabb = AABB.ofSize(p_147098_, 1.0, 1.0, 1.0); int i = p_147097_.getRandom().nextInt(40); List list = p_147097_.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), aabb, p_147081_ -> canMerge(p_147081_, i, p_147099_)); if (!list.isEmpty()) { ExperienceOrb experienceorb = list.get(0); experienceorb.count++; experienceorb.age = 0; return true; } else { return false; } } private boolean canMerge(ExperienceOrb p_147087_) { return p_147087_ != this && canMerge(p_147087_, this.getId(), this.value); } private static boolean canMerge(ExperienceOrb p_147089_, int p_147090_, int p_147091_) { return !p_147089_.isRemoved() && (p_147089_.getId() - p_147090_) % 40 == 0 && p_147089_.value == p_147091_; } private void merge(ExperienceOrb p_147101_) { this.count = this.count + p_147101_.count; this.age = Math.min(this.age, p_147101_.age); p_147101_.discard(); } private void setUnderwaterMovement() { Vec3 vec3 = this.getDeltaMovement(); this.setDeltaMovement(vec3.x * 0.99F, Math.min(vec3.y + 5.0E-4F, 0.06F), vec3.z * 0.99F); } @Override protected void doWaterSplashEffect() { } @Override public final boolean hurtClient(DamageSource p_369585_) { return !this.isInvulnerableToBase(p_369585_); } @Override public final boolean hurtServer(ServerLevel p_365476_, DamageSource p_362340_, float p_369855_) { if (this.isInvulnerableToBase(p_362340_)) { return false; } else { this.markHurt(); this.health = (int)((float)this.health - p_369855_); if (this.health <= 0) { this.discard(); } return true; } } @Override public void addAdditionalSaveData(CompoundTag p_20796_) { p_20796_.putShort("Health", (short)this.health); p_20796_.putShort("Age", (short)this.age); p_20796_.putShort("Value", (short)this.value); p_20796_.putInt("Count", this.count); } @Override public void readAdditionalSaveData(CompoundTag p_20788_) { this.health = p_20788_.getShort("Health"); this.age = p_20788_.getShort("Age"); this.value = p_20788_.getShort("Value"); this.count = Math.max(p_20788_.getInt("Count"), 1); } @Override public void playerTouch(Player p_20792_) { if (p_20792_ instanceof ServerPlayer serverplayer) { if (p_20792_.takeXpDelay == 0) { p_20792_.takeXpDelay = 2; p_20792_.take(this, 1); int i = this.repairPlayerItems(serverplayer, this.value); if (i > 0) { p_20792_.giveExperiencePoints(i); } this.count--; if (this.count == 0) { this.discard(); } } } } private int repairPlayerItems(ServerPlayer p_343572_, int p_147094_) { Optional optional = EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, p_343572_, ItemStack::isDamaged); if (optional.isPresent()) { ItemStack itemstack = optional.get().itemStack(); int i = EnchantmentHelper.modifyDurabilityToRepairFromXp(p_343572_.serverLevel(), itemstack, p_147094_); int j = Math.min(i, itemstack.getDamageValue()); itemstack.setDamageValue(itemstack.getDamageValue() - j); if (j > 0) { int k = p_147094_ - j * p_147094_ / i; if (k > 0) { return this.repairPlayerItems(p_343572_, k); } } return 0; } else { return p_147094_; } } public int getValue() { return this.value; } public int getIcon() { if (this.value >= 2477) { return 10; } else if (this.value >= 1237) { return 9; } else if (this.value >= 617) { return 8; } else if (this.value >= 307) { return 7; } else if (this.value >= 149) { return 6; } else if (this.value >= 73) { return 5; } else if (this.value >= 37) { return 4; } else if (this.value >= 17) { return 3; } else if (this.value >= 7) { return 2; } else { return this.value >= 3 ? 1 : 0; } } public static int getExperienceValue(int p_20783_) { if (p_20783_ >= 2477) { return 2477; } else if (p_20783_ >= 1237) { return 1237; } else if (p_20783_ >= 617) { return 617; } else if (p_20783_ >= 307) { return 307; } else if (p_20783_ >= 149) { return 149; } else if (p_20783_ >= 73) { return 73; } else if (p_20783_ >= 37) { return 37; } else if (p_20783_ >= 17) { return 17; } else if (p_20783_ >= 7) { return 7; } else { return p_20783_ >= 3 ? 3 : 1; } } @Override public boolean isAttackable() { return false; } @Override public Packet getAddEntityPacket(ServerEntity p_342735_) { return new ClientboundAddExperienceOrbPacket(this, p_342735_); } @Override public SoundSource getSoundSource() { return SoundSource.AMBIENT; } }