even more work

This commit is contained in:
Aurora 2021-06-13 16:14:18 +02:00
parent 0ea382b41d
commit 8af12b17c1
No known key found for this signature in database
GPG key ID: 89839F67B53656AD
16 changed files with 40 additions and 38 deletions

View file

@ -1,34 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 15 Jun 2019 10:28:25 -0700
Subject: [PATCH] Show blockstate location if we failed to read it
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
index 730fda7f0bf02400d349959e9cc2aafaed000b21..66aee7635cd9260d97ae9dd2e9a2a0590fe3c433 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
@@ -19,6 +19,8 @@ public class CraftBlockEntityState<T extends BlockEntity> extends CraftBlockStat
public CraftBlockEntityState(Block block, Class<T> tileEntityClass) {
super(block);
+ try {// Paper - show location on failure
+
this.tileEntityClass = tileEntityClass;
// get tile entity from block:
@@ -38,6 +40,14 @@ public class CraftBlockEntityState<T extends BlockEntity> extends CraftBlockStat
this.load(this.snapshot);
}
// Paper end
+ // Paper start - show location on failure
+ } catch (Throwable thr) {
+ if (thr instanceof ThreadDeath) {
+ throw (ThreadDeath)thr;
+ }
+ throw new RuntimeException("Failed to read BlockState at: world: " + block.getWorld().getName() + " location: (" + block.getX() + ", " + block.getY() + ", " + block.getZ() + ")", thr);
+ }
+ // Paper end
}
public final boolean snapshotDisabled; // Paper

View file

