2025-11-24 22:52:51 +03:00

558 lines
20 KiB
Java

/*
* 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<Integer> 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<? extends Slime> type, Level level) {
super((EntityType<? extends Mob>)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<Player>(this, Player.class, 10, true, false, (target, level) -> Math.abs(target.getY() - this.getY()) <= 4.0));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<IronGolem>((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<? extends Slime> 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<Slime> 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);
}
}
}
}