/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.annotations.VisibleForTesting * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.monster; import com.google.common.annotations.VisibleForTesting; import java.util.EnumSet; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.BiomeTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.ConversionParams; import net.minecraft.world.entity.ConversionType; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.animal.IronGolem; import net.minecraft.world.entity.monster.Enemy; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.PlayerTeam; import org.jspecify.annotations.Nullable; public class Slime extends Mob implements Enemy { private static final EntityDataAccessor ID_SIZE = SynchedEntityData.defineId(Slime.class, EntityDataSerializers.INT); public static final int MIN_SIZE = 1; public static final int MAX_SIZE = 127; public static final int MAX_NATURAL_SIZE = 4; private static final boolean DEFAULT_WAS_ON_GROUND = false; public float targetSquish; public float squish; public float oSquish; private boolean wasOnGround = false; public Slime(EntityType type, Level level) { super((EntityType)type, level); this.fixupDimensions(); this.moveControl = new SlimeMoveControl(this); } @Override protected void registerGoals() { this.goalSelector.addGoal(1, new SlimeFloatGoal(this)); this.goalSelector.addGoal(2, new SlimeAttackGoal(this)); this.goalSelector.addGoal(3, new SlimeRandomDirectionGoal(this)); this.goalSelector.addGoal(5, new SlimeKeepOnJumpingGoal(this)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal(this, Player.class, 10, true, false, (target, level) -> Math.abs(target.getY() - this.getY()) <= 4.0)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal((Mob)this, IronGolem.class, true)); } @Override public SoundSource getSoundSource() { return SoundSource.HOSTILE; } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(ID_SIZE, 1); } @VisibleForTesting public void setSize(int size, boolean updateHealth) { int actualSize = Mth.clamp(size, 1, 127); this.entityData.set(ID_SIZE, actualSize); this.reapplyPosition(); this.refreshDimensions(); this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(actualSize * actualSize); this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.2f + 0.1f * (float)actualSize); this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(actualSize); if (updateHealth) { this.setHealth(this.getMaxHealth()); } this.xpReward = actualSize; } public int getSize() { return this.entityData.get(ID_SIZE); } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.putInt("Size", this.getSize() - 1); output.putBoolean("wasOnGround", this.wasOnGround); } @Override protected void readAdditionalSaveData(ValueInput input) { this.setSize(input.getIntOr("Size", 0) + 1, false); super.readAdditionalSaveData(input); this.wasOnGround = input.getBooleanOr("wasOnGround", false); } public boolean isTiny() { return this.getSize() <= 1; } protected ParticleOptions getParticleType() { return ParticleTypes.ITEM_SLIME; } @Override public void tick() { this.oSquish = this.squish; this.squish += (this.targetSquish - this.squish) * 0.5f; super.tick(); if (this.onGround() && !this.wasOnGround) { float size = this.getDimensions(this.getPose()).width() * 2.0f; float radius = size / 2.0f; int i = 0; while ((float)i < size * 16.0f) { float dir = this.random.nextFloat() * ((float)Math.PI * 2); float d = this.random.nextFloat() * 0.5f + 0.5f; float xd = Mth.sin(dir) * radius * d; float zd = Mth.cos(dir) * radius * d; this.level().addParticle(this.getParticleType(), this.getX() + (double)xd, this.getY(), this.getZ() + (double)zd, 0.0, 0.0, 0.0); ++i; } this.playSound(this.getSquishSound(), this.getSoundVolume(), ((this.random.nextFloat() - this.random.nextFloat()) * 0.2f + 1.0f) / 0.8f); this.targetSquish = -0.5f; } else if (!this.onGround() && this.wasOnGround) { this.targetSquish = 1.0f; } this.wasOnGround = this.onGround(); this.decreaseSquish(); } protected void decreaseSquish() { this.targetSquish *= 0.6f; } protected int getJumpDelay() { return this.random.nextInt(20) + 10; } @Override public void refreshDimensions() { double oldX = this.getX(); double oldY = this.getY(); double oldZ = this.getZ(); super.refreshDimensions(); this.setPos(oldX, oldY, oldZ); } @Override public void onSyncedDataUpdated(EntityDataAccessor accessor) { if (ID_SIZE.equals(accessor)) { this.refreshDimensions(); this.setYRot(this.yHeadRot); this.yBodyRot = this.yHeadRot; if (this.isInWater() && this.random.nextInt(20) == 0) { this.doWaterSplashEffect(); } } super.onSyncedDataUpdated(accessor); } public EntityType getType() { return super.getType(); } @Override public void remove(Entity.RemovalReason reason) { int size = this.getSize(); if (!this.level().isClientSide() && size > 1 && this.isDeadOrDying()) { float width = this.getDimensions(this.getPose()).width(); float xzSlimeSpawnOffset = width / 2.0f; int halfSize = size / 2; int count = 2 + this.random.nextInt(3); PlayerTeam team = this.getTeam(); for (int i = 0; i < count; ++i) { float xd = ((float)(i % 2) - 0.5f) * xzSlimeSpawnOffset; float zd = ((float)(i / 2) - 0.5f) * xzSlimeSpawnOffset; this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, team), EntitySpawnReason.TRIGGERED, slime -> { slime.setSize(halfSize, true); slime.snapTo(this.getX() + (double)xd, this.getY() + 0.5, this.getZ() + (double)zd, this.random.nextFloat() * 360.0f, 0.0f); }); } } super.remove(reason); } @Override public void push(Entity entity) { super.push(entity); if (entity instanceof IronGolem && this.isDealsDamage()) { this.dealDamage((LivingEntity)entity); } } @Override public void playerTouch(Player player) { if (this.isDealsDamage()) { this.dealDamage(player); } } protected void dealDamage(LivingEntity target) { Level level = this.level(); if (level instanceof ServerLevel) { DamageSource damageSource; ServerLevel level2 = (ServerLevel)level; if (this.isAlive() && this.isWithinMeleeAttackRange(target) && this.hasLineOfSight(target) && target.hurtServer(level2, damageSource = this.damageSources().mobAttack(this), this.getAttackDamage())) { this.playSound(SoundEvents.SLIME_ATTACK, 1.0f, (this.random.nextFloat() - this.random.nextFloat()) * 0.2f + 1.0f); EnchantmentHelper.doPostAttackEffects(level2, target, damageSource); } } } @Override protected Vec3 getPassengerAttachmentPoint(Entity passenger, EntityDimensions dimensions, float scale) { return new Vec3(0.0, (double)dimensions.height() - 0.015625 * (double)this.getSize() * (double)scale, 0.0); } protected boolean isDealsDamage() { return !this.isTiny() && this.isEffectiveAi(); } protected float getAttackDamage() { return (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE); } @Override protected SoundEvent getHurtSound(DamageSource source) { if (this.isTiny()) { return SoundEvents.SLIME_HURT_SMALL; } return SoundEvents.SLIME_HURT; } @Override protected SoundEvent getDeathSound() { if (this.isTiny()) { return SoundEvents.SLIME_DEATH_SMALL; } return SoundEvents.SLIME_DEATH; } protected SoundEvent getSquishSound() { if (this.isTiny()) { return SoundEvents.SLIME_SQUISH_SMALL; } return SoundEvents.SLIME_SQUISH; } public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { if (level.getDifficulty() != Difficulty.PEACEFUL) { boolean slimeChunk; if (EntitySpawnReason.isSpawner(spawnReason)) { return Slime.checkMobSpawnRules(type, level, spawnReason, pos, random); } if (level.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70) { float surfaceSlimeSpawnChance = level.environmentAttributes().getValue(EnvironmentAttributes.SURFACE_SLIME_SPAWN_CHANCE, pos).floatValue(); if (random.nextFloat() < surfaceSlimeSpawnChance && level.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { return Slime.checkMobSpawnRules(type, level, spawnReason, pos, random); } } if (!(level instanceof WorldGenLevel)) { return false; } ChunkPos chunkPos = new ChunkPos(pos); boolean bl = slimeChunk = WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel)level).getSeed(), 987234911L).nextInt(10) == 0; if (random.nextInt(10) == 0 && slimeChunk && pos.getY() < 40) { return Slime.checkMobSpawnRules(type, level, spawnReason, pos, random); } } return false; } @Override protected float getSoundVolume() { return 0.4f * (float)this.getSize(); } @Override public int getMaxHeadXRot() { return 0; } protected boolean doPlayJumpSound() { return this.getSize() > 0; } @Override public void jumpFromGround() { Vec3 movement = this.getDeltaMovement(); this.setDeltaMovement(movement.x, this.getJumpPower(), movement.z); this.needsSync = true; } @Override public @Nullable SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData) { RandomSource random = level.getRandom(); int sizeScale = random.nextInt(3); if (sizeScale < 2 && random.nextFloat() < 0.5f * difficulty.getSpecialMultiplier()) { ++sizeScale; } int size = 1 << sizeScale; this.setSize(size, true); return super.finalizeSpawn(level, difficulty, spawnReason, groupData); } private float getSoundPitch() { float pitchAdjuster = this.isTiny() ? 1.4f : 0.8f; return ((this.random.nextFloat() - this.random.nextFloat()) * 0.2f + 1.0f) * pitchAdjuster; } protected SoundEvent getJumpSound() { return this.isTiny() ? SoundEvents.SLIME_JUMP_SMALL : SoundEvents.SLIME_JUMP; } @Override public EntityDimensions getDefaultDimensions(Pose pose) { return super.getDefaultDimensions(pose).scale(this.getSize()); } private static class SlimeMoveControl extends MoveControl { private float yRot; private int jumpDelay; private final Slime slime; private boolean isAggressive; public SlimeMoveControl(Slime slime) { super(slime); this.slime = slime; this.yRot = 180.0f * slime.getYRot() / (float)Math.PI; } public void setDirection(float yRot, boolean isAggressive) { this.yRot = yRot; this.isAggressive = isAggressive; } public void setWantedMovement(double speedModifier) { this.speedModifier = speedModifier; this.operation = MoveControl.Operation.MOVE_TO; } @Override public void tick() { this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0f)); this.mob.yHeadRot = this.mob.getYRot(); this.mob.yBodyRot = this.mob.getYRot(); if (this.operation != MoveControl.Operation.MOVE_TO) { this.mob.setZza(0.0f); return; } this.operation = MoveControl.Operation.WAIT; if (this.mob.onGround()) { this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); if (this.jumpDelay-- <= 0) { this.jumpDelay = this.slime.getJumpDelay(); if (this.isAggressive) { this.jumpDelay /= 3; } this.slime.getJumpControl().jump(); if (this.slime.doPlayJumpSound()) { this.slime.playSound(this.slime.getJumpSound(), this.slime.getSoundVolume(), this.slime.getSoundPitch()); } } else { this.slime.xxa = 0.0f; this.slime.zza = 0.0f; this.mob.setSpeed(0.0f); } } else { this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); } } } private static class SlimeFloatGoal extends Goal { private final Slime slime; public SlimeFloatGoal(Slime mob) { this.slime = mob; this.setFlags(EnumSet.of(Goal.Flag.JUMP, Goal.Flag.MOVE)); mob.getNavigation().setCanFloat(true); } @Override public boolean canUse() { return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof SlimeMoveControl; } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { MoveControl moveControl; if (this.slime.getRandom().nextFloat() < 0.8f) { this.slime.getJumpControl().jump(); } if ((moveControl = this.slime.getMoveControl()) instanceof SlimeMoveControl) { SlimeMoveControl slimeMoveControl = (SlimeMoveControl)moveControl; slimeMoveControl.setWantedMovement(1.2); } } } private static class SlimeAttackGoal extends Goal { private final Slime slime; private int growTiredTimer; public SlimeAttackGoal(Slime slime) { this.slime = slime; this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } @Override public boolean canUse() { LivingEntity target = this.slime.getTarget(); if (target == null) { return false; } if (!this.slime.canAttack(target)) { return false; } return this.slime.getMoveControl() instanceof SlimeMoveControl; } @Override public void start() { this.growTiredTimer = SlimeAttackGoal.reducedTickDelay(300); super.start(); } @Override public boolean canContinueToUse() { LivingEntity target = this.slime.getTarget(); if (target == null) { return false; } if (!this.slime.canAttack(target)) { return false; } return --this.growTiredTimer > 0; } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { MoveControl moveControl; LivingEntity target = this.slime.getTarget(); if (target != null) { this.slime.lookAt(target, 10.0f, 10.0f); } if ((moveControl = this.slime.getMoveControl()) instanceof SlimeMoveControl) { SlimeMoveControl slimeMoveControl = (SlimeMoveControl)moveControl; slimeMoveControl.setDirection(this.slime.getYRot(), this.slime.isDealsDamage()); } } } private static class SlimeRandomDirectionGoal extends Goal { private final Slime slime; private float chosenDegrees; private int nextRandomizeTime; public SlimeRandomDirectionGoal(Slime slime) { this.slime = slime; this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } @Override public boolean canUse() { return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof SlimeMoveControl; } @Override public void tick() { MoveControl moveControl; if (--this.nextRandomizeTime <= 0) { this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60)); this.chosenDegrees = this.slime.getRandom().nextInt(360); } if ((moveControl = this.slime.getMoveControl()) instanceof SlimeMoveControl) { SlimeMoveControl slimeMoveControl = (SlimeMoveControl)moveControl; slimeMoveControl.setDirection(this.chosenDegrees, false); } } } private static class SlimeKeepOnJumpingGoal extends Goal { private final Slime slime; public SlimeKeepOnJumpingGoal(Slime mob) { this.slime = mob; this.setFlags(EnumSet.of(Goal.Flag.JUMP, Goal.Flag.MOVE)); } @Override public boolean canUse() { return !this.slime.isPassenger(); } @Override public void tick() { MoveControl moveControl = this.slime.getMoveControl(); if (moveControl instanceof SlimeMoveControl) { SlimeMoveControl slimeMoveControl = (SlimeMoveControl)moveControl; slimeMoveControl.setWantedMovement(1.0); } } } }