593 lines
29 KiB
Diff
593 lines
29 KiB
Diff
--- a/net/minecraft/world/level/Level.java
|
|
+++ b/net/minecraft/world/level/Level.java
|
|
@@ -83,6 +_,16 @@
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.scores.Scoreboard;
|
|
|
|
+// CraftBukkit start
|
|
+import java.util.Map;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import org.bukkit.craftbukkit.CraftServer;
|
|
+import org.bukkit.craftbukkit.CraftWorld;
|
|
+import org.bukkit.craftbukkit.block.CraftBlockState;
|
|
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
+import org.bukkit.entity.SpawnCategory;
|
|
+// CraftBukkit end
|
|
+
|
|
public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, 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"));
|
|
@@ -121,6 +_,57 @@
|
|
private final DamageSources damageSources;
|
|
private long subTickCount;
|
|
|
|
+ // CraftBukkit start
|
|
+ private final CraftWorld world;
|
|
+ public boolean pvpMode;
|
|
+ public @Nullable org.bukkit.generator.ChunkGenerator generator;
|
|
+
|
|
+ public boolean captureBlockStates = false;
|
|
+ public boolean captureTreeGeneration = false;
|
|
+ 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
|
|
+ @Nullable
|
|
+ public List<net.minecraft.world.entity.item.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 @Nullable BlockPos lastPhysicsProblem; // Spigot
|
|
+ private int tileTickPosition;
|
|
+ public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new java.util.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) org.bukkit.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(org.bukkit.craftbukkit.util.CraftSpawnCategory.toNMS(spawnCategory));
|
|
+ if (perWorld >= 0) {
|
|
+ return perWorld;
|
|
+ }
|
|
+ return this.getCraftServer().getTicksPerSpawns(spawnCategory);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
+ public abstract ResourceKey<net.minecraft.world.level.dimension.LevelStem> getTypeKey();
|
|
+
|
|
protected Level(
|
|
WritableLevelData levelData,
|
|
ResourceKey<Level> dimension,
|
|
@@ -129,8 +_,24 @@
|
|
boolean isClientSide,
|
|
boolean isDebug,
|
|
long biomeZoomSeed,
|
|
- int maxChainedNeighborUpdates
|
|
+ int maxChainedNeighborUpdates,
|
|
+ @Nullable org.bukkit.generator.ChunkGenerator generator, // Paper
|
|
+ @Nullable org.bukkit.generator.BiomeProvider biomeProvider, // Paper
|
|
+ org.bukkit.World.Environment environment, // Paper
|
|
+ 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) levelData).getLevelName()); // Spigot
|
|
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
|
|
+ this.generator = generator;
|
|
+ this.world = new CraftWorld((ServerLevel) this, generator, biomeProvider, environment);
|
|
+
|
|
+ for (SpawnCategory spawnCategory : SpawnCategory.values()) {
|
|
+ if (org.bukkit.craftbukkit.util.CraftSpawnCategory.isValidForLimits(spawnCategory)) {
|
|
+ this.ticksPerSpawnCategory.put(spawnCategory, this.getTicksPerSpawn(spawnCategory));
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.levelData = levelData;
|
|
this.dimensionTypeRegistration = dimensionTypeRegistration;
|
|
final DimensionType dimensionType = dimensionTypeRegistration.value();
|
|
@@ -140,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 {
|
|
@@ -158,7 +_,84 @@
|
|
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 net.minecraft.world.level.border.BorderChangeListener() {
|
|
+ @Override
|
|
+ public void onBorderSizeSet(WorldBorder border, double size) {
|
|
+ Level.this.getCraftServer().getHandle().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket(border), border.world);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) {
|
|
+ Level.this.getCraftServer().getHandle().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket(border), border.world);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) {
|
|
+ Level.this.getCraftServer().getHandle().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket(border), border.world);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
|
|
+ Level.this.getCraftServer().getHandle().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket(border), border.world);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) {
|
|
+ Level.this.getCraftServer().getHandle().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket(border), border.world);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {}
|
|
+
|
|
+ @Override
|
|
+ public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {}
|
|
+ });
|
|
+ // CraftBukkit end
|
|
+ }
|
|
+
|
|
+ // Paper start - Cancel hit for vanished players
|
|
+ // ret true if no collision
|
|
+ public final boolean checkEntityCollision(BlockState state, Entity source, net.minecraft.world.phys.shapes.CollisionContext collisionContext,
|
|
+ BlockPos position, boolean checkCanSee) {
|
|
+ // Copied from CollisionGetter#isUnobstructed(BlockState, BlockPos, CollisionContext) & EntityGetter#isUnobstructed(Entity, VoxelShape)
|
|
+ net.minecraft.world.phys.shapes.VoxelShape collisionShape = state.getCollisionShape(this, position, collisionContext);
|
|
+ if (collisionShape.isEmpty()) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ collisionShape = collisionShape.move((double) position.getX(), (double) position.getY(), (double) position.getZ());
|
|
+ if (collisionShape.isEmpty()) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ List<Entity> entities = this.getEntities(null, collisionShape.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.isRemoved() && entity1.blocksBuilding && (entity == null || !entity1.isPassengerOfSameVehicle(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(collisionShape, 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() {
|
|
@@ -171,8 +_,15 @@
|
|
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);
|
|
+ return pos.isInsideBuildHeightAndWorldBoundsHorizontal(this); // Paper - Perf: Optimize isInWorldBounds
|
|
}
|
|
|
|
public static boolean isInSpawnableBounds(BlockPos pos) {
|
|
@@ -180,21 +_,86 @@
|
|
}
|
|
|
|
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() and isInsideBuildHeightAndWorldBoundsHorizontal
|
|
}
|
|
|
|
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
|
|
+ @Nullable
|
|
+ public final FluidState getFluidIfLoaded(BlockPos pos) {
|
|
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);
|
|
+
|
|
+ return chunk == null ? null : chunk.getFluidState(pos);
|
|
+ }
|
|
+
|
|
+ @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 pos) { // Paper - final for inline
|
|
+ return getWorldBorder().isWithinBounds(pos) && getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) != null;
|
|
+ }
|
|
+
|
|
+ public @Nullable LevelChunk getChunkIfLoaded(int x, int z) { // Overridden in ServerLevel for ABI compat which has final
|
|
+ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(x, z);
|
|
+ }
|
|
+
|
|
+ public final @Nullable LevelChunk getChunkIfLoaded(BlockPos pos) {
|
|
+ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);
|
|
+ }
|
|
+
|
|
+ // reduces need to do isLoaded before getType
|
|
+ public final @Nullable BlockState getBlockStateIfLoadedAndInBounds(BlockPos pos) {
|
|
+ return getWorldBorder().isWithinBounds(pos) ? getBlockStateIfLoaded(pos) : null;
|
|
+ }
|
|
+ // Paper end
|
|
|
|
@Nullable
|
|
@Override
|
|
@@ -214,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 = org.bukkit.craftbukkit.block.CapturedBlockState.getTreeBlockState(this, pos, flags);
|
|
+ this.capturedBlockStates.put(pos.immutable(), blockstate);
|
|
+ }
|
|
+ blockstate.setData(state);
|
|
+ blockstate.setFlags(flags);
|
|
+ return true;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (this.isOutsideBuildHeight(pos)) {
|
|
return false;
|
|
} else if (!this.isClientSide && this.isDebug()) {
|
|
@@ -221,11 +_,31 @@
|
|
} else {
|
|
LevelChunk chunkAt = this.getChunkAt(pos);
|
|
Block block = state.getBlock();
|
|
+ // CraftBukkit start - capture blockstates
|
|
+ boolean captured = false;
|
|
+ if (this.captureBlockStates) {
|
|
+ final CraftBlockState snapshot;
|
|
+ if (!this.capturedBlockStates.containsKey(pos)) {
|
|
+ snapshot = (CraftBlockState) org.bukkit.craftbukkit.block.CraftBlock.at(this, pos).getState(); // Paper - use CB getState to get a suitable snapshot
|
|
+ this.capturedBlockStates.put(pos.immutable(), snapshot);
|
|
+ captured = true;
|
|
+ } else {
|
|
+ snapshot = this.capturedBlockStates.get(pos);
|
|
+ }
|
|
+ snapshot.setFlags(flags); // Paper - always set the flag of the most recent call to mitigate issues with multiple update at the same pos with different flags
|
|
+ }
|
|
BlockState blockState = chunkAt.setBlockState(pos, state, flags);
|
|
+ // CraftBukkit end
|
|
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);
|
|
@@ -253,12 +_,68 @@
|
|
|
|
this.updatePOIOnBlockStateChange(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 = pos.immutable();
|
|
+ }
|
|
+ // 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 pos, LevelChunk chunkAt, BlockState oldState, BlockState newState, BlockState currentState, int flags, int recursionLeft) {
|
|
+ BlockState state = newState;
|
|
+ BlockState blockState = oldState;
|
|
+ BlockState blockState1 = currentState;
|
|
+ if (blockState1 == state) {
|
|
+ if (blockState != blockState1) {
|
|
+ this.setBlocksDirty(pos, blockState, blockState1);
|
|
+ }
|
|
+
|
|
+ if ((flags & 2) != 0 && (!this.isClientSide || (flags & 4) == 0) && (this.isClientSide || chunkAt == null || (chunkAt.getFullStatus() != null && chunkAt.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(pos, blockState, state, flags);
|
|
+ }
|
|
+
|
|
+ if ((flags & 1) != 0) {
|
|
+ this.updateNeighborsAt(pos, blockState.getBlock());
|
|
+ if (!this.isClientSide && state.hasAnalogOutputSignal()) {
|
|
+ this.updateNeighbourForOutputSignal(pos, newState.getBlock());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if ((flags & 16) == 0 && recursionLeft > 0) {
|
|
+ int i = flags & -34;
|
|
+
|
|
+ // CraftBukkit start
|
|
+ blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); // Don't call an event for the old block to limit event spam
|
|
+ boolean cancelledUpdates = false; // Paper - Fix block place logic
|
|
+ if (((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent
|
|
+ org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), CraftBlockData.fromData(state));
|
|
+ cancelledUpdates = !event.callEvent(); // Paper - Fix block place logic
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ if (!cancelledUpdates) { // Paper - Fix block place logic
|
|
+ state.updateNeighbourShapes(this, pos, i, recursionLeft - 1);
|
|
+ state.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1);
|
|
+ } // Paper - Fix block place logic
|
|
+ }
|
|
+
|
|
+ this.updatePOIOnBlockStateChange(pos, blockState, blockState1);
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) {
|
|
}
|
|
|
|
@@ -275,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 removeBlock method 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);
|
|
@@ -345,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);
|
|
}
|
|
}
|
|
@@ -463,32 +_,48 @@
|
|
this.pendingBlockEntityTickers.clear();
|
|
}
|
|
|
|
- Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
|
|
+ // Spigot start
|
|
boolean runsNormally = this.tickRateManager().runsNormally();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- TickingBlockEntity tickingBlockEntity = iterator.next();
|
|
+ var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
|
|
+ toRemove.add(null); // Paper - Fix MC-117075
|
|
+ for (this.tileTickPosition = 0; this.tileTickPosition < this.blockEntityTickers.size(); this.tileTickPosition++) { // Paper - Disable tick limiters
|
|
+ TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition);
|
|
+ // Spigot end
|
|
if (tickingBlockEntity.isRemoved()) {
|
|
- iterator.remove();
|
|
+ toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
|
|
} 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, var6))); // 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;
|
|
@@ -608,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 {
|
|
@@ -620,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);
|
|
}
|
|
}
|
|
@@ -1009,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;
|