more patches

This commit is contained in:
Jake Potrebic 2021-06-13 12:29:58 -07:00 committed by MiniDigger | Martin
parent 123a41d132
commit 27a8d6da9a
32 changed files with 825 additions and 1168 deletions

View file

@ -0,0 +1,165 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Byteflux <byte@byteflux.net>
Date: Wed, 2 Mar 2016 02:17:54 -0600
Subject: [PATCH] Generator Settings
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 4c177a383b277debe8a7c02a70d029d862e6b048..0c336a794d21d5084b9ea39308379b2ffb4a4330 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -416,5 +416,10 @@ public class PaperWorldConfig {
private void disableRelativeProjectileVelocity() {
disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
}
+
+ public boolean generateFlatBedrock;
+ private void generatorSettings() {
+ generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false);
+ }
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index dd7de2c3db10d9d606d47c52eba40e71034fc11a..5473b22d21ea981f245f4826c4d47295890f88ef 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -678,7 +678,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
this.markPositionReplaceable(pos);
- return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level));
+ return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level, this.level)); // Paper - add level
// Paper start - Async chunk io
};
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> ret = new CompletableFuture<>();
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
index 974ab04b08bbd3c27a394b37c1af112be5f28f43..c0075d226331f32e470dae5bf1ce8d79e8b263dc 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -29,6 +29,18 @@ public interface ChunkAccess extends BlockGetter, FeatureAccess {
return GameEventDispatcher.NOOP;
}
+ // Paper start
+ default boolean generateFlatBedrock() {
+ if (this instanceof ProtoChunk) {
+ return ((ProtoChunk)this).level.paperConfig.generateFlatBedrock;
+ } else if (this instanceof LevelChunk) {
+ return ((LevelChunk)this).level.paperConfig.generateFlatBedrock;
+ } else {
+ return false;
+ }
+ }
+ // Paper end
+
BlockState getType(final int x, final int y, final int z); // Paper
@Nullable
BlockState setBlockState(BlockPos pos, BlockState state, boolean moved);
diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
index 452b513e8b89d865a396066adaf4feb1140e1c62..8245c5834ec69beb8e3b95fb3900601009a9273f 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
@@ -25,7 +25,7 @@ public class ImposterProtoChunk extends ProtoChunk {
private final LevelChunk wrapped;
public ImposterProtoChunk(LevelChunk wrapped) {
- super(wrapped.getPos(), UpgradeData.EMPTY, wrapped);
+ super(wrapped.getPos(), UpgradeData.EMPTY, wrapped, wrapped.level); // Paper - add level
this.wrapped = wrapped;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
index da36e6d40ad3e8b7cdbe09ef911d1e5b8c28670f..245998e2cea32cf15ee2659639c647f449704ec0 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
@@ -63,16 +63,24 @@ public class ProtoChunk implements ChunkAccess {
private long inhabitedTime;
private final Map<GenerationStep.Carving, BitSet> carvingMasks = new Object2ObjectArrayMap<>();
private volatile boolean isLightCorrect;
+ final net.minecraft.world.level.Level level; // Paper - Add level
- public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world) {
+ // Paper start - add level
+ @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world) { this(pos, upgradeData, world, null); }
+ public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world, net.minecraft.server.level.ServerLevel level) {
+ // Paper end
this(pos, upgradeData, (LevelChunkSection[])null, new ProtoTickList<>((block) -> {
return block == null || block.defaultBlockState().isAir();
}, pos, world), new ProtoTickList<>((fluid) -> {
return fluid == null || fluid == Fluids.EMPTY;
- }, pos, world), world);
+ }, pos, world), world, level); // Paper - add level
}
- public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] levelChunkSections, ProtoTickList<Block> blockTickScheduler, ProtoTickList<Fluid> fluidTickScheduler, LevelHeightAccessor world) {
+ // Paper start - add level
+ @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] levelChunkSections, ProtoTickList<Block> blockTickScheduler, ProtoTickList<Fluid> fluidTickScheduler, LevelHeightAccessor world) { this(pos, upgradeData, levelChunkSections, blockTickScheduler, fluidTickScheduler, world, null); }
+ public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] levelChunkSections, ProtoTickList<Block> blockTickScheduler, ProtoTickList<Fluid> fluidTickScheduler, LevelHeightAccessor world, net.minecraft.server.level.ServerLevel level) {
+ this.level = level;
+ // Paper end
this.chunkPos = pos;
this.upgradeData = upgradeData;
this.blockTicks = blockTickScheduler;
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index fc320450878279a6aa48019fbde35bb183f5f06e..3cb35f2f47ba841915ac2825d30cc4c3274ce3c4 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -210,7 +210,7 @@ public class ChunkSerializer {
// CraftBukkit end
});
} else {
- ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world);
+ ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, world); // Paper - add level
protochunk.setBiomes(biomestorage);
object = protochunk;
diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
index a0a3cf7655eca5c2b181476b0842d9e8c19dbffd..db0abd275be2df7a4fdeb1d7179bff4d61bab052 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
@@ -270,8 +270,8 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
int j = chunk.getPos().getMinBlockZ();
NoiseGeneratorSettings noiseGeneratorSettings = this.settings.get();
int k = noiseGeneratorSettings.noiseSettings().minY();
- int l = k + noiseGeneratorSettings.getBedrockFloorPosition();
- int m = this.height - 1 + k - noiseGeneratorSettings.getBedrockRoofPosition();
+ int l = k + noiseGeneratorSettings.getBedrockFloorPosition(); final int floorHeight = k; // Paper
+ int m = this.height - 1 + k - noiseGeneratorSettings.getBedrockRoofPosition(); final int roofHeight = l; // Paper
int n = 5;
int o = chunk.getMinBuildHeight();
int p = chunk.getMaxBuildHeight();
@@ -281,7 +281,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
for(BlockPos blockPos : BlockPos.betweenClosed(i, 0, j, i + 15, 0, j + 15)) {
if (bl) {
for(int q = 0; q < 5; ++q) {
- if (q <= random.nextInt(5)) {
+ if (q <= (chunk.generateFlatBedrock() ? roofHeight : random.nextInt(5))) { // Paper - Configurable flat bedrock roof
chunk.setBlockState(mutableBlockPos.set(blockPos.getX(), m - q, blockPos.getZ()), Blocks.BEDROCK.defaultBlockState(), false);
}
}
@@ -289,7 +289,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
if (bl2) {
for(int r = 4; r >= 0; --r) {
- if (r <= random.nextInt(5)) {
+ if (r <= (chunk.generateFlatBedrock() ? roofHeight : random.nextInt(5))) { // Paper - Configurable flat bedrock floor
chunk.setBlockState(mutableBlockPos.set(blockPos.getX(), l + r, blockPos.getZ()), Blocks.BEDROCK.defaultBlockState(), false);
}
}
@@ -309,14 +309,14 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
if (l <= 0) {
return CompletableFuture.completedFuture(chunk);
} else {
- int m = chunk.getSectionIndex(l * this.cellHeight - 1 + i);
+ int mStart = chunk.getSectionIndex(l * this.cellHeight - 1 + i); // Paper - decompile fix
int n = chunk.getSectionIndex(i);
return CompletableFuture.supplyAsync(() -> {
Set<LevelChunkSection> set = Sets.newHashSet();
ChunkAccess var16;
try {
- for(int m = m; m >= n; --m) {
+ for(int m = mStart; m >= n; --m) { // Paper - decompile fix
LevelChunkSection levelChunkSection = chunk.getOrCreateSection(m);
levelChunkSection.acquire();
set.add(levelChunkSection);

View file

@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 24 Sep 2019 16:03:00 -0700
Subject: [PATCH] Fix MC-161754
Fixes https://github.com/PaperMC/Paper/issues/2580
We can use an entity valid check since this method is invoked for
each inventory iteraction (thanks to CB) and on player tick (vanilla).
diff --git a/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java b/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java
index 9da2f9334ea98ecba328c7b0d022321d6194529b..0d145f075798dde27beef80022cd7c0f582f8253 100644
--- a/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java
@@ -95,7 +95,7 @@ public class HorseInventoryMenu extends AbstractContainerMenu {
@Override
public boolean stillValid(Player player) {
- return !this.horse.hasInventoryChanged(this.horseContainer) && this.horseContainer.stillValid(player) && this.horse.isAlive() && this.horse.distanceTo((Entity) player) < 8.0F;
+ return !this.horse.hasInventoryChanged(this.horseContainer) && this.horseContainer.stillValid(player) && (this.horse.isAlive() && this.horse.valid) && this.horse.distanceTo((Entity) player) < 8.0F; // Paper - Fix MC-161754
}
private boolean hasChest(AbstractHorse entityhorseabstract) {

View file

@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MisterErwin <git@askarian.net>
Date: Wed, 30 Oct 2019 16:57:54 +0100
Subject: [PATCH] Fix spawning of hanging entities that are not ItemFrames and
can not face UP or DOWN
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 4841591539fdd5a01f9ded0ee510991602c266a4..f1cbcdb1e409f8544125dde5f24bff5b07cb5082 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1881,7 +1881,12 @@ public class CraftWorld implements World {
height = 9;
}
- BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN};
+ // Paper start - In addition to d65a2576e40e58c8e446b330febe6799d13a604f do not check UP/DOWN for non item frames
+ // BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN};
+ BlockFace[] faces = (ItemFrame.class.isAssignableFrom(clazz))
+ ? new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN}
+ : new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH};
+ // Paper end
final BlockPos pos = new BlockPos(x, y, z);
for (BlockFace dir : faces) {
net.minecraft.world.level.block.state.BlockState nmsBlock = this.world.getBlockState(pos.relative(CraftBlock.blockFaceToNotch(dir)));

View file

@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Sat, 20 Apr 2019 19:47:34 -0500
Subject: [PATCH] Expose the internal current tick
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 58d7fec472dd8c8d7775eb6d931be29871102054..9730c701b734eb3491bc1fa5d9bb81ddfefc910a 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2364,5 +2364,10 @@ public final class CraftServer implements Server {
}
return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name);
}
+
+ @Override
+ public int getCurrentTick() {
+ return net.minecraft.server.MinecraftServer.currentTick;
+ }
// Paper end
}

View file

@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Wed, 9 Oct 2019 21:51:43 -0500
Subject: [PATCH] Fix stuck in sneak when changing worlds (MC-10657)
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 679f1e3ef4c19ab466cf2d06c47b1018aabaa7bf..8da35a7dde494b059e542d049d7ee039deb5833d 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -1101,6 +1101,8 @@ public class ServerPlayer extends Player {
this.lastSentHealth = -1.0F;
this.lastSentFood = -1;
+ setShiftKeyDown(false); // Paper - fix MC-10657
+
// CraftBukkit start
PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
this.level.getCraftServer().getPluginManager().callEvent(changeEvent);
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 6f9bd5da1504af296e7ee2a69d8afdd3bc4cfd5e..bcc946d2747443c34ee8ac2485a5ab41773c93af 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -820,6 +820,8 @@ public abstract class PlayerList {
entityplayer.connection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobEffect));
}
+ entityplayer.setShiftKeyDown(false); // Paper - fix MC-10657
+
// Fire advancement trigger
entityplayer.triggerDimensionChangeTriggers(((CraftWorld) fromWorld).getHandle());

View file

@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Wed, 9 Oct 2019 21:46:15 -0500
Subject: [PATCH] Add option to disable pillager patrols
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 0c336a794d21d5084b9ea39308379b2ffb4a4330..f46731897f4234b27dafa49a5f652535a99d2992 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -421,5 +421,10 @@ public class PaperWorldConfig {
private void generatorSettings() {
generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false);
}
+
+ public boolean disablePillagerPatrols = false;
+ private void pillagerSettings() {
+ disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols);
+ }
}
diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java
index e0a376617f6b6232591942da0bc9d7b1ee58c2e7..744b58d59a5f34ed3bd6f2d4a0f876acfa6a7135 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java
@@ -26,6 +26,7 @@ public class PatrolSpawner implements CustomSpawner {
@Override
public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) {
+ if (world.paperConfig.disablePillagerPatrols) return 0; // Paper
if (!spawnMonsters) {
return 0;
} else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) {

View file

@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Lukasz Derlatka <toranktto@gmail.com>
Date: Mon, 11 Nov 2019 16:08:13 +0100
Subject: [PATCH] Fix AssertionError when player hand set to empty type
Fixes an AssertionError when setting the player's item in hand to null or a new ItemStack of Air in PlayerInteractEvent
Fixes GH-2718
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 377e224acb06bfd5f769b5f75b7f52129394b494..9240064a17e7ac2492fe157b759d1f724105cd0e 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1741,6 +1741,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524
return;
}
+ // Paper start
+ itemstack = this.player.getItemInHand(enumhand);
+ if (itemstack.isEmpty()) return;
+ // Paper end
InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand);
if (enuminteractionresult.shouldSwing()) {

View file

@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Callahan <mr.callahhh@gmail.com>
Date: Mon, 13 Jan 2020 23:47:28 -0600
Subject: [PATCH] Prevent sync chunk loads when villagers try to find beds
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java
index 455774a211c679367c6e7845a11159ad84ca07e2..673b6e60731d440cc02b1e86bfba50e6ebeb0da9 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java
@@ -41,7 +41,8 @@ public class SleepInBed extends Behavior<LivingEntity> {
}
}
- BlockState blockState = world.getBlockState(globalPos.pos());
+ BlockState blockState = world.getTypeIfLoaded(globalPos.pos()); // Paper
+ if (blockState == null) { return false; } // Paper
return globalPos.pos().closerThan(entity.position(), 2.0D) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED);
}
}

View file

@ -0,0 +1,65 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Wed, 18 Dec 2019 22:21:35 -0600
Subject: [PATCH] MC-145656 Fix Follow Range Initial Target
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index f46731897f4234b27dafa49a5f652535a99d2992..8190c30346c0fd2d86fb7cbcfc7ce17333e05146 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -426,5 +426,10 @@ public class PaperWorldConfig {
private void pillagerSettings() {
disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols);
}
+
+ public boolean entitiesTargetWithFollowRange = false;
+ private void entitiesTargetWithFollowRange() {
+ entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange);
+ }
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
index 6cbd2fc4a7041f957966e5b09616e70aae63c0d4..b5bbcb9fa6de4c919e4d4fabbab483054d81574e 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
@@ -32,6 +32,7 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
this.randomInterval = reciprocalChance;
this.setFlags(EnumSet.of(Goal.Flag.TARGET));
this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(targetPredicate);
+ if (mob.level.paperConfig.entitiesTargetWithFollowRange) this.targetConditions.useFollowRange(); // Paper
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
index e45434b844c98c322e1432c2382c1ccb8c3e57a7..3ee691d4caccbc1b3e0f52decb41d436ac0d08ec 100644
--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -75,7 +75,7 @@ public class TargetingConditions {
if (this.range > 0.0D) {
double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
- double e = Math.max(this.range * d, 2.0D);
+ double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0D); // Paper
double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ());
if (f > e * e) {
return false;
@@ -90,4 +90,18 @@ public class TargetingConditions {
return true;
}
}
+
+ // Paper start
+ private boolean useFollowRange = false;
+
+ public TargetingConditions useFollowRange() {
+ this.useFollowRange = true;
+ return this;
+ }
+
+ private double getFollowRange(LivingEntity entityliving) {
+ net.minecraft.world.entity.ai.attributes.AttributeInstance attributeinstance = entityliving.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE);
+ return attributeinstance == null ? 16.0D : attributeinstance.getValue();
+ }
+ // Paper end
}

