/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Maps * com.google.common.collect.Sets * com.mojang.datafixers.kinds.App * com.mojang.datafixers.kinds.Applicative * com.mojang.serialization.Codec * com.mojang.serialization.MapCodec * com.mojang.serialization.codecs.RecordCodecBuilder * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.raid; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.mojang.datafixers.kinds.App; import com.mojang.datafixers.kinds.Applicative; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Stream; import net.minecraft.SharedConstants; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderGetter; import net.minecraft.core.SectionPos; import net.minecraft.core.UUIDUtil; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundSoundPacket; import net.minecraft.server.level.ServerBossEvent; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stats; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.world.BossEvent; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; 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.SpawnPlacementType; import net.minecraft.world.entity.SpawnPlacements; import net.minecraft.world.entity.raid.Raider; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.Rarity; import net.minecraft.world.item.component.TooltipDisplay; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BannerPattern; import net.minecraft.world.level.block.entity.BannerPatternLayers; import net.minecraft.world.level.block.entity.BannerPatterns; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; public class Raid { public static final SpawnPlacementType RAVAGER_SPAWN_PLACEMENT_TYPE = SpawnPlacements.getPlacementType(EntityType.RAVAGER); public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(i -> i.group((App)Codec.BOOL.fieldOf("started").forGetter(r -> r.started), (App)Codec.BOOL.fieldOf("active").forGetter(r -> r.active), (App)Codec.LONG.fieldOf("ticks_active").forGetter(r -> r.ticksActive), (App)Codec.INT.fieldOf("raid_omen_level").forGetter(r -> r.raidOmenLevel), (App)Codec.INT.fieldOf("groups_spawned").forGetter(r -> r.groupsSpawned), (App)Codec.INT.fieldOf("cooldown_ticks").forGetter(r -> r.raidCooldownTicks), (App)Codec.INT.fieldOf("post_raid_ticks").forGetter(r -> r.postRaidTicks), (App)Codec.FLOAT.fieldOf("total_health").forGetter(r -> Float.valueOf(r.totalHealth)), (App)Codec.INT.fieldOf("group_count").forGetter(r -> r.numGroups), (App)RaidStatus.CODEC.fieldOf("status").forGetter(r -> r.status), (App)BlockPos.CODEC.fieldOf("center").forGetter(r -> r.center), (App)UUIDUtil.CODEC_SET.fieldOf("heroes_of_the_village").forGetter(r -> r.heroesOfTheVillage)).apply((Applicative)i, Raid::new)); private static final int ALLOW_SPAWNING_WITHIN_VILLAGE_SECONDS_THRESHOLD = 7; private static final int SECTION_RADIUS_FOR_FINDING_NEW_VILLAGE_CENTER = 2; private static final int VILLAGE_SEARCH_RADIUS = 32; private static final int RAID_TIMEOUT_TICKS = 48000; private static final int NUM_SPAWN_ATTEMPTS = 5; private static final Component OMINOUS_BANNER_PATTERN_NAME = Component.translatable("block.minecraft.ominous_banner"); private static final String RAIDERS_REMAINING = "event.minecraft.raid.raiders_remaining"; public static final int VILLAGE_RADIUS_BUFFER = 16; private static final int POST_RAID_TICK_LIMIT = 40; private static final int DEFAULT_PRE_RAID_TICKS = 300; public static final int MAX_NO_ACTION_TIME = 2400; public static final int MAX_CELEBRATION_TICKS = 600; private static final int OUTSIDE_RAID_BOUNDS_TIMEOUT = 30; public static final int DEFAULT_MAX_RAID_OMEN_LEVEL = 5; private static final int LOW_MOB_THRESHOLD = 2; private static final Component RAID_NAME_COMPONENT = Component.translatable("event.minecraft.raid"); private static final Component RAID_BAR_VICTORY_COMPONENT = Component.translatable("event.minecraft.raid.victory.full"); private static final Component RAID_BAR_DEFEAT_COMPONENT = Component.translatable("event.minecraft.raid.defeat.full"); private static final int HERO_OF_THE_VILLAGE_DURATION = 48000; private static final int VALID_RAID_RADIUS = 96; public static final int VALID_RAID_RADIUS_SQR = 9216; public static final int RAID_REMOVAL_THRESHOLD_SQR = 12544; private final Map groupToLeaderMap = Maps.newHashMap(); private final Map> groupRaiderMap = Maps.newHashMap(); private final Set heroesOfTheVillage = Sets.newHashSet(); private long ticksActive; private BlockPos center; private boolean started; private float totalHealth; private int raidOmenLevel; private boolean active; private int groupsSpawned; private final ServerBossEvent raidEvent = new ServerBossEvent(RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); private int postRaidTicks; private int raidCooldownTicks; private final RandomSource random = RandomSource.create(); private final int numGroups; private RaidStatus status; private int celebrationTicks; private Optional waveSpawnPos = Optional.empty(); public Raid(BlockPos center, Difficulty difficulty) { this.active = true; this.raidCooldownTicks = 300; this.raidEvent.setProgress(0.0f); this.center = center; this.numGroups = this.getNumGroups(difficulty); this.status = RaidStatus.ONGOING; } private Raid(boolean started, boolean active, long ticksActive, int raidOmenLevel, int groupsSpawned, int raidCooldownTicks, int postRaidTicks, float totalHealth, int numGroups, RaidStatus status, BlockPos center, Set heroesOfTheVillage) { this.started = started; this.active = active; this.ticksActive = ticksActive; this.raidOmenLevel = raidOmenLevel; this.groupsSpawned = groupsSpawned; this.raidCooldownTicks = raidCooldownTicks; this.postRaidTicks = postRaidTicks; this.totalHealth = totalHealth; this.center = center; this.numGroups = numGroups; this.status = status; this.heroesOfTheVillage.addAll(heroesOfTheVillage); } public boolean isOver() { return this.isVictory() || this.isLoss(); } public boolean isBetweenWaves() { return this.hasFirstWaveSpawned() && this.getTotalRaidersAlive() == 0 && this.raidCooldownTicks > 0; } public boolean hasFirstWaveSpawned() { return this.groupsSpawned > 0; } public boolean isStopped() { return this.status == RaidStatus.STOPPED; } public boolean isVictory() { return this.status == RaidStatus.VICTORY; } public boolean isLoss() { return this.status == RaidStatus.LOSS; } public float getTotalHealth() { return this.totalHealth; } public Set getAllRaiders() { HashSet raiders = Sets.newHashSet(); for (Set raiderSet : this.groupRaiderMap.values()) { raiders.addAll(raiderSet); } return raiders; } public boolean isStarted() { return this.started; } public int getGroupsSpawned() { return this.groupsSpawned; } private Predicate validPlayer() { return player -> { BlockPos pos = player.blockPosition(); return player.isAlive() && player.level().getRaidAt(pos) == this; }; } private void updatePlayers(ServerLevel level) { HashSet currentPlayersInRaid = Sets.newHashSet(this.raidEvent.getPlayers()); List newPlayersInRaid = level.getPlayers(this.validPlayer()); for (ServerPlayer player : newPlayersInRaid) { if (currentPlayersInRaid.contains(player)) continue; this.raidEvent.addPlayer(player); } for (ServerPlayer player : currentPlayersInRaid) { if (newPlayersInRaid.contains(player)) continue; this.raidEvent.removePlayer(player); } } public int getMaxRaidOmenLevel() { return 5; } public int getRaidOmenLevel() { return this.raidOmenLevel; } public void setRaidOmenLevel(int raidOmenLevel) { this.raidOmenLevel = raidOmenLevel; } public boolean absorbRaidOmen(ServerPlayer player) { MobEffectInstance effect = player.getEffect(MobEffects.RAID_OMEN); if (effect == null) { return false; } this.raidOmenLevel += effect.getAmplifier() + 1; this.raidOmenLevel = Mth.clamp(this.raidOmenLevel, 0, this.getMaxRaidOmenLevel()); if (!this.hasFirstWaveSpawned()) { player.awardStat(Stats.RAID_TRIGGER); CriteriaTriggers.RAID_OMEN.trigger(player); } return true; } public void stop() { this.active = false; this.raidEvent.removeAllPlayers(); this.status = RaidStatus.STOPPED; } public void tick(ServerLevel level) { if (this.isStopped()) { return; } if (this.status == RaidStatus.ONGOING) { boolean oldActive = this.active; this.active = level.hasChunkAt(this.center); if (level.getDifficulty() == Difficulty.PEACEFUL) { this.stop(); return; } if (oldActive != this.active) { this.raidEvent.setVisible(this.active); } if (!this.active) { return; } if (!level.isVillage(this.center)) { this.moveRaidCenterToNearbyVillageSection(level); } if (!level.isVillage(this.center)) { if (this.groupsSpawned > 0) { this.status = RaidStatus.LOSS; } else { this.stop(); } } ++this.ticksActive; if (this.ticksActive >= 48000L) { this.stop(); return; } int raidersAlive = this.getTotalRaidersAlive(); if (raidersAlive == 0 && this.hasMoreWaves()) { if (this.raidCooldownTicks > 0) { boolean shouldTryToFindSpawnPos; boolean hasCachedWaveSpawnPos = this.waveSpawnPos.isPresent(); boolean bl = shouldTryToFindSpawnPos = !hasCachedWaveSpawnPos && this.raidCooldownTicks % 5 == 0; if (hasCachedWaveSpawnPos && !level.isPositionEntityTicking(this.waveSpawnPos.get())) { shouldTryToFindSpawnPos = true; } if (shouldTryToFindSpawnPos) { this.waveSpawnPos = this.getValidSpawnPos(level); } if (this.raidCooldownTicks == 300 || this.raidCooldownTicks % 20 == 0) { this.updatePlayers(level); } --this.raidCooldownTicks; this.raidEvent.setProgress(Mth.clamp((float)(300 - this.raidCooldownTicks) / 300.0f, 0.0f, 1.0f)); } else if (this.raidCooldownTicks == 0 && this.groupsSpawned > 0) { this.raidCooldownTicks = 300; this.raidEvent.setName(RAID_NAME_COMPONENT); return; } } if (this.ticksActive % 20L == 0L) { this.updatePlayers(level); this.updateRaiders(level); if (raidersAlive > 0) { if (raidersAlive <= 2) { this.raidEvent.setName(RAID_NAME_COMPONENT.copy().append(" - ").append(Component.translatable(RAIDERS_REMAINING, raidersAlive))); } else { this.raidEvent.setName(RAID_NAME_COMPONENT); } } else { this.raidEvent.setName(RAID_NAME_COMPONENT); } } if (SharedConstants.DEBUG_RAIDS) { this.raidEvent.setName(RAID_NAME_COMPONENT.copy().append(" wave: ").append("" + this.groupsSpawned).append(CommonComponents.SPACE).append("Raiders alive: ").append("" + this.getTotalRaidersAlive()).append(CommonComponents.SPACE).append("" + this.getHealthOfLivingRaiders()).append(" / ").append("" + this.totalHealth).append(" Is bonus? ").append("" + (this.hasBonusWave() && this.hasSpawnedBonusWave())).append(" Status: ").append(this.status.getSerializedName())); } boolean soundPlayed = false; int attempt = 0; while (this.shouldSpawnGroup()) { BlockPos spawnPos = this.waveSpawnPos.orElseGet(() -> this.findRandomSpawnPos(level, 20)); if (spawnPos != null) { this.started = true; this.spawnGroup(level, spawnPos); if (!soundPlayed) { this.playSound(level, spawnPos); soundPlayed = true; } } else { ++attempt; } if (attempt <= 5) continue; this.stop(); break; } if (this.isStarted() && !this.hasMoreWaves() && raidersAlive == 0) { if (this.postRaidTicks < 40) { ++this.postRaidTicks; } else { this.status = RaidStatus.VICTORY; for (UUID heroUUID : this.heroesOfTheVillage) { Entity entity = level.getEntity(heroUUID); if (!(entity instanceof LivingEntity)) continue; LivingEntity hero = (LivingEntity)entity; if (entity.isSpectator()) continue; hero.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); if (!(hero instanceof ServerPlayer)) continue; ServerPlayer playerHero = (ServerPlayer)hero; playerHero.awardStat(Stats.RAID_WIN); CriteriaTriggers.RAID_WIN.trigger(playerHero); } } } this.setDirty(level); } else if (this.isOver()) { ++this.celebrationTicks; if (this.celebrationTicks >= 600) { this.stop(); return; } if (this.celebrationTicks % 20 == 0) { this.updatePlayers(level); this.raidEvent.setVisible(true); if (this.isVictory()) { this.raidEvent.setProgress(0.0f); this.raidEvent.setName(RAID_BAR_VICTORY_COMPONENT); } else { this.raidEvent.setName(RAID_BAR_DEFEAT_COMPONENT); } } } } private void moveRaidCenterToNearbyVillageSection(ServerLevel level) { Stream sectionsToSearchForVillage = SectionPos.cube(SectionPos.of(this.center), 2); sectionsToSearchForVillage.filter(level::isVillage).map(SectionPos::center).min(Comparator.comparingDouble(pos -> pos.distSqr(this.center))).ifPresent(this::setCenter); } private Optional getValidSpawnPos(ServerLevel level) { BlockPos spawnPos = this.findRandomSpawnPos(level, 8); if (spawnPos != null) { return Optional.of(spawnPos); } return Optional.empty(); } private boolean hasMoreWaves() { if (this.hasBonusWave()) { return !this.hasSpawnedBonusWave(); } return !this.isFinalWave(); } private boolean isFinalWave() { return this.getGroupsSpawned() == this.numGroups; } private boolean hasBonusWave() { return this.raidOmenLevel > 1; } private boolean hasSpawnedBonusWave() { return this.getGroupsSpawned() > this.numGroups; } private boolean shouldSpawnBonusGroup() { return this.isFinalWave() && this.getTotalRaidersAlive() == 0 && this.hasBonusWave(); } private void updateRaiders(ServerLevel level) { Iterator> raiders = this.groupRaiderMap.values().iterator(); HashSet toRemove = Sets.newHashSet(); while (raiders.hasNext()) { Set raiderSet = raiders.next(); for (Raider raider : raiderSet) { BlockPos raiderPos = raider.blockPosition(); if (raider.isRemoved() || raider.level().dimension() != level.dimension() || this.center.distSqr(raiderPos) >= 12544.0) { toRemove.add(raider); continue; } if (raider.tickCount <= 600) continue; if (level.getEntity(raider.getUUID()) == null) { toRemove.add(raider); } if (!level.isVillage(raiderPos) && raider.getNoActionTime() > 2400) { raider.setTicksOutsideRaid(raider.getTicksOutsideRaid() + 1); } if (raider.getTicksOutsideRaid() < 30) continue; toRemove.add(raider); } } for (Raider raider : toRemove) { this.removeFromRaid(level, raider, true); if (!raider.isPatrolLeader()) continue; this.removeLeader(raider.getWave()); } } private void playSound(ServerLevel level, BlockPos soundOrigin) { float distAway = 13.0f; int range = 64; Collection playersInRaid = this.raidEvent.getPlayers(); long seed = this.random.nextLong(); for (ServerPlayer player : level.players()) { Vec3 playerLoc = player.position(); Vec3 raidLoc = Vec3.atCenterOf(soundOrigin); double distBtwn = Math.sqrt((raidLoc.x - playerLoc.x) * (raidLoc.x - playerLoc.x) + (raidLoc.z - playerLoc.z) * (raidLoc.z - playerLoc.z)); double x3 = playerLoc.x + 13.0 / distBtwn * (raidLoc.x - playerLoc.x); double z3 = playerLoc.z + 13.0 / distBtwn * (raidLoc.z - playerLoc.z); if (!(distBtwn <= 64.0) && !playersInRaid.contains(player)) continue; player.connection.send(new ClientboundSoundPacket(SoundEvents.RAID_HORN, SoundSource.NEUTRAL, x3, player.getY(), z3, 64.0f, 1.0f, seed)); } } private void spawnGroup(ServerLevel level, BlockPos pos) { boolean leaderSet = false; int groupNumber = this.groupsSpawned + 1; this.totalHealth = 0.0f; DifficultyInstance difficulty = level.getCurrentDifficultyAt(pos); boolean isBonusGroup = this.shouldSpawnBonusGroup(); for (RaiderType raiderType : RaiderType.VALUES) { Raider raider; int numSpawns = this.getDefaultNumSpawns(raiderType, groupNumber, isBonusGroup) + this.getPotentialBonusSpawns(raiderType, this.random, groupNumber, difficulty, isBonusGroup); int ravagersSpawned = 0; for (int i = 0; i < numSpawns && (raider = raiderType.entityType.create(level, EntitySpawnReason.EVENT)) != null; ++i) { if (!leaderSet && raider.canBeLeader()) { raider.setPatrolLeader(true); this.setLeader(groupNumber, raider); leaderSet = true; } this.joinRaid(level, groupNumber, raider, pos, false); if (raiderType.entityType != EntityType.RAVAGER) continue; Raider ridingRaider = null; if (groupNumber == this.getNumGroups(Difficulty.NORMAL)) { ridingRaider = EntityType.PILLAGER.create(level, EntitySpawnReason.EVENT); } else if (groupNumber >= this.getNumGroups(Difficulty.HARD)) { ridingRaider = ravagersSpawned == 0 ? (Raider)EntityType.EVOKER.create(level, EntitySpawnReason.EVENT) : (Raider)EntityType.VINDICATOR.create(level, EntitySpawnReason.EVENT); } ++ravagersSpawned; if (ridingRaider == null) continue; this.joinRaid(level, groupNumber, ridingRaider, pos, false); ridingRaider.snapTo(pos, 0.0f, 0.0f); ridingRaider.startRiding(raider, false, false); } } this.waveSpawnPos = Optional.empty(); ++this.groupsSpawned; this.updateBossbar(); this.setDirty(level); } public void joinRaid(ServerLevel level, int groupNumber, Raider raider, @Nullable BlockPos pos, boolean exists) { boolean added = this.addWaveMob(level, groupNumber, raider); if (added) { raider.setCurrentRaid(this); raider.setWave(groupNumber); raider.setCanJoinRaid(true); raider.setTicksOutsideRaid(0); if (!exists && pos != null) { raider.setPos((double)pos.getX() + 0.5, (double)pos.getY() + 1.0, (double)pos.getZ() + 0.5); raider.finalizeSpawn(level, level.getCurrentDifficultyAt(pos), EntitySpawnReason.EVENT, null); raider.applyRaidBuffs(level, groupNumber, false); raider.setOnGround(true); level.addFreshEntityWithPassengers(raider); } } } public void updateBossbar() { this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0f, 1.0f)); } public float getHealthOfLivingRaiders() { float health = 0.0f; for (Set raiders : this.groupRaiderMap.values()) { for (Raider raider : raiders) { health += raider.getHealth(); } } return health; } private boolean shouldSpawnGroup() { return this.raidCooldownTicks == 0 && (this.groupsSpawned < this.numGroups || this.shouldSpawnBonusGroup()) && this.getTotalRaidersAlive() == 0; } public int getTotalRaidersAlive() { return this.groupRaiderMap.values().stream().mapToInt(Set::size).sum(); } public void removeFromRaid(ServerLevel level, Raider raider, boolean removeFromTotalHealth) { boolean couldRemove; Set raiders = this.groupRaiderMap.get(raider.getWave()); if (raiders != null && (couldRemove = raiders.remove(raider))) { if (removeFromTotalHealth) { this.totalHealth -= raider.getHealth(); } raider.setCurrentRaid(null); this.updateBossbar(); this.setDirty(level); } } private void setDirty(ServerLevel level) { level.getRaids().setDirty(); } public static ItemStack getOminousBannerInstance(HolderGetter patternGetter) { ItemStack banner = new ItemStack(Items.WHITE_BANNER); BannerPatternLayers patterns = new BannerPatternLayers.Builder().addIfRegistered(patternGetter, BannerPatterns.RHOMBUS_MIDDLE, DyeColor.CYAN).addIfRegistered(patternGetter, BannerPatterns.STRIPE_BOTTOM, DyeColor.LIGHT_GRAY).addIfRegistered(patternGetter, BannerPatterns.STRIPE_CENTER, DyeColor.GRAY).addIfRegistered(patternGetter, BannerPatterns.BORDER, DyeColor.LIGHT_GRAY).addIfRegistered(patternGetter, BannerPatterns.STRIPE_MIDDLE, DyeColor.BLACK).addIfRegistered(patternGetter, BannerPatterns.HALF_HORIZONTAL, DyeColor.LIGHT_GRAY).addIfRegistered(patternGetter, BannerPatterns.CIRCLE_MIDDLE, DyeColor.LIGHT_GRAY).addIfRegistered(patternGetter, BannerPatterns.BORDER, DyeColor.BLACK).build(); banner.set(DataComponents.BANNER_PATTERNS, patterns); banner.set(DataComponents.TOOLTIP_DISPLAY, TooltipDisplay.DEFAULT.withHidden(DataComponents.BANNER_PATTERNS, true)); banner.set(DataComponents.ITEM_NAME, OMINOUS_BANNER_PATTERN_NAME); banner.set(DataComponents.RARITY, Rarity.UNCOMMON); return banner; } public @Nullable Raider getLeader(int wave) { return this.groupToLeaderMap.get(wave); } private @Nullable BlockPos findRandomSpawnPos(ServerLevel level, int maxTries) { int secondsRemaining = this.raidCooldownTicks / 20; float howFar = 0.22f * (float)secondsRemaining - 0.24f; BlockPos.MutableBlockPos spawnPos = new BlockPos.MutableBlockPos(); float startAngle = level.random.nextFloat() * ((float)Math.PI * 2); for (int i = 0; i < maxTries; ++i) { int spawnZ; float angle = startAngle + (float)Math.PI * (float)i / 8.0f; int spawnX = this.center.getX() + Mth.floor(Mth.cos(angle) * 32.0f * howFar) + level.random.nextInt(3) * Mth.floor(howFar); int spawnY = level.getHeight(Heightmap.Types.WORLD_SURFACE, spawnX, spawnZ = this.center.getZ() + Mth.floor(Mth.sin(angle) * 32.0f * howFar) + level.random.nextInt(3) * Mth.floor(howFar)); if (Mth.abs(spawnY - this.center.getY()) > 96) continue; spawnPos.set(spawnX, spawnY, spawnZ); if (level.isVillage(spawnPos) && secondsRemaining > 7) continue; int delta = 10; if (!level.hasChunksAt(spawnPos.getX() - 10, spawnPos.getZ() - 10, spawnPos.getX() + 10, spawnPos.getZ() + 10) || !level.isPositionEntityTicking(spawnPos) || !RAVAGER_SPAWN_PLACEMENT_TYPE.isSpawnPositionOk(level, spawnPos, EntityType.RAVAGER) && (!level.getBlockState((BlockPos)spawnPos.below()).is(Blocks.SNOW) || !level.getBlockState(spawnPos).isAir())) continue; return spawnPos; } return null; } private boolean addWaveMob(ServerLevel level, int wave, Raider raider) { return this.addWaveMob(level, wave, raider, true); } public boolean addWaveMob(ServerLevel level, int wave, Raider raider, boolean updateHealth) { this.groupRaiderMap.computeIfAbsent(wave, v -> Sets.newHashSet()); Set raiders = this.groupRaiderMap.get(wave); Raider existingCopy = null; for (Raider r : raiders) { if (!r.getUUID().equals(raider.getUUID())) continue; existingCopy = r; break; } if (existingCopy != null) { raiders.remove(existingCopy); raiders.add(raider); } raiders.add(raider); if (updateHealth) { this.totalHealth += raider.getHealth(); } this.updateBossbar(); this.setDirty(level); return true; } public void setLeader(int wave, Raider raider) { this.groupToLeaderMap.put(wave, raider); raider.setItemSlot(EquipmentSlot.HEAD, Raid.getOminousBannerInstance(raider.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN))); raider.setDropChance(EquipmentSlot.HEAD, 2.0f); } public void removeLeader(int wave) { this.groupToLeaderMap.remove(wave); } public BlockPos getCenter() { return this.center; } private void setCenter(BlockPos center) { this.center = center; } private int getDefaultNumSpawns(RaiderType type, int wav, boolean isBonusWave) { return isBonusWave ? type.spawnsPerWaveBeforeBonus[this.numGroups] : type.spawnsPerWaveBeforeBonus[wav]; } private int getPotentialBonusSpawns(RaiderType type, RandomSource random, int wav, DifficultyInstance difficultyInstance, boolean isBonusWave) { int bonusSpawns; Difficulty difficulty = difficultyInstance.getDifficulty(); boolean isEasy = difficulty == Difficulty.EASY; boolean isNormal = difficulty == Difficulty.NORMAL; switch (type.ordinal()) { case 3: { if (!isEasy && wav > 2 && wav != 4) { bonusSpawns = 1; break; } return 0; } case 0: case 2: { if (isEasy) { bonusSpawns = random.nextInt(2); break; } if (isNormal) { bonusSpawns = 1; break; } bonusSpawns = 2; break; } case 4: { bonusSpawns = !isEasy && isBonusWave ? 1 : 0; break; } default: { return 0; } } return bonusSpawns > 0 ? random.nextInt(bonusSpawns + 1) : 0; } public boolean isActive() { return this.active; } public int getNumGroups(Difficulty difficulty) { return switch (difficulty) { default -> throw new MatchException(null, null); case Difficulty.PEACEFUL -> 0; case Difficulty.EASY -> 3; case Difficulty.NORMAL -> 5; case Difficulty.HARD -> 7; }; } public float getEnchantOdds() { int raidOmenLvl = this.getRaidOmenLevel(); if (raidOmenLvl == 2) { return 0.1f; } if (raidOmenLvl == 3) { return 0.25f; } if (raidOmenLvl == 4) { return 0.5f; } if (raidOmenLvl == 5) { return 0.75f; } return 0.0f; } public void addHeroOfTheVillage(Entity killer) { this.heroesOfTheVillage.add(killer.getUUID()); } private static enum RaidStatus implements StringRepresentable { ONGOING("ongoing"), VICTORY("victory"), LOSS("loss"), STOPPED("stopped"); public static final Codec CODEC; private final String name; private RaidStatus(String name) { this.name = name; } @Override public String getSerializedName() { return this.name; } static { CODEC = StringRepresentable.fromEnum(RaidStatus::values); } } private static enum RaiderType { VINDICATOR(EntityType.VINDICATOR, new int[]{0, 0, 2, 0, 1, 4, 2, 5}), EVOKER(EntityType.EVOKER, new int[]{0, 0, 0, 0, 0, 1, 1, 2}), PILLAGER(EntityType.PILLAGER, new int[]{0, 4, 3, 3, 4, 4, 4, 2}), WITCH(EntityType.WITCH, new int[]{0, 0, 0, 0, 3, 0, 0, 1}), RAVAGER(EntityType.RAVAGER, new int[]{0, 0, 0, 1, 0, 1, 0, 2}); private static final RaiderType[] VALUES; private final EntityType entityType; private final int[] spawnsPerWaveBeforeBonus; private RaiderType(EntityType entityType, int[] spawnsPerWaveBeforeBonus) { this.entityType = entityType; this.spawnsPerWaveBeforeBonus = spawnsPerWaveBeforeBonus; } static { VALUES = RaiderType.values(); } } }