package net.minecraft.world.entity.monster; import java.util.EnumSet; import java.util.List; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.util.RandomSource; 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.SpawnGroupData; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.Goal.Flag; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.raid.Raid; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public abstract class PatrollingMonster extends Monster { private static final boolean DEFAULT_PATROL_LEADER = false; private static final boolean DEFAULT_PATROLLING = false; @Nullable private BlockPos patrolTarget; private boolean patrolLeader = false; private boolean patrolling = false; protected PatrollingMonster(EntityType entityType, Level level) { super(entityType, level); } @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(4, new PatrollingMonster.LongDistancePatrolGoal<>(this, 0.7, 0.595)); } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); tag.storeNullable("patrol_target", BlockPos.CODEC, this.patrolTarget); tag.putBoolean("PatrolLeader", this.patrolLeader); tag.putBoolean("Patrolling", this.patrolling); } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.patrolTarget = (BlockPos)tag.read("patrol_target", BlockPos.CODEC).orElse(null); this.patrolLeader = tag.getBooleanOr("PatrolLeader", false); this.patrolling = tag.getBooleanOr("Patrolling", false); } public boolean canBeLeader() { return true; } @Nullable @Override public SpawnGroupData finalizeSpawn( ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData ) { if (spawnReason != EntitySpawnReason.PATROL && spawnReason != EntitySpawnReason.EVENT && spawnReason != EntitySpawnReason.STRUCTURE && level.getRandom().nextFloat() < 0.06F && this.canBeLeader()) { this.patrolLeader = true; } if (this.isPatrolLeader()) { this.setItemSlot(EquipmentSlot.HEAD, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN))); this.setDropChance(EquipmentSlot.HEAD, 2.0F); } if (spawnReason == EntitySpawnReason.PATROL) { this.patrolling = true; } return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } public static boolean checkPatrollingMonsterSpawnRules( EntityType entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource randomSource ) { return level.getBrightness(LightLayer.BLOCK, pos) > 8 ? false : checkAnyLightMonsterSpawnRules(entityType, level, spawnReason, pos, randomSource); } @Override public boolean removeWhenFarAway(double distanceToClosestPlayer) { return !this.patrolling || distanceToClosestPlayer > 16384.0; } public void setPatrolTarget(BlockPos patrolTarget) { this.patrolTarget = patrolTarget; this.patrolling = true; } public BlockPos getPatrolTarget() { return this.patrolTarget; } public boolean hasPatrolTarget() { return this.patrolTarget != null; } public void setPatrolLeader(boolean patrolLeader) { this.patrolLeader = patrolLeader; this.patrolling = true; } public boolean isPatrolLeader() { return this.patrolLeader; } public boolean canJoinPatrol() { return true; } public void findPatrolTarget() { this.patrolTarget = this.blockPosition().offset(-500 + this.random.nextInt(1000), 0, -500 + this.random.nextInt(1000)); this.patrolling = true; } protected boolean isPatrolling() { return this.patrolling; } protected void setPatrolling(boolean patrolling) { this.patrolling = patrolling; } public static class LongDistancePatrolGoal extends Goal { private static final int NAVIGATION_FAILED_COOLDOWN = 200; private final T mob; private final double speedModifier; private final double leaderSpeedModifier; private long cooldownUntil; public LongDistancePatrolGoal(T mob, double speedModifier, double leaderSpeedModifier) { this.mob = mob; this.speedModifier = speedModifier; this.leaderSpeedModifier = leaderSpeedModifier; this.cooldownUntil = -1L; this.setFlags(EnumSet.of(Flag.MOVE)); } @Override public boolean canUse() { boolean bl = this.mob.level().getGameTime() < this.cooldownUntil; return this.mob.isPatrolling() && this.mob.getTarget() == null && !this.mob.hasControllingPassenger() && this.mob.hasPatrolTarget() && !bl; } @Override public void start() { } @Override public void stop() { } @Override public void tick() { boolean bl = this.mob.isPatrolLeader(); PathNavigation pathNavigation = this.mob.getNavigation(); if (pathNavigation.isDone()) { List list = this.findPatrolCompanions(); if (this.mob.isPatrolling() && list.isEmpty()) { this.mob.setPatrolling(false); } else if (bl && this.mob.getPatrolTarget().closerToCenterThan(this.mob.position(), 10.0)) { this.mob.findPatrolTarget(); } else { Vec3 vec3 = Vec3.atBottomCenterOf(this.mob.getPatrolTarget()); Vec3 vec32 = this.mob.position(); Vec3 vec33 = vec32.subtract(vec3); vec3 = vec33.yRot(90.0F).scale(0.4).add(vec3); Vec3 vec34 = vec3.subtract(vec32).normalize().scale(10.0).add(vec32); BlockPos blockPos = BlockPos.containing(vec34); blockPos = this.mob.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos); if (!pathNavigation.moveTo(blockPos.getX(), blockPos.getY(), blockPos.getZ(), bl ? this.leaderSpeedModifier : this.speedModifier)) { this.moveRandomly(); this.cooldownUntil = this.mob.level().getGameTime() + 200L; } else if (bl) { for (PatrollingMonster patrollingMonster : list) { patrollingMonster.setPatrolTarget(blockPos); } } } } } private List findPatrolCompanions() { return this.mob .level() .getEntitiesOfClass( PatrollingMonster.class, this.mob.getBoundingBox().inflate(16.0), patrollingMonster -> patrollingMonster.canJoinPatrol() && !patrollingMonster.is(this.mob) ); } private boolean moveRandomly() { RandomSource randomSource = this.mob.getRandom(); BlockPos blockPos = this.mob .level() .getHeightmapPos( Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.mob.blockPosition().offset(-8 + randomSource.nextInt(16), 0, -8 + randomSource.nextInt(16)) ); return this.mob.getNavigation().moveTo(blockPos.getX(), blockPos.getY(), blockPos.getZ(), this.speedModifier); } } }