net/minecraft/world/level

This commit is contained in:
Shane Freeder
2024-12-14 23:42:27 +00:00
parent 5deb3e9671
commit 767215bf9b
23 changed files with 1110 additions and 1224 deletions

View File

@@ -0,0 +1,40 @@
--- a/net/minecraft/world/level/BaseCommandBlock.java
+++ b/net/minecraft/world/level/BaseCommandBlock.java
@@ -32,6 +_,11 @@
private String command = "";
@Nullable
private Component customName;
+ // CraftBukkit start
+ @Override
+ public abstract org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper);
+ // CraftBukkit end
+
public int getSuccessCount() {
return this.successCount;
@@ -126,7 +_,7 @@
this.successCount++;
}
});
- server.getCommands().performPrefixedCommand(commandSourceStack, this.command);
+ server.getCommands().dispatchServerCommand(commandSourceStack, this.command); // CraftBukkit
} catch (Throwable var6) {
CrashReport crashReport = CrashReport.forThrowable(var6, "Executing command block");
CrashReportCategory crashReportCategory = crashReport.addCategory("Command to be executed");
@@ -162,6 +_,7 @@
@Override
public void sendSystemMessage(Component component) {
if (this.trackOutput) {
+ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks
this.lastOutput = Component.literal("[" + TIME_FORMAT.format(new Date()) + "] ").append(component);
this.onUpdated();
}
@@ -184,7 +_,7 @@
}
public InteractionResult usedBy(Player player) {
- if (!player.canUseGameMasterBlocks()) {
+ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission
return InteractionResult.PASS;
} else {
if (player.getCommandSenderWorld().isClientSide) {

View File

@@ -0,0 +1,156 @@
--- a/net/minecraft/world/level/BaseSpawner.java
+++ b/net/minecraft/world/level/BaseSpawner.java
@@ -44,9 +_,11 @@
public int maxNearbyEntities = 6;
public int requiredPlayerRange = 16;
public int spawnRange = 4;
+ private int tickDelay = 0; // Paper - Configurable mob spawner tick rate
public void setEntityId(EntityType<?> type, @Nullable Level level, RandomSource random, BlockPos pos) {
this.getOrCreateNextSpawnData(level, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());
+ this.spawnPotentials = SimpleWeightedRandomList.empty(); // CraftBukkit - SPIGOT-3496, MC-92282
}
public boolean isNearPlayer(Level level, BlockPos pos) {
@@ -73,13 +_,19 @@
}
public void serverTick(ServerLevel serverLevel, BlockPos pos) {
+ if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick
+ // Paper start - Configurable mob spawner tick rate
+ if (spawnDelay > 0 && --tickDelay > 0) return;
+ tickDelay = serverLevel.paperConfig().tickRates.mobSpawner;
+ if (tickDelay == -1) { return; } // If disabled
+ // Paper end - Configurable mob spawner tick rate
if (this.isNearPlayer(serverLevel, pos)) {
- if (this.spawnDelay == -1) {
+ if (this.spawnDelay < -tickDelay) { // Paper - Configurable mob spawner tick rate
this.delay(serverLevel, pos);
}
if (this.spawnDelay > 0) {
- this.spawnDelay--;
+ this.spawnDelay -= tickDelay; // Paper - Configurable mob spawner tick rate
} else {
boolean flag = false;
RandomSource random = serverLevel.getRandom();
@@ -113,6 +_,21 @@
continue;
}
+ // Paper start - PreCreatureSpawnEvent
+ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent(
+ io.papermc.paper.util.MCUtil.toLocation(serverLevel, d, d1, d2),
+ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()),
+ io.papermc.paper.util.MCUtil.toLocation(serverLevel, pos)
+ );
+ if (!event.callEvent()) {
+ flag = true;
+ if (event.shouldAbortSpawn()) {
+ break;
+ }
+ continue;
+ }
+ // Paper end - PreCreatureSpawnEvent
+
Entity entity = EntityType.loadEntityRecursive(entityToSpawn, serverLevel, EntitySpawnReason.SPAWNER, entity1 -> {
entity1.moveTo(d, d1, d2, entity1.getYRot(), entity1.getXRot());
return entity1;
@@ -133,6 +_,7 @@
return;
}
+ entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag
entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), random.nextFloat() * 360.0F, 0.0F);
if (entity instanceof Mob mob) {
if (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER)
@@ -147,9 +_,22 @@
}
nextSpawnData.getEquipment().ifPresent(mob::equip);
+ // Spigot Start
+ if (mob.level().spigotConfig.nerfSpawnerMobs) {
+ mob.aware = false;
+ }
+ // Spigot End
}
- if (!serverLevel.tryAddFreshEntityWithPassengers(entity)) {
+ entity.spawnedViaMobSpawner = true; // Paper
+ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason
+ flag = true; // Paper
+ // CraftBukkit start
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
+ continue;
+ }
+ if (!serverLevel.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) {
+ // CraftBukkit end
this.delay(serverLevel, pos);
return;
}
@@ -160,7 +_,7 @@
((Mob)entity).spawnAnim();
}
- flag = true;
+ //flag = true; // Paper - moved up above cancellable event
}
}
@@ -184,7 +_,13 @@
}
public void load(@Nullable Level level, BlockPos pos, CompoundTag tag) {
- this.spawnDelay = tag.getShort("Delay");
+ // Paper start - use larger int if set
+ if (tag.contains("Paper.Delay")) {
+ this.spawnDelay = tag.getInt("Paper.Delay");
+ } else {
+ this.spawnDelay = tag.getShort("Delay");
+ }
+ // Paper end
boolean flag = tag.contains("SpawnData", 10);
if (flag) {
SpawnData spawnData = SpawnData.CODEC
@@ -205,9 +_,15 @@
this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData());
}
+ // Paper start - use ints if set
+ if (tag.contains("Paper.MinSpawnDelay", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
+ this.minSpawnDelay = tag.getInt("Paper.MinSpawnDelay");
+ this.maxSpawnDelay = tag.getInt("Paper.MaxSpawnDelay");
+ this.spawnCount = tag.getShort("SpawnCount");
+ } else // Paper end
if (tag.contains("MinSpawnDelay", 99)) {
- this.minSpawnDelay = tag.getShort("MinSpawnDelay");
- this.maxSpawnDelay = tag.getShort("MaxSpawnDelay");
+ this.minSpawnDelay = tag.getInt("MinSpawnDelay"); // Paper - short -> int
+ this.maxSpawnDelay = tag.getInt("MaxSpawnDelay"); // Paper - short -> int
this.spawnCount = tag.getShort("SpawnCount");
}
@@ -224,9 +_,20 @@
}
public CompoundTag save(CompoundTag tag) {
- tag.putShort("Delay", (short)this.spawnDelay);
- tag.putShort("MinSpawnDelay", (short)this.minSpawnDelay);
- tag.putShort("MaxSpawnDelay", (short)this.maxSpawnDelay);
+ // Paper start
+ if (spawnDelay > Short.MAX_VALUE) {
+ tag.putInt("Paper.Delay", this.spawnDelay);
+ }
+ tag.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay));
+
+ if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) {
+ tag.putInt("Paper.MinSpawnDelay", this.minSpawnDelay);
+ tag.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay);
+ }
+
+ tag.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay));
+ tag.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay));
+ // Paper end
tag.putShort("SpawnCount", (short)this.spawnCount);
tag.putShort("MaxNearbyEntities", (short)this.maxNearbyEntities);
tag.putShort("RequiredPlayerRange", (short)this.requiredPlayerRange);

View File