@ -1,117 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 29 May 2020 20:29:02 -0400
Subject: [PATCH] Synchronize DataPaletteBlock instead of ReentrantLock
Mojang has flaws in their logic about chunks being concurrently
wrote to. So we constantly see crashes around multiple threads writing.
Additionally, java has optimized synchronization so well that its
in many times faster than trying to manage read wrote locks for low
contention situations.
And this is extremely a low contention situation.
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
index 6d3dcd19ce1abc9d502903b8008949b5174a13c3..917b0a64083ebbe24321089b784b91f3af4918b9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -8,7 +8,6 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.minecraft.CrashReport;
-import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.IdMapper;
import net.minecraft.nbt.CompoundTag;
@@ -32,23 +31,23 @@ public class PalettedContainer<T> implements PaletteResize<T> {
private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER
private final ReentrantLock lock = new ReentrantLock();
- public void acquire() {
- if (this.lock.isLocked() && !this.lock.isHeldByCurrentThread()) {
+ public void acquire() { /* // Paper start - disable this - use proper synchronization
+ if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) {
String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> {
return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat "));
}).collect(Collectors.joining("\n"));
CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException());
- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Thread dumps");
+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps");
- crashreportsystemdetails.setDetail("Thread dumps", (Object) s);
+ crashreportsystemdetails.a("Thread dumps", (Object) s);
throw new ReportedException(crashreport);
} else {
- this.lock.lock();
- }
+ this.j.lock();
+ } */ // Paper end
}
public void release() {
- this.lock.unlock();
+ //this.j.unlock(); // Paper - disable this
}
public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> elementSerializer, T defaultElement) {
@@ -84,7 +83,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
}
@Override
- public int onResize(int newSize, T objectAdded) {
+ public synchronized int onResize(int newSize, T objectAdded) { // Paper - synchronize
this.acquire();
BitStorage databits = this.storage;
Palette<T> datapalette = this.palette;
@@ -107,18 +106,18 @@ public class PalettedContainer<T> implements PaletteResize<T> {
}
public T getAndSet(int x, int y, int z, T value) {
- this.acquire();
- T t1 = this.getAndSet(getIndex(x, y, z), value);
+ //this.a(); // Paper - remove to reduce ops - synchronize handled below
+ return this.getAndSet(getIndex(x, y, z), value); // Paper
- this.release();
- return t1;
+ //this.b(); // Paper
+ //return t1; // PAper
}
public T getAndSetUnchecked(int x, int y, int z, T value) {
return this.getAndSet(getIndex(x, y, z), value);
}
- protected T getAndSet(int index, T value) {
+ protected synchronized T getAndSet(int index, T value) { // Paper - synchronize - writes
int j = this.palette.idFor(value);
int k = this.storage.getAndSet(index, j);
T t1 = this.palette.valueFor(k);
@@ -143,7 +142,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
}
public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER
- public void write(FriendlyByteBuf buf) {
+ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
this.acquire();
buf.writeByte(this.bits);
this.palette.write(buf);
@@ -151,7 +150,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
this.release();
}
- public void read(ListTag paletteTag, long[] data) {
+ public synchronized void read(ListTag paletteTag, long[] data) { // Paper - synchronize
this.acquire();
int i = Math.max(4, Mth.ceillog2(paletteTag.size()));
@@ -184,7 +183,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
this.release();
}
- public void write(CompoundTag nbttagcompound, String s, String s1) {
+ public synchronized void write(CompoundTag nbttagcompound, String s, String s1) { // Paper - synchronize
this.acquire();
HashMapPalette<T> datapalettehash = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);
T t0 = this.defaultValue;

View file

@ -1,317 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sun, 9 Jun 2019 03:53:22 +0100
Subject: [PATCH] incremental chunk saving
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index ffe9b1a63d78925e1d77b9e730aef42fed6d58fa..1278d09f70c1e97607ef20d87a178dc252c7f723 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -446,4 +446,19 @@ public class PaperWorldConfig {
keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16);
log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16));
}
+
+ public int autoSavePeriod = -1;
+ private void autoSavePeriod() {
+ autoSavePeriod = getInt("auto-save-interval", -1);
+ if (autoSavePeriod > 0) {
+ log("Auto Save Interval: " +autoSavePeriod + " (" + (autoSavePeriod / 20) + "s)");
+ } else if (autoSavePeriod < 0) {
+ autoSavePeriod = net.minecraft.server.MinecraftServer.getServer().autosavePeriod;
+ }
+ }
+
+ public int maxAutoSaveChunksPerTick = 24;
+ private void maxAutoSaveChunksPerTick() {
+ maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
+ }
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0efe7024493f96bb54e7d8c1ea7b233a1b481a04..aab1a055c065d1f1a92461e4442ec2cdd8e0b347 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -261,6 +261,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public static int currentTick = 0; // Paper - Further improve tick loop
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
+ public boolean serverAutoSave = false; // Paper
public Commands vanillaCommandDispatcher;
private boolean forceTicks;
// CraftBukkit end
@@ -1256,14 +1257,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.status.getPlayers().setSample(agameprofile);
}
- if (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0) { // CraftBukkit
- MinecraftServer.LOGGER.debug("Autosave started");
+ //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down
+ //MinecraftServer.LOGGER.debug("Autosave started"); // Paper
+ serverAutoSave = (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0); // Paper
this.profiler.push("save");
+ if (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0) { // Paper
this.playerList.saveAll();
- this.saveAllChunks(true, false, false);
+ }// Paper
+ // Paper start
+ for (ServerLevel world : getAllLevels()) {
+ if (world.paperConfig.autoSavePeriod > 0) {
+ world.saveIncrementally(serverAutoSave);
+ }
+ }
+ // Paper end
+
this.profiler.pop();
- MinecraftServer.LOGGER.debug("Autosave finished");
- }
+ //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper
+ //} // Paper
this.profiler.push("snooper");
if (((DedicatedServer) this).getProperties().snooperEnabled && !this.snooper.isStarted() && this.tickCount > 100) { // Spigot
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 7010e0a970462d2b2e1b5696a1a49dba9ea60935..491a9e78fdcec8c211499e8f48cceb829f1e5c8b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -66,6 +66,9 @@ public class ChunkHolder {
private final ChunkMap chunkMap; // Paper
+ long lastAutoSaveTime; // Paper - incremental autosave
+ long inactiveTimeStart; // Paper - incremental autosave
+
public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
@@ -421,7 +424,19 @@ public class ChunkHolder {
boolean flag2 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
boolean flag3 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ boolean prevHasBeenLoaded = this.wasAccessibleSinceLastSave; // Paper
this.wasAccessibleSinceLastSave |= flag3;
+ // Paper start - incremental autosave
+ if (this.wasAccessibleSinceLastSave & !prevHasBeenLoaded) {
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
+ if (timeSinceAutoSave < 0) {
+ // safest bet is to assume autosave is needed here
+ timeSinceAutoSave = this.chunkMap.level.paperConfig.autoSavePeriod;
+ }
+ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave;
+ this.chunkMap.autoSaveQueue.add(this);
+ }
+ // Paper end
if (!flag2 && flag3) {
// Paper start - cache ticking ready status
int expectCreateCount = ++this.fullChunkCreateCount;
@@ -541,8 +556,32 @@ public class ChunkHolder {
}
public void refreshAccessibility() {
+ boolean prev = this.wasAccessibleSinceLastSave; // Paper
+ this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ // Paper start - incremental autosave
+ if (prev != this.wasAccessibleSinceLastSave) {
+ if (this.wasAccessibleSinceLastSave) {
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
+ if (timeSinceAutoSave < 0) {
+ // safest bet is to assume autosave is needed here
+ timeSinceAutoSave = this.chunkMap.level.paperConfig.autoSavePeriod;
+ }
+ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave;
+ this.chunkMap.autoSaveQueue.add(this);
+ } else {
+ this.inactiveTimeStart = this.chunkMap.level.getGameTime();
+ this.chunkMap.autoSaveQueue.remove(this);
+ }
+ }
+ // Paper end
+ }
+
+ // Paper start - incremental autosave
+ public boolean setHasBeenLoaded() {
this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ return this.wasAccessibleSinceLastSave;
}
+ // Paper end
public void replaceProtoChunk(ImposterProtoChunk protochunkextension) {
for (int i = 0; i < this.futures.length(); ++i) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 0aac29de933c84c34cb24e204e8fcc7010060d8f..cfec04e12dfaeb8852dc129a6a7e68c61dac54b6 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -91,6 +91,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.phys.Vec3;
+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -378,6 +379,64 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
+ // Paper start - incremental autosave
+ final ObjectRBTreeSet<ChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> {
+ int timeCompare = Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime);
+ if (timeCompare != 0) {
+ return timeCompare;
+ }
+
+ return Long.compare(MCUtil.getCoordinateKey(playerchunk1.pos), MCUtil.getCoordinateKey(playerchunk2.pos));
+ });
+
+ protected void saveIncrementally() {
+ int savedThisTick = 0;
+ // optimized since we search far less chunks to hit ones that need to be saved
+ List<ChunkHolder> reschedule = new java.util.ArrayList<>(this.level.paperConfig.maxAutoSaveChunksPerTick);
+ long currentTick = this.level.getGameTime();
+ long maxSaveTime = currentTick - this.level.paperConfig.autoSavePeriod;
+
+ for (Iterator<ChunkHolder> iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) {
+ ChunkHolder playerchunk = iterator.next();
+ if (playerchunk.lastAutoSaveTime > maxSaveTime) {
+ break;
+ }
+
+ iterator.remove();
+
+ ChunkAccess ichunkaccess = playerchunk.getChunkToSave().getNow(null);
+ if (ichunkaccess instanceof LevelChunk) {
+ boolean shouldSave = ((LevelChunk)ichunkaccess).lastSaveTime <= maxSaveTime;
+
+ if (shouldSave && this.save(ichunkaccess)) {
+ ++savedThisTick;
+
+ if (!playerchunk.setHasBeenLoaded()) {
+ // do not fall through to reschedule logic
+ playerchunk.inactiveTimeStart = currentTick;
+ if (savedThisTick >= this.level.paperConfig.maxAutoSaveChunksPerTick) {
+ break;
+ }
+ continue;
+ }
+ }
+ }
+
+ reschedule.add(playerchunk);
+
+ if (savedThisTick >= this.level.paperConfig.maxAutoSaveChunksPerTick) {
+ break;
+ }
+ }
+
+ for (int i = 0, len = reschedule.size(); i < len; ++i) {
+ ChunkHolder playerchunk = reschedule.get(i);
+ playerchunk.lastAutoSaveTime = this.level.getGameTime();
+ this.autoSaveQueue.add(playerchunk);
+ }
+ }
+ // Paper end
+
protected void saveAllChunks(boolean flush) {
if (flush) {
List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
@@ -488,6 +547,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.level.unload(chunk);
}
+ this.autoSaveQueue.remove(playerchunk); // Paper
this.lightEngine.updateChunkStatus(ichunkaccess.getPos());
this.lightEngine.tryScheduleUpdate();
@@ -680,6 +740,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
playerchunk.replaceProtoChunk(new ImposterProtoChunk(chunk));
}
+ chunk.setLastSaveTime(this.level.getGameTime() - 1); // Paper - avoid autosaving newly generated/loaded chunks
+
chunk.setFullStatus(() -> {
return ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel());
});
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 1e8ac0110badbf2d1c2336168c3e11991667c782..c1aa40c01a80a8870478193b8cd7354b0d71045c 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -558,6 +558,15 @@ public class ServerChunkCache extends ChunkSource {
} // Paper - Timings
}
+ // Paper start - duplicate save, but call incremental
+ public void saveIncrementally() {
+ this.runDistanceManagerUpdates();
+ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
+ this.chunkMap.saveIncrementally();
+ } // Paper - Timings
+ }
+ // Paper end
+
@Override
public void close() throws IOException {
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index b2ddf145ae9f581ec6820deb9cb6a98be87658d7..fd7ee4badb383ffb4347d62c00ea2dfa3d76fd12 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -882,6 +882,38 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos);
}
+ // Paper start - derived from below
+ public void saveIncrementally(boolean doFull) {
+ ServerChunkCache chunkproviderserver = this.getChunkSource();
+
+ if (doFull) {
+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
+ }
+
+ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) {
+ if (doFull) {
+ this.saveData();
+ }
+
+ timings.worldSaveChunks.startTiming(); // Paper
+ if (!this.noSave()) chunkproviderserver.saveIncrementally();
+ timings.worldSaveChunks.stopTiming(); // Paper
+
+
+ // Copied from save()
+ // CraftBukkit start - moved from MinecraftServer.saveChunks
+ if (doFull) { // Paper
+ ServerLevel worldserver1 = this;
+
+ worldDataServer.setWorldBorder(worldserver1.getWorldBorder().createSettings());
+ worldDataServer.setCustomBossEvents(this.server.getCustomBossEvents().save());
+ convertable.saveDataTag(this.server.registryHolder, this.worldDataServer, this.server.getPlayerList().getSingleplayerData());
+ }
+ // CraftBukkit end
+ }
+ }
+ // Paper end
+
public void save(@Nullable ProgressListener progressListener, boolean flush, boolean flag1) {
ServerChunkCache chunkproviderserver = this.getChunkSource();
@@ -912,6 +944,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
// CraftBukkit end
}
+ private void saveData() { this.saveLevelData(); } // Paper - OBFHELPER
private void saveLevelData() {
if (this.dragonFight != null) {
this.worldDataServer.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
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 55872a17060a35b727a597bc414fecec3ada3515..419b4bf0549d798d52d73fbbd9de59313fc05eb1 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -80,7 +80,7 @@ public class LevelChunk implements ChunkAccess {
private TickList<Block> blockTicks;
private TickList<Fluid> liquidTicks;
private boolean lastSaveHadEntities;
- private long lastSaveTime;
+ public long lastSaveTime; // Paper
private volatile boolean unsaved;
private long inhabitedTime;
@Nullable

File diff suppressed because it is too large Load diff

View file

@ -1,57 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 24 Mar 2019 01:01:32 -0400
Subject: [PATCH] Only count Natural Spawned mobs towards natural spawn mob
limit
This resolves the super common complaint about mobs not spawning.
This was ultimately a flaw in the vanilla count algorithim that allows
spawners and other misc mobs to count against the mob limit, which are
not bounded, and can prevent the entire world from spawning new.
I believe Bukkits changes around persistence may of actually made it
worse than vanilla.
This should fully solve all of the issues around it so that only natural
influences natural spawns.
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index c45493e88bf7e8811be2759ff9ac19e3fe9d938a..384cb363eed794551bee6b0ec11ba1be92a3d7ac 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -464,6 +464,16 @@ public class PaperWorldConfig {
maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
}
+ public boolean countAllMobsForSpawning = false;
+ private void countAllMobsForSpawning() {
+ countAllMobsForSpawning = getBoolean("count-all-mobs-for-spawning", false);
+ if (countAllMobsForSpawning) {
+ log("Counting all mobs for spawning. Mob farms may reduce natural spawns elsewhere in world.");
+ } else {
+ log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)");
+ }
+ }
+
public boolean antiXray;
public EngineMode engineMode;
public int maxChunkSectionIndex;
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index e23875ae07c23fed1161ea070e63bbc3a30168a0..0fb69f9194078e5e05e36ed909eb48424b6465b4 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -79,6 +79,13 @@ public final class NaturalSpawner {
MobCategory enumcreaturetype = entity.getType().getCategory();
if (enumcreaturetype != MobCategory.MISC) {
+ // Paper start - Only count natural spawns
+ if (!entity.level.paperConfig.countAllMobsForSpawning &&
+ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL ||
+ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
+ continue;
+ }
+ // Paper end
BlockPos blockposition = entity.blockPosition();
long j = ChunkPos.asLong(blockposition.getX() >> 4, blockposition.getZ() >> 4);

View file

@ -1,53 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Lucavon <lucavonlp@gmail.com>
Date: Tue, 23 Jul 2019 20:29:20 -0500
Subject: [PATCH] Configurable projectile relative velocity
This patch adds an option "disable relative projectile velocity", which, when
nabled, will cause projectiles to ignore the shooter's current velocity,
like they did in Minecraft 1.8 and prior.
If a player is falling, for example, their shooting range will be drastically
reduced, as a downwards velocity is applied to the projectile. This prevents
players from saving themselves from falling off floating islands, for example,
as a thrown ender pearl will not make it back to the island, while it would
have in 1.8.
While this could easily be done with plugins, too, there are multiple problems:
P1) If multiple plugins cancel the velocity by subtracting the shooter's velocity
from the projectile's velocity, the projectile's velocity would be different.
As there's no way to detect whether the projectile's velocity has already been
adjusted to ignore the player's velocity, plugins can't not do it if it's not
necessary.
P2) I've noticed some inconsistencies, e.g. weird velocity when shooting while
using an elytra. Checking for those inconsistencies is possible, but not as
efficient as just not applying the velocity in the first place.
P3) Solutions for 1) and especially 2) might not be future-proof, while this
server-internal fix makes this change future-proof.
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 384cb363eed794551bee6b0ec11ba1be92a3d7ac..1ee2cced100626e48eb36ee14f84b9257c79a2f8 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -507,4 +507,9 @@ public class PaperWorldConfig {
Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues");
}
}
+
+ public boolean disableRelativeProjectileVelocity;
+ private void disableRelativeProjectileVelocity() {
+ disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
+ }
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
index d385fb6eee5000951c350b6ced5669dc3dcce725..ca3d936433cd47caa4e0335e41246b1c4ce0eb99 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
@@ -129,7 +129,7 @@ public abstract class Projectile extends Entity {
this.shoot((double) f5, (double) f6, (double) f7, modifierZ, modifierXYZ);
Vec3 vec3d = user.getDeltaMovement();
- this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, user.isOnGround() ? 0.0D : vec3d.y, vec3d.z));
+ if (!user.level.paperConfig.disableRelativeProjectileVelocity) this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, user.isOnGround() ? 0.0D : vec3d.y, vec3d.z)); // Paper - allow disabling relative velocity
}
// CraftBukkit start - call projectile hit event

