Move patches to unapplied
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
--- a/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -28,14 +28,18 @@
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||
|
||||
+// CraftBukkit start
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+// CraftBukkit end
|
||||
+
|
||||
public class ChunkHolder extends GenerationChunkHolder {
|
||||
|
||||
public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
|
||||
private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
||||
private final LevelHeightAccessor levelHeightAccessor;
|
||||
- private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture;
|
||||
- private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture;
|
||||
- private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture;
|
||||
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
|
||||
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
|
||||
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
|
||||
public int oldTicketLevel;
|
||||
private int ticketLevel;
|
||||
private int queueLevel;
|
||||
@@ -58,9 +62,9 @@
|
||||
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
||||
this.blockChangedLightSectionFilter = new BitSet();
|
||||
this.skyChangedLightSectionFilter = new BitSet();
|
||||
- this.pendingFullStateConfirmation = CompletableFuture.completedFuture((Object) null);
|
||||
- this.sendSync = CompletableFuture.completedFuture((Object) null);
|
||||
- this.saveSync = CompletableFuture.completedFuture((Object) null);
|
||||
+ this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
|
||||
+ this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
|
||||
+ this.saveSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
|
||||
this.levelHeightAccessor = world;
|
||||
this.lightEngine = lightingProvider;
|
||||
this.onLevelChange = levelUpdateListener;
|
||||
@@ -72,6 +76,18 @@
|
||||
this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ public LevelChunk getFullChunkNow() {
|
||||
+ // Note: We use the oldTicketLevel for isLoaded checks.
|
||||
+ if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null;
|
||||
+ return this.getFullChunkNowUnchecked();
|
||||
+ }
|
||||
+
|
||||
+ public LevelChunk getFullChunkNowUnchecked() {
|
||||
+ return (LevelChunk) this.getChunkIfPresentUnchecked(ChunkStatus.FULL);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
|
||||
return this.tickingChunkFuture;
|
||||
}
|
||||
@@ -85,8 +101,8 @@
|
||||
}
|
||||
|
||||
@Nullable
|
||||
- public LevelChunk getTickingChunk() {
|
||||
- return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse((Object) null);
|
||||
+ public final LevelChunk getTickingChunk() { // Paper - final for inline
|
||||
+ return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -138,6 +154,7 @@
|
||||
boolean flag = this.hasChangedSections;
|
||||
int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
|
||||
|
||||
+ if (i < 0 || i >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
|
||||
if (this.changedBlocksPerSection[i] == null) {
|
||||
this.hasChangedSections = true;
|
||||
this.changedBlocksPerSection[i] = new ShortOpenHashSet();
|
||||
@@ -224,8 +241,11 @@
|
||||
ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection);
|
||||
|
||||
this.broadcast(list, packetplayoutmultiblockchange);
|
||||
+ // CraftBukkit start
|
||||
+ List finalList = list;
|
||||
packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
|
||||
- this.broadcastBlockEntityIfNeeded(list, world, blockposition1, iblockdata1);
|
||||
+ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
|
||||
+ // CraftBukkit end
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -291,7 +311,7 @@
|
||||
this.pendingFullStateConfirmation = completablefuture1;
|
||||
chunkFuture.thenAccept((chunkresult) -> {
|
||||
chunkresult.ifSuccess((chunk) -> {
|
||||
- completablefuture1.complete((Object) null);
|
||||
+ completablefuture1.complete(null); // CraftBukkit - decompile error
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -301,6 +321,38 @@
|
||||
chunkLoadingManager.onFullChunkStatusChange(this.pos, target);
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
||||
+ // SPIGOT-7780: Moved out of updateFutures to call all chunk unload events before calling updateHighestAllowedStatus for all chunks
|
||||
+ protected void callEventIfUnloading(ChunkMap playerchunkmap) {
|
||||
+ FullChunkStatus oldFullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
|
||||
+ FullChunkStatus newFullChunkStatus = ChunkLevel.fullStatus(this.ticketLevel);
|
||||
+ boolean oldIsFull = oldFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
|
||||
+ boolean newIsFull = newFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
|
||||
+ if (oldIsFull && !newIsFull) {
|
||||
+ this.getFullChunkFuture().thenAccept((either) -> {
|
||||
+ LevelChunk chunk = (LevelChunk) either.orElse(null);
|
||||
+ if (chunk != null) {
|
||||
+ playerchunkmap.callbackExecutor.execute(() -> {
|
||||
+ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
|
||||
+ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
|
||||
+ // These actions may however happen deferred, so we manually set the needsSaving flag already here.
|
||||
+ chunk.markUnsaved();
|
||||
+ chunk.unloadCallback();
|
||||
+ });
|
||||
+ }
|
||||
+ }).exceptionally((throwable) -> {
|
||||
+ // ensure exceptions are printed, by default this is not the case
|
||||
+ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
|
||||
+ return null;
|
||||
+ });
|
||||
+
|
||||
+ // Run callback right away if the future was already done
|
||||
+ playerchunkmap.callbackExecutor.run();
|
||||
+ }
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
protected void updateFutures(ChunkMap chunkLoadingManager, Executor executor) {
|
||||
FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
|
||||
FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
|
||||
@@ -309,12 +361,28 @@
|
||||
|
||||
this.wasAccessibleSinceLastSave |= flag1;
|
||||
if (!flag && flag1) {
|
||||
+ int expectCreateCount = ++this.fullChunkCreateCount; // Paper
|
||||
this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this);
|
||||
this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL);
|
||||
+ // Paper start - cache ticking ready status
|
||||
+ this.fullChunkFuture.thenAccept(chunkResult -> {
|
||||
+ chunkResult.ifSuccess(chunk -> {
|
||||
+ if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
|
||||
+ ChunkHolder.this.isFullChunkReady = true;
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkBorder(chunk, this);
|
||||
+ }
|
||||
+ });
|
||||
+ });
|
||||
+ // Paper end - cache ticking ready status
|
||||
this.addSaveDependency(this.fullChunkFuture);
|
||||
}
|
||||
|
||||
if (flag && !flag1) {
|
||||
+ // Paper start
|
||||
+ if (this.isFullChunkReady) {
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
||||
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
||||
}
|
||||
@@ -325,11 +393,25 @@
|
||||
if (!flag2 && flag3) {
|
||||
this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this);
|
||||
this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
|
||||
+ // Paper start - cache ticking ready status
|
||||
+ this.tickingChunkFuture.thenAccept(chunkResult -> {
|
||||
+ chunkResult.ifSuccess(chunk -> {
|
||||
+ // note: Here is a very good place to add callbacks to logic waiting on this.
|
||||
+ ChunkHolder.this.isTickingReady = true;
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkTicking(chunk, this);
|
||||
+ });
|
||||
+ });
|
||||
+ // Paper end
|
||||
this.addSaveDependency(this.tickingChunkFuture);
|
||||
}
|
||||
|
||||
if (flag2 && !flag3) {
|
||||
- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
||||
+ // Paper start
|
||||
+ if (this.isTickingReady) {
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
|
||||
+ }
|
||||
+ // Paper end
|
||||
+ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
|
||||
this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
||||
}
|
||||
|
||||
@@ -343,11 +425,24 @@
|
||||
|
||||
this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this);
|
||||
this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
|
||||
+ // Paper start - cache ticking ready status
|
||||
+ this.entityTickingChunkFuture.thenAccept(chunkResult -> {
|
||||
+ chunkResult.ifSuccess(chunk -> {
|
||||
+ ChunkHolder.this.isEntityTickingReady = true;
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkEntityTicking(chunk, this);
|
||||
+ });
|
||||
+ });
|
||||
+ // Paper end
|
||||
this.addSaveDependency(this.entityTickingChunkFuture);
|
||||
}
|
||||
|
||||
if (flag4 && !flag5) {
|
||||
- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
||||
+ // Paper start
|
||||
+ if (this.isEntityTickingReady) {
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
|
||||
+ }
|
||||
+ // Paper end
|
||||
+ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
|
||||
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
||||
}
|
||||
|
||||
@@ -357,6 +452,26 @@
|
||||
|
||||
this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
|
||||
this.oldTicketLevel = this.ticketLevel;
|
||||
+ // CraftBukkit start
|
||||
+ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
|
||||
+ if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
|
||||
+ this.getFullChunkFuture().thenAccept((either) -> {
|
||||
+ LevelChunk chunk = (LevelChunk) either.orElse(null);
|
||||
+ if (chunk != null) {
|
||||
+ chunkLoadingManager.callbackExecutor.execute(() -> {
|
||||
+ chunk.loadCallback();
|
||||
+ });
|
||||
+ }
|
||||
+ }).exceptionally((throwable) -> {
|
||||
+ // ensure exceptions are printed, by default this is not the case
|
||||
+ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
|
||||
+ return null;
|
||||
+ });
|
||||
+
|
||||
+ // Run callback right away if the future was already done
|
||||
+ chunkLoadingManager.callbackExecutor.run();
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
|
||||
public boolean wasAccessibleSinceLastSave() {
|
||||
@@ -0,0 +1,433 @@
|
||||
--- a/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -104,6 +104,10 @@
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
+// CraftBukkit start
|
||||
+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
||||
+// CraftBukkit end
|
||||
+
|
||||
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap {
|
||||
|
||||
private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
|
||||
@@ -149,6 +153,33 @@
|
||||
public int serverViewDistance;
|
||||
private final WorldGenContext worldGenContext;
|
||||
|
||||
+ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
|
||||
+ public final CallbackExecutor callbackExecutor = new CallbackExecutor();
|
||||
+ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
|
||||
+
|
||||
+ private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
|
||||
+
|
||||
+ @Override
|
||||
+ public void execute(Runnable runnable) {
|
||||
+ this.queue.add(runnable);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void run() {
|
||||
+ Runnable task;
|
||||
+ while ((task = this.queue.poll()) != null) {
|
||||
+ task.run();
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
+ // Paper start
|
||||
+ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
|
||||
+ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
|
||||
super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
|
||||
this.visibleChunkMap = this.updatingChunkMap.clone();
|
||||
@@ -170,13 +201,19 @@
|
||||
RegistryAccess iregistrycustom = world.registryAccess();
|
||||
long j = world.getSeed();
|
||||
|
||||
- if (chunkGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
|
||||
+ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
|
||||
+ ChunkGenerator randomGenerator = chunkGenerator;
|
||||
+ if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) {
|
||||
+ randomGenerator = customChunkGenerator.getDelegate();
|
||||
+ }
|
||||
+ if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
|
||||
+ // CraftBukkit end
|
||||
this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
|
||||
} else {
|
||||
this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
|
||||
}
|
||||
|
||||
- this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j);
|
||||
+ this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot
|
||||
this.mainThreadExecutor = mainThreadExecutor;
|
||||
ConsecutiveExecutor consecutiveexecutor = new ConsecutiveExecutor(executor, "worldgen");
|
||||
|
||||
@@ -198,6 +235,12 @@
|
||||
this.chunksToEagerlySave.add(pos.toLong());
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
protected ChunkGenerator generator() {
|
||||
return this.worldGenContext.generator();
|
||||
}
|
||||
@@ -325,7 +368,7 @@
|
||||
throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
|
||||
}
|
||||
|
||||
- ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse((Object) null);
|
||||
+ ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
|
||||
|
||||
if (ichunkaccess == null) {
|
||||
return ChunkMap.UNLOADED_CHUNK_LIST_RESULT;
|
||||
@@ -354,9 +397,9 @@
|
||||
};
|
||||
|
||||
stringbuilder.append("Updating:").append(System.lineSeparator());
|
||||
- this.updatingChunkMap.values().forEach(consumer);
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
|
||||
stringbuilder.append("Visible:").append(System.lineSeparator());
|
||||
- this.visibleChunkMap.values().forEach(consumer);
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
|
||||
CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
|
||||
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
|
||||
|
||||
@@ -398,6 +441,9 @@
|
||||
holder.setTicketLevel(level);
|
||||
} else {
|
||||
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this);
|
||||
+ // Paper start
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
this.updatingChunkMap.put(pos, holder);
|
||||
@@ -427,7 +473,7 @@
|
||||
|
||||
protected void saveAllChunks(boolean flush) {
|
||||
if (flush) {
|
||||
- List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
|
||||
+ List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
|
||||
MutableBoolean mutableboolean = new MutableBoolean();
|
||||
|
||||
do {
|
||||
@@ -453,7 +499,7 @@
|
||||
} else {
|
||||
this.nextChunkSaveTime.clear();
|
||||
long i = Util.getMillis();
|
||||
- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
|
||||
+ Iterator<ChunkHolder> objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
|
||||
|
||||
while (objectiterator.hasNext()) {
|
||||
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
|
||||
@@ -478,7 +524,7 @@
|
||||
}
|
||||
|
||||
public boolean hasWork() {
|
||||
- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
|
||||
+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
|
||||
}
|
||||
|
||||
private void processUnloads(BooleanSupplier shouldKeepTicking) {
|
||||
@@ -537,8 +583,11 @@
|
||||
this.scheduleUnload(pos, chunk);
|
||||
} else {
|
||||
ChunkAccess ichunkaccess = chunk.getLatestChunk();
|
||||
-
|
||||
- if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) {
|
||||
+ // Paper start
|
||||
+ boolean removed;
|
||||
+ if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) {
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
|
||||
+ // Paper end
|
||||
LevelChunk chunk1;
|
||||
|
||||
if (ichunkaccess instanceof LevelChunk) {
|
||||
@@ -556,7 +605,9 @@
|
||||
this.lightEngine.tryScheduleUpdate();
|
||||
this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
|
||||
this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong());
|
||||
- }
|
||||
+ } else if (removed) { // Paper start
|
||||
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
|
||||
+ } // Paper end
|
||||
|
||||
}
|
||||
};
|
||||
@@ -905,7 +956,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- protected void setServerViewDistance(int watchDistance) {
|
||||
+ public void setServerViewDistance(int watchDistance) { // Paper - public
|
||||
int j = Mth.clamp(watchDistance, 2, 32);
|
||||
|
||||
if (j != this.serverViewDistance) {
|
||||
@@ -922,7 +973,7 @@
|
||||
|
||||
}
|
||||
|
||||
- int getPlayerViewDistance(ServerPlayer player) {
|
||||
+ public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
|
||||
return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
|
||||
}
|
||||
|
||||
@@ -951,7 +1002,7 @@
|
||||
}
|
||||
|
||||
public int size() {
|
||||
- return this.visibleChunkMap.size();
|
||||
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
|
||||
}
|
||||
|
||||
public DistanceManager getDistanceManager() {
|
||||
@@ -959,25 +1010,26 @@
|
||||
}
|
||||
|
||||
protected Iterable<ChunkHolder> getChunks() {
|
||||
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
|
||||
+ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
|
||||
}
|
||||
|
||||
void dumpChunks(Writer writer) throws IOException {
|
||||
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
|
||||
TickingTracker tickingtracker = this.distanceManager.tickingTracker();
|
||||
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
|
||||
+ Iterator<ChunkHolder> objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
|
||||
|
||||
while (objectbidirectionaliterator.hasNext()) {
|
||||
- Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
|
||||
- long i = entry.getLongKey();
|
||||
+ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
|
||||
+ long i = playerchunk.pos.toLong(); // Paper
|
||||
ChunkPos chunkcoordintpair = new ChunkPos(i);
|
||||
- ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
|
||||
+ // Paper - move up
|
||||
Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLatestChunk());
|
||||
Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
|
||||
return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
|
||||
});
|
||||
|
||||
- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse((Object) null), optional1.map(LevelChunk::getFullStatus).orElse((Object) null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
|
||||
+ // CraftBukkit - decompile error
|
||||
+ csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
|
||||
return chunk.getBlockEntities().size();
|
||||
}).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
|
||||
return chunk.getBlockTicks().count();
|
||||
@@ -990,7 +1042,7 @@
|
||||
|
||||
private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
|
||||
try {
|
||||
- ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow((Object) null);
|
||||
+ ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error
|
||||
|
||||
return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed";
|
||||
} catch (CompletionException completionexception) {
|
||||
@@ -1002,12 +1054,14 @@
|
||||
|
||||
private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
|
||||
return this.read(chunkPos).thenApplyAsync((optional) -> {
|
||||
- return optional.map(this::upgradeChunkTag);
|
||||
+ return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
|
||||
}, Util.backgroundExecutor().forName("upgradeChunk"));
|
||||
}
|
||||
|
||||
- private CompoundTag upgradeChunkTag(CompoundTag nbt) {
|
||||
- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator().getTypeNameForDataFixer());
|
||||
+ // CraftBukkit start
|
||||
+ private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
|
||||
+ return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level);
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
|
||||
void forEachSpawnCandidateChunk(Consumer<ChunkHolder> callback) {
|
||||
@@ -1025,10 +1079,23 @@
|
||||
}
|
||||
|
||||
public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
|
||||
- return !this.distanceManager.hasPlayersNearby(pos.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(pos);
|
||||
+ // Spigot start
|
||||
+ return this.anyPlayerCloseEnoughForSpawning(pos, false);
|
||||
}
|
||||
|
||||
+ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
|
||||
+ return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange);
|
||||
+ // Spigot end
|
||||
+ }
|
||||
+
|
||||
private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) {
|
||||
+ // Spigot start
|
||||
+ return this.anyPlayerCloseEnoughForSpawningInternal(pos, false);
|
||||
+ }
|
||||
+
|
||||
+ private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) {
|
||||
+ double blockRange; // Paper - use from event
|
||||
+ // Spigot end
|
||||
Iterator iterator = this.playerMap.getAllPlayers().iterator();
|
||||
|
||||
ServerPlayer entityplayer;
|
||||
@@ -1039,7 +1106,16 @@
|
||||
}
|
||||
|
||||
entityplayer = (ServerPlayer) iterator.next();
|
||||
- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, pos));
|
||||
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
|
||||
+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
|
||||
+ blockRange = 16384.0D;
|
||||
+ if (reducedRange) {
|
||||
+ event = entityplayer.playerNaturallySpawnedEvent;
|
||||
+ if (event == null || event.isCancelled()) continue;
|
||||
+ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
|
||||
+ }
|
||||
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
|
||||
+ } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1056,7 +1132,7 @@
|
||||
while (iterator.hasNext()) {
|
||||
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
|
||||
|
||||
- if (this.playerIsCloseEnoughForSpawning(entityplayer, pos)) {
|
||||
+ if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot
|
||||
builder.add(entityplayer);
|
||||
}
|
||||
}
|
||||
@@ -1065,13 +1141,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) {
|
||||
- if (player.isSpectator()) {
|
||||
+ private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot
|
||||
+ if (entityplayer.isSpectator()) {
|
||||
return false;
|
||||
} else {
|
||||
- double d0 = ChunkMap.euclideanDistanceSquared(pos, player);
|
||||
+ double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer);
|
||||
|
||||
- return d0 < 16384.0D;
|
||||
+ return d0 < range; // Spigot
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1215,9 +1291,19 @@
|
||||
}
|
||||
|
||||
public void addEntity(Entity entity) {
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
|
||||
+ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
|
||||
+ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
|
||||
+ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
|
||||
+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
|
||||
+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets
|
||||
if (!(entity instanceof EnderDragonPart)) {
|
||||
EntityType<?> entitytypes = entity.getType();
|
||||
int i = entitytypes.clientTrackingRange() * 16;
|
||||
+ i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
|
||||
|
||||
if (i != 0) {
|
||||
int j = entitytypes.updateInterval();
|
||||
@@ -1250,6 +1336,7 @@
|
||||
}
|
||||
|
||||
protected void removeEntity(Entity entity) {
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
|
||||
if (entity instanceof ServerPlayer entityplayer) {
|
||||
this.updatePlayerStatus(entityplayer, false);
|
||||
ObjectIterator objectiterator = this.entityMap.values().iterator();
|
||||
@@ -1391,7 +1478,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
- private class ChunkDistanceManager extends DistanceManager {
|
||||
+ public class ChunkDistanceManager extends DistanceManager { // Paper - public
|
||||
|
||||
protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
|
||||
super(workerExecutor, mainThreadExecutor);
|
||||
@@ -1421,10 +1508,10 @@
|
||||
final Entity entity;
|
||||
private final int range;
|
||||
SectionPos lastSectionPos;
|
||||
- public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
|
||||
+ public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
|
||||
|
||||
public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
|
||||
- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast);
|
||||
+ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
|
||||
this.entity = entity;
|
||||
this.range = i;
|
||||
this.lastSectionPos = SectionPos.of((EntityAccess) entity);
|
||||
@@ -1469,6 +1556,7 @@
|
||||
}
|
||||
|
||||
public void removePlayer(ServerPlayer player) {
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
|
||||
if (this.seenBy.remove(player.connection)) {
|
||||
this.serverEntity.removePairing(player);
|
||||
}
|
||||
@@ -1476,17 +1564,41 @@
|
||||
}
|
||||
|
||||
public void updatePlayer(ServerPlayer player) {
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
|
||||
if (player != this.entity) {
|
||||
- Vec3 vec3d = player.position().subtract(this.entity.position());
|
||||
+ // Paper start - remove allocation of Vec3D here
|
||||
+ // Vec3 vec3d = player.position().subtract(this.entity.position());
|
||||
+ double vec3d_dx = player.getX() - this.entity.getX();
|
||||
+ double vec3d_dz = player.getZ() - this.entity.getZ();
|
||||
+ // Paper end - remove allocation of Vec3D here
|
||||
int i = ChunkMap.this.getPlayerViewDistance(player);
|
||||
double d0 = (double) Math.min(this.getEffectiveRange(), i * 16);
|
||||
- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z;
|
||||
+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
|
||||
double d2 = d0 * d0;
|
||||
- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
|
||||
+ // Paper start - Configurable entity tracking range by Y
|
||||
+ boolean flag = d1 <= d2;
|
||||
+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
|
||||
+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
|
||||
+ if (rangeY != -1) {
|
||||
+ double vec3d_dy = player.getY() - this.entity.getY();
|
||||
+ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY;
|
||||
+ }
|
||||
+ }
|
||||
+ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
|
||||
+ // Paper end - Configurable entity tracking range by Y
|
||||
|
||||
+ // CraftBukkit start - respect vanish API
|
||||
+ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
|
||||
+ flag = false;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
if (flag) {
|
||||
if (this.seenBy.add(player.connection)) {
|
||||
+ // Paper start - entity tracking events
|
||||
+ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) {
|
||||
this.serverEntity.addPairing(player);
|
||||
+ }
|
||||
+ // Paper end - entity tracking events
|
||||
}
|
||||
} else if (this.seenBy.remove(player.connection)) {
|
||||
this.serverEntity.removePairing(player);
|
||||
@@ -1506,6 +1618,7 @@
|
||||
while (iterator.hasNext()) {
|
||||
Entity entity = (Entity) iterator.next();
|
||||
int j = entity.getType().clientTrackingRange() * 16;
|
||||
+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
|
||||
|
||||
if (j > i) {
|
||||
i = j;
|
||||
@@ -0,0 +1,152 @@
|
||||
--- a/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -117,8 +117,17 @@
|
||||
|
||||
ChunkHolder playerchunk;
|
||||
|
||||
+ // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
|
||||
while (iterator.hasNext()) {
|
||||
playerchunk = (ChunkHolder) iterator.next();
|
||||
+ playerchunk.callEventIfUnloading(chunkLoadingManager);
|
||||
+ }
|
||||
+
|
||||
+ iterator = this.chunksToUpdateFutures.iterator();
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
+ while (iterator.hasNext()) {
|
||||
+ playerchunk = (ChunkHolder) iterator.next();
|
||||
playerchunk.updateHighestAllowedStatus(chunkLoadingManager);
|
||||
}
|
||||
|
||||
@@ -165,30 +174,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
- void addTicket(long position, Ticket<?> ticket) {
|
||||
- SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(position);
|
||||
+ boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
|
||||
+ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
|
||||
int j = DistanceManager.getTicketLevelAt(arraysetsorted);
|
||||
Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
|
||||
|
||||
ticket1.setCreatedTick(this.ticketTickCounter);
|
||||
if (ticket.getTicketLevel() < j) {
|
||||
- this.ticketTracker.update(position, ticket.getTicketLevel(), true);
|
||||
+ this.ticketTracker.update(i, ticket.getTicketLevel(), true);
|
||||
}
|
||||
|
||||
+ return ticket == ticket1; // CraftBukkit
|
||||
}
|
||||
|
||||
- void removeTicket(long pos, Ticket<?> ticket) {
|
||||
- SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(pos);
|
||||
+ boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
|
||||
+ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
|
||||
|
||||
+ boolean removed = false; // CraftBukkit
|
||||
if (arraysetsorted.remove(ticket)) {
|
||||
- ;
|
||||
+ removed = true; // CraftBukkit
|
||||
}
|
||||
|
||||
if (arraysetsorted.isEmpty()) {
|
||||
- this.tickets.remove(pos);
|
||||
+ this.tickets.remove(i);
|
||||
}
|
||||
|
||||
- this.ticketTracker.update(pos, DistanceManager.getTicketLevelAt(arraysetsorted), false);
|
||||
+ this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false);
|
||||
+ return removed; // CraftBukkit
|
||||
}
|
||||
|
||||
public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T argument) {
|
||||
@@ -202,19 +214,33 @@
|
||||
}
|
||||
|
||||
public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
|
||||
- Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument);
|
||||
- long j = pos.toLong();
|
||||
+ // CraftBukkit start
|
||||
+ this.addRegionTicketAtDistance(type, pos, radius, argument);
|
||||
+ }
|
||||
|
||||
- this.addTicket(j, ticket);
|
||||
+ public <T> boolean addRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
|
||||
+ // CraftBukkit end
|
||||
+ Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
|
||||
+ long j = chunkcoordintpair.toLong();
|
||||
+
|
||||
+ boolean added = this.addTicket(j, ticket); // CraftBukkit
|
||||
this.tickingTicketsTracker.addTicket(j, ticket);
|
||||
+ return added; // CraftBukkit
|
||||
}
|
||||
|
||||
public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
|
||||
- Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument);
|
||||
- long j = pos.toLong();
|
||||
+ // CraftBukkit start
|
||||
+ this.removeRegionTicketAtDistance(type, pos, radius, argument);
|
||||
+ }
|
||||
|
||||
- this.removeTicket(j, ticket);
|
||||
+ public <T> boolean removeRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
|
||||
+ // CraftBukkit end
|
||||
+ Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
|
||||
+ long j = chunkcoordintpair.toLong();
|
||||
+
|
||||
+ boolean removed = this.removeTicket(j, ticket); // CraftBukkit
|
||||
this.tickingTicketsTracker.removeTicket(j, ticket);
|
||||
+ return removed; // CraftBukkit
|
||||
}
|
||||
|
||||
private SortedArraySet<Ticket<?>> getTickets(long position) {
|
||||
@@ -253,9 +279,10 @@
|
||||
ChunkPos chunkcoordintpair = pos.chunk();
|
||||
long i = chunkcoordintpair.toLong();
|
||||
ObjectSet<ServerPlayer> objectset = (ObjectSet) this.playersPerChunk.get(i);
|
||||
+ if (objectset == null) return; // CraftBukkit - SPIGOT-6208
|
||||
|
||||
- objectset.remove(player);
|
||||
- if (objectset.isEmpty()) {
|
||||
+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
|
||||
+ if (objectset == null || objectset.isEmpty()) { // Paper
|
||||
this.playersPerChunk.remove(i);
|
||||
this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
|
||||
this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
|
||||
@@ -358,7 +385,7 @@
|
||||
}
|
||||
|
||||
public void removeTicketsOnClosing() {
|
||||
- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN);
|
||||
+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
|
||||
ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
|
||||
|
||||
while (objectiterator.hasNext()) {
|
||||
@@ -389,7 +416,27 @@
|
||||
|
||||
public boolean hasTickets() {
|
||||
return !this.tickets.isEmpty();
|
||||
+ }
|
||||
+
|
||||
+ // CraftBukkit start
|
||||
+ public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
|
||||
+ Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
|
||||
+
|
||||
+ for (java.util.Iterator<Entry<SortedArraySet<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
||||
+ Entry<SortedArraySet<Ticket<?>>> entry = iterator.next();
|
||||
+ SortedArraySet<Ticket<?>> tickets = entry.getValue();
|
||||
+ if (tickets.remove(target)) {
|
||||
+ // copied from removeTicket
|
||||
+ this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
|
||||
+
|
||||
+ // can't use entry after it's removed
|
||||
+ if (tickets.isEmpty()) {
|
||||
+ iterator.remove();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
+ // CraftBukkit end
|
||||
|
||||
private class ChunkTicketTracker extends ChunkTracker {
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
--- a/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -74,6 +74,13 @@
|
||||
@Nullable
|
||||
@VisibleForDebug
|
||||
private NaturalSpawner.SpawnState lastSpawnState;
|
||||
+ // Paper start
|
||||
+ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
|
||||
+ public int getFullChunksCount() {
|
||||
+ return this.fullChunks.size();
|
||||
+ }
|
||||
+ long chunkFutureAwaitCounter;
|
||||
+ // Paper end
|
||||
|
||||
public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
|
||||
this.level = world;
|
||||
@@ -95,6 +102,64 @@
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
+ // CraftBukkit start - properly implement isChunkLoaded
|
||||
+ public boolean isChunkLoaded(int chunkX, int chunkZ) {
|
||||
+ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(ChunkPos.asLong(chunkX, chunkZ));
|
||||
+ if (chunk == null) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return chunk.getFullChunkNow() != null;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+ // Paper start
|
||||
+ public void addLoadedChunk(LevelChunk chunk) {
|
||||
+ this.fullChunks.put(chunk.coordinateKey, chunk);
|
||||
+ }
|
||||
+
|
||||
+ public void removeLoadedChunk(LevelChunk chunk) {
|
||||
+ this.fullChunks.remove(chunk.coordinateKey);
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ public ChunkAccess getChunkAtImmediately(int x, int z) {
|
||||
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
|
||||
+ if (holder == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ return holder.getLatestChunk();
|
||||
+ }
|
||||
+
|
||||
+ public <T> void addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
|
||||
+ this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier);
|
||||
+ }
|
||||
+
|
||||
+ public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
|
||||
+ this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier);
|
||||
+ }
|
||||
+
|
||||
+ // "real" get chunk if loaded
|
||||
+ // Note: Partially copied from the getChunkAt method below
|
||||
+ @Nullable
|
||||
+ public LevelChunk getChunkAtIfCachedImmediately(int x, int z) {
|
||||
+ long k = ChunkPos.asLong(x, z);
|
||||
+
|
||||
+ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
|
||||
+
|
||||
+ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k);
|
||||
+ if (playerChunk == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ return playerChunk.getFullChunkNowUnchecked();
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
|
||||
+ return this.fullChunks.get(ChunkPos.asLong(x, z));
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
@Override
|
||||
public ThreadedLevelLightEngine getLightEngine() {
|
||||
return this.lightEngine;
|
||||
@@ -138,7 +203,7 @@
|
||||
if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) {
|
||||
ChunkAccess ichunkaccess = this.lastChunk[l];
|
||||
|
||||
- if (ichunkaccess != null || !create) {
|
||||
+ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
|
||||
return ichunkaccess;
|
||||
}
|
||||
}
|
||||
@@ -150,8 +215,9 @@
|
||||
|
||||
Objects.requireNonNull(completablefuture);
|
||||
chunkproviderserver_b.managedBlock(completablefuture::isDone);
|
||||
+ // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
|
||||
ChunkResult<ChunkAccess> chunkresult = (ChunkResult) completablefuture.join();
|
||||
- ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse((Object) null);
|
||||
+ ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
|
||||
|
||||
if (ichunkaccess1 == null && create) {
|
||||
throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkresult.getError()));
|
||||
@@ -231,7 +297,15 @@
|
||||
int l = ChunkLevel.byStatus(leastStatus);
|
||||
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
|
||||
|
||||
- if (create) {
|
||||
+ // CraftBukkit start - don't add new ticket for currently unloading chunk
|
||||
+ boolean currentlyUnloading = false;
|
||||
+ if (playerchunk != null) {
|
||||
+ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel);
|
||||
+ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel());
|
||||
+ currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
|
||||
+ }
|
||||
+ if (create && !currentlyUnloading) {
|
||||
+ // CraftBukkit end
|
||||
this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
|
||||
if (this.chunkAbsent(playerchunk, l)) {
|
||||
ProfilerFiller gameprofilerfiller = Profiler.get();
|
||||
@@ -250,7 +324,7 @@
|
||||
}
|
||||
|
||||
private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
|
||||
- return holder == null || holder.getTicketLevel() > maxLevel;
|
||||
+ return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -279,7 +353,7 @@
|
||||
return this.mainThreadProcessor.pollTask();
|
||||
}
|
||||
|
||||
- boolean runDistanceManagerUpdates() {
|
||||
+ public boolean runDistanceManagerUpdates() { // Paper - public
|
||||
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
|
||||
boolean flag1 = this.chunkMap.promoteChunkMap();
|
||||
|
||||
@@ -309,18 +383,40 @@
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
- this.save(true);
|
||||
+ // CraftBukkit start
|
||||
+ this.close(true);
|
||||
+ }
|
||||
+
|
||||
+ public void close(boolean save) throws IOException {
|
||||
+ if (save) {
|
||||
+ this.save(true);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
this.dataStorage.close();
|
||||
this.lightEngine.close();
|
||||
this.chunkMap.close();
|
||||
}
|
||||
|
||||
+ // CraftBukkit start - modelled on below
|
||||
+ public void purgeUnload() {
|
||||
+ ProfilerFiller gameprofilerfiller = Profiler.get();
|
||||
+
|
||||
+ gameprofilerfiller.push("purge");
|
||||
+ this.distanceManager.purgeStaleTickets();
|
||||
+ this.runDistanceManagerUpdates();
|
||||
+ gameprofilerfiller.popPush("unload");
|
||||
+ this.chunkMap.tick(() -> true);
|
||||
+ gameprofilerfiller.pop();
|
||||
+ this.clearCache();
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
@Override
|
||||
public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) {
|
||||
ProfilerFiller gameprofilerfiller = Profiler.get();
|
||||
|
||||
gameprofilerfiller.push("purge");
|
||||
- if (this.level.tickRateManager().runsNormally() || !tickChunks) {
|
||||
+ if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot
|
||||
this.distanceManager.purgeStaleTickets();
|
||||
}
|
||||
|
||||
@@ -401,14 +497,22 @@
|
||||
|
||||
this.lastSpawnState = spawnercreature_d;
|
||||
profiler.popPush("spawnAndTick");
|
||||
- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
|
||||
+ boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
|
||||
int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
|
||||
List list1;
|
||||
|
||||
if (flag && (this.spawnEnemies || this.spawnFriendlies)) {
|
||||
- boolean flag1 = this.level.getLevelData().getGameTime() % 400L == 0L;
|
||||
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
|
||||
+ for (ServerPlayer entityPlayer : this.level.players()) {
|
||||
+ int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
|
||||
+ chunkRange = Math.min(chunkRange, 8);
|
||||
+ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
|
||||
+ entityPlayer.playerNaturallySpawnedEvent.callEvent();
|
||||
+ }
|
||||
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
|
||||
+ boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
|
||||
|
||||
- list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
|
||||
+ list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit
|
||||
} else {
|
||||
list1 = List.of();
|
||||
}
|
||||
@@ -420,7 +524,7 @@
|
||||
ChunkPos chunkcoordintpair = chunk.getPos();
|
||||
|
||||
chunk.incrementInhabitedTime(timeDelta);
|
||||
- if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) {
|
||||
+ if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
|
||||
NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1);
|
||||
}
|
||||
|
||||
@@ -541,10 +645,16 @@
|
||||
|
||||
@Override
|
||||
public void setSpawnSettings(boolean spawnMonsters) {
|
||||
- this.spawnEnemies = spawnMonsters;
|
||||
- this.spawnFriendlies = this.spawnFriendlies;
|
||||
+ // CraftBukkit start
|
||||
+ this.setSpawnSettings(spawnMonsters, this.spawnFriendlies);
|
||||
}
|
||||
|
||||
+ public void setSpawnSettings(boolean flag, boolean spawnFriendlies) {
|
||||
+ this.spawnEnemies = flag;
|
||||
+ this.spawnFriendlies = spawnFriendlies;
|
||||
+ // CraftBukkit end
|
||||
+ }
|
||||
+
|
||||
public String getChunkDebugData(ChunkPos pos) {
|
||||
return this.chunkMap.getChunkDebugData(pos);
|
||||
}
|
||||
@@ -618,14 +728,20 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
- protected boolean pollTask() {
|
||||
+ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
||||
+ public boolean pollTask() {
|
||||
+ try {
|
||||
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
|
||||
return true;
|
||||
} else {
|
||||
ServerChunkCache.this.lightEngine.tryScheduleUpdate();
|
||||
return super.pollTask();
|
||||
}
|
||||
+ } finally {
|
||||
+ ServerChunkCache.this.chunkMap.callbackExecutor.run();
|
||||
}
|
||||
+ // CraftBukkit end
|
||||
+ }
|
||||
}
|
||||
|
||||
private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
|
||||
@@ -0,0 +1,179 @@
|
||||
--- a/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -31,7 +31,6 @@
|
||||
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
|
||||
import net.minecraft.network.protocol.game.VecDeltaCodec;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
-import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.Leashable;
|
||||
@@ -50,6 +49,13 @@
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
+// CraftBukkit start
|
||||
+import net.minecraft.server.network.ServerPlayerConnection;
|
||||
+import net.minecraft.util.Mth;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.bukkit.event.player.PlayerVelocityEvent;
|
||||
+// CraftBukkit end
|
||||
+
|
||||
public class ServerEntity {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -69,18 +75,22 @@
|
||||
private Vec3 lastSentMovement;
|
||||
private int tickCount;
|
||||
private int teleportDelay;
|
||||
- private List<Entity> lastPassengers = Collections.emptyList();
|
||||
+ private List<Entity> lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks
|
||||
private boolean wasRiding;
|
||||
private boolean wasOnGround;
|
||||
@Nullable
|
||||
private List<SynchedEntityData.DataValue<?>> trackedDataValues;
|
||||
+ // CraftBukkit start
|
||||
+ private final Set<ServerPlayerConnection> trackedPlayers;
|
||||
|
||||
- public ServerEntity(ServerLevel world, Entity entity, int tickInterval, boolean alwaysUpdateVelocity, Consumer<Packet<?>> receiver) {
|
||||
- this.level = world;
|
||||
- this.broadcast = receiver;
|
||||
+ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayerConnection> trackedPlayers) {
|
||||
+ this.trackedPlayers = trackedPlayers;
|
||||
+ // CraftBukkit end
|
||||
+ this.level = worldserver;
|
||||
+ this.broadcast = consumer;
|
||||
this.entity = entity;
|
||||
- this.updateInterval = tickInterval;
|
||||
- this.trackDelta = alwaysUpdateVelocity;
|
||||
+ this.updateInterval = i;
|
||||
+ this.trackDelta = flag;
|
||||
this.positionCodec.setBase(entity.trackingPosition());
|
||||
this.lastSentMovement = entity.getDeltaMovement();
|
||||
this.lastSentYRot = Mth.packDegrees(entity.getYRot());
|
||||
@@ -94,7 +104,7 @@
|
||||
List<Entity> list = this.entity.getPassengers();
|
||||
|
||||
if (!list.equals(this.lastPassengers)) {
|
||||
- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
|
||||
+ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
|
||||
ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> {
|
||||
if (entity instanceof ServerPlayer entityplayer) {
|
||||
entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
|
||||
@@ -106,19 +116,19 @@
|
||||
|
||||
Entity entity = this.entity;
|
||||
|
||||
- if (entity instanceof ItemFrame entityitemframe) {
|
||||
- if (this.tickCount % 10 == 0) {
|
||||
+ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it
|
||||
+ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
|
||||
ItemStack itemstack = entityitemframe.getItem();
|
||||
|
||||
- if (itemstack.getItem() instanceof MapItem) {
|
||||
- MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID);
|
||||
+ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
|
||||
+ MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames
|
||||
MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level);
|
||||
|
||||
if (worldmap != null) {
|
||||
- Iterator iterator = this.level.players().iterator();
|
||||
+ Iterator<ServerPlayerConnection> iterator = this.trackedPlayers.iterator(); // CraftBukkit
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
- ServerPlayer entityplayer = (ServerPlayer) iterator.next();
|
||||
+ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit
|
||||
|
||||
worldmap.tickCarriedBy(entityplayer, itemstack);
|
||||
Packet<?> packet = worldmap.getUpdatePacket(mapid, entityplayer);
|
||||
@@ -168,7 +178,13 @@
|
||||
|
||||
++this.teleportDelay;
|
||||
Vec3 vec3d = this.entity.trackingPosition();
|
||||
- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D;
|
||||
+ // Paper start - reduce allocation of Vec3D here
|
||||
+ Vec3 base = this.positionCodec.base;
|
||||
+ double vec3d_dx = vec3d.x - base.x;
|
||||
+ double vec3d_dy = vec3d.y - base.y;
|
||||
+ double vec3d_dz = vec3d.z - base.z;
|
||||
+ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D;
|
||||
+ // Paper end - reduce allocation of Vec3D here
|
||||
Packet<?> packet1 = null;
|
||||
boolean flag2 = flag1 || this.tickCount % 60 == 0;
|
||||
boolean flag3 = false;
|
||||
@@ -248,6 +264,27 @@
|
||||
|
||||
++this.tickCount;
|
||||
if (this.entity.hurtMarked) {
|
||||
+ // CraftBukkit start - Create PlayerVelocity event
|
||||
+ boolean cancelled = false;
|
||||
+
|
||||
+ if (this.entity instanceof ServerPlayer) {
|
||||
+ Player player = (Player) this.entity.getBukkitEntity();
|
||||
+ org.bukkit.util.Vector velocity = player.getVelocity();
|
||||
+
|
||||
+ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
|
||||
+ this.entity.level().getCraftServer().getPluginManager().callEvent(event);
|
||||
+
|
||||
+ if (event.isCancelled()) {
|
||||
+ cancelled = true;
|
||||
+ } else if (!velocity.equals(event.getVelocity())) {
|
||||
+ player.setVelocity(event.getVelocity());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (cancelled) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
this.entity.hurtMarked = false;
|
||||
this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
|
||||
}
|
||||
@@ -298,7 +335,10 @@
|
||||
|
||||
public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> sender) {
|
||||
if (this.entity.isRemoved()) {
|
||||
- ServerEntity.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
|
||||
+ // CraftBukkit start - Remove useless error spam, just return
|
||||
+ // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
|
||||
+ return;
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
|
||||
Packet<ClientGamePacketListener> packet = this.entity.getAddEntityPacket(this);
|
||||
@@ -313,6 +353,12 @@
|
||||
if (this.entity instanceof LivingEntity) {
|
||||
Collection<AttributeInstance> collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes();
|
||||
|
||||
+ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
|
||||
+ if (this.entity.getId() == player.getId()) {
|
||||
+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
if (!collection.isEmpty()) {
|
||||
sender.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection));
|
||||
}
|
||||
@@ -342,8 +388,9 @@
|
||||
}
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
- sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
|
||||
+ sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization
|
||||
}
|
||||
+ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
|
||||
}
|
||||
|
||||
if (!this.entity.getPassengers().isEmpty()) {
|
||||
@@ -396,6 +443,11 @@
|
||||
Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync();
|
||||
|
||||
if (!set.isEmpty()) {
|
||||
+ // CraftBukkit start - Send scaled max health
|
||||
+ if (this.entity instanceof ServerPlayer) {
|
||||
+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,463 @@
|
||||
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
|
||||
@@ -13,6 +13,7 @@
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
+import net.minecraft.world.item.DoubleHighBlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
@@ -20,12 +21,30 @@
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.GameMasterBlock;
|
||||
+import net.minecraft.world.level.block.TrapDoorBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
+import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
+// CraftBukkit start
|
||||
+import java.util.ArrayList;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+import net.minecraft.world.level.block.Blocks;
|
||||
+import net.minecraft.world.level.block.CakeBlock;
|
||||
+import net.minecraft.world.level.block.DoorBlock;
|
||||
+import org.bukkit.GameMode;
|
||||
+import org.bukkit.craftbukkit.block.CraftBlock;
|
||||
+import org.bukkit.event.block.BlockBreakEvent;
|
||||
+import org.bukkit.craftbukkit.event.CraftEventFactory;
|
||||
+import org.bukkit.event.Event;
|
||||
+import org.bukkit.event.block.Action;
|
||||
+import org.bukkit.event.player.PlayerGameModeChangeEvent;
|
||||
+import org.bukkit.event.player.PlayerInteractEvent;
|
||||
+// CraftBukkit end
|
||||
+
|
||||
public class ServerPlayerGameMode {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -42,6 +61,8 @@
|
||||
private BlockPos delayedDestroyPos;
|
||||
private int delayedTickStart;
|
||||
private int lastSentState;
|
||||
+ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction
|
||||
+ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction
|
||||
|
||||
public ServerPlayerGameMode(ServerPlayer player) {
|
||||
this.gameModeForPlayer = GameType.DEFAULT_MODE;
|
||||
@@ -53,18 +74,32 @@
|
||||
}
|
||||
|
||||
public boolean changeGameModeForPlayer(GameType gameMode) {
|
||||
+ // Paper start - Expand PlayerGameModeChangeEvent
|
||||
+ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
|
||||
+ return event != null && event.isCancelled();
|
||||
+ }
|
||||
+ @Nullable
|
||||
+ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
|
||||
+ // Paper end - Expand PlayerGameModeChangeEvent
|
||||
if (gameMode == this.gameModeForPlayer) {
|
||||
- return false;
|
||||
+ return null; // Paper - Expand PlayerGameModeChangeEvent
|
||||
} else {
|
||||
- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer);
|
||||
+ // CraftBukkit start
|
||||
+ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper
|
||||
+ this.level.getCraftServer().getPluginManager().callEvent(event);
|
||||
+ if (event.isCancelled()) {
|
||||
+ return event; // Paper - Expand PlayerGameModeChangeEvent
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571
|
||||
this.player.onUpdateAbilities();
|
||||
- this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
|
||||
+ this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit
|
||||
this.level.updateSleepingPlayerList();
|
||||
if (gameMode == GameType.CREATIVE) {
|
||||
this.player.resetCurrentImpulseContext();
|
||||
}
|
||||
|
||||
- return true;
|
||||
+ return event; // Paper - Expand PlayerGameModeChangeEvent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +127,12 @@
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
- ++this.gameTicks;
|
||||
+ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
|
||||
BlockState iblockdata;
|
||||
|
||||
if (this.hasDelayedDestroy) {
|
||||
- iblockdata = this.level.getBlockState(this.delayedDestroyPos);
|
||||
- if (iblockdata.isAir()) {
|
||||
+ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
|
||||
+ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks
|
||||
this.hasDelayedDestroy = false;
|
||||
} else {
|
||||
float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart);
|
||||
@@ -108,7 +143,13 @@
|
||||
}
|
||||
}
|
||||
} else if (this.isDestroyingBlock) {
|
||||
- iblockdata = this.level.getBlockState(this.destroyPos);
|
||||
+ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead
|
||||
+ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos);
|
||||
+ if (iblockdata == null) {
|
||||
+ this.isDestroyingBlock = false;
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - Don't allow digging into unloaded chunks
|
||||
if (iblockdata.isAir()) {
|
||||
this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
|
||||
this.lastSentState = -1;
|
||||
@@ -137,6 +178,7 @@
|
||||
|
||||
public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
|
||||
if (!this.player.canInteractWithBlock(pos, 1.0D)) {
|
||||
+ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away
|
||||
this.debugLogging(pos, false, sequence, "too far");
|
||||
} else if (pos.getY() > worldHeight) {
|
||||
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
||||
@@ -146,16 +188,40 @@
|
||||
|
||||
if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
|
||||
if (!this.level.mayInteract(this.player, pos)) {
|
||||
+ // CraftBukkit start - fire PlayerInteractEvent
|
||||
+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
|
||||
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
||||
this.debugLogging(pos, false, sequence, "may not interact");
|
||||
+ // Update any tile entity data for this block
|
||||
+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
|
||||
+ // CraftBukkit end
|
||||
return;
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
|
||||
+ if (event.isCancelled()) {
|
||||
+ // Let the client know the block still exists
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
|
||||
+ // Update any tile entity data for this block
|
||||
+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
|
||||
+ return;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
if (this.isCreative()) {
|
||||
this.destroyAndAck(pos, sequence, "creative destroy");
|
||||
return;
|
||||
}
|
||||
|
||||
+ // Spigot start - handle debug stick left click for non-creative
|
||||
+ if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK)
|
||||
+ && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) {
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
|
||||
+ return;
|
||||
+ }
|
||||
+ // Spigot end
|
||||
+
|
||||
if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
|
||||
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
|
||||
this.debugLogging(pos, false, sequence, "block action restricted");
|
||||
@@ -166,7 +232,21 @@
|
||||
float f = 1.0F;
|
||||
|
||||
iblockdata = this.level.getBlockState(pos);
|
||||
- if (!iblockdata.isAir()) {
|
||||
+ // CraftBukkit start - Swings at air do *NOT* exist.
|
||||
+ if (event.useInteractedBlock() == Event.Result.DENY) {
|
||||
+ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
|
||||
+ // Paper start - Don't resync blocks
|
||||
+ //BlockState data = this.level.getBlockState(pos);
|
||||
+ //if (data.getBlock() instanceof DoorBlock) {
|
||||
+ // // For some reason *BOTH* the bottom/top part have to be marked updated.
|
||||
+ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below()));
|
||||
+ //} else if (data.getBlock() instanceof TrapDoorBlock) {
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
|
||||
+ //}
|
||||
+ // Paper end - Don't resync blocks
|
||||
+ } else if (!iblockdata.isAir()) {
|
||||
EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> {
|
||||
this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND);
|
||||
});
|
||||
@@ -174,6 +254,26 @@
|
||||
f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos);
|
||||
}
|
||||
|
||||
+ if (event.useItemInHand() == Event.Result.DENY) {
|
||||
+ // If we 'insta destroyed' then the client needs to be informed.
|
||||
+ if (f > 1.0f) {
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent
|
||||
+
|
||||
+ if (blockEvent.isCancelled()) {
|
||||
+ // Let the client know the block still exists
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (blockEvent.getInstaBreak()) {
|
||||
+ f = 2.0f;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
if (!iblockdata.isAir() && f >= 1.0F) {
|
||||
this.destroyAndAck(pos, sequence, "insta mine");
|
||||
} else {
|
||||
@@ -217,14 +317,18 @@
|
||||
this.debugLogging(pos, true, sequence, "stopped destroying");
|
||||
} else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
|
||||
this.isDestroyingBlock = false;
|
||||
- if (!Objects.equals(this.destroyPos, pos)) {
|
||||
- ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos);
|
||||
- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
|
||||
- this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
|
||||
+ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper
|
||||
+ ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled
|
||||
+ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here
|
||||
+ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
|
||||
+ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
|
||||
+ this.destroyPos = BlockPos.ZERO; // Paper
|
||||
}
|
||||
|
||||
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
|
||||
this.debugLogging(pos, true, sequence, "aborted destroying");
|
||||
+
|
||||
+ CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
|
||||
}
|
||||
|
||||
}
|
||||
@@ -242,19 +346,82 @@
|
||||
|
||||
public boolean destroyBlock(BlockPos pos) {
|
||||
BlockState iblockdata = this.level.getBlockState(pos);
|
||||
+ // CraftBukkit start - fire BlockBreakEvent
|
||||
+ org.bukkit.block.Block bblock = CraftBlock.at(this.level, pos);
|
||||
+ BlockBreakEvent event = null;
|
||||
|
||||
- if (!this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) {
|
||||
+ if (this.player instanceof ServerPlayer) {
|
||||
+ // Sword + Creative mode pre-cancel
|
||||
+ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player);
|
||||
+
|
||||
+ // Tell client the block is gone immediately then process events
|
||||
+ // Don't tell the client if its a creative sword break because its not broken!
|
||||
+ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block
|
||||
+ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState());
|
||||
+ this.player.connection.send(packet);
|
||||
+ }
|
||||
+
|
||||
+ event = new BlockBreakEvent(bblock, this.player.getBukkitEntity());
|
||||
+
|
||||
+ // Sword + Creative mode pre-cancel
|
||||
+ event.setCancelled(isSwordNoBreak);
|
||||
+
|
||||
+ // Calculate default block experience
|
||||
+ BlockState nmsData = this.level.getBlockState(pos);
|
||||
+ Block nmsBlock = nmsData.getBlock();
|
||||
+
|
||||
+ ItemStack itemstack = this.player.getItemBySlot(EquipmentSlot.MAINHAND);
|
||||
+
|
||||
+ if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(nmsBlock.defaultBlockState())) {
|
||||
+ event.setExpToDrop(nmsBlock.getExpDrop(nmsData, this.level, pos, itemstack, true));
|
||||
+ }
|
||||
+
|
||||
+ this.level.getCraftServer().getPluginManager().callEvent(event);
|
||||
+
|
||||
+ if (event.isCancelled()) {
|
||||
+ if (isSwordNoBreak) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ // Paper start - Don't resync blocks
|
||||
+ // Let the client know the block still exists
|
||||
+ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
|
||||
+
|
||||
+ // Brute force all possible updates
|
||||
+ //for (Direction dir : Direction.values()) {
|
||||
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir)));
|
||||
+ //}
|
||||
+ // Paper end - Don't resync blocks
|
||||
+
|
||||
+ // Update any tile entity data for this block
|
||||
+ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction
|
||||
+ BlockEntity tileentity = this.level.getBlockEntity(pos);
|
||||
+ if (tileentity != null) {
|
||||
+ this.player.connection.send(tileentity.getUpdatePacket());
|
||||
+ }
|
||||
+ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
+ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false
|
||||
return false;
|
||||
} else {
|
||||
+ iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins
|
||||
+ if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling
|
||||
BlockEntity tileentity = this.level.getBlockEntity(pos);
|
||||
Block block = iblockdata.getBlock();
|
||||
|
||||
- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
|
||||
+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
|
||||
this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3);
|
||||
return false;
|
||||
} else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
|
||||
return false;
|
||||
} else {
|
||||
+ // CraftBukkit start
|
||||
+ org.bukkit.block.BlockState state = bblock.getState();
|
||||
+ this.level.captureDrops = new ArrayList<>();
|
||||
+ // CraftBukkit end
|
||||
BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player);
|
||||
boolean flag = this.level.removeBlock(pos, false);
|
||||
|
||||
@@ -262,20 +429,46 @@
|
||||
block.destroy(this.level, pos, iblockdata1);
|
||||
}
|
||||
|
||||
+ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place
|
||||
+ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place
|
||||
if (this.isCreative()) {
|
||||
- return true;
|
||||
+ // return true; // CraftBukkit
|
||||
} else {
|
||||
ItemStack itemstack = this.player.getMainHandItem();
|
||||
ItemStack itemstack1 = itemstack.copy();
|
||||
boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
|
||||
+ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
|
||||
+ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
|
||||
|
||||
itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
|
||||
- if (flag && flag1) {
|
||||
- block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1);
|
||||
+ if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
|
||||
+ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion
|
||||
}
|
||||
|
||||
- return true;
|
||||
+ // return true; // CraftBukkit
|
||||
}
|
||||
+ // CraftBukkit start
|
||||
+ java.util.List<net.minecraft.world.entity.item.ItemEntity> itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world
|
||||
+ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff
|
||||
+ if (event.isDropItems()) {
|
||||
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world
|
||||
+ }
|
||||
+ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up
|
||||
+
|
||||
+ // Drop event experience
|
||||
+ if (flag && event != null) {
|
||||
+ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
|
||||
+ }
|
||||
+ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy)
|
||||
+ if (mainHandStack != null) {
|
||||
+ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above
|
||||
+ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount());
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - Trigger bee_nest_destroyed trigger in the correct place
|
||||
+
|
||||
+ return true;
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,17 +514,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ // CraftBukkit start - whole method
|
||||
+ public boolean interactResult = false;
|
||||
+ public boolean firedInteract = false;
|
||||
+ public BlockPos interactPosition;
|
||||
+ public InteractionHand interactHand;
|
||||
+ public ItemStack interactItemStack;
|
||||
public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
|
||||
BlockPos blockposition = hitResult.getBlockPos();
|
||||
BlockState iblockdata = world.getBlockState(blockposition);
|
||||
+ boolean cancelledBlock = false;
|
||||
+ boolean cancelledItem = false; // Paper - correctly handle items on cooldown
|
||||
|
||||
if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) {
|
||||
return InteractionResult.FAIL;
|
||||
} else if (this.gameModeForPlayer == GameType.SPECTATOR) {
|
||||
MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition);
|
||||
+ cancelledBlock = !(itileinventory instanceof MenuProvider);
|
||||
+ }
|
||||
|
||||
- if (itileinventory != null) {
|
||||
- player.openMenu(itileinventory);
|
||||
+ if (player.getCooldowns().isOnCooldown(stack)) {
|
||||
+ cancelledItem = true; // Paper - correctly handle items on cooldown
|
||||
+ }
|
||||
+
|
||||
+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown
|
||||
+ this.firedInteract = true;
|
||||
+ this.interactResult = event.useItemInHand() == Event.Result.DENY;
|
||||
+ this.interactPosition = blockposition.immutable();
|
||||
+ this.interactHand = hand;
|
||||
+ this.interactItemStack = stack.copy();
|
||||
+
|
||||
+ if (event.useInteractedBlock() == Event.Result.DENY) {
|
||||
+ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
|
||||
+ if (iblockdata.getBlock() instanceof DoorBlock) {
|
||||
+ // Paper start - Don't resync blocks
|
||||
+ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
|
||||
+ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below()));
|
||||
+ // Paper end - Don't resync blocks
|
||||
+ } else if (iblockdata.getBlock() instanceof CakeBlock) {
|
||||
+ player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake
|
||||
+ } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) {
|
||||
+ // send a correcting update to the client, as it already placed the upper half of the bisected item
|
||||
+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks
|
||||
+
|
||||
+ // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc)
|
||||
+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks
|
||||
+ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method
|
||||
+ } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) {
|
||||
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId));
|
||||
+ }
|
||||
+ // Paper end - extend Player Interact cancellation
|
||||
+ player.getBukkitEntity().updateInventory(); // SPIGOT-2867
|
||||
+ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items
|
||||
+ return (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS;
|
||||
+ } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
|
||||
+ MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition);
|
||||
+
|
||||
+ if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
|
||||
return InteractionResult.CONSUME;
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
@@ -359,7 +598,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) {
|
||||
+ if (!stack.isEmpty() && !this.interactResult) { // add !interactResult SPIGOT-764
|
||||
UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult);
|
||||
|
||||
if (this.isCreative()) {
|
||||
@@ -377,6 +616,11 @@
|
||||
|
||||
return enuminteractionresult;
|
||||
} else {
|
||||
+ // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response
|
||||
+ if (this.interactResult && this.interactResult != cancelledItem) {
|
||||
+ this.player.resyncUsingItem(this.player);
|
||||
+ }
|
||||
+ // Paper end - Properly cancel usable items
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
--- a/net/minecraft/server/level/TicketType.java
|
||||
+++ b/net/minecraft/server/level/TicketType.java
|
||||
@@ -7,6 +7,7 @@
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
|
||||
public class TicketType<T> {
|
||||
+ public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
|
||||
|
||||
private final String name;
|
||||
private final Comparator<T> comparator;
|
||||
@@ -22,6 +23,9 @@
|
||||
public static final TicketType<BlockPos> PORTAL = TicketType.create("portal", Vec3i::compareTo, 300);
|
||||
public static final TicketType<ChunkPos> ENDER_PEARL = TicketType.create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40);
|
||||
public static final TicketType<ChunkPos> UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
|
||||
+ public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
|
||||
+ public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
|
||||
+ public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type
|
||||
|
||||
public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
|
||||
return new TicketType<>(name, argumentComparator, 0L);
|
||||
@@ -0,0 +1,105 @@
|
||||
--- a/net/minecraft/server/level/WorldGenRegion.java
|
||||
+++ b/net/minecraft/server/level/WorldGenRegion.java
|
||||
@@ -167,7 +167,27 @@
|
||||
int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
|
||||
|
||||
return k < this.generatingStep.directDependencies().size();
|
||||
+ }
|
||||
+
|
||||
+ // Paper start - if loaded util
|
||||
+ @Nullable
|
||||
+ @Override
|
||||
+ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
||||
+ return this.getChunk(x, z, ChunkStatus.FULL, false);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
||||
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
||||
+ return chunk == null ? null : chunk.getBlockState(blockposition);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
||||
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
||||
+ return chunk == null ? null : chunk.getFluidState(blockposition);
|
||||
}
|
||||
+ // Paper end
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
@@ -217,7 +237,8 @@
|
||||
if (iblockdata.isAir()) {
|
||||
return false;
|
||||
} else {
|
||||
- if (drop) {
|
||||
+ if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens
|
||||
+ if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation
|
||||
BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
|
||||
|
||||
Block.dropResources(iblockdata, this.level, pos, tileentity, breakingEntity, ItemStack.EMPTY);
|
||||
@@ -264,6 +285,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls
|
||||
@Override
|
||||
public boolean ensureCanWrite(BlockPos pos) {
|
||||
int i = SectionPos.blockToSectionCoord(pos.getX());
|
||||
@@ -283,7 +305,15 @@
|
||||
|
||||
return true;
|
||||
} else {
|
||||
+ // Paper start - Buffer OOB setBlock calls
|
||||
+ if (!hasSetFarWarned) {
|
||||
Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get()));
|
||||
+ hasSetFarWarned = true;
|
||||
+ if (this.getServer() != null && this.getServer().isDebugging()) {
|
||||
+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call");
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - Buffer OOB setBlock calls
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -294,7 +324,7 @@
|
||||
return false;
|
||||
} else {
|
||||
ChunkAccess ichunkaccess = this.getChunk(pos);
|
||||
- BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false);
|
||||
+ BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper
|
||||
|
||||
if (iblockdata1 != null) {
|
||||
this.level.onBlockStateChange(pos, iblockdata1, state);
|
||||
@@ -310,6 +340,17 @@
|
||||
ichunkaccess.removeBlockEntity(pos);
|
||||
}
|
||||
} else {
|
||||
+ // Paper start - Clear block entity before setting up a DUMMY block entity
|
||||
+ // The concept of removing a block entity when the block itself changes is generally lifted
|
||||
+ // from LevelChunk#setBlockState.
|
||||
+ // It is however to note that this may only run if the block actually changes.
|
||||
+ // Otherwise a chest block entity generated by a structure template that is later "updated" to
|
||||
+ // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750)
|
||||
+ // This logic is *also* found in LevelChunk#setBlockState.
|
||||
+ if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) {
|
||||
+ ichunkaccess.removeBlockEntity(pos);
|
||||
+ }
|
||||
+ // Paper end - Clear block entity before setting up a DUMMY block entity
|
||||
CompoundTag nbttagcompound = new CompoundTag();
|
||||
|
||||
nbttagcompound.putInt("x", pos.getX());
|
||||
@@ -336,6 +377,13 @@
|
||||
|
||||
@Override
|
||||
public boolean addFreshEntity(Entity entity) {
|
||||
+ // CraftBukkit start
|
||||
+ return this.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
|
||||
+ // CraftBukkit end
|
||||
int i = SectionPos.blockToSectionCoord(entity.getBlockX());
|
||||
int j = SectionPos.blockToSectionCoord(entity.getBlockZ());
|
||||
|
||||
Reference in New Issue
Block a user