package net.minecraft.world.entity.monster; import java.time.LocalDate; import java.time.temporal.ChronoField; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.util.RandomSource; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; 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.PathfinderMob; 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.FleeSunGoal; 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.RangedBowAttackGoal; import net.minecraft.world.entity.ai.goal.RestrictSunGoal; 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.IronGolem; import net.minecraft.world.entity.animal.Turtle; import net.minecraft.world.entity.animal.wolf.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.entity.projectile.ProjectileUtil; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.ProjectileWeaponItem; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; public abstract class AbstractSkeleton extends Monster implements RangedAttackMob { private static final int HARD_ATTACK_INTERVAL = 20; private static final int NORMAL_ATTACK_INTERVAL = 40; private final RangedBowAttackGoal bowGoal = new RangedBowAttackGoal<>(this, 1.0, 20, 15.0F); private final MeleeAttackGoal meleeGoal = new MeleeAttackGoal(this, 1.2, false) { @Override public void stop() { super.stop(); AbstractSkeleton.this.setAggressive(false); } @Override public void start() { super.start(); AbstractSkeleton.this.setAggressive(true); } }; protected AbstractSkeleton(EntityType entityType, Level level) { super(entityType, level); this.reassessWeaponGoal(); } @Override protected void registerGoals() { this.goalSelector.addGoal(2, new RestrictSunGoal(this)); this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0)); this.goalSelector.addGoal(3, new AvoidEntityGoal(this, Wolf.class, 6.0F, 1.0, 1.2)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, IronGolem.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); } public static Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.25); } @Override protected void playStepSound(BlockPos pos, BlockState state) { this.playSound(this.getStepSound(), 0.15F, 1.0F); } abstract SoundEvent getStepSound(); @Override public void aiStep() { boolean bl = this.isSunBurnTick(); if (bl) { ItemStack itemStack = this.getItemBySlot(EquipmentSlot.HEAD); if (!itemStack.isEmpty()) { if (itemStack.isDamageableItem()) { Item item = itemStack.getItem(); itemStack.setDamageValue(itemStack.getDamageValue() + this.random.nextInt(2)); if (itemStack.getDamageValue() >= itemStack.getMaxDamage()) { this.onEquippedItemBroken(item, EquipmentSlot.HEAD); this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); } } bl = false; } if (bl) { this.igniteForSeconds(8.0F); } } super.aiStep(); } @Override public void rideTick() { super.rideTick(); if (this.getControlledVehicle() instanceof PathfinderMob pathfinderMob) { this.yBodyRot = pathfinderMob.yBodyRot; } } @Override protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) { super.populateDefaultEquipmentSlots(random, difficulty); this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); } @Nullable @Override public SpawnGroupData finalizeSpawn( ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData ) { spawnGroupData = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); RandomSource randomSource = level.getRandom(); this.populateDefaultEquipmentSlots(randomSource, difficulty); this.populateDefaultEquipmentEnchantments(level, randomSource, difficulty); this.reassessWeaponGoal(); this.setCanPickUpLoot(randomSource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { LocalDate localDate = LocalDate.now(); int i = localDate.get(ChronoField.DAY_OF_MONTH); int j = localDate.get(ChronoField.MONTH_OF_YEAR); if (j == 10 && i == 31 && randomSource.nextFloat() < 0.25F) { this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomSource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); this.setDropChance(EquipmentSlot.HEAD, 0.0F); } } return spawnGroupData; } /** * Sets this entity's combat AI. */ public void reassessWeaponGoal() { if (this.level() != null && !this.level().isClientSide) { this.goalSelector.removeGoal(this.meleeGoal); this.goalSelector.removeGoal(this.bowGoal); ItemStack itemStack = this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, Items.BOW)); if (itemStack.is(Items.BOW)) { int i = this.getHardAttackInterval(); if (this.level().getDifficulty() != Difficulty.HARD) { i = this.getAttackInterval(); } this.bowGoal.setMinAttackInterval(i); this.goalSelector.addGoal(4, this.bowGoal); } else { this.goalSelector.addGoal(4, this.meleeGoal); } } } protected int getHardAttackInterval() { return 20; } protected int getAttackInterval() { return 40; } @Override public void performRangedAttack(LivingEntity target, float velocity) { ItemStack itemStack = this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, Items.BOW)); ItemStack itemStack2 = this.getProjectile(itemStack); AbstractArrow abstractArrow = this.getArrow(itemStack2, velocity, itemStack); double d = target.getX() - this.getX(); double e = target.getY(0.3333333333333333) - abstractArrow.getY(); double f = target.getZ() - this.getZ(); double g = Math.sqrt(d * d + f * f); if (this.level() instanceof ServerLevel serverLevel) { Projectile.spawnProjectileUsingShoot(abstractArrow, serverLevel, itemStack2, d, e + g * 0.2F, f, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4); } this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); } protected AbstractArrow getArrow(ItemStack arrow, float velocity, @Nullable ItemStack weapon) { return ProjectileUtil.getMobArrow(this, arrow, velocity, weapon); } @Override public boolean canFireProjectileWeapon(ProjectileWeaponItem projectileWeapon) { return projectileWeapon == Items.BOW; } @Override public TagKey getPreferredWeaponType() { return ItemTags.SKELETON_PREFERRED_WEAPONS; } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.reassessWeaponGoal(); } @Override public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem) { super.onEquipItem(slot, oldItem, newItem); if (!this.level().isClientSide) { this.reassessWeaponGoal(); } } public boolean isShaking() { return this.isFullyFrozen(); } }