@@ -0,0 +1,76 @@
--- a/net/minecraft/world/level/BlockGetter.java
+++ b/net/minecraft/world/level/BlockGetter.java
@@ -11,6 +_,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
+import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@@ -33,6 +_,16 @@
BlockState getBlockState(BlockPos pos);
+ // Paper start - if loaded util
+ @Nullable BlockState getBlockStateIfLoaded(BlockPos blockposition);
+
+ default @Nullable Block getBlockIfLoaded(BlockPos blockposition) {
+ BlockState type = this.getBlockStateIfLoaded(blockposition);
+ return type == null ? null : type.getBlock();
+ }
+ @Nullable FluidState getFluidIfLoaded(BlockPos blockposition);
+ // Paper end
+
FluidState getFluidState(BlockPos pos);
default int getLightEmission(BlockPos pos) {
@@ -66,10 +_,25 @@
);
}
- default BlockHitResult clip(ClipContext context) {
- return traverseBlocks(context.getFrom(), context.getTo(), context, (traverseContext, traversePos) -> {
- BlockState blockState = this.getBlockState(traversePos);
- FluidState fluidState = this.getFluidState(traversePos);
+ // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace
+ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) {
+ // Paper start - Add predicate for blocks when raytracing
+ return clip(raytrace1, blockposition, null);
+ }
+
+ default BlockHitResult clip(ClipContext traverseContext, BlockPos traversePos, java.util.function.Predicate<? super org.bukkit.block.Block> canCollide) {
+ // Paper end - Add predicate for blocks when raytracing
+ // Paper start - Prevent raytrace from loading chunks
+ BlockState blockState = this.getBlockStateIfLoaded(traversePos);
+ if (blockState == null) {
+ // copied the last function parameter (listed below)
+ Vec3 vec3d = traverseContext.getFrom().subtract(traverseContext.getTo());
+
+ return BlockHitResult.miss(traverseContext.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo()));
+ }
+ // Paper end - Prevent raytrace from loading chunks
+ if (blockState.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate
+ FluidState fluidState = blockState.getFluidState(); // Paper - Perf: don't need to go to world state again
Vec3 from = traverseContext.getFrom();
Vec3 to = traverseContext.getTo();
VoxelShape blockShape = traverseContext.getBlockShape(blockState, this, traversePos);
@@ -79,6 +_,18 @@
double d = blockHitResult == null ? Double.MAX_VALUE : traverseContext.getFrom().distanceToSqr(blockHitResult.getLocation());
double d1 = blockHitResult1 == null ? Double.MAX_VALUE : traverseContext.getFrom().distanceToSqr(blockHitResult1.getLocation());
return d <= d1 ? blockHitResult : blockHitResult1;
+ }
+ // CraftBukkit end
+
+ default BlockHitResult clip(ClipContext context) {
+ // Paper start - Add predicate for blocks when raytracing
+ return clip(context, (java.util.function.Predicate<org.bukkit.block.Block>) null);
+ }
+
+ default BlockHitResult clip(ClipContext context, java.util.function.Predicate<? super org.bukkit.block.Block> canCollide) {
+ // Paper end - Add predicate for blocks when raytracing
+ return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> {
+ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing
}, failContext -> {
Vec3 vec3 = failContext.getFrom().subtract(failContext.getTo());
return BlockHitResult.miss(failContext.getTo(), Direction.getApproximateNearest(vec3.x, vec3.y, vec3.z), BlockPos.containing(failContext.getTo()));

View File

@@ -0,0 +1,30 @@
--- a/net/minecraft/world/level/ChunkPos.java
+++ b/net/minecraft/world/level/ChunkPos.java
@@ -46,6 +_,7 @@
public static final int REGION_MAX_INDEX = 31;
public final int x;
public final int z;
+ public final long longKey; // Paper
private static final int HASH_A = 1664525;
private static final int HASH_C = 1013904223;
private static final int HASH_Z_XOR = -559038737;
@@ -53,16 +_,19 @@
public ChunkPos(int x, int y) {
this.x = x;
this.z = y;
+ this.longKey = asLong(this.x, this.z); // Paper
}
public ChunkPos(BlockPos pos) {
this.x = SectionPos.blockToSectionCoord(pos.getX());
this.z = SectionPos.blockToSectionCoord(pos.getZ());
+ this.longKey = asLong(this.x, this.z); // Paper
}
public ChunkPos(long packedPos) {
this.x = (int)packedPos;
this.z = (int)(packedPos >> 32);
+ this.longKey = asLong(this.x, this.z); // Paper
}
public static ChunkPos minFromRegion(int chunkX, int chunkZ) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/ClipContext.java
+++ b/net/minecraft/world/level/ClipContext.java
@@ -21,7 +_,7 @@
private final CollisionContext collisionContext;
public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, Entity entity) {
- this(from, to, block, fluid, CollisionContext.of(entity));
+ this(from, to, block, fluid, (entity == null) ? CollisionContext.empty() : CollisionContext.of(entity)); // CraftBukkit
}
public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, CollisionContext collisionContext) {

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/level/EmptyBlockGetter.java
+++ b/net/minecraft/world/level/EmptyBlockGetter.java
@@ -17,6 +_,18 @@
return null;
}
+ // Paper start - If loaded util
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ return Fluids.EMPTY.defaultFluidState();
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ return Blocks.AIR.defaultBlockState();
+ }
+ // Paper end
+
@Override
public BlockState getBlockState(BlockPos pos) {
return Blocks.AIR.defaultBlockState();

View File

@@ -0,0 +1,77 @@
--- a/net/minecraft/world/level/EntityGetter.java
+++ b/net/minecraft/world/level/EntityGetter.java
@@ -71,6 +_,12 @@
}
}
+ // Paper start - Affects Spawning API
+ default @Nullable Player findNearbyPlayer(Entity entity, double maxDistance, @Nullable Predicate<Entity> predicate) {
+ return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, predicate);
+ }
+ // Paper end - Affects Spawning API
+
@Nullable
default Player getNearestPlayer(double x, double y, double z, double distance, @Nullable Predicate<Entity> predicate) {
double d = -1.0;
@@ -89,6 +_,28 @@
return player;
}
+ // Paper start
+ default List<org.bukkit.entity.HumanEntity> findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) {
+ return findNearbyBukkitPlayers(x, y, z, radius, notSpectator ? EntitySelector.NO_SPECTATORS : net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR);
+ }
+
+ default List<org.bukkit.entity.HumanEntity> findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate<Entity> predicate) {
+ com.google.common.collect.ImmutableList.Builder<org.bukkit.entity.HumanEntity> builder = com.google.common.collect.ImmutableList.builder();
+
+ for (Player human : this.players()) {
+ if (predicate == null || predicate.test(human)) {
+ double distanceSquared = human.distanceToSqr(x, y, z);
+
+ if (radius < 0.0D || distanceSquared < radius * radius) {
+ builder.add(human.getBukkitEntity());
+ }
+ }
+ }
+
+ return builder.build();
+ }
+ // Paper end
+
@Nullable
default Player getNearestPlayer(Entity entity, double distance) {
return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), distance, false);
@@ -100,6 +_,20 @@
return this.getNearestPlayer(x, y, z, distance, predicate);
}
+ // Paper start - Affects Spawning API
+ default boolean hasNearbyAlivePlayerThatAffectsSpawning(double x, double y, double z, double range) {
+ for (Player player : this.players()) {
+ if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check
+ double distanceSqr = player.distanceToSqr(x, y, z);
+ if (range < 0.0D || distanceSqr < range * range) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ // Paper end - Affects Spawning API
+
default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) {
for (Player player : this.players()) {
if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) {
@@ -124,4 +_,11 @@
return null;
}
+
+ // Paper start - check global player list where appropriate
+ @Nullable
+ default Player getGlobalPlayerByUUID(UUID uuid) {
+ return this.getPlayerByUUID(uuid);
+ }
+ // Paper end - check global player list where appropriate
}

View File

@@ -0,0 +1,298 @@
--- a/net/minecraft/world/level/GameRules.java
+++ b/net/minecraft/world/level/GameRules.java
@@ -32,6 +_,14 @@
import org.slf4j.Logger;
public class GameRules {
+ // Paper start - allow disabling gamerule limits
+ private static final boolean DISABLE_LIMITS = Boolean.getBoolean("paper.disableGameRuleLimits");
+
+ private static int limit(final int limit, final int unlimited) {
+ return DISABLE_LIMITS ? unlimited : limit;
+ }
+ // Paper end - allow disabling gamerule limits
+
public static final int DEFAULT_RANDOM_TICK_SPEED = 3;
static final Logger LOGGER = LogUtils.getLogger();
private static final Map<GameRules.Key<?>, GameRules.Type<?>> GAME_RULE_TYPES = Maps.newTreeMap(Comparator.comparing(entry -> entry.id));
@@ -81,10 +_,10 @@
"sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true)
);
public static final GameRules.Key<GameRules.BooleanValue> RULE_REDUCEDDEBUGINFO = register(
- "reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (server, value) -> {
+ "reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (level, value) -> { // Paper - rename param to match changes
byte b = (byte)(value.get() ? 22 : 23);
- for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
+ for (ServerPlayer serverPlayer : level.players()) {
serverPlayer.connection.send(new ClientboundEntityEventPacket(serverPlayer, b));
}
})
@@ -108,8 +_,8 @@
"doWeatherCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)
);
public static final GameRules.Key<GameRules.BooleanValue> RULE_LIMITED_CRAFTING = register(
- "doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (server, value) -> {
- for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
+ "doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (level, value) -> { // Paper - rename param to match changes
+ for (ServerPlayer serverPlayer : level.players()) {
serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LIMITED_CRAFTING, value.get() ? 1.0F : 0.0F));
}
})
@@ -133,8 +_,8 @@
"doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true)
);
public static final GameRules.Key<GameRules.BooleanValue> RULE_DO_IMMEDIATE_RESPAWN = register(
- "doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (server, value) -> {
- for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
+ "doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (level, value) -> { // Paper - rename param to match changes
+ for (ServerPlayer serverPlayer : level.players()) {
serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, value.get() ? 1.0F : 0.0F));
}
})
@@ -205,16 +_,17 @@
public static final GameRules.Key<GameRules.IntegerValue> RULE_MINECART_MAX_SPEED = register(
"minecartMaxSpeed",
GameRules.Category.MISC,
- GameRules.IntegerValue.create(8, 1, 1000, FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (server, value) -> {})
+ GameRules.IntegerValue.create(8, 1, limit(1000, Integer.MAX_VALUE), FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (server, value) -> {}) // Paper - allow disabling gamerule limits
);
public static final GameRules.Key<GameRules.IntegerValue> RULE_SPAWN_CHUNK_RADIUS = register(
- "spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, 32, FeatureFlagSet.of(), (server, value) -> {
- ServerLevel serverLevel = server.overworld();
+ "spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, limit(32, Integer.MAX_VALUE), FeatureFlagSet.of(), (level, value) -> { // Paper - allow disabling gamerule limits - also, rename param
+ ServerLevel serverLevel = level; // CraftBukkit - per-world
serverLevel.setDefaultSpawnPos(serverLevel.getSharedSpawnPos(), serverLevel.getSharedSpawnAngle());
})
);
private final Map<GameRules.Key<?>, GameRules.Value<?>> rules;
private final FeatureFlagSet enabledFeatures;
+ private final GameRules.Value<?>[] gameruleArray; // Paper - Perf: Use array for gamerule storage
private static <T extends GameRules.Value<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
GameRules.Key<T> key = new GameRules.Key<>(name, category);
@@ -242,10 +_,21 @@
private GameRules(Map<GameRules.Key<?>, GameRules.Value<?>> rules, FeatureFlagSet enabledFeatures) {
this.rules = rules;
this.enabledFeatures = enabledFeatures;
+
+ // Paper start - Perf: Use array for gamerule storage
+ int arraySize = GameRules.Key.lastGameRuleIndex + 1;
+ GameRules.Value<?>[] values = new GameRules.Value[arraySize];
+
+ for (Entry<GameRules.Key<?>, GameRules.Value<?>> entry : rules.entrySet()) {
+ values[entry.getKey().gameRuleIndex] = entry.getValue();
+ }
+
+ this.gameruleArray = values;
+ // Paper end - Perf: Use array for gamerule storage
}
public <T extends GameRules.Value<T>> T getRule(GameRules.Key<T> key) {
- T value = (T)this.rules.get(key);
+ T value = key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage
if (value == null) {
throw new IllegalArgumentException("Tried to access invalid game rule");
} else {
@@ -286,13 +_,13 @@
}
}
- public void assignFrom(GameRules rules, @Nullable MinecraftServer server) {
- rules.rules.keySet().forEach(key -> this.assignCap((GameRules.Key<?>)key, rules, server));
+ public void assignFrom(GameRules rules, @Nullable ServerLevel level) { // CraftBukkit - per-world
+ rules.rules.keySet().forEach(key -> this.assignCap((GameRules.Key<?>)key, rules, level)); // CraftBukkit - per-world
}
- private <T extends GameRules.Value<T>> void assignCap(GameRules.Key<T> key, GameRules rules, @Nullable MinecraftServer server) {
+ private <T extends GameRules.Value<T>> void assignCap(GameRules.Key<T> key, GameRules rules, @Nullable ServerLevel level) { // CraftBukkit - per-world
T rule = rules.getRule(key);
- this.<T>getRule(key).setFrom(rule, server);
+ this.<T>getRule(key).setFrom(rule, level); // CraftBukkit - per-world
}
public boolean getBoolean(GameRules.Key<GameRules.BooleanValue> key) {
@@ -306,7 +_,7 @@
public static class BooleanValue extends GameRules.Value<GameRules.BooleanValue> {
private boolean value;
- static GameRules.Type<GameRules.BooleanValue> create(boolean defaultValue, BiConsumer<MinecraftServer, GameRules.BooleanValue> changeListener) {
+ static GameRules.Type<GameRules.BooleanValue> create(boolean defaultValue, BiConsumer<ServerLevel, GameRules.BooleanValue> changeListener) { // CraftBukkit - per-world
return new GameRules.Type<>(
BoolArgumentType::bool,
type -> new GameRules.BooleanValue(type, defaultValue),
@@ -326,17 +_,21 @@
}
@Override
- protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName) {
- this.value = BoolArgumentType.getBool(context, paramName);
+ // Paper start - Add WorldGameRuleChangeEvent
+ protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<BooleanValue> gameRuleKey) {
+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Boolean>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, paramName)));
+ if (!event.callEvent()) return;
+ this.value = Boolean.parseBoolean(event.getValue());
+ // Paper end - Add WorldGameRuleChangeEvent
}
public boolean get() {
return this.value;
}
- public void set(boolean value, @Nullable MinecraftServer server) {
+ public void set(boolean value, @Nullable ServerLevel level) { // CraftBukkit - per-world
this.value = value;
- this.onChanged(server);
+ this.onChanged(level); // CraftBukkit - per-world
}
@Override
@@ -345,7 +_,7 @@
}
@Override
- protected void deserialize(String value) {
+ public void deserialize(String value) { // PAIL - protected->public
this.value = Boolean.parseBoolean(value);
}
@@ -365,9 +_,9 @@
}
@Override
- public void setFrom(GameRules.BooleanValue value, @Nullable MinecraftServer server) {
+ public void setFrom(GameRules.BooleanValue value, @Nullable ServerLevel level) { // CraftBukkit - per-world
this.value = value.value;
- this.onChanged(server);
+ this.onChanged(level); // CraftBukkit - per-world
}
}
@@ -405,7 +_,7 @@
public static class IntegerValue extends GameRules.Value<GameRules.IntegerValue> {
private int value;
- private static GameRules.Type<GameRules.IntegerValue> create(int defaultValue, BiConsumer<MinecraftServer, GameRules.IntegerValue> changeListener) {
+ private static GameRules.Type<GameRules.IntegerValue> create(int defaultValue, BiConsumer<ServerLevel, GameRules.IntegerValue> changeListener) { // CraftBukkit - per-world
return new GameRules.Type<>(
IntegerArgumentType::integer,
type -> new GameRules.IntegerValue(type, defaultValue),
@@ -416,7 +_,7 @@
}
static GameRules.Type<GameRules.IntegerValue> create(
- int defaultValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer<MinecraftServer, GameRules.IntegerValue> changeListener
+ int defaultValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer<ServerLevel, GameRules.IntegerValue> changeListener // CraftBukkit - per-world
) {
return new GameRules.Type<>(
() -> IntegerArgumentType.integer(min, max),
@@ -437,17 +_,21 @@
}
@Override
- protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName) {
- this.value = IntegerArgumentType.getInteger(context, paramName);
+ // Paper start - Add WorldGameRuleChangeEvent
+ protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<IntegerValue> gameRuleKey) {
+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Integer>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, paramName)));
+ if (!event.callEvent()) return;
+ this.value = Integer.parseInt(event.getValue());
+ // Paper end - Add WorldGameRuleChangeEvent
}
public int get() {
return this.value;
}
- public void set(int value, @Nullable MinecraftServer server) {
+ public void set(int value, @Nullable ServerLevel level) { // CraftBukkit - per-world
this.value = value;
- this.onChanged(server);
+ this.onChanged(level) ;// CraftBukkit - per-world
}
@Override
@@ -456,7 +_,7 @@
}
@Override
- protected void deserialize(String value) {
+ public void deserialize(String value) { // PAIL - protected->public
this.value = safeParse(value);
}
@@ -498,13 +_,17 @@
}
@Override
- public void setFrom(GameRules.IntegerValue value, @Nullable MinecraftServer server) {
+ public void setFrom(GameRules.IntegerValue value, @Nullable ServerLevel level) { // CraftBukkit - per-world
this.value = value.value;
- this.onChanged(server);
+ this.onChanged(level); // CraftBukkit - per-world
}
}
public static final class Key<T extends GameRules.Value<T>> {
+ // Paper start - Perf: Use array for gamerule storage
+ public static int lastGameRuleIndex = 0;
+ public final int gameRuleIndex = lastGameRuleIndex++;
+ // Paper end - Perf: Use array for gamerule storage
final String id;
private final GameRules.Category category;
@@ -544,14 +_,14 @@
public static class Type<T extends GameRules.Value<T>> {
final Supplier<ArgumentType<?>> argument;
private final Function<GameRules.Type<T>, T> constructor;
- final BiConsumer<MinecraftServer, T> callback;
+ final BiConsumer<ServerLevel, T> callback; // CraftBukkit - per-world
private final GameRules.VisitorCaller<T> visitorCaller;
final FeatureFlagSet requiredFeatures;
Type(
Supplier<ArgumentType<?>> argument,
Function<GameRules.Type<T>, T> constructor,
- BiConsumer<MinecraftServer, T> callback,
+ BiConsumer<ServerLevel, T> callback, // CraftBukkit - per-world
GameRules.VisitorCaller<T> visitorCaller,
FeatureFlagSet requiredFeature
) {
@@ -586,20 +_,20 @@
this.type = type;
}
- protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName);
+ protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<T> gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
- public void setFromArgument(CommandContext<CommandSourceStack> context, String paramName) {
- this.updateFromArgument(context, paramName);
- this.onChanged(context.getSource().getServer());
+ public void setFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<T> gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent
+ this.updateFromArgument(context, paramName, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
+ this.onChanged(context.getSource().getLevel());
}
- public void onChanged(@Nullable MinecraftServer server) {
- if (server != null) {
- this.type.callback.accept(server, this.getSelf());
+ public void onChanged(@Nullable ServerLevel level) { // CraftBukkit - per-world
+ if (level != null) { // CraftBukkit - per-world
+ this.type.callback.accept(level, this.getSelf()); // CraftBukkit - per-world
}
}
- protected abstract void deserialize(String value);
+ public abstract void deserialize(String value); // PAIL - private->public
public abstract String serialize();
@@ -614,7 +_,7 @@
protected abstract T copy();
- public abstract void setFrom(T value, @Nullable MinecraftServer server);
+ public abstract void setFrom(T value, @Nullable ServerLevel level); // CraftBukkit - per-world
}
interface VisitorCaller<T extends GameRules.Value<T>> {

View File

@@ -0,0 +1,649 @@
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -24,8 +_,10 @@
import net.minecraft.network.protocol.Packet;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
+import io.papermc.paper.util.MCUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.FullChunkStatus;
+import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
@@ -42,6 +_,7 @@
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.alchemy.PotionBrewing;
@@ -79,6 +_,27 @@
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Scoreboard;
+// CraftBukkit start
+import java.util.HashMap;
+import java.util.Map;
+import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
+import net.minecraft.world.level.border.BorderChangeListener;
+import net.minecraft.world.level.dimension.LevelStem;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.block.CapturedBlockState;
+import org.bukkit.craftbukkit.block.CraftBlockState;
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
+import org.bukkit.craftbukkit.util.CraftSpawnCategory;
+import org.bukkit.entity.SpawnCategory;
+import org.bukkit.event.block.BlockPhysicsEvent;
+// CraftBukkit end
+
public abstract class Level implements LevelAccessor, AutoCloseable {
public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld"));
@@ -91,7 +_,7 @@
public static final int TICKS_PER_DAY = 24000;
public static final int MAX_ENTITY_SPAWN_Y = 20000000;
public static final int MIN_ENTITY_SPAWN_Y = -20000000;
- protected final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList();
+ public final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList(); // Paper - public
protected final NeighborUpdater neighborUpdater;
private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
private boolean tickingBlockEntities;
@@ -117,6 +_,61 @@
private final DamageSources damageSources;
private long subTickCount;
+ // CraftBukkit start Added the following
+ private final CraftWorld world;
+ public boolean pvpMode;
+ public org.bukkit.generator.ChunkGenerator generator;
+
+ public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710
+ public boolean captureBlockStates = false;
+ public boolean captureTreeGeneration = false;
+ public boolean isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
+ public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
+ public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
+ public List<ItemEntity> captureDrops;
+ public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
+ public boolean populating;
+ public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
+ // Paper start - add paper world config
+ private final io.papermc.paper.configuration.WorldConfiguration paperConfig;
+ public io.papermc.paper.configuration.WorldConfiguration paperConfig() {
+ return this.paperConfig;
+ }
+ // Paper end - add paper world config
+
+ public static BlockPos lastPhysicsProblem; // Spigot
+ private org.spigotmc.TickLimiter entityLimiter;
+ private org.spigotmc.TickLimiter tileLimiter;
+ private int tileTickPosition;
+ public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
+ public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
+
+ public CraftWorld getWorld() {
+ return this.world;
+ }
+
+ public CraftServer getCraftServer() {
+ return (CraftServer) Bukkit.getServer();
+ }
+ // Paper start - Use getChunkIfLoadedImmediately
+ @Override
+ public boolean hasChunk(int chunkX, int chunkZ) {
+ return this.getChunkIfLoaded(chunkX, chunkZ) != null;
+ }
+ // Paper end - Use getChunkIfLoadedImmediately
+ // Paper start - per world ticks per spawn
+ private int getTicksPerSpawn(SpawnCategory spawnCategory) {
+ final int perWorld = this.paperConfig().entities.spawning.ticksPerSpawn.getInt(CraftSpawnCategory.toNMS(spawnCategory));
+ if (perWorld >= 0) {
+ return perWorld;
+ }
+ return this.getCraftServer().getTicksPerSpawns(spawnCategory);
+ }
+ // Paper end
+
+
+ public abstract ResourceKey<LevelStem> getTypeKey();
+
protected Level(
WritableLevelData levelData,
ResourceKey<Level> dimension,
@@ -125,8 +_,26 @@
boolean isClientSide,
boolean isDebug,
long biomeZoomSeed,
- int maxChainedNeighborUpdates
+ int maxChainedNeighborUpdates,
+ org.bukkit.generator.ChunkGenerator gen, // CraftBukkit
+ org.bukkit.generator.BiomeProvider biomeProvider, // CraftBukkit
+ org.bukkit.World.Environment env, // CraftBukkit
+ java.util.function.Function<org.spigotmc.SpigotWorldConfig, // Spigot - create per world config
+ io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator // Paper - create paper world config
) {
+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.generator = gen;
+ this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
+
+ // CraftBukkit Ticks things
+ for (SpawnCategory spawnCategory : SpawnCategory.values()) {
+ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
+ this.ticksPerSpawnCategory.put(spawnCategory, this.getTicksPerSpawn(spawnCategory)); // Paper
+ }
+ }
+
+ // CraftBukkit end
this.levelData = levelData;
this.dimensionTypeRegistration = dimensionTypeRegistration;
final DimensionType dimensionType = dimensionTypeRegistration.value();
@@ -136,12 +_,12 @@
this.worldBorder = new WorldBorder() {
@Override
public double getCenterX() {
- return super.getCenterX() / dimensionType.coordinateScale();
+ return super.getCenterX(); // CraftBukkit
}
@Override
public double getCenterZ() {
- return super.getCenterZ() / dimensionType.coordinateScale();
+ return super.getCenterZ(); // CraftBukkit
}
};
} else {
@@ -154,7 +_,86 @@
this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates);
this.registryAccess = registryAccess;
this.damageSources = new DamageSources(registryAccess);
- }
+
+ // CraftBukkit start
+ this.getWorldBorder().world = (ServerLevel) this;
+ // From PlayerList.setPlayerFileData
+ this.getWorldBorder().addListener(new BorderChangeListener() {
+ @Override
+ public void onBorderSizeSet(WorldBorder border, double size) {
+ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderSizePacket(border), border.world);
+ }
+
+ @Override
+ public void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) {
+ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world);
+ }
+
+ @Override
+ public void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) {
+ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world);
+ }
+
+ @Override
+ public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
+ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world);
+ }
+
+ @Override
+ public void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) {
+ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world);
+ }
+
+ @Override
+ public void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {}
+
+ @Override
+ public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {}
+ });
+ // CraftBukkit end
+ this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
+ this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
+ }
+
+ // Paper start - Cancel hit for vanished players
+ // ret true if no collision
+ public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision,
+ BlockPos position, boolean checkCanSee) {
+ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape)
+ net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision);
+ if (voxelshape.isEmpty()) {
+ return true;
+ }
+
+ voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ());
+ if (voxelshape.isEmpty()) {
+ return true;
+ }
+
+ List<Entity> entities = this.getEntities(null, voxelshape.bounds());
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ Entity entity = entities.get(i);
+
+ if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer
+ && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) {
+ continue;
+ }
+
+ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity));
+ // elide the last check since vanilla calls with entity = null
+ // only we care about the source for the canSee check
+ if (entity.isRemoved() || !entity.blocksBuilding) {
+ continue;
+ }
+
+ if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ // Paper end - Cancel hit for vanished players
@Override
public boolean isClientSide() {
@@ -167,6 +_,13 @@
return null;
}
+ // Paper start
+ public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) {
+ // To be patched over
+ return this.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, context)).getType();
+ }
+ // Paper end
+
public boolean isInWorldBounds(BlockPos pos) {
return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
}
@@ -176,25 +_,88 @@
}
private static boolean isInWorldBoundsHorizontal(BlockPos pos) {
- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000;
+ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk()
}
private static boolean isOutsideSpawnableHeight(int y) {
return y < -20000000 || y >= 20000000;
}
- public LevelChunk getChunkAt(BlockPos pos) {
+ public final LevelChunk getChunkAt(BlockPos pos) { // Paper - help inline
return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
}
@Override
- public LevelChunk getChunk(int chunkX, int chunkZ) {
- return (LevelChunk)this.getChunk(chunkX, chunkZ, ChunkStatus.FULL);
+ public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
+ // Paper start - Perf: make sure loaded chunks get the inlined variant of this function
+ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
+ LevelChunk ifLoaded = cps.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
+ if (ifLoaded != null) {
+ return ifLoaded;
+ }
+ return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
+ // Paper end - Perf: make sure loaded chunks get the inlined variant of this function
+ }
+
+ // Paper start - if loaded
+ @Nullable
+ @Override
+ public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+ return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z);
+ }
+
+ @Override
+ @Nullable
+ public final BlockState getBlockStateIfLoaded(BlockPos pos) {
+ // CraftBukkit start - tree generation
+ if (this.captureTreeGeneration) {
+ CraftBlockState previous = this.capturedBlockStates.get(pos);
+ if (previous != null) {
+ return previous.getHandle();
+ }
+ }
+ // CraftBukkit end
+ if (this.isOutsideBuildHeight(pos)) {
+ return Blocks.VOID_AIR.defaultBlockState();
+ } else {
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);
+
+ return chunk == null ? null : chunk.getBlockState(pos);
+ }
+ }
+
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+
+ return chunk == null ? null : chunk.getFluidState(blockposition);
+ }
+
+ @Override
+ public final boolean hasChunkAt(BlockPos pos) {
+ return getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4) != null; // Paper - Perf: Optimize Level.hasChunkAt(BlockPosition)Z
+ }
+
+ public final boolean isLoadedAndInBounds(BlockPos blockposition) { // Paper - final for inline
+ return getWorldBorder().isWithinBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null;
+ }
+
+ public @Nullable LevelChunk getChunkIfLoaded(int x, int z) { // Overridden in WorldServer for ABI compat which has final
+ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(x, z);
+ }
+ public final @Nullable LevelChunk getChunkIfLoaded(BlockPos blockposition) {
+ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+ }
+
+ // reduces need to do isLoaded before getType
+ public final @Nullable BlockState getBlockStateIfLoadedAndInBounds(BlockPos blockposition) {
+ return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null;
}
@Nullable
@Override
public ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
+ // Paper end
ChunkAccess chunk = this.getChunkSource().getChunk(x, z, chunkStatus, requireChunk);
if (chunk == null && requireChunk) {
throw new IllegalStateException("Should always be able to create a chunk!");
@@ -210,6 +_,22 @@
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) {
+ // CraftBukkit start - tree generation
+ if (this.captureTreeGeneration) {
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
+ BlockState type = getBlockState(pos);
+ if (!type.isDestroyable()) return false;
+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
+ CraftBlockState blockstate = this.capturedBlockStates.get(pos);
+ if (blockstate == null) {
+ blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags);
+ this.capturedBlockStates.put(pos.immutable(), blockstate);
+ }
+ blockstate.setData(state);
+ blockstate.setFlag(flags);
+ return true;
+ }
+ // CraftBukkit end
if (this.isOutsideBuildHeight(pos)) {
return false;
} else if (!this.isClientSide && this.isDebug()) {
@@ -217,11 +_,28 @@
} else {
LevelChunk chunkAt = this.getChunkAt(pos);
Block block = state.getBlock();
- BlockState blockState = chunkAt.setBlockState(pos, state, (flags & 64) != 0);
+ // CraftBukkit start - capture blockstates
+ boolean captured = false;
+ if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) {
+ CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot
+ blockstate.setFlag(flags); // Paper - set flag
+ this.capturedBlockStates.put(pos.immutable(), blockstate);
+ captured = true;
+ }
+ // CraftBukkit end
+
+ BlockState blockState = chunkAt.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
+
if (blockState == null) {
+ // CraftBukkit start - remove blockstate if failed (or the same)
+ if (this.captureBlockStates && captured) {
+ this.capturedBlockStates.remove(pos);
+ }
+ // CraftBukkit end
return false;
} else {
BlockState blockState1 = this.getBlockState(pos);
+ /*
if (blockState1 == state) {
if (blockState != blockState1) {
this.setBlocksDirty(pos, blockState, blockState1);
@@ -249,12 +_,76 @@
this.onBlockStateChange(pos, blockState, blockState1);
}
+ */
+
+ // CraftBukkit start
+ if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates
+ // Modularize client and physic updates
+ // Spigot start
+ try {
+ this.notifyAndUpdatePhysics(pos, chunkAt, blockState, state, blockState1, flags, recursionLeft);
+ } catch (StackOverflowError ex) {
+ Level.lastPhysicsProblem = new BlockPos(pos);
+ }
+ // Spigot end
+ }
+ // CraftBukkit end
return true;
}
}
}
+ // CraftBukkit start - Split off from above in order to directly send client and physic updates
+ public void notifyAndUpdatePhysics(BlockPos blockposition, LevelChunk chunk, BlockState oldBlock, BlockState newBlock, BlockState actualBlock, int i, int j) {
+ BlockState iblockdata = newBlock;
+ BlockState iblockdata1 = oldBlock;
+ BlockState iblockdata2 = actualBlock;
+ if (iblockdata2 == iblockdata) {
+ if (iblockdata1 != iblockdata2) {
+ this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
+ }
+
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
+ this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
+ }
+
+ if ((i & 1) != 0) {
+ this.blockUpdated(blockposition, iblockdata1.getBlock());
+ if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
+ this.updateNeighbourForOutputSignal(blockposition, newBlock.getBlock());
+ }
+ }
+
+ if ((i & 16) == 0 && j > 0) {
+ int k = i & -34;
+
+ // CraftBukkit start
+ iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam
+ CraftWorld world = ((ServerLevel) this).getWorld();
+ boolean cancelledUpdates = false; // Paper - Fix block place logic
+ if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent
+ BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata));
+ this.getCraftServer().getPluginManager().callEvent(event);
+
+ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic
+ }
+ // CraftBukkit end
+ if (!cancelledUpdates) { // Paper - Fix block place logic
+ iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
+ iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
+ } // Paper - Fix block place logic
+ }
+
+ // CraftBukkit start - SPIGOT-5710
+ if (!this.preventPoiUpdated) {
+ this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
+ }
+ // CraftBukkit end
+ }
+ }
+ // CraftBukkit end
+
public void onBlockStateChange(BlockPos pos, BlockState blockState, BlockState newState) {
}
@@ -271,13 +_,31 @@
return false;
} else {
FluidState fluidState = this.getFluidState(pos);
- if (!(blockState.getBlock() instanceof BaseFireBlock)) {
- this.levelEvent(2001, pos, Block.getId(blockState));
+ // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar
+ // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent,
+ // it doesn't imply destruction of a block that plays a sound effect / drops an item.
+ boolean playEffect = true;
+ BlockState effectType = blockState;
+ int xp = blockState.getBlock().getExpDrop(blockState, (ServerLevel) this, pos, ItemStack.EMPTY, true);
+ if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) {
+ com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluidState.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, dropBlock);
+ if (!event.callEvent()) {
+ return false;
+ }
+ effectType = ((CraftBlockData) event.getEffectBlock()).getState();
+ playEffect = event.playEffect();
+ dropBlock = event.willDrop();
+ xp = event.getExpToDrop();
+ }
+ // Paper end - BlockDestroyEvent
+ if (playEffect && !(blockState.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent
+ this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent
}
if (dropBlock) {
BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null;
- Block.dropResources(blockState, this, pos, blockEntity, entity, ItemStack.EMPTY);
+ Block.dropResources(blockState, this, pos, blockEntity, entity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
+ blockState.getBlock().popExperience((ServerLevel) this, pos, xp, entity); // Paper - Properly handle xp dropping; custom amount
}
boolean flag = this.setBlock(pos, fluidState.createLegacyBlock(), 3, recursionLeft);
@@ -344,10 +_,18 @@
@Override
public BlockState getBlockState(BlockPos pos) {
+ // CraftBukkit start - tree generation
+ if (this.captureTreeGeneration) {
+ CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper
+ if (previous != null) {
+ return previous.getHandle();
+ }
+ }
+ // CraftBukkit end
if (this.isOutsideBuildHeight(pos)) {
return Blocks.VOID_AIR.defaultBlockState();
} else {
- LevelChunk chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
+ ChunkAccess chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine
return chunk.getBlockState(pos);
}
}
@@ -454,32 +_,54 @@
this.pendingBlockEntityTickers.clear();
}
- Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
+ // Spigot start
+ // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
boolean runsNormally = this.tickRateManager().runsNormally();
- while (iterator.hasNext()) {
- TickingBlockEntity tickingBlockEntity = iterator.next();
+ int tilesThisCycle = 0;
+ var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
+ toRemove.add(null); // Paper - Fix MC-117075
+ for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
+ this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0;
+ TickingBlockEntity tickingBlockEntity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition);
+ // Spigot end
if (tickingBlockEntity.isRemoved()) {
- iterator.remove();
+ // Spigot start
+ tilesThisCycle--;
+ toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
+ // Spigot end
} else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
tickingBlockEntity.tick();
}
}
+ this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
this.tickingBlockEntities = false;
profilerFiller.pop();
+ this.spigotConfig.currentPrimedTnt = 0; // Spigot
}
public <T extends Entity> void guardEntityTick(Consumer<T> consumerEntity, T entity) {
try {
consumerEntity.accept(entity);
} catch (Throwable var6) {
- CrashReport crashReport = CrashReport.forThrowable(var6, "Ticking entity");
- CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being ticked");
- entity.fillCrashReportCategory(crashReportCategory);
- throw new ReportedException(crashReport);
+ // Paper start - Prevent block entity and entity crashes
+ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
+ MinecraftServer.LOGGER.error(msg, var6);
+ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ // Paper end - Prevent block entity and entity crashes
}
}
+
+ // Paper start - Option to prevent armor stands from doing entity lookups
+ @Override
+ public boolean noCollision(@Nullable Entity entity, AABB box) {
+ if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups)
+ return false;
+ return LevelAccessor.super.noCollision(entity, box);
+ }
+ // Paper end - Option to prevent armor stands from doing entity lookups
public boolean shouldTickDeath(Entity entity) {
return true;
@@ -599,6 +_,19 @@
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
+ // CraftBukkit start
+ return this.getBlockEntity(pos, true);
+ }
+
+ @Nullable
+ public BlockEntity getBlockEntity(BlockPos pos, boolean validate) {
+ // Paper start - Perf: Optimize capturedTileEntities lookup
+ net.minecraft.world.level.block.entity.BlockEntity blockEntity;
+ if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) {
+ return blockEntity;
+ }
+ // Paper end - Perf: Optimize capturedTileEntities lookup
+ // CraftBukkit end
if (this.isOutsideBuildHeight(pos)) {
return null;
} else {
@@ -611,6 +_,12 @@
public void setBlockEntity(BlockEntity blockEntity) {
BlockPos blockPos = blockEntity.getBlockPos();
if (!this.isOutsideBuildHeight(blockPos)) {
+ // CraftBukkit start
+ if (this.captureBlockStates) {
+ this.capturedTileEntities.put(blockPos.immutable(), blockEntity);
+ return;
+ }
+ // CraftBukkit end
this.getChunkAt(blockPos).addAndRegisterBlockEntity(blockEntity);
}
}
@@ -987,7 +_,8 @@
BLOCK("block"),
MOB("mob"),
TNT("tnt"),
- TRIGGER("trigger");
+ TRIGGER("trigger"),
+ STANDARD("standard"); // CraftBukkit - Add STANDARD which will always use Explosion.Effect.DESTROY
public static final Codec<Level.ExplosionInteraction> CODEC = StringRepresentable.fromEnum(Level.ExplosionInteraction::values);
private final String id;

View File

@@ -0,0 +1,9 @@
--- a/net/minecraft/world/level/LevelAccessor.java
+++ b/net/minecraft/world/level/LevelAccessor.java
@@ -101,4 +_,6 @@
default void gameEvent(ResourceKey<GameEvent> gameEvent, BlockPos pos, GameEvent.Context context) {
this.gameEvent(this.registryAccess().lookupOrThrow(Registries.GAME_EVENT).getOrThrow(gameEvent), pos, context);
}
+
+ net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit
}