View file

@ -1,26 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sun, 28 Jul 2019 00:51:11 +0100
Subject: [PATCH] Mark entities as being ticked when notifying navigation
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 7a09bc921827958f58290bd3d6f19984bb34a8f6..a811ced17721b70bb51837f47e466c2261db2466 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1469,6 +1469,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
+ boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
Iterator iterator = this.navigations.iterator();
while (iterator.hasNext()) {
@@ -1490,6 +1491,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
}
}
+ this.tickingEntities = wasTicking; // Paper
}
}

View file

@ -1,19 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Tue, 30 Jul 2019 03:17:16 +0500
Subject: [PATCH] offset item frame ticking
diff --git a/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java b/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java
index e0bf8c6c838b18a0c55b6f3317033e892b631f5c..3277a56bcf3831f8d3c9fa9168c608b369eed7e4 100644
--- a/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/decoration/HangingEntity.java
@@ -35,7 +35,7 @@ public abstract class HangingEntity extends Entity {
protected static final Predicate<Entity> HANGING_ENTITY = (entity) -> {
return entity instanceof HangingEntity;
};
- private int checkInterval;
+ private int checkInterval; { this.checkInterval = this.getId() % this.level.spigotConfig.hangingTickFrequency; } // Paper
public BlockPos pos;
protected Direction direction;

View file

@ -1,127 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: CullanP <cullanpage@gmail.com>
Date: Thu, 3 Mar 2016 02:13:38 -0600
Subject: [PATCH] Avoid hopper searches if there are no items
Hoppers searching for items and minecarts is the most expensive part of hopper ticking.
We keep track of the number of minecarts and items in a chunk.
If there are no items in the chunk, we skip searching for items.
If there are no minecarts in the chunk, we skip searching for them.
Usually hoppers aren't near items, so we can skip most item searches.
And since minecart hoppers are used _very_ rarely near we can avoid alot of searching there.
Combined, this adds up a lot.
diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java
index d3640975c5a33b4911428760691215905b987385..e7facd849e3511c64b4ae44b34382f4a4985f2a4 100644
--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java
+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java
@@ -16,6 +16,7 @@ public final class EntitySelector {
public static final Predicate<Entity> ENTITY_NOT_BEING_RIDDEN = (entity) -> {
return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger();
};
+ public static final Predicate<Entity> isInventory() { return CONTAINER_ENTITY_SELECTOR; } // Paper - OBFHELPER
public static final Predicate<Entity> CONTAINER_ENTITY_SELECTOR = (entity) -> {
return entity instanceof Container && entity.isAlive();
};
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 85861545ec4620a6cfd06876dad091637bd29b0b..4fef3abe4b416cbebe1b456468b5c3e162de18f1 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -31,10 +31,13 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
+import net.minecraft.world.Container;
import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.ChunkTickList;
import net.minecraft.world.level.EmptyTickList;
@@ -122,6 +125,10 @@ public class LevelChunk implements ChunkAccess {
return removed;
}
}
+ // Track the number of minecarts and items
+ // Keep this synced with entitySlices.add() and entitySlices.remove()
+ private final int[] itemCounts = new int[16];
+ private final int[] inventoryEntityCounts = new int[16];
// Paper end
public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes, UpgradeData upgradeData, TickList<Block> blockTickScheduler, TickList<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable Consumer<LevelChunk> loadToWorldConsumer) {
@@ -581,6 +588,13 @@ public class LevelChunk implements ChunkAccess {
entity.zChunk = this.chunkPos.z;
this.entities.add(entity); // Paper - per chunk entity list
this.entitySlices[k].add(entity);
+ // Paper start
+ if (entity instanceof ItemEntity) {
+ itemCounts[k]++;
+ } else if (entity instanceof Container) {
+ inventoryEntityCounts[k]++;
+ }
+ // Paper end
entity.entitySlice = this.entitySlices[k]; // Paper
this.markUnsaved(); // Paper
}
@@ -614,6 +628,11 @@ public class LevelChunk implements ChunkAccess {
if (!this.entitySlices[section].remove(entity)) {
return;
}
+ if (entity instanceof ItemEntity) {
+ itemCounts[section]--;
+ } else if (entity instanceof Container) {
+ inventoryEntityCounts[section]--;
+ }
entityCounts.decrement(entity.getMinecraftKeyString());
this.markUnsaved(); // Paper
// Paper end
@@ -899,6 +918,14 @@ public class LevelChunk implements ChunkAccess {
for (int k = i; k <= j; ++k) {
Iterator iterator = this.entitySlices[k].iterator(); // Spigot
+ // Paper start - Don't search for inventories if we have none, and that is all we want
+ /*
+ * We check if they want inventories by seeing if it is the static `IEntitySelector.d`
+ *
+ * Make sure the inventory selector stays in sync.
+ * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()`
+ */
+ if (predicate == EntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue;
while (iterator.hasNext()) {
T entity = (T) iterator.next(); // CraftBukkit - decompile error
if (entity.shouldBeRemoved) continue; // Paper
@@ -919,9 +946,29 @@ public class LevelChunk implements ChunkAccess {
i = Mth.clamp(i, 0, this.entitySlices.length - 1);
j = Mth.clamp(j, 0, this.entitySlices.length - 1);
+ // Paper start
+ int[] counts;
+ if (ItemEntity.class.isAssignableFrom(entityClass)) {
+ counts = itemCounts;
+ } else if (Container.class.isAssignableFrom(entityClass)) {
+ counts = inventoryEntityCounts;
+ } else {
+ counts = null;
+ }
+ // Paper end
for (int k = i; k <= j; ++k) {
+ if (counts != null && counts[k] <= 0) continue; // Paper - Don't check a chunk if it doesn't have the type we are looking for
Iterator iterator = this.entitySlices[k].iterator(); // Spigot
+ // Paper start - Don't search for inventories if we have none, and that is all we want
+ /*
+ * We check if they want inventories by seeing if it is the static `IEntitySelector.d`
+ *
+ * Make sure the inventory selector stays in sync.
+ * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()`
+ */
+ if (predicate == EntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue;
+ // Paper end
while (iterator.hasNext()) {
T t0 = (T) iterator.next(); // CraftBukkit - decompile error
if (t0.shouldBeRemoved) continue; // Paper

View file

@ -1,119 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 8 Jul 2019 00:13:36 -0700
Subject: [PATCH] Use getChunkIfLoadedImmediately in places
This prevents us from hitting chunk loads for chunks at or less-than
ticket level 33 (yes getChunkIfLoaded will actually perform a chunk
load in that case).
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 95eff4f6165024d21e5c4268a9ae1b7a4268de4b..9d4c81a4964317a0726171dc6d490d12bd959cc4 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -201,7 +201,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
}
@Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI
- return this.chunkSource.getChunk(x, z, false);
+ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper
}
// Paper start - Asynchronous IO
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 5dd99709d6b0ed15bbcee184fe33a28bc1c19dac..e45690b6197335ed1c07fa04c39b311b401724d7 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1242,7 +1242,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener {
speed = player.abilities.walkingSpeed * 10f;
}
// Paper start - Prevent moving into unloaded chunks
- if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.hasChunk((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4)) {
+ if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately
this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.yRot, this.player.xRot, Collections.emptySet());
return;
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
index d082af8cf4c0c7ca434598aa370712c62e05bb24..b9d32e3322c2cce1aca2a90df71b6175a6f8c548 100644
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
@@ -284,10 +284,10 @@ public class PoiManager extends SectionStorage<PoiSection> {
// Paper start - Asynchronous chunk io
@javax.annotation.Nullable
@Override
- public CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException {
+ public CompoundTag read(ChunkPos pos) throws java.io.IOException {
if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE
- .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(),
+ .loadChunkDataAsyncFuture(this.world, pos.x, pos.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(),
true, false, true).join().poiData;
if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) {
@@ -295,18 +295,18 @@ public class PoiManager extends SectionStorage<PoiSection> {
}
return ret;
}
- return super.read(chunkcoordintpair);
+ return super.read(pos);
}
@Override
- public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws java.io.IOException {
+ public void write(ChunkPos pos, CompoundTag tag) throws java.io.IOException {
if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(
- this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null,
+ this.world, pos.x, pos.z, tag, null,
com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread());
return;
}
- super.write(chunkcoordintpair, nbttagcompound);
+ super.write(pos, tag);
}
// Paper end
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 1377465e3dc062f34be25cac10aa018776fb22e7..3a1b9f1ba19b28cebdafeb3b2476217d47213862 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -164,6 +164,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
return (CraftServer) Bukkit.getServer();
}
+ // Paper start
+ @Override
+ public boolean hasChunk(int chunkX, int chunkZ) {
+ return ((ServerLevel)this).getChunkIfLoaded(chunkX, chunkZ) != null;
+ }
+ // Paper end
+
public ResourceKey<DimensionType> getTypeKey() {
return typeKey;
}
@@ -1190,7 +1197,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
for (int i1 = i; i1 < j; ++i1) {
for (int j1 = k; j1 < l; ++j1) {
- LevelChunk chunk = ichunkprovider.getChunkNow(i1, j1);
+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
if (chunk != null) {
chunk.getEntitiesOfClass(entityClass, box, list, predicate);
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 0a9bd85e0308e962df3b24a74bd5aac919744d6d..61f180a7c95d83bb88c7df4910c498d9bdf6785a 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -140,9 +140,10 @@ public class ActivationRange
{
for ( int j1 = k; j1 <= l; ++j1 )
{
- if ( world.getWorld().isChunkLoaded( i1, j1 ) )
+ LevelChunk chunk = (LevelChunk) world.getChunkIfLoadedImmediately( i1, j1 );
+ if ( chunk != null )
{
- activateChunkEntities( world.getChunk( i1, j1 ) );
+ activateChunkEntities( chunk );
}
}
}

View file

@ -1,350 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 19 Jul 2019 03:29:14 -0700
Subject: [PATCH] Reduce sync loads
This reduces calls to getChunkAt which would load chunks.
This patch also adds a tool to find calls which are doing this, however
it must be enabled by setting the startup flag -Dpaper.debug-sync-loads=true
To get a debug log for sync loads, the command is /paper syncloadinfo
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
index 62711d95db62221a2e4e6423c518afe13a6c7dbe..ff718bc7f521575e6a670e17fcf59a2d30841705 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -1,12 +1,18 @@
package com.destroystokyo.paper;
import com.destroystokyo.paper.io.chunk.ChunkTaskManager;
+import com.destroystokyo.paper.io.SyncLoadFinder;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MCUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
@@ -14,7 +20,6 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.ChunkPos;
-import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MCUtil;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
@@ -29,6 +34,9 @@ import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Player;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.StringWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -44,7 +52,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", "dumpwaiting").build();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo").build();
public PaperCommand(String name) {
super(name);
@@ -162,6 +170,9 @@ public class PaperCommand extends Command {
case "chunkinfo":
doChunkInfo(sender, args);
break;
+ case "syncloadinfo":
+ this.doSyncLoadInfo(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":
@@ -179,6 +190,40 @@ public class PaperCommand extends Command {
return true;
}
+ private void doSyncLoadInfo(CommandSender sender, String[] args) {
+ if (!SyncLoadFinder.ENABLED) {
+ sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.");
+ return;
+ }
+ File file = new File(new File(new File("."), "debug"),
+ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
+ file.getParentFile().mkdirs();
+ sender.sendMessage(ChatColor.GREEN + "Writing sync load info to " + file.toString());
+
+
+ try {
+ final JsonObject data = SyncLoadFinder.serialize();
+
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndent(" ");
+ jsonWriter.setLenient(false);
+ Streams.write(data, jsonWriter);
+
+ String fileData = stringWriter.toString();
+
+ try (
+ PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")
+ ) {
+ out.print(fileData);
+ }
+ sender.sendMessage(ChatColor.GREEN + "Successfully written sync load information!");
+ } catch (Throwable thr) {
+ sender.sendMessage(ChatColor.RED + "Failed to write sync load information");
+ thr.printStackTrace();
+ }
+ }
+
private void doChunkInfo(CommandSender sender, String[] args) {
List<org.bukkit.World> worlds;
if (args.length < 2 || args[1].equals("*")) {
diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
new file mode 100644
index 0000000000000000000000000000000000000000..524f33371b9de1d4dd6972fe59ffbe1804d7c5f3
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
@@ -0,0 +1,171 @@
+package com.destroystokyo.paper.io;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.mojang.datafixers.util.Pair;
+import it.unimi.dsi.fastutil.longs.Long2IntMap;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import net.minecraft.world.level.Level;
+
+public class SyncLoadFinder {
+
+ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads");
+
+ private static final WeakHashMap<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> SYNC_LOADS = new WeakHashMap<>();
+
+ private static final class SyncLoadInformation {
+
+ public int times;
+
+ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap();
+ }
+
+ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) {
+ if (!ENABLED) {
+ return;
+ }
+
+ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace());
+
+ SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation> map) -> {
+ if (map == null) {
+ map = new Object2ObjectOpenHashMap<>();
+ }
+
+ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> {
+ if (valueInMap == null) {
+ valueInMap = new SyncLoadInformation();
+ }
+
+ ++valueInMap.times;
+
+ valueInMap.coordinateTimes.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> {
+ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1);
+ });
+
+ return valueInMap;
+ });
+
+ return map;
+ });
+ }
+
+ public static JsonObject serialize() {
+ final JsonObject ret = new JsonObject();
+
+ final JsonArray worldsData = new JsonArray();
+
+ for (final Map.Entry<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> entry : SYNC_LOADS.entrySet()) {
+ final Level world = entry.getKey();
+
+ final JsonObject worldData = new JsonObject();
+
+ worldData.addProperty("name", world.getWorld().getName());
+
+ final List<Pair<ThrowableWithEquals, SyncLoadInformation>> data = new ArrayList<>();
+
+ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> {
+ data.add(new Pair<>(stacktrace, times));
+ });
+
+ data.sort((Pair<ThrowableWithEquals, SyncLoadInformation> pair1, Pair<ThrowableWithEquals, SyncLoadInformation> pair2) -> {
+ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order
+ });
+
+ final JsonArray stacktraces = new JsonArray();
+
+ for (Pair<ThrowableWithEquals, SyncLoadInformation> pair : data) {
+ final JsonObject stacktrace = new JsonObject();
+
+ stacktrace.addProperty("times", pair.getSecond().times);
+
+ final JsonArray traces = new JsonArray();
+
+ for (StackTraceElement element : pair.getFirst().stacktrace) {
+ traces.add(String.valueOf(element));
+ }
+
+ stacktrace.add("stacktrace", traces);
+
+ final JsonArray coordinates = new JsonArray();
+
+ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) {
+ final long key = coordinate.getLongKey();
+ final int times = coordinate.getIntValue();
+ coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + times);
+ }
+
+ stacktrace.add("coordinates", coordinates);
+
+ stacktraces.add(stacktrace);
+ }
+
+
+ worldData.add("stacktraces", stacktraces);
+ worldsData.add(worldData);
+ }
+
+ ret.add("worlds", worldsData);
+
+ return ret;
+ }
+
+ static final class ThrowableWithEquals {
+
+ private final StackTraceElement[] stacktrace;
+ private final int hash;
+
+ public ThrowableWithEquals(final StackTraceElement[] stacktrace) {
+ this.stacktrace = stacktrace;
+ this.hash = ThrowableWithEquals.hash(stacktrace);
+ }
+
+ public static int hash(final StackTraceElement[] stacktrace) {
+ int hash = 0;
+
+ for (int i = 0; i < stacktrace.length; ++i) {
+ hash *= 31;
+ hash += stacktrace[i].hashCode();
+ }
+
+ return hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+
+ final ThrowableWithEquals other = (ThrowableWithEquals)obj;
+ final StackTraceElement[] otherStackTrace = other.stacktrace;
+
+ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) {
+ return false;
+ }
+
+ if (this == obj) {
+ return true;
+ }
+
+ for (int i = 0; i < this.stacktrace.length; ++i) {
+ if (!this.stacktrace[i].equals(otherStackTrace[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 120b604d91643248ab375969f95f62a74cbf6be7..5e0d55c3821b1769d20514a8a6c5c74477019778 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -494,6 +494,7 @@ public class ServerChunkCache extends ChunkSource {
this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
// Paper end
+ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info
this.level.timings.syncChunkLoad.startTiming(); // Paper
this.mainThreadProcessor.managedBlock(completablefuture::isDone);
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 9d4c81a4964317a0726171dc6d490d12bd959cc4..01f879a8dd0e1ffec380e02072567330152eaceb 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -276,6 +276,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
};
public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
// Paper end
+ // Paper start
+ @Override
+ public boolean hasChunk(int chunkX, int chunkZ) {
+ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
+ }
+ // 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) {
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 3a1b9f1ba19b28cebdafeb3b2476217d47213862..3e2cd6c7a34c1a792d7346019a8b039d1f4a7c04 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1130,7 +1130,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
for (int i1 = i; i1 <= j; ++i1) {
for (int j1 = k; j1 <= l; ++j1) {
- LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false);
+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
if (chunk != null) {
chunk.getEntities(except, box, list, predicate);
@@ -1151,7 +1151,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
for (int i1 = i; i1 < j; ++i1) {
for (int j1 = k; j1 < l; ++j1) {
- LevelChunk chunk = this.getChunkSource().getChunk(i1, j1, false);
+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
if (chunk != null) {
chunk.getEntities(type, box, list, predicate);
@@ -1174,7 +1174,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
for (int i1 = i; i1 < j; ++i1) {
for (int j1 = k; j1 < l; ++j1) {
- LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false);
+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
if (chunk != null) {
chunk.getEntitiesOfClass(entityClass, box, list, predicate);

View file

@ -1,127 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Mon, 3 Jun 2019 02:02:39 -0400
Subject: [PATCH] Implement alternative item-despawn-rate
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 1ee2cced100626e48eb36ee14f84b9257c79a2f8..b913cd2dd0cd1b369b3f7b5a9d8b1be73f6d7920 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -1,10 +1,15 @@
package com.destroystokyo.paper;
import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode;
import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.spigotmc.SpigotWorldConfig;
@@ -512,4 +517,52 @@ public class PaperWorldConfig {
private void disableRelativeProjectileVelocity() {
disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
}
+
+ public boolean altItemDespawnRateEnabled;
+ public Map<Material, Integer> altItemDespawnRateMap;
+ private void altItemDespawnRate() {
+ String path = "alt-item-despawn-rate";
+
+ altItemDespawnRateEnabled = getBoolean(path + ".enabled", false);
+
+ Map<Material, Integer> altItemDespawnRateMapDefault = new EnumMap<>(Material.class);
+ altItemDespawnRateMapDefault.put(Material.COBBLESTONE, 300);
+ for (Material key : altItemDespawnRateMapDefault.keySet()) {
+ config.addDefault("world-settings.default." + path + ".items." + key, altItemDespawnRateMapDefault.get(key));
+ }
+
+ Map<String, Integer> rawMap = new HashMap<>();
+ try {
+ ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items");
+ if (mapSection == null) {
+ mapSection = config.getConfigurationSection("world-settings.default." + path + ".items");
+ }
+ for (String key : mapSection.getKeys(false)) {
+ int val = mapSection.getInt(key);
+ rawMap.put(key, val);
+ }
+ }
+ catch (Exception e) {
+ logError("alt-item-despawn-rate was malformatted");
+ altItemDespawnRateEnabled = false;
+ }
+
+ altItemDespawnRateMap = new EnumMap<>(Material.class);
+ if (!altItemDespawnRateEnabled) {
+ return;
+ }
+
+ for(String key : rawMap.keySet()) {
+ try {
+ altItemDespawnRateMap.put(Material.valueOf(key), rawMap.get(key));
+ } catch (Exception e) {
+ logError("Could not add item " + key + " to altItemDespawnRateMap: " + e.getMessage());
+ }
+ }
+ if(altItemDespawnRateEnabled) {
+ for(Material key : altItemDespawnRateMap.keySet()) {
+ log("Alternative item despawn rate of " + key + ": " + altItemDespawnRateMap.get(key));
+ }
+ }
+ }
}
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 281f5646980afc70890bdafd358ff9b20d32420d..96b8102773cbee2c3fe2711008ba1487084d67b0 100644
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
@@ -32,6 +32,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.stats.Stats;
+import org.bukkit.Material; // Paper
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
// CraftBukkit end
@@ -160,7 +161,7 @@ public class ItemEntity extends Entity {
}
}
- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot
+ if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper
// CraftBukkit start - fire ItemDespawnEvent
if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
this.age = 0;
@@ -184,7 +185,7 @@ public class ItemEntity extends Entity {
this.lastTick = MinecraftServer.currentTick;
// CraftBukkit end
- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot
+ if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper
// CraftBukkit start - fire ItemDespawnEvent
if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
this.age = 0;
@@ -534,9 +535,16 @@ public class ItemEntity extends Entity {
public void makeFakeItem() {
this.setNeverPickUp();
- this.age = level.spigotConfig.itemDespawnRate - 1; // Spigot
+ this.age = this.getDespawnRate() - 1; // Spigot // Paper
}
+ // Paper start
+ public int getDespawnRate(){
+ Material material = this.getItem().getBukkitStack().getType();
+ return level.paperConfig.altItemDespawnRateMap.getOrDefault(material, level.spigotConfig.itemDespawnRate);
+ }
+ // Paper end
+
@Override
public Packet<?> getAddEntityPacket() {
return new ClientboundAddEntityPacket(this);

View file

@ -1,42 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Paul Sauve <paul@burngames.net>
Date: Sun, 14 Jul 2019 21:05:03 -0500
Subject: [PATCH] Do less work if we have a custom Bukkit generator
If the Bukkit generator already has a spawn, use it immediately instead
of spending time generating one that we won't use
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 643d75b999c3da006eaaab11f4acd77e807683d4..753e6f609189c589514739bea80007bace3c89d2 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -628,12 +628,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
} else if (debugWorld) {
iworlddataserver.setSpawn(BlockPos.ZERO.above(), 0.0F);
} else {
- BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
- Random random = new Random(world.getSeed());
- BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
- return biomebase.getMobSettings().playerSpawnFriendly();
- }, random);
- ChunkPos chunkcoordintpair = blockposition == null ? new ChunkPos(0, 0) : new ChunkPos(blockposition);
+ // Paper start - moved down
// CraftBukkit start
if (world.generator != null) {
Random rand = new Random(world.getSeed());
@@ -649,6 +644,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
// CraftBukkit end
+ // Paper start - if the generator created a spawn for us, then there is no need for us to also create a spawn -
+ // only do it if the generator did not
+ BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
+ Random random = new Random(world.getSeed());
+ BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
+ return biomebase.getMobSettings().playerSpawnFriendly();
+ }, random);
+ ChunkPos chunkcoordintpair = blockposition == null ? new ChunkPos(0, 0) : new ChunkPos(blockposition);
+ // Paper end
if (blockposition == null) {
MinecraftServer.LOGGER.warn("Unable to find spawn biome");

View file

@ -1,25 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 13 Aug 2019 06:35:17 -0700
Subject: [PATCH] Fix MC-158900
The problem was we were checking isExpired() on the entry, but if it
was expired at that point, then it would be null.
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 7412765020854caabd32fb6f4fffcf7f4bf6dba7..e6eebb8f6f48cc55fc8fb114c959b8fbec4b8472 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -603,8 +603,10 @@ public abstract class PlayerList {
Player player = entity.getBukkitEntity();
PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress());
- if (getBans().isBanned(gameprofile) && !getBans().get(gameprofile).hasExpired()) {
- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile);
+ // Paper start - Fix MC-158900
+ UserBanListEntry gameprofilebanentry;
+ if (getBans().isBanned(gameprofile) && (gameprofilebanentry = getBans().get(gameprofile)) != null) {
+ // Paper end
chatmessage = new TranslatableComponent("multiplayer.disconnect.banned.reason", new Object[]{gameprofilebanentry.getReason()});
if (gameprofilebanentry.getExpires() != null) {

View file

@ -1,924 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Mon, 19 Aug 2019 01:27:58 +0500
Subject: [PATCH] implement optional per player mob spawns
diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
index 24eac9400fbf971742e89bbf47b0ba52b587c4eb..b818a7451d45d2ab7d4678f0065ada9017d8a631 100644
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
@@ -58,6 +58,7 @@ public class WorldTimingsHandler {
public final Timing miscMobSpawning;
+ public final Timing playerMobDistanceMapUpdate;
public final Timing poiUnload;
public final Timing chunkUnload;
@@ -123,6 +124,7 @@ public class WorldTimingsHandler {
miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc");
+ playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update");
poiUnload = Timings.ofSafe(name + "Chunk unload - POI");
chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk");
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index b913cd2dd0cd1b369b3f7b5a9d8b1be73f6d7920..6aec502eb529d4090306e12e837117cde7e114eb 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -565,4 +565,9 @@ public class PaperWorldConfig {
}
}
}
+
+ public boolean perPlayerMobSpawns = false;
+ private void perPlayerMobSpawns() {
+ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false);
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a87599922d7075a9f888f48a2deb35ed3eb7c54
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
@@ -0,0 +1,252 @@
+package com.destroystokyo.paper.util;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import java.util.List;
+import java.util.Map;
+import net.minecraft.core.SectionPos;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.ChunkPos;
+import org.spigotmc.AsyncCatcher;
+import java.util.HashMap;
+
+/** @author Spottedleaf */
+public final class PlayerMobDistanceMap {
+
+ private static final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>();
+
+ private final Map<ServerPlayer, SectionPos> players = new HashMap<>();
+ // we use linked for better iteration.
+ private final Long2ObjectOpenHashMap<PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer>> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f);
+ private int viewDistance;
+
+ private final PooledHashSets<ServerPlayer> pooledHashSets = new PooledHashSets<>();
+
+ public PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInRange(final ChunkPos chunkPos) {
+ return this.getPlayersInRange(chunkPos.x, chunkPos.z);
+ }
+
+ public PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInRange(final int chunkX, final int chunkZ) {
+ return this.playerMap.getOrDefault(ChunkPos.asLong(chunkX, chunkZ), EMPTY_SET);
+ }
+
+ public void update(final List<ServerPlayer> currentPlayers, final int newViewDistance) {
+ AsyncCatcher.catchOp("Distance map update");
+ final ObjectLinkedOpenHashSet<ServerPlayer> gone = new ObjectLinkedOpenHashSet<>(this.players.keySet());
+
+ final int oldViewDistance = this.viewDistance;
+ this.viewDistance = newViewDistance;
+
+ for (final ServerPlayer player : currentPlayers) {
+ if (player.isSpectator() || !player.affectsSpawning) {
+ continue; // will be left in 'gone' (or not added at all)
+ }
+
+ gone.remove(player);
+
+ final SectionPos newPosition = player.getPlayerMapSection();
+ final SectionPos oldPosition = this.players.put(player, newPosition);
+
+ if (oldPosition == null) {
+ this.addNewPlayer(player, newPosition, newViewDistance);
+ } else {
+ this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance);
+ }
+ //this.validatePlayer(player, newViewDistance); // debug only
+ }
+
+ for (final ServerPlayer player : gone) {
+ final SectionPos oldPosition = this.players.remove(player);
+ if (oldPosition != null) {
+ this.removePlayer(player, oldPosition, oldViewDistance);
+ }
+ }
+ }
+
+ // expensive op, only for debug
+ private void validatePlayer(final ServerPlayer player, final int viewDistance) {
+ int entiesGot = 0;
+ int expectedEntries = (2 * viewDistance + 1);
+ expectedEntries *= expectedEntries;
+
+ final SectionPos currPosition = player.getPlayerMapSection();
+
+ final int centerX = currPosition.getX();
+ final int centerZ = currPosition.getZ();
+
+ for (final Long2ObjectLinkedOpenHashMap.Entry<PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer>> entry : this.playerMap.long2ObjectEntrySet()) {
+ final long key = entry.getLongKey();
+ final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> map = entry.getValue();
+
+ if (map.referenceCount == 0) {
+ throw new IllegalStateException("Invalid map");
+ }
+
+ if (map.set.contains(player)) {
+ ++entiesGot;
+
+ final int chunkX = ChunkPos.getX(key);
+ final int chunkZ = ChunkPos.getZ(key);
+
+ final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ));
+
+ if (dist > viewDistance) {
+ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist);
+ }
+ }
+ }
+
+ if (entiesGot != expectedEntries) {
+ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot);
+ }
+ }
+
+ private void addPlayerTo(final ServerPlayer player, final int chunkX, final int chunkZ) {
+ this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players) -> {
+ if (players == null) {
+ return player.cachedSingleMobDistanceMap;
+ } else {
+ return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player);
+ }
+ });
+ }
+
+ private void removePlayerFrom(final ServerPlayer player, final int chunkX, final int chunkZ) {
+ this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players) -> {
+ return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map
+ });
+ }
+
+ private void updatePlayer(final ServerPlayer player, final SectionPos oldPosition, final SectionPos newPosition, final int oldViewDistance, final int newViewDistance) {
+ final int toX = newPosition.getX();
+ final int toZ = newPosition.getZ();
+ final int fromX = oldPosition.getX();
+ final int fromZ = oldPosition.getZ();
+
+ final int dx = toX - fromX;
+ final int dz = toZ - fromZ;
+
+ final int totalX = Math.abs(fromX - toX);
+ final int totalZ = Math.abs(fromZ - toZ);
+
+ if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) {
+ // teleported?
+ this.removePlayer(player, oldPosition, oldViewDistance);
+ this.addNewPlayer(player, newPosition, newViewDistance);
+ return;
+ }
+
+ // x axis is width
+ // z axis is height
+ // right refers to the x axis of where we moved
+ // top refers to the z axis of where we moved
+
+ if (oldViewDistance == newViewDistance) {
+ // same view distance
+
+ // used for relative positioning
+ final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise
+ final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise
+
+ // The area excluded by overlapping the two view distance squares creates four rectangles:
+ // Two on the left, and two on the right. The ones on the left we consider the "removed" section
+ // and on the right the "added" section.
+ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
+ // exclusive to the regions they surround.
+
+ // 4 points of the rectangle
+ int maxX; // exclusive
+ int minX; // inclusive
+ int maxZ; // exclusive
+ int minZ; // inclusive
+
+ if (dx != 0) {
+ // handle right addition
+
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
+ minX = fromX + (oldViewDistance * right) + right; // inclusive
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+ minZ = toZ - (oldViewDistance * up); // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.addPlayerTo(player, currX, currZ);
+ }
+ }
+ }
+
+ if (dz != 0) {
+ // handle up addition
+
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
+ minX = toX - (oldViewDistance * right); // inclusive
+ maxZ = toZ + (oldViewDistance * up) + up; // exclusive
+ minZ = fromZ + (oldViewDistance * up) + up; // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.addPlayerTo(player, currX, currZ);
+ }
+ }
+ }
+
+ if (dx != 0) {
+ // handle left removal
+
+ maxX = toX - (oldViewDistance * right); // exclusive
+ minX = fromX - (oldViewDistance * right); // inclusive
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+ minZ = toZ - (oldViewDistance * up); // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.removePlayerFrom(player, currX, currZ);
+ }
+ }
+ }
+
+ if (dz != 0) {
+ // handle down removal
+
+ maxX = fromX + (oldViewDistance * right) + right; // exclusive
+ minX = fromX - (oldViewDistance * right); // inclusive
+ maxZ = toZ - (oldViewDistance * up); // exclusive
+ minZ = fromZ - (oldViewDistance * up); // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.removePlayerFrom(player, currX, currZ);
+ }
+ }
+ }
+ } else {
+ // different view distance
+ // for now :)
+ this.removePlayer(player, oldPosition, oldViewDistance);
+ this.addNewPlayer(player, newPosition, newViewDistance);
+ }
+ }
+
+ private void removePlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) {
+ final int x = position.getX();
+ final int z = position.getZ();
+
+ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
+ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
+ this.removePlayerFrom(player, x + xoff, z + zoff);
+ }
+ }
+ }
+
+ private void addNewPlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) {
+ final int x = position.getX();
+ final int z = position.getZ();
+
+ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
+ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
+ this.addPlayerTo(player, x + xoff, z + zoff);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f13d3ff8391793a99f067189f854078334499c6
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
@@ -0,0 +1,241 @@
+package com.destroystokyo.paper.util;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+
+/** @author Spottedleaf */
+public class PooledHashSets<E> {
+
+ // we really want to avoid that equals() check as much as possible...
+ protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f);
+
+ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> current) {
+ if (current.referenceCount == 0) {
+ throw new IllegalStateException("Cannot decrement reference count for " + current);
+ }
+ if (current.referenceCount == -1 || --current.referenceCount > 0) {
+ return;
+ }
+
+ this.mapPool.remove(current);
+ return;
+ }
+
+ public PooledObjectLinkedOpenHashSet<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
+
+ if (cached != null) {
+ if (cached.referenceCount != -1) {
+ ++cached.referenceCount;
+ }
+
+ decrementReferenceCount(current);
+
+ return cached;
+ }
+
+ if (!current.add(object)) {
+ return current;
+ }
+
+ // we use get/put since we use a different key on put
+ PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
+
+ if (ret == null) {
+ ret = new PooledObjectLinkedOpenHashSet<>(current);
+ current.remove(object);
+ this.mapPool.put(ret, ret);
+ ret.referenceCount = 1;
+ } else {
+ if (ret.referenceCount != -1) {
+ ++ret.referenceCount;
+ }
+ current.remove(object);
+ }
+
+ current.updateAddCache(object, ret);
+
+ decrementReferenceCount(current);
+ return ret;
+ }
+
+ // rets null if current.size() == 1
+ public PooledObjectLinkedOpenHashSet<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ if (current.set.size() == 1) {
+ decrementReferenceCount(current);
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
+
+ if (cached != null) {
+ if (cached.referenceCount != -1) {
+ ++cached.referenceCount;
+ }
+
+ decrementReferenceCount(current);
+
+ return cached;
+ }
+
+ if (!current.remove(object)) {
+ return current;
+ }
+
+ // we use get/put since we use a different key on put
+ PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
+
+ if (ret == null) {
+ ret = new PooledObjectLinkedOpenHashSet<>(current);
+ current.add(object);
+ this.mapPool.put(ret, ret);
+ ret.referenceCount = 1;
+ } else {
+ if (ret.referenceCount != -1) {
+ ++ret.referenceCount;
+ }
+ current.add(object);
+ }
+
+ current.updateRemoveCache(object, ret);
+
+ decrementReferenceCount(current);
+ return ret;
+ }
+
+ public static final class PooledObjectLinkedOpenHashSet<E> implements Iterable<E> {
+
+ private static final WeakReference NULL_REFERENCE = new WeakReference(null);
+
+ final ObjectLinkedOpenHashSet<E> set;
+ int referenceCount; // -1 if special
+ int hash; // optimize hashcode
+
+ // add cache
+ WeakReference<E> lastAddObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
+
+ // remove cache
+ WeakReference<E> lastRemoveObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
+
+ public PooledObjectLinkedOpenHashSet() {
+ this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final E single) {
+ this();
+ this.referenceCount = -1;
+ this.add(single);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> other) {
+ this.set = other.set.clone();
+ this.hash = other.hash;
+ }
+
+ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
+ // generated by https://github.com/skeeto/hash-prospector
+ static int hash0(int x) {
+ x *= 0x36935555;
+ x ^= x >>> 16;
+ return x;
+ }
+
+ public PooledObjectLinkedOpenHashSet<E> getAddCache(final E element) {
+ final E currentAdd = this.lastAddObject.get();
+
+ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) {
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> map = this.lastAddMap.get();
+ if (map == null || map.referenceCount == 0) {
+ // we need to ret null if ref count is zero as calling code will assume the map is in use
+ return null;
+ }
+
+ return map;
+ }
+
+ public PooledObjectLinkedOpenHashSet<E> getRemoveCache(final E element) {
+ final E currentRemove = this.lastRemoveObject.get();
+
+ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) {
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> map = this.lastRemoveMap.get();
+ if (map == null || map.referenceCount == 0) {
+ // we need to ret null if ref count is zero as calling code will assume the map is in use
+ return null;
+ }
+
+ return map;
+ }
+
+ public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
+ this.lastAddObject = new WeakReference<>(element);
+ this.lastAddMap = new WeakReference<>(map);
+ }
+
+ public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
+ this.lastRemoveObject = new WeakReference<>(element);
+ this.lastRemoveMap = new WeakReference<>(map);
+ }
+
+ boolean add(final E element) {
+ boolean added = this.set.add(element);
+
+ if (added) {
+ this.hash += hash0(element.hashCode());
+ }
+
+ return added;
+ }
+
+ boolean remove(Object element) {
+ boolean removed = this.set.remove(element);
+
+ if (removed) {
+ this.hash -= hash0(element.hashCode());
+ }
+
+ return removed;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return this.set.iterator();
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hash;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof PooledObjectLinkedOpenHashSet)) {
+ return false;
+ }
+ if (this.referenceCount == 0) {
+ return other == this;
+ } else {
+ if (other == this) {
+ // Unfortunately we are never equal to our own instance while in use!
+ return false;
+ }
+ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " +
+ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString();
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index c00f7c60ce7b497d697d1abdf230f91f327e2113..190ddd4d9ef3472c33d46c2ead72fa0dc918054a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -71,6 +71,7 @@ import net.minecraft.util.thread.ProcessorMailbox;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.level.ChunkPos;
@@ -127,7 +128,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
private final Long2ByteMap chunkTypeCache;
private final Queue<Runnable> unloadQueue; private final Queue<Runnable> getUnloadQueueTasks() { return this.unloadQueue; } // Paper - OBFHELPER
- private int viewDistance;
+ int viewDistance; // Paper - private -> package private
+ public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
@@ -206,6 +208,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.overworldDataStorage = supplier;
this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag, this.level); // Paper
this.setViewDistance(i);
+ this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
+ }
+
+ public void updatePlayerMobTypeMap(Entity entity) {
+ if (!this.level.paperConfig.perPlayerMobSpawns) {
+ return;
+ }
+ int chunkX = (int)Math.floor(entity.getX()) >> 4;
+ int chunkZ = (int)Math.floor(entity.getZ()) >> 4;
+ int index = entity.getType().getEnumCreatureType().ordinal();
+
+ for (ServerPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) {
+ ++player.mobCounts[index];
+ }
+ }
+
+ public int getMobCountNear(ServerPlayer entityPlayer, MobCategory enumCreatureType) {
+ return entityPlayer.mobCounts[enumCreatureType.ordinal()];
}
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 5e0d55c3821b1769d20514a8a6c5c74477019778..eac5e799c4d26e53286a27c54b56899ba0b9ffb2 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -768,7 +768,22 @@ public class ServerChunkCache extends ChunkSource {
this.level.getProfiler().push("naturalSpawnCount");
this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
int l = this.distanceManager.getNaturalSpawnChunkCount();
- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk);
+ // Paper start - per player mob spawning
+ NaturalSpawner.SpawnState spawnercreature_d; // moved down
+ if (this.chunkMap.playerMobDistanceMap != null) {
+ // update distance map
+ this.level.timings.playerMobDistanceMapUpdate.startTiming();
+ this.chunkMap.playerMobDistanceMap.update(this.level.players, this.chunkMap.viewDistance);
+ this.level.timings.playerMobDistanceMapUpdate.stopTiming();
+ // re-set mob counts
+ for (ServerPlayer player : this.level.players) {
+ Arrays.fill(player.mobCounts, 0);
+ }
+ spawnercreature_d = NaturalSpawner.countMobs(l, this.level.getAllEntities(), this::getFullChunk, true);
+ } else {
+ spawnercreature_d = NaturalSpawner.countMobs(l, this.level.getAllEntities(), this::getFullChunk, false);
+ }
+ // Paper end
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
this.lastSpawnState = spawnercreature_d;
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index d6cfe68be1a944ff5d5780666467f5fd8e2794e3..b0eed4e18fc183856613c05f378576eb19985c46 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -93,6 +93,7 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.NeutralMob;
import net.minecraft.world.entity.animal.horse.AbstractHorse;
import net.minecraft.world.entity.item.ItemEntity;
@@ -216,6 +217,11 @@ public class ServerPlayer extends Player implements ContainerListener {
public boolean queueHealthUpdatePacket = false;
public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
// Paper end
+ // Paper start - mob spawning rework
+ public static final int ENUMCREATURETYPE_TOTAL_ENUMS = MobCategory.values().length;
+ public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper
+ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleMobDistanceMap;
+ // Paper end
// CraftBukkit start
public String displayName;
@@ -254,6 +260,7 @@ public class ServerPlayer extends Player implements ContainerListener {
this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
this.canPickUpLoot = true;
this.maxHealthCache = this.getMaxHealth();
+ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
}
// Yes, this doesn't match Vanilla, but it's the best we can do for now.
@@ -2058,6 +2065,7 @@ public class ServerPlayer extends Player implements ContainerListener {
}
+ public final SectionPos getPlayerMapSection() { return this.getLastSectionPos(); } // Paper - OBFHELPER
public SectionPos getLastSectionPos() {
return this.lastSectionPos;
}
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
index e39d950783599b01271bdb7e67fe68b46af0c49c..ae50030df7512c56c552e800b74ef4c69ec6d6d2 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -426,6 +426,7 @@ public class EntityType<T extends Entity> {
return this.canSpawnFarFromPlayer;
}
+ public final MobCategory getEnumCreatureType() { return this.getCategory(); } // Paper - OBFHELPER
public MobCategory getCategory() {
return this.category;
}
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 0fb69f9194078e5e05e36ed909eb48424b6465b4..df271598f6036c8cab8a8811151a376dda46e44d 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -17,6 +17,7 @@ import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MCUtil;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.Tag;
@@ -60,9 +61,14 @@ public final class NaturalSpawner {
});
public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource) {
+ // Paper start - add countMobs parameter
+ return countMobs(spawningChunkCount, entities, chunkSource, false);
+ }
+ public static NaturalSpawner.SpawnState countMobs(int i, Iterable<Entity> iterable, NaturalSpawner.ChunkGetter spawnercreature_b, boolean countMobs) {
+ // Paper end - add countMobs parameter
PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator();
Object2IntOpenHashMap<MobCategory> object2intopenhashmap = new Object2IntOpenHashMap();
- Iterator iterator = entities.iterator();
+ Iterator iterator = iterable.iterator();
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
@@ -89,7 +95,7 @@ public final class NaturalSpawner {
BlockPos blockposition = entity.blockPosition();
long j = ChunkPos.asLong(blockposition.getX() >> 4, blockposition.getZ() >> 4);
- chunkSource.query(j, (chunk) -> {
+ spawnercreature_b.query(j, (chunk) -> {
MobSpawnSettings.MobSpawnCost biomesettingsmobs_b = getRoughBiome(blockposition, chunk).getMobSettings().getMobSpawnCost(entity.getType());
if (biomesettingsmobs_b != null) {
@@ -97,11 +103,16 @@ public final class NaturalSpawner {
}
object2intopenhashmap.addTo(enumcreaturetype, 1);
+ // Paper start
+ if (countMobs) {
+ ((ServerLevel)chunk.world).getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
+ }
+ // Paper end
});
}
}
- return new NaturalSpawner.SpawnState(spawningChunkCount, object2intopenhashmap, spawnercreatureprobabilities);
+ return new NaturalSpawner.SpawnState(i, object2intopenhashmap, spawnercreatureprobabilities);
}
private static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) {
@@ -155,13 +166,31 @@ public final class NaturalSpawner {
continue;
}
- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (shouldSpawnAnimals || !enumcreaturetype.isPersistent()) && info.a(enumcreaturetype, limit)) {
+ // Paper start - only allow spawns upto the limit per chunk and update count afterwards
+ int currEntityCount = info.getEntityCountsByType().getInt(enumcreaturetype);
+ int k1 = limit * info.getSpawnerChunks() / NaturalSpawner.MAGIC_NUMBER;
+ int difference = k1 - currEntityCount;
+
+ if (world.paperConfig.perPlayerMobSpawns) {
+ int minDiff = Integer.MAX_VALUE;
+ for (ServerPlayer entityplayer : world.getChunkSource().chunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) {
+ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff);
+ }
+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
+ }
+ // Paper end
+
+ // Paper start - per player mob spawning
+ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (shouldSpawnAnimals || !enumcreaturetype.isPersistent()) && difference > 0) {
// CraftBukkit end
- spawnCategoryForChunk(enumcreaturetype, world, chunk, (entitytypes, blockposition, ichunkaccess) -> {
+ int spawnCount = spawnMobs(enumcreaturetype, world, chunk, (entitytypes, blockposition, ichunkaccess) -> {
return info.canSpawn(entitytypes, blockposition, ichunkaccess);
}, (entityinsentient, ichunkaccess) -> {
info.afterSpawn(entityinsentient, ichunkaccess);
- });
+ },
+ difference, world.paperConfig.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
+ info.getEntityCountsByType().mergeInt(enumcreaturetype, spawnCount, Integer::sum);
+ // Paper end - per player mob spawning
}
}
@@ -170,31 +199,43 @@ public final class NaturalSpawner {
}
public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
- BlockPos blockposition = getRandomPosWithin(world, chunk);
+ // Paper start - add parameters and int ret type
+ spawnMobs(group, world, chunk, checker, runner, Integer.MAX_VALUE, null);
+ }
+ public static int spawnMobs(MobCategory enumcreaturetype, ServerLevel worldserver, LevelChunk chunk, NaturalSpawner.SpawnPredicate spawnercreature_c, NaturalSpawner.AfterSpawnCallback spawnercreature_a, int maxSpawns, Consumer<Entity> trackEntity) {
+ // Paper end - add parameters and int ret type
+ BlockPos blockposition = getRandomPosWithin(worldserver, chunk);
if (blockposition.getY() >= 1) {
- spawnCategoryForPosition(group, world, (ChunkAccess) chunk, blockposition, checker, runner);
+ return spawnMobsInternal(enumcreaturetype, worldserver, (ChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a, maxSpawns, trackEntity);
}
+ return 0; // Paper
}
public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
- StructureFeatureManager structuremanager = world.structureFeatureManager();
- ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
- int i = pos.getY();
- BlockState iblockdata = world.getTypeIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
-
- if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
+ // Paper start - add maxSpawns parameter and return spawned mobs
+ spawnMobsInternal(group, world, chunk, pos, checker, runner, Integer.MAX_VALUE, null);
+ }
+ public static int spawnMobsInternal(MobCategory enumcreaturetype, ServerLevel worldserver, ChunkAccess ichunkaccess, BlockPos blockposition, NaturalSpawner.SpawnPredicate spawnercreature_c, NaturalSpawner.AfterSpawnCallback spawnercreature_a, int maxSpawns, Consumer<Entity> trackEntity) {
+ // Paper end - add maxSpawns parameter and return spawned mobs
+ StructureFeatureManager structuremanager = worldserver.structureFeatureManager();
+ ChunkGenerator chunkgenerator = worldserver.getChunkSource().getGenerator();
+ int i = blockposition.getY();
+ BlockState iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn
+ int j = 0; // Paper - moved up
+
+ if (iblockdata != null && !iblockdata.isRedstoneConductor(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- int j = 0;
+ // Paper - moved up
int k = 0;
while (k < 3) {
- int l = pos.getX();
- int i1 = pos.getZ();
+ int l = blockposition.getX();
+ int i1 = blockposition.getZ();
boolean flag = true;
MobSpawnSettings.SpawnerData biomesettingsmobs_c = null;
SpawnGroupData groupdataentity = null;
- int j1 = Mth.ceil(world.random.nextFloat() * 4.0F);
+ int j1 = Mth.ceil(worldserver.random.nextFloat() * 4.0F);
int k1 = 0;
int l1 = 0;
@@ -202,53 +243,58 @@ public final class NaturalSpawner {
if (l1 < j1) {
label53:
{
- l += world.random.nextInt(6) - world.random.nextInt(6);
- i1 += world.random.nextInt(6) - world.random.nextInt(6);
+ l += worldserver.random.nextInt(6) - worldserver.random.nextInt(6);
+ i1 += worldserver.random.nextInt(6) - worldserver.random.nextInt(6);
blockposition_mutableblockposition.set(l, i, i1);
double d0 = (double) l + 0.5D;
double d1 = (double) i1 + 0.5D;
- Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false);
+ Player entityhuman = worldserver.getNearestPlayer(d0, (double) i, d1, -1.0D, false);
if (entityhuman != null) {
double d2 = entityhuman.distanceToSqr(d0, (double) i, d1);
- if (isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2) && world.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn
+ if (isRightDistanceToPlayerAndSpawnPoint(worldserver, ichunkaccess, blockposition_mutableblockposition, d2) && worldserver.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn
if (biomesettingsmobs_c == null) {
- biomesettingsmobs_c = getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, (BlockPos) blockposition_mutableblockposition);
+ biomesettingsmobs_c = getRandomSpawnMobAt(worldserver, structuremanager, chunkgenerator, enumcreaturetype, worldserver.random, (BlockPos) blockposition_mutableblockposition);
if (biomesettingsmobs_c == null) {
break label53;
}
- j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount);
+ j1 = biomesettingsmobs_c.minCount + worldserver.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount);
}
// Paper start
- Boolean doSpawning = a(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
+ Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
if (doSpawning == null) {
- return;
+ return j; // Paper
}
- if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
+ if (doSpawning && spawnercreature_c.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, ichunkaccess)) {
// Paper end
- Mob entityinsentient = getMobForSpawn(world, biomesettingsmobs_c.type);
+ Mob entityinsentient = getMobForSpawn(worldserver, biomesettingsmobs_c.type);
if (entityinsentient == null) {
- return;
+ return j; // Paper
}
- entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F);
- if (isValidPositionForMob(world, entityinsentient, d2)) {
- groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null);
+ entityinsentient.moveTo(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F);
+ if (isValidPositionForMob(worldserver, entityinsentient, d2)) {
+ groupdataentity = entityinsentient.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entityinsentient.blockPosition()), MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null);
// CraftBukkit start
- world.addAllEntities(entityinsentient, SpawnReason.NATURAL);
+ worldserver.addAllEntities(entityinsentient, SpawnReason.NATURAL);
if (!entityinsentient.removed) {
- ++j;
+ ++j; // Paper - force diff on name change - we expect this to be the total amount spawned
++k1;
- runner.run(entityinsentient, chunk);
+ spawnercreature_a.run(entityinsentient, ichunkaccess);
+ // Paper start
+ if (trackEntity != null) {
+ trackEntity.accept(entityinsentient);
+ }
+ // Paper end
}
// CraftBukkit end
- if (j >= entityinsentient.getMaxSpawnClusterSize()) {
- return;
+ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper
+ return j; // Paper
}
if (entityinsentient.isMaxGroupSizeReached(k1)) {
@@ -270,6 +316,7 @@ public final class NaturalSpawner {
}
}
+ return j; // Paper
}
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
@@ -510,8 +557,8 @@ public final class NaturalSpawner {
public static class SpawnState {
- private final int spawnableChunkCount;
- private final Object2IntOpenHashMap<MobCategory> mobCategoryCounts;
+ private final int spawnableChunkCount; final int getSpawnerChunks() { return this.spawnableChunkCount; } // Paper - OBFHELPER
+ private final Object2IntOpenHashMap<MobCategory> mobCategoryCounts; final Object2IntMap<MobCategory> getEntityCountsByType() { return this.mobCategoryCounts; } // Paper - OBFHELPER
private final PotentialCalculator spawnPotential;
private final Object2IntMap<MobCategory> unmodifiableMobCategoryCounts;
@Nullable
@@ -572,7 +619,7 @@ public final class NaturalSpawner {
// CraftBukkit start
private boolean a(MobCategory enumcreaturetype, int limit) {
- int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
+ int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; // Paper - diff on change, needed in the spawn method
// CraftBukkit end
return this.mobCategoryCounts.getInt(enumcreaturetype) < i;

View file

@ -1,52 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Mon, 19 Aug 2019 19:42:35 +0500
Subject: [PATCH] Prevent consuming the wrong itemstack
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 046a05925739005080af35c4be984303b575bf68..f8917123547615dd624e3e428ec1bf6450c7b7d8 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3198,15 +3198,18 @@ public abstract class LivingEntity extends Entity {
this.entityData.set(LivingEntity.DATA_LIVING_ENTITY_FLAGS, (byte) j);
}
- public void startUsingItem(InteractionHand hand) {
- ItemStack itemstack = this.getItemInHand(hand);
+ // Paper start -- OBFHELPER and forwarder to method with forceUpdate parameter
+ public void startUsingItem(InteractionHand hand) { this.updateActiveItem(hand, false); }
+ public void updateActiveItem(InteractionHand enumhand, boolean forceUpdate) {
+ // Paper end
+ ItemStack itemstack = this.getItemInHand(enumhand);
- if (!itemstack.isEmpty() && !this.isUsingItem()) {
+ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag
this.useItem = itemstack;
this.useItemRemaining = itemstack.getUseDuration();
if (!this.level.isClientSide) {
this.setLivingEntityFlag(1, true);
- this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
+ this.setLivingEntityFlag(2, enumhand == InteractionHand.OFF_HAND);
}
}
@@ -3279,6 +3282,7 @@ public abstract class LivingEntity extends Entity {
this.releaseUsingItem();
} else {
if (!this.useItem.isEmpty() && this.isUsingItem()) {
+ this.updateActiveItem(this.getUsedItemHand(), true); // Paper
this.triggerItemUseEffects(this.useItem, 16);
// CraftBukkit start - fire PlayerItemConsumeEvent
ItemStack itemstack;
@@ -3313,8 +3317,8 @@ public abstract class LivingEntity extends Entity {
}
this.stopUsingItem();
- // Paper start - if the replacement is anything but the default, update the client inventory
- if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) {
+ // Paper start
+ if (this instanceof ServerPlayer) {
((ServerPlayer) this).getBukkitEntity().updateInventory();
}
// Paper end