package net.minecraft.world.entity.monster; import java.util.Collection; import javax.annotation.Nullable; import net.minecraft.nbt.CompoundTag; 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.tags.ItemTags; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.AreaEffectCloud; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LightningBolt; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.SwellGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.animal.Cat; import net.minecraft.world.entity.animal.Ocelot; import net.minecraft.world.entity.animal.goat.Goat; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.gameevent.GameEvent; public class Creeper extends Monster { private static final EntityDataAccessor DATA_SWELL_DIR = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_IS_POWERED = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor DATA_IS_IGNITED = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.BOOLEAN); private static final boolean DEFAULT_IGNITED = false; private static final boolean DEFAULT_POWERED = false; private static final short DEFAULT_MAX_SWELL = 30; private static final byte DEFAULT_EXPLOSION_RADIUS = 3; private int oldSwell; private int swell; private int maxSwell = 30; private int explosionRadius = 3; private int droppedSkulls; public Creeper(EntityType p_32278_, Level p_32279_) { super(p_32278_, p_32279_); } @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); this.goalSelector.addGoal(2, new SwellGoal(this)); this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0, 1.2)); this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0, 1.2)); this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, false)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this)); } public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.25); } @Override public int getMaxFallDistance() { return this.getTarget() == null ? this.getComfortableFallDistance(0.0F) : this.getComfortableFallDistance(this.getHealth() - 1.0F); } @Override public boolean causeFallDamage(double p_396975_, float p_149687_, DamageSource p_149689_) { boolean flag = super.causeFallDamage(p_396975_, p_149687_, p_149689_); this.swell += (int)(p_396975_ * 1.5); if (this.swell > this.maxSwell - 5) { this.swell = this.maxSwell - 5; } return flag; } @Override protected void defineSynchedData(SynchedEntityData.Builder p_330760_) { super.defineSynchedData(p_330760_); p_330760_.define(DATA_SWELL_DIR, -1); p_330760_.define(DATA_IS_POWERED, false); p_330760_.define(DATA_IS_IGNITED, false); } @Override public void addAdditionalSaveData(CompoundTag p_32304_) { super.addAdditionalSaveData(p_32304_); p_32304_.putBoolean("powered", this.isPowered()); p_32304_.putShort("Fuse", (short)this.maxSwell); p_32304_.putByte("ExplosionRadius", (byte)this.explosionRadius); p_32304_.putBoolean("ignited", this.isIgnited()); } @Override public void readAdditionalSaveData(CompoundTag p_32296_) { super.readAdditionalSaveData(p_32296_); this.entityData.set(DATA_IS_POWERED, p_32296_.getBooleanOr("powered", false)); this.maxSwell = p_32296_.getShortOr("Fuse", (short)30); this.explosionRadius = p_32296_.getByteOr("ExplosionRadius", (byte)3); if (p_32296_.getBooleanOr("ignited", false)) { this.ignite(); } } @Override public void tick() { if (this.isAlive()) { this.oldSwell = this.swell; if (this.isIgnited()) { this.setSwellDir(1); } int i = this.getSwellDir(); if (i > 0 && this.swell == 0) { this.playSound(SoundEvents.CREEPER_PRIMED, 1.0F, 0.5F); this.gameEvent(GameEvent.PRIME_FUSE); } this.swell += i; if (this.swell < 0) { this.swell = 0; } if (this.swell >= this.maxSwell) { this.swell = this.maxSwell; this.explodeCreeper(); } } super.tick(); } @Override public void setTarget(@Nullable LivingEntity p_149691_) { if (!(p_149691_ instanceof Goat)) { super.setTarget(p_149691_); } } @Override protected SoundEvent getHurtSound(DamageSource p_32309_) { return SoundEvents.CREEPER_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.CREEPER_DEATH; } @Override protected void dropCustomDeathLoot(ServerLevel p_342918_, DamageSource p_32292_, boolean p_32294_) { super.dropCustomDeathLoot(p_342918_, p_32292_, p_32294_); Entity entity = p_32292_.getEntity(); if (entity != this && entity instanceof Creeper creeper && creeper.canDropMobsSkull()) { creeper.increaseDroppedSkulls(); this.spawnAtLocation(p_342918_, Items.CREEPER_HEAD); } } @Override public boolean doHurtTarget(ServerLevel p_365514_, Entity p_32281_) { return true; } public boolean isPowered() { return this.entityData.get(DATA_IS_POWERED); } public float getSwelling(float p_32321_) { return Mth.lerp(p_32321_, this.oldSwell, this.swell) / (this.maxSwell - 2); } public int getSwellDir() { return this.entityData.get(DATA_SWELL_DIR); } public void setSwellDir(int p_32284_) { this.entityData.set(DATA_SWELL_DIR, p_32284_); } @Override public void thunderHit(ServerLevel p_32286_, LightningBolt p_32287_) { super.thunderHit(p_32286_, p_32287_); this.entityData.set(DATA_IS_POWERED, true); } @Override protected InteractionResult mobInteract(Player p_32301_, InteractionHand p_32302_) { ItemStack itemstack = p_32301_.getItemInHand(p_32302_); if (itemstack.is(ItemTags.CREEPER_IGNITERS)) { SoundEvent soundevent = itemstack.is(Items.FIRE_CHARGE) ? SoundEvents.FIRECHARGE_USE : SoundEvents.FLINTANDSTEEL_USE; this.level() .playSound(p_32301_, this.getX(), this.getY(), this.getZ(), soundevent, this.getSoundSource(), 1.0F, this.random.nextFloat() * 0.4F + 0.8F); if (!this.level().isClientSide) { this.ignite(); if (!itemstack.isDamageableItem()) { itemstack.shrink(1); } else { itemstack.hurtAndBreak(1, p_32301_, getSlotForHand(p_32302_)); } } return InteractionResult.SUCCESS; } else { return super.mobInteract(p_32301_, p_32302_); } } private void explodeCreeper() { if (this.level() instanceof ServerLevel serverlevel) { float f = this.isPowered() ? 2.0F : 1.0F; this.dead = true; serverlevel.explode(this, this.getX(), this.getY(), this.getZ(), this.explosionRadius * f, Level.ExplosionInteraction.MOB); this.spawnLingeringCloud(); this.triggerOnDeathMobEffects(serverlevel, Entity.RemovalReason.KILLED); this.discard(); } } private void spawnLingeringCloud() { Collection collection = this.getActiveEffects(); if (!collection.isEmpty()) { AreaEffectCloud areaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); areaeffectcloud.setRadius(2.5F); areaeffectcloud.setRadiusOnUse(-0.5F); areaeffectcloud.setWaitTime(10); areaeffectcloud.setDuration(300); areaeffectcloud.setPotionDurationScale(0.25F); areaeffectcloud.setRadiusPerTick(-areaeffectcloud.getRadius() / areaeffectcloud.getDuration()); for (MobEffectInstance mobeffectinstance : collection) { areaeffectcloud.addEffect(new MobEffectInstance(mobeffectinstance)); } this.level().addFreshEntity(areaeffectcloud); } } public boolean isIgnited() { return this.entityData.get(DATA_IS_IGNITED); } public void ignite() { this.entityData.set(DATA_IS_IGNITED, true); } public boolean canDropMobsSkull() { return this.isPowered() && this.droppedSkulls < 1; } public void increaseDroppedSkulls() { this.droppedSkulls++; } }