View File

@@ -0,0 +1,12 @@
--- a/net/minecraft/world/level/LevelReader.java
+++ b/net/minecraft/world/level/LevelReader.java
@@ -26,6 +_,9 @@
@Nullable
ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk);
+ @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading)
+ @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);}
+
@Deprecated
boolean hasChunk(int chunkX, int chunkZ);

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/level/LevelWriter.java
+++ b/net/minecraft/world/level/LevelWriter.java
@@ -27,4 +_,10 @@
default boolean addFreshEntity(Entity entity) {
return false;
}
+
+ // CraftBukkit start
+ default boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+ return false;
+ }
+ // CraftBukkit end
}

View File

@@ -0,0 +1,234 @@
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -49,6 +_,13 @@
import net.minecraft.world.phys.Vec3;
import org.slf4j.Logger;
+// CraftBukkit start
+import net.minecraft.world.level.storage.LevelData;
+import org.bukkit.craftbukkit.util.CraftSpawnCategory;
+import org.bukkit.entity.SpawnCategory;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+// CraftBukkit end
+
public final class NaturalSpawner {
private static final Logger LOGGER = LogUtils.getLogger();
private static final int MIN_SPAWN_DISTANCE = 24;
@@ -72,6 +_,13 @@
if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) {
MobCategory category = entity.getType().getCategory();
if (category != MobCategory.MISC) {
+ // Paper start - Only count natural spawns
+ if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
+ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL ||
+ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
+ continue;
+ }
+ // Paper end - Only count natural spawns
BlockPos blockPos = entity.blockPosition();
chunkGetter.query(ChunkPos.asLong(blockPos), chunk -> {
MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
@@ -96,17 +_,36 @@
return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
}
+ // CraftBukkit start - add server
public static List<MobCategory> getFilteredSpawningCategories(
- NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives
+ NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives, ServerLevel worldserver
) {
+ LevelData worlddata = worldserver.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
+ // CraftBukkit end
List<MobCategory> list = new ArrayList<>(SPAWNING_CATEGORIES.length);
-
- for (MobCategory mobCategory : SPAWNING_CATEGORIES) {
- if ((spawnFriendlies || !mobCategory.isFriendly())
- && (spawnEnemies || mobCategory.isFriendly())
- && (spawnPassives || !mobCategory.isPersistent())
- && spawnState.canSpawnForCategoryGlobal(mobCategory)) {
- list.add(mobCategory);
+ MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES;
+ int i = aenumcreaturetype.length;
+
+ for (int j = 0; j < i; ++j) {
+ MobCategory enumcreaturetype = SPAWNING_CATEGORIES[j];
+ // CraftBukkit start - Use per-world spawn limits
+ boolean spawnThisTick = true;
+ int limit = enumcreaturetype.getMaxInstancesPerChunk();
+ SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype);
+ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
+ spawnThisTick = worldserver.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % worldserver.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
+ limit = worldserver.getWorld().getSpawnLimit(spawnCategory);
+ }
+
+ if (!spawnThisTick || limit == 0) {
+ continue;
+ }
+
+ if ((spawnFriendlies || !enumcreaturetype.isFriendly())
+ && (spawnEnemies || enumcreaturetype.isFriendly())
+ && (spawnPassives || !enumcreaturetype.isPersistent())
+ && spawnState.canSpawnForCategoryGlobal(enumcreaturetype)) {
+ list.add(enumcreaturetype);
}
}
@@ -126,6 +_,16 @@
profilerFiller.pop();
}
+ // Paper start - Add mobcaps commands
+ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) {
+ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category));
+ if (categoryLimit < 1) {
+ return categoryLimit;
+ }
+ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
+ }
+ // Paper end - Add mobcaps commands
+
public static void spawnCategoryForChunk(
MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback
) {
@@ -151,8 +_,8 @@
StructureManager structureManager = level.structureManager();
ChunkGenerator generator = level.getChunkSource().getGenerator();
int y = pos.getY();
- BlockState blockState = chunk.getBlockState(pos);
- if (!blockState.isRedstoneConductor(chunk, pos)) {
+ BlockState blockState = chunk.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
+ if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
int i = 0;
@@ -174,7 +_,7 @@
Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, false);
if (nearestPlayer != null) {
double d2 = nearestPlayer.distanceToSqr(d, y, d1);
- if (isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) {
+ if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn
if (spawnerData == null) {
Optional<MobSpawnSettings.SpawnerData> randomSpawnMobAt = getRandomSpawnMobAt(
level, structureManager, generator, category, level.random, mutableBlockPos
@@ -187,8 +_,13 @@
ceil = spawnerData.minCount + level.random.nextInt(1 + spawnerData.maxCount - spawnerData.minCount);
}
- if (isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2)
- && filter.test(spawnerData.type, mutableBlockPos, chunk)) {
+ // Paper start - PreCreatureSpawnEvent
+ PreSpawnStatus doSpawning = isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2);
+ if (doSpawning == PreSpawnStatus.ABORT) {
+ return;
+ }
+ if (doSpawning == PreSpawnStatus.SUCCESS && filter.test(spawnerData.type, mutableBlockPos, chunk)) {
+ // Paper end - PreCreatureSpawnEvent
Mob mobForSpawn = getMobForSpawn(level, spawnerData.type);
if (mobForSpawn == null) {
return;
@@ -199,10 +_,15 @@
spawnGroupData = mobForSpawn.finalizeSpawn(
level, level.getCurrentDifficultyAt(mobForSpawn.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData
);
- i++;
- i3++;
- level.addFreshEntityWithPassengers(mobForSpawn);
- callback.run(mobForSpawn, chunk);
+ // CraftBukkit start
+ // SPIGOT-7045: Give ocelot babies back their special spawn reason. Note: This is the only modification required as ocelots count as monsters which means they only spawn during normal chunk ticking and do not spawn during chunk generation as starter mobs.
+ level.addFreshEntityWithPassengers(mobForSpawn, (mobForSpawn instanceof net.minecraft.world.entity.animal.Ocelot && !((org.bukkit.entity.Ageable) mobForSpawn.getBukkitEntity()).isAdult()) ? SpawnReason.OCELOT_BABY : SpawnReason.NATURAL);
+ if (!mobForSpawn.isRemoved()) {
+ ++i;
+ ++i3;
+ callback.run(mobForSpawn, chunk);
+ }
+ // CraftBukkit end
if (i >= mobForSpawn.getMaxSpawnClusterSize()) {
return;
}
@@ -225,7 +_,15 @@
&& (Objects.equals(new ChunkPos(pos), chunk.getPos()) || level.isNaturalSpawningAllowed(pos));
}
- private static boolean isValidSpawnPostitionForType(
+ // Paper start - PreCreatureSpawnEvent
+ private enum PreSpawnStatus {
+ FAIL,
+ SUCCESS,
+ CANCELLED,
+ ABORT
+ }
+ private static PreSpawnStatus isValidSpawnPostitionForType(
+ // Paper end - PreCreatureSpawnEvent
ServerLevel level,
MobCategory category,
StructureManager structureManager,
@@ -235,16 +_,20 @@
double distance
) {
EntityType<?> entityType = data.type;
- return entityType.getCategory() != MobCategory.MISC
- && (
- entityType.canSpawnFarFromPlayer()
- || !(distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance())
- )
- && entityType.canSummon()
- && canSpawnMobAt(level, structureManager, generator, category, data, pos)
- && SpawnPlacements.isSpawnPositionOk(entityType, level, pos)
- && SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
- && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
+
+ // Paper start - PreCreatureSpawnEvent
+ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+ io.papermc.paper.util.MCUtil.toLocation(level, pos),
+ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType), SpawnReason.NATURAL
+ );
+ if (!event.callEvent()) {
+ if (event.shouldAbortSpawn()) {
+ return PreSpawnStatus.ABORT;
+ }
+ return PreSpawnStatus.CANCELLED;
+ }
+ // Paper end - PreCreatureSpawnEvent
+ return entityType.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entityType.canSpawnFarFromPlayer() && distance > (double) (entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entityType.canSummon() && NaturalSpawner.canSpawnMobAt(level, structureManager, generator, category, data, pos) ? (!SpawnPlacements.isSpawnPositionOk(entityType, level, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random) ? PreSpawnStatus.FAIL : level.noCollision(entityType.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent
}
@Nullable
@@ -258,6 +_,7 @@
LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(entityType));
} catch (Exception var4) {
LOGGER.warn("Failed to create mob", (Throwable)var4);
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var4); // Paper - ServerExceptionEvent
}
return null;
@@ -364,6 +_,7 @@
entity = spawnerData.type.create(levelAccessor.getLevel(), EntitySpawnReason.NATURAL);
} catch (Exception var27) {
LOGGER.warn("Failed to create mob", (Throwable)var27);
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var27); // Paper - ServerExceptionEvent
continue;
}
@@ -381,7 +_,7 @@
EntitySpawnReason.CHUNK_GENERATION,
spawnGroupData
);
- levelAccessor.addFreshEntityWithPassengers(mob);
+ levelAccessor.addFreshEntityWithPassengers(mob, SpawnReason.CHUNK_GEN); // CraftBukkit
flag = true;
}
}
@@ -501,8 +_,10 @@
return this.unmodifiableMobCategoryCounts;
}
- boolean canSpawnForCategoryGlobal(MobCategory category) {
- int i = category.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
+ // CraftBukkit start
+ boolean canSpawnForCategoryGlobal(MobCategory category, int limit) {
+ int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
+ // CraftBukkit end
return this.mobCategoryCounts.getInt(category) < i;
}

View File

@@ -0,0 +1,49 @@
--- a/net/minecraft/world/level/PathNavigationRegion.java
+++ b/net/minecraft/world/level/PathNavigationRegion.java
@@ -8,6 +_,7 @@
import net.minecraft.core.Holder;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
+import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
@@ -66,13 +_,37 @@
private ChunkAccess getChunk(int x, int z) {
int i = x - this.centerX;
int i1 = z - this.centerZ;
- if (i >= 0 && i < this.chunks.length && i1 >= 0 && i1 < this.chunks[i].length) {
+ if (i >= 0 && i < this.chunks.length && i1 >= 0 && i1 < this.chunks[i].length) { // Paper - if this changes, update getChunkIfLoaded below
ChunkAccess chunkAccess = this.chunks[i][i1];
return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(x, z), this.plains.get()));
} else {
return new EmptyLevelChunk(this.level, new ChunkPos(x, z), this.plains.get());
}
}
+
+ // Paper start - if loaded util
+ private @Nullable ChunkAccess getChunkIfLoaded(int x, int z) {
+ // Based on getChunk(int, int)
+ int xx = x - this.centerX;
+ int zz = z - this.centerZ;
+
+ if (xx >= 0 && xx < this.chunks.length && zz >= 0 && zz < this.chunks[xx].length) {
+ return this.chunks[xx][zz];
+ }
+ return null;
+ }
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+ return chunk == null ? null : chunk.getFluidState(blockposition);
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+ return chunk == null ? null : chunk.getBlockState(blockposition);
+ }
+ // Paper end
@Override
public WorldBorder getWorldBorder() {

View File

@@ -0,0 +1,332 @@
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -33,6 +_,18 @@
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import net.minecraft.world.entity.boss.EnderDragonPart;
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.craftbukkit.util.CraftLocation;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.Location;
+import org.bukkit.event.block.BlockExplodeEvent;
+// CraftBukkit end
+
public class ServerExplosion implements Explosion {
private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator();
private static final int MAX_DROPS_PER_COMBINED_STACK = 16;
@@ -47,6 +_,11 @@
private final DamageSource damageSource;
private final ExplosionDamageCalculator damageCalculator;
private final Map<Player, Vec3> hitPlayers = new HashMap<>();
+ // CraftBukkit - add field
+ public boolean wasCanceled = false;
+ public float yield;
+ // CraftBukkit end
+ public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source
public ServerExplosion(
ServerLevel level,
@@ -60,12 +_,13 @@
) {
this.level = level;
this.source = source;
- this.radius = radius;
+ this.radius = (float) Math.max(radius, 0.0); // CraftBukkit - clamp bad values
this.center = center;
this.fire = fire;
this.blockInteraction = blockInteraction;
this.damageSource = damageSource == null ? level.damageSources().explosion(this) : damageSource;
this.damageCalculator = damageCalculator == null ? this.makeDamageCalculator(source) : damageCalculator;
+ this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
}
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
@@ -139,7 +_,8 @@
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
BlockPos blockPos = BlockPos.containing(d3, d4, d5);
BlockState blockState = this.level.getBlockState(blockPos);
- FluidState fluidState = this.level.getFluidState(blockPos);
+ if (!blockState.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
+ FluidState fluidState = blockState.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
if (!this.level.isInWorldBounds(blockPos)) {
break;
}
@@ -152,6 +_,15 @@
if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, f)) {
set.add(blockPos);
+ // Paper start - prevent headless pistons from forming
+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.getBlock() == Blocks.MOVING_PISTON) {
+ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockPos);
+ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
+ net.minecraft.core.Direction direction = blockState.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
+ set.add(blockPos.relative(direction.getOpposite()));
+ }
+ }
+ // Paper end - prevent headless pistons from forming
}
d3 += d * 0.3F;
@@ -174,8 +_,8 @@
int floor3 = Mth.floor(this.center.y + f + 1.0);
int floor4 = Mth.floor(this.center.z - f - 1.0);
int floor5 = Mth.floor(this.center.z + f + 1.0);
-
- for (Entity entity : this.level.getEntities(this.source, new AABB(floor, floor2, floor4, floor1, floor3, floor5))) {
+ List <Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB(floor, floor2, floor4, floor1, floor3, floor5), (com.google.common.base.Predicate<Entity>) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
+ for (Entity entity : list) { // Paper - used in loop
if (!entity.ignoreExplosion(this)) {
double d = Math.sqrt(entity.distanceToSqr(this.center)) / f;
if (d <= 1.0) {
@@ -189,15 +_,43 @@
d3 /= squareRoot;
boolean shouldDamageEntity = this.damageCalculator.shouldDamageEntity(this, entity);
float knockbackMultiplier = this.damageCalculator.getKnockbackMultiplier(entity);
- float f1 = !shouldDamageEntity && knockbackMultiplier == 0.0F ? 0.0F : getSeenPercent(this.center, entity);
+ float f1 = !shouldDamageEntity && knockbackMultiplier == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions
if (shouldDamageEntity) {
- entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
+ // CraftBukkit start
+
+ // Special case ender dragon only give knockback if no damage is cancelled
+ // Thinks to note:
+ // - Setting a velocity to a ComplexEntityPart is ignored (and therefore not needed)
+ // - Damaging ComplexEntityPart while forward the damage to EntityEnderDragon
+ // - Damaging EntityEnderDragon does nothing
+ // - EntityEnderDragon hitbock always covers the other parts and is therefore always present
+ if (entity instanceof EnderDragonPart) {
+ continue;
+ }
+
+ entity.lastDamageCancelled = false;
+
+ if (entity instanceof EnderDragon) {
+ for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
+ // Calculate damage separately for each EntityComplexPart
+ if (list.contains(entityComplexPart)) {
+ entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
+ }
+ }
+ } else {
+ entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
+ }
+
+ if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
+ continue;
+ }
+ // CraftBukkit end
}
double d4 = (1.0 - d) * f1 * knockbackMultiplier;
double d5;
if (entity instanceof LivingEntity livingEntity) {
- d5 = d4 * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE));
+ d5 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d4 * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); // Paper
} else {
d5 = d4;
}
@@ -206,10 +_,18 @@
d2 *= d5;
d3 *= d5;
Vec3 vec3 = new Vec3(d1, d2, d3);
+ // CraftBukkit start - Call EntityKnockbackEvent
+ if (entity instanceof LivingEntity) {
+ // Paper start - knockback events
+ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d5, vec3);
+ vec3 = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback());
+ // Paper end - knockback events
+ }
+ // CraftBukkit end
entity.push(vec3);
if (entity instanceof Player) {
Player player = (Player)entity;
- if (!player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying)) {
+ if (!player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback
this.hitPlayers.put(player, vec3);
}
}
@@ -225,7 +_,61 @@
List<ServerExplosion.StackCollector> list = new ArrayList<>();
Util.shuffle(blocks, this.level.random);
+ // CraftBukkit start
+ org.bukkit.World bworld = this.level.getWorld();
+ Location location = CraftLocation.toBukkit(this.center, bworld);
+
+ List<org.bukkit.block.Block> blockList = new ObjectArrayList<>();
+ for (int i1 = blocks.size() - 1; i1 >= 0; i1--) {
+ BlockPos cpos = blocks.get(i1);
+ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ());
+ if (!bblock.getType().isAir()) {
+ blockList.add(bblock);
+ }
+ }
+
+ List<org.bukkit.block.Block> bukkitBlocks;
+
+ if (this.source != null) {
+ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blockList, this.yield, this.getBlockInteraction());
+ this.wasCanceled = event.isCancelled();
+ bukkitBlocks = event.blockList();
+ this.yield = event.getYield();
+ } else {
+ org.bukkit.block.Block block = location.getBlock();
+ org.bukkit.block.BlockState blockState = (this.damageSource.getDirectBlockState() != null) ? this.damageSource.getDirectBlockState() : block.getState();
+ BlockExplodeEvent event = CraftEventFactory.callBlockExplodeEvent(block, blockState, blockList, this.yield, this.getBlockInteraction());
+ this.wasCanceled = event.isCancelled();
+ bukkitBlocks = event.blockList();
+ this.yield = event.getYield();
+ }
+
+ blocks.clear();
+
+ for (org.bukkit.block.Block bblock : bukkitBlocks) {
+ BlockPos coords = new BlockPos(bblock.getX(), bblock.getY(), bblock.getZ());
+ blocks.add(coords);
+ }
+
+ if (this.wasCanceled) {
+ return;
+ }
+ // CraftBukkit end
+
for (BlockPos blockPos : blocks) {
+ // CraftBukkit start - TNTPrimeEvent
+ BlockState iblockdata = this.level.getBlockState(blockPos);
+ Block block = iblockdata.getBlock();
+ if (block instanceof net.minecraft.world.level.block.TntBlock) {
+ Entity sourceEntity = this.source == null ? null : this.source;
+ BlockPos sourceBlock = sourceEntity == null ? BlockPos.containing(this.center) : null;
+ if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) {
+ this.level.sendBlockUpdated(blockPos, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client
+ continue;
+ }
+ }
+ // CraftBukkit end
+
this.level
.getBlockState(blockPos)
.onExplosionHit(this.level, blockPos, this, (itemStack, blockPos1) -> addOrAppendStack(list, itemStack, blockPos1));
@@ -239,12 +_,21 @@
private void createFire(List<BlockPos> blocks) {
for (BlockPos blockPos : blocks) {
if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockPos).isAir() && this.level.getBlockState(blockPos.below()).isSolidRender()) {
- this.level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level, blockPos));
+ // CraftBukkit start - Ignition by explosion
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockPos, this).isCancelled()) {
+ this.level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level, blockPos));
+ }
+ // CraftBukkit end
}
}
}
public void explode() {
+ // CraftBukkit start
+ if (this.radius < 0.1F) {
+ return;
+ }
+ // CraftBukkit end
this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center);
List<BlockPos> list = this.calculateExplodedPositions();
this.hurtEntities();
@@ -261,6 +_,7 @@
}
private static void addOrAppendStack(List<ServerExplosion.StackCollector> stackCollectors, ItemStack stack, BlockPos pos) {
+ if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-5425
for (ServerExplosion.StackCollector stackCollector : stackCollectors) {
stackCollector.tryMerge(stack);
if (stack.isEmpty()) {
@@ -342,4 +_,86 @@
}
}
}
+
+
+ // Paper start - Optimize explosions
+ private float getBlockDensity(Vec3 vec3d, Entity entity) {
+ if (!this.level.paperConfig().environment.optimizeExplosions) {
+ return getSeenPercent(vec3d, entity);
+ }
+ CacheKey key = new CacheKey(this, entity.getBoundingBox());
+ Float blockDensity = this.level.explosionDensityCache.get(key);
+ if (blockDensity == null) {
+ blockDensity = getSeenPercent(vec3d, entity);
+ this.level.explosionDensityCache.put(key, blockDensity);
+ }
+
+ return blockDensity;
+ }
+
+ static class CacheKey {
+ private final Level world;
+ private final double posX, posY, posZ;
+ private final double minX, minY, minZ;
+ private final double maxX, maxY, maxZ;
+
+ public CacheKey(Explosion explosion, AABB aabb) {
+ this.world = explosion.level();
+ this.posX = explosion.center().x;
+ this.posY = explosion.center().y;
+ this.posZ = explosion.center().z;
+ this.minX = aabb.minX;
+ this.minY = aabb.minY;
+ this.minZ = aabb.minZ;
+ this.maxX = aabb.maxX;
+ this.maxY = aabb.maxY;
+ this.maxZ = aabb.maxZ;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CacheKey cacheKey = (CacheKey) o;
+
+ if (Double.compare(cacheKey.posX, posX) != 0) return false;
+ if (Double.compare(cacheKey.posY, posY) != 0) return false;
+ if (Double.compare(cacheKey.posZ, posZ) != 0) return false;
+ if (Double.compare(cacheKey.minX, minX) != 0) return false;
+ if (Double.compare(cacheKey.minY, minY) != 0) return false;
+ if (Double.compare(cacheKey.minZ, minZ) != 0) return false;
+ if (Double.compare(cacheKey.maxX, maxX) != 0) return false;
+ if (Double.compare(cacheKey.maxY, maxY) != 0) return false;
+ if (Double.compare(cacheKey.maxZ, maxZ) != 0) return false;
+ return world.equals(cacheKey.world);
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = world.hashCode();
+ temp = Double.doubleToLongBits(posX);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(posY);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(posZ);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minX);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minY);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minZ);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxX);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxY);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxZ);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+ }
+ // Paper end
}