View file

@ -0,0 +1,482 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 27 Apr 2016 22:09:52 -0400
Subject: [PATCH] Optimize Hoppers
* Removes unnecessary extra calls to .update() that are very expensive
* Lots of itemstack cloning removed. Only clone if the item is actually moved
* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items.
However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on.
* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory
* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration
* Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried)
* Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins)
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 8190c30346c0fd2d86fb7cbcfc7ce17333e05146..041efecbfcd54384d15ebf771cdfa0e711483997 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -431,5 +431,14 @@ public class PaperWorldConfig {
private void entitiesTargetWithFollowRange() {
entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange);
}
+
+ public boolean cooldownHopperWhenFull = true;
+ public boolean disableHopperMoveEvents = false;
+ private void hopperOptimizations() {
+ cooldownHopperWhenFull = getBoolean("hopper.cooldown-when-full", cooldownHopperWhenFull);
+ log("Cooldown Hoppers when Full: " + (cooldownHopperWhenFull ? "enabled" : "disabled"));
+ disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents);
+ log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled"));
+ }
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index f00839eab02277bf10b742c88fadc4aa9e89e7e0..312be2221e1acc44aaf6936533b0eb968f796dc6 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1422,6 +1422,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
while (iterator.hasNext()) {
ServerLevel worldserver = (ServerLevel) iterator.next();
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
this.profiler.push(() -> {
return worldserver + " " + worldserver.dimension().location();
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
index dd4deb08eb3ed15a156269a11d3d43abfd61fe5b..10f55ba189d12c46e0ef38b0b96a29894b4fba93 100644
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
@@ -601,11 +601,12 @@ public final class ItemStack {
return this.getItem().interactLivingEntity(this, user, entity, hand);
}
- public ItemStack copy() {
- if (this.isEmpty()) {
+ public ItemStack copy() { return cloneItemStack(false); } // Paper
+ public ItemStack cloneItemStack(boolean origItem) { // Paper
+ if (!origItem && this.isEmpty()) { // Paper
return ItemStack.EMPTY;
} else {
- ItemStack itemstack = new ItemStack(this.getItem(), this.count);
+ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper
itemstack.setPopTime(this.getPopTime());
if (this.tag != null) {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
index 8a079ee3ed243fd19b1dd7eed2de1dd33785faa1..c3a07ccccd5cc38552363c82398f432c8d624288 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -63,6 +63,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject {
getMinecraftKey(); // Try to load if it doesn't exists.
return tileEntityKeyString;
}
+ static boolean IGNORE_TILE_UPDATES = false;
// Paper end
@Nullable
@@ -145,6 +146,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject {
public void setChanged() {
if (this.level != null) {
+ if (IGNORE_TILE_UPDATES) return; // Paper
BlockEntity.setChanged(this.level, this.worldPosition, this.blockState);
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java
index a05acf709735b40ca86f978508c63a86065fd405..c1754df6a60ff93d75a28145b9dc4e9174241a21 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java
@@ -14,6 +14,10 @@ public interface Hopper extends Container {
return SUCK;
}
+ net.minecraft.world.level.Level getLevel();
+
+ default net.minecraft.core.BlockPos getBlockPosition() { return new net.minecraft.core.BlockPos(getLevelX(), getLevelY(), getLevelZ()); } // Paper
+
double getLevelX();
double getLevelY();
diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 0ec16d554c2b51a64614c73783505c7b06ff02c7..86c0a477883102e4aebdab7ac28bf9f2ab2f7c48 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -3,7 +3,6 @@ package net.minecraft.world.level.block.entity;
import java.util.Iterator;
import java.util.List;
import java.util.function.BooleanSupplier;
-import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
@@ -33,7 +32,6 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
-import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.HumanEntity;
@@ -192,6 +190,159 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
return false;
}
+ // Paper start - Optimize Hoppers
+ private static boolean skipPullModeEventFire = false;
+ private static boolean skipPushModeEventFire = false;
+ public static boolean skipHopperEvents = false;
+
+ private static boolean hopperPush(Level level, BlockPos pos, Container iinventory, Direction enumdirection, HopperBlockEntity hopper) {
+ skipPushModeEventFire = skipHopperEvents;
+ boolean foundItem = false;
+ for (int i = 0; i < iinventory.getContainerSize(); ++i) {
+ ItemStack item = iinventory.getItem(i);
+ if (!item.isEmpty()) {
+ foundItem = true;
+ ItemStack origItemStack = item;
+ ItemStack itemstack = origItemStack;
+
+ final int origCount = origItemStack.getCount();
+ final int moved = Math.min(level.spigotConfig.hopperAmount, origCount);
+ origItemStack.setCount(moved);
+
+ // We only need to fire the event once to give protection plugins a chance to cancel this event
+ // Because nothing uses getItem, every event call should end up the same result.
+ if (!skipPushModeEventFire) {
+ itemstack = callPushMoveEvent(iinventory, itemstack, hopper);
+ if (itemstack == null) { // cancelled
+ origItemStack.setCount(origCount);
+ return false;
+ }
+ }
+ final ItemStack itemstack2 = addItem(hopper, iinventory, itemstack, enumdirection);
+ final int remaining = itemstack2.getCount();
+ if (remaining != moved) {
+ origItemStack = origItemStack.cloneItemStack(true);
+ origItemStack.setCount(origCount);
+ if (!origItemStack.isEmpty()) {
+ origItemStack.setCount(origCount - moved + remaining);
+ }
+ hopper.setItem(i, origItemStack);
+ iinventory.setChanged();
+ return true;
+ }
+ origItemStack.setCount(origCount);
+ }
+ }
+ if (foundItem && level.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown
+ hopper.setCooldown(level.spigotConfig.hopperTransfer);
+ }
+ return false;
+ }
+
+ private static boolean hopperPull(Hopper ihopper, Container iinventory, ItemStack origItemStack, int i) {
+ ItemStack itemstack = origItemStack;
+ final int origCount = origItemStack.getCount();
+ final Level world = ihopper.getLevel();
+ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount);
+ itemstack.setCount(moved);
+
+ if (!skipPullModeEventFire) {
+ itemstack = callPullMoveEvent(ihopper, iinventory, itemstack);
+ if (itemstack == null) { // cancelled
+ origItemStack.setCount(origCount);
+ // Drastically improve performance by returning true.
+ // No plugin could of relied on the behavior of false as the other call
+ // site for IMIE did not exhibit the same behavior
+ return true;
+ }
+ }
+
+ final ItemStack itemstack2 = addItem(iinventory, ihopper, itemstack, null);
+ final int remaining = itemstack2.getCount();
+ if (remaining != moved) {
+ origItemStack = origItemStack.cloneItemStack(true);
+ origItemStack.setCount(origCount);
+ if (!origItemStack.isEmpty()) {
+ origItemStack.setCount(origCount - moved + remaining);
+ }
+ IGNORE_TILE_UPDATES = true;
+ iinventory.setItem(i, origItemStack);
+ IGNORE_TILE_UPDATES = false;
+ iinventory.setChanged();
+ return true;
+ }
+ origItemStack.setCount(origCount);
+
+ if (world.paperConfig.cooldownHopperWhenFull) {
+ cooldownHopper(ihopper);
+ }
+
+ return false;
+ }
+
+ private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) {
+ Inventory destinationInventory = getInventory(iinventory);
+ InventoryMoveItemEvent event = new InventoryMoveItemEvent(hopper.getOwner(false).getInventory(),
+ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true);
+ boolean result = event.callEvent();
+ if (!event.calledGetItem && !event.calledSetItem) {
+ skipPushModeEventFire = true;
+ }
+ if (!result) {
+ cooldownHopper(hopper);
+ return null;
+ }
+
+ if (event.calledSetItem) {
+ return CraftItemStack.asNMSCopy(event.getItem());
+ } else {
+ return itemstack;
+ }
+ }
+
+ private static ItemStack callPullMoveEvent(Hopper hopper, Container iinventory, ItemStack itemstack) {
+ Inventory sourceInventory = getInventory(iinventory);
+ Inventory destination = getInventory(hopper);
+
+ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory,
+ // Mirror is safe as we no plugins ever use this item
+ CraftItemStack.asCraftMirror(itemstack), destination, false);
+ boolean result = event.callEvent();
+ if (!event.calledGetItem && !event.calledSetItem) {
+ skipPullModeEventFire = true;
+ }
+ if (!result) {
+ cooldownHopper(hopper);
+ return null;
+ }
+
+ if (event.calledSetItem) {
+ return CraftItemStack.asNMSCopy(event.getItem());
+ } else {
+ return itemstack;
+ }
+ }
+
+ private static Inventory getInventory(Container iinventory) {
+ Inventory sourceInventory;// Have to special case large chests as they work oddly
+ if (iinventory instanceof CompoundContainer) {
+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory);
+ } else if (iinventory instanceof BlockEntity) {
+ sourceInventory = ((BlockEntity) iinventory).getOwner(false).getInventory();
+ } else {
+ sourceInventory = iinventory.getOwner().getInventory();
+ }
+ return sourceInventory;
+ }
+
+ private static void cooldownHopper(Hopper hopper) {
+ if (hopper instanceof HopperBlockEntity) {
+ ((HopperBlockEntity) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer);
+ } else if (hopper instanceof MinecartHopper) {
+ ((MinecartHopper) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer / 2);
+ }
+ }
+ // Paper end
private static boolean a(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit
Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata);
@@ -204,6 +355,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
if (HopperBlockEntity.isFullContainer(iinventory1, enumdirection)) {
return false;
} else {
+ return hopperPush(world, blockposition, iinventory, enumdirection, hopper); /* // Paper - disable rest
for (int i = 0; i < iinventory.getContainerSize(); ++i) {
if (!iinventory.getItem(i).isEmpty()) {
ItemStack itemstack = iinventory.getItem(i).copy();
@@ -241,7 +393,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
}
- return false;
+ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations
}
}
}
@@ -251,27 +403,68 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
private static boolean isFullContainer(Container inventory, Direction direction) {
- return HopperBlockEntity.getSlots(inventory, direction).allMatch((i) -> {
- ItemStack itemstack = inventory.getItem(i);
-
- return itemstack.getCount() >= itemstack.getMaxStackSize();
- });
+ return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams
}
private static boolean isEmptyContainer(Container inv, Direction facing) {
- return HopperBlockEntity.getSlots(inv, facing).allMatch((i) -> {
- return inv.getItem(i).isEmpty();
- });
+ // Paper start
+ return allMatch(inv, facing, IS_EMPTY_TEST);
+ }
+ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
+ if (iinventory instanceof WorldlyContainer) {
+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) {
+ if (!test.test(iinventory.getItem(i), i)) {
+ return false;
+ }
+ }
+ } else {
+ int size = iinventory.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ if (!test.test(iinventory.getItem(i), i)) {
+ return false;
+ }
+ }
+ }
+ return true;
}
+ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
+ if (iinventory instanceof WorldlyContainer) {
+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) {
+ if (test.test(iinventory.getItem(i), i)) {
+ return true;
+ }
+ }
+ } else {
+ int size = iinventory.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ if (test.test(iinventory.getItem(i), i)) {
+ return true;
+ }
+ }
+ }
+ return true;
+ }
+ private static final java.util.function.BiPredicate<ItemStack, Integer> STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize();
+ private static final java.util.function.BiPredicate<ItemStack, Integer> IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty();
+ // Paper end
+
public static boolean suckInItems(Level world, Hopper hopper) {
Container iinventory = HopperBlockEntity.getSourceContainer(world, hopper);
if (iinventory != null) {
Direction enumdirection = Direction.DOWN;
- return HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) ? false : HopperBlockEntity.getSlots(iinventory, enumdirection).anyMatch((i) -> {
- return HopperBlockEntity.a(hopper, iinventory, i, enumdirection, world); // Spigot
+ // Paper start - optimize hoppers and remove streams
+ skipPullModeEventFire = skipHopperEvents;
+ return !HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> {
+ // Logic copied from below to avoid extra getItem calls
+ if (!item.isEmpty() && canTakeItemFromContainer(iinventory, item, i, enumdirection)) {
+ return hopperPull(hopper, iinventory, item, i);
+ } else {
+ return false;
+ }
+ // Paper end
});
} else {
Iterator iterator = HopperBlockEntity.getItemsAtAndAbove(world, hopper).iterator();
@@ -290,10 +483,12 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
}
+ // Paper - method unused as logic is inlined above
private static boolean a(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot
ItemStack itemstack = iinventory.getItem(i);
- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(iinventory, itemstack, i, enumdirection)) {
+ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left inused incase reflective plugins
+ return hopperPull(ihopper, iinventory, itemstack, i); /* // Paper - disable rest
ItemStack itemstack1 = itemstack.copy();
// ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null);
// CraftBukkit start - Call event on collection of items from inventories into the hopper
@@ -330,7 +525,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot
- iinventory.setItem(i, itemstack1);
+ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations
}
return false;
@@ -339,7 +534,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public static boolean addItem(Container inventory, ItemEntity itemEntity) {
boolean flag = false;
// CraftBukkit start
- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
+ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation
itemEntity.level.getCraftServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return false;
@@ -393,7 +588,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
boolean flag1 = to.isEmpty();
if (itemstack1.isEmpty()) {
+ IGNORE_TILE_UPDATES = true; // Paper
to.setItem(slot, stack);
+ IGNORE_TILE_UPDATES = false; // Paper
stack = ItemStack.EMPTY;
flag = true;
} else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) {
@@ -444,18 +641,23 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
public static List<ItemEntity> getItemsAtAndAbove(Level world, Hopper hopper) {
- return (List) hopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> {
- return world.getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(hopper.getLevelX() - 0.5D, hopper.getLevelY() - 0.5D, hopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream();
- }).collect(Collectors.toList());
+ // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?!
+ double d0 = hopper.getLevelX();
+ double d1 = hopper.getLevelY();
+ double d2 = hopper.getLevelZ();
+ AABB bb = new AABB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D);
+ return world.getEntitiesOfClass(ItemEntity.class, bb, Entity::isAlive);
+ // Paper end
}
@Nullable
public static Container getContainerAt(Level world, BlockPos pos) {
- return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D);
+ return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); // Paper
}
+ public static Container getContainerAt(Level world, double x, double y, double z) { return getContainerAt(world, x, y, z, false); } // Paper - overload to default false
@Nullable
- private static Container getContainerAt(Level world, double x, double y, double z) {
+ private static Container getContainerAt(Level world, double x, double y, double z, boolean optimizeEntities) {
Object object = null;
BlockPos blockposition = new BlockPos(x, y, z);
if ( !world.hasChunkAt( blockposition ) ) return null; // Spigot
@@ -475,7 +677,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
}
- if (object == null) {
+ if (object == null && (!optimizeEntities || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper
List<Entity> list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR);
if (!list.isEmpty()) {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
index f23fff80d07ac7d06715efe67cb49ebbe704967b..ed3518fe7c841d9e1a9c97626acaa3d765a6d76f 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
@@ -95,12 +95,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
@Override
public boolean isEmpty() {
this.unpackLootTable((Player)null);
- return this.getItems().stream().allMatch(ItemStack::isEmpty);
+ // Paper start
+ for (ItemStack itemStack : this.getItems()) {
+ if (!itemStack.isEmpty()) {
+ return false;
+ }
+ }
+ // Paper end
+ return true;
}
@Override
public ItemStack getItem(int slot) {
- this.unpackLootTable((Player)null);
+ if (slot == 0) this.unpackLootTable((Player) null); // Paper
return this.getItems().get(slot);
}

View file

@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Tue, 24 Dec 2019 00:35:42 +0000
Subject: [PATCH] PlayerDeathEvent#shouldDropExperience
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 8da35a7dde494b059e542d049d7ee039deb5833d..4fef4c0240be90ddcb9640f5719e292d2d5dbdf8 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -849,7 +849,7 @@ public class ServerPlayer extends Player {
this.tellNeutralMobsThatIDied();
}
// SPIGOT-5478 must be called manually now
- this.dropExperience();
+ if (event.shouldDropExperience()) this.dropExperience(); // Paper - tie to event
// we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory.
if (!event.getKeepInventory()) {
this.getInventory().clearContent();

View file

@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sun, 5 Jan 2020 17:24:34 -0600
Subject: [PATCH] Prevent bees loading chunks checking hive position
diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
index 488dd4693dbbc303f0fc6d98667b822304b06c9f..f3ba4b26553915917c79f013ed9dd7c87d9f65a4 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
@@ -495,6 +495,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
if (!this.hasHive()) {
return false;
} else {
+ if (level.getChunkIfLoadedImmediately(hivePos.getX() >> 4, hivePos.getZ() >> 4) == null) return true; // Paper - just assume the hive is still there, no need to load the chunk(s)
BlockEntity tileentity = this.level.getBlockEntity(this.hivePos);
return tileentity != null && tileentity.getType() == BlockEntityType.BEEHIVE;

View file

@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 3 Nov 2016 20:28:12 -0400
Subject: [PATCH] Don't load Chunks from Hoppers and other things
Hoppers call this to I guess "get the primary side" of a double sided chest.
If the double sided chest crosses chunk lines, it causes the chunk to load.
This will end up causing sync chunk loads, which will unload with Chunk GC,
only to be reloaded again the next tick.
This of course is undesirable, so just return the loaded side as "primary"
and treat it as a single chest if the other sides are unloaded
diff --git a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java
index ff2a7b08fe70adaecdaa508baadcfe40416519e0..7082bb0b28b6a046e3925f69e18b7c319871128f 100644
--- a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java
+++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java
@@ -25,7 +25,12 @@ public class DoubleBlockCombiner {
return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity);
} else {
BlockPos blockPos = pos.relative(function.apply(state));
- BlockState blockState = world.getBlockState(blockPos);
+ // Paper start
+ BlockState blockState = world.getTypeIfLoaded(blockPos);
+ if (blockState == null) {
+ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity);
+ }
+ // Paper end
if (blockState.is(state.getBlock())) {
DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState);
if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE && blockType != blockType2 && blockState.getValue(directionProperty) == state.getValue(directionProperty)) {

View file

@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 27 Dec 2019 09:42:26 -0800
Subject: [PATCH] Guard against serializing mismatching chunk coordinate
Should help if something dumb happens
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 3cb35f2f47ba841915ac2825d30cc4c3274ce3c4..7c04aef3eac54981ca1e34cb87d97104c3c9685b 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -69,6 +69,13 @@ public class ChunkSerializer {
public ChunkSerializer() {}
+ // Paper start - guard against serializing mismatching coordinates
+ // TODO Note: This needs to be re-checked each update
+ public static ChunkPos getChunkCoordinate(CompoundTag chunkData) {
+ CompoundTag levelData = chunkData.getCompound("Level");
+ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
+ }
+ // Paper end
// Paper start
public static final class InProgressChunkHolder {
@@ -95,8 +102,8 @@ public class ChunkSerializer {
// Paper end
ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
- CompoundTag nbttagcompound1 = nbt.getCompound("Level");
- ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos"));
+ CompoundTag nbttagcompound1 = nbt.getCompound("Level"); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
+ ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
if (!Objects.equals(pos, chunkcoordintpair1)) {
ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", pos, pos, chunkcoordintpair1);
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
index 6f13c7adce7d4b3d170045ea5ef2a841d34ae7b0..176610b31f66b890afe61f4de46c412382bb8d22 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -117,6 +117,13 @@ public class ChunkStorage implements AutoCloseable {
// Paper start - async chunk io
public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException {
+ // Paper start
+ if (!chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) {
+ String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap)this).level.getWorld().getName() : null;
+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos.toString()
+ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt).toString() + (world == null ? " for an unknown world" : (" for world: " + world)));
+ }
+ // Paper end
this.regionFileCache.write(chunkPos, nbt);
// Paper end - Async chunk loading
if (this.legacyStructureHandler != null) {

View file

@ -0,0 +1,26 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 11 Jan 2020 21:50:56 -0800
Subject: [PATCH] Optimise IEntityAccess#getPlayerByUUID
Use the world entity map instead of iterating over all players
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 736521c0925339dc74c2648641932104a947708e..eabf8fa6e8802ebea8dbe124363bb085849af5d9 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -281,6 +281,14 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
// Paper end
+ // Paper start - optimise getPlayerByUUID
+ @Nullable
+ @Override
+ public Player getPlayerByUUID(UUID uuid) {
+ return this.getServer().getPlayerList().getPlayer(uuid);
+ }
+ // Paper end
+
// Add env and gen to constructor, WorldData -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<net.minecraft.world.level.Level> resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
// Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error

View file

@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: AJMFactsheets <AJMFactsheets@gmail.com>
Date: Fri, 17 Jan 2020 17:17:54 -0600
Subject: [PATCH] Fix items not falling correctly
Since 1.14, Mojang has added an optimization which skips checking if
an item should fall every fourth tick.
However, Spigot's entity activation range class also has an
optimization which skips ticking active entities every fourth tick.
This can result in a state where an item will never properly fall
due to its move method never being called.
This patch resolves the conflict by offsetting checking an item's
move method from Spigot's entity activation range check.
diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
index 82ffe3624943d2e931e2cc2f85ede94f369bd06b..9ee1dc89dd4c6b9453e1f6f92208d454877d23c9 100644
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
@@ -135,7 +135,7 @@ public class ItemEntity extends Entity {
}
}
- if (!this.onGround || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) {
+ if (!this.onGround || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || this.tickCount % 4 == 0) { // Paper - Ensure checking item movement is always offset from Spigot's entity activation range check
this.move(MoverType.SELF, this.getDeltaMovement());
float f1 = 0.98F;

View file

@ -0,0 +1,75 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 14 Jan 2020 15:28:28 -0800
Subject: [PATCH] Lag compensate eating
When the server is lagging, players will wait longer when eating.
Change to also use a time check instead if it passes.
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 5def2e0379373b126a1b2281b0859b39443fc4ac..f48435dc344fb48feb48e2a141b394829058c5c4 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3492,6 +3492,11 @@ public abstract class LivingEntity extends Entity {
return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
}
+ // Paper start - lag compensate eating
+ protected long eatStartTime;
+ protected int totalEatTimeTicks;
+ // Paper end
+
private void updatingUsingItem() {
if (this.isUsingItem()) {
if (ItemStack.isSameIgnoreDurability(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
@@ -3509,8 +3514,12 @@ public abstract class LivingEntity extends Entity {
if (this.shouldTriggerItemUseEffects()) {
this.triggerItemUseEffects(stack, 5);
}
-
- if (--this.useItemRemaining == 0 && !this.level.isClientSide && !stack.useOnRelease()) {
+ // Paper start - lag compensate eating
+ // we add 1 to the expected time to avoid lag compensating when we should not
+ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000));
+ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level.isClientSide && !this.useItem.useOnRelease()) {
+ this.useItemRemaining = 0;
+ // Paper end
this.completeUsingItem();
}
@@ -3556,7 +3565,10 @@ public abstract class LivingEntity extends Entity {
if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag
this.useItem = itemstack;
- this.useItemRemaining = itemstack.getUseDuration();
+ // Paper start - lag compensate eating
+ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration();
+ this.eatStartTime = System.nanoTime();
+ // Paper end
if (!this.level.isClientSide) {
this.setLivingEntityFlag(1, true);
this.setLivingEntityFlag(2, enumhand == InteractionHand.OFF_HAND);
@@ -3580,7 +3592,10 @@ public abstract class LivingEntity extends Entity {
}
} else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
this.useItem = ItemStack.EMPTY;
- this.useItemRemaining = 0;
+ // Paper start - lag compensate eating
+ this.useItemRemaining = this.totalEatTimeTicks = 0;
+ this.eatStartTime = -1L;
+ // Paper end
}
}
@@ -3706,7 +3721,10 @@ public abstract class LivingEntity extends Entity {
}
this.useItem = ItemStack.EMPTY;
- this.useItemRemaining = 0;
+ // Paper start - lag compensate eating
+ this.useItemRemaining = this.totalEatTimeTicks = 0;
+ this.eatStartTime = -1L;
+ // Paper end
}
public boolean isBlocking() {

View file

@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: BrodyBeckwith <brody@beckwith.dev>
Date: Tue, 14 Jan 2020 17:49:03 -0500
Subject: [PATCH] Optimize call to getFluid for explosions
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index edc0845ae735a9cf3e0f1a3a2b7eabf726d62744..cdf214fca3b0055efa56702470d9d2f890a8aead 100644
--- a/src/main/java/net/minecraft/world/level/Explosion.java
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
@@ -174,7 +174,7 @@ public class Explosion {
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
BlockPos blockposition = new BlockPos(d4, d5, d6);
BlockState iblockdata = this.level.getBlockState(blockposition);
- FluidState fluid = this.level.getFluidState(blockposition);
+ FluidState fluid = iblockdata.getFluidState(); // Paper
if (!this.level.isInWorldBounds(blockposition)) {
break;

View file

@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 17 Jan 2020 18:44:55 -0800
Subject: [PATCH] Fix last firework in stack not having effects when dispensed
- #2871
CB used the resulting item in the dispenser rather than the item
dispensed. The resulting item would have size == 0 and therefore
be convertered to air, hence why the effects disappeared.
diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
index b665d4293b746b221d469a7b029c1c7f17df6188..92623ae25249d63efb92be8bd6c95228f9155ad2 100644
--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -431,7 +431,7 @@ public interface DispenseItemBehavior {
}
itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
- FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), stack, pointer.x(), pointer.y(), pointer.x(), true);
+ FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), itemstack1, pointer.x(), pointer.y(), pointer.x(), true); // Paper - GH-2871 - fix last firework in stack having no effects when dispensed
DispenseItemBehavior.setEntityPokingOutOfBlock(pointer, entityfireworks, enumdirection);
entityfireworks.shoot((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), 0.5F, 1.0F);

View file

@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Thu, 2 Jan 2020 12:25:07 -0600
Subject: [PATCH] Add effect to block break naturally
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
index 0a9ed9992a2fc97472a06591a5d129a767ce21af..ca95a6b1b156b37f839c6479733e5184691af66c 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
@@ -632,6 +632,13 @@ public class CraftBlock implements Block {
@Override
public boolean breakNaturally(ItemStack item) {
+ // Paper start
+ return breakNaturally(item, false);
+ }
+
+ @Override
+ public boolean breakNaturally(ItemStack item, boolean triggerEffect) {
+ // Paper end
// Order matters here, need to drop before setting to air so skulls can get their data
net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS();
net.minecraft.world.level.block.Block block = iblockdata.getBlock();
@@ -641,6 +648,7 @@ public class CraftBlock implements Block {
// Modelled off EntityHuman#hasBlock
if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) {
net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), position, this.world.getBlockEntity(position), null, nmsItem);
+ if (triggerEffect) world.levelEvent(org.bukkit.Effect.STEP_SOUND.getId(), position, net.minecraft.world.level.block.Block.getId(block.defaultBlockState())); // Paper
result = true;
}

View file

@ -0,0 +1,774 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 13 May 2016 01:38:06 -0400
Subject: [PATCH] Entity Activation Range 2.0
Optimizes performance of Activation Range
Adds many new configurations and a new wake up inactive system
Fixes and adds new Immunities to improve gameplay behavior
Adds water Mobs to activation range config and nerfs fish
Adds flying monsters to control ghast and phantoms
Adds villagers as separate config
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index eabf8fa6e8802ebea8dbe124363bb085849af5d9..67b9daee8d7e55fdf2015e6616f393f176b1ca96 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2,7 +2,6 @@ package net.minecraft.server.level;
import com.google.common.annotations.VisibleForTesting;
import co.aikar.timings.TimingHistory; // Paper
-import co.aikar.timings.Timings; // Paper
import com.google.common.collect.Lists;
import com.mojang.datafixers.DataFixer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -11,7 +10,6 @@ import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -123,7 +121,6 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.storage.EntityStorage;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.end.EndDragonFight;
-import net.minecraft.world.level.entity.EntityAccess;
import net.minecraft.world.level.entity.EntityPersistentStorage;
import net.minecraft.world.level.entity.EntityTickList;
import net.minecraft.world.level.entity.EntityTypeTest;
@@ -888,17 +885,17 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
++TimingHistory.entityTicks; // Paper - timings
// Spigot start
co.aikar.timings.Timing timer; // Paper
- if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
+ /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below
entity.tickCount++;
timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings
entity.inactiveTick();
} finally { timer.stopTiming(); } // Paper
return;
- }
+ }*/ // Paper - comment out EAR 2
// Spigot end
// Paper start- timings
- TimingHistory.activatedEntityTicks++;
- timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming();
+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity);
+ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper
try {
// Paper end - timings
entity.isInLava();
@@ -909,9 +906,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
return Registry.ENTITY_TYPE.getKey(entity.getType()).toString();
});
gameprofilerfiller.incrementCounter("tickNonPassenger");
+ if (isActive) { // Paper - EAR 2
+ TimingHistory.activatedEntityTicks++;
entity.tick();
entity.postTick(); // CraftBukkit
+ } else { entity.inactiveTick(); } // Paper - EAR 2
this.getProfiler().pop();
+ } finally { timer.stopTiming(); } // Paper - timings
Iterator iterator = entity.getPassengers().iterator();
while (iterator.hasNext()) {
@@ -920,13 +921,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
this.tickPassenger(entity, entity1);
}
- } finally { timer.stopTiming(); } // Paper - timings
+ // } finally { timer.stopTiming(); } // Paper - timings - move up
}
private void tickPassenger(Entity vehicle, Entity passenger) {
if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) {
if (passenger instanceof Player || this.entityTickList.contains(passenger)) {
+ // Paper - EAR 2
+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger);
+ co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper
+ try {
+ // Paper end
passenger.setOldPosAndRot();
++passenger.tickCount;
ProfilerFiller gameprofilerfiller = this.getProfiler();
@@ -935,8 +941,17 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
return Registry.ENTITY_TYPE.getKey(passenger.getType()).toString();
});
gameprofilerfiller.incrementCounter("tickPassenger");
+ // Paper start - EAR 2
+ if (isActive) {
passenger.rideTick();
passenger.postTick(); // CraftBukkit
+ } else {
+ passenger.setDeltaMovement(Vec3.ZERO);
+ passenger.inactiveTick();
+ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
+ vehicle.positionRider(passenger);
+ }
+ // Paper end - EAR 2
gameprofilerfiller.pop();
Iterator iterator = passenger.getPassengers().iterator();
@@ -946,6 +961,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
this.tickPassenger(passenger, entity2);
}
+ } finally { timer.stopTiming(); }// Paper - EAR2 timings
}
} else {
passenger.stopRiding();
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index a5159198003e43ce272ae73941d2be47d50eedc9..fffeba61e9af5c69876921b48241edb881af2a64 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -275,7 +275,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
public boolean noCulling;
public boolean hasImpulse;
public int portalCooldown;
- protected boolean isInsidePortal;
+ public boolean isInsidePortal; // Paper - public
protected int portalTime;
protected BlockPos portalEntrancePos;
private boolean invulnerable;
@@ -305,6 +305,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
public final boolean defaultActivationState;
public long activatedTick = Integer.MIN_VALUE;
+ public boolean isTemporarilyActive = false; // Paper
public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
protected int numCollisions = 0; // Paper
public void inactiveTick() { }
@@ -746,6 +747,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
} else {
this.wasOnFire = this.isOnFire();
if (movementType == MoverType.PISTON) {
+ this.activatedTick = MinecraftServer.currentTick + 20; // Paper
movement = this.limitPistonMovement(movement);
if (movement.equals(Vec3.ZERO)) {
return;
@@ -758,6 +760,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
this.stuckSpeedMultiplier = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
}
+ // Paper start - ignore movement changes while inactive.
+ if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) {
+ setDeltaMovement(Vec3.ZERO);
+ this.level.getProfiler().pop();
+ return;
+ }
+ // Paper end
movement = this.maybeBackOffFromEdge(movement, movementType);
Vec3 vec3d1 = this.collide(movement);
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index f48435dc344fb48feb48e2a141b394829058c5c4..bd1cc5e6fea4b9a171718c1249f652782b7ce13e 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -219,7 +219,7 @@ public abstract class LivingEntity extends Entity {
protected float rotOffs;
protected int deathScore;
public float lastHurt;
- protected boolean jumping;
+ public boolean jumping; // Paper protected -> public
public float xxa;
public float yya;
public float zza;
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 32e8ae0d2a0f78af671a632c4d1be58a0b38a392..79e10f41b2993a02c4218551b06673f1848325a3 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -117,7 +117,7 @@ public abstract class Mob extends LivingEntity {
public ResourceLocation lootTable;
public long lootTableSeed;
@Nullable
- private Entity leashHolder;
+ public Entity leashHolder; // Paper - private -> public
private int delayedLeashHolderId;
@Nullable
private CompoundTag leashInfoTag;
@@ -198,6 +198,19 @@ public abstract class Mob extends LivingEntity {
return this.lookControl;
}
+ // Paper start
+ @Override
+ public void inactiveTick() {
+ super.inactiveTick();
+ if (this.goalSelector.inactiveTick()) {
+ this.goalSelector.tick();
+ }
+ if (this.targetSelector.inactiveTick()) {
+ this.targetSelector.tick();
+ }
+ }
+ // Paper end
+
public MoveControl getMoveControl() {
if (this.isPassenger() && this.getVehicle() instanceof Mob) {
Mob entityinsentient = (Mob) this.getVehicle();
diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java
index 920ae9af8985705a0ada7da5b7085a1ed8ca7f27..7c82d453388a27b69207d051dec316fc14715e2b 100644
--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java
+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java
@@ -13,6 +13,7 @@ import org.bukkit.event.entity.EntityUnleashEvent;
public abstract class PathfinderMob extends Mob {
public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper
+ public BlockPos movingTarget = null; public BlockPos getMovingTarget() { return movingTarget; } // Paper
protected PathfinderMob(EntityType<? extends PathfinderMob> type, Level world) {
super(type, world);
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
index 70e5b4446a8485b5995a0ba26af3a86d9b79bcc7..f29ace7b6a27a602102d37d43a6dd0571f218dfe 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
@@ -31,6 +31,7 @@ public class GoalSelector {
private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
private int tickCount;
private int newGoalRate = 3;
+ private int curRate;
public GoalSelector(Supplier<ProfilerFiller> profiler) {
this.profiler = profiler;
@@ -45,6 +46,20 @@ public class GoalSelector {
this.availableGoals.clear();
}
+ // Paper start
+ public boolean inactiveTick() {
+ this.curRate++;
+ return this.curRate % this.newGoalRate == 0;
+ }
+ public boolean hasTasks() {
+ for (WrappedGoal task : this.availableGoals) {
+ if (task.isRunning()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // Paper end
public void removeGoal(Goal goal) {
this.availableGoals.stream().filter((wrappedGoal) -> {
return wrappedGoal.getGoal() == goal;
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index 065d0752db0e3ae2a89d707aaa2145807f50ecad..c93805ae832d049ea13ca495b778ed52381b1f78 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
@@ -14,7 +14,7 @@ public abstract class MoveToBlockGoal extends Goal {
protected int nextStartTick;
protected int tryTicks;
private int maxStayTicks;
- protected BlockPos blockPos = BlockPos.ZERO; public final BlockPos getTargetPosition() { return this.blockPos; } // Paper - OBFHELPER
+ protected BlockPos blockPos = BlockPos.ZERO; public final BlockPos getTargetPosition() { return this.blockPos; } public void setTargetPosition(BlockPos pos) { this.blockPos = pos; mob.movingTarget = pos != BlockPos.ZERO ? pos : null; } // Paper - OBFHELPER
private boolean reachedTarget;
private final int searchRange;
private final int verticalSearchRange;
@@ -23,6 +23,13 @@ public abstract class MoveToBlockGoal extends Goal {
public MoveToBlockGoal(PathfinderMob mob, double speed, int range) {
this(mob, speed, range, 1);
}
+ // Paper start - activation range improvements
+ @Override
+ public void stop() {
+ super.stop();
+ setTargetPosition(BlockPos.ZERO);
+ }
+ // Paper end
public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) {
this.mob = mob;
@@ -109,6 +116,7 @@ public abstract class MoveToBlockGoal extends Goal {
mutableBlockPos.setWithOffset(blockPos, m, k - 1, n);
if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level, mutableBlockPos)) {
this.blockPos = mutableBlockPos;
+ setTargetPosition(mutableBlockPos.immutable()); // Paper
return true;
}
}
diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
index 6a9e2105b2d9a4ee83c0a2516d5ef26dc3b99053..a35ce224b11b44567814207c3821d0cd4cdd9ec1 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
@@ -227,17 +227,29 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
@Override
public void inactiveTick() {
// SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
- if (level.spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
- this.customServerAiStep();
+ // Paper start
+ if (this.getUnhappyCounter() > 0) {
+ this.setUnhappyCounter(this.getUnhappyCounter() - 1);
+ }
+ if (this.isEffectiveAi()) {
+ if (level.spigotConfig.tickInactiveVillagers) {
+ this.customServerAiStep();
+ } else {
+ this.mobTick(true);
+ }
}
+ maybeDecayGossip();
+ // Paper end
+
super.inactiveTick();
}
// Spigot End
@Override
- protected void customServerAiStep() {
+ protected void customServerAiStep() { mobTick(false); }
+ protected void mobTick(boolean inactive) {
this.level.getProfiler().push("villagerBrain");
- this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error
+ if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error // Paper
this.level.getProfiler().pop();
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
@@ -261,7 +273,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
this.lastTradedPlayer = null;
}
- if (!this.isNoAi() && this.random.nextInt(100) == 0) {
+ if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper
Raid raid = ((ServerLevel) this.level).getRaidAt(this.blockPosition());
if (raid != null && raid.isActive() && !raid.isOver()) {
@@ -272,6 +284,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) {
this.stopTrading();
}
+ if (inactive) return; // Paper
super.customServerAiStep();
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 9891e40972c1dc6bebe8ccec2bf82123dcdd7e94..ddcde88a8c8b748b4dc2583b4dfd0fb8ea9e201e 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -153,6 +153,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public long ticksPerWaterSpawns;
public long ticksPerWaterAmbientSpawns;
public long ticksPerAmbientSpawns;
+ // Paper start
+ public int wakeupInactiveRemainingAnimals;
+ public int wakeupInactiveRemainingFlying;
+ public int wakeupInactiveRemainingMonsters;
+ public int wakeupInactiveRemainingVillagers;
+ // Paper end
public boolean populating;
public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 84ce3d38d5decb4a2f9fae78e0ef5d715860dc7d..2ab585a018290996e7fa9ca6f3ad7d734cd7beaa 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -1,39 +1,51 @@
package org.spigotmc;
-import java.util.Collection;
+import net.minecraft.core.BlockPos;
import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.FlyingMob;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ambient.AmbientCreature;
import net.minecraft.world.entity.animal.Animal;
+import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.entity.animal.Sheep;
+import net.minecraft.world.entity.animal.WaterAnimal;
+import net.minecraft.world.entity.animal.horse.Llama;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.entity.boss.wither.WitherBoss;
import net.minecraft.world.entity.item.PrimedTnt;
import net.minecraft.world.entity.monster.Creeper;
-import net.minecraft.world.entity.monster.Monster;
-import net.minecraft.world.entity.monster.Slime;
+import net.minecraft.world.entity.monster.Enemy;
+import net.minecraft.world.entity.monster.Pillager;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
+import net.minecraft.world.entity.projectile.EyeOfEnder;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.entity.projectile.ThrowableProjectile;
import net.minecraft.world.entity.projectile.ThrownTrident;
import net.minecraft.world.entity.raid.Raider;
+import co.aikar.timings.MinecraftTimings;
+import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
-import co.aikar.timings.MinecraftTimings;
public class ActivationRange
{
public enum ActivationType
{
+ WATER, // Paper
+ FLYING_MONSTER, // Paper
+ VILLAGER, // Paper
MONSTER,
ANIMAL,
RAIDER,
@@ -41,6 +53,43 @@ public class ActivationRange
AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 );
}
+ // Paper start
+
+ static Activity[] VILLAGER_PANIC_IMMUNITIES = {
+ Activity.HIDE,
+ Activity.PRE_RAID,
+ Activity.RAID,
+ Activity.PANIC
+ };
+
+ private static int checkInactiveWakeup(Entity entity) {
+ Level world = entity.level;
+ SpigotWorldConfig config = world.spigotConfig;
+ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
+ if (entity.activationType == ActivationType.VILLAGER) {
+ if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) {
+ world.wakeupInactiveRemainingVillagers--;
+ return config.wakeUpInactiveVillagersFor;
+ }
+ } else if (entity.activationType == ActivationType.ANIMAL) {
+ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) {
+ world.wakeupInactiveRemainingAnimals--;
+ return config.wakeUpInactiveAnimalsFor;
+ }
+ } else if (entity.activationType == ActivationType.FLYING_MONSTER) {
+ if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) {
+ world.wakeupInactiveRemainingFlying--;
+ return config.wakeUpInactiveFlyingFor;
+ }
+ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
+ if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) {
+ world.wakeupInactiveRemainingMonsters--;
+ return config.wakeUpInactiveMonstersFor;
+ }
+ }
+ return -1;
+ }
+ // Paper end
static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 );
@@ -53,10 +102,13 @@ public class ActivationRange
*/
public static ActivationType initializeEntityActivationType(Entity entity)
{
+ if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper
+ else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper
+ else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future
if ( entity instanceof Raider )
{
return ActivationType.RAIDER;
- } else if ( entity instanceof Monster || entity instanceof Slime )
+ } else if ( entity instanceof Enemy ) // Paper - correct monster check
{
return ActivationType.MONSTER;
} else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature )
@@ -77,10 +129,14 @@ public class ActivationRange
*/
public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
{
- if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 )
- || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 )
- || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 )
- || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 )
+ if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 )
+ || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 )
+ || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 )
+ || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 )
+ || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper
+ || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper
+ || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper
+ || entity instanceof EyeOfEnder // Paper
|| entity instanceof Player
|| entity instanceof ThrowableProjectile
|| entity instanceof EnderDragon
@@ -113,10 +169,25 @@ public class ActivationRange
final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
final int animalActivationRange = world.spigotConfig.animalActivationRange;
final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
+ // Paper start
+ final int waterActivationRange = world.spigotConfig.waterActivationRange;
+ final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange;
+ final int villagerActivationRange = world.spigotConfig.villagerActivationRange;
+ world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals);
+ world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers);
+ world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters);
+ world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying);
+ final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource();
+ // Paper end
int maxRange = Math.max( monsterActivationRange, animalActivationRange );
maxRange = Math.max( maxRange, raiderActivationRange );
maxRange = Math.max( maxRange, miscActivationRange );
+ // Paper start
+ maxRange = Math.max( maxRange, flyingActivationRange );
+ maxRange = Math.max( maxRange, waterActivationRange );
+ maxRange = Math.max( maxRange, villagerActivationRange );
+ // Paper end
maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
for ( Player player : world.players() )
@@ -128,6 +199,11 @@ public class ActivationRange
ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange );
ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange );
ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange );
+ // Paper start
+ ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, 256, waterActivationRange );
+ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, 256, flyingActivationRange );
+ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, 256, waterActivationRange );
+ // Paper end
world.getEntities().get(maxBB, ActivationRange::activateEntity);
}
@@ -162,56 +238,105 @@ public class ActivationRange
* @param entity
* @return
*/
- public static boolean checkEntityImmunities(Entity entity)
+ public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity
{
+ // Paper start
+ SpigotWorldConfig config = entity.level.spigotConfig;
+ int inactiveWakeUpImmunity = checkInactiveWakeup(entity);
+ if (inactiveWakeUpImmunity > -1) {
+ return inactiveWakeUpImmunity;
+ }
+ if (entity.remainingFireTicks > 0) {
+ return 2;
+ }
+ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
+ // Paper end
// quick checks.
- if ( entity.wasTouchingWater || entity.remainingFireTicks > 0 )
+ if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByFluid()) ) // Paper
{
- return true;
+ return 100; // Paper
}
if ( !( entity instanceof AbstractArrow ) )
{
- if ( !entity.isOnGround() || !entity.passengers.isEmpty() || entity.isPassenger() )
+ if ( (!entity.isOnGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic
{
- return true;
+ return 10; // Paper
}
} else if ( !( (AbstractArrow) entity ).inGround )
{
- return true;
+ return 1; // Paper
}
// special cases.
if ( entity instanceof LivingEntity )
{
LivingEntity living = (LivingEntity) entity;
- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 )
+ if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper
{
- return true;
+ return 1; // Paper
}
- if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null )
+ if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper
{
- return true;
+ return 20; // Paper
+ }
+ // Paper start
+ if (entity instanceof Bee) {
+ Bee bee = (Bee)entity;
+ BlockPos movingTarget = bee.getMovingTarget();
+ if (bee.isAngry() ||
+ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) ||
+ (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget))
+ ) {
+ return 20;
+ }
+ }
+ if ( entity instanceof Villager ) {
+ Brain<Villager> behaviorController = ((Villager) entity).getBrain();
+
+ if (config.villagersActiveForPanic) {
+ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) {
+ if (behaviorController.isActive(activity)) {
+ return 20*5;
+ }
+ }
+ }
+
+ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) {
+ if (behaviorController.isActive(Activity.WORK)) {
+ return config.villagersWorkImmunityFor;
+ }
+ }
}
- if ( entity instanceof Villager && ( (Villager) entity ).canBreed() )
+ if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() )
{
- return true;
+ return 1;
}
+ // Paper end
if ( entity instanceof Animal )
{
Animal animal = (Animal) entity;
if ( animal.isBaby() || animal.isInLove() )
{
- return true;
+ return 5; // Paper
}
if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() )
{
- return true;
+ return 1; // Paper
}
}
if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive
- return true;
+ return 20; // Paper
+ }
+ // Paper start
+ if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) {
+ return 0;
+ }
+ if (entity instanceof Pillager) {
+ Pillager pillager = (Pillager) entity;
+ // TODO:?
}
+ // Paper end
}
- return false;
+ return -1; // Paper
}
/**
@@ -226,8 +351,19 @@ public class ActivationRange
if ( entity instanceof FireworkRocketEntity ) {
return true;
}
+ // Paper start - special case always immunities
+ // immunize brand new entities, dead entities, and portal scenarios
+ if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || entity.isInsidePortal || entity.portalCooldown > 0) {
+ return true;
+ }
+ // immunize leashed entities
+ if (entity instanceof Mob && ((Mob)entity).leashHolder instanceof Player) {
+ return true;
+ }
+ // Paper end
- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
+ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick;
+ entity.isTemporarilyActive = false; // Paper
// Should this entity tick?
if ( !isActive )
@@ -235,15 +371,19 @@ public class ActivationRange
if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 )
{
// Check immunities every 20 ticks.
- if ( ActivationRange.checkEntityImmunities( entity ) )
- {
- // Triggered some sort of immunity, give 20 full ticks before we check again.
- entity.activatedTick = MinecraftServer.currentTick + 20;
+ // Paper start
+ int immunity = checkEntityImmunities(entity);
+ if (immunity >= 0) {
+ entity.activatedTick = MinecraftServer.currentTick + immunity;
+ } else {
+ entity.isTemporarilyActive = true;
}
+ // Paper end
isActive = true;
+
}
// Add a little performance juice to active entities. Skip 1/4 if not immune.
- } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) )
+ } else if ( entity.tickCount % 4 == 0 && ActivationRange.checkEntityImmunities( entity ) < 0 ) // Paper
{
isActive = false;
}
diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
index 8e6eae7cc27f66faede9a3dc74571e1814df3652..308de0757c219c1e5fa8bde182343c4095301cde 100644
--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
@@ -180,13 +180,59 @@ public class SpigotWorldConfig
public int monsterActivationRange = 32;
public int raiderActivationRange = 48;
public int miscActivationRange = 16;
+ // Paper start
+ public int flyingMonsterActivationRange = 32;
+ public int waterActivationRange = 16;
+ public int villagerActivationRange = 32;
+ public int wakeUpInactiveAnimals = 4;
+ public int wakeUpInactiveAnimalsEvery = 60*20;
+ public int wakeUpInactiveAnimalsFor = 5*20;
+ public int wakeUpInactiveMonsters = 8;
+ public int wakeUpInactiveMonstersEvery = 20*20;
+ public int wakeUpInactiveMonstersFor = 5*20;
+ public int wakeUpInactiveVillagers = 4;
+ public int wakeUpInactiveVillagersEvery = 30*20;
+ public int wakeUpInactiveVillagersFor = 5*20;
+ public int wakeUpInactiveFlying = 8;
+ public int wakeUpInactiveFlyingEvery = 10*20;
+ public int wakeUpInactiveFlyingFor = 5*20;
+ public int villagersWorkImmunityAfter = 5*20;
+ public int villagersWorkImmunityFor = 20;
+ public boolean villagersActiveForPanic = true;
+ // Paper end
public boolean tickInactiveVillagers = true;
private void activationRange()
{
+ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper
this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange );
this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange );
this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange );
this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange );
+ // Paper start
+ this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange );
+ this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange );
+ this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange );
+
+ this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals);
+ this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery);
+ this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor);
+
+ this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters);
+ this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery);
+ this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor);
+
+ this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers);
+ this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery);
+ this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor);
+
+ this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying);
+ this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery);
+ this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor);
+
+ this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter );
+ this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor );
+ this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic );
+ // Paper end
this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers );
this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers );
}

View file

@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 8 Apr 2020 21:24:05 -0400
Subject: [PATCH] Increase Light Queue Size
Wiz mentioned that large WorldEdit operations cause light to run on
main thread. The queue was small, set to 5.. this bumps it to 20
but makes it configurable per-world.
The main risk of increasing this higher is during shutdown, some
queued light updates may be lost because mojang did not flush the
light engine on shutdown...
The queue size only puts a cap on max loss, doesn't solve that problem.
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 041efecbfcd54384d15ebf771cdfa0e711483997..e29566cad2647da4d9288e912188b57f00e8dd0c 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -440,5 +440,10 @@ public class PaperWorldConfig {
disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents);
log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled"));
}
+
+ public int lightQueueSize = 20;
+ private void lightQueueSize() {
+ lightQueueSize = getInt("light-queue-size", lightQueueSize);
+ }
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 312be2221e1acc44aaf6936533b0eb968f796dc6..494a3afaaa0e3496d30e8d97edbab62b21610dfe 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -832,7 +832,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.executeModerately();
// CraftBukkit end
if (worldserver.getWorld().getKeepSpawnInMemory()) worldloadlistener.stop(); // Paper
- chunkproviderserver.getLightEngine().setTaskPerBatch(5);
+ chunkproviderserver.getLightEngine().setTaskPerBatch(worldserver.paperConfig.lightQueueSize); // Paper - increase light queue size
// CraftBukkit start
// this.updateSpawnFlags();
worldserver.setSpawnSettings(this.isSpawningMonsters(), this.isSpawningAnimals());

View file

@ -0,0 +1,179 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 7 May 2020 19:17:36 -0400
Subject: [PATCH] Fix Light Command
This lets you run /paper fixlight <chunkRadius> (max 5) to automatically
fix all light data in the chunks.
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
index 71ffa66973d8994e2a480435ac1ada3fe61600a4..7b5afc5d34b78e6404c1a5c6bb823d9589471f13 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -10,7 +10,8 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
-import net.minecraft.world.entity.Entity;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ThreadedLevelLightEngine;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.resources.ResourceLocation;
@@ -25,15 +26,18 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -43,7 +47,7 @@ import java.util.stream.Collectors;
public class PaperCommand extends Command {
private static final String BASE_PERM = "bukkit.command.paper.";
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build();
public PaperCommand(String name) {
super(name);
@@ -158,6 +162,9 @@ public class PaperCommand extends Command {
case "chunkinfo":
doChunkInfo(sender, args);
break;
+ case "fixlight":
+ this.doFixLight(sender, args);
+ break;
case "ver":
if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set)
case "version":
@@ -413,4 +420,74 @@ public class PaperCommand extends Command {
Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Paper config reload complete.");
}
+ private void doFixLight(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage("Only players can use this command");
+ return;
+ }
+ int radius = 2;
+ if (args.length > 1) {
+ try {
+ radius = Math.min(5, Integer.parseInt(args[1]));
+ } catch (Exception e) {
+ sender.sendMessage("Not a number");
+ return;
+ }
+
+ }
+
+ CraftPlayer player = (CraftPlayer) sender;
+ ServerPlayer handle = player.getHandle();
+ ServerLevel world = (ServerLevel) handle.level;
+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine();
+
+ net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation());
+ Deque<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
+ updateLight(sender, world, lightengine, queue);
+ }
+
+ private void updateLight(CommandSender sender, ServerLevel world, ThreadedLevelLightEngine lightengine, Deque<ChunkPos> queue) {
+ ChunkPos coord = queue.poll();
+ if (coord == null) {
+ sender.sendMessage("All Chunks Light updated");
+ return;
+ }
+ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> {
+ if (ex != null) {
+ sender.sendMessage("Error loading chunk " + coord);
+ updateLight(sender, world, lightengine, queue);
+ return;
+ }
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
+ if (chunk == null) {
+ updateLight(sender, world, lightengine, queue);
+ return;
+ }
+ lightengine.setTaskPerBatch(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue
+ sender.sendMessage("Updating Light " + coord);
+ int cx = chunk.getPos().x << 4;
+ int cz = chunk.getPos().z << 4;
+ for (int y = 0; y < world.getHeight(); y++) {
+ for (int x = 0; x < 16; x++) {
+ for (int z = 0; z < 16; z++) {
+ net.minecraft.core.BlockPos pos = new net.minecraft.core.BlockPos(cx + x, y, cz + z);
+ lightengine.checkBlock(pos);
+ }
+ }
+ }
+ lightengine.tryScheduleUpdate();
+ ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey);
+ if (visibleChunk != null) {
+ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> {
+ MinecraftServer.getServer().processQueue.add(() -> {
+ visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false);
+ updateLight(sender, world, lightengine, queue);
+ });
+ });
+ } else {
+ updateLight(sender, world, lightengine, queue);
+ }
+ lightengine.setTaskPerBatch(world.paperConfig.lightQueueSize);
+ }, MinecraftServer.getServer());
+ }
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 56e292e6550b19d0cae9ebad369da730ca1cabd8..3653ec6f93d627092e63cede51f4db2e12b10613 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -312,7 +312,7 @@ public class ChunkHolder {
}
- private void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
+ public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) { // Paper - private -> public
this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
entityplayer.connection.send(packet);
});
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 5473b22d21ea981f245f4826c4d47295890f88ef..480c361c919bbfbebacf9ac94418eac5ed38e233 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -123,6 +123,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private final ChunkTaskPriorityQueueSorter queueSorter;
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
+ // Paper start
+ final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mailboxLight;
+ public void addLightTask(ChunkHolder playerchunk, Runnable run) {
+ this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run));
+ }
+ // Paper end
public final ChunkProgressListener progressListener;
private final ChunkStatusUpdateListener chunkStatusListener;
public final ChunkMap.ChunkDistanceManager distanceManager;
@@ -199,11 +205,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.progressListener = worldGenerationProgressListener;
this.chunkStatusListener = chunkStatusChangeListener;
- ProcessorMailbox<Runnable> threadedmailbox1 = ProcessorMailbox.create(executor, "light");
+ ProcessorMailbox<Runnable> lightthreaded; ProcessorMailbox<Runnable> threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper
this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE);
this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false);
this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false);
+ this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false);// Paper
this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false));
this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
this.overworldDataStorage = persistentStateManagerFactory;

View file

@ -0,0 +1,710 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 5 May 2020 21:23:34 -0700
Subject: [PATCH] No-Tick view distance implementation
Implements world view distance getters/setters
Per-Player is absent due to difficulty of maintaining
the diff required to make it happen.
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
index f27fadc15cb7f5c782e45885ec6a5a69963beade..2ff4d4921e2076abf415bd3c8f5173ecd6222168 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -152,7 +152,8 @@ public class TimingsExport extends Thread {
pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
return pair(rule, world.getWorld().getGameRuleValue(rule));
})),
- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
+ pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()),
+ pair("notick-viewdistance", world.getChunkSource().chunkMap.getEffectiveNoTickViewDistance())
));
}));
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index e29566cad2647da4d9288e912188b57f00e8dd0c..3cd8895adecd345c3bdfb8b5e3e9fdf0ef9097db 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -445,5 +445,10 @@ public class PaperWorldConfig {
private void lightQueueSize() {
lightQueueSize = getInt("light-queue-size", lightQueueSize);
}
+
+ public int noTickViewDistance;
+ private void viewDistance() {
+ this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1);
+ }
}
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index bb0a07a280c7d4885165e9d6488e7741aaa7b47c..9c88426ab1275ee5fb6e28be8b213533dc4ab859 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -636,7 +636,8 @@ public final class MCUtil {
});
worldData.addProperty("name", world.getWorld().getName());
- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
+ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance());
+ worldData.addProperty("no-view-distance", world.getChunkSource().chunkMap.getRawNoTickViewDistance());
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
worldData.addProperty("visible-chunk-count", visibleChunks.size());
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 3653ec6f93d627092e63cede51f4db2e12b10613..995d7977233f0d7683c00a75c3833f9a1eba7f92 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -75,6 +75,17 @@ public class ChunkHolder {
boolean isUpdateQueued = false; // Paper
private final ChunkMap chunkMap; // Paper
+ // Paper start - no-tick view distance
+ public final LevelChunk getSendingChunk() {
+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
+ // in Chunk's neighbour callback
+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
+ if (ret != null && ret.areNeighboursLoaded(1)) {
+ return ret;
+ }
+ return null;
+ }
+ // Paper end - no-tick view distance
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
@@ -207,7 +218,7 @@ public class ChunkHolder {
}
public void blockChanged(BlockPos pos) {
- LevelChunk chunk = this.getTickingChunk();
+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
@@ -223,7 +234,7 @@ public class ChunkHolder {
}
public void sectionLightChanged(LightLayer lightType, int y) {
- LevelChunk chunk = this.getTickingChunk();
+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
chunk.setUnsaved(true);
@@ -313,9 +324,48 @@ public class ChunkHolder {
}
public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) { // Paper - private -> public
- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
- entityplayer.connection.send(packet);
- });
+ // Paper start - per player view distance
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
+ // view distance map here.
+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
+ if (players == null) {
+ return;
+ }
+
+ if (onlyOnWatchDistanceEdge) { // flag -> border only
+ Object[] backingSet = players.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)temp;
+
+ int viewDistance = viewDistanceMap.getLastViewDistance(player);
+ long lastPosition = viewDistanceMap.getLastCoordinate(player);
+
+ int distX = Math.abs(net.minecraft.server.MCUtil.getCoordinateX(lastPosition) - this.pos.x);
+ int distZ = Math.abs(net.minecraft.server.MCUtil.getCoordinateZ(lastPosition) - this.pos.z);
+
+ if (Math.max(distX, distZ) == viewDistance) {
+ player.connection.send(packet);
+ }
+ }
+ } else {
+ Object[] backingSet = players.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)temp;
+ player.connection.send(packet);
+ }
+ }
+
+ return;
+ // Paper end - per player view distance
}
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 480c361c919bbfbebacf9ac94418eac5ed38e233..fdf5d8ede4b01e399272ddebfbd49258b166f00b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -122,7 +122,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private boolean modified;
private final ChunkTaskPriorityQueueSorter queueSorter;
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
- private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
+ public final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox; // Paper - private -> public
// Paper start
final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mailboxLight;
public void addLightTask(ChunkHolder playerchunk, Runnable run) {
@@ -164,21 +164,68 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper start - distance maps
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+ // Paper start - no-tick view distance
+ int noTickViewDistance;
+ public final int getRawNoTickViewDistance() {
+ return this.noTickViewDistance;
+ }
+ public final int getEffectiveNoTickViewDistance() {
+ return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance;
+ }
+ public final int getLoadViewDistance() {
+ return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance());
+ }
+
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap;
+ // Paper end - no-tick view distance
void addPlayerToDistanceMaps(ServerPlayer player) {
int chunkX = MCUtil.getChunkCoordinate(player.getX());
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
// Note: players need to be explicitly added to distance maps before they can be updated
+ // Paper start - no-tick view distance
+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
+
+ if (!this.skipPlayer(player)) {
+ this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance);
+ this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
+ }
+
+ player.needsChunkCenterUpdate = true;
+ this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
+ player.needsChunkCenterUpdate = false;
+ // Paper end - no-tick view distance
}
void removePlayerFromDistanceMaps(ServerPlayer player) {
+ // Paper start - no-tick view distance
+ this.playerViewDistanceBroadcastMap.remove(player);
+ this.playerViewDistanceTickMap.remove(player);
+ this.playerViewDistanceNoTickMap.remove(player);
+ // Paper end - no-tick view distance
}
void updateMaps(ServerPlayer player) {
int chunkX = MCUtil.getChunkCoordinate(player.getX());
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
// Note: players need to be explicitly added to distance maps before they can be updated
+ // Paper start - no-tick view distance
+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
+
+ if (!this.skipPlayer(player)) {
+ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance);
+ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
+ }
+
+ player.needsChunkCenterUpdate = true;
+ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
+ player.needsChunkCenterUpdate = false;
+ // Paper end - no-tick view distance
}
// Paper end
@@ -216,6 +263,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.overworldDataStorage = persistentStateManagerFactory;
this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, dsync, world);
this.setViewDistance(viewDistance);
+ // Paper start - no-tick view distance
+ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
+ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ if (newState.size() != 1) {
+ return;
+ }
+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
+ return;
+ }
+
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
+ ChunkMap.this.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
+ },
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ if (newState != null) {
+ return;
+ }
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
+ ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
+ });
+ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ if (player.needsChunkCenterUpdate) {
+ player.needsChunkCenterUpdate = false;
+ player.connection.send(new ClientboundSetChunkCacheCenterPacket(currPosX, currPosZ));
+ }
+ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded
+ },
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded
+ });
+ // Paper end - no-tick view distance
}
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
@@ -863,14 +949,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
completablefuture1.thenAcceptAsync((either) -> {
either.ifLeft((chunk) -> {
this.tickingGenerated.getAndIncrement();
- Packet<?>[] apacket = new Packet[2];
-
- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
- this.playerLoadedChunk(entityplayer, apacket, chunk);
- });
+ // Paper - no-tick view distance - moved to Chunk neighbour update
});
}, (runnable) -> {
- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); // Paper - diff on change, this is the scheduling method copied in Chunk used to schedule chunk broadcasts (on change it needs to be copied again)
});
return completablefuture1;
}
@@ -962,28 +1044,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
}
- protected void setViewDistance(int watchDistance) {
- int j = Mth.clamp(watchDistance + 1, 3, 33);
+ public void setViewDistance(int watchDistance) { // Paper - public
+ int j = Mth.clamp(watchDistance + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32
if (j != this.viewDistance) {
int k = this.viewDistance;
this.viewDistance = j;
- this.distanceManager.updatePlayerTickets(this.viewDistance);
- ObjectIterator objectiterator = this.updatingChunkMap.values().iterator();
+ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); //Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending
+ }
- while (objectiterator.hasNext()) {
- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
- ChunkPos chunkcoordintpair = playerchunk.getPos();
- Packet<?>[] apacket = new Packet[2];
+ }
- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
- int l = ChunkMap.checkerboardDistance(chunkcoordintpair, entityplayer, true);
- boolean flag = l <= k;
- boolean flag1 = l <= this.viewDistance;
+ // Paper start - no-tick view distance
+ public final void setNoTickViewDistance(int viewDistance) {
+ viewDistance = viewDistance == -1 ? -1 : Mth.clamp(viewDistance, 2, 32);
- this.updateChunkTracking(entityplayer, chunkcoordintpair, apacket, flag, flag1);
- });
+ this.noTickViewDistance = viewDistance;
+ int loadViewDistance = this.getLoadViewDistance();
+ this.distanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2
+
+ if (this.level != null && this.level.players != null) { // this can be called from constructor, where these aren't set
+ for (ServerPlayer player : this.level.players) {
+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection;
+ if (connection != null) {
+ // moved in from PlayerList
+ connection.send(new net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket(loadViewDistance));
+ }
+ this.updateMaps(player);
+ // Paper end - no-tick view distance
}
}
@@ -995,7 +1084,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
if (playerchunk != null) {
- LevelChunk chunk = playerchunk.getTickingChunk();
+ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
this.playerLoadedChunk(player, packets, chunk);
@@ -1202,13 +1291,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.removePlayerFromDistanceMaps(player); // Paper - distance maps
}
- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) {
- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) {
- ChunkPos chunkcoordintpair = new ChunkPos(k, l);
-
- this.updateChunkTracking(player, chunkcoordintpair, new Packet[2], !added, added);
- }
- }
+ // Paper - broadcast view distance map handles this (see remove/add calls above)
}
@@ -1216,7 +1299,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
SectionPos sectionposition = SectionPos.of((Entity) player);
player.setLastSectionPos(sectionposition);
- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z()));
+ // player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - distance map handles this now
return sectionposition;
}
@@ -1271,6 +1354,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
int k1;
int l1;
+ /* // Paper start - replaced by distance map
if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
k1 = Math.min(i, i1) - this.viewDistance;
l1 = Math.min(j, j1) - this.viewDistance;
@@ -1309,6 +1393,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
}
}
+ */ // Paper end - replaced by distance map
this.updateMaps(player); // Paper - distance maps
@@ -1316,11 +1401,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@Override
public Stream<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
- return this.playerMap.getPlayers(chunkPos.toLong()).filter((entityplayer) -> {
- int i = ChunkMap.checkerboardDistance(chunkPos, entityplayer, true);
+ // Paper start - per player view distance
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
+ // view distance map here.
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkPos);
- return i > this.viewDistance ? false : !onlyOnWatchDistanceEdge || i == this.viewDistance;
- });
+ if (inRange == null) {
+ return Stream.empty();
+ }
+ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it
+ List<ServerPlayer> players = new java.util.ArrayList<>();
+ Object[] backingSet = inRange.getBackingSet();
+
+ if (onlyOnWatchDistanceEdge) { // flag -> border only
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)temp;
+ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
+ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
+
+ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x);
+ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z);
+ if (Math.max(distX, distZ) == viewDistance) {
+ players.add(player);
+ }
+ }
+ } else {
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)temp;
+ players.add(player);
+ }
+ }
+ return players.stream();
+ // Paper end - per player view distance
}
protected void addEntity(Entity entity) {
@@ -1442,7 +1562,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
- private void playerLoadedChunk(ServerPlayer player, Packet<?>[] packets, LevelChunk chunk) {
+ // Paper start
+ private static int getLightMask(final LevelChunk chunk) {
+ final net.minecraft.world.level.chunk.LevelChunkSection[] chunkSections = chunk.getSections();
+ int mask = 0;
+
+ for (int i = 0; i < chunkSections.length; ++i) {
+ /*
+
+
+Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section.
+Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d.
+
+ */
+ mask |= (net.minecraft.world.level.chunk.LevelChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i;
+ }
+
+ return mask;
+ }
+
+ private static int getCeilingLightMask(final LevelChunk chunk) {
+ int mask = getLightMask(chunk);
+
+ /*
+ It is similar to get highest bit, it would turn an 001010 into an 001111 so basically the highest bit and all below.
+ We then invert this, so we'd have 110000 and compare that to the "main" chunk.
+ This is because the bug only appears when the current chunks lightmaps are higher than those of the neighbors, thus we can omit sending neighbors which are lower than the current chunks lights.
+
+ so TLDR is that getCeilingLightMask returns a light mask with all bits set below the highest affected section. We could also count the number of leading zeros and invert them, somehow.
+ @TODO: Implement Leafs suggestion
+ either use Integer#numberOfLeadingZeros or document what this bithack is supposed to be doing then
+ */
+ mask |= mask >> 1;
+ mask |= mask >> 2;
+ mask |= mask >> 4;
+ mask |= mask >> 8;
+ mask |= mask >> 16;
+
+ return mask;
+ }
+ // Paper end
+
+ public void playerLoadedChunk(ServerPlayer player, Packet<?>[] packets, LevelChunk chunk) { // Paper - private -> public
if (packets[0] == null) {
packets[0] = new ClientboundLevelChunkPacket(chunk);
packets[1] = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, (BitSet) null, (BitSet) null, true);
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index 45c7ebe67019cdbe88b6617a95d5c40d3a68286c..38eebda226e007c8910e04f502ce218cdfe1d456 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -275,8 +275,8 @@ public abstract class DistanceManager {
return s;
}
- protected void updatePlayerTickets(int viewDistance) {
- this.playerTicketManager.updateViewDistance(viewDistance);
+ protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change
+ this.playerTicketManager.updateViewDistance(i);
}
public int getNaturalSpawnChunkCount() {
@@ -503,7 +503,7 @@ public abstract class DistanceManager {
private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) {
if (oldWithinViewDistance != withinViewDistance) {
- Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, DistanceManager.PLAYER_TICKET_LEVEL, new ChunkPos(pos));
+ Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance
if (withinViewDistance) {
DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> {
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 4fef4c0240be90ddcb9640f5719e292d2d5dbdf8..bffc897cb88a54c36432c98264f3416051aeab17 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -241,6 +241,7 @@ public class ServerPlayer extends Player {
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
+ boolean needsChunkCenterUpdate; // Paper - no-tick view distance
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index bcc946d2747443c34ee8ac2485a5ab41773c93af..2730923bd0bf3b0f928765b9e09e2299fa9a393d 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -241,7 +241,7 @@ public abstract class PlayerList {
boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
// Spigot - view distance
- playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat()));
+ playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - no-tick view distance
player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
@@ -789,7 +789,7 @@ public abstract class PlayerList {
// CraftBukkit start
LevelData worlddata = worldserver1.getLevelData();
entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag));
- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance
entityplayer1.setLevel(worldserver1);
entityplayer1.unsetRemoved();
entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
@@ -1273,7 +1273,7 @@ public abstract class PlayerList {
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
+ //this.sendAll(new PacketPlayOutViewDistance(i)); // Paper - move into setViewDistance
Iterator iterator = this.server.getAllLevels().iterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index ddcde88a8c8b748b4dc2583b4dfd0fb8ea9e201e..07e81fa1119bba4981e34e70b9e67f43280f8071 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -521,8 +521,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
}
- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - diff on change, see below
this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance
+ // if copied from above
+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) {
+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
+ // Paper end - per player view distance
}
if ((i & 1) != 0) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 521f199e495f3bec232cc9ca36e51e0392afe737..922026da8c234427e0322443004d3c32993adfce 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -33,7 +33,10 @@ import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
@@ -227,7 +230,51 @@ public class LevelChunk implements ChunkAccess {
}
protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
+ // Paper start - no-tick view distance
+ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource();
+ ChunkMap chunkMap = chunkProviderServer.chunkMap;
+ // this code handles the addition of ticking tickets - the distance map handles the removal
+ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
+ if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) {
+ // now we're ready for entity ticking
+ chunkProviderServer.mainThreadProcessor.execute(() -> {
+ // double check that this condition still holds.
+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) {
+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update
+ }
+ });
+ }
+ }
+ // this code handles the chunk sending
+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
+ if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) {
+ // now we're ready to send
+ chunkMap.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(chunkMap.getUpdatingChunkIfPresent(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap
+ // double check that this condition still holds.
+ if (!LevelChunk.this.areNeighboursLoaded(1)) {
+ return;
+ }
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(LevelChunk.this.coordinateKey);
+ if (inRange == null) {
+ return;
+ }
+
+ // broadcast
+ Object[] backingSet = inRange.getBackingSet();
+ Packet[] chunkPackets = new Packet[2];
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
+ Object temp = backingSet[index];
+ if (!(temp instanceof net.minecraft.server.level.ServerPlayer)) {
+ continue;
+ }
+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)temp;
+ chunkMap.playerLoadedChunk(player, chunkPackets, LevelChunk.this);
+ }
+ })));
+ }
+ }
+ // Paper end - no-tick view distance
}
public final boolean isAnyNeighborsLoaded() {
@@ -1005,7 +1052,7 @@ public class LevelChunk implements ChunkAccess {
BlockState iblockdata = this.getBlockState(blockposition);
BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, (LevelAccessor) this.level, blockposition);
- this.level.setBlock(blockposition, iblockdata1, 20);
+ this.level.setBlock(blockposition, iblockdata1, 20 | 2); // Paper - We send chunks before they're ticking ready, so we need to notify here
}
this.postProcessing[i].clear();
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index f1cbcdb1e409f8544125dde5f24bff5b07cb5082..1d22119b962840dff789a0619fd2188958f924d0 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2540,10 +2540,39 @@ public class CraftWorld implements World {
// Spigot start
@Override
public int getViewDistance() {
- return world.spigotConfig.viewDistance;
+ return getHandle().getChunkSource().chunkMap.getEffectiveViewDistance(); // Paper - no-tick view distance
}
// Spigot end
+ // Paper start - per player view distance
+ @Override
+ public void setViewDistance(int viewDistance) {
+ if (viewDistance < 2 || viewDistance > 32) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
+ if (viewDistance != chunkMap.getEffectiveViewDistance()) {
+ chunkMap.setViewDistance(viewDistance);
+ }
+ }
+
+ @Override
+ public int getNoTickViewDistance() {
+ return getHandle().getChunkSource().chunkMap.getEffectiveNoTickViewDistance();
+ }
+
+ @Override
+ public void setNoTickViewDistance(int viewDistance) {
+ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
+ if (viewDistance != chunkMap.getRawNoTickViewDistance()) {
+ chunkMap.setNoTickViewDistance(viewDistance);
+ }
+ }
+ // Paper end - per player view distance
+
// Spigot start
private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
{
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 2ab585a018290996e7fa9ca6f3ad7d734cd7beaa..a08583863f9fa08016bdfc7949a273eaa4429927 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -188,7 +188,7 @@ public class ActivationRange
maxRange = Math.max( maxRange, waterActivationRange );
maxRange = Math.max( maxRange, villagerActivationRange );
// Paper end
- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
+ maxRange = Math.min( ( ((net.minecraft.server.level.ServerLevel)world).getChunkSource().chunkMap.getEffectiveViewDistance() << 4 ) - 8, maxRange ); // Paper - no-tick view distance
for ( Player player : world.players() )
{