package net.minecraft.world.entity.monster; import java.util.EnumSet; import java.util.function.Predicate; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.util.RandomSource; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; import net.minecraft.world.entity.ai.goal.BreakDoorGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; import net.minecraft.world.entity.ai.goal.RandomStrollGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.ai.navigation.GroundPathNavigation; import net.minecraft.world.entity.ai.util.GoalUtils; import net.minecraft.world.entity.animal.IronGolem; import net.minecraft.world.entity.monster.creaking.Creaking; import net.minecraft.world.entity.npc.AbstractVillager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.raid.Raid; import net.minecraft.world.entity.raid.Raider; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.providers.EnchantmentProvider; import net.minecraft.world.item.enchantment.providers.VanillaEnchantmentProviders; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import org.jetbrains.annotations.Nullable; public class Vindicator extends AbstractIllager { private static final String TAG_JOHNNY = "Johnny"; static final Predicate DOOR_BREAKING_PREDICATE = difficulty -> difficulty == Difficulty.NORMAL || difficulty == Difficulty.HARD; boolean isJohnny; public Vindicator(EntityType entityType, Level level) { super(entityType, level); } @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Creaking.class, 8.0F, 1.0, 1.2)); this.goalSelector.addGoal(2, new Vindicator.VindicatorBreakDoorGoal(this)); this.goalSelector.addGoal(3, new AbstractIllager.RaiderOpenDoorGoal(this)); this.goalSelector.addGoal(4, new Raider.HoldGroundAttackGoal(this, 10.0F)); this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, false)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, AbstractVillager.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, IronGolem.class, true)); this.targetSelector.addGoal(4, new Vindicator.VindicatorJohnnyAttackGoal(this)); this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); } @Override protected void customServerAiStep(ServerLevel serverLevel) { if (!this.isNoAi() && GoalUtils.hasGroundPathNavigation(this)) { boolean bl = serverLevel.isRaided(this.blockPosition()); ((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(bl); } super.customServerAiStep(serverLevel); } public static Builder createAttributes() { return Monster.createMonsterAttributes() .add(Attributes.MOVEMENT_SPEED, 0.35F) .add(Attributes.FOLLOW_RANGE, 12.0) .add(Attributes.MAX_HEALTH, 24.0) .add(Attributes.ATTACK_DAMAGE, 5.0); } @Override public void addAdditionalSaveData(CompoundTag compound) { super.addAdditionalSaveData(compound); if (this.isJohnny) { compound.putBoolean("Johnny", true); } } @Override public AbstractIllager.IllagerArmPose getArmPose() { if (this.isAggressive()) { return AbstractIllager.IllagerArmPose.ATTACKING; } else { return this.isCelebrating() ? AbstractIllager.IllagerArmPose.CELEBRATING : AbstractIllager.IllagerArmPose.CROSSED; } } @Override public void readAdditionalSaveData(CompoundTag compound) { super.readAdditionalSaveData(compound); if (compound.contains("Johnny", 99)) { this.isJohnny = compound.getBoolean("Johnny"); } } @Override public SoundEvent getCelebrateSound() { return SoundEvents.VINDICATOR_CELEBRATE; } @Nullable @Override public SpawnGroupData finalizeSpawn( ServerLevelAccessor serverLevelAccessor, DifficultyInstance difficultyInstance, EntitySpawnReason entitySpawnReason, @Nullable SpawnGroupData spawnGroupData ) { SpawnGroupData spawnGroupData2 = super.finalizeSpawn(serverLevelAccessor, difficultyInstance, entitySpawnReason, spawnGroupData); ((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(true); RandomSource randomSource = serverLevelAccessor.getRandom(); this.populateDefaultEquipmentSlots(randomSource, difficultyInstance); this.populateDefaultEquipmentEnchantments(serverLevelAccessor, randomSource, difficultyInstance); return spawnGroupData2; } @Override protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) { if (this.getCurrentRaid() == null) { this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_AXE)); } } @Override public void setCustomName(@Nullable Component name) { super.setCustomName(name); if (!this.isJohnny && name != null && name.getString().equals("Johnny")) { this.isJohnny = true; } } @Override protected SoundEvent getAmbientSound() { return SoundEvents.VINDICATOR_AMBIENT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.VINDICATOR_DEATH; } @Override protected SoundEvent getHurtSound(DamageSource damageSource) { return SoundEvents.VINDICATOR_HURT; } @Override public void applyRaidBuffs(ServerLevel level, int wave, boolean unused) { ItemStack itemStack = new ItemStack(Items.IRON_AXE); Raid raid = this.getCurrentRaid(); boolean bl = this.random.nextFloat() <= raid.getEnchantOdds(); if (bl) { ResourceKey resourceKey = wave > raid.getNumGroups(Difficulty.NORMAL) ? VanillaEnchantmentProviders.RAID_VINDICATOR_POST_WAVE_5 : VanillaEnchantmentProviders.RAID_VINDICATOR; EnchantmentHelper.enchantItemFromProvider(itemStack, level.registryAccess(), resourceKey, level.getCurrentDifficultyAt(this.blockPosition()), this.random); } this.setItemSlot(EquipmentSlot.MAINHAND, itemStack); } static class VindicatorBreakDoorGoal extends BreakDoorGoal { public VindicatorBreakDoorGoal(Mob mob) { super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canContinueToUse() { Vindicator vindicator = (Vindicator)this.mob; return vindicator.hasActiveRaid() && super.canContinueToUse(); } @Override public boolean canUse() { Vindicator vindicator = (Vindicator)this.mob; return vindicator.hasActiveRaid() && vindicator.random.nextInt(reducedTickDelay(10)) == 0 && super.canUse(); } @Override public void start() { super.start(); this.mob.setNoActionTime(0); } } static class VindicatorJohnnyAttackGoal extends NearestAttackableTargetGoal { public VindicatorJohnnyAttackGoal(Vindicator vindicator) { super(vindicator, LivingEntity.class, 0, true, true, (livingEntity, serverLevel) -> livingEntity.attackable()); } @Override public boolean canUse() { return ((Vindicator)this.mob).isJohnny && super.canUse(); } @Override public void start() { super.start(); this.mob.setNoActionTime(0); } } }