View File

@@ -0,0 +1,22 @@
--- a/net/minecraft/world/level/ServerLevelAccessor.java
+++ b/net/minecraft/world/level/ServerLevelAccessor.java
@@ -7,6 +_,17 @@
ServerLevel getLevel();
default void addFreshEntityWithPassengers(Entity entity) {
- entity.getSelfAndPassengers().forEach(this::addFreshEntity);
- }
+ // CraftBukkit start
+ this.addFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
+ default void addFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+ entity.getSelfAndPassengers().forEach((e) -> this.addFreshEntity(e, reason));
+ }
+
+ @Override
+ default ServerLevel getMinecraftWorld() {
+ return this.getLevel();
+ }
+ // CraftBukkit end
}

View File

@@ -0,0 +1,39 @@
--- a/net/minecraft/world/level/StructureManager.java
+++ b/net/minecraft/world/level/StructureManager.java
@@ -48,7 +_,13 @@
}
public List<StructureStart> startsForStructure(ChunkPos chunkPos, Predicate<Structure> structurePredicate) {
- Map<Structure, LongSet> allReferences = this.level.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
+ // Paper start - Fix swamp hut cat generation deadlock
+ return this.startsForStructure(chunkPos, structurePredicate, null);
+ }
+
+ public List<StructureStart> startsForStructure(ChunkPos chunkPos, Predicate<Structure> structurePredicate, @Nullable ServerLevelAccessor levelAccessor) {
+ Map<Structure, LongSet> allReferences = (levelAccessor == null ? this.level : levelAccessor).getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
+ // Paper end - Fix swamp hut cat generation deadlock
Builder<StructureStart> builder = ImmutableList.builder();
for (Entry<Structure, LongSet> entry : allReferences.entrySet()) {
@@ -118,10 +_,20 @@
}
public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate<Holder<Structure>> predicate) {
+ // Paper start - Fix swamp hut cat generation deadlock
+ return this.getStructureWithPieceAt(pos, predicate, null);
+ }
+
+ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey<Structure> tag, @Nullable ServerLevelAccessor levelAccessor) {
+ return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor);
+ }
+
+ public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate<Holder<Structure>> predicate, @Nullable ServerLevelAccessor levelAccessor) {
+ // Paper end - Fix swamp hut cat generation deadlock
Registry<Structure> registry = this.registryAccess().lookupOrThrow(Registries.STRUCTURE);
for (StructureStart structureStart : this.startsForStructure(
- new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false)
+ new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock
)) {
if (this.structureHasPieceAt(pos, structureStart)) {
return structureStart;