2024-12-11 22:26:55 +01:00
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
2019-02-06 06:20:33 +01:00
@@ -25,8 +25,10 @@
import net.minecraft.network.protocol.Packet;
import net.minecraft.resources.ResourceKey;
2024-12-11 22:26:55 +01:00
import net.minecraft.resources.ResourceLocation;
2019-02-06 06:20:33 +01:00
+import io.papermc.paper.util.MCUtil;
2024-12-11 22:26:55 +01:00
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;
2019-02-06 06:20:33 +01:00
@@ -43,6 +45,7 @@
2024-12-11 22:26:55 +01:00
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;
2019-02-06 06:20:33 +01:00
@@ -57,12 +60,14 @@
2024-12-11 22:26:55 +01:00
import net.minecraft.world.level.block.entity.FuelValues;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.BorderChangeListener;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
+import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.entity.LevelEntityGetter;
import net.minecraft.world.level.gameevent.GameEvent;
2019-02-06 06:20:33 +01:00
@@ -81,6 +86,25 @@
2024-12-11 22:26:55 +01:00
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 org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.block.CapturedBlockState;
2016-03-29 02:55:47 +02:00
+import org.bukkit.craftbukkit.block.CraftBlockState;
2024-12-11 22:26:55 +01:00
+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);
2019-02-06 06:20:33 +01:00
@@ -94,7 +118,7 @@
2017-01-07 21:24:46 +01:00
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;
2019-07-08 09:13:36 +02:00
@@ -121,23 +145,81 @@
2024-12-11 22:26:55 +01:00
private final DamageSources damageSources;
private long subTickCount;
- protected Level(WritableLevelData properties, ResourceKey<Level> registryRef, RegistryAccess registryManager, Holder<DimensionType> dimensionEntry, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) {
- this.levelData = properties;
- this.dimensionTypeRegistration = dimensionEntry;
- final DimensionType dimensionmanager = (DimensionType) dimensionEntry.value();
+ // CraftBukkit start Added the following
+ private final CraftWorld world;
+ public boolean pvpMode;
+ public org.bukkit.generator.ChunkGenerator generator;
- this.dimension = registryRef;
- this.isClientSide = isClient;
+ public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710
+ public boolean captureBlockStates = false;
+ public boolean captureTreeGeneration = false;
2017-11-16 13:12:41 +01:00
+ public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
2020-08-07 13:27:56 +02:00
+ public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
2024-12-11 22:26:55 +01:00
+ public List<ItemEntity> captureDrops;
+ public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
+ public boolean populating;
2013-07-07 01:32:53 +02:00
+ public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
2022-06-09 07:20:16 +02:00
+ // 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
2024-12-11 22:26:55 +01:00
+
2014-04-21 13:43:08 +02:00
+ public static BlockPos lastPhysicsProblem; // Spigot
2015-02-20 11:39:31 +01:00
+ private org.spigotmc.TickLimiter entityLimiter;
+ private org.spigotmc.TickLimiter tileLimiter;
+ private int tileTickPosition;
2016-03-02 18:59:48 +01:00
+ public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
2016-05-23 12:12:37 +02:00
+ 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
2013-01-10 06:18:11 +01:00
+
2024-12-11 22:26:55 +01:00
+ public CraftWorld getWorld() {
+ return this.world;
+ }
+
+ public CraftServer getCraftServer() {
+ return (CraftServer) Bukkit.getServer();
+ }
2019-07-08 09:13:36 +02:00
+ // Paper start - Use getChunkIfLoadedImmediately
+ @Override
+ public boolean hasChunk(int chunkX, int chunkZ) {
+ return this.getChunkIfLoaded(chunkX, chunkZ) != null;
+ }
+ // Paper end - Use getChunkIfLoadedImmediately
+
2024-12-11 22:26:55 +01:00
+
+ public abstract ResourceKey<LevelStem> getTypeKey();
+
2022-06-09 07:20:16 +02:00
+ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper - create paper world config
2013-07-07 01:32:53 +02:00
+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
2022-06-09 07:20:16 +02:00
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
2024-12-11 22:26:55 +01:00
+ 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, (long) this.getCraftServer().getTicksPerSpawns(spawnCategory));
+ }
+ }
+
+ // CraftBukkit end
+ this.levelData = worlddatamutable;
+ this.dimensionTypeRegistration = holder;
+ final DimensionType dimensionmanager = (DimensionType) holder.value();
+
+ this.dimension = resourcekey;
+ this.isClientSide = flag;
if (dimensionmanager.coordinateScale() != 1.0D) {
- this.worldBorder = new WorldBorder(this) {
+ this.worldBorder = new WorldBorder() { // CraftBukkit - decompile error
@Override
public double getCenterX() {
- return super.getCenterX() / dimensionmanager.coordinateScale();
+ return super.getCenterX(); // CraftBukkit
}
@Override
public double getCenterZ() {
- return super.getCenterZ() / dimensionmanager.coordinateScale();
+ return super.getCenterZ(); // CraftBukkit
}
};
} else {
2019-07-08 09:13:36 +02:00
@@ -145,13 +227,90 @@
2024-12-11 22:26:55 +01:00
}
this.thread = Thread.currentThread();
- this.biomeManager = new BiomeManager(this, seed);
- this.isDebug = debugWorld;
- this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates);
- this.registryAccess = registryManager;
- this.damageSources = new DamageSources(registryManager);
+ this.biomeManager = new BiomeManager(this, i);
+ this.isDebug = flag1;
+ this.neighborUpdater = new CollectingNeighborUpdater(this, j);
+ this.registryAccess = iregistrycustom;
+ this.damageSources = new DamageSources(iregistrycustom);
+ // 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
2015-02-20 11:39:31 +01:00
+ this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
+ this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
2024-12-11 22:26:55 +01:00
}
2018-07-23 14:22:26 +02:00
+ // 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
2024-12-11 22:26:55 +01:00
@Override
2018-07-23 14:22:26 +02:00
public boolean isClientSide() {
return this.isClientSide;
2019-07-08 09:13:36 +02:00
@@ -163,6 +322,13 @@
2016-03-29 02:55:47 +02:00
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) && Level.isInWorldBoundsHorizontal(pos);
}
2022-07-29 18:35:19 +02:00
@@ -172,25 +338,87 @@
}
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) {
2016-03-29 02:55:47 +02:00
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
2020-09-21 01:10:49 +02:00
+ // 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
2016-03-29 02:55:47 +02:00
}
+ // Paper start - if loaded
@Nullable
2023-04-03 18:46:49 +02:00
+ @Override
2016-03-29 02:55:47 +02:00
+ 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);
+ }
+
2019-02-06 06:20:33 +01:00
+ @Override
2016-12-02 06:11:43 +01:00
+ public final boolean hasChunkAt(BlockPos pos) {
+ return getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4) != null; // Paper - Perf: Optimize Level.hasChunkAt(BlockPosition)Z
+ }
+
2016-03-19 01:16:03 +01:00
+ 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;
+ }
+
2023-04-03 18:46:49 +02:00
@Override
2016-03-29 02:55:47 +02:00
public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
+ // Paper end
ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create);
if (ichunkaccess == null && create) {
2020-09-21 01:10:49 +02:00
@@ -207,6 +435,18 @@
2024-12-11 22:26:55 +01:00
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
+ // CraftBukkit start - tree generation
+ if (this.captureTreeGeneration) {
2017-11-16 13:12:41 +01:00
+ CraftBlockState blockstate = this.capturedBlockStates.get(pos);
2024-12-11 22:26:55 +01:00
+ 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()) {
2023-04-03 18:46:49 +02:00
@@ -214,45 +454,126 @@
2024-12-11 22:26:55 +01:00
} else {
LevelChunk chunk = this.getChunkAt(pos);
Block block = state.getBlock();
- BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0);
2015-02-20 11:39:31 +01:00
2024-12-11 22:26:55 +01:00
+ // CraftBukkit start - capture blockstates
+ boolean captured = false;
+ if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) {
2017-11-16 13:12:41 +01:00
+ 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
2024-12-11 22:26:55 +01:00
+ this.capturedBlockStates.put(pos.immutable(), blockstate);
+ captured = true;
+ }
+ // CraftBukkit end
2015-02-20 11:39:31 +01:00
+
2024-12-11 22:26:55 +01:00
+ BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
+
if (iblockdata1 == null) {
+ // CraftBukkit start - remove blockstate if failed (or the same)
+ if (this.captureBlockStates && captured) {
+ this.capturedBlockStates.remove(pos);
+ }
+ // CraftBukkit end
return false;
} else {
BlockState iblockdata2 = this.getBlockState(pos);
- if (iblockdata2 == state) {
+ /*
+ if (iblockdata2 == iblockdata) {
if (iblockdata1 != iblockdata2) {
- this.setBlocksDirty(pos, iblockdata1, iblockdata2);
+ this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
}
- if ((flags & 2) != 0 && (!this.isClientSide || (flags & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) {
- this.sendBlockUpdated(pos, iblockdata1, state, flags);
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) {
+ this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
}
- if ((flags & 1) != 0) {
- this.blockUpdated(pos, iblockdata1.getBlock());
- if (!this.isClientSide && state.hasAnalogOutputSignal()) {
- this.updateNeighbourForOutputSignal(pos, block);
+ if ((i & 1) != 0) {
+ this.blockUpdated(blockposition, iblockdata1.getBlock());
+ if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
+ this.updateNeighbourForOutputSignal(blockposition, block);
}
}
- if ((flags & 16) == 0 && maxUpdateDepth > 0) {
- int k = flags & -34;
+ if ((i & 16) == 0 && j > 0) {
+ int k = i & -34;
- iblockdata1.updateIndirectNeighbourShapes(this, pos, k, maxUpdateDepth - 1);
- state.updateNeighbourShapes(this, pos, k, maxUpdateDepth - 1);
- state.updateIndirectNeighbourShapes(this, pos, k, maxUpdateDepth - 1);
+ iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
+ iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
+ iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
}
- this.onBlockStateChange(pos, iblockdata1, iblockdata2);
+ this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
}
+ */
+ // CraftBukkit start
+ if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates
+ // Modularize client and physic updates
2014-04-21 13:43:08 +02:00
+ // Spigot start
+ try {
+ this.notifyAndUpdatePhysics(pos, chunk, iblockdata1, state, iblockdata2, flags, maxUpdateDepth);
+ } catch (StackOverflowError ex) {
+ Level.lastPhysicsProblem = new BlockPos(pos);
+ }
+ // Spigot end
2024-12-11 22:26:55 +01:00
+ }
+ // CraftBukkit end
+
return true;
2023-04-03 18:46:49 +02:00
}
}
}
2024-12-11 22:26:55 +01:00
+ // 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);
2017-08-10 00:51:22 +02:00
+ }
2024-12-11 22:26:55 +01:00
+
+ if ((i & 1) != 0) {
+ this.blockUpdated(blockposition, iblockdata1.getBlock());
+ if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
+ this.updateNeighbourForOutputSignal(blockposition, newBlock.getBlock());
+ }
2018-07-23 14:22:26 +02:00
+ }
2024-12-11 22:26:55 +01:00
+
+ 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();
2023-04-03 18:46:49 +02:00
+ boolean cancelledUpdates = false; // Paper - Fix block place logic
2016-03-29 01:55:45 +02:00
+ if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent
2024-12-11 22:26:55 +01:00
+ BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata));
+ this.getCraftServer().getPluginManager().callEvent(event);
+
2023-04-03 18:46:49 +02:00
+ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic
2024-12-11 22:26:55 +01:00
+ }
+ // CraftBukkit end
2023-04-03 18:46:49 +02:00
+ if (!cancelledUpdates) { // Paper - Fix block place logic
2024-12-11 22:26:55 +01:00
+ iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
+ iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
2023-04-03 18:46:49 +02:00
+ } // Paper - Fix block place logic
+ }
2024-12-11 22:26:55 +01:00
+
+ // CraftBukkit start - SPIGOT-5710
+ if (!this.preventPoiUpdated) {
+ this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
+ }
+ // CraftBukkit end
2023-04-03 18:46:49 +02:00
+ }
+ }
2024-12-11 22:26:55 +01:00
+ // CraftBukkit end
2023-04-03 18:46:49 +02:00
+
2024-12-11 22:26:55 +01:00
public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {}
2023-04-03 18:46:49 +02:00
@Override
@@ -270,9 +591,26 @@
2019-02-06 06:20:33 +01:00
return false;
} else {
FluidState fluid = this.getFluidState(pos);
+ // 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 = iblockdata;
+ int xp = iblockdata.getBlock().getExpDrop(iblockdata, (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), fluid.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, drop);
+ if (!event.callEvent()) {
+ return false;
+ }
+ effectType = ((CraftBlockData) event.getEffectBlock()).getState();
+ playEffect = event.playEffect();
+ drop = event.willDrop();
+ xp = event.getExpToDrop();
+ }
+ // Paper end - BlockDestroyEvent
- if (!(iblockdata.getBlock() instanceof BaseFireBlock)) {
- this.levelEvent(2001, pos, Block.getId(iblockdata));
+ if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent
+ this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent
}
if (drop) {
2023-04-03 18:46:49 +02:00
@@ -340,10 +678,18 @@
2024-12-11 22:26:55 +01:00
@Override
public BlockState getBlockState(BlockPos pos) {
+ // CraftBukkit start - tree generation
+ if (this.captureTreeGeneration) {
2017-11-16 13:12:41 +01:00
+ CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper
2024-12-11 22:26:55 +01:00
+ if (previous != null) {
+ return previous.getHandle();
+ }
+ }
+ // CraftBukkit end
if (this.isOutsideBuildHeight(pos)) {
return Blocks.VOID_AIR.defaultBlockState();
} else {
2016-03-29 02:55:47 +02:00
- 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);
}
2023-04-03 18:46:49 +02:00
@@ -446,34 +792,53 @@
2013-01-10 06:18:11 +01:00
this.pendingBlockEntityTickers.clear();
}
2015-02-20 11:39:31 +01:00
- Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
+ // Spigot start
+ // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
2013-01-10 06:18:11 +01:00
boolean flag = this.tickRateManager().runsNormally();
2015-02-20 11:39:31 +01:00
- while (iterator.hasNext()) {
- TickingBlockEntity tickingblockentity = (TickingBlockEntity) iterator.next();
+ int tilesThisCycle = 0;
2017-08-10 00:51:22 +02:00
+ var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
+ toRemove.add(null); // Paper - Fix MC-117075
2016-03-03 06:45:17 +01:00
+ for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
2015-02-20 11:39:31 +01:00
+ 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--;
2017-08-10 00:51:22 +02:00
+ toRemove.add(tickingblockentity); // Paper - Fix MC-117075; use removeAll
2015-02-20 11:39:31 +01:00
+ // Spigot end
} else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
tickingblockentity.tick();
2013-01-10 06:18:11 +01:00
}
2017-08-10 00:51:22 +02:00
}
+ this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
2013-01-10 06:18:11 +01:00
this.tickingBlockEntities = false;
gameprofilerfiller.pop();
2014-08-21 00:12:32 +02:00
+ this.spigotConfig.currentPrimedTnt = 0; // Spigot
2013-01-10 06:18:11 +01:00
}
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
2016-03-02 06:52:34 +01:00
try {
tickConsumer.accept(entity);
} catch (Throwable throwable) {
- CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity");
- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked");
-
- entity.fillCrashReportCategory(crashreportsystemdetails);
- 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, throwable);
2016-03-03 10:15:41 +01:00
+ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
2016-03-02 06:52:34 +01:00
+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ // Paper end - Prevent block entity and entity crashes
}
2023-04-03 18:46:49 +02:00
+ }
2018-07-23 12:57:39 +02:00
+ // 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);
2023-04-03 18:46:49 +02:00
}
2018-07-23 12:57:39 +02:00
+ // Paper end - Option to prevent armor stands from doing entity lookups
2016-03-02 06:52:34 +01:00
2018-07-23 12:57:39 +02:00
public boolean shouldTickDeath(Entity entity) {
return true;
2023-04-03 18:46:49 +02:00
@@ -510,13 +875,32 @@
2024-12-11 22:26:55 +01:00
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
- return this.isOutsideBuildHeight(pos) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE));
+ // CraftBukkit start
+ return this.getBlockEntity(pos, true);
2020-09-21 01:10:49 +02:00
+ }
+
2024-12-11 22:26:55 +01:00
+ @Nullable
+ public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
2019-04-06 16:16:48 +02:00
+ // Paper start - Perf: Optimize capturedTileEntities lookup
+ net.minecraft.world.level.block.entity.BlockEntity blockEntity;
+ if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) {
+ return blockEntity;
2024-12-11 22:26:55 +01:00
+ }
2019-04-06 16:16:48 +02:00
+ // Paper end - Perf: Optimize capturedTileEntities lookup
2024-12-11 22:26:55 +01:00
+ // CraftBukkit end
+ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE));
2020-09-21 01:10:49 +02:00
}
2024-12-11 22:26:55 +01:00
public void setBlockEntity(BlockEntity blockEntity) {
BlockPos blockposition = blockEntity.getBlockPos();
if (!this.isOutsideBuildHeight(blockposition)) {
+ // CraftBukkit start
+ if (this.captureBlockStates) {
+ this.capturedTileEntities.put(blockposition.immutable(), blockEntity);
+ return;
+ }
+ // CraftBukkit end
this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity);
}
}
2023-04-03 18:46:49 +02:00
@@ -643,7 +1027,7 @@
2024-12-11 22:26:55 +01:00
for (int k = 0; k < j; ++k) {
EnderDragonPart entitycomplexpart = aentitycomplexpart[k];
- T t0 = (Entity) filter.tryCast(entitycomplexpart);
+ T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error
if (t0 != null && predicate.test(t0)) {
result.add(t0);
2023-04-03 18:46:49 +02:00
@@ -912,7 +1296,7 @@
2024-12-11 22:26:55 +01:00
public static enum ExplosionInteraction implements StringRepresentable {
- NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger");
+ NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), 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;