Minecraft 24w35a
This snapshot brings unexpected changes to raid spawning mechanics. See the changelog.
Salmon
Salmon variants now have different hitboxes sizes, fixing MC-275222.
Type | Width | Height |
---|---|---|
Small | 0.35 | 0.2 |
Medium | 0.7 | 0.4 |
Large | 1.05 | 0.6 |
Raids
The way the game chooses where to spawn raiders has changed significantly. Some of these changes are meant to fix MC-275279.
24w34a Behavior
The game tries to find a valid spawn point during the 15 second (300 tick) countdown before a wave starts. Every 5 ticks (starting at 300 ticks, then 295, then 290…), the game attempts 3 times to pick a spawn location. The game will pick a different spawn location if the existing location is no longer in an entity-ticking chunk without waiting for the 5-tick cycle.
- 300-100 ticks: picks a random location 64 blocks horizontally away from the raid center, then checks if the highest non-air block is within 64 blocks vertically of the raid center (known as spawning phase 1)
- 300-100 ticks: picks a random location 32 blocks horizontally away from the raid center, then checks if the highest non-air block is within 32 blocks vertically of the raid center (known as spawning phase 1)
- 39-1 ticks: picks the coordinate directly at the raid center (radius 0), but only if the highest non-air block is the raid center (known as spawning phase 3)
Ticks | Phase | Radius | Bias | Spawning Area |
---|---|---|---|---|
100-300 | 1 | 64 | 0-4 | 69x69 |
40-99 | 2 | 32 | 0-4 | 37x37 |
1-39 | 3 | 0 | 0-4 | 5x5 |
After the random coordinate is chosen and rounded down, the game selects a random bias from 0-4 in the X direction and a random bias from 0-4 in the Z direction. The bias is added to the random coordinate to create the spawn location (so the spawn location is biased in the +X and +Z directions).
If the raid countdown expires and there isn’t a valid spawn location yet, the game tries 20 attempts with spawning phase 1, 20 attempts with spawning phase 2, and 20 attempts with spawning phase 3.
24w35a Behavior
When the game tries to find a valid spawn point during the wave countdown, it makes 8 attempts every 5 ticks instead of 3 attempts every 5 ticks.
The game no longer separates the raid countdown into three spawning phases. Instead, each 20-tick interval has different radius and bias values (bias is randomly picked from the listed numbers):
Ticks | Horizontal Radius | Bias | Spawning Area |
---|---|---|---|
300 | 97.92 | 0,3,6 | 105x105 |
280-299 | 90.88 | 0,2,4 | 96x96 |
260-279 | 83.84 | 0,2,4 | 89x89 |
240-259 | 76.80 | 0,2,4 | 82x82 |
220-239 | 69.76 | 0,2,4 | 75x75 |
200-219 | 62.72 | 0,1,2 | 66x66 |
180-199 | 55.68 | 0,1,2 | 59x59 |
160-179 | 48.64 | 0,1,2 | 52x52 |
140-159 | 41.60 | 0,1,2 | 45x45 |
120-139 | 34.56 | 0,1,2 | 38x38 |
100-119 | 27.52 | 0 | 29x29 |
80-99 | 20.48 | 0 | 22x22 |
60-79 | 13.44 | 0 | 15x15 |
40-59 | 6.40 | 0 | 8x8 |
20-39 | 0.64 | -2,-1,0 | 4x4 |
0-19 | 7.68 | -2,-1,0 | 11x11 |
Note that:
- Horizontal radius ranges from 0.64 to 97.92.
- Bias ranges from -2 to +6.
- The radius decreases with time until it increases between 20-39 ticks and 0-19 ticks.
As an example (assuming the raid center is at (0, 0)), if the raid countdown is within 20-39 ticks, the radius is 0.64 and the bias is either -2, -1, or 0. Since the selected coordinate within the 0.64 block ring is rounded down to the nearest block, the possible coordinates are a 2x2 square: (0, 0), (-1, 0), (0, -1), and (-1, -1). Accounting for the -2 to 0 bias, mobs can spawn in a 4x4 square with the raid center in the +X+Z corner.
When the spawn location is selected, the highest non-air block now must be within 96 blocks (instead of 0, 32, or 64 blocks) vertically of the raid center. This applies to every wave, not just the starting wave.
If the raid countdown expires and there isn’t a valid spawn location yet, the game tries 5 times (instead of 3 times), 20 attempts each, to find a valid spawn location. The radius is always 7.68 and the bias is always from -2 to 0.
Previously, each attempt would select a completely random point on the spawning ring. Now, for each set of attempts (8 attempts during countdown and 20 attempts after countdown), the game chooses the point on a random starting angle, then tries increasing the angle in 1/16th increments. This causes the game to search a half-circle arc during each countdown attempt, and to search the full circle once the countdown has finished.
Code Comparison
The old behavior is equivalent to the following code:
public void tick() {
if (!shouldTryToFindSpawnLocation()) {
return;
}
if (raidCooldownTicks > 0) {
// Tries the 3 spawning phases in order depending on time left
int spawningPhase;
if (raidCooldownTicks >= 100) {
spawningPhase = 1; // 100-300 ticks
} else if (raidCooldownTicks >= 40) {
spawningPhase = 2; // 40-99 ticks
} else {
spawningPhase = 3; // 0-39 ticks
}
spawnLocation = findRaidSpawn(3, spawningPhase);
} else {
// When countdown expires, game tries all 3 spawning phases
// one after the other as a last ditch effort
for (int spawningPhase = 0; spawningPhase < 3; spawningPhase++) {
if (spawnLocation != null) {
spawnLocation = findRaidSpawn(20, spawningPhase);
}
}
}
}
private BlockPos findRaidSpawn(int numAttempts, int spawningPhase) {
for (int i = 0; i < numAttempts; i++) {
// Random angle chosen per attempt
float randomAngle = random.nextFloat() * (float) (Math.PI * 2);
// Radius 64 for spawning phase 1,
// 32 for phase 2,
// 0 for phase 3
int spawnRingRadius = 64 - 32 * (spawningPhase - 1);
// Biases from 0-4 are chosen separately for X and Z directions
int biasX = random.nextInt(5);
int biasZ = random.nextInt(5);
// Spawn locations are floored to nearest block position
int spawnX = center.getX() + (int) Math.floor(Math.cos(randomAngle) * spawnRingRadius + biasX);
int spawnZ = center.getZ() + (int) Math.floor(Math.sin(randomAngle) * spawnRingRadius + biasZ);
// The highest block in the column must be within a
// certain vertical distance from the raid center:
// 64 blocks (phase 1)
// 32 blocks (phase 2)
// 0 blocks (phase 3)
int spawnY = getHighestNonAirBlockY(spawnX, spawnZ);
if (Math.abs(center.getY() - spawnY) <= spawnRingRadius) {
return new BlockPos(spawnX, spawnY, spawnZ);
}
}
return null;
}
The new behavior is equivalent to the following code:
public void tick() {
if (!shouldTryToFindSpawnLocation()) {
return;
}
// No more separation into spawning "phases"
if (raidCooldownTicks > 0) {
// During countdown, 8 attempts every 5 ticks
// (unless existing position unloads)
spawnLocation = findRaidSpawn(8);
} else {
// Once countdown expires, game does 100 attempts total
// Unlike before, the ring radius and bias does not change
// Radius is always 7.68 and bias is always -2, -1, or 0
for (int i = 0; i < 5; i++) {
if (spawnLocation != null) {
spawnLocation = findRaidSpawn(20);
}
}
}
}
private BlockPos findRaidSpawn(int numAttempts) {
int raidCooldownSeconds = raidCooldownTicks / 20;
// Now chooses a starting angle, then increases the angle
// for each attempt in 1/16th increments
// During countdown, sweeps through a half-circle
// When countdown expires, sweeps through a circle and a quarter
float randomStartAngle = random.nextFloat() * (float) (Math.PI * 2);
for (int i = 0; i < numAttempts; i++) {
float currentAngle = randomStartAngle + i * (float) (Math.PI / 8);
// See table for radius and bias values
float spawnRingRadius = Math.abs(7.04f * (float) raidCooldownSeconds - 7.68f);
int biasX = random.nextInt(3) * (int) Math.floor(0.22f * (float) raidCooldownSeconds - 0.24f);
int biasZ = random.nextInt(3) * (int) Math.floor(0.22f * (float) raidCooldownSeconds - 0.24f);
// Spawn locations are still floored to nearest block position
int spawnX = center.getX() + (int) Math.floor(Math.cos(currentAngle) * spawnRingRadius + biasX);
int spawnZ = center.getZ() + (int) Math.floor(Math.sin(currentAngle) * spawnRingRadius + biasZ);
// The highest block in the column must be within
// 96 blocks vertically of the raid center,
// no longer a variable radius based on spawning phase
int spawnY = getHighestNonAirBlockY(spawnX, spawnZ);
if (Math.abs(center.getY() - spawnY) <= 96) {
return new BlockPos(spawnX, spawnY, spawnZ);
}
}
return null;
}
Both behaviors share the following code:
private int raidCooldownTicks; // decrements from 300 to 0
private BlockPos center; // raid center
private BlockPos spawnLocation; // location raiders will be spawned, may be null
private boolean shouldTryToFindSpawnLocation() {
if (spawnLocation == null) {
// Try to find spawn location every 5 ticks...
return raidCooldownTicks % 5 == 0;
} else {
// ...unless spawn location is no longer entity-ticking
return !isEntityTicking(spawnLocation);
}
}
Edits
September 7 2024
- Clarified bias selection for new raid behavior
- Extracted common code and explained local variables in code comparison section