From 21e0b98494a69295e221207410c5f6aab9caa2fc Mon Sep 17 00:00:00 2001 From: BillyGalbreath Date: Sat, 14 Nov 2020 06:55:31 -0600 Subject: [PATCH] Updated Upstream (Paper & Tuinity) Upstream has released updates that appears to apply and compile correctly Paper Changes: 372dc10f Updated Upstream (CraftBukkit) (#4760) d6fb9717 Updated Upstream (CraftBukkit) Tuinity Changes: 2194b64 Optimizations (starlight, aka leaflight) 1a412ae Fix worldborder crash 957a2ef Fix chunk object leak bcfe550 Re-Add unload poi data patch --- Paper | 2 +- current-paper | 2 +- .../server/0001-Tuinity-Server-Changes.patch | 6163 +++++++++++++++-- patches/server/0009-AFK-API.patch | 26 +- .../0021-Player-invulnerabilities.patch | 18 +- .../0033-Zombie-horse-naturally-spawn.patch | 6 +- .../server/0043-Cat-spawning-options.patch | 16 +- patches/server/0045-Cows-eat-mushrooms.patch | 10 +- .../server/0054-Controllable-Minecarts.patch | 14 +- ...056-Players-should-not-cram-to-death.patch | 4 +- ...0060-Fix-the-dead-lagging-the-server.patch | 4 +- ...-should-not-bypass-cramming-gamerule.patch | 8 +- .../server/0085-Item-entity-immunities.patch | 4 +- ...ed-to-crystals-and-crystals-shoot-ph.patch | 4 +- .../0104-Fix-death-message-colors.patch | 4 +- .../server/0106-Populator-seed-controls.patch | 4 +- .../server/0112-Add-no-tick-block-list.patch | 6 +- ...Stop-squids-floating-on-top-of-water.patch | 4 +- patches/server/0117-Ridables.patch | 20 +- ...ing-obsidian-valid-for-portal-frames.patch | 4 +- ...tities-can-use-portals-configuration.patch | 6 +- .../0134-Configurable-daylight-cycle.patch | 6 +- ...38-Add-tablist-suffix-option-for-afk.patch | 4 +- 23 files changed, 5714 insertions(+), 625 deletions(-) diff --git a/Paper b/Paper index f2606228b..372dc10f9 160000 --- a/Paper +++ b/Paper @@ -1 +1 @@ -Subproject commit f2606228bb13bf29fcc75fbca09a861070cfb67d +Subproject commit 372dc10f9167bb8d1efc04e9f87a044c1846dc1a diff --git a/current-paper b/current-paper index 34ba60362..0a74873d9 100644 --- a/current-paper +++ b/current-paper @@ -1 +1 @@ -1.16.4-- +1.16.4--11496f974814db1c814249f94b605bf82c25822b diff --git a/patches/server/0001-Tuinity-Server-Changes.patch b/patches/server/0001-Tuinity-Server-Changes.patch index 0e33de87c..8529121e4 100644 --- a/patches/server/0001-Tuinity-Server-Changes.patch +++ b/patches/server/0001-Tuinity-Server-Changes.patch @@ -342,6 +342,63 @@ Do not retain playerchunkmap instance in light thread factory The executor returned is finalizable and of course that causes issues. +Rewrite the light engine + +The standard vanilla light engine is plagued by +awful performance. Paper's changes to the light engine +help a bit, however they appear to cause some lighting +errors - most easily noticed in coral generation. + +The vanilla light engine's is too abstract to be modified - +so an entirely new implementation is required to fix the +performance and lighting errors. + +The new implementation is designed primarily to optimise +light level propagations (increase and decrease). Unlike +the vanilla light engine, this implementation tracks more +information per queued value when performing a +breadth first search. Vanilla just tracks coordinate, which +means every time they handle a queued value, they must +also determine the coordinate's target light level +from its neighbours - very wasteful, especially considering +these checks read neighbour block data. +The new light engine tracks both position and target level, +as well as whether the target block needs to be read at all +(for checking sided propagation). So, the work done per coordinate +is significantly reduced because no work is done for calculating +the target level. +In my testing, the block get calls were reduced by approximately +an order of magnitude. However, the light read checks were only +reduced by approximately 2x - but this is fine, light read checks +are extremely cheap compared to block gets. + +Generation testing showed that the new light engine improved +total generation (not lighting itself, but the whole generation process) +by 2x. According to cpu time, the light engine itself spent 10x less time +lighting chunks for generation. + +fixup! Highly optimise single and multi-AABB VoxelShapes and collisions + +Revert Temporarily-Revert-usage-of-Region-Manager + +Optimise WorldServer#notify + +Iterating over all of the navigators in the world is pretty expensive. +Instead, only iterate over navigators in the current region that are +eligible for repathing. + +fixup! Util patch + +fixup! Util patch + +Actually unload POI data + +While it's not likely for a poi data leak to be meaningful, +sometimes it is. + +This patch also prevents the saving/unloading of POI data when +world saving is disabled. + diff --git a/pom.xml b/pom.xml index 80f165291..78c2a8bbc 100644 --- a/pom.xml @@ -470,6 +527,40 @@ index e33e889c2..5dfa06588 100644 )); new TimingsExport(listeners, parent, history).start(); +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index d798de637..a4602ebaa 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -225,8 +225,20 @@ public class PaperCommand extends Command { + updateLight(sender, world, lightengine, queue); + return; + } +- lightengine.a(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) lightengine.a(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue // Tuinity - no longer needed + sender.sendMessage("Updating Light " + coord); ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) { // Tuinity start - replace impl: faster, and it actually works ++ lightengine.relight(coord.x, coord.z, () -> { ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ PlayerChunk visibleChunk = world.getChunkProvider().playerChunkMap.getVisibleChunk(chunk.coordinateKey); ++ if (visibleChunk != null) { ++ visibleChunk.sendPacketToTrackedPlayers(new PacketPlayOutLightUpdate(chunk.getPos(), lightengine, true), false); ++ } ++ updateLight(sender, world, lightengine, queue); ++ }); ++ }); ++ lightengine.queueUpdate(); ++ } else { // Tuinity end - replace impl: faster, and it actually works + int cx = chunk.getPos().x << 4; + int cz = chunk.getPos().z << 4; + for (int y = 0; y < world.getHeight(); y++) { +@@ -250,6 +262,7 @@ public class PaperCommand extends Command { + updateLight(sender, world, lightengine, queue); + } + lightengine.a(world.paperConfig.lightQueueSize); ++ } // Tuinity - replace impl: faster, and it actually works + }, MinecraftServer.getServer()); + } + diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java index 49a38c660..255bbd6e4 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -861,10 +952,10 @@ index 000000000..37428f4b9 +} diff --git a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java new file mode 100644 -index 000000000..4cb10fe69 +index 000000000..cae06962d --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java -@@ -0,0 +1,408 @@ +@@ -0,0 +1,491 @@ +package com.tuinity.tuinity.chunk; + +import co.aikar.timings.MinecraftTimings; @@ -877,18 +968,18 @@ index 000000000..4cb10fe69 +import net.minecraft.server.MCUtil; +import net.minecraft.server.WorldServer; +import java.util.ArrayList; ++import java.util.Arrays; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; -+import java.util.function.LongFunction; + +public final class SingleThreadChunkRegionManager & SingleThreadChunkRegionManager.RegionDataCreator> { + + static final int REGION_SECTION_MERGE_RADIUS = 1; + // if this becomes > 8, then the RegionSection needs to be properly modified (see bitset) -+ public static final int REGION_CHUNK_SIZE = 8; -+ public static final int REGION_CHUNK_SIZE_SHIFT = 3; // log2(REGION_CHUNK_SIZE) ++ public static final int REGION_CHUNK_SIZE = 32; ++ public static final int REGION_CHUNK_SIZE_SHIFT = 5; // log2(REGION_CHUNK_SIZE) + + public final WorldServer world; + public final Class dataClass; @@ -918,6 +1009,23 @@ index 000000000..4cb10fe69 + this.regionRecalculateTimings = MinecraftTimings.getInternalTaskName(prefix.concat("recalculate")); + } + ++ // tested via https://gist.github.com/Spottedleaf/aa7ade3451c37b4cac061fc77074db2f ++ ++ /* ++ protected void check() { ++ ReferenceOpenHashSet> checked = new ReferenceOpenHashSet<>(); ++ ++ for (RegionSection section : this.regionsBySection.values()) { ++ if (!checked.add(section.region)) { ++ section.region.check(); ++ } ++ } ++ for (Region region : this.needsRecalculation) { ++ region.check(); ++ } ++ } ++ */ ++ + protected void addToRecalcQueue(final Region region) { + this.needsRecalculation.add(region); + } @@ -936,20 +1044,26 @@ index 000000000..4cb10fe69 + } + + private final List> toMerge = new ArrayList<>((2 * REGION_SECTION_MERGE_RADIUS + 1) * (2 * REGION_SECTION_MERGE_RADIUS + 1)); -+ protected final LongFunction> createRegionIfAbsent = (final long keyInMap) -> { -+ return new RegionSection<>(keyInMap, SingleThreadChunkRegionManager.this); -+ }; + + protected RegionSection getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection force) { -+ // find optimal candidate to merge into -+ final int minX = sectionX - REGION_SECTION_MERGE_RADIUS; -+ final int maxX = sectionX + REGION_SECTION_MERGE_RADIUS; -+ final int minZ = sectionZ - REGION_SECTION_MERGE_RADIUS; -+ final int maxZ = sectionZ + REGION_SECTION_MERGE_RADIUS; ++ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ); ++ ++ if (force == null) { ++ RegionSection region = this.regionsBySection.get(sectionKey); ++ if (region != null) { ++ return region; ++ } ++ } + + int mergeCandidateSectionSize = -1; + Region mergeIntoCandidate = null; + ++ // find optimal candidate to merge into ++ ++ final int minX = sectionX - REGION_SECTION_MERGE_RADIUS; ++ final int maxX = sectionX + REGION_SECTION_MERGE_RADIUS; ++ final int minZ = sectionZ - REGION_SECTION_MERGE_RADIUS; ++ final int maxZ = sectionZ + REGION_SECTION_MERGE_RADIUS; + for (int currX = minX; currX <= maxX; ++currX) { + for (int currZ = minZ; currZ <= maxZ; ++currZ) { + final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ)); @@ -972,21 +1086,21 @@ index 000000000..4cb10fe69 + + // merge + if (mergeIntoCandidate != null) { -+ for (int len = this.toMerge.size(), i = len - 1; i >= 0; --i) { -+ final Region region = this.toMerge.remove(i); ++ for (int i = 0; i < this.toMerge.size(); ++i) { ++ final Region region = this.toMerge.get(i); + if (region.dead || mergeIntoCandidate == region) { + continue; + } + region.mergeInto(mergeIntoCandidate); + } ++ this.toMerge.clear(); + } else { + mergeIntoCandidate = new Region<>(this); + } + -+ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ); + final RegionSection section; + if (force == null) { -+ section = this.regionsBySection.computeIfAbsent(sectionKey, this.createRegionIfAbsent); ++ this.regionsBySection.put(sectionKey, section = new RegionSection<>(sectionKey, this)); + } else { + final RegionSection existing = this.regionsBySection.putIfAbsent(sectionKey, force); + if (existing != null) { @@ -999,6 +1113,8 @@ index 000000000..4cb10fe69 + + section.region = mergeIntoCandidate; + mergeIntoCandidate.sections.add(section); ++ //mergeIntoCandidate.check(); ++ //this.check(); + + return section; + } @@ -1018,7 +1134,8 @@ index 000000000..4cb10fe69 + this.removeChunkTimings.startTiming(); + try { + final RegionSection section = this.regionsBySection.get( -+ MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT)); ++ MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT) ++ ); + if (section != null) { + section.removeChunk(chunkX, chunkZ); + } else { @@ -1035,6 +1152,7 @@ index 000000000..4cb10fe69 + final Region region = this.needsRecalculation.removeFirst(); + + this.recalculateRegion(region); ++ //this.check(); + } + } + @@ -1042,23 +1160,28 @@ index 000000000..4cb10fe69 + this.regionRecalculateTimings.startTiming(); + try { + region.markedForRecalc = false; ++ //region.check(); + // clear unused regions -+ for (final Iterator> iterator = region.deadSections.iterator(); iterator.hasNext(); ) { ++ for (final Iterator> iterator = region.deadSections.iterator(); iterator.hasNext();) { + final RegionSection deadSection = iterator.next(); ++ ++ if (deadSection.hasChunks()) { ++ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!"); ++ } ++ if (!region.sections.remove(deadSection)) { ++ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); ++ } + if (!this.regionsBySection.remove(deadSection.regionCoordinate, deadSection)) { + throw new IllegalStateException("Cannot remove dead section '" + + deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " + + this.regionsBySection.get(deadSection.regionCoordinate)); + } -+ if (!region.sections.remove(deadSection)) { -+ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); -+ } -+ -+ iterator.remove(); + } ++ region.deadSections.clear(); + + // implicitly cover cases where size == 0 + if (region.sections.size() < this.minSectionRecalcCount) { ++ //region.check(); + return; + } + @@ -1071,6 +1194,9 @@ index 000000000..4cb10fe69 + // destroy region state + for (final Iterator> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { + final RegionSection aliveSection = iterator.next(); ++ if (!aliveSection.hasChunks()) { ++ throw new IllegalStateException("Alive section '" + aliveSection.toStringWithRegion() + "' has no chunks!"); ++ } + if (!this.regionsBySection.remove(aliveSection.regionCoordinate, aliveSection)) { + throw new IllegalStateException("Cannot remove alive section '" + + aliveSection.toStringWithRegion() + "' from section state! State at section coordinate: " + @@ -1108,13 +1234,36 @@ index 000000000..4cb10fe69 + return (double)this.deadSections.size() / (double)this.sections.size(); + } + ++ /* ++ protected void check() { ++ if (this.dead) { ++ throw new IllegalStateException("Dead region!"); ++ } ++ for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final RegionSection section = iterator.next(); ++ if (section.region != this) { ++ throw new IllegalStateException("Region section must point to us!"); ++ } ++ if (this.regionManager.regionsBySection.get(section.regionCoordinate) != section) { ++ throw new IllegalStateException("Region section must match the regionmanager state!"); ++ } ++ } ++ } ++ */ ++ + protected void mergeInto(final Region mergeTarget) { ++ if (this == mergeTarget) { ++ throw new IllegalStateException("Cannot merge a region onto itself"); ++ } + if (this.dead) { + throw new IllegalStateException("Source region is dead! Source " + this + ", target " + mergeTarget); + } else if (mergeTarget.dead) { + throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget); + } + this.dead = true; ++ if (this.markedForRecalc) { ++ this.regionManager.removeFromRecalcQueue(this); ++ } + + for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { + final RegionSection section = iterator.next(); @@ -1132,6 +1281,7 @@ index 000000000..4cb10fe69 + } + mergeTarget.deadSections.add(deadSection); + } ++ //mergeTarget.check(); + } + + protected void markSectionAlive(final RegionSection section) { @@ -1176,7 +1326,8 @@ index 000000000..4cb10fe69 + + public static final class RegionSection & SingleThreadChunkRegionManager.RegionDataCreator> { + protected final long regionCoordinate; -+ protected long chunksBitset; ++ protected final long[] chunksBitset = new long[Math.max(1, REGION_CHUNK_SIZE * REGION_CHUNK_SIZE / Long.SIZE)]; ++ protected int chunkCount; + protected Region region; + protected final EnumMap data; + protected final Function createIfAbsentFunction; @@ -1224,25 +1375,31 @@ index 000000000..4cb10fe69 + return (chunkX & (REGION_CHUNK_SIZE - 1)) | ((chunkZ & (REGION_CHUNK_SIZE - 1)) << REGION_CHUNK_SIZE_SHIFT); + } + ++ protected boolean hasChunks() { ++ return this.chunkCount != 0; ++ } ++ + protected void addChunk(final int chunkX, final int chunkZ) { -+ final long bitset = this.chunksBitset; -+ final long after = this.chunksBitset = bitset | (1L << getChunkIndex(chunkX, chunkZ)); ++ final int index = getChunkIndex(chunkX, chunkZ); ++ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE ++ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1))); + if (after == bitset) { + throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString()); + } -+ if (bitset != 0L) { ++ if (++this.chunkCount != 1) { + return; + } + this.region.markSectionAlive(this); + } + + protected void removeChunk(final int chunkX, final int chunkZ) { -+ final long before = this.chunksBitset; -+ final long bitset = this.chunksBitset = before & ~(1L << getChunkIndex(chunkX, chunkZ)); ++ final int index = getChunkIndex(chunkX, chunkZ); ++ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE ++ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1))); + if (before == bitset) { + throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString()); + } -+ if (bitset != 0L) { ++ if (--this.chunkCount != 0) { + return; + } + this.region.markSectionDead(this); @@ -1252,19 +1409,36 @@ index 000000000..4cb10fe69 + public String toString() { + return "RegionSection{" + + "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," + -+ "chunksBitset=" + Long.toHexString(this.chunksBitset) + "," + -+ "hash=" + this.hashCode() + "," + ++ "chunkCount=" + this.chunkCount + "," + ++ "chunksBitset=" + toString(this.chunksBitset) + "," + ++ "hash=" + this.hashCode() + + "}"; + } + + public String toStringWithRegion() { + return "RegionSection{" + + "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," + -+ "chunksBitset=" + Long.toHexString(this.chunksBitset) + "," + ++ "chunkCount=" + this.chunkCount + "," + ++ "chunksBitset=" + toString(this.chunksBitset) + "," + + "hash=" + this.hashCode() + "," + -+ "region=" + this.region + "," + ++ "region=" + this.region + + "}"; + } ++ ++ private static String toString(final long[] array) { ++ StringBuilder ret = new StringBuilder(); ++ for (long value : array) { ++ // zero pad the hex string ++ char[] zeros = new char[Long.SIZE / 4]; ++ Arrays.fill(zeros, '0'); ++ String string = Long.toHexString(value); ++ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length()); ++ ++ ret.append(zeros); ++ } ++ ++ return ret.toString(); ++ } + } + + public static interface RegionDataCreator & RegionDataCreator> { @@ -1274,12 +1448,1950 @@ index 000000000..4cb10fe69 + } +} \ No newline at end of file +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java +new file mode 100644 +index 000000000..e7df9dc4b +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java +@@ -0,0 +1,155 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.Chunk; ++import net.minecraft.server.ChunkSection; ++import net.minecraft.server.ChunkStatus; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.ProtoChunkExtension; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Set; ++ ++public final class BlockStarLightEngine extends StarLightEngine { ++ ++ public BlockStarLightEngine() { ++ super(false); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk) { ++ return chunk.getBlockNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setBlockNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final IChunkAccess chunk) { ++ return chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT) && chunk.isLit(); ++ } ++ ++ @Override ++ protected final void checkBlock(final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change emitted light ++ // blocks can change direction of propagation ++ ++ final int encodeOffset = this.coordinateOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ final IBlockData blockData = this.getBlockData(worldX, worldY, worldZ); ++ final int emittedLevel = blockData.getEmittedLight() & emittedMask; ++ ++ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); ++ // this accounts for change in emitted light that would cause an increase ++ if (emittedLevel != 0) { ++ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) | ++ emittedLevel << (6 + 6 + 9) | ++ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) | ++ (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0); ++ } ++ // this also accounts for a change in emitted light that would cause a decrease ++ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) ++ // as it checks all neighbours (even if current level is 0) ++ this.decreaseQueue[this.decreaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) | ++ currentLevel << (6 + 6 + 9) | ++ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)); ++ // always keep sided transparent false here, new block might be conditionally transparent which would ++ // prevent us from decreasing sources in the directions where the new block is opaque ++ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always ++ // catch that and fix it. ++ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk, ++ final Set positions) { ++ for (final BlockPosition pos : positions) { ++ this.checkBlock(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected Iterator getSources(final IChunkAccess chunk) { ++ if (chunk instanceof ProtoChunkExtension || chunk instanceof Chunk) { ++ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is ++ // skipping empty sections, and the far more optimised reading of types. ++ List sources = new ArrayList<>(); ++ ++ int offX = chunk.getPos().x << 4; ++ int offZ = chunk.getPos().z << 4; ++ ++ final ChunkSection[] sections = chunk.getSections(); ++ for (int sectionY = 0; sectionY <= 15; ++sectionY) { ++ if (sections[sectionY] == null || sections[sectionY].isFullOfAir()) { ++ // no sources in empty sections ++ continue; ++ } ++ final ChunkSection section = sections[sectionY]; ++ ++ for (int localY = 0; localY <= 15; ++localY) { ++ final int realY = localY | (sectionY << 4); ++ for (int localZ = 0; localZ <= 15; ++localZ) { ++ for (int localX = 0; localX <= 15; ++localX) { ++ final IBlockData blockData = section.getType(localX, localY, localZ); ++ if (blockData.getEmittedLight() <= 0) { ++ continue; ++ } ++ ++ sources.add(new BlockPosition(offX + localX, realY, offZ + localZ)); ++ } ++ } ++ } ++ } ++ ++ return sources.iterator(); ++ } else { ++ return chunk.getLightSources().iterator(); ++ } ++ } ++ ++ @Override ++ public void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks) { ++ // setup sources ++ final int emittedMask = this.emittedLightMask; ++ for (final Iterator positions = this.getSources(chunk); positions.hasNext();) { ++ final BlockPosition pos = positions.next(); ++ final IBlockData blockData = this.getBlockData(pos.getX(), pos.getY(), pos.getZ()); ++ final int emittedLight = blockData.getEmittedLight() & emittedMask; ++ ++ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { ++ // some other source is brighter ++ continue; ++ } ++ ++ this.increaseQueue[this.increaseQueueInitialLength++] = (pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) | ++ (emittedLight) << (6 + 6 + 9) | ++ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) | ++ (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0); ++ ++ ++ // propagation wont set this for us ++ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); ++ } ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ // verify neighbour edges ++ this.checkChunkEdges(lightAccess, chunk); ++ } else { ++ this.propagateNeighbourLevels(lightAccess, chunk, -1, 16); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java b/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java +new file mode 100644 +index 000000000..6a63699ae +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java +@@ -0,0 +1,189 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import net.minecraft.server.NibbleArray; ++ ++import java.util.Arrays; ++ ++// SWMR -> Single Writer Multi Reader Nibble Array ++public final class SWMRNibbleArray { ++ ++ public static final int SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block ++ protected static final byte[] FULL_LIT = new byte[SIZE]; ++ static { ++ Arrays.fill(FULL_LIT, (byte)-1); ++ } ++ ++ protected byte[] updatingBytes; ++ protected byte[] visibleBytes; ++ protected final int defaultNullValue; ++ private boolean dirty; ++ private boolean isNullNibble; ++ ++ public SWMRNibbleArray(final boolean isNullNibble, final int defaultNullValue) { ++ this(null, defaultNullValue); ++ this.isNullNibble = isNullNibble; ++ } ++ ++ public SWMRNibbleArray() { ++ this(null, 0); // lazy init ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes) { ++ this(bytes, 0); ++ } ++ ++ protected SWMRNibbleArray(final byte[] bytes, final int defaultNullValue) { ++ if (bytes != null && bytes.length != SIZE) { ++ throw new IllegalArgumentException(); ++ } ++ this.defaultNullValue = defaultNullValue; ++ this.updatingBytes = bytes; ++ this.visibleBytes = bytes != null ? bytes.clone() : null; ++ } ++ ++ public boolean isDirty() { ++ return this.dirty; ++ } ++ ++ public NibbleArray asNibble() { ++ synchronized (this) { ++ return this.visibleBytes == null ? (this.isNullNibble ? null : new NibbleArray()) : new NibbleArray(this.visibleBytes.clone()); ++ } ++ } ++ ++ public boolean isInitialisedUpdating() { ++ return this.updatingBytes != null; ++ } ++ ++ public boolean isInitialised() { ++ synchronized (this) { ++ return this.visibleBytes != null; ++ } ++ } ++ ++ public boolean isNullNibble() { ++ synchronized (this) { ++ return this.isNullNibble; ++ } ++ } ++ ++ public void markNonNull() { ++ this.isNullNibble = false; ++ } ++ ++ public void zero() { ++ if (this.updatingBytes == null) { ++ this.updatingBytes = new byte[SIZE]; ++ } else { ++ Arrays.fill(this.updatingBytes, (byte)0); ++ } ++ this.dirty = true; ++ } ++ ++ public void initialise() { ++ if (this.updatingBytes != null) { ++ return; ++ } ++ if (this.isNullNibble && this.defaultNullValue != 0) { ++ if (this.defaultNullValue == 15) { ++ this.updatingBytes = FULL_LIT.clone(); ++ } else { ++ this.updatingBytes = new byte[SIZE]; ++ Arrays.fill(this.updatingBytes, (byte)(this.defaultNullValue | (this.defaultNullValue << 4))); ++ } ++ this.dirty = true; ++ } else { ++ this.zero(); ++ } ++ } ++ ++ public void copyFrom(final byte[] src) { ++ if (src.length != SIZE) { ++ throw new IllegalStateException("Arg length should be " + SIZE + ", not " + src.length); ++ } ++ if (this.updatingBytes == null) { ++ this.updatingBytes = src.clone(); ++ } else { ++ System.arraycopy(src, 0, this.updatingBytes, 0, SIZE); ++ } ++ this.dirty = true; ++ } ++ ++ public boolean updateVisible() { ++ if (!this.dirty) { ++ return false; ++ } ++ this.dirty = false; ++ synchronized (this) { ++ this.isNullNibble = false; ++ if (this.visibleBytes == null) { ++ this.visibleBytes = this.updatingBytes.clone(); ++ } else { ++ System.arraycopy(this.updatingBytes, 0, this.visibleBytes, 0, SIZE); ++ } ++ } ++ return true; ++ } ++ ++ public final int getVisible(final int x, final int y, final int z) { ++ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ public synchronized final int getVisible(final int index) { ++ // indices range from 0 -> 4096 ++ if (this.visibleBytes == null) { ++ return this.isNullNibble ? this.defaultNullValue : 0; ++ } ++ final byte value = this.visibleBytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ // all axis from [0, 15] ++ // index = x | (z << 4) | (y << 8) ++ ++ public final int get(final int index) { ++ // indices range from 0 -> 4096 ++ if (this.updatingBytes == null) { ++ return this.isNullNibble ? this.defaultNullValue : 0; ++ } ++ final byte value = this.updatingBytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ public final void set(final int index, final int value) { ++ if (this.updatingBytes == null) { ++ this.initialise(); ++ } ++ final int shift = (index & 1) << 2; ++ final int i = index >>> 1; ++ ++ this.updatingBytes[i] = (byte)((this.updatingBytes[i] & (0xF0 >>> shift)) | (value << shift)); ++ this.dirty = true; ++ } ++ ++ public byte[] getClone() { ++ synchronized (this) { ++ if (this.visibleBytes != null) { ++ return this.visibleBytes.clone(); ++ } ++ } ++ return new byte[SIZE]; ++ } ++ ++ public void copyInto(final byte[] dst, final int off) { ++ synchronized (this) { ++ if (this.visibleBytes != null) { ++ System.arraycopy(this.visibleBytes, 0, dst, off, SIZE); ++ return; ++ } ++ } ++ ++ Arrays.fill(dst, off, SIZE, (byte)0); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java +new file mode 100644 +index 000000000..954d9ac31 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java +@@ -0,0 +1,359 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.ChunkSection; ++import net.minecraft.server.ChunkStatus; ++import net.minecraft.server.IBlockAccess; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.VoxelShape; ++import net.minecraft.server.VoxelShapes; ++import java.util.Arrays; ++import java.util.Set; ++ ++public final class SkyStarLightEngine extends StarLightEngine { ++ ++ public SkyStarLightEngine() { ++ super(true); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk) { ++ return chunk.getSkyNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setSkyNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final IChunkAccess chunk) { ++ return chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT) && chunk.isLit(); ++ } ++ ++ @Override ++ protected final void checkBlock(final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change direction of propagation ++ ++ // same logic applies from BlockStarLightEngine#checkBlock ++ ++ final int encodeOffset = this.coordinateOffset; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ ++ if (currentLevel == 15) { ++ // must re-propagate clobbered source ++ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) | ++ currentLevel << (6 + 6 + 9) | ++ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) | ++ (FLAG_HAS_SIDED_TRANSPARENT_BLOCKS); // don't know if the block is conditionally transparent ++ } else { ++ this.setLightLevel(worldX, worldY, worldZ, 0); ++ } ++ ++ this.decreaseQueue[this.decreaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) | ++ (currentLevel) << (6 + 6 + 9) | ++ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)); ++ } ++ ++ protected final int[] heightMap = new int[16 * 16]; ++ { ++ Arrays.fill(this.heightMap, -1024); // clear heightmap ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk, ++ final Set positions) { ++ final IBlockAccess world = lightAccess.getWorld(); ++ final int chunkX = atChunk.getPos().x; ++ final int chunkZ = atChunk.getPos().z; ++ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); ++ ++ // setup heightmap for changes ++ int highestBlockY = -1024; ++ for (final BlockPosition pos : positions) { ++ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; ++ final int curr = this.heightMap[index]; ++ if (pos.getY() > curr) { ++ this.heightMap[index] = pos.getY(); ++ } ++ if (pos.getY() > highestBlockY) { ++ highestBlockY = pos.getY(); ++ } ++ } ++ ++ // now we can recalculate the sources for the changed columns ++ for (int index = 0; index < (16 * 16); ++index) { ++ final int maxY = this.heightMap[index]; ++ if (maxY == -1024) { ++ // not changed ++ continue; ++ } ++ this.heightMap[index] = -1024; // restore default for next caller ++ ++ final int columnX = (index & 15) | (chunkX << 4); ++ final int columnZ = (index >>> 4) | (chunkZ << 4); ++ ++ // try and propagate from the above y ++ int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ); ++ ++ // maxPropagationY is now the highest block that could not be propagated to ++ ++ // remove all sources below that are not 15 ++ final int propagateDirection = Direction.NEGATIVE_Y.ordinal(); ++ final int encodeOffset = this.coordinateOffset; ++ for (int currY = maxPropagationY; currY >= -15; --currY) { ++ if (this.getLightLevel(columnX, currY, columnZ) != 15) { ++ break; ++ } ++ this.setLightLevel(columnX, currY, columnZ, 0); ++ this.decreaseQueue[this.decreaseQueueInitialLength++] = (columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) | ++ (15 << (6 + 6 + 9)) | ++ ((propagateDirection) << (6 + 6 + 9 + 4)); ++ // do not set transparent blocks for the same reason we don't in the checkBlock method ++ } ++ } ++ ++ // we need to initialise nibbles up to the highest section (we don't save null nibbles) ++ for (int y = -1; y <= Math.min(16, (highestBlockY >> 4)); ++y) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, y, chunkZ); ++ if (nibble.isNullNibble()) { ++ nibble.initialise(); ++ } ++ } ++ ++ for (final BlockPosition pos : positions) { ++ this.checkBlock(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected void initLightNeighbours(final int chunkX, final int chunkZ) { ++ // vanilla requires that written nibble data has initialised nibble data in 1 radius ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ IChunkAccess chunk = this.getChunkInCache(dx + chunkX, dz + chunkZ); ++ if (chunk == null) { ++ continue; ++ } ++ // find lowest section ++ int lowest = 15; ++ ChunkSection[] sections = chunk.getSections(); ++ for (;lowest > 0 && (sections[lowest] == null || sections[lowest].isFullOfAir()); --lowest) {} ++ ++ if (lowest == -1) { ++ continue; ++ } ++ ++ for (int y = lowest; y >= -1; --y) { ++ SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, y, dz + chunkZ); ++ if (nibble != null && !nibble.isDirty() && nibble.isInitialisedUpdating()) { ++ for (int dy = -1; dy <= 1; ++dy) { ++ SWMRNibbleArray ours = this.getNibbleFromCache(chunkX, dy + y, chunkZ); ++ if (ours != null && !ours.isInitialisedUpdating() && !ours.isDirty()) { ++ ours.initialise(); ++ ours.updateVisible(); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ @Override ++ protected void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks) { ++ final IBlockAccess world = lightAccess.getWorld(); ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ final ChunkSection[] sections = chunk.getSections(); ++ final SWMRNibbleArray[] originalNibbles = this.getNibblesForChunkFromCache(chunkX, chunkZ); ++ ++ int highestNonEmptySection = 16; ++ while (highestNonEmptySection == -1 || highestNonEmptySection == 16 || sections[highestNonEmptySection] == null || sections[highestNonEmptySection].isFullOfAir()) { ++ // try propagate FULL to neighbours ++ ++ // check neighbours to see if we need to propagate into them ++ for (final Direction direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourX = chunkX + direction.x; ++ final int neighbourZ = chunkZ + direction.z; ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); ++ if (neighbourNibble == null) { ++ // unloaded neighbour ++ continue; ++ } ++ if (neighbourNibble.isNullNibble()) { ++ // most of the time we fall here ++ // no point of propagating full light into full light ++ continue; ++ } ++ ++ // it looks like we need to propagate into the neighbour ++ ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (direction.x != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (direction.z < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final int encodeOffset = this.coordinateOffset; ++ final int propagateDirection = direction.ordinal() | 16; // we only want to check in this direction ++ ++ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) | ++ (15 << (6 + 6 + 9)) | // we know we're at full lit here ++ ((propagateDirection) << (6 + 6 + 9 + 4)); ++ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) ++ } ++ } ++ } ++ ++ if (highestNonEmptySection-- == -1) { ++ break; ++ } ++ } ++ ++ if (highestNonEmptySection >= 0) { ++ // mark the rest of our nibbles as 0 ++ for (int currY = highestNonEmptySection; currY >= -1; --currY) { ++ this.getNibbleFromCache(chunkX, currY, chunkZ).markNonNull(); ++ } ++ ++ // fill out our other sources ++ final int minX = chunkPos.x << 4; ++ final int maxX = chunkPos.x << 4 | 15; ++ final int minZ = chunkPos.z << 4; ++ final int maxZ = chunkPos.z << 4 | 15; ++ final int startY = highestNonEmptySection << 4 | 15; ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ this.tryPropagateSkylight(world, currX, startY, currZ); ++ } ++ } ++ } // else: apparently the chunk is empty ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ this.checkChunkEdges(lightAccess, chunk); ++ } else { ++ this.propagateNeighbourLevels(lightAccess, chunk, -1, highestNonEmptySection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ ++ this.initLightNeighbours(chunkPos.x, chunkPos.z); ++ } ++ ++ protected final int tryPropagateSkylight(final IBlockAccess world, final int worldX, final int startY, final int worldZ) { ++ final BlockPosition.MutableBlockPosition mutablePos = this.mutablePos3; ++ final int encodeOffset = this.coordinateOffset; ++ final int propagateDirection = Direction.NEGATIVE_Y.ordinal(); // just don't check upwards. ++ ++ if (this.getLightLevel(worldX, startY + 1, worldZ) != 15) { ++ return startY; ++ } ++ ++ IBlockData above = this.getBlockData(worldX, startY + 1, worldZ); ++ if (above == null) { ++ above = AIR_BLOCK_DATA; ++ } ++ ++ int maxPropagationY; ++ for (maxPropagationY = startY; maxPropagationY >= -15; --maxPropagationY) { ++ IBlockData current = this.getBlockData(worldX, maxPropagationY, worldZ); ++ if (current == null) { ++ current = AIR_BLOCK_DATA; ++ } ++ ++ final VoxelShape fromShape; ++ if (above.isConditionallyFullOpaque()) { ++ this.mutablePos2.setValues(worldX, maxPropagationY + 1, worldZ); ++ fromShape = above.getCullingFace(world, this.mutablePos2, Direction.NEGATIVE_Y.nms); ++ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) { ++ // above wont let us propagate ++ break; ++ } ++ } else { ++ fromShape = VoxelShapes.getEmptyShape(); ++ } ++ ++ final int opacityIfCached = current.getOpacityIfCached(); ++ // does light propagate from the top down? ++ if (opacityIfCached != -1) { ++ if (opacityIfCached != 0) { ++ // we cannot propagate 15 through this ++ break; ++ } ++ // most of the time it falls here. ++ this.setLightLevel(worldX, maxPropagationY, worldZ, 15); ++ // add to propagate ++ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (maxPropagationY << (6 + 6)) + encodeOffset) | ++ (15 << (6 + 6 + 9)) | // we know we're at full lit here ++ ((propagateDirection) << (6 + 6 + 9 + 4)); ++ } else { ++ mutablePos.setValues(worldX, maxPropagationY, worldZ); ++ int flags = 0; ++ if (current.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = current.getCullingFace(world, mutablePos, Direction.POSITIVE_Y.nms); ++ ++ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) { ++ // can't propagate here, we're done on this column. ++ break; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = current.getOpacity(world, mutablePos); ++ if (opacity > 0) { ++ // let the queued value (if any) handle it from here. ++ break; ++ } ++ ++ this.setLightLevel(worldX, maxPropagationY, worldZ, 15); ++ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (maxPropagationY << (6 + 6)) + encodeOffset) | ++ (15 << (6 + 6 + 9)) | // we know we're at full lit here ++ ((propagateDirection) << (6 + 6 + 9 + 4)) | ++ flags; ++ } ++ ++ above = current; ++ } ++ ++ return maxPropagationY; ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java +new file mode 100644 +index 000000000..1a1784814 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java +@@ -0,0 +1,1024 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import com.destroystokyo.paper.util.math.IntegerUtil; ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.Blocks; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.ChunkSection; ++import net.minecraft.server.EnumDirection; ++import net.minecraft.server.EnumSkyBlock; ++import net.minecraft.server.IBlockAccess; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.SectionPosition; ++import net.minecraft.server.VoxelShape; ++import net.minecraft.server.VoxelShapes; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.Set; ++ ++public abstract class StarLightEngine { ++ ++ protected static final IBlockData AIR_BLOCK_DATA = Blocks.AIR.getBlockData(); ++ ++ protected static final ChunkSection EMPTY_CHUNK_SECTION = new ChunkSection(0); ++ ++ protected static final Direction[] DIRECTIONS = Direction.values(); ++ protected static final Direction[] AXIS_DIRECTIONS = DIRECTIONS; ++ protected static final Direction[] ONLY_HORIZONTAL_DIRECTIONS = new Direction[] { ++ Direction.POSITIVE_X, Direction.NEGATIVE_X, ++ Direction.POSITIVE_Z, Direction.NEGATIVE_Z ++ }; ++ ++ protected static enum Direction { ++ ++ // Declaration order is important and relied upon. Do not change without modifying propagation code. ++ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), ++ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), ++ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); ++ ++ static { ++ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; ++ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; ++ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; ++ } ++ ++ protected Direction opposite; ++ ++ public final int x; ++ public final int y; ++ public final int z; ++ public final EnumDirection nms; ++ ++ Direction(final int x, final int y, final int z) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ this.nms = EnumDirection.from(x, y, z); ++ } ++ ++ public Direction getOpposite() { ++ return this.opposite; ++ } ++ } ++ ++ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 ++ // for explaining how light propagates via breadth-first search ++ ++ // While the above is a good start to understanding the general idea of what the general principles are, it's not ++ // exactly how the vanilla light engine should behave for minecraft. ++ ++ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] ++ // for the y chunk section it's from [-1, 16] or [0, 17] ++ // index = x + (z * 5) + (y * 25) ++ // null index indicates the chunk section doesn't exist (empty or out of bounds) ++ protected final ChunkSection[] sectionCache = new ChunkSection[5 * 5 * (16 + 2 + 2)]; // add two extra sections for buffer ++ ++ // the exact same as above, except for storing fast access to SWMRNibbleArray ++ // for the y chunk section it's from [-1, 16] or [0, 17] ++ // index = x + (z * 5) + (y * 25) ++ protected final SWMRNibbleArray[] nibbleCache = new SWMRNibbleArray[5 * 5 * (16 + 2 + 2)]; // add two extra sections for buffer ++ ++ // always initialsed during start of lighting. no index is null. ++ // index = x + (z * 5) ++ protected final IChunkAccess[] chunkCache = new IChunkAccess[5 * 5]; ++ ++ protected final BlockPosition.MutableBlockPosition mutablePos1 = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition mutablePos2 = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition mutablePos3 = new BlockPosition.MutableBlockPosition(); ++ ++ protected int encodeOffsetX; ++ protected int encodeOffsetY; ++ protected int encodeOffsetZ; ++ ++ protected int coordinateOffset; ++ ++ protected int chunkOffsetX; ++ protected int chunkOffsetY; ++ protected int chunkOffsetZ; ++ ++ protected int chunkIndexOffset; ++ protected int chunkSectionIndexOffset; ++ ++ protected final boolean skylightPropagator; ++ protected final int emittedLightMask; ++ ++ protected StarLightEngine(final boolean skylightPropagator) { ++ this.skylightPropagator = skylightPropagator; ++ this.emittedLightMask = skylightPropagator ? 0 : 0xF; ++ } ++ ++ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { ++ // 31 = center + encodeOffset ++ this.encodeOffsetX = 31 - centerX; ++ this.encodeOffsetY = 31; // we want 0 to be the smallest encoded value ++ this.encodeOffsetZ = 31 - centerZ; ++ ++ // coordinateIndex = x | (z << 6) | (y << 12) ++ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); ++ ++ // 2 = (centerX >> 4) + chunkOffset ++ this.chunkOffsetX = 2 - (centerX >> 4); ++ this.chunkOffsetY = 2; // lowest should be 0, not -2 ++ this.chunkOffsetZ = 2 - (centerZ >> 4); ++ ++ // chunk index = x + (5 * z) ++ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); ++ ++ // chunk section index = x + (5 * z) + ((5*5) * y) ++ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); ++ } ++ ++ protected final void setupCaches(final ILightAccess world, final int centerX, final int centerY, final int centerZ, final boolean relaxed) { ++ final int centerChunkX = centerX >> 4; ++ final int centerChunkY = centerY >> 4; ++ final int centerChunkZ = centerZ >> 4; ++ ++ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); ++ ++ final int minX = centerChunkX - 1; ++ final int minZ = centerChunkZ - 1; ++ final int maxX = centerChunkX + 1; ++ final int maxZ = centerChunkZ + 1; ++ ++ for (int cx = minX; cx <= maxX; ++cx) { ++ for (int cz = minZ; cz <= maxZ; ++cz) { ++ final IChunkAccess chunk = (IChunkAccess)world.getFeaturesReadyChunk(cx, cz); ++ ++ if (chunk == null) { ++ if (relaxed) { ++ continue; ++ } ++ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); ++ } ++ ++ if (!this.canUseChunk(chunk)) { ++ continue; ++ } ++ ++ this.setChunkInCache(cx, cz, chunk); ++ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); ++ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); ++ } ++ } ++ } ++ ++ protected final IChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { ++ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setChunkInCache(final int chunkX, final int chunkZ, final IChunkAccess chunk) { ++ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; ++ } ++ ++ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final ChunkSection[] sections) { ++ final int chunkIndex = chunkX + 5*chunkZ; ++ for (int cy = -1; cy <= 16; ++cy) { ++ this.sectionCache[chunkIndex + (cy * (5 * 5)) + this.chunkSectionIndexOffset] = sections == null ? null : (cy >= 0 && cy <= 15 ? (sections[cy] == null || sections[cy].isFullOfAir() ? EMPTY_CHUNK_SECTION : sections[cy]) : EMPTY_CHUNK_SECTION); ++ } ++ } ++ ++ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { ++ final SWMRNibbleArray[] ret = getEmptyLightArray(); ++ ++ for (int cy = -1; cy <= 16; ++cy) { ++ ret[cy + 1] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; ++ } ++ ++ return ret; ++ } ++ ++ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { ++ for (int cy = -1; cy <= 16; ++cy) { ++ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy + 1]); ++ } ++ } ++ ++ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { ++ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; ++ } ++ ++ protected final void updateVisible(final ILightAccess lightAccess) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (nibble != null && nibble.updateVisible()) { ++ final int chunkX = (index % 5) - this.chunkOffsetX; ++ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; ++ final int chunkY = ((index / (5*5)) % (16 + 2 + 2)) - this.chunkOffsetY; ++ lightAccess.markLightSectionDirty(this.skylightPropagator ? EnumSkyBlock.SKY : EnumSkyBlock.BLOCK, new SectionPosition(chunkX, chunkY, chunkZ)); ++ // initialise 1 radius neighbours ++ if (this.skylightPropagator) { ++ for (int dy = -1; dy <= 1; ++dy) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ SWMRNibbleArray neighbour = this.getNibbleFromCache(chunkX + dx, chunkY + dy, chunkZ + dz); ++ if (neighbour != null && !neighbour.isDirty() && !neighbour.isInitialisedUpdating()) { ++ neighbour.initialise(); ++ neighbour.updateVisible(); ++ lightAccess.markLightSectionDirty(this.skylightPropagator ? EnumSkyBlock.SKY : EnumSkyBlock.BLOCK, ++ new SectionPosition(chunkX + dx, chunkY + dy, chunkZ + dz)); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void destroyCaches() { ++ Arrays.fill(this.sectionCache, null); ++ Arrays.fill(this.nibbleCache, null); ++ Arrays.fill(this.chunkCache, null); ++ } ++ ++ protected final IBlockData getBlockData(final int worldX, final int worldY, final int worldZ) { ++ final ChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ if (section != null) { ++ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_DATA : section.blockIds.rawGet((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); ++ } ++ ++ return null; ++ } ++ ++ protected final IBlockData getBlockData(final int sectionIndex, final int localIndex) { ++ final ChunkSection section = this.sectionCache[sectionIndex]; ++ ++ if (section != null) { ++ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_DATA : section.blockIds.rawGet(localIndex); ++ } ++ ++ return null; ++ } ++ ++ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { ++ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ return nibble == null ? 0 : nibble.get((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); ++ } ++ ++ protected final int getLightLevel(final int sectionIndex, final int localIndex) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ return nibble == null ? 0 : nibble.get(localIndex); ++ } ++ ++ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { ++ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ if (nibble != null) { ++ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); ++ } ++ } ++ ++ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int level) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set(localIndex, level); ++ } ++ } ++ ++ public static SWMRNibbleArray[] getFilledEmptyLight(final boolean skylight) { ++ final SWMRNibbleArray[] ret = getEmptyLightArray(); ++ ++ for (int i = 0, len = ret.length; i < len; ++i) { ++ ret[i] = new SWMRNibbleArray(true, skylight ? 15 : 0); ++ } ++ ++ return ret; ++ } ++ ++ public static SWMRNibbleArray[] getEmptyLightArray() { ++ return new SWMRNibbleArray[16 + 2]; ++ } ++ ++ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk); ++ ++ protected abstract void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to); ++ ++ protected abstract boolean canUseChunk(final IChunkAccess chunk); ++ ++ public final void blocksChangedInChunk(final ILightAccess lightAccess, final int chunkX, final int chunkZ, ++ final Set positions) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false); ++ try { ++ this.propagateBlockChanges(lightAccess, this.getChunkInCache(chunkX, chunkZ), positions); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ protected abstract void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk, ++ final Set positions); ++ ++ protected abstract void checkBlock(final int worldX, final int worldY, final int worldZ); ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ protected final void checkChunkEdges(final ILightAccess lightAccess, final IChunkAccess chunk) { ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = 16; currSectionY >= -1; --currSectionY) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); ++ for (final Direction direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ currSectionY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null) { ++ continue; ++ } ++ ++ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { ++ if (this.skylightPropagator) { ++ if (currNibble.isNullNibble() == neighbourNibble.isNullNibble()) { ++ continue; ++ } // else fall through to edge checks ++ } else { ++ continue; ++ } ++ } ++ ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int neighbourX = currX + neighbourOffX; ++ final int neighbourZ = currZ + neighbourOffZ; ++ ++ final int currentLevel = currNibble.get((currX & 15) | ++ ((currZ & 15)) << 4 | ++ ((currY & 15) << 8) ++ ); ++ final int neighbourLevel = neighbourNibble.get((neighbourX & 15) | ++ ((neighbourZ & 15)) << 4 | ++ ((currY & 15) << 8) ++ ); ++ ++ if (currentLevel == neighbourLevel && (currentLevel == 0 || currentLevel == 15)) { ++ // nothing to check here ++ continue; ++ } ++ ++ if (IntegerUtil.branchlessAbs(currentLevel - neighbourLevel) == 1) { ++ final IBlockData currentBlock = this.getBlockData(currX, currY, currZ); ++ final IBlockData neighbourBlock = this.getBlockData(neighbourX, currY, neighbourZ); ++ ++ final int currentOpacity = currentBlock.getOpacityIfCached(); ++ final int neighbourOpacity = neighbourBlock.getOpacityIfCached(); ++ if (currentOpacity == 0 || currentOpacity == 1 || ++ neighbourOpacity == 0 || neighbourOpacity == 1) { ++ // looks good ++ continue; ++ } ++ } ++ ++ // setup queue, it looks like something could be inconsistent ++ this.checkBlock(currX, currY, currZ); ++ this.checkBlock(neighbourX, currY, neighbourZ); ++ } ++ } ++ } ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected final void propagateNeighbourLevels(final ILightAccess lightAccess, final IChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); ++ for (final Direction direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ currSectionY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null) { ++ continue; ++ } ++ ++ if (!neighbourNibble.isInitialisedUpdating()) { ++ if (this.skylightPropagator) { ++ if (currNibble.isNullNibble() == neighbourNibble.isNullNibble() || !neighbourNibble.isNullNibble()) { ++ continue; ++ } // else fall through to edge checks ++ } else { ++ continue; ++ } ++ } ++ ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = (chunkX << 4) - 1; ++ } else { ++ startX = (chunkX << 4) + 16; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = (chunkZ << 4) - 1; ++ } else { ++ startZ = (chunkZ << 4) + 16; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final int propagateDirection = direction.getOpposite().ordinal() | 16; // we only want to check in this direction towards this chunk ++ final int encodeOffset = this.coordinateOffset; ++ ++ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int level = neighbourNibble.get( ++ (currX & 15) | ++ (currZ & 15) << 4 | ++ (currY & 15) << 8 ++ ); ++ ++ if (level <= 1) { ++ // nothing to propagate ++ continue; ++ } ++ ++ this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) | ++ (level << (6 + 6 + 9)) | ++ ((propagateDirection) << (6 + 6 + 9 + 4)) | ++ FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; // don't know if the current block is transparent, must check. ++ } ++ } ++ } ++ } ++ } ++ ++ public final void checkChunkEdges(final ILightAccess lightAccess, final int chunkX, final int chunkZ) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false); ++ try { ++ this.checkChunkEdges(lightAccess, this.getChunkInCache(chunkX, chunkZ)); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current ++ // chunks light values with respect to neighbours ++ protected abstract void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks); ++ ++ public final void light(final ILightAccess lightAccess, final int chunkX, final int chunkZ) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false); ++ // force current chunk into cache ++ final IChunkAccess chunk = (IChunkAccess)lightAccess.getFeaturesReadyChunk(chunkX, chunkZ); ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); ++ ++ // do we need to check edges? ++ boolean checkEdges = false; ++ ++ for (final IChunkAccess chunkInCache : this.chunkCache) { ++ if (chunkInCache != null && chunkInCache.wasLoadedFromDisk()) { ++ checkEdges = true; ++ break; ++ } ++ } ++ ++ try { ++ this.lightChunk(lightAccess, chunk, checkEdges); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void relight(final ILightAccess world, final int chunkX, final int chunkZ) { ++ final IChunkAccess chunk = (IChunkAccess)world.getFeaturesReadyChunk(chunkX, chunkZ); ++ this.relightChunk(world, chunk); ++ } ++ ++ protected final void relightChunk(final ILightAccess lightAccess, final IChunkAccess chunk) { ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ this.setupEncodeOffset(chunkPos.x * 16 + 7, 128, chunkPos.z * 16 + 7); ++ ++ try { ++ this.setChunkInCache(chunkPos.x, chunkPos.z, chunk); ++ this.setBlocksForChunkInCache(chunkPos.x, chunkPos.z, chunk.getSections()); ++ final SWMRNibbleArray[] chunkNibbles = getFilledEmptyLight(this.skylightPropagator); ++ this.setNibblesForChunkInCache(chunkPos.x, chunkPos.z, chunkNibbles); ++ this.lightChunk(lightAccess, chunk, false); ++ ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if ((dx | dz) == 0) { ++ continue; ++ } ++ ++ final int cx = dx + chunkPos.x; ++ final int cz = dz + chunkPos.z; ++ final IChunkAccess neighbourChunk = (IChunkAccess)lightAccess.getFeaturesReadyChunk(cx, cz); ++ ++ if (neighbourChunk == null || !this.canUseChunk(neighbourChunk)) { ++ continue; ++ } ++ ++ this.setChunkInCache(cx, cz, neighbourChunk); ++ this.setBlocksForChunkInCache(cx, cz, neighbourChunk.getSections()); ++ this.setNibblesForChunkInCache(cx, cz, getFilledEmptyLight(this.skylightPropagator)); ++ this.lightChunk(lightAccess, neighbourChunk, false); ++ } ++ } ++ ++ this.setNibbles(chunk, chunkNibbles); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // old algorithm for propagating ++ // this is also the basic algorithm, the optimised algorithm is always going to be tested against this one ++ // and this one is always tested against vanilla ++ // contains: ++ // lower 21 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) ++ // next 4 bits: propagated light level (0, 15] ++ // next 5 bits: direction propagated from ++ // next 0 bits: unused ++ // last 2 bits: state flags ++ // state flags: ++ // whether the propagation needs to check if its current level is equal to the expected level ++ // used only in increase propagation ++ protected static final int FLAG_RECHECK_LEVEL = Integer.MIN_VALUE >>> 1; ++ // whether the propagation needs to consider if its block is conditionally transparent ++ protected static final int FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Integer.MIN_VALUE; ++ ++ protected final int[] increaseQueue = new int[16 * 16 * (16 * (16 + 2)) * 9 + 1]; ++ protected int increaseQueueInitialLength; ++ protected final int[] decreaseQueue = new int[16 * 16 * (16 * (16 + 2)) * 9 + 1]; ++ protected int decreaseQueueInitialLength; ++ ++ protected static final Direction[][] OLD_CHECK_DIRECTIONS = new Direction[4 * 8][]; ++ static { ++ for (int i = 0; i < AXIS_DIRECTIONS.length; ++i) { ++ final Direction direction = AXIS_DIRECTIONS[i]; ++ final List directions = new ArrayList<>(Arrays.asList(AXIS_DIRECTIONS)); ++ directions.remove(direction.getOpposite()); ++ OLD_CHECK_DIRECTIONS[direction.ordinal()] = directions.toArray(new Direction[0]); ++ OLD_CHECK_DIRECTIONS[direction.ordinal() | 8] = AXIS_DIRECTIONS; // flag ALL_DIRECTIONS ++ OLD_CHECK_DIRECTIONS[direction.ordinal() | 16] = new Direction[] { direction }; // flag ONLY_THIS_DIRECTION ++ } ++ } ++ ++ protected final void performLightIncrease(final ILightAccess lightAccess) { ++ final IBlockAccess world = lightAccess.getWorld(); ++ final int[] queue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.increaseQueueInitialLength; ++ this.increaseQueueInitialLength = 0; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ ++ while (queueReadIndex < queueLength) { ++ final int queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((queueValue & 63) + decodeOffsetX); ++ final int posZ = (((queueValue >>> 6) & 63) + decodeOffsetZ); ++ final int posY = (((queueValue >>> 12) & 511) + decodeOffsetY); ++ final int propagatedLightLevel = ((queueValue >>> (6 + 6 + 9)) & 0xF); ++ final int fromDirection = ((queueValue >>> (6 + 6 + 9 + 4)) & 0x1F); ++ final Direction[] checkDirections = OLD_CHECK_DIRECTIONS[fromDirection]; ++ ++ if ((queueValue & FLAG_RECHECK_LEVEL) != 0) { ++ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { ++ // not at the level we expect, so something changed. ++ continue; ++ } ++ } ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0) { ++ // we don't need to worry about our state here. ++ for (final Direction propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final int currentLevel = this.getLightLevel(sectionIndex, localIndex); ++ ++ if (currentLevel >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want ++ } ++ ++ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex); ++ if (blockData == null) { ++ continue; ++ } ++ final int opacityCached = blockData.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ this.setLightLevel(sectionIndex, localIndex, targetLevel); ++ if (targetLevel > 1) { ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ int flags = 0; ++ if (blockData.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockData.getOpacity(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ this.setLightLevel(sectionIndex, localIndex, targetLevel); ++ if (targetLevel > 1) { ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final IBlockData fromBlock = this.getBlockData(posX, posY, posZ); ++ this.mutablePos2.setValues(posX, posY, posZ); ++ for (final Direction propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.getEmptyShape(); ++ ++ if (fromShape != VoxelShapes.getEmptyShape() && VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) { ++ continue; ++ } ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final int currentLevel = this.getLightLevel(sectionIndex, localIndex); ++ ++ if (currentLevel >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want ++ } ++ ++ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex); ++ if (blockData == null) { ++ continue; ++ } ++ final int opacityCached = blockData.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ this.setLightLevel(sectionIndex, localIndex, targetLevel); ++ if (targetLevel > 1) { ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ int flags = 0; ++ if (blockData.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockData.getOpacity(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ this.setLightLevel(sectionIndex, localIndex, targetLevel); ++ if (targetLevel > 1) { ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void performLightDecrease(final ILightAccess lightAccess) { ++ final IBlockAccess world = lightAccess.getWorld(); ++ final int[] queue = this.decreaseQueue; ++ final int[] increaseQueue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.decreaseQueueInitialLength; ++ this.decreaseQueueInitialLength = 0; ++ int increaseQueueLength = this.increaseQueueInitialLength; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ while (queueReadIndex < queueLength) { ++ final int queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((queueValue & 63) + decodeOffsetX); ++ final int posZ = (((queueValue >>> 6) & 63) + decodeOffsetZ); ++ final int posY = (((queueValue >>> 12) & 511) + decodeOffsetY); ++ final int propagatedLightLevel = ((queueValue >>> (6 + 6 + 9)) & 0xF); ++ final int fromDirection = ((queueValue >>> (6 + 6 + 9 + 4)) & 0x1F); ++ final Direction[] checkDirections = OLD_CHECK_DIRECTIONS[fromDirection]; ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0) { ++ // we don't need to worry about our state here. ++ for (final Direction propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final int lightLevel = this.getLightLevel(sectionIndex, localIndex); ++ ++ if (lightLevel == 0) { ++ // already at lowest, nothing we can do ++ continue; ++ } ++ ++ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex); ++ final int opacityCached = blockData.getOpacityIfCached(); ++ // no null check, blockData cannot be null if level != 0 ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (lightLevel << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = blockData.getEmittedLight() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (emittedLight << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0); ++ } ++ this.setLightLevel(sectionIndex, localIndex, emittedLight); ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ int flags = 0; ++ if (blockData.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockData.getOpacity(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (lightLevel << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = blockData.getEmittedLight() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (emittedLight << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | flags; ++ } ++ this.setLightLevel(sectionIndex, localIndex, emittedLight); ++ if (targetLevel > 0) { ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final IBlockData fromBlock = this.getBlockData(posX, posY, posZ); ++ this.mutablePos2.setValues(posX, posY, posZ); ++ for (final Direction propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.getEmptyShape(); ++ ++ if (fromShape != VoxelShapes.getEmptyShape() && VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) { ++ continue; ++ } ++ ++ final int lightLevel = this.getLightLevel(sectionIndex, localIndex); ++ ++ if (lightLevel == 0) { ++ // already at lowest, nothing we can do ++ continue; ++ } ++ ++ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex); ++ final int opacityCached = blockData.getOpacityIfCached(); ++ // no null check, blockData cannot be null if level != 0 ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (lightLevel << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = blockData.getEmittedLight() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (emittedLight << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0); ++ } ++ this.setLightLevel(sectionIndex, localIndex, emittedLight); ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ int flags = 0; ++ if (blockData.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockData.getOpacity(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (lightLevel << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = blockData.getEmittedLight() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ increaseQueue[increaseQueueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (emittedLight << (6 + 6 + 9)) ++ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) ++ | flags; ++ } ++ this.setLightLevel(sectionIndex, localIndex, emittedLight); ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ queue[queueLength++] = ++ (offX + (offZ << 6) + (offY << 12) + encodeOffset) ++ | (targetLevel << (6 + 6 + 9)) ++ | (propagate.ordinal() << (6 + 6 + 9 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered ++ this.increaseQueueInitialLength = increaseQueueLength; ++ this.performLightIncrease(lightAccess); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/ThreadedStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/ThreadedStarLightEngine.java +new file mode 100644 +index 000000000..767831d16 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/ThreadedStarLightEngine.java +@@ -0,0 +1,181 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.WorldServer; ++import java.util.ArrayDeque; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.Set; ++ ++public final class ThreadedStarLightEngine { ++ ++ protected final WorldServer world; ++ protected final ILightAccess lightAccess; ++ ++ protected final ArrayDeque cachedSkyPropagators; ++ protected final ArrayDeque cachedBlockPropagators; ++ ++ protected final Long2ObjectOpenHashMap> changedBlocks = new Long2ObjectOpenHashMap<>(); ++ ++ public ThreadedStarLightEngine(final ILightAccess lightAccess, final boolean hasSkyLight, final boolean hasBlockLight) { ++ this.lightAccess = lightAccess; ++ this.world = (WorldServer)lightAccess.getWorld(); ++ this.cachedSkyPropagators = hasSkyLight ? new ArrayDeque<>() : null; ++ this.cachedBlockPropagators = hasBlockLight ? new ArrayDeque<>() : null; ++ } ++ ++ public WorldServer getWorld() { ++ return this.world; ++ } ++ ++ protected final SkyStarLightEngine getSkyLightEngine() { ++ if (this.cachedSkyPropagators == null) { ++ return null; ++ } ++ final SkyStarLightEngine ret; ++ synchronized (this.cachedSkyPropagators) { ++ ret = this.cachedSkyPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new SkyStarLightEngine(); ++ } ++ return ret; ++ } ++ ++ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { ++ if (this.cachedSkyPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedSkyPropagators) { ++ this.cachedSkyPropagators.addFirst(engine); ++ } ++ } ++ ++ protected final BlockStarLightEngine getBlockLightEngine() { ++ if (this.cachedBlockPropagators == null) { ++ return null; ++ } ++ final BlockStarLightEngine ret; ++ synchronized (this.cachedBlockPropagators) { ++ ret = this.cachedBlockPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new BlockStarLightEngine(); ++ } ++ return ret; ++ } ++ ++ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { ++ if (this.cachedBlockPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedBlockPropagators) { ++ this.cachedBlockPropagators.addFirst(engine); ++ } ++ } ++ ++ public void blockChange(BlockPosition pos) { ++ if (pos.getY() < 0 || pos.getY() > 255) { ++ return; ++ } ++ ++ pos = pos.immutableCopy(); ++ synchronized (this.changedBlocks) { ++ this.changedBlocks.computeIfAbsent(MCUtil.getCoordinateKey(pos), (final long keyInMap) -> { ++ return new HashSet<>(); ++ }).add(pos); ++ } ++ } ++ ++ public void lightChunk(final int chunkX, final int chunkZ) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.light(this.lightAccess, chunkX, chunkZ); ++ } ++ if (blockEngine != null) { ++ blockEngine.light(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void relightChunk(final int chunkX, final int chunkZ) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.relight(this.lightAccess, chunkX, chunkZ); ++ } ++ if (blockEngine != null) { ++ blockEngine.relight(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkChunkEdges(final int chunkX, final int chunkZ) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void propagateChanges() { ++ synchronized (this.changedBlocks) { ++ if (this.changedBlocks.isEmpty()) { ++ return; ++ } ++ } ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ // TODO be smarter about this in the future ++ final Long2ObjectOpenHashMap> changedBlocks; ++ synchronized (this.changedBlocks) { ++ changedBlocks = this.changedBlocks.clone(); ++ this.changedBlocks.clear(); ++ } ++ ++ for (final Iterator>> iterator = changedBlocks.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Long2ObjectMap.Entry> entry = iterator.next(); ++ final long coordinate = entry.getLongKey(); ++ final Set positions = entry.getValue(); ++ ++ if (skyEngine != null) { ++ skyEngine.blocksChangedInChunk(this.lightAccess, MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate), positions); ++ } ++ if (blockEngine != null) { ++ blockEngine.blocksChangedInChunk(this.lightAccess, MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate), positions); ++ } ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++} diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java new file mode 100644 -index 000000000..048139d13 +index 000000000..42ce3b802 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java -@@ -0,0 +1,375 @@ +@@ -0,0 +1,381 @@ +package com.tuinity.tuinity.config; + +import com.destroystokyo.paper.util.SneakyThrow; @@ -1505,6 +3617,12 @@ index 000000000..048139d13 + } + } + ++ public static boolean useNewLightEngine; ++ ++ private static void useNewLightEngine() { ++ useNewLightEngine = TuinityConfig.getBoolean("use-new-light-engine", true); ++ } ++ + public static final class WorldConfig { + + public final String worldName; @@ -1658,10 +3776,10 @@ index 000000000..048139d13 \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/CachedLists.java b/src/main/java/com/tuinity/tuinity/util/CachedLists.java new file mode 100644 -index 000000000..104a5c7bd +index 000000000..21e50c75e --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java -@@ -0,0 +1,73 @@ +@@ -0,0 +1,74 @@ +package com.tuinity.tuinity.util; + +import net.minecraft.server.AxisAlignedBB; @@ -1733,6 +3851,7 @@ index 000000000..104a5c7bd + public static void reset() { + TEMP_COLLISION_LIST.completeReset(); + TEMP_GET_ENTITIES_LIST.completeReset(); ++ TEMP_GET_CHUNKS_LIST.completeReset(); + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java @@ -1891,14 +4010,15 @@ index 000000000..08ed24325 \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java new file mode 100644 -index 000000000..6d2851ffa +index 000000000..be408aebb --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java -@@ -0,0 +1,288 @@ +@@ -0,0 +1,335 @@ +package com.tuinity.tuinity.util.maplist; + +import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.bukkit.Bukkit; +import java.util.Arrays; +import java.util.NoSuchElementException; @@ -1941,6 +4061,48 @@ index 000000000..6d2851ffa + this.threadRestricted = threadRestricted; + } + ++ /* ++ public void check() { ++ int iterated = 0; ++ ReferenceOpenHashSet check = new ReferenceOpenHashSet<>(); ++ if (this.listElements != null) { ++ for (int i = 0; i < this.listSize; ++i) { ++ Object obj = this.listElements[i]; ++ if (obj != null) { ++ iterated++; ++ if (!check.add((E)obj)) { ++ throw new IllegalStateException("contains duplicate"); ++ } ++ if (!this.contains((E)obj)) { ++ throw new IllegalStateException("desync"); ++ } ++ } ++ } ++ } ++ ++ if (iterated != this.size()) { ++ throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size()); ++ } ++ ++ check.clear(); ++ iterated = 0; ++ for (final java.util.Iterator iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final E element = iterator.next(); ++ iterated++; ++ if (!check.add(element)) { ++ throw new IllegalStateException("contains duplicate (iterator is wrong)"); ++ } ++ if (!this.contains(element)) { ++ throw new IllegalStateException("desync (iterator is wrong)"); ++ } ++ } ++ ++ if (iterated != this.size()) { ++ throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size()); ++ } ++ } ++ */ ++ + protected final boolean allowSafeIteration() { + return !this.threadRestricted || Bukkit.isPrimaryThread(); + } @@ -1993,6 +4155,7 @@ index 000000000..6d2851ffa + if (this.allowSafeIteration() && this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { + this.defrag(); + } ++ //this.check(); + return true; + } + return false; @@ -2016,6 +4179,7 @@ index 000000000..6d2851ffa + this.listElements[listSize] = element; + this.listSize = listSize + 1; + ++ //this.check(); + return true; + } + @@ -2028,6 +4192,7 @@ index 000000000..6d2851ffa + Arrays.fill(this.listElements, 0, this.listSize, null); + this.listSize = 0; + this.firstInvalidIndex = -1; ++ //this.check(); + return; + } + @@ -2072,6 +4237,7 @@ index 000000000..6d2851ffa + Arrays.fill(backingArray, lastValidIndex, this.listSize, null); + this.listSize = lastValidIndex; + this.firstInvalidIndex = -1; ++ //this.check(); + } + + public E rawGet(final int index) { @@ -2183,12 +4349,1032 @@ index 000000000..6d2851ffa + } + } +} +diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java +new file mode 100644 +index 000000000..155d10994 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java +@@ -0,0 +1,295 @@ ++package com.tuinity.tuinity.util.misc; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import net.minecraft.server.MCUtil; ++ ++public final class Delayed26WayDistancePropagator3D { ++ ++ // this map is considered "stale" unless updates are propagated. ++ protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f); ++ ++ // this map is never stale ++ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); ++ ++ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when ++ // propagating updates ++ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); ++ ++ @FunctionalInterface ++ public static interface LevelChangeCallback { ++ ++ /** ++ * This can be called for intermediate updates. So do not rely on newLevel being close to or ++ * the exact level that is expected after a full propagation has occured. ++ */ ++ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); ++ ++ } ++ ++ protected final LevelChangeCallback changeCallback; ++ ++ public Delayed26WayDistancePropagator3D() { ++ this(null); ++ } ++ ++ public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) { ++ this.changeCallback = changeCallback; ++ } ++ ++ public int getLevel(final long pos) { ++ return this.levels.get(pos); ++ } ++ ++ public int getLevel(final int x, final int y, final int z) { ++ return this.levels.get(MCUtil.getSectionKey(x, y, z)); ++ } ++ ++ public void setSource(final int x, final int y, final int z, final int level) { ++ this.setSource(MCUtil.getSectionKey(x, y, z), level); ++ } ++ ++ public void setSource(final long coordinate, final int level) { ++ if ((level & 63) != level || level == 0) { ++ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); ++ } ++ ++ final byte byteLevel = (byte)level; ++ final byte oldLevel = this.sources.put(coordinate, byteLevel); ++ ++ if (oldLevel == byteLevel) { ++ return; // nothing to do ++ } ++ ++ // queue to update later ++ this.updatedSources.add(coordinate); ++ } ++ ++ public void removeSource(final int x, final int y, final int z) { ++ this.removeSource(MCUtil.getSectionKey(x, y, z)); ++ } ++ ++ public void removeSource(final long coordinate) { ++ if (this.sources.remove(coordinate) != 0) { ++ this.updatedSources.add(coordinate); ++ } ++ } ++ ++ // queues used for BFS propagating levels ++ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { ++ this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); ++ } ++ } ++ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { ++ this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); ++ } ++ } ++ protected long levelIncreaseWorkQueueBitset; ++ protected long levelRemoveWorkQueueBitset; ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << level); ++ } ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << index); ++ } ++ ++ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelRemoveWorkQueueBitset |= (1L << level); ++ } ++ ++ public void propagateUpdates() { ++ if (this.updatedSources.isEmpty()) { ++ return; ++ } ++ ++ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { ++ final long coordinate = iterator.nextLong(); ++ ++ final byte currentLevel = this.levels.get(coordinate); ++ final byte updatedSource = this.sources.get(coordinate); ++ ++ if (currentLevel == updatedSource) { ++ continue; ++ } ++ ++ if (updatedSource > currentLevel) { ++ // level increase ++ this.addToIncreaseWorkQueue(coordinate, updatedSource); ++ } else { ++ // level decrease ++ this.addToRemoveWorkQueue(coordinate, currentLevel); ++ // if the current coordinate is a source, then the decrease propagation will detect that and queue ++ // the source propagation ++ } ++ } ++ ++ this.updatedSources.clear(); ++ ++ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions ++ // make the removes remove less) ++ this.propagateIncreases(); ++ ++ // now we propagate the decreases (which will then re-propagate clobbered sources) ++ this.propagateDecreases(); ++ } ++ ++ protected void propagateIncreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); ++ this.levelIncreaseWorkQueueBitset != 0L; ++ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { ++ ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final boolean neighbourCheck = level < 0; ++ ++ final byte currentLevel; ++ if (neighbourCheck) { ++ level = (byte)-level; ++ currentLevel = this.levels.get(coordinate); ++ } else { ++ currentLevel = this.levels.putIfGreater(coordinate, level); ++ } ++ ++ if (neighbourCheck) { ++ // used when propagating from decrease to indicate that this level needs to check its neighbours ++ // this means the level at coordinate could be equal, but would still need neighbours checked ++ ++ if (currentLevel != level) { ++ // something caused the level to change, which means something propagated to it (which means ++ // us propagating here is redundant), or something removed the level (which means we ++ // cannot propagate further) ++ continue; ++ } ++ } else if (currentLevel >= level) { ++ // something higher/equal propagated ++ continue; ++ } ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); ++ } ++ ++ if (level == 1) { ++ // can't propagate 0 to neighbours ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = MCUtil.getSectionX(coordinate); ++ final int y = MCUtil.getSectionY(coordinate); ++ final int z = MCUtil.getSectionZ(coordinate); ++ ++ for (int dy = -1; dy <= 1; ++dy) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if ((dy | dz | dx) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z); ++ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected void propagateDecreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); ++ this.levelRemoveWorkQueueBitset != 0L; ++ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { ++ ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ final byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); ++ if (currentLevel == 0) { ++ // something else removed ++ continue; ++ } ++ ++ if (currentLevel > level) { ++ // something higher propagated here or we hit the propagation of another source ++ // in the second case we need to re-propagate because we could have just clobbered another source's ++ // propagation ++ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking ++ continue; ++ } ++ ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); ++ } ++ ++ final byte source = this.sources.get(coordinate); ++ if (source != 0) { ++ // must re-propagate source later ++ this.addToIncreaseWorkQueue(coordinate, source); ++ } ++ ++ if (level == 0) { ++ // can't propagate -1 to neighbours ++ // we have to check neighbours for removing 1 just in case the neighbour is 2 ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = MCUtil.getSectionX(coordinate); ++ final int y = MCUtil.getSectionY(coordinate); ++ final int z = MCUtil.getSectionZ(coordinate); ++ ++ for (int dy = -1; dy <= 1; ++dy) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if ((dy | dz | dx) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z); ++ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered in the process ++ this.propagateIncreases(); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java +new file mode 100644 +index 000000000..606417a8a +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java +@@ -0,0 +1,713 @@ ++package com.tuinity.tuinity.util.misc; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import net.minecraft.server.MCUtil; ++ ++public final class Delayed8WayDistancePropagator2D { ++ ++ // Test ++ /* ++ protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference, Delayed8WayDistancePropagator2D test) { ++ int got = test.getLevel(x, z); ++ ++ int expect = 0; ++ Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet(); ++ if (nearest != null) { ++ for (Object _obj : nearest) { ++ if (_obj instanceof Ticket) { ++ Ticket ticket = (Ticket)_obj; ++ long ticketCoord = reference.getLastCoordinate(ticket); ++ int viewDistance = reference.getLastViewDistance(ticket); ++ int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x), ++ com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z)); ++ int level = viewDistance - distance; ++ if (level > expect) { ++ expect = level; ++ } ++ } ++ } ++ } ++ ++ if (expect != got) { ++ throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got); ++ } ++ } ++ ++ static class Ticket { ++ ++ int x; ++ int z; ++ ++ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty ++ = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); ++ ++ } ++ ++ public static void main(final String[] args) { ++ com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap() { ++ @Override ++ protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(Ticket object) { ++ return object.empty; ++ } ++ }; ++ Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D(); ++ ++ final int maxDistance = 64; ++ // test origin ++ { ++ Ticket originTicket = new Ticket(); ++ int originDistance = 31; ++ // test single source ++ reference.add(originTicket, 0, 0, originDistance); ++ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ // test single source decrease ++ reference.update(originTicket, 0, 0, originDistance/2); ++ test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ // test source increase ++ originDistance = 2*originDistance; ++ reference.update(originTicket, 0, 0, originDistance); ++ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { ++ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ reference.remove(originTicket); ++ test.removeSource(0, 0); test.propagateUpdates(); ++ } ++ ++ // test multiple sources at origin ++ { ++ int originDistance = 31; ++ java.util.List list = new java.util.ArrayList<>(); ++ for (int i = 0; i < 10; ++i) { ++ Ticket a = new Ticket(); ++ list.add(a); ++ a.x = (i & 1) == 1 ? -i : i; ++ a.z = (i & 1) == 1 ? -i : i; ++ } ++ for (Ticket ticket : list) { ++ reference.add(ticket, ticket.x, ticket.z, originDistance); ++ test.setSource(ticket.x, ticket.z, originDistance); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level decrease ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance/2); ++ test.setSource(ticket.x, ticket.z, originDistance/2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level increase ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance*2); ++ test.setSource(ticket.x, ticket.z, originDistance*2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket remove ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ if ((i & 3) != 0) { ++ continue; ++ } ++ Ticket ticket = list.get(i); ++ reference.remove(ticket); ++ test.removeSource(ticket.x, ticket.z); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ } ++ ++ // now test at coordinate offsets ++ // test offset ++ { ++ Ticket originTicket = new Ticket(); ++ int originDistance = 31; ++ int offX = 54432; ++ int offZ = -134567; ++ // test single source ++ reference.add(originTicket, offX, offZ, originDistance); ++ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx + offX, dz + offZ, reference, test); ++ } ++ } ++ // test single source decrease ++ reference.update(originTicket, offX, offZ, originDistance/2); ++ test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx + offX, dz + offZ, reference, test); ++ } ++ } ++ // test source increase ++ originDistance = 2*originDistance; ++ reference.update(originTicket, offX, offZ, originDistance); ++ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { ++ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { ++ test(dx + offX, dz + offZ, reference, test); ++ } ++ } ++ ++ reference.remove(originTicket); ++ test.removeSource(offX, offZ); test.propagateUpdates(); ++ } ++ ++ // test multiple sources at origin ++ { ++ int originDistance = 31; ++ int offX = 54432; ++ int offZ = -134567; ++ java.util.List list = new java.util.ArrayList<>(); ++ for (int i = 0; i < 10; ++i) { ++ Ticket a = new Ticket(); ++ list.add(a); ++ a.x = offX + ((i & 1) == 1 ? -i : i); ++ a.z = offZ + ((i & 1) == 1 ? -i : i); ++ } ++ for (Ticket ticket : list) { ++ reference.add(ticket, ticket.x, ticket.z, originDistance); ++ test.setSource(ticket.x, ticket.z, originDistance); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level decrease ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance/2); ++ test.setSource(ticket.x, ticket.z, originDistance/2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level increase ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance*2); ++ test.setSource(ticket.x, ticket.z, originDistance*2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket remove ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ if ((i & 3) != 0) { ++ continue; ++ } ++ Ticket ticket = list.get(i); ++ reference.remove(ticket); ++ test.removeSource(ticket.x, ticket.z); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ } ++ } ++ */ ++ ++ // this map is considered "stale" unless updates are propagated. ++ protected final LevelMap levels = new LevelMap(8192*2, 0.6f); ++ ++ // this map is never stale ++ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); ++ ++ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when ++ // propagating updates ++ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); ++ ++ @FunctionalInterface ++ public static interface LevelChangeCallback { ++ ++ /** ++ * This can be called for intermediate updates. So do not rely on newLevel being close to or ++ * the exact level that is expected after a full propagation has occured. ++ */ ++ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); ++ ++ } ++ ++ protected final LevelChangeCallback changeCallback; ++ ++ public Delayed8WayDistancePropagator2D() { ++ this(null); ++ } ++ ++ public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) { ++ this.changeCallback = changeCallback; ++ } ++ ++ public int getLevel(final long pos) { ++ return this.levels.get(pos); ++ } ++ ++ public int getLevel(final int x, final int z) { ++ return this.levels.get(MCUtil.getCoordinateKey(x, z)); ++ } ++ ++ public void setSource(final int x, final int z, final int level) { ++ this.setSource(MCUtil.getCoordinateKey(x, z), level); ++ } ++ ++ public void setSource(final long coordinate, final int level) { ++ if ((level & 63) != level || level == 0) { ++ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); ++ } ++ ++ final byte byteLevel = (byte)level; ++ final byte oldLevel = this.sources.put(coordinate, byteLevel); ++ ++ if (oldLevel == byteLevel) { ++ return; // nothing to do ++ } ++ ++ // queue to update later ++ this.updatedSources.add(coordinate); ++ } ++ ++ public void removeSource(final int x, final int z) { ++ this.removeSource(MCUtil.getCoordinateKey(x, z)); ++ } ++ ++ public void removeSource(final long coordinate) { ++ if (this.sources.remove(coordinate) != 0) { ++ this.updatedSources.add(coordinate); ++ } ++ } ++ ++ // queues used for BFS propagating levels ++ protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { ++ this.levelIncreaseWorkQueues[i] = new WorkQueue(); ++ } ++ } ++ protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { ++ this.levelRemoveWorkQueues[i] = new WorkQueue(); ++ } ++ } ++ protected long levelIncreaseWorkQueueBitset; ++ protected long levelRemoveWorkQueueBitset; ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { ++ final WorkQueue queue = this.levelIncreaseWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << level); ++ } ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { ++ final WorkQueue queue = this.levelIncreaseWorkQueues[index]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << index); ++ } ++ ++ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { ++ final WorkQueue queue = this.levelRemoveWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelRemoveWorkQueueBitset |= (1L << level); ++ } ++ ++ public void propagateUpdates() { ++ if (this.updatedSources.isEmpty()) { ++ return; ++ } ++ ++ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { ++ final long coordinate = iterator.nextLong(); ++ ++ final byte currentLevel = this.levels.get(coordinate); ++ final byte updatedSource = this.sources.get(coordinate); ++ ++ if (currentLevel == updatedSource) { ++ continue; ++ } ++ ++ if (updatedSource > currentLevel) { ++ // level increase ++ this.addToIncreaseWorkQueue(coordinate, updatedSource); ++ } else { ++ // level decrease ++ this.addToRemoveWorkQueue(coordinate, currentLevel); ++ // if the current coordinate is a source, then the decrease propagation will detect that and queue ++ // the source propagation ++ } ++ } ++ ++ this.updatedSources.clear(); ++ ++ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions ++ // make the removes remove less) ++ this.propagateIncreases(); ++ ++ // now we propagate the decreases (which will then re-propagate clobbered sources) ++ this.propagateDecreases(); ++ } ++ ++ protected void propagateIncreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); ++ this.levelIncreaseWorkQueueBitset != 0L; ++ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { ++ ++ final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final boolean neighbourCheck = level < 0; ++ ++ final byte currentLevel; ++ if (neighbourCheck) { ++ level = (byte)-level; ++ currentLevel = this.levels.get(coordinate); ++ } else { ++ currentLevel = this.levels.putIfGreater(coordinate, level); ++ } ++ ++ if (neighbourCheck) { ++ // used when propagating from decrease to indicate that this level needs to check its neighbours ++ // this means the level at coordinate could be equal, but would still need neighbours checked ++ ++ if (currentLevel != level) { ++ // something caused the level to change, which means something propagated to it (which means ++ // us propagating here is redundant), or something removed the level (which means we ++ // cannot propagate further) ++ continue; ++ } ++ } else if (currentLevel >= level) { ++ // something higher/equal propagated ++ continue; ++ } ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); ++ } ++ ++ if (level == 1) { ++ // can't propagate 0 to neighbours ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = (int)coordinate; ++ final int z = (int)(coordinate >>> 32); ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ if ((dx | dz) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); ++ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ } ++ ++ protected void propagateDecreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); ++ this.levelRemoveWorkQueueBitset != 0L; ++ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { ++ ++ final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ final byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); ++ if (currentLevel == 0) { ++ // something else removed ++ continue; ++ } ++ ++ if (currentLevel > level) { ++ // something higher propagated here or we hit the propagation of another source ++ // in the second case we need to re-propagate because we could have just clobbered another source's ++ // propagation ++ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking ++ continue; ++ } ++ ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); ++ } ++ ++ final byte source = this.sources.get(coordinate); ++ if (source != 0) { ++ // must re-propagate source later ++ this.addToIncreaseWorkQueue(coordinate, source); ++ } ++ ++ if (level == 0) { ++ // can't propagate -1 to neighbours ++ // we have to check neighbours for removing 1 just in case the neighbour is 2 ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = (int)coordinate; ++ final int z = (int)(coordinate >>> 32); ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ if ((dx | dz) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); ++ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered in the process ++ this.propagateIncreases(); ++ } ++ ++ protected static final class LevelMap extends Long2ByteOpenHashMap { ++ public LevelMap() { ++ super(); ++ } ++ ++ public LevelMap(final int expected, final float loadFactor) { ++ super(expected, loadFactor); ++ } ++ ++ // copied from superclass ++ private int find(final long k) { ++ if (k == 0L) { ++ return this.containsNullKey ? this.n : -(this.n + 1); ++ } else { ++ final long[] key = this.key; ++ long curr; ++ int pos; ++ if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) { ++ return -(pos + 1); ++ } else if (k == curr) { ++ return pos; ++ } else { ++ while((curr = key[pos = pos + 1 & this.mask]) != 0L) { ++ if (k == curr) { ++ return pos; ++ } ++ } ++ ++ return -(pos + 1); ++ } ++ } ++ } ++ ++ // copied from superclass ++ private void insert(final int pos, final long k, final byte v) { ++ if (pos == this.n) { ++ this.containsNullKey = true; ++ } ++ ++ this.key[pos] = k; ++ this.value[pos] = v; ++ if (this.size++ >= this.maxFill) { ++ this.rehash(HashCommon.arraySize(this.size + 1, this.f)); ++ } ++ } ++ ++ // copied from superclass ++ public byte putIfGreater(final long key, final byte value) { ++ final int pos = this.find(key); ++ if (pos < 0) { ++ if (this.defRetValue < value) { ++ this.insert(-pos - 1, key, value); ++ } ++ return this.defRetValue; ++ } else { ++ final byte curr = this.value[pos]; ++ if (value > curr) { ++ this.value[pos] = value; ++ return curr; ++ } ++ return curr; ++ } ++ } ++ ++ // copied from superclass ++ private void removeEntry(final int pos) { ++ --this.size; ++ this.shiftKeys(pos); ++ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { ++ this.rehash(this.n / 2); ++ } ++ } ++ ++ // copied from superclass ++ private void removeNullEntry() { ++ this.containsNullKey = false; ++ --this.size; ++ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { ++ this.rehash(this.n / 2); ++ } ++ } ++ ++ // copied from superclass ++ public byte removeIfGreaterOrEqual(final long key, final byte value) { ++ if (key == 0L) { ++ if (!this.containsNullKey) { ++ return this.defRetValue; ++ } ++ final byte current = this.value[this.n]; ++ if (value >= current) { ++ this.removeNullEntry(); ++ return current; ++ } ++ return current; ++ } else { ++ long[] keys = this.key; ++ byte[] values = this.value; ++ long curr; ++ int pos; ++ if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) { ++ return this.defRetValue; ++ } else if (key == curr) { ++ final byte current = values[pos]; ++ if (value >= current) { ++ this.removeEntry(pos); ++ return current; ++ } ++ return current; ++ } else { ++ while((curr = keys[pos = pos + 1 & this.mask]) != 0L) { ++ if (key == curr) { ++ final byte current = values[pos]; ++ if (value >= current) { ++ this.removeEntry(pos); ++ return current; ++ } ++ return current; ++ } ++ } ++ ++ return this.defRetValue; ++ } ++ } ++ } ++ } ++ ++ protected static final class WorkQueue { ++ ++ public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); ++ public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); ++ ++ } ++ ++ protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue { ++ ++ /** ++ * Assumes non-empty. If empty, undefined behaviour. ++ */ ++ public long removeFirstLong() { ++ // copied from superclass ++ long t = this.array[this.start]; ++ if (++this.start == this.length) { ++ this.start = 0; ++ } ++ ++ return t; ++ } ++ } ++ ++ protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue { ++ ++ /** ++ * Assumes non-empty. If empty, undefined behaviour. ++ */ ++ public byte removeFirstByte() { ++ // copied from superclass ++ byte t = this.array[this.start]; ++ if (++this.start == this.length) { ++ this.start = 0; ++ } ++ ++ return t; ++ } ++ } ++} diff --git a/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java new file mode 100644 -index 000000000..b321ad516 +index 000000000..002abb3cb --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java -@@ -0,0 +1,162 @@ +@@ -0,0 +1,165 @@ +package com.tuinity.tuinity.voxel; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; @@ -2334,6 +5520,9 @@ index 000000000..b321ad516 + + @Override + public double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, double d0) { // collide ++ if (this.aabb.isEmpty() || axisalignedbb.isEmpty()) { ++ return d0; ++ } + switch (enumdirection_enumaxis.ordinal()) { + case 0: + return AxisAlignedBB.collideX(this.aabb, axisalignedbb, d0); @@ -2352,10 +5541,10 @@ index 000000000..b321ad516 + } +} diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java -index ed9b2f9ad..6aa9f0733 100644 +index ed9b2f9ad..d759a6c2b 100644 --- a/src/main/java/net/minecraft/server/AxisAlignedBB.java +++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java -@@ -13,6 +13,149 @@ public class AxisAlignedBB { +@@ -13,6 +13,140 @@ public class AxisAlignedBB { public final double maxY; public final double maxZ; @@ -2391,9 +5580,6 @@ index ed9b2f9ad..6aa9f0733 100644 + } + + public static double collideX(AxisAlignedBB target, AxisAlignedBB source, double source_move) { -+ if (target.isEmpty() || source.isEmpty()) { -+ return source_move; -+ } + if (Math.abs(source_move) < MCUtil.COLLISION_EPSILON) { + return 0.0; + } @@ -2419,9 +5605,6 @@ index ed9b2f9ad..6aa9f0733 100644 + } + + public static double collideY(AxisAlignedBB target, AxisAlignedBB source, double source_move) { -+ if (target.isEmpty() || source.isEmpty()) { -+ return source_move; -+ } + if (Math.abs(source_move) < MCUtil.COLLISION_EPSILON) { + return 0.0; + } @@ -2446,9 +5629,6 @@ index ed9b2f9ad..6aa9f0733 100644 + } + + public static double collideZ(AxisAlignedBB target, AxisAlignedBB source, double source_move) { -+ if (target.isEmpty() || source.isEmpty()) { -+ return source_move; -+ } + if (Math.abs(source_move) < MCUtil.COLLISION_EPSILON) { + return 0.0; + } @@ -2505,7 +5685,7 @@ index ed9b2f9ad..6aa9f0733 100644 public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5) { this.minX = Math.min(d0, d3); this.minY = Math.min(d1, d4); -@@ -185,6 +328,7 @@ public class AxisAlignedBB { +@@ -185,6 +319,7 @@ public class AxisAlignedBB { return new AxisAlignedBB(d0, d1, d2, d3, d4, d5); } @@ -2513,7 +5693,7 @@ index ed9b2f9ad..6aa9f0733 100644 public AxisAlignedBB d(double d0, double d1, double d2) { return new AxisAlignedBB(this.minX + d0, this.minY + d1, this.minZ + d2, this.maxX + d0, this.maxY + d1, this.maxZ + d2); } -@@ -193,6 +337,7 @@ public class AxisAlignedBB { +@@ -193,6 +328,7 @@ public class AxisAlignedBB { return new AxisAlignedBB(this.minX + (double) blockposition.getX(), this.minY + (double) blockposition.getY(), this.minZ + (double) blockposition.getZ(), this.maxX + (double) blockposition.getX(), this.maxY + (double) blockposition.getY(), this.maxZ + (double) blockposition.getZ()); } @@ -2521,7 +5701,7 @@ index ed9b2f9ad..6aa9f0733 100644 public AxisAlignedBB c(Vec3D vec3d) { return this.d(vec3d.x, vec3d.y, vec3d.z); } -@@ -212,6 +357,7 @@ public class AxisAlignedBB { +@@ -212,6 +348,7 @@ public class AxisAlignedBB { return this.e(vec3d.x, vec3d.y, vec3d.z); } @@ -3016,7 +6196,7 @@ index a33303c31..ce57e6a4a 100644 return this.b.equals(entityliving.getEntityType()) && this.d.test(entityliving); } diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java -index 1f334d632..2760b377d 100644 +index 1f334d632..4d1ac4e6b 100644 --- a/src/main/java/net/minecraft/server/BlockBase.java +++ b/src/main/java/net/minecraft/server/BlockBase.java @@ -295,21 +295,23 @@ public abstract class BlockBase { @@ -3047,7 +6227,15 @@ index 1f334d632..2760b377d 100644 protected BlockData(Block block, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { super(block, immutablemap, mapcodec); -@@ -343,12 +345,22 @@ public abstract class BlockBase { +@@ -328,6 +330,7 @@ public abstract class BlockBase { + this.n = blockbase_info.s; + this.o = blockbase_info.t; + this.p = blockbase_info.u; ++ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Tuinity + } + // Paper start - impl cached craft block data, lazy load to fix issue with loading at the wrong time + private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; +@@ -343,12 +346,100 @@ public abstract class BlockBase { protected Fluid fluid; // Paper end @@ -3057,6 +6245,30 @@ index 1f334d632..2760b377d 100644 + return this.shapeExceedsCube; + } + // Tuinity end ++ ++ // Tuinity start ++ protected int opacityIfCached = -1; ++ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] ++ public final int getOpacityIfCached() { ++ return this.opacityIfCached; ++ } ++ ++ protected final boolean conditionallyFullOpaque; ++ public final boolean isConditionallyFullOpaque() { ++ return this.conditionallyFullOpaque; ++ } ++ // Tuinity end ++ ++ // Tuinity start ++ public static boolean isSpecialCollidingBlock(BlockBase.BlockData blockData) { ++ return blockData.shapeExceedsCube() || blockData.getBlock() == Blocks.MOVING_PISTON; ++ } ++ ++ protected long blockCollisionBehavior = ChunkSection.KNOWN_SPECIAL_BLOCK; ++ public final long getBlockCollisionBehavior() { ++ return this.blockCollisionBehavior; ++ } ++ // Tuinity end + public void a() { this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid() @@ -3067,10 +6279,64 @@ index 1f334d632..2760b377d 100644 + this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here + this.staticPathType = null; // Tuinity - cache static path type + this.neighbourOverridePathType = null; // Tuinity - cache static path types ++ this.opacityIfCached = this.a == null || this.isConditionallyFullOpaque() ? -1 : this.a.getOpacity(); // Tuinity - cache opacity for light ++ // Tuinity start - optimise culling shape cache for light ++ if (this.a != null && this.a.getCullingShapeCache() != null) { ++ for (int i = 0, len = this.a.getCullingShapeCache().length; i < len; ++i) { ++ VoxelShape face = this.a.getCullingShapeCache()[i].simplify(); ++ if (face.isEmpty()) { ++ this.a.getCullingShapeCache()[i] = VoxelShapes.getEmptyShape(); ++ continue; ++ } ++ List boxes = face.getBoundingBoxesRepresentation(); ++ if (boxes.size() == 1) { ++ AxisAlignedBB boundingBox = boxes.get(0); ++ if (boundingBox.equals(VoxelShapes.optimisedFullCube.aabb)) { ++ this.a.getCullingShapeCache()[i] = VoxelShapes.fullCube(); ++ } else { ++ this.a.getCullingShapeCache()[i] = VoxelShapes.of(boundingBox); ++ if (!(this.a.getCullingShapeCache()[i] instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) && ++ this.a.getCullingShapeCache()[i].getBoundingBoxesRepresentation().size() == 1) { ++ this.a.getCullingShapeCache()[i] = new com.tuinity.tuinity.voxel.AABBVoxelShape(boundingBox); ++ } ++ } ++ continue; ++ } ++ this.a.getCullingShapeCache()[i] = face; ++ } ++ } ++ // Tuinity end - optimise culling shape cache for light ++ // Tuinity start - init block collision behavior field ++ if (isSpecialCollidingBlock(this)) { ++ this.blockCollisionBehavior = ChunkSection.KNOWN_SPECIAL_BLOCK; ++ } else { ++ try { ++ VoxelShape constantShape = this.getCollisionShape(null, null, null); ++ if (constantShape == null) { ++ this.blockCollisionBehavior = ChunkSection.KNOWN_UNKNOWN_BLOCK; ++ } else { ++ if (constantShape.isEmpty()) { ++ this.blockCollisionBehavior = ChunkSection.KNOWN_EMPTY_BLOCK; ++ } else { ++ List boxes = constantShape.simplify().getBoundingBoxesRepresentation(); ++ if (boxes.size() == 1 && boxes.get(0).equals(VoxelShapes.optimisedFullCube.aabb)) { ++ this.blockCollisionBehavior = ChunkSection.KNOWN_FULL_BLOCK; ++ } else { ++ this.blockCollisionBehavior = ChunkSection.KNOWN_UNKNOWN_BLOCK; ++ } ++ } ++ } ++ } catch (ThreadDeath thr) { ++ throw thr; ++ } catch (Throwable thr) { ++ this.blockCollisionBehavior = ChunkSection.KNOWN_UNKNOWN_BLOCK; ++ } ++ } ++ // Tuinity end - init block collision behavior field } -@@ -372,10 +384,12 @@ public abstract class BlockBase { +@@ -372,10 +463,12 @@ public abstract class BlockBase { return this.a != null ? this.a.g : this.getBlock().b(this.p(), iblockaccess, blockposition); } @@ -3083,7 +6349,7 @@ index 1f334d632..2760b377d 100644 public VoxelShape a(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { return this.a != null && this.a.i != null ? this.a.i[enumdirection.ordinal()] : VoxelShapes.a(this.c(iblockaccess, blockposition), enumdirection); } -@@ -385,7 +399,7 @@ public abstract class BlockBase { +@@ -385,7 +478,7 @@ public abstract class BlockBase { } public final boolean d() { // Paper @@ -3092,6 +6358,18 @@ index 1f334d632..2760b377d 100644 } public final boolean e() { // Paper +@@ -675,9 +768,9 @@ public abstract class BlockBase { + private static final int f = EnumBlockSupport.values().length; + protected final boolean a; + private final boolean g; +- private final int h; ++ private final int h; private final int getOpacity() { return this.h; } // Tuinity - OBFHELPER + @Nullable +- private final VoxelShape[] i; ++ private final VoxelShape[] i; private final VoxelShape[] getCullingShapeCache () { return this.i; } // Tuinity - OBFHELPER + protected final VoxelShape b; + protected final boolean c; + private final boolean[] j; diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java index 12a023044..9e5e6de52 100644 --- a/src/main/java/net/minecraft/server/BlockChest.java @@ -3203,10 +6481,10 @@ index 2d887af90..2291135ea 100644 @Override public BlockPosition immutableCopy() { diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java -index dcbae1c45..f3702ed75 100644 +index dcbae1c45..9d749dea1 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java -@@ -91,6 +91,151 @@ public class Chunk implements IChunkAccess { +@@ -91,6 +91,186 @@ public class Chunk implements IChunkAccess { private final int[] inventoryEntityCounts = new int[16]; // Paper end @@ -3259,6 +6537,41 @@ index dcbae1c45..f3702ed75 100644 + } + } + // Tuinity end - optimise hard collision handling ++ // Tuinity start - rewrite light engine ++ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(false); ++ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(true); ++ private volatile boolean wasLoadedFromDisk; ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.blockNibbles; ++ } ++ ++ @Override ++ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.blockNibbles = nibbles; ++ } ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.skyNibbles; ++ } ++ ++ @Override ++ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.skyNibbles = nibbles; ++ } ++ ++ @Override ++ public void setWasLoadedFromDisk(boolean wasLoadedFromDisk) { ++ this.wasLoadedFromDisk = wasLoadedFromDisk; ++ } ++ ++ @Override ++ public boolean wasLoadedFromDisk() { ++ return this.wasLoadedFromDisk; ++ } ++ // Tuinity end - rewrite light engine + + // Tuinity start - entity slices by class + private final com.tuinity.tuinity.chunk.ChunkEntitiesByClass entitiesByClass = new com.tuinity.tuinity.chunk.ChunkEntitiesByClass(this); @@ -3358,7 +6671,19 @@ index dcbae1c45..f3702ed75 100644 public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { this.sections = new ChunkSection[16]; this.e = Maps.newHashMap(); -@@ -330,7 +475,7 @@ public class Chunk implements IChunkAccess { +@@ -297,6 +477,11 @@ public class Chunk implements IChunkAccess { + + public Chunk(World world, ProtoChunk protochunk) { + this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null); ++ // Tuinity start - copy over protochunk light ++ this.blockNibbles = protochunk.getBlockNibbles(); ++ this.skyNibbles = protochunk.getSkyNibbles(); ++ this.wasLoadedFromDisk = protochunk.wasLoadedFromDisk(); ++ // Tuinity end - copy over protochunk light + Iterator iterator = protochunk.y().iterator(); + + while (iterator.hasNext()) { +@@ -330,7 +515,7 @@ public class Chunk implements IChunkAccess { Entry entry = (Entry) iterator.next(); if (ChunkStatus.FULL.h().contains(entry.getKey())) { @@ -3367,7 +6692,7 @@ index dcbae1c45..f3702ed75 100644 } } -@@ -547,6 +692,7 @@ public class Chunk implements IChunkAccess { +@@ -547,6 +732,7 @@ public class Chunk implements IChunkAccess { @Override public void a(Entity entity) { @@ -3375,7 +6700,7 @@ index dcbae1c45..f3702ed75 100644 this.q = true; int i = MathHelper.floor(entity.locX() / 16.0D); int j = MathHelper.floor(entity.locZ() / 16.0D); -@@ -592,8 +738,8 @@ public class Chunk implements IChunkAccess { +@@ -592,8 +778,8 @@ public class Chunk implements IChunkAccess { entity.chunkX = this.loc.x; entity.chunkY = k; entity.chunkZ = this.loc.z; @@ -3386,7 +6711,7 @@ index dcbae1c45..f3702ed75 100644 // Paper start if (entity instanceof EntityItem) { itemCounts[k]++; -@@ -616,6 +762,7 @@ public class Chunk implements IChunkAccess { +@@ -616,6 +802,7 @@ public class Chunk implements IChunkAccess { } public void a(Entity entity, int i) { @@ -3394,7 +6719,7 @@ index dcbae1c45..f3702ed75 100644 if (i < 0) { i = 0; } -@@ -630,7 +777,7 @@ public class Chunk implements IChunkAccess { +@@ -630,7 +817,7 @@ public class Chunk implements IChunkAccess { entity.entitySlice = null; entity.inChunk = false; } @@ -3403,7 +6728,7 @@ index dcbae1c45..f3702ed75 100644 return; } if (entity instanceof EntityItem) { -@@ -873,6 +1020,7 @@ public class Chunk implements IChunkAccess { +@@ -873,6 +1060,7 @@ public class Chunk implements IChunkAccess { } public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { @@ -3411,7 +6736,7 @@ index dcbae1c45..f3702ed75 100644 int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); -@@ -912,6 +1060,7 @@ public class Chunk implements IChunkAccess { +@@ -912,6 +1100,7 @@ public class Chunk implements IChunkAccess { } public void a(@Nullable EntityTypes entitytypes, AxisAlignedBB axisalignedbb, List list, Predicate predicate) { @@ -3419,7 +6744,7 @@ index dcbae1c45..f3702ed75 100644 int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); -@@ -941,7 +1090,9 @@ public class Chunk implements IChunkAccess { +@@ -941,7 +1130,9 @@ public class Chunk implements IChunkAccess { } @@ -3430,33 +6755,50 @@ index dcbae1c45..f3702ed75 100644 int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); diff --git a/src/main/java/net/minecraft/server/ChunkCache.java b/src/main/java/net/minecraft/server/ChunkCache.java -index 8eecdcde5..53c977513 100644 +index 8eecdcde5..ab8664ef2 100644 --- a/src/main/java/net/minecraft/server/ChunkCache.java +++ b/src/main/java/net/minecraft/server/ChunkCache.java -@@ -12,6 +12,142 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { +@@ -1,5 +1,6 @@ + package net.minecraft.server; + ++import java.util.List; + import java.util.function.Predicate; + import java.util.stream.Stream; + import javax.annotation.Nullable; +@@ -12,6 +13,216 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { protected boolean d; protected final World e; protected final World getWorld() { return e; } // Paper - OBFHELPER + // Tuinity start - optimise pathfinder collision detection + @Override + public boolean getCubes(Entity entity) { -+ return !this.collidesWithAnyBlockOrWorldBorder(entity, entity.getBoundingBox()); ++ return !this.getCollisionsForBlocksOrWorldBorder(entity, entity.getBoundingBox(), null, true, null); + } + + @Override + public boolean getCubes(Entity entity, AxisAlignedBB axisalignedbb) { -+ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb); ++ return !this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, true, null); + } + + @Override + public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { -+ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb); ++ return !this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, true, null); + } + -+ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { ++ public boolean getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, ++ boolean collidesWithUnloaded, ++ java.util.function.BiPredicate predicate) { ++ boolean ret = false; ++ final boolean checkOnly = true; ++ + if (entity != null) { + if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) { -+ return true; ++ if (checkOnly) { ++ return true; ++ } else { ++ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list); ++ ret = true; ++ } + } + } + @@ -3471,12 +6813,12 @@ index 8eecdcde5..53c977513 100644 + + + BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); -+ VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy ++ VoxelShapeCollision collisionShape = null; + + // special cases: + if (minBlockY > 255 || maxBlockY < 0) { + // no point in checking -+ return false; ++ return ret; + } + + int minYIterate = Math.max(0, minBlockY); @@ -3503,7 +6845,15 @@ index 8eecdcde5..53c977513 100644 + Chunk chunk = (Chunk)this.getChunkIfLoaded(currChunkX, currChunkZ); + + if (chunk == null) { -+ return true; ++ if (collidesWithUnloaded) { ++ if (checkOnly) { ++ return true; ++ } else { ++ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; + } + + ChunkSection[] sections = chunk.getSections(); @@ -3519,47 +6869,103 @@ index 8eecdcde5..53c977513 100644 + continue; + } + ++ int minXIterate; ++ int maxXIterate; ++ int minZIterate; ++ int maxZIterate; ++ ++ boolean sectionHasSpecial = section.hasSpecialCollidingBlocks(); ++ if (sectionHasSpecial && currChunkX == minChunkX) { ++ minXIterate = minX + 1; ++ } else { ++ minXIterate = minX; ++ } ++ if (sectionHasSpecial && currChunkX == maxChunkX) { ++ maxXIterate = maxX - 1; ++ } else { ++ maxXIterate = maxX; ++ } ++ ++ if (sectionHasSpecial && currChunkZ == minChunkZ) { ++ minZIterate = minZ + 1; ++ } else { ++ minZIterate = minZ; ++ } ++ if (sectionHasSpecial && currChunkZ == maxChunkZ) { ++ maxZIterate = maxZ - 1; ++ } else { ++ maxZIterate = maxZ; ++ } ++ + DataPaletteBlock blocks = section.blockIds; -+ int blockKeyY = (currY & 15) << 8; + -+ int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0; ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ block_search_loop: ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8); ++ long blockInfo = section.getKnownBlockInfo(localBlockIndex); ++ switch ((int)blockInfo) { ++ case (int)ChunkSection.KNOWN_EMPTY_BLOCK: { ++ continue block_search_loop; ++ } ++ case (int)ChunkSection.KNOWN_FULL_BLOCK: { ++ AxisAlignedBB box = new AxisAlignedBB( ++ currX | chunkXGlobalPos, currY, currZ | chunkZGlobalPos, ++ (currX | chunkXGlobalPos) + 1, currY + 1, (currZ | chunkZGlobalPos) + 1, ++ false ++ ); ++ if (predicate != null) { ++ if (!box.voxelShapeIntersect(axisalignedbb)) { ++ continue block_search_loop; ++ } ++ // fall through to get the block for the predicate ++ } else { ++ if (box.voxelShapeIntersect(axisalignedbb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ list.add(box); ++ ret = true; ++ } ++ } ++ continue block_search_loop; ++ } ++ } ++ default: { ++ int blockX = currX | chunkXGlobalPos; ++ int blockY = currY; ++ int blockZ = currZ | chunkZGlobalPos; + -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ int blockKeyZY = blockKeyY | (currZ << 4); -+ int blockZ = currZ | chunkZGlobalPos; // world position ++ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3 || (edgeCount != 0 && blockInfo != ChunkSection.KNOWN_SPECIAL_BLOCK)) { ++ continue block_search_loop; ++ } + -+ int edgeCountZY; -+ if (blockZ == minBlockZ || blockZ == maxBlockZ) { -+ edgeCountZY = edgeCountY + 1; -+ } else { -+ edgeCountZY = edgeCountY; -+ } ++ IBlockData blockData = blocks.rawGet(localBlockIndex); + -+ for (int currX = minX; currX <= maxX; ++currX) { -+ int blockX = currX | chunkXGlobalPos; // world position ++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { ++ if (collisionShape == null) { ++ collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); ++ } ++ mutablePos.setValues(blockX, blockY, blockZ); ++ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); ++ if (voxelshape2 != VoxelShapes.getEmptyShape()) { ++ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)blockY, (double)blockZ); + -+ int edgeCountFull; -+ if (blockX == minBlockX || blockX == maxBlockX) { -+ edgeCountFull = edgeCountZY + 1; -+ } else { -+ edgeCountFull = edgeCountZY; -+ } ++ if (predicate != null && !predicate.test(blockData, mutablePos)) { ++ continue block_search_loop; ++ } + -+ if (edgeCountFull == 3) { -+ continue; -+ } -+ -+ int blockKeyFull = blockKeyZY | currX; -+ IBlockData blockData = blocks.rawGet(blockKeyFull); -+ -+ if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.setValues(blockX, currY, blockZ); -+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); -+ if (voxelshape2 != VoxelShapes.getEmptyShape()) { -+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ); -+ -+ if (voxelshape3.intersects(axisalignedbb)) { -+ return true; ++ if (checkOnly) { ++ if (voxelshape3.intersects(axisalignedbb)) { ++ return true; ++ } ++ } else { ++ ret |= VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list); ++ } ++ } + } + } + } @@ -3569,7 +6975,7 @@ index 8eecdcde5..53c977513 100644 + } + } + -+ return false; ++ return ret; + } + // Tuinity end - optimise pathfinder collision detection + @@ -4177,7 +7583,7 @@ index 6acb5f05a..84429f12d 100644 try { boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java -index 8e7da2c5f..5eb14c4cd 100644 +index 8e7da2c5f..83761ddf0 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -24,6 +24,14 @@ public class ChunkRegionLoader { @@ -4195,7 +7601,95 @@ index 8e7da2c5f..5eb14c4cd 100644 // Paper start - guard against serializing mismatching coordinates // TODO Note: This needs to be re-checked each update public static ChunkCoordIntPair getChunkCoordinate(NBTTagCompound chunkData) { -@@ -401,10 +409,10 @@ public class ChunkRegionLoader { +@@ -85,13 +93,15 @@ public class ChunkRegionLoader { + ProtoChunkTickList protochunkticklist1 = new ProtoChunkTickList<>((fluidtype) -> { + return fluidtype == null || fluidtype == FluidTypes.EMPTY; + }, chunkcoordintpair, nbttagcompound1.getList("LiquidsToBeTicked", 9)); +- boolean flag = nbttagcompound1.getBoolean("isLightOn"); ++ boolean flag = nbttagcompound1.getBoolean("isLightOn"); boolean canUseSkyLight = flag && getStatus(nbttagcompound).isAtLeastStatus(ChunkStatus.LIGHT); boolean canUseBlockLight = canUseSkyLight; // Tuinity + NBTTagList nbttaglist = nbttagcompound1.getList("Sections", 10); + boolean flag1 = true; + ChunkSection[] achunksection = new ChunkSection[16]; + boolean flag2 = worldserver.getDimensionManager().hasSkyLight(); + ChunkProviderServer chunkproviderserver = worldserver.getChunkProvider(); + LightEngine lightengine = chunkproviderserver.getLightEngine(); ++ com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(false); // Tuinity - replace light impl ++ com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(true); // Tuinity - replace light impl + + if (flag) { + tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main +@@ -119,6 +129,7 @@ public class ChunkRegionLoader { + + if (flag) { + if (nbttagcompound2.hasKeyOfType("BlockLight", 7)) { ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && canUseBlockLight) blockNibbles[b0 + 1] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(nbttagcompound2.getByteArray("BlockLight")); // Tuinity - replace light impl + // Paper start - delay this task since we're executing off-main + NibbleArray blockLight = new NibbleArray(nbttagcompound2.getByteArray("BlockLight")); + tasksToExecuteOnMain.add(() -> { +@@ -128,6 +139,7 @@ public class ChunkRegionLoader { + } + + if (flag2 && nbttagcompound2.hasKeyOfType("SkyLight", 7)) { ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && canUseSkyLight) skyNibbles[b0 + 1] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(nbttagcompound2.getByteArray("SkyLight")); // Tuinity - replace light impl + // Paper start - delay this task since we're executing off-main + NibbleArray skyLight = new NibbleArray(nbttagcompound2.getByteArray("SkyLight")); + tasksToExecuteOnMain.add(() -> { +@@ -138,6 +150,20 @@ public class ChunkRegionLoader { + } + } + ++ // Tuinity start - correctly set nullable status for light data ++ boolean nullbleSky = true; ++ for (int y = 16; y >= -1; --y) { ++ com.tuinity.tuinity.chunk.light.SWMRNibbleArray nibble = skyNibbles[y + 1]; ++ if (nibble.isInitialised()) { ++ nullbleSky = false; ++ continue; ++ } ++ if (!nullbleSky) { ++ nibble.markNonNull(); ++ } ++ } ++ // Tuinity end - correctly set nullable status for light data ++ + long j = nbttagcompound1.getLong("InhabitedTime"); + ChunkStatus.Type chunkstatus_type = a(nbttagcompound); + Object object; +@@ -173,8 +199,14 @@ public class ChunkRegionLoader { + object = new Chunk(worldserver.getMinecraftWorld(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys. + createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here + );// Paper end ++ ((Chunk)object).setBlockNibbles(blockNibbles); // Tuinity - replace light impl ++ ((Chunk)object).setSkyNibbles(skyNibbles); // Tuinity - replace light impl ++ ((Chunk)object).setWasLoadedFromDisk(true); // Tuinity - replace light impl + } else { + ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter ++ protochunk.setBlockNibbles(blockNibbles); // Tuinity - replace light impl ++ protochunk.setSkyNibbles(skyNibbles); // Tuinity - replace light impl ++ protochunk.setWasLoadedFromDisk(true); // Tuinity - replace light impl + + protochunk.a(biomestorage); + object = protochunk; +@@ -354,14 +386,14 @@ public class ChunkRegionLoader { + NibbleArray[] skyLight = new NibbleArray[17 - (-1)]; + + for (int i = -1; i < 17; ++i) { +- NibbleArray blockArray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i)); +- NibbleArray skyArray = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i)); ++ NibbleArray blockArray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasBlockLight ? null : chunk.getBlockNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i)); // Tuinity - chunk might not be loaded ++ NibbleArray skyArray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasSkyLight ? null : chunk.getSkyNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i)); // Tuinity - chunk might not be loaded + + // copy data for safety +- if (blockArray != null) { ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && blockArray != null) { // Tuinity - data already copied + blockArray = blockArray.copy(); + } +- if (skyArray != null) { ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && skyArray != null) { // Tuinity - data already copied + skyArray = skyArray.copy(); + } + +@@ -401,10 +433,10 @@ public class ChunkRegionLoader { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); nbttagcompound.setInt("DataVersion", SharedConstants.getGameVersion().getWorldVersion()); @@ -4208,11 +7702,100 @@ index 8e7da2c5f..5eb14c4cd 100644 nbttagcompound1.setLong("InhabitedTime", ichunkaccess.getInhabitedTime()); nbttagcompound1.setString("Status", ichunkaccess.getChunkStatus().d()); ChunkConverter chunkconverter = ichunkaccess.p(); +@@ -429,8 +461,8 @@ public class ChunkRegionLoader { + NibbleArray nibblearray; // block light + NibbleArray nibblearray1; // sky light + if (asyncsavedata == null) { +- nibblearray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) +- nibblearray1 = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) ++ nibblearray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasBlockLight ? null : ichunkaccess.getBlockNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); // Tuinity - chunk might not be loaded ++ nibblearray1 = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasSkyLight ? null : ichunkaccess.getSkyNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Tuinity - chunk might not be loaded + } else { + nibblearray = asyncsavedata.blockLight[i + 1]; // +1 to offset the -1 starting index + nibblearray1 = asyncsavedata.skyLight[i + 1]; // +1 to offset the -1 starting index +@@ -444,11 +476,11 @@ public class ChunkRegionLoader { + } + + if (nibblearray != null && !nibblearray.c()) { +- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper ++ nbttagcompound2.setByteArray("BlockLight", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.asBytesPoolSafe().clone()); // Paper // Tuinity - data is already cloned + } + + if (nibblearray1 != null && !nibblearray1.c()) { +- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper ++ nbttagcompound2.setByteArray("SkyLight", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray1.asBytes() : nibblearray1.asBytesPoolSafe().clone()); // Paper // Tuinity - data is already cloned + } + + nbttaglist.add(nbttagcompound2); diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java -index e52df8096..cebd808e2 100644 +index e52df8096..ea943e44a 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java -@@ -96,6 +96,7 @@ public class ChunkSection { +@@ -11,10 +11,45 @@ public class ChunkSection { + short nonEmptyBlockCount; // Paper - package-private + short tickingBlockCount; // Paper - private -> package-private + private short e; +- final DataPaletteBlock blockIds; // Paper - package-private ++ public final DataPaletteBlock blockIds; // Paper - package-private // Tuinity - public + + final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper + ++ // Tuinity start ++ protected int specialCollidingBlocks; ++ ++ public final boolean hasSpecialCollidingBlocks() { ++ return this.specialCollidingBlocks != 0; ++ } ++ ++ private final long[] knownBlockCollisionData = new long[16 * 16 * 16 * 2 / 64]; // blocks * bits per block / bits per long ++ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to have voxelshape of empty ++ public static final long KNOWN_FULL_BLOCK = 0b01; // known to have voxelshape of full cube ++ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info ++ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions ++ ++ private final void updateKnownBlockInfo(int blockIndex, IBlockData blockData) { ++ int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) ++ int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); ++ ++ long value = this.knownBlockCollisionData[arrayIndex]; ++ ++ value &= ~(0b11L << (valueShift << 1)); ++ value |= (blockData.getBlockCollisionBehavior() << (valueShift << 1)); ++ ++ this.knownBlockCollisionData[arrayIndex] = value; ++ } ++ ++ public final long getKnownBlockInfo(int blockIndex) { ++ int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) ++ int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); ++ ++ long value = this.knownBlockCollisionData[arrayIndex]; ++ ++ return (value >>> (valueShift << 1)) & 0b11L; ++ } ++ // Tuinity end ++ + // Paper start - Anti-Xray - Add parameters + @Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) { +@@ -62,6 +97,16 @@ public class ChunkSection { + iblockdata1 = (IBlockData) this.blockIds.b(i, j, k, iblockdata); + } + ++ // Tuinity start ++ this.updateKnownBlockInfo(i | (k << 4) | (j << 8), iblockdata); ++ if (iblockdata1.getBlockCollisionBehavior() == KNOWN_SPECIAL_BLOCK) { ++ --this.specialCollidingBlocks; ++ } ++ if (iblockdata.getBlockCollisionBehavior() == KNOWN_SPECIAL_BLOCK) { ++ ++this.specialCollidingBlocks; ++ } ++ // Tuinity end ++ + Fluid fluid = iblockdata1.getFluid(); + Fluid fluid1 = iblockdata.getFluid(); + +@@ -96,6 +141,7 @@ public class ChunkSection { return iblockdata1; } @@ -4220,6 +7803,19 @@ index e52df8096..cebd808e2 100644 public boolean c() { return this.nonEmptyBlockCount == 0; } +@@ -129,6 +175,12 @@ public class ChunkSection { + this.tickingBlockCount = 0; + this.e = 0; + this.blockIds.forEachLocation((iblockdata, location) -> { // Paper ++ // Tuinity start ++ if (iblockdata.getBlockCollisionBehavior() == KNOWN_SPECIAL_BLOCK) { ++ ++this.specialCollidingBlocks; ++ } ++ this.updateKnownBlockInfo(location, iblockdata); ++ // Tuinity end + Fluid fluid = iblockdata.getFluid(); + + if (!iblockdata.isAir()) { diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java index f6c9bdbf5..51ea295d6 100644 --- a/src/main/java/net/minecraft/server/ChunkStatus.java @@ -4308,7 +7904,7 @@ index 550232cb3..229c3b0f0 100644 throwable = throwable1; throw throwable1; diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 0c952fea3..420557c27 100644 +index 0c952fea3..152f3cc5b 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -136,7 +136,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke @@ -4426,7 +8022,16 @@ index 0c952fea3..420557c27 100644 if (vec3d1.g() > 1.0E-7D) { this.a(this.getBoundingBox().c(vec3d1)); -@@ -722,11 +797,39 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -710,7 +785,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + + try { +- this.checkBlockCollisions(); ++ this.checkBlockCollisions(this.fireTicks <= 0); // Tuinity - move fire checking into method here + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Checking entity block collision"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being checked for collision"); +@@ -722,11 +797,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke float f2 = this.getBlockSpeedFactor(); this.setMot(this.getMot().d((double) f2, 1.0D, (double) f2)); @@ -4434,43 +8039,12 @@ index 0c952fea3..420557c27 100644 - return iblockdata1.a((Tag) TagsBlock.FIRE) || iblockdata1.a(Blocks.LAVA); - }) && this.fireTicks <= 0) { - this.setFireTicks(-this.getMaxFireTicks()); -+ // Tuinity start - remove streams here -+ if (this.fireTicks <= 0) { -+ AxisAlignedBB boundingBox = this.getBoundingBox().shrink(0.001D); -+ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); -+ int minX = MathHelper.floor(boundingBox.minX); -+ int minY = MathHelper.floor(boundingBox.minY); -+ int minZ = MathHelper.floor(boundingBox.minZ); -+ int maxX = MathHelper.floor(boundingBox.maxX); -+ int maxY = MathHelper.floor(boundingBox.maxY); -+ int maxZ = MathHelper.floor(boundingBox.maxZ); -+ boolean inFireLoaded = true; -+ boolean inFire = false; -+ fire_search: -+ for (int currY = minY; currY <= maxY; ++currY) { -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ mutablePos.setValues(currX, currY, currZ); -+ IBlockData type = this.getWorld().getTypeIfLoaded(mutablePos); -+ if (type == null) { -+ inFireLoaded = false; -+ break fire_search; -+ } -+ if (!inFire && (type.a(TagsBlock.FIRE) || type.a(Blocks.LAVA))) { -+ inFire = true; -+ } -+ } -+ } -+ } -+ if (!inFire & inFireLoaded) { -+ this.setFireTicks(-this.getMaxFireTicks()); -+ } - } -+ // Tuinity end - remove streams here +- } ++ // Tuinity - move into checkBlockCollisions if (this.aG() && this.isBurning()) { this.playSound(SoundEffects.ENTITY_GENERIC_EXTINGUISH_FIRE, 0.7F, 1.6F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F); -@@ -735,6 +838,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -735,6 +806,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.world.getMethodProfiler().exit(); } @@ -4484,7 +8058,7 @@ index 0c952fea3..420557c27 100644 } protected BlockPosition ap() { -@@ -815,6 +925,132 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -815,6 +893,132 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return d0; } @@ -4617,7 +8191,7 @@ index 0c952fea3..420557c27 100644 private Vec3D g(Vec3D vec3d) { AxisAlignedBB axisalignedbb = this.getBoundingBox(); VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this); -@@ -850,6 +1086,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -850,6 +1054,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return vec3d1; } @@ -4625,7 +8199,57 @@ index 0c952fea3..420557c27 100644 public static double c(Vec3D vec3d) { return vec3d.x * vec3d.x + vec3d.z * vec3d.z; } -@@ -1358,6 +1595,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -962,18 +1167,34 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + + protected void checkBlockCollisions() { ++ // Tuinity start ++ this.checkBlockCollisions(false); ++ } ++ protected void checkBlockCollisions(boolean checkFire) { ++ boolean inFire = false; ++ // Tuinity end + AxisAlignedBB axisalignedbb = this.getBoundingBox(); + BlockPosition blockposition = new BlockPosition(axisalignedbb.minX + 0.001D, axisalignedbb.minY + 0.001D, axisalignedbb.minZ + 0.001D); + BlockPosition blockposition1 = new BlockPosition(axisalignedbb.maxX - 0.001D, axisalignedbb.maxY - 0.001D, axisalignedbb.maxZ - 0.001D); + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); + + if (this.world.areChunksLoadedBetween(blockposition, blockposition1)) { +- for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) { +- for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) { +- for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) { ++ // Tuinity start - reorder iteration to more cache aware ++ for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) { ++ for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) { ++ for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) { ++ // Tuinity end - reorder iteration to more cache aware + blockposition_mutableblockposition.d(i, j, k); + IBlockData iblockdata = this.world.getType(blockposition_mutableblockposition); + ++ // Tuinity start - move fire checking in here - reuse getType from this method ++ if (checkFire) { ++ if (!inFire && (iblockdata.a(TagsBlock.FIRE) || iblockdata.a(Blocks.LAVA))) { ++ inFire = true; ++ } ++ } ++ // Tuinity end - move fire checking in here - reuse getType from this method ++ + try { + iblockdata.a(this.world, blockposition_mutableblockposition, this); + this.a(iblockdata); +@@ -987,6 +1208,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + } + } ++ // Tuinity start - move fire checking in here - reuse getType from this method ++ if (checkFire & !inFire) { ++ this.setFireTicks(-this.getMaxFireTicks()); ++ } ++ // Tuinity end - move fire checking in here - reuse getType from this method + } + + } +@@ -1358,6 +1584,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return d3 * d3 + d4 * d4 + d5 * d5; } @@ -4633,7 +8257,7 @@ index 0c952fea3..420557c27 100644 public double h(Entity entity) { return this.e(entity.getPositionVector()); } -@@ -1938,9 +2176,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1938,9 +2165,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke float f1 = this.size.width * 0.8F; AxisAlignedBB axisalignedbb = AxisAlignedBB.g((double) f1, 0.10000000149011612D, (double) f1).d(this.locX(), this.getHeadY(), this.locZ()); @@ -4645,7 +8269,7 @@ index 0c952fea3..420557c27 100644 } } -@@ -1948,11 +2186,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1948,11 +2175,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return EnumInteractionResult.PASS; } @@ -4661,7 +8285,7 @@ index 0c952fea3..420557c27 100644 return false; } -@@ -3294,12 +3534,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -3294,12 +3523,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return this.locBlock; } @@ -4678,7 +8302,7 @@ index 0c952fea3..420557c27 100644 } public void setMot(double d0, double d1, double d2) { -@@ -3354,7 +3598,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -3354,7 +3587,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // Paper end if (this.loc.x != d0 || this.loc.y != d1 || this.loc.z != d2) { @@ -4747,6 +8371,185 @@ index fe0334b50..87cd6fb0c 100644 } +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 59d47a9f7..f8921eb83 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -524,6 +524,174 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + } + ++ /* // TODO remove debug ++ this.networkManager.disableAutomaticFlush(); ++ ++ if (MinecraftServer.currentTick % 20 == 0) { ++ int centerX = MathHelper.floor(this.locX()) >> 4; ++ int centerZ = MathHelper.floor(this.locZ()) >> 4; ++ byte[] full = new byte[2048]; ++ byte[] empty = new byte[2048]; ++ java.util.Arrays.fill(full, (byte)-1); ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ int cx = centerX + dx; ++ int cz = centerZ + dz; ++ ++ if (this.getWorldServer().getChunkProvider().getChunkAtIfLoadedImmediately(cx, cz) == null) { ++ continue; ++ } ++ ++ for (int y = -1; y <= 16; ++y) { ++ NibbleArray nibble = this.getWorldServer().getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY) ++ .a(new SectionPosition(cx, y, cz)); ++ org.bukkit.Color color; ++ org.bukkit.block.data.BlockData blockColor; ++ if (nibble == null) { ++ color = org.bukkit.Color.RED; ++ blockColor = org.bukkit.Material.RED_WOOL.createBlockData(); ++ continue; ++ } else { ++ if (nibble.c()) { // is null ++ color = org.bukkit.Color.BLUE; ++ blockColor = org.bukkit.Material.BLUE_WOOL.createBlockData(); ++ } else if (java.util.Arrays.equals(nibble.justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist(), full)) { ++ color = org.bukkit.Color.RED; ++ blockColor = org.bukkit.Material.RED_WOOL.createBlockData(); ++ } else if (java.util.Arrays.equals(nibble.justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist(), empty)) { ++ color = org.bukkit.Color.BLACK; ++ blockColor = org.bukkit.Material.BLACK_WOOL.createBlockData(); ++ } else { ++ color = org.bukkit.Color.ORANGE; ++ blockColor = org.bukkit.Material.ORANGE_WOOL.createBlockData(); ++ } ++ } ++ ++ org.bukkit.Particle.DustOptions dustOptions = new org.bukkit.Particle.DustOptions(color, 1.7f); ++ ++ for (int i = 0; i <= 16; ++i) { ++ // y axis ++ ++ double xVal = i == 0 ? 0.5 : (i == 16 ? 15.5 : i); ++ ++ // left side ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + xVal, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + xVal, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ // right side ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + xVal, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + xVal, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ ++ // x axis ++ ++ // bottom ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 0.5, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 0.5, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ // top ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 15.5, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 15.5, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ // z axis ++ // bottom ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + 0.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + 0.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ ++ //top ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + 15.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + 15.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ } ++ } ++ } ++ } ++ } ++ ++ this.networkManager.enableAutomaticFlush(); ++ ++ System.out.println("Block: " + this.getBukkitEntity().getLocation().getBlock().getLightFromBlocks()); ++ System.out.println("Sky: " + this.getBukkitEntity().getLocation().getBlock().getLightFromSky()); ++ */ // TODO remove debug ++ + if (this.getHealth() != this.lastHealthSent || this.lastFoodSent != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastSentSaturationZero) { + this.playerConnection.sendPacket(new PacketPlayOutUpdateHealth(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit + this.lastHealthSent = this.getHealth(); diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index 4efc40c01..f322dccd8 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -4759,6 +8562,21 @@ index 4efc40c01..f322dccd8 100644 List list = this.tracker.getPassengers(); if (!list.equals(this.p)) { +diff --git a/src/main/java/net/minecraft/server/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java +index 36aafc3b7..c9963c198 100644 +--- a/src/main/java/net/minecraft/server/EnumDirection.java ++++ b/src/main/java/net/minecraft/server/EnumDirection.java +@@ -160,8 +160,8 @@ public enum EnumDirection implements INamable { + return EnumDirection.q[MathHelper.a(i % EnumDirection.q.length)]; + } + +- @Nullable +- public static EnumDirection a(int i, int j, int k) { ++ @Nullable public static EnumDirection from(int i, int j, int k) { return a(i, j, k); } // Tuinity - OBFHELPER ++ @Nullable public static EnumDirection a(int i, int j, int k) { + return (EnumDirection) EnumDirection.r.get(BlockPosition.a(i, j, k)); + } + diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java index 068b92c5c..a43c4ca3e 100644 --- a/src/main/java/net/minecraft/server/HeightMap.java @@ -4856,6 +8674,57 @@ index c4a83448e..5c3eb4fc7 100644 Vec3D vec3d = raytrace1.b(); Vec3D vec3d1 = raytrace1.a(); VoxelShape voxelshape = raytrace1.a(iblockdata, this, blockposition); +diff --git a/src/main/java/net/minecraft/server/IChunkAccess.java b/src/main/java/net/minecraft/server/IChunkAccess.java +index 180b6b58d..752d9402e 100644 +--- a/src/main/java/net/minecraft/server/IChunkAccess.java ++++ b/src/main/java/net/minecraft/server/IChunkAccess.java +@@ -24,6 +24,30 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess { + } + // Paper end + ++ // Tuinity start ++ default com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ default void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ ++ default com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ default void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ ++ default boolean wasLoadedFromDisk() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ ++ default void setWasLoadedFromDisk(boolean value) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ // Tuinity end ++ + IBlockData getType(final int x, final int y, final int z); // Paper + @Nullable + IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag); +@@ -122,6 +146,7 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess { + @Nullable + NBTTagCompound j(BlockPosition blockposition); + ++ default Stream getLightSources() { return this.m(); } // Tuinity - OBFHELPER + Stream m(); + + TickList n(); +@@ -142,6 +167,7 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess { + return ashortlist[i]; + } + ++ default boolean isLit() { return this.r(); } // Tuinity - OBFHELPER + boolean r(); + + void b(boolean flag); diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java index 582a5695b..5601088cd 100644 --- a/src/main/java/net/minecraft/server/IChunkLoader.java @@ -4870,10 +8739,22 @@ index 582a5695b..5601088cd 100644 // Paper - nuke IOWorker } diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java -index 25e54a1fa..b66c802d5 100644 +index 25e54a1fa..cce0ac8a3 100644 --- a/src/main/java/net/minecraft/server/ICollisionAccess.java +++ b/src/main/java/net/minecraft/server/ICollisionAccess.java -@@ -46,6 +46,11 @@ public interface ICollisionAccess extends IBlockAccess { +@@ -28,6 +28,11 @@ public interface ICollisionAccess extends IBlockAccess { + } + + default boolean b(AxisAlignedBB axisalignedbb) { ++ // Tuinity start - allow overriding in WorldServer ++ return this.getCubes(axisalignedbb); ++ } ++ default boolean getCubes(AxisAlignedBB axisalignedbb) { ++ // Tuinity end - allow overriding in WorldServer + return this.b((Entity) null, axisalignedbb, (entity) -> { + return true; + }); +@@ -46,6 +51,11 @@ public interface ICollisionAccess extends IBlockAccess { } default boolean b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { @@ -4942,6 +8823,38 @@ index 07dbdd560..40ca3364d 100644 return this.a(this.b(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix } +diff --git a/src/main/java/net/minecraft/server/ILightAccess.java b/src/main/java/net/minecraft/server/ILightAccess.java +index be5384ee4..df28f7a6b 100644 +--- a/src/main/java/net/minecraft/server/ILightAccess.java ++++ b/src/main/java/net/minecraft/server/ILightAccess.java +@@ -4,9 +4,10 @@ import javax.annotation.Nullable; + + public interface ILightAccess { + +- @Nullable +- IBlockAccess c(int i, int j); ++ default @Nullable IBlockAccess getFeaturesReadyChunk(int i, int j) { return this.c(i, j); } // Tuinity - OBFHELPER ++ @Nullable IBlockAccess c(int i, int j); + ++ default void markLightSectionDirty(EnumSkyBlock enumskyblock, SectionPosition sectionposition) { this.a(enumskyblock, sectionposition); } // Tuinity - OBFHELPER + default void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition) {} + + IBlockAccess getWorld(); +diff --git a/src/main/java/net/minecraft/server/LightEngineGraphSection.java b/src/main/java/net/minecraft/server/LightEngineGraphSection.java +index 13d067f48..04afd7f28 100644 +--- a/src/main/java/net/minecraft/server/LightEngineGraphSection.java ++++ b/src/main/java/net/minecraft/server/LightEngineGraphSection.java +@@ -74,8 +74,10 @@ public abstract class LightEngineGraphSection extends LightEngineGraph { + return i == Long.MAX_VALUE ? this.b(j) : k + 1; + } + ++ public final int getSource(long coordinate) { return this.b(coordinate); } // Tuinity - OBFHELPER + protected abstract int b(long i); + ++ public final void update(long coordinate, int level, boolean flag) { this.b(coordinate, level, flag); } // Tuinity - OBFHELPER + public void b(long i, int j, boolean flag) { + this.a(Long.MAX_VALUE, i, j, flag); + } diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java index b98e60772..e0bbfe142 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorage.java @@ -4974,6 +8887,431 @@ index b98e60772..e0bbfe142 100644 while (objectiterator.hasNext()) { entry = (Entry) objectiterator.next(); +diff --git a/src/main/java/net/minecraft/server/LightEngineThreaded.java b/src/main/java/net/minecraft/server/LightEngineThreaded.java +index 2f9c97dd4..ed728e401 100644 +--- a/src/main/java/net/minecraft/server/LightEngineThreaded.java ++++ b/src/main/java/net/minecraft/server/LightEngineThreaded.java +@@ -2,6 +2,11 @@ package net.minecraft.server; + + import com.mojang.datafixers.util.Pair; + import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper ++// Tuinity start ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++// Tuinity end + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectList; + import it.unimi.dsi.fastutil.objects.ObjectListIterator; +@@ -156,13 +161,245 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + private volatile int f = 5; + private final AtomicBoolean g = new AtomicBoolean(); + ++ // Tuinity start - replace light engine impl ++ protected final com.tuinity.tuinity.chunk.light.ThreadedStarLightEngine theLightEngine; ++ public final boolean hasBlockLight; ++ public final boolean hasSkyLight; ++ ++ protected final LightEngineLayerEventListener skyReader; ++ protected final LightEngineLayerEventListener blockReader; ++ // Tuinity end - replace light engine impl ++ + public LightEngineThreaded(ILightAccess ilightaccess, PlayerChunkMap playerchunkmap, boolean flag, ThreadedMailbox threadedmailbox, Mailbox> mailbox) { + super(ilightaccess, true, flag); + this.d = playerchunkmap; this.playerChunkMap = d; // Paper + this.e = mailbox; + this.b = threadedmailbox; ++ // Tuinity start - replace light engine impl ++ this.hasBlockLight = true; ++ this.hasSkyLight = flag; ++ this.theLightEngine = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? new com.tuinity.tuinity.chunk.light.ThreadedStarLightEngine(ilightaccess, flag, true) : null; ++ ++ this.blockReader = new LightEngineLayerEventListener() { ++ @Override ++ public NibbleArray a(SectionPosition sectionPosition) { ++ IChunkAccess chunk = LightEngineThreaded.this.getChunk(sectionPosition.getX(), sectionPosition.getZ()); ++ return chunk == null ? null : chunk.getBlockNibbles()[sectionPosition.getY() + 1].asNibble(); ++ } ++ ++ @Override ++ public int b(BlockPosition blockPosition) { ++ int cx = blockPosition.getX() >> 4; ++ int cy = blockPosition.getY() >> 4; ++ int cz = blockPosition.getZ() >> 4; ++ IChunkAccess chunk = LightEngineThreaded.this.getChunk(cx, cz); ++ if (chunk == null) { ++ return 0; ++ } ++ if (cy < -1 || cy > 16) { ++ return 0; ++ } ++ return chunk.getBlockNibbles()[cy + 1].getVisible(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); ++ } ++ ++ @Override ++ public void a(SectionPosition sectionPosition, boolean b) { ++ return; // don't care. ++ } ++ }; ++ if (!flag) { ++ this.skyReader = LightEngineLayerEventListener.Void.INSTANCE; ++ } else { ++ this.skyReader = new LightEngineLayerEventListener() { ++ @Override ++ public NibbleArray a(SectionPosition sectionPosition) { ++ IChunkAccess chunk = LightEngineThreaded.this.getChunk(sectionPosition.getX(), sectionPosition.getZ()); ++ return chunk == null ? null : chunk.getSkyNibbles()[sectionPosition.getY() + 1].asNibble(); ++ } ++ ++ @Override ++ public int b(BlockPosition blockPosition) { ++ int cx = blockPosition.getX() >> 4; ++ int cy = blockPosition.getY() >> 4; ++ int cz = blockPosition.getZ() >> 4; ++ IChunkAccess chunk = LightEngineThreaded.this.getChunk(cx, cz); ++ if (chunk == null) { ++ return 15; ++ } ++ if (cy < -1) { ++ cy = -1; ++ } else if (cy > 16) { ++ cy = 16; ++ } ++ com.tuinity.tuinity.chunk.light.SWMRNibbleArray nibble = chunk.getSkyNibbles()[cy + 1]; ++ return nibble.getVisible(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); ++ } ++ ++ @Override ++ public void a(SectionPosition sectionPosition, boolean b) { ++ return; // don't care. ++ } ++ }; ++ } ++ // Tuinity end - replace light engine impl ++ } ++ ++ // Tuinity start - replace light engine impl ++ protected final IChunkAccess getChunk(final int chunkX, final int chunkZ) { ++ final WorldServer world = this.theLightEngine.getWorld(); ++ return world.getChunkProvider().getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ public void relight(int chunkX, int chunkZ, Runnable whenComplete) { ++ this.scheduleLightWorkTask(chunkX, chunkZ, LightEngineThreaded.Update.POST_UPDATE, () -> { ++ this.theLightEngine.relightChunk(chunkX, chunkZ); ++ whenComplete.run(); ++ }); ++ } ++ ++ protected final Long2IntOpenHashMap holdingChunks = new Long2IntOpenHashMap(); ++ protected final LongArrayList postWorkTicketRelease = new LongArrayList(); ++ ++ private void addLightWorkTicket(int chunkX, int chunkZ) { ++ final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final int current = this.holdingChunks.putIfAbsent(coordinate, 1); ++ if (current == 0) { ++ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate); ++ this.theLightEngine.getWorld().getChunkProvider().addTicketAtLevel(TicketType.LIGHT_UPDATE, chunkPos, ++ MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), chunkPos); ++ this.theLightEngine.getWorld().getChunkProvider().tickDistanceManager(); ++ } else { ++ this.holdingChunks.put(coordinate, current + 1); ++ } + } + ++ protected final void releaseLightWorkChunk(int chunkX, int chunkZ) { ++ final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final int current = this.holdingChunks.get(coordinate); ++ if (current == 1) { ++ this.holdingChunks.remove(coordinate); ++ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate); ++ this.theLightEngine.getWorld().getChunkProvider().removeTicketAtLevel(TicketType.LIGHT_UPDATE, chunkPos, ++ MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), chunkPos); ++ } else { ++ this.holdingChunks.put(coordinate, current - 1); ++ } ++ } ++ ++ protected final CompletableFuture acquireLightWorkChunk(int chunkX, int chunkZ) { ++ ChunkProviderServer chunkProvider = this.theLightEngine.getWorld().getChunkProvider(); ++ PlayerChunkMap chunkMap = chunkProvider.playerChunkMap; ++ int targetLevel = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); ++ ++ this.addLightWorkTicket(chunkX, chunkZ); ++ ++ // light doesn't always load one radius neighbours... ++ // i.e if they get unloaded ++ boolean neighboursAtFeatures = true; ++ int targetNeighbourLevel = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT.getPreviousStatus()); ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ PlayerChunk neighbour = chunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(dx + chunkX, dz + chunkZ)); ++ ChunkStatus status; ++ if (neighbour == null || neighbour.getTicketLevel() > targetNeighbourLevel || ++ (status = neighbour.getChunkHolderStatus()) == null || ++ !status.isAtLeastStatus(ChunkStatus.LIGHT.getPreviousStatus())) { ++ neighboursAtFeatures = false; ++ break; ++ } ++ } ++ } ++ ++ PlayerChunk playerChunk = chunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ ChunkStatus holderStatus; ++ if (!neighboursAtFeatures || playerChunk == null || playerChunk.getTicketLevel() > targetLevel || ++ (holderStatus = playerChunk.getChunkHolderStatus()) == null || ++ !holderStatus.isAtLeastStatus(ChunkStatus.LIGHT)) { ++ CompletableFuture ret = new CompletableFuture<>(); ++ ++ int[] loads = new int[1]; ++ int requiredLoads = 3 * 3; ++ java.util.function.Consumer onLoad = (chunk) -> { ++ if (++loads[0] == requiredLoads) { ++ ret.complete(this.getChunk(chunkX, chunkZ)); ++ } ++ }; ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ chunkProvider.getChunkAtAsynchronously(chunkX + dx, chunkZ + dz, ++ (dx | dz) == 0 ? ChunkStatus.LIGHT : ChunkStatus.LIGHT.getPreviousStatus(), ++ true, false, onLoad); ++ } ++ } ++ ++ return ret; ++ } ++ ++ return CompletableFuture.completedFuture(playerChunk.getAvailableChunkNow()); ++ } ++ ++ // note: task is discarded if the chunk is not at light status or if the chunk is not lit ++ protected final void scheduleLightWorkTask(int chunkX, int chunkZ, LightEngineThreaded.Update type, Runnable task) { ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ this.playerChunkMap.mainInvokingExecutor.execute(() -> { ++ this.scheduleLightWorkTask(chunkX, chunkZ, type, task); ++ }); ++ return; ++ } ++ ++ IChunkAccess current = this.getChunk(chunkX, chunkZ); ++ ++ if (current == null || !current.isLit() || !current.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) { ++ return; ++ } ++ ++ this.acquireLightWorkChunk(chunkX, chunkZ).whenCompleteAsync((chunk, throwable) -> { ++ if (throwable != null) { ++ MinecraftServer.LOGGER.fatal("Failed to load light chunk for light work", throwable); ++ this.releaseLightWorkChunk(chunkX, chunkZ); ++ } else { ++ this.scheduleTask(chunkX, chunkZ, type, () -> { ++ try { ++ task.run(); ++ } finally { ++ this.postWorkTicketRelease.add(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ } ++ }); ++ } ++ }, this.playerChunkMap.mainInvokingExecutor); ++ } ++ ++ // override things from superclass ++ ++ @Override ++ public boolean a() { ++ return this.theLightEngine != null ? false : super.a(); ++ } ++ ++ @Override ++ public LightEngineLayerEventListener a(EnumSkyBlock var0) { ++ if (this.theLightEngine == null) { ++ return super.a(var0); ++ } ++ if (var0 == EnumSkyBlock.BLOCK) { ++ return this.blockReader; ++ } else { ++ return this.skyReader; ++ } ++ } ++ ++ @Override ++ public int b(BlockPosition var0, int var1) { ++ if (this.theLightEngine == null) { ++ return super.b(var0, var1); ++ } ++ int var2 = this.skyReader.b(var0) - var1; ++ int var3 = this.blockReader.b(var0); ++ return Math.max(var3, var2); ++ } ++ // Tuinity end - replace light engine impl ++ + public void close() {} + + @Override +@@ -179,6 +416,15 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + public void a(BlockPosition blockposition) { + BlockPosition blockposition1 = blockposition.immutableCopy(); + ++ // Tuinity start - replace light engine impl ++ if (this.theLightEngine != null) { ++ this.scheduleLightWorkTask(blockposition1.getX() >> 4, blockposition1.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, () -> { ++ this.theLightEngine.blockChange(blockposition1); ++ }); ++ return; ++ } ++ // Tuinity start - replace light engine impl ++ + this.a(blockposition.getX() >> 4, blockposition.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, SystemUtils.a(() -> { + super.a(blockposition1); + }, () -> { +@@ -187,6 +433,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + } + + protected void a(ChunkCoordIntPair chunkcoordintpair) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -211,6 +462,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void a(SectionPosition sectionposition, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(sectionposition.a(), sectionposition.c(), () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -222,6 +478,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { + super.a(chunkcoordintpair, flag); + }, () -> { +@@ -231,6 +492,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition, @Nullable NibbleArray nibblearray, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(sectionposition.a(), sectionposition.c(), () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -240,6 +506,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + })); + } + ++ private void scheduleTask(int x, int z, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { this.a(x, z, lightenginethreaded_update, runnable); } // Tuinity - OBFHELPER + private void a(int i, int j, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { + this.a(i, j, this.d.c(ChunkCoordIntPair.pair(i, j)), lightenginethreaded_update, runnable); + } +@@ -252,6 +519,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void b(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -277,6 +549,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + return; + } + // Paper end ++ if (this.theLightEngine == null) { // Tuinity - replace light engine impl + ChunkSection[] achunksection = ichunkaccess.getSections(); + + for (int i = 0; i < 16; ++i) { +@@ -293,6 +566,13 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + super.a(blockposition, ichunkaccess.g(blockposition)); + }); + } ++ } else { // Tuinity start - replace light engine impl ++ if (flag) { ++ this.theLightEngine.checkChunkEdges(chunkcoordintpair.x, chunkcoordintpair.z); ++ } else { ++ this.theLightEngine.lightChunk(chunkcoordintpair.x, chunkcoordintpair.z); ++ } ++ } // Tuinity end - replace light engine impl + + // this.d.c(chunkcoordintpair); // Paper - move into post task below + }, () -> { +@@ -302,7 +582,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + this.d.c(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done + if (skippedPre[0]) return; // Paper - future's already complete + ichunkaccess.b(true); +- super.b(chunkcoordintpair, false); ++ if (this.theLightEngine == null) super.b(chunkcoordintpair, false); // Tuinity - replace light engine impl + // Paper start + future.complete(ichunkaccess); + }); +@@ -311,7 +591,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + } + + public void queueUpdate() { +- if ((!this.queue.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { // Paper ++ if ((!this.queue.isEmpty() || (this.theLightEngine == null && super.a())) && this.g.compareAndSet(false, true)) { // Paper // Tuinity - replace light impl + this.b.a((() -> { // Paper - decompile error + this.b(); + this.g.set(false); +@@ -325,17 +605,36 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + private final java.util.List pre = new java.util.ArrayList<>(); + private final java.util.List post = new java.util.ArrayList<>(); + private void b() { ++ //final long start = System.nanoTime(); // TODO remove debug + if (queue.poll(pre, post)) { + pre.forEach(Runnable::run); + pre.clear(); +- super.a(Integer.MAX_VALUE, true, true); ++ if (this.theLightEngine == null) super.a(Integer.MAX_VALUE, true, true); // Tuinity - replace light impl + post.forEach(Runnable::run); + post.clear(); + } else { + // might have level updates to go still +- super.a(Integer.MAX_VALUE, true, true); ++ if (this.theLightEngine == null) super.a(Integer.MAX_VALUE, true, true); // Tuinity - replace light impl ++ } ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ this.theLightEngine.propagateChanges(); ++ if (!this.postWorkTicketRelease.isEmpty()) { ++ LongArrayList copy = this.postWorkTicketRelease.clone(); ++ this.postWorkTicketRelease.clear(); ++ this.playerChunkMap.mainInvokingExecutor.execute(() -> { ++ LongIterator iterator = copy.iterator(); ++ while (iterator.hasNext()) { ++ long coordinate = iterator.nextLong(); ++ this.releaseLightWorkChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate)); ++ } ++ }); ++ } + } ++ // Tuinity end - replace light impl + // Paper end ++ //final long end = System.nanoTime(); // TODO remove debug ++ //System.out.println("Block updates took " + (end - start) * 1.0e-6 + "ms"); // TODO remove debug + } + + public void a(int i) { diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java index c61cd50df..d98748325 100644 --- a/src/main/java/net/minecraft/server/LoginListener.java @@ -4988,7 +9326,7 @@ index c61cd50df..d98748325 100644 throw new IllegalStateException("Protocol error", cryptographyexception); } diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index ff74be145..653ba0f1d 100644 +index ff74be145..e79e773f2 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -38,6 +38,7 @@ import java.util.function.Consumer; @@ -4999,8 +9337,72 @@ index ff74be145..653ba0f1d 100644 public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( 0, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(), +@@ -221,6 +222,63 @@ public final class MCUtil { + return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ())); + } + ++ // Tuinity start ++ ++ static final int SECTION_X_BITS = 22; ++ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; ++ static final int SECTION_Y_BITS = 20; ++ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; ++ static final int SECTION_Z_BITS = 22; ++ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; ++ // format is y,z,x (in order of LSB to MSB) ++ static final int SECTION_Y_SHIFT = 0; ++ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; ++ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; ++ static final int SECTION_TO_BLOCK_SHIFT = 4; ++ ++ public static long getSectionKey(final int x, final int y, final int z) { ++ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getSectionKey(final SectionPosition pos) { ++ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getSectionKey(final ChunkCoordIntPair pos, final int y) { ++ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getSectionKey(final BlockPosition pos) { ++ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static long getSectionKey(final Entity entity) { ++ return ((MCUtil.fastFloor(entity.locX()) & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((MCUtil.fastFloor(entity.locY()) & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((MCUtil.fastFloor(entity.locZ()) & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static int getSectionX(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); ++ } ++ ++ public static int getSectionY(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); ++ } ++ ++ public static int getSectionZ(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); ++ } ++ // Tuinity end ++ + // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable + public static void mergeSortedSets(final java.util.function.Consumer consumer, final java.util.Comparator comparator, final java.util.SortedSet...sets) { + final ObjectRBTreeSet all = new ObjectRBTreeSet<>(comparator); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d0ffe5790..08518ff57 100644 +index d0ffe5790..fe4a7ed6f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -155,6 +155,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant stream, int i) { @@ -5229,6 +9654,15 @@ index 1558c5f82..55fa39117 100644 } @Nullable +@@ -393,7 +400,7 @@ public abstract class NavigationAbstract { + } + + public void b(BlockPosition blockposition) { +- if (this.c != null && !this.c.c() && this.c.e() != 0) { ++ if (this.c != null && !this.c.c() && this.c.e() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking() + PathPoint pathpoint = this.c.d(); + Vec3D vec3d = new Vec3D(((double) pathpoint.a + this.a.locX()) / 2.0D, ((double) pathpoint.b + this.a.locY()) / 2.0D, ((double) pathpoint.c + this.a.locZ()) / 2.0D); + diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java index 7a84ea411..eb6b81d88 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java @@ -5547,6 +9981,45 @@ index 7a84ea411..eb6b81d88 100644 } public boolean isConnected() { +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +index 4085426af..348d16dde 100644 +--- a/src/main/java/net/minecraft/server/NibbleArray.java ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -56,6 +56,7 @@ public class NibbleArray { + boolean poolSafe = false; + public java.lang.Runnable cleaner; + private void registerCleaner() { ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) return; // Tuinity - purge cleaner usage + if (!poolSafe) { + cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes); + } else { +@@ -63,7 +64,7 @@ public class NibbleArray { + } + } + // Paper end +- @Nullable protected byte[] a; ++ @Nullable protected byte[] a; public final byte[] justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist() { return this.a; } + + + public NibbleArray() {} +@@ -74,7 +75,7 @@ public class NibbleArray { + } + public NibbleArray(byte[] abyte, boolean isSafe) { + this.a = abyte; +- if (!isSafe) this.a = getCloneIfSet(); // Paper - clone for safety ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && !isSafe) this.a = getCloneIfSet(); // Paper - clone for safety // Tuinity - no need to clone + registerCleaner(); + // Paper end + if (abyte.length != 2048) { +@@ -162,7 +163,7 @@ public class NibbleArray { + + public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER + public NibbleArray b() { +- return this.a == null ? new NibbleArray() : new NibbleArray(this.a); // Paper - clone in ctor ++ return this.a == null ? new NibbleArray() : new NibbleArray(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? this.a.clone() : this.a); // Paper - clone in ctor // Tuinity - no longer clone in constructor + } + + public String toString() { diff --git a/src/main/java/net/minecraft/server/PacketCompressor.java b/src/main/java/net/minecraft/server/PacketCompressor.java index 3cdd07cad..50b2a8dfb 100644 --- a/src/main/java/net/minecraft/server/PacketCompressor.java @@ -5790,6 +10263,92 @@ index e35369476..aba14794c 100644 } } +// Tuinity end +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +index a22f0ccce..6cc4a035c 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +@@ -26,12 +26,12 @@ public class PacketPlayOutLightUpdate implements Packet { + + @Override + public void onPacketDispatch(EntityPlayer player) { +- remainingSends.incrementAndGet(); ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) remainingSends.incrementAndGet(); + } + + @Override + public void onPacketDispatchFinish(EntityPlayer player, ChannelFuture future) { +- if (remainingSends.decrementAndGet() <= 0) { ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && remainingSends.decrementAndGet() <= 0) { + // incase of any race conditions, schedule this delayed + MCUtil.scheduleTask(5, () -> { + if (remainingSends.get() == 0) { +@@ -44,7 +44,7 @@ public class PacketPlayOutLightUpdate implements Packet { + + @Override + public boolean hasFinishListener() { +- return true; ++ return !com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine; // Tuinity - replace light impl + } + + // Paper end +@@ -54,8 +54,8 @@ public class PacketPlayOutLightUpdate implements Packet { + this.a = chunkcoordintpair.x; + this.b = chunkcoordintpair.z; + this.i = flag; +- this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper +- this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper ++ this.g = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage ++ this.h = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage + + for (int i = 0; i < 18; ++i) { + NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i)); +@@ -66,7 +66,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.e |= 1 << i; + } else { + this.c |= 1 << i; +- this.g.add(nibblearray.getCloneIfSet()); // Paper ++ this.g.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again + } + } + +@@ -75,7 +75,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.f |= 1 << i; + } else { + this.d |= 1 << i; +- this.h.add(nibblearray1.getCloneIfSet()); // Paper ++ this.h.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray1.asBytes() : nibblearray1.getCloneIfSet()); // Paper // Tuinity - don't clone again + } + } + } +@@ -88,8 +88,8 @@ public class PacketPlayOutLightUpdate implements Packet { + this.i = flag; + this.c = i; + this.d = j; +- this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper +- this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper ++ this.g = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage ++ this.h = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage + + for (int k = 0; k < 18; ++k) { + NibbleArray nibblearray; +@@ -97,7 +97,7 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.c & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.g.add(nibblearray.getCloneIfSet()); // Paper ++ this.g.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again + } else { + this.c &= ~(1 << k); + if (nibblearray != null) { +@@ -109,7 +109,7 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.d & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.h.add(nibblearray.getCloneIfSet()); // Paper ++ this.h.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again + } else { + this.d &= ~(1 << k); + if (nibblearray != null) { diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java index 5094a5d6f..72fdbf153 100644 --- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java @@ -6064,7 +10623,7 @@ index 253377c62..3ebe3d0dc 100644 if (entityliving == entityliving1) { return false; diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java -index 11a67ca18..80cc96a4d 100644 +index 11a67ca18..35a569f61 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -56,6 +56,12 @@ public class PlayerChunk { @@ -6089,7 +10648,24 @@ index 11a67ca18..80cc96a4d 100644 byte b0 = (byte) SectionPosition.a(blockposition.getY()); if (b0 >= this.dirtyBlocks.length) return; // CraftBukkit - SPIGOT-6086 -@@ -421,7 +427,7 @@ public class PlayerChunk { +@@ -377,13 +383,14 @@ public class PlayerChunk { + + public void a(EnumSkyBlock enumskyblock, int i) { + Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance ++ if (this.getAvailableChunkNow() != null) this.getAvailableChunkNow().setNeedsSaving(true); // Tuinity - always mark as needing saving + + if (chunk != null) { + chunk.setNeedsSaving(true); + if (enumskyblock == EnumSkyBlock.SKY) { +- this.s |= 1 << i - -1; ++ this.s |= 1 << (i - -1); // Tuinity - fix mojang oopsie + } else { +- this.r |= 1 << i - -1; ++ this.r |= 1 << (i - -1); // Tuinity - fix mojang oopsie + } + + } +@@ -421,7 +428,7 @@ public class PlayerChunk { this.a(world, blockposition, iblockdata); } else { ChunkSection chunksection = chunk.getSections()[sectionposition.getY()]; @@ -6098,7 +10674,7 @@ index 11a67ca18..80cc96a4d 100644 PacketPlayOutMultiBlockChange packetplayoutmultiblockchange = new PacketPlayOutMultiBlockChange(sectionposition, shortset, chunksection, this.x); this.a(packetplayoutmultiblockchange, false); -@@ -504,6 +510,7 @@ public class PlayerChunk { +@@ -504,6 +511,7 @@ public class PlayerChunk { // Paper end - per player view distance } @@ -6106,7 +10682,7 @@ index 11a67ca18..80cc96a4d 100644 public CompletableFuture> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { int i = chunkstatus.c(); CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(i); -@@ -559,6 +566,7 @@ public class PlayerChunk { +@@ -559,6 +567,7 @@ public class PlayerChunk { } protected void a(PlayerChunkMap playerchunkmap) { @@ -6114,7 +10690,7 @@ index 11a67ca18..80cc96a4d 100644 ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel); ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel); boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET; -@@ -568,7 +576,8 @@ public class PlayerChunk { +@@ -568,7 +577,8 @@ public class PlayerChunk { // CraftBukkit start // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { @@ -6124,7 +10700,7 @@ index 11a67ca18..80cc96a4d 100644 Chunk chunk = (Chunk)either.left().orElse(null); if (chunk != null) { playerchunkmap.callbackExecutor.execute(() -> { -@@ -633,7 +642,8 @@ public class PlayerChunk { +@@ -633,7 +643,8 @@ public class PlayerChunk { if (!flag2 && flag3) { // Paper start - cache ticking ready status int expectCreateCount = ++this.fullChunkCreateCount; @@ -6134,7 +10710,7 @@ index 11a67ca18..80cc96a4d 100644 if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { // note: Here is a very good place to add callbacks to logic waiting on this. Chunk fullChunk = either.left().get(); -@@ -664,7 +674,8 @@ public class PlayerChunk { +@@ -664,7 +675,8 @@ public class PlayerChunk { if (!flag4 && flag5) { // Paper start - cache ticking ready status @@ -6144,7 +10720,7 @@ index 11a67ca18..80cc96a4d 100644 if (either.left().isPresent()) { // note: Here is a very good place to add callbacks to logic waiting on this. Chunk tickingChunk = either.left().get(); -@@ -674,6 +685,9 @@ public class PlayerChunk { +@@ -674,6 +686,9 @@ public class PlayerChunk { // Paper start - rewrite ticklistserver PlayerChunk.this.chunkMap.world.onChunkSetTicking(PlayerChunk.this.location.x, PlayerChunk.this.location.z); // Paper end - rewrite ticklistserver @@ -6154,7 +10730,7 @@ index 11a67ca18..80cc96a4d 100644 } }); -@@ -684,6 +698,12 @@ public class PlayerChunk { +@@ -684,6 +699,12 @@ public class PlayerChunk { if (flag4 && !flag5) { this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; @@ -6167,7 +10743,7 @@ index 11a67ca18..80cc96a4d 100644 } boolean flag6 = playerchunk_state.isAtLeast(PlayerChunk.State.ENTITY_TICKING); -@@ -695,13 +715,16 @@ public class PlayerChunk { +@@ -695,13 +716,16 @@ public class PlayerChunk { } // Paper start - cache ticking ready status @@ -6186,7 +10762,7 @@ index 11a67ca18..80cc96a4d 100644 } -@@ -713,6 +736,12 @@ public class PlayerChunk { +@@ -713,6 +737,12 @@ public class PlayerChunk { if (flag6 && !flag7) { this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; @@ -6199,7 +10775,7 @@ index 11a67ca18..80cc96a4d 100644 } // Paper start - raise IO/load priority if priority changes, use our preferred priority -@@ -738,7 +767,8 @@ public class PlayerChunk { +@@ -738,7 +768,8 @@ public class PlayerChunk { // CraftBukkit start // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { @@ -6210,7 +10786,7 @@ index 11a67ca18..80cc96a4d 100644 if (chunk != null) { playerchunkmap.callbackExecutor.execute(() -> { diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 6c399bcea..8286039c4 100644 +index 6c399bcea..d3bf356ea 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -121,31 +121,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @@ -6297,7 +10873,7 @@ index 6c399bcea..8286039c4 100644 int chunkX = MCUtil.getChunkCoordinate(player.locX()); int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); // Note: players need to be explicitly added to distance maps before they can be updated -@@ -274,9 +283,27 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -274,9 +283,35 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured player.needsChunkCenterUpdate = false; // Paper end - no-tick view distance @@ -6309,7 +10885,15 @@ index 6c399bcea..8286039c4 100644 + // Tuinity start + public static enum RegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionDataCreator { -+ ++ // Tuinity start - optimise notify() ++ PATHING_NAVIGATORS() { ++ @Override ++ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager regionManager) { ++ return new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(true); ++ } ++ } ++ // Tuinity end - optimise notify() + ; + + @Override @@ -6325,7 +10909,7 @@ index 6c399bcea..8286039c4 100644 private final java.util.concurrent.ExecutorService lightThread; public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i, boolean flag) { super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag); -@@ -310,9 +337,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -310,9 +345,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.worldLoadListener = worldloadlistener; // Paper start - use light thread @@ -6337,12 +10921,12 @@ index 6c399bcea..8286039c4 100644 thread.setDaemon(true); thread.setPriority(Thread.NORM_PRIORITY+1); return thread; -@@ -444,6 +472,26 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -444,6 +480,26 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded }); // Paper end - no-tick view distance + // Tuinity start -+ this.dataRegionManager = null;//new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 2, (1.0 / 3.0), "Data"); ++ this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 2, (1.0 / 3.0), "Data"); + // Tuinity end + // Tuinity start - optimise checkDespawn + this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, @@ -6364,7 +10948,7 @@ index 6c399bcea..8286039c4 100644 } // Paper start - Chunk Prioritization public void queueHolderUpdate(PlayerChunk playerchunk) { -@@ -756,6 +804,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -756,6 +812,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @Nullable private PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k) { @@ -6373,15 +10957,26 @@ index 6c399bcea..8286039c4 100644 if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) { return playerchunk; } else { -@@ -778,6 +828,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -778,7 +836,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunk.a(j); } else { playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this); -+ //this.dataRegionManager.addChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity ++ this.dataRegionManager.addChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity } ++ this.getVillagePlace().dequeueUnload(playerchunk.location.pair()); // Tuinity - unload POI data this.updatingChunks.put(i, playerchunk); -@@ -970,7 +1021,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.updatingChunksModified = true; +@@ -904,7 +964,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + +- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more ++ static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more // Tuinity - private -> package private + + protected void unloadChunks(BooleanSupplier booleansupplier) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); +@@ -970,7 +1030,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, chunkPos.x, chunkPos.z, @@ -6390,7 +10985,7 @@ index 6c399bcea..8286039c4 100644 if (!chunk.isNeedsSaving()) { return; -@@ -1004,7 +1055,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1004,7 +1064,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { asyncSaveData = ChunkRegionLoader.getAsyncSaveData(this.world, chunk); } @@ -6399,7 +10994,7 @@ index 6c399bcea..8286039c4 100644 asyncSaveData, chunk); chunk.setLastSaved(this.world.getTime()); -@@ -1012,6 +1063,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1012,6 +1072,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } // Paper end @@ -6408,7 +11003,7 @@ index 6c399bcea..8286039c4 100644 private void a(long i, PlayerChunk playerchunk) { CompletableFuture completablefuture = playerchunk.getChunkSave(); Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error -@@ -1020,7 +1073,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1020,7 +1082,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { if (completablefuture1 != completablefuture) { this.a(i, playerchunk); } else { @@ -6425,16 +11020,17 @@ index 6c399bcea..8286039c4 100644 if (ichunkaccess instanceof Chunk) { ((Chunk) ichunkaccess).setLoaded(false); } -@@ -1044,6 +1105,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1044,6 +1114,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.lightEngine.queueUpdate(); this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null); } -+ //if (removed) this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity ++ if (removed) this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity ++ if (removed) this.getVillagePlace().queueUnload(playerchunk.location.pair(), MinecraftServer.currentTickLong + 1); // Tuinity - unload POI data + } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks } }; -@@ -1059,6 +1122,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1059,6 +1132,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } protected boolean b() { @@ -6442,7 +11038,15 @@ index 6c399bcea..8286039c4 100644 if (!this.updatingChunksModified) { return false; } else { -@@ -1246,7 +1310,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1134,6 +1208,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + this.getVillagePlace().loadInData(chunkcoordintpair, chunkHolder.poiData); + chunkHolder.tasks.forEach(Runnable::run); ++ this.getVillagePlace().dequeueUnload(chunkcoordintpair.pair()); // Tuinity + // Paper end + + if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async +@@ -1246,7 +1321,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } // Paper end this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); @@ -6454,7 +11058,7 @@ index 6c399bcea..8286039c4 100644 } protected void c(ChunkCoordIntPair chunkcoordintpair) { -@@ -1498,6 +1565,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1498,6 +1576,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } public void setViewDistance(int i) { // Paper - public @@ -6462,7 +11066,7 @@ index 6c399bcea..8286039c4 100644 int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32 if (j != this.viewDistance) { -@@ -1511,6 +1579,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1511,6 +1590,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // Paper start - no-tick view distance public final void setNoTickViewDistance(int viewDistance) { @@ -6470,7 +11074,7 @@ index 6c399bcea..8286039c4 100644 viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32); this.noTickViewDistance = viewDistance; -@@ -1626,7 +1695,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1626,7 +1706,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( this.world, chunkcoordintpair.x, chunkcoordintpair.z, null, nbttagcompound, @@ -6479,7 +11083,7 @@ index 6c399bcea..8286039c4 100644 return; } super.write(chunkcoordintpair, nbttagcompound); -@@ -1710,6 +1779,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1710,6 +1790,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow(); } // Paper end @@ -6491,7 +11095,7 @@ index 6c399bcea..8286039c4 100644 // Paper start - async io -@@ -2037,22 +2111,25 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -2037,22 +2122,25 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { private final void processTrackQueue() { this.world.timings.tracker1.startTiming(); try { @@ -6764,10 +11368,60 @@ index 485b609bb..614cfacb1 100644 this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); // CraftBukkit - SPIGOT-5196 } diff --git a/src/main/java/net/minecraft/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java -index 5b0cd414c..a3ac88350 100644 +index 5b0cd414c..2c78cbffb 100644 --- a/src/main/java/net/minecraft/server/ProtoChunk.java +++ b/src/main/java/net/minecraft/server/ProtoChunk.java -@@ -179,14 +179,11 @@ public class ProtoChunk implements IChunkAccess { +@@ -48,6 +48,42 @@ public class ProtoChunk implements IChunkAccess { + private volatile boolean u; + final World world; // Paper - Anti-Xray - Add world // Paper - private -> default + ++ // Tuinity start - rewrite light engine ++ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(false); ++ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(true); ++ private volatile boolean wasLoadedFromDisk; ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.blockNibbles; ++ } ++ ++ @Override ++ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.blockNibbles = nibbles; ++ } ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.skyNibbles; ++ } ++ ++ @Override ++ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.skyNibbles = nibbles; ++ } ++ ++ @Override ++ public void setWasLoadedFromDisk(boolean wasLoadedFromDisk) { ++ this.wasLoadedFromDisk = wasLoadedFromDisk; ++ } ++ ++ @Override ++ public boolean wasLoadedFromDisk() { ++ return this.wasLoadedFromDisk; ++ } ++ // Tuinity end - rewrite light engine ++ + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { this(chunkcoordintpair, chunkconverter, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, World world) { +@@ -173,20 +209,17 @@ public class ProtoChunk implements IChunkAccess { + ChunkSection chunksection = this.a(j >> 4); + IBlockData iblockdata1 = chunksection.setType(i & 15, j & 15, k & 15, iblockdata); + +- if (this.g.b(ChunkStatus.FEATURES) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) { ++ if ((com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (this.g.b(ChunkStatus.LIGHT) && this.isLit()) : (this.g.b(ChunkStatus.FEATURES))) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) { // Tuinity - move block updates to only happen after lighting occurs + LightEngine lightengine = this.e(); + lightengine.a(blockposition); } @@ -6785,7 +11439,7 @@ index 5b0cd414c..a3ac88350 100644 HeightMap heightmap = (HeightMap) this.f.get(heightmap_type); if (heightmap == null) { -@@ -202,10 +199,9 @@ public class ProtoChunk implements IChunkAccess { +@@ -202,10 +235,9 @@ public class ProtoChunk implements IChunkAccess { HeightMap.a(this, enumset1); } @@ -6799,8 +11453,53 @@ index 5b0cd414c..a3ac88350 100644 ((HeightMap) this.f.get(heightmap_type)).a(i & 15, j, k & 15, iblockdata); } +diff --git a/src/main/java/net/minecraft/server/ProtoChunkExtension.java b/src/main/java/net/minecraft/server/ProtoChunkExtension.java +index 300cbb8b0..211d9fa90 100644 +--- a/src/main/java/net/minecraft/server/ProtoChunkExtension.java ++++ b/src/main/java/net/minecraft/server/ProtoChunkExtension.java +@@ -8,7 +8,39 @@ import javax.annotation.Nullable; + + public class ProtoChunkExtension extends ProtoChunk { + +- private final Chunk a; ++ private final Chunk a; public final Chunk getWrappedChunk() { return this.a; } // Tuinity - OBFHELPER ++ ++ // Tuinity start - rewrite light engine ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.getWrappedChunk().getBlockNibbles(); ++ } ++ ++ @Override ++ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.getWrappedChunk().setBlockNibbles(nibbles); ++ } ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.getWrappedChunk().getSkyNibbles(); ++ } ++ ++ @Override ++ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.getWrappedChunk().setSkyNibbles(nibbles); ++ } ++ ++ @Override ++ public boolean wasLoadedFromDisk() { ++ return this.getWrappedChunk().wasLoadedFromDisk(); ++ } ++ ++ @Override ++ public void setWasLoadedFromDisk(boolean wasLoadedFromDisk) { ++ this.getWrappedChunk().setWasLoadedFromDisk(wasLoadedFromDisk); ++ } ++ // Tuinity end - rewrite light engine + + public ProtoChunkExtension(Chunk chunk) { + super(chunk.getPos(), ChunkConverter.a, chunk.world); // Paper - Anti-Xray - Add parameter diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java -index e969625d2..3dfd91513 100644 +index 1751fb693..1ffa213a8 100644 --- a/src/main/java/net/minecraft/server/RegionFile.java +++ b/src/main/java/net/minecraft/server/RegionFile.java @@ -5,6 +5,7 @@ import java.io.BufferedInputStream; @@ -7606,20 +12305,94 @@ index 3382d678e..3b7894256 100644 return (InputStream) this.f.wrap(inputstream); } diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java -index 04256a951..d9362b74f 100644 +index 04256a951..79a11d17a 100644 --- a/src/main/java/net/minecraft/server/RegionFileSection.java +++ b/src/main/java/net/minecraft/server/RegionFileSection.java -@@ -50,8 +50,8 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab +@@ -25,8 +25,8 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + + private static final Logger LOGGER = LogManager.getLogger(); + // Paper - nuke IOWorker +- private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); +- protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); // Paper - private -> protected ++ private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); protected final Long2ObjectMap> getDataBySection() { return this.c; } // Tuinity - OBFHELPER ++ protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); protected final LongLinkedOpenHashSet getDirtySections() { return this.d; } // Paper - private -> protected // Tuinity - OBFHELPER + private final Function> e; + private final Function f; + private final DataFixer g; +@@ -50,8 +50,42 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab } - @Nullable - protected Optional c(long i) { ++ // Tuinity start - actually unload POI data ++ public void unloadData(long coordinate) { ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate); ++ this.writeDirtyData(chunkPos); ++ ++ Long2ObjectMap> data = this.getDataBySection(); ++ int before = data.size(); ++ ++ for (int section = 0; section < 16; ++section) { ++ data.remove(SectionPosition.asLong(chunkPos.x, section, chunkPos.z)); ++ } ++ ++ if (before != data.size()) { ++ this.onUnload(coordinate); ++ } ++ } ++ ++ protected void onUnload(long coordinate) {} ++ ++ public boolean isEmpty(long coordinate) { ++ Long2ObjectMap> data = this.getDataBySection(); ++ int x = MCUtil.getCoordinateX(coordinate); ++ int z = MCUtil.getCoordinateZ(coordinate); ++ for (int section = 0; section < 16; ++section) { ++ Optional optional = data.get(SectionPosition.asLong(x, section, z)); ++ if (optional != null && optional.orElse(null) != null) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Tuinity end - actually unload POI data ++ + @Nullable protected Optional getIfLoaded(long value) { return this.c(value); } // Tuinity - OBFHELPER + @Nullable protected Optional c(long i) { // Tuinity - OBFHELPER return (Optional) this.c.get(i); } +@@ -150,6 +184,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + }); + } + } ++ if (this instanceof VillagePlace) { ((VillagePlace)this).queueUnload(chunkcoordintpair.pair(), MinecraftServer.currentTickLong + 1); } // Tuinity - unload POI data + + } + +@@ -221,6 +256,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + return dynamic.get("DataVersion").asInt(1945); + } + ++ public final void writeDirtyData(ChunkCoordIntPair chunkcoordintpair) { this.a(chunkcoordintpair); } // Tuinity - OBFHELPER + public void a(ChunkCoordIntPair chunkcoordintpair) { + if (!this.d.isEmpty()) { + for (int i = 0; i < 16; ++i) { +diff --git a/src/main/java/net/minecraft/server/SectionPosition.java b/src/main/java/net/minecraft/server/SectionPosition.java +index f95925f1c..0bb3ad0bf 100644 +--- a/src/main/java/net/minecraft/server/SectionPosition.java ++++ b/src/main/java/net/minecraft/server/SectionPosition.java +@@ -7,7 +7,7 @@ import java.util.stream.StreamSupport; + + public class SectionPosition extends BaseBlockPosition { + +- private SectionPosition(int i, int j, int k) { ++ public SectionPosition(int i, int j, int k) { // Tuinity - private -> public + super(i, j, k); + } + diff --git a/src/main/java/net/minecraft/server/SensorNearestBed.java b/src/main/java/net/minecraft/server/SensorNearestBed.java index ad3609f2b..d3d28f97f 100644 --- a/src/main/java/net/minecraft/server/SensorNearestBed.java @@ -7876,16 +12649,17 @@ index e41cb8613..c19ffb925 100644 return j != 0L && i - this.d > j; } diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java -index 5c789b25f..25cff70b4 100644 +index 5c789b25f..3f942c632 100644 --- a/src/main/java/net/minecraft/server/TicketType.java +++ b/src/main/java/net/minecraft/server/TicketType.java -@@ -26,8 +26,19 @@ public class TicketType { +@@ -26,8 +26,21 @@ public class TicketType { public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper - public static final TicketType DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper + public static final TicketType DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads + public static final TicketType REQUIRED_LOAD = a("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail ++ public static final TicketType LIGHT_UPDATE = a("light_update", Comparator.comparingLong(ChunkCoordIntPair::pair)); // Tuinity - ensure chunks stay loaded for lighting + // Tuinity start - delay chunk unloads + boolean delayUnloadViable = true; @@ -7895,6 +12669,7 @@ index 5c789b25f..25cff70b4 100644 + TicketType.PRIORITY.delayUnloadViable = false; + TicketType.URGENT.delayUnloadViable = false; + TicketType.DELAYED_UNLOAD.delayUnloadViable = false; ++ TicketType.LIGHT_UPDATE.delayUnloadViable = false; // Tuinity - ensure chunks stay loaded for lighting + } + // Tuinity end - delay chunk unloads public static TicketType a(String s, Comparator comparator) { @@ -8020,10 +12795,193 @@ index 7f05587d4..5af554870 100644 return this.x * this.x + this.y * this.y + this.z * this.z; } diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java -index b926cebd0..3c9668c9c 100644 +index b926cebd0..99778f80c 100644 --- a/src/main/java/net/minecraft/server/VillagePlace.java +++ b/src/main/java/net/minecraft/server/VillagePlace.java -@@ -165,7 +165,7 @@ public class VillagePlace extends RegionFileSection { +@@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Pair; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; + import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Tuinity + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import java.io.File; +@@ -22,8 +23,24 @@ import java.util.stream.Stream; + + public class VillagePlace extends RegionFileSection { + +- private final VillagePlace.a a = new VillagePlace.a(); +- private final LongSet b = new LongOpenHashSet(); ++ // Tuinity start - unload poi data ++ // the vanilla tracker needs to be replaced because it does not support level removes ++ private final com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D(); ++ static final int POI_DATA_SOURCE = 7; ++ public static int convertBetweenLevels(final int level) { ++ return POI_DATA_SOURCE - level; ++ } ++ ++ protected void updateDistanceTracking(long section) { ++ if (this.isSectionDistanceTrackerSource(section)) { ++ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); ++ } else { ++ this.villageDistanceTracker.removeSource(section); ++ } ++ } ++ // Tuinity end - unload poi data ++ ++ private final LongSet b = new LongOpenHashSet(); private final LongSet getLoadedChunks() { return this.b; } // Tuinity - OBFHELPER + + private final WorldServer world; // Paper + +@@ -34,9 +51,124 @@ public class VillagePlace extends RegionFileSection { + public VillagePlace(File file, DataFixer datafixer, boolean flag, WorldServer world) { + super(file, VillagePlaceSection::a, VillagePlaceSection::new, datafixer, DataFixTypes.POI_CHUNK, flag); + this.world = world; ++ if (world == null) { throw new IllegalStateException("world must be non-null"); }// Tuinity - require non-null + // Paper end - add world parameter + } + ++ // Tuinity start - actually unload POI data ++ private final java.util.TreeSet queuedUnloads = new java.util.TreeSet<>(); ++ private final Long2ObjectOpenHashMap queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>(); ++ ++ static final class QueuedUnload implements Comparable { ++ ++ private final long unloadTick; ++ private final long coordinate; ++ ++ public QueuedUnload(long unloadTick, long coordinate) { ++ this.unloadTick = unloadTick; ++ this.coordinate = coordinate; ++ } ++ ++ @Override ++ public int compareTo(QueuedUnload other) { ++ if (other.unloadTick == this.unloadTick) { ++ return Long.compare(this.coordinate, other.coordinate); ++ } else { ++ return Long.compare(this.unloadTick, other.unloadTick); ++ } ++ } ++ ++ @Override ++ public int hashCode() { ++ int hash = 1; ++ hash = hash * 31 + Long.hashCode(this.unloadTick); ++ hash = hash * 31 + Long.hashCode(this.coordinate); ++ return hash; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (obj == null || obj.getClass() != QueuedUnload.class) { ++ return false; ++ } ++ QueuedUnload other = (QueuedUnload)obj; ++ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate; ++ } ++ } ++ ++ long determineDelay(long coordinate) { ++ if (this.isEmpty(coordinate)) { ++ return 60 * 20; ++ } else { ++ return 5 * 20; ++ } ++ } ++ ++ public void queueUnload(long coordinate, long minTarget) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload queue"); ++ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate); ++ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload); ++ if (existing != null) { ++ this.queuedUnloads.remove(existing); ++ } ++ this.queuedUnloads.add(unload); ++ } ++ ++ public void dequeueUnload(long coordinate) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload dequeue"); ++ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate); ++ if (unload != null) { ++ this.queuedUnloads.remove(unload); ++ } ++ } ++ ++ public void pollUnloads(BooleanSupplier canSleepForTick) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload"); ++ long currentTick = MinecraftServer.currentTickLong; ++ ChunkProviderServer chunkProvider = this.world.getChunkProvider(); ++ PlayerChunkMap playerChunkMap = chunkProvider.playerChunkMap; ++ // copied target determination from PlayerChunkMap ++ int target = Math.min(this.queuedUnloads.size() - 100, (int) (this.queuedUnloads.size() * PlayerChunkMap.UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive ++ for (java.util.Iterator iterator = this.queuedUnloads.iterator(); ++ iterator.hasNext() && (this.queuedUnloads.size() > target || canSleepForTick.getAsBoolean());) { ++ QueuedUnload unload = iterator.next(); ++ if (unload.unloadTick > currentTick) { ++ break; ++ } ++ ++ long coordinate = unload.coordinate; ++ ++ iterator.remove(); ++ this.queuedUnloadsByCoordinate.remove(coordinate); ++ ++ if (playerChunkMap.getUnloadingPlayerChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate)) != null ++ || playerChunkMap.getUpdatingChunk(coordinate) != null) { ++ continue; ++ } ++ ++ this.unloadData(coordinate); ++ } ++ } ++ ++ @Override ++ public void unloadData(long coordinate) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async unloading poi data"); ++ super.unloadData(coordinate); ++ } ++ ++ @Override ++ protected void onUnload(long coordinate) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload callback"); ++ this.getLoadedChunks().remove(coordinate); ++ int chunkX = MCUtil.getCoordinateX(coordinate); ++ int chunkZ = MCUtil.getCoordinateZ(coordinate); ++ for (int section = 0; section < 16; ++section) { ++ long sectionPos = SectionPosition.asLong(chunkX, section, chunkZ); ++ this.updateDistanceTracking(sectionPos); ++ } ++ } ++ // Tuinity end - actually unload POI data ++ + public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) { + ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition, villageplacetype); + } +@@ -138,10 +270,11 @@ public class VillagePlace extends RegionFileSection { + } + + public int a(SectionPosition sectionposition) { +- this.a.a(); +- return this.a.c(sectionposition.s()); ++ this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking util ++ return convertBetweenLevels(this.villageDistanceTracker.getLevel(MCUtil.getSectionKey(sectionposition))); // Tuinity - replace distance tracking util + } + ++ private boolean isSectionDistanceTrackerSource(long section) { return this.f(section); } // Tuinity - OBFHELPER + private boolean f(long i) { + Optional optional = this.c(i); + +@@ -157,7 +290,7 @@ public class VillagePlace extends RegionFileSection { + super.a(booleansupplier); + } else { + //super.a(booleansupplier); // re-implement below +- while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean()) { ++ while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean() && !this.world.isSavingDisabled()) { // Tuinity - unload POI data - don't write to disk if saving is disabled + ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).r(); + + NBTTagCompound data; +@@ -165,22 +298,27 @@ public class VillagePlace extends RegionFileSection { data = this.getData(chunkcoordintpair); } com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, @@ -8031,8 +12989,40 @@ index b926cebd0..3c9668c9c 100644 + chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority } } ++ // Tuinity start - unload POI data ++ if (!this.world.isSavingDisabled()) { // don't write to disk if saving is disabled ++ this.pollUnloads(booleansupplier); ++ } ++ // Tuinity end - unload POI data // Paper end -@@ -290,7 +290,7 @@ public class VillagePlace extends RegionFileSection { +- this.a.a(); ++ this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking until + } + + @Override + protected void a(long i) { + super.a(i); +- this.a.b(i, this.a.b(i), false); ++ this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util + } + + @Override + protected void b(long i) { +- this.a.b(i, this.a.b(i), false); ++ this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util + } + + public void a(ChunkCoordIntPair chunkcoordintpair, ChunkSection chunksection) { +@@ -245,7 +383,7 @@ public class VillagePlace extends RegionFileSection { + + @Override + protected int b(long i) { +- return VillagePlace.this.f(i) ? 0 : 7; ++ return VillagePlace.this.f(i) ? 0 : 7; // Tuinity - unload poi data - diff on change, this specifies the source level to use for distance tracking + } + + @Override +@@ -290,7 +428,7 @@ public class VillagePlace extends RegionFileSection { if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null, @@ -8041,7 +13031,7 @@ index b926cebd0..3c9668c9c 100644 return; } super.write(chunkcoordintpair, nbttagcompound); -@@ -309,6 +309,7 @@ public class VillagePlace extends RegionFileSection { +@@ -309,6 +447,7 @@ public class VillagePlace extends RegionFileSection { this.d = predicate; } @@ -8082,7 +13072,7 @@ index 77c66bc99..f43bc1f7d 100644 return villageplacesection.e; }), VillagePlaceRecord.a(runnable).listOf().fieldOf("Records").forGetter((villageplacesection) -> { diff --git a/src/main/java/net/minecraft/server/VoxelShape.java b/src/main/java/net/minecraft/server/VoxelShape.java -index eb926b74e..700660dd9 100644 +index eb926b74e..e3b72922e 100644 --- a/src/main/java/net/minecraft/server/VoxelShape.java +++ b/src/main/java/net/minecraft/server/VoxelShape.java @@ -8,11 +8,11 @@ import javax.annotation.Nullable; @@ -8099,7 +13089,7 @@ index eb926b74e..700660dd9 100644 this.a = voxelshapediscrete; } -@@ -48,9 +48,15 @@ public abstract class VoxelShape { +@@ -48,9 +48,16 @@ public abstract class VoxelShape { public final VoxelShape offset(double x, double y, double z) { return this.a(x, y, z); } // Paper - OBFHELPER public VoxelShape a(double d0, double d1, double d2) { @@ -8113,10 +13103,11 @@ index eb926b74e..700660dd9 100644 + } + // Tuinity end - optimise multi-aabb shapes + ++ public final VoxelShape simplify() { return this.c(); } // Tuinity - OBFHELPER public VoxelShape c() { VoxelShape[] avoxelshape = new VoxelShape[]{VoxelShapes.a()}; -@@ -70,6 +76,7 @@ public abstract class VoxelShape { +@@ -70,6 +77,7 @@ public abstract class VoxelShape { }, true); } @@ -8259,10 +13250,10 @@ index e841611bb..259605daa 100644 } diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java -index e21c747b6..db735e29d 100644 +index e21c747b6..636bbbc42 100644 --- a/src/main/java/net/minecraft/server/VoxelShapes.java +++ b/src/main/java/net/minecraft/server/VoxelShapes.java -@@ -17,18 +17,80 @@ public final class VoxelShapes { +@@ -17,18 +17,101 @@ public final class VoxelShapes { voxelshapebitset.a(0, 0, 0, true, true); return new VoxelShapeCube(voxelshapebitset); @@ -8287,12 +13278,14 @@ index e21c747b6..db735e29d 100644 + static final com.tuinity.tuinity.voxel.AABBVoxelShape optimisedFullCube = new com.tuinity.tuinity.voxel.AABBVoxelShape(new AxisAlignedBB(0, 0, 0, 1.0, 1.0, 1.0)); // Tuinity - optimise voxelshape + + // Tuinity start - optimise voxelshapes -+ public static void addBoxesToIfIntersects(VoxelShape shape, AxisAlignedBB aabb, java.util.List list) { ++ public static boolean addBoxesToIfIntersects(VoxelShape shape, AxisAlignedBB aabb, java.util.List list) { + if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { + com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape; -+ if (shapeCasted.aabb.voxelShapeIntersect(aabb)) { ++ if (!shapeCasted.aabb.isEmpty() && shapeCasted.aabb.voxelShapeIntersect(aabb)) { + list.add(shapeCasted.aabb); ++ return true; + } ++ return false; + } else if (shape instanceof VoxelShapeArray) { + VoxelShapeArray shapeCasted = (VoxelShapeArray)shape; + // this can be optimised by checking an "overall shape" first, but not needed @@ -8301,39 +13294,58 @@ index e21c747b6..db735e29d 100644 + double offY = shapeCasted.offsetY; + double offZ = shapeCasted.offsetZ; + ++ boolean ret = false; ++ + for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) { + double minX, minY, minZ, maxX, maxY, maxZ; + if (aabb.voxelShapeIntersect(minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ, + maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ)) { -+ list.add(new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ, false)); ++ AxisAlignedBB box = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ, false); ++ if (!box.isEmpty()) { ++ list.add(box); ++ ret = true; ++ } + } + } ++ ++ return ret; + } else { ++ boolean ret = false; ++ + java.util.List boxes = shape.getBoundingBoxesRepresentation(); + for (int i = 0, len = boxes.size(); i < len; ++i) { + AxisAlignedBB box = boxes.get(i); -+ if (box.voxelShapeIntersect(aabb)) { ++ if (!box.isEmpty() && box.voxelShapeIntersect(aabb)) { + list.add(box); ++ ret = true; + } + } ++ ++ return ret; + } + } + + public static void addBoxesTo(VoxelShape shape, java.util.List list) { + if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { + com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape; -+ list.add(shapeCasted.aabb); ++ if (!shapeCasted.isEmpty()) { ++ list.add(shapeCasted.aabb); ++ } + } else if (shape instanceof VoxelShapeArray) { + VoxelShapeArray shapeCasted = (VoxelShapeArray)shape; + + for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) { -+ list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ)); ++ if (!boundingBox.isEmpty()) { ++ list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ)); ++ } + } + } else { + java.util.List boxes = shape.getBoundingBoxesRepresentation(); + for (int i = 0, len = boxes.size(); i < len; ++i) { + AxisAlignedBB box = boxes.get(i); -+ list.add(box); ++ if (!box.isEmpty()) { ++ list.add(box); ++ } + } + } + } @@ -8346,7 +13358,7 @@ index e21c747b6..db735e29d 100644 } public static VoxelShape create(double d0, double d1, double d2, double d3, double d4, double d5) { -@@ -67,7 +129,7 @@ public final class VoxelShapes { +@@ -67,7 +150,7 @@ public final class VoxelShapes { return new VoxelShapeCube(voxelshapebitset); } } else { @@ -8355,7 +13367,7 @@ index e21c747b6..db735e29d 100644 } } -@@ -132,6 +194,20 @@ public final class VoxelShapes { +@@ -132,6 +215,20 @@ public final class VoxelShapes { public static final boolean applyOperation(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { return VoxelShapes.c(voxelshape, voxelshape1, operatorboolean); } // Paper - OBFHELPER public static boolean c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { @@ -8376,13 +13388,56 @@ index e21c747b6..db735e29d 100644 if (operatorboolean.apply(false, false)) { throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException())); } else if (voxelshape == voxelshape1) { -@@ -314,8 +390,9 @@ public final class VoxelShapes { +@@ -314,8 +411,52 @@ public final class VoxelShapes { } } + public static boolean combinationOccludes(VoxelShape voxelshape, VoxelShape voxelshape1) { return b(voxelshape, voxelshape1); } // Tuinity - OBFHELPER public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) { - return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; ++ if (voxelshape == getFullUnoptimisedCube() || voxelshape == optimisedFullCube ++ || voxelshape1 == getFullUnoptimisedCube() || voxelshape1 == optimisedFullCube) { ++ return true; ++ } ++ boolean v1Empty = voxelshape == getEmptyShape(); ++ boolean v2Empty = voxelshape1 == getEmptyShape(); ++ if (v1Empty && v2Empty) { ++ return false; ++ } ++ if ((voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v1Empty) && (voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v2Empty)) { ++ if (!v1Empty && !v2Empty && (voxelshape != voxelshape1)) { ++ AxisAlignedBB boundingBox1 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb; ++ AxisAlignedBB boundingBox2 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb; ++ // can call it here in some cases ++ ++ // check overall bounding box ++ double minY = Math.min(boundingBox1.minY, boundingBox2.minY); ++ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY); ++ if (minY > MCUtil.COLLISION_EPSILON || maxY < (1 - MCUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ double minX = Math.min(boundingBox1.minX, boundingBox2.minX); ++ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX); ++ if (minX > MCUtil.COLLISION_EPSILON || maxX < (1 - MCUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ); ++ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ); ++ if (minZ > MCUtil.COLLISION_EPSILON || maxZ < (1 - MCUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ // fall through to full merge check ++ } else { ++ AxisAlignedBB boundingBox = v1Empty ? ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb : ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb; ++ // check if the bounding box encloses the full cube ++ return (boundingBox.minY <= MCUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - MCUtil.COLLISION_EPSILON)) && ++ (boundingBox.minX <= MCUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - MCUtil.COLLISION_EPSILON)) && ++ (boundingBox.minZ <= MCUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - MCUtil.COLLISION_EPSILON)); ++ } ++ } ++ return b_rare(voxelshape, voxelshape1); ++ } ++ public static boolean b_rare(VoxelShape voxelshape, VoxelShape voxelshape1) { + return (voxelshape != b() || voxelshape != getFullUnoptimisedCube()) && (voxelshape1 != b() || voxelshape1 != getFullUnoptimisedCube()) ? ((voxelshape == VoxelShapes.getEmptyShape() || voxelshape.isEmpty()) && (voxelshape1 == VoxelShapes.getEmptyShape() || voxelshape1.isEmpty()) ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; // Tuinity - optimise call by checking against more constant shapes } @@ -8745,7 +13800,7 @@ index f01186988..26a8c4ffe 100644 return this.j.d(); } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index b19681031..274a383be 100644 +index b19681031..0ac1bd39a 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -55,12 +55,13 @@ import org.bukkit.event.server.MapInitializeEvent; @@ -8877,253 +13932,20 @@ index b19681031..274a383be 100644 // Add env and gen to constructor, WorldData -> WorldDataServer public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor -@@ -265,6 +364,451 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -265,6 +364,303 @@ public class WorldServer extends World implements GeneratorAccessSeed { this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper } + // Tuinity start - optimise collision -+ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) { -+ if (entity != null) { -+ if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) { -+ return true; -+ } -+ } -+ -+ int minBlockX = MathHelper.floor(axisalignedbb.minX - MCUtil.COLLISION_EPSILON) - 1; -+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1; -+ -+ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1; -+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1; -+ -+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1; -+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 1; -+ -+ -+ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); -+ VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy -+ -+ // special cases: -+ if (minBlockY > 255 || maxBlockY < 0) { -+ // no point in checking -+ return false; -+ } -+ -+ int minYIterate = Math.max(0, minBlockY); -+ int maxYIterate = Math.min(255, maxBlockY); -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider; -+ // TODO special case single chunk? -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk -+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk -+ -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk -+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk -+ -+ int chunkXGlobalPos = currChunkX << 4; -+ int chunkZGlobalPos = currChunkZ << 4; -+ Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); -+ -+ if (chunk == null) { -+ return true; -+ } -+ -+ ChunkSection[] sections = chunk.getSections(); -+ -+ // bound y -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ ChunkSection section = sections[currY >>> 4]; -+ if (section == null || section.isFullOfAir()) { -+ // empty -+ // skip to next section -+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one -+ continue; -+ } -+ -+ DataPaletteBlock blocks = section.blockIds; -+ int blockKeyY = (currY & 15) << 8; -+ -+ int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ int blockKeyZY = blockKeyY | (currZ << 4); -+ int blockZ = currZ | chunkZGlobalPos; // world position -+ -+ int edgeCountZY; -+ if (blockZ == minBlockZ || blockZ == maxBlockZ) { -+ edgeCountZY = edgeCountY + 1; -+ } else { -+ edgeCountZY = edgeCountY; -+ } -+ -+ for (int currX = minX; currX <= maxX; ++currX) { -+ int blockX = currX | chunkXGlobalPos; // world position -+ -+ int edgeCountFull; -+ if (blockX == minBlockX || blockX == maxBlockX) { -+ edgeCountFull = edgeCountZY + 1; -+ } else { -+ edgeCountFull = edgeCountZY; -+ } -+ -+ if (edgeCountFull == 3) { -+ continue; -+ } -+ -+ int blockKeyFull = blockKeyZY | currX; -+ IBlockData blockData = blocks.rawGet(blockKeyFull); -+ -+ if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.setValues(blockX, currY, blockZ); -+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); -+ if (voxelshape2 != VoxelShapes.getEmptyShape()) { -+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ); -+ -+ if (voxelshape3.intersects(axisalignedbb)) { -+ return true; -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ return false; ++ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks, ++ boolean collidesWithUnloaded) { ++ return this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, loadChunks, collidesWithUnloaded, true, null); + } + -+ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks, boolean collideWithUnloaded, java.util.function.BiPredicate predicate) { -+ if (entity != null) { -+ if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) { -+ return true; -+ } -+ } -+ -+ int minBlockX = MathHelper.floor(axisalignedbb.minX - MCUtil.COLLISION_EPSILON) - 1; -+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1; -+ -+ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1; -+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1; -+ -+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1; -+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 1; -+ -+ -+ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); -+ VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy -+ -+ // special cases: -+ if (minBlockY > 255 || maxBlockY < 0) { -+ // no point in checking -+ return false; -+ } -+ -+ int minYIterate = Math.max(0, minBlockY); -+ int maxYIterate = Math.min(255, maxBlockY); -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider; -+ // TODO special case single chunk? -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk -+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk -+ -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk -+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk -+ -+ int chunkXGlobalPos = currChunkX << 4; -+ int chunkZGlobalPos = currChunkZ << 4; -+ Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); -+ -+ if (chunk == null) { -+ if (collideWithUnloaded) { -+ return true; -+ } else { -+ continue; -+ } -+ } -+ -+ ChunkSection[] sections = chunk.getSections(); -+ -+ // bound y -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ ChunkSection section = sections[currY >>> 4]; -+ if (section == null || section.isFullOfAir()) { -+ // empty -+ // skip to next section -+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one -+ continue; -+ } -+ -+ DataPaletteBlock blocks = section.blockIds; -+ int blockKeyY = (currY & 15) << 8; -+ -+ int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ int blockKeyZY = blockKeyY | (currZ << 4); -+ int blockZ = currZ | chunkZGlobalPos; // world position -+ -+ int edgeCountZY; -+ if (blockZ == minBlockZ || blockZ == maxBlockZ) { -+ edgeCountZY = edgeCountY + 1; -+ } else { -+ edgeCountZY = edgeCountY; -+ } -+ -+ for (int currX = minX; currX <= maxX; ++currX) { -+ int blockX = currX | chunkXGlobalPos; // world position -+ -+ int edgeCountFull; -+ if (blockX == minBlockX || blockX == maxBlockX) { -+ edgeCountFull = edgeCountZY + 1; -+ } else { -+ edgeCountFull = edgeCountZY; -+ } -+ -+ if (edgeCountFull == 3) { -+ continue; -+ } -+ -+ int blockKeyFull = blockKeyZY | currX; -+ IBlockData blockData = blocks.rawGet(blockKeyFull); -+ -+ if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.setValues(blockX, currY, blockZ); -+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); -+ if (voxelshape2 != VoxelShapes.getEmptyShape()) { -+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ); -+ -+ if (voxelshape3.intersects(axisalignedbb) && (predicate == null || predicate.test(blockData, mutablePos))) { -+ return true; -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ return false; ++ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, ++ boolean loadChunks, boolean collidesWithUnloaded, ++ java.util.function.BiPredicate predicate) { ++ return this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, loadChunks, collidesWithUnloaded, true, predicate); + } + + public final boolean hardCollidesWithAnyEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { @@ -9158,13 +13980,24 @@ index b19681031..274a383be 100644 + } + + public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) { -+ return this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, loadChunks) || this.hardCollidesWithAnyEntities(entity, axisalignedbb, null); ++ return this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, loadChunks, true) ++ || this.hardCollidesWithAnyEntities(entity, axisalignedbb, null); + } + -+ public void getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, boolean loadChunks) { ++ // returns whether any collisions were detected ++ public boolean getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, ++ boolean loadChunks, boolean collidesWithUnloaded, boolean checkOnly, ++ java.util.function.BiPredicate predicate) { ++ boolean ret = false; ++ + if (entity != null) { + if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) { -+ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list); ++ if (checkOnly) { ++ return true; ++ } else { ++ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list); ++ ret = true; ++ } + } + } + @@ -9179,12 +14012,12 @@ index b19681031..274a383be 100644 + + + BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); -+ VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy ++ VoxelShapeCollision collisionShape = null; + + // special cases: + if (minBlockY > 255 || maxBlockY < 0) { + // no point in checking -+ return; ++ return ret; + } + + int minYIterate = Math.max(0, minBlockY); @@ -9212,7 +14045,14 @@ index b19681031..274a383be 100644 + Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); + + if (chunk == null) { -+ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ)); ++ if (collidesWithUnloaded) { ++ if (checkOnly) { ++ return true; ++ } else { ++ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } + continue; + } + @@ -9229,46 +14069,104 @@ index b19681031..274a383be 100644 + continue; + } + ++ int minXIterate; ++ int maxXIterate; ++ int minZIterate; ++ int maxZIterate; ++ ++ boolean sectionHasSpecial = section.hasSpecialCollidingBlocks(); ++ if (sectionHasSpecial && currChunkX == minChunkX) { ++ minXIterate = minX + 1; ++ } else { ++ minXIterate = minX; ++ } ++ if (sectionHasSpecial && currChunkX == maxChunkX) { ++ maxXIterate = maxX - 1; ++ } else { ++ maxXIterate = maxX; ++ } ++ ++ if (sectionHasSpecial && currChunkZ == minChunkZ) { ++ minZIterate = minZ + 1; ++ } else { ++ minZIterate = minZ; ++ } ++ if (sectionHasSpecial && currChunkZ == maxChunkZ) { ++ maxZIterate = maxZ - 1; ++ } else { ++ maxZIterate = maxZ; ++ } ++ + DataPaletteBlock blocks = section.blockIds; -+ int blockKeyY = (currY & 15) << 8; + -+ int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0; ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ block_search_loop: ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8); ++ long blockInfo = section.getKnownBlockInfo(localBlockIndex); ++ switch ((int)blockInfo) { ++ case (int)ChunkSection.KNOWN_EMPTY_BLOCK: { ++ continue block_search_loop; ++ } ++ case (int)ChunkSection.KNOWN_FULL_BLOCK: { ++ AxisAlignedBB box = new AxisAlignedBB( ++ currX | chunkXGlobalPos, currY, currZ | chunkZGlobalPos, ++ (currX | chunkXGlobalPos) + 1, currY + 1, (currZ | chunkZGlobalPos) + 1, ++ false ++ ); ++ if (predicate != null) { ++ if (!box.voxelShapeIntersect(axisalignedbb)) { ++ continue block_search_loop; ++ } ++ // fall through to get the block for the predicate ++ } else { ++ if (box.voxelShapeIntersect(axisalignedbb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ list.add(box); ++ ret = true; ++ } ++ } ++ continue block_search_loop; ++ } ++ } ++ default: { ++ int blockX = currX | chunkXGlobalPos; ++ int blockY = currY; ++ int blockZ = currZ | chunkZGlobalPos; + -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ int blockKeyZY = blockKeyY | (currZ << 4); -+ int blockZ = currZ | chunkZGlobalPos; // world position ++ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3 || (edgeCount != 0 && blockInfo != ChunkSection.KNOWN_SPECIAL_BLOCK)) { ++ continue block_search_loop; ++ } + -+ int edgeCountZY; -+ if (blockZ == minBlockZ || blockZ == maxBlockZ) { -+ edgeCountZY = edgeCountY + 1; -+ } else { -+ edgeCountZY = edgeCountY; -+ } ++ IBlockData blockData = blocks.rawGet(localBlockIndex); + -+ for (int currX = minX; currX <= maxX; ++currX) { -+ int blockX = currX | chunkXGlobalPos; // world position ++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { ++ mutablePos.setValues(blockX, blockY, blockZ); ++ if (collisionShape == null) { ++ collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); ++ } ++ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); ++ if (voxelshape2 != VoxelShapes.getEmptyShape()) { ++ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)blockY, (double)blockZ); + -+ int edgeCountFull; -+ if (blockX == minBlockX || blockX == maxBlockX) { -+ edgeCountFull = edgeCountZY + 1; -+ } else { -+ edgeCountFull = edgeCountZY; -+ } ++ if (predicate != null && !predicate.test(blockData, mutablePos)) { ++ continue block_search_loop; ++ } + -+ if (edgeCountFull == 3) { -+ continue; -+ } -+ -+ int blockKeyFull = blockKeyZY | currX; -+ IBlockData blockData = blocks.rawGet(blockKeyFull); -+ -+ if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.setValues(blockX, currY, blockZ); -+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); -+ if (voxelshape2 != VoxelShapes.getEmptyShape()) { -+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ); -+ -+ VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list); ++ if (checkOnly) { ++ if (voxelshape3.intersects(axisalignedbb)) { ++ return true; ++ } ++ } else { ++ ret |= VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list); ++ } ++ } ++ } + } + } + } @@ -9276,6 +14174,8 @@ index b19681031..274a383be 100644 + } + } + } ++ ++ return ret; + } + + public final void getEntityHardCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate, List list) { @@ -9295,7 +14195,9 @@ index b19681031..274a383be 100644 + Entity otherEntity = entities.get(i); + + if ((entity == null || otherEntity.collisionBoxIsHard()) || entity.hardCollidesWith(otherEntity)) { -+ list.add(otherEntity.getBoundingBox()); ++ if (!otherEntity.getBoundingBox().isEmpty()) { ++ list.add(otherEntity.getBoundingBox()); ++ } + } + } + } finally { @@ -9304,11 +14206,16 @@ index b19681031..274a383be 100644 + } + + public final void getCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, boolean loadChunks) { -+ this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, list, loadChunks); ++ this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, list, loadChunks, true, false, null); + this.getEntityHardCollisions(entity, axisalignedbb, null, list); + } + + @Override ++ public boolean getCubes(AxisAlignedBB axisalignedbb) { ++ return !this.hasAnyCollisions(null, axisalignedbb); ++ } ++ ++ @Override + public boolean getCubes(Entity entity) { + return !this.hasAnyCollisions(entity, entity.getBoundingBox()); + } @@ -9322,14 +14229,14 @@ index b19681031..274a383be 100644 + @Override + public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { + if (entity instanceof EntityArmorStand && !entity.world.paperConfig.armorStandEntityLookups) return false; -+ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, predicate); ++ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, true, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, predicate); + } + // Tuinity end - optimise collision + // CraftBukkit start @Override protected TileEntity getTileEntity(BlockPosition pos, boolean validate) { -@@ -318,6 +862,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -318,6 +714,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { public void doTick(BooleanSupplier booleansupplier) { GameProfilerFiller gameprofilerfiller = this.getMethodProfiler(); @@ -9344,7 +14251,7 @@ index b19681031..274a383be 100644 this.ticking = true; gameprofilerfiller.enter("world border"); -@@ -467,7 +1019,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -467,7 +871,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } timings.scheduledBlocks.stopTiming(); // Paper @@ -9353,7 +14260,7 @@ index b19681031..274a383be 100644 gameprofilerfiller.exitEnter("raid"); this.timings.raids.startTiming(); // Paper - timings this.persistentRaid.a(); -@@ -476,7 +1028,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -476,7 +880,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { timings.doSounds.startTiming(); // Spigot this.ak(); timings.doSounds.stopTiming(); // Spigot @@ -9362,7 +14269,7 @@ index b19681031..274a383be 100644 this.ticking = false; gameprofilerfiller.exitEnter("entities"); boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players -@@ -492,13 +1044,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -492,13 +896,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { } this.tickingEntities = true; @@ -9378,16 +14285,39 @@ index b19681031..274a383be 100644 Entity entity1 = entity.getVehicle(); /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed -@@ -534,7 +1085,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -514,6 +917,15 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.enter("checkDespawn"); + if (!entity.dead) { + entity.checkDespawn(); ++ // Tuinity start - optimise notify() ++ if (entity.inChunk && entity.valid) { ++ if (this.getChunkProvider().isInEntityTickingChunk(entity)) { ++ this.updateNavigatorsInRegion(entity); ++ } ++ } else { ++ this.removeNavigatorsFromData(entity); ++ } ++ // Tuinity end - optimise notify() + } + + gameprofilerfiller.exit(); +@@ -534,14 +946,22 @@ public class WorldServer extends World implements GeneratorAccessSeed { gameprofilerfiller.enter("remove"); if (entity.dead) { this.removeEntityFromChunk(entity); - objectiterator.remove(); + this.entitiesById.remove(entity.getId()); // Tuinity this.unregisterEntity(entity); ++ } else if (entity.inChunk && entity.valid) { // Tuinity start - optimise notify() ++ if (this.getChunkProvider().isInEntityTickingChunk(entity)) { ++ this.updateNavigatorsInRegion(entity); ++ } ++ } else { ++ this.removeNavigatorsFromData(entity); } ++ // Tuinity end - optimise notify() -@@ -542,6 +1093,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.exit(); } timings.entityTick.stopTiming(); // Spigot @@ -9395,7 +14325,7 @@ index b19681031..274a383be 100644 this.tickingEntities = false; // Paper start for (java.lang.Runnable run : this.afterEntityTickingTasks) { -@@ -553,7 +1105,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -553,7 +973,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } this.afterEntityTickingTasks.clear(); // Paper end @@ -9404,7 +14334,7 @@ index b19681031..274a383be 100644 Entity entity2; -@@ -563,7 +1115,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -563,7 +983,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } timings.tickEntities.stopTiming(); // Spigot @@ -9413,7 +14343,7 @@ index b19681031..274a383be 100644 this.tickBlockEntities(); } -@@ -809,7 +1361,26 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -809,7 +1229,26 @@ public class WorldServer extends World implements GeneratorAccessSeed { } @@ -9440,7 +14370,7 @@ index b19681031..274a383be 100644 if (!(entity instanceof EntityHuman) && !this.getChunkProvider().a(entity)) { this.chunkCheck(entity); } else { -@@ -862,6 +1433,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -862,6 +1301,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { //} finally { timer.stopTiming(); } // Paper - timings - move up } @@ -9452,7 +14382,45 @@ index b19681031..274a383be 100644 } public void a(Entity entity, Entity entity1) { -@@ -1299,7 +1875,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -919,6 +1363,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + int i = MathHelper.floor(entity.locX() / 16.0D); + int j = Math.min(15, Math.max(0, MathHelper.floor(entity.locY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior + int k = MathHelper.floor(entity.locZ() / 16.0D); ++ // Tuinity start ++ int oldRegionX = entity.chunkX >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ int oldRegionZ = entity.chunkZ >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ int newRegionX = i >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ int newRegionZ = k >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ // Tuinity end + + if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) { + // Paper start - remove entity if its in a chunk more correctly. +@@ -928,6 +1378,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + // Paper end + ++ // Tuinity start ++ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { ++ this.removeNavigatorsFromData(entity); ++ } ++ // Tuinity end ++ + if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) { + this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY); + } +@@ -941,6 +1397,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } else { + this.getChunkAt(i, k).a(entity); + } ++ // Tuinity start ++ if (entity.inChunk && (oldRegionX != newRegionX || oldRegionZ != newRegionZ)) { ++ this.addNavigatorsIfPathingToRegion(entity); ++ } ++ // Tuinity end + } + + this.getMethodProfiler().exit(); +@@ -1299,7 +1760,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { Entity entity = (Entity) iterator.next(); if (!(entity instanceof EntityPlayer)) { @@ -9461,7 +14429,7 @@ index b19681031..274a383be 100644 throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); } -@@ -1327,6 +1903,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1327,6 +1788,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { public void unregisterEntity(Entity entity) { org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot @@ -9469,7 +14437,7 @@ index b19681031..274a383be 100644 // Paper start - fix entity registration issues if (entity instanceof EntityComplexPart) { // Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways -@@ -1393,12 +1970,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1393,17 +1855,108 @@ public class WorldServer extends World implements GeneratorAccessSeed { this.getScoreboard().a(entity); // CraftBukkit start - SPIGOT-5278 if (entity instanceof EntityDrowned) { @@ -9488,8 +14456,100 @@ index b19681031..274a383be 100644 + // Tuinity end } new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid ++ this.removeNavigatorsFromData(entity); // Tuinity - optimise notify() entity.valid = false; // CraftBukkit -@@ -1414,7 +1995,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + ++ // Tuinity start - optimise notify() ++ void removeNavigatorsIfNotPathingFromRegion(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ if (!((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) { ++ navigators.remove(((EntityDrowned)entity).navigationWater); ++ } ++ if (!((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) { ++ navigators.remove(((EntityDrowned)entity).navigationLand); ++ } ++ } else if (entity instanceof EntityInsentient) { ++ if (!((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ navigators.remove(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ } ++ ++ void removeNavigatorsFromData(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ navigators.remove(((EntityDrowned)entity).navigationWater); ++ navigators.remove(((EntityDrowned)entity).navigationLand); ++ } else if (entity instanceof EntityInsentient) { ++ navigators.remove(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ ++ void addNavigatorsIfPathingToRegion(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationWater); ++ } ++ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationLand); ++ } ++ } else if (entity instanceof EntityInsentient) { ++ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ } ++ ++ void updateNavigatorsInRegion(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationWater); ++ } else { ++ navigators.remove(((EntityDrowned)entity).navigationWater); ++ } ++ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationLand); ++ } else { ++ navigators.remove(((EntityDrowned)entity).navigationLand); ++ } ++ } else if (entity instanceof EntityInsentient) { ++ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityInsentient)entity).getNavigation()); ++ } else { ++ navigators.remove(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ } ++ // Tuinity end - optimise notify() ++ + private void registerEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot + // Paper start - don't double enqueue entity registration +@@ -1414,7 +1967,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { return; } // Paper end @@ -9498,7 +14558,7 @@ index b19681031..274a383be 100644 if (!entity.isQueuedForRegister) { // Paper this.entitiesToAdd.add(entity); entity.isQueuedForRegister = true; // Paper -@@ -1422,6 +2003,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1422,6 +1975,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } else { entity.isQueuedForRegister = false; // Paper this.entitiesById.put(entity.getId(), entity); @@ -9506,7 +14566,7 @@ index b19681031..274a383be 100644 if (entity instanceof EntityEnderDragon) { EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity).eJ(); int i = aentitycomplexpart.length; -@@ -1430,6 +2012,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1430,6 +1984,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { EntityComplexPart entitycomplexpart = aentitycomplexpart[j]; this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart); @@ -9514,7 +14574,7 @@ index b19681031..274a383be 100644 } } -@@ -1454,12 +2037,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1454,12 +2009,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { // this.getChunkProvider().addEntity(entity); // Paper - moved down below valid=true // CraftBukkit start - SPIGOT-5278 if (entity instanceof EntityDrowned) { @@ -9534,7 +14594,7 @@ index b19681031..274a383be 100644 } entity.valid = true; // CraftBukkit this.getChunkProvider().addEntity(entity); // Paper - from above to be below valid=true -@@ -1475,7 +2062,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1475,7 +2034,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } public void removeEntity(Entity entity) { @@ -9543,7 +14603,7 @@ index b19681031..274a383be 100644 throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); } else { this.removeEntityFromChunk(entity); -@@ -1571,13 +2158,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1571,13 +2130,32 @@ public class WorldServer extends World implements GeneratorAccessSeed { @Override public void notify(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1, int i) { @@ -9553,21 +14613,50 @@ index b19681031..274a383be 100644 VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition); if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) { ++ // Tuinity start - optimise notify() ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkProvider().playerChunkMap.dataRegionManager.getRegion(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ if (region == null) { ++ return; ++ } ++ // Tuinity end - optimise notify() boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper - Iterator iterator = this.navigators.iterator(); + // Tuinity start -+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.navigatorsForIteration.iterator(); ++ // Tuinity start - optimise notify() ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator> sectionIterator = null; ++ try { ++ for (sectionIterator = region.getSections(); sectionIterator.hasNext();) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = sectionIterator.next(); ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ if (navigators == null) { ++ continue; ++ } ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigators.iterator(); ++ // Tuinity end - optimise notify() + try { // Tuinity end while (iterator.hasNext()) { NavigationAbstract navigationabstract = (NavigationAbstract) iterator.next(); -@@ -1586,6 +2176,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1585,7 +2163,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (!navigationabstract.i()) { navigationabstract.b(blockposition); } - } +- } ++ // Tuinity start - optimise notify() ++ if (!navigationabstract.isViableForPathRecalculationChecking()) { ++ navigators.remove(navigationabstract); ++ } ++ // Tuinity end - optimise notify() ++ } + } finally { // Tuinity start + iterator.finishedIterating(); + } // Tuinity end ++ } // Tuinity start - optimise notify() ++ } finally { ++ if (sectionIterator != null) { ++ sectionIterator.finishedIterating(); ++ } ++ } // Tuinity end - optimise notify() this.tickingEntities = wasTicking; // Paper } diff --git a/patches/server/0009-AFK-API.patch b/patches/server/0009-AFK-API.patch index 194b89b1d..6167a3881 100644 --- a/patches/server/0009-AFK-API.patch +++ b/patches/server/0009-AFK-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] AFK API diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index caaa01b20e..335cb9c4a9 100644 +index caaa01b20..335cb9c4a 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java @@ -84,6 +84,15 @@ public abstract class EntityHuman extends EntityLiving { @@ -25,10 +25,10 @@ index caaa01b20e..335cb9c4a9 100644 super(EntityTypes.PLAYER, world); this.bL = ItemStack.b; diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 56677b8bf4..77156ecff2 100644 +index f8921eb83..c7cf49897 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -1722,8 +1722,54 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -1890,8 +1890,54 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void resetIdleTimer() { this.ca = SystemUtils.getMonotonicMillis(); @@ -84,7 +84,7 @@ index 56677b8bf4..77156ecff2 100644 return this.serverStatisticManager; } diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java -index 40ca3364d4..9643dffc59 100644 +index 40ca3364d..9643dffc5 100644 --- a/src/main/java/net/minecraft/server/IEntityAccess.java +++ b/src/main/java/net/minecraft/server/IEntityAccess.java @@ -149,28 +149,18 @@ public interface IEntityAccess { @@ -129,7 +129,7 @@ index 40ca3364d4..9643dffc59 100644 @Nullable diff --git a/src/main/java/net/minecraft/server/IEntitySelector.java b/src/main/java/net/minecraft/server/IEntitySelector.java -index 31eb6868c2..9f9d9b2de8 100644 +index 31eb6868c..9f9d9b2de 100644 --- a/src/main/java/net/minecraft/server/IEntitySelector.java +++ b/src/main/java/net/minecraft/server/IEntitySelector.java @@ -7,6 +7,7 @@ import javax.annotation.Nullable; @@ -154,7 +154,7 @@ index 31eb6868c2..9f9d9b2de8 100644 // Paper start public static final Predicate affectsSpawning = (entity) -> { diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index b89caa8ad1..4365d0bbb5 100644 +index b89caa8ad..4365d0bbb 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -247,6 +247,12 @@ public class PlayerConnection implements PacketListenerPlayIn { @@ -198,10 +198,10 @@ index b89caa8ad1..4365d0bbb5 100644 if (from.getX() != Double.MAX_VALUE) { Location oldTo = to.clone(); diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 274a383be1..95159d501a 100644 +index 0ac1bd39a..18e4fe468 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java -@@ -982,7 +982,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -834,7 +834,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { // CraftBukkit end if (this.everyoneSleeping && this.players.stream().noneMatch((entityplayer) -> { @@ -210,7 +210,7 @@ index 274a383be1..95159d501a 100644 })) { // CraftBukkit start long l = this.worldData.getDayTime() + 24000L; -@@ -1303,7 +1303,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1171,7 +1171,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { while (iterator.hasNext()) { EntityPlayer entityplayer = (EntityPlayer) iterator.next(); @@ -220,7 +220,7 @@ index 274a383be1..95159d501a 100644 } else if (entityplayer.isSleeping()) { ++j; diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index 803a7ff92a..d699a91685 100644 +index 803a7ff92..d699a9168 100644 --- a/src/main/java/net/pl3x/purpur/PurpurConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java @@ -1,6 +1,7 @@ @@ -248,7 +248,7 @@ index 803a7ff92a..d699a91685 100644 private static void timingsSettings() { timingsUrl = getString("settings.timings.url", timingsUrl); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 361f7857e4..2578a4677d 100644 +index 361f7857e..2578a4677 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -56,4 +56,15 @@ public class PurpurWorldConfig { @@ -268,7 +268,7 @@ index 361f7857e4..2578a4677d 100644 + } } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index f4f4e29f56..93c8f9fc8d 100644 +index f4f4e29f5..93c8f9fc8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -2216,4 +2216,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @@ -294,7 +294,7 @@ index f4f4e29f56..93c8f9fc8d 100644 + // Purpur end } diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 0b93635ba5..b47d6fa2de 100644 +index 0b93635ba..b47d6fa2d 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -207,6 +207,7 @@ public class ActivationRange diff --git a/patches/server/0021-Player-invulnerabilities.patch b/patches/server/0021-Player-invulnerabilities.patch index 71e13563a..c58d1cc0b 100644 --- a/patches/server/0021-Player-invulnerabilities.patch +++ b/patches/server/0021-Player-invulnerabilities.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Player invulnerabilities diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 77156ecff2..1360127549 100644 +index c7cf49897..fd7099adc 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -147,6 +147,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { @@ -17,7 +17,7 @@ index 77156ecff2..1360127549 100644 } // Paper start public BlockPosition getPointInFront(double inFront) { -@@ -809,6 +811,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -977,6 +979,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } @@ -30,7 +30,7 @@ index 77156ecff2..1360127549 100644 @Override public boolean damageEntity(DamageSource damagesource, float f) { if (this.isInvulnerable(damagesource)) { -@@ -816,7 +824,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -984,7 +992,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } else { boolean flag = this.server.j() && this.canPvP() && "fall".equals(damagesource.translationIndex); @@ -39,7 +39,7 @@ index 77156ecff2..1360127549 100644 return false; } else { if (damagesource instanceof EntityDamageSource) { -@@ -987,6 +995,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -1155,6 +1163,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { // CraftBukkit end } @@ -47,7 +47,7 @@ index 77156ecff2..1360127549 100644 return this; } } -@@ -2130,9 +2139,17 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -2298,9 +2307,17 @@ public class EntityPlayer extends EntityHuman implements ICrafting { @Override public boolean isFrozen() { // Paper - protected > public @@ -67,7 +67,7 @@ index 77156ecff2..1360127549 100644 public Scoreboard getScoreboard() { return getBukkitEntity().getScoreboard().getHandle(); diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index 4365d0bbb5..727975c26b 100644 +index 4365d0bbb..727975c26 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -1652,6 +1652,7 @@ public class PlayerConnection implements PacketListenerPlayIn { @@ -79,7 +79,7 @@ index 4365d0bbb5..727975c26b 100644 this.server.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), packStatus)); // Paper end diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index 38f26e25cb..c92a703da4 100644 +index 38f26e25c..c92a703da 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -905,6 +905,8 @@ public abstract class PlayerList { @@ -92,7 +92,7 @@ index 38f26e25cb..c92a703da4 100644 return entityplayer1; } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 2578a4677d..c441fcea9b 100644 +index 2578a4677..c441fcea9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -67,4 +67,11 @@ public class PurpurWorldConfig { @@ -108,7 +108,7 @@ index 2578a4677d..c441fcea9b 100644 + } } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 93c8f9fc8d..21dc18c653 100644 +index 93c8f9fc8..21dc18c65 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -2232,5 +2232,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { diff --git a/patches/server/0033-Zombie-horse-naturally-spawn.patch b/patches/server/0033-Zombie-horse-naturally-spawn.patch index 6dd8103e7..520ea6861 100644 --- a/patches/server/0033-Zombie-horse-naturally-spawn.patch +++ b/patches/server/0033-Zombie-horse-naturally-spawn.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Zombie horse naturally spawn diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 95159d501a..8366d36300 100644 +index 18e4fe468..c6f253d17 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java -@@ -1180,12 +1180,18 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1048,12 +1048,18 @@ public class WorldServer extends World implements GeneratorAccessSeed { boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper if (flag1) { @@ -34,7 +34,7 @@ index 95159d501a..8366d36300 100644 EntityLightning entitylightning = (EntityLightning) EntityTypes.LIGHTNING_BOLT.a((World) this); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 0073e3b85a..b652bfbdc3 100644 +index 0073e3b85..b652bfbdc 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -145,4 +145,9 @@ public class PurpurWorldConfig { diff --git a/patches/server/0043-Cat-spawning-options.patch b/patches/server/0043-Cat-spawning-options.patch index e77d6cfab..de93cfcea 100644 --- a/patches/server/0043-Cat-spawning-options.patch +++ b/patches/server/0043-Cat-spawning-options.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Cat spawning options diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java -index 9643dffc59..d9e6e78b15 100644 +index 9643dffc5..d9e6e78b1 100644 --- a/src/main/java/net/minecraft/server/IEntityAccess.java +++ b/src/main/java/net/minecraft/server/IEntityAccess.java @@ -44,6 +44,7 @@ public interface IEntityAccess { @@ -17,7 +17,7 @@ index 9643dffc59..d9e6e78b15 100644 return this.a(oclass, axisalignedbb, IEntitySelector.g); } diff --git a/src/main/java/net/minecraft/server/MobSpawnerCat.java b/src/main/java/net/minecraft/server/MobSpawnerCat.java -index 5e17868a76..6d0ebd8afe 100644 +index 5e17868a7..6d0ebd8af 100644 --- a/src/main/java/net/minecraft/server/MobSpawnerCat.java +++ b/src/main/java/net/minecraft/server/MobSpawnerCat.java @@ -16,7 +16,7 @@ public class MobSpawnerCat implements MobSpawner { @@ -62,10 +62,10 @@ index 5e17868a76..6d0ebd8afe 100644 } diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java -index 3c9668c9c3..f460e76ea7 100644 +index 99778f80c..be2ff3547 100644 --- a/src/main/java/net/minecraft/server/VillagePlace.java +++ b/src/main/java/net/minecraft/server/VillagePlace.java -@@ -45,6 +45,7 @@ public class VillagePlace extends RegionFileSection { +@@ -177,6 +177,7 @@ public class VillagePlace extends RegionFileSection { ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition); } @@ -74,7 +74,7 @@ index 3c9668c9c3..f460e76ea7 100644 return this.c(predicate, blockposition, i, villageplace_occupancy).count(); } diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java -index a5718af9b6..b6b4c8c491 100644 +index a5718af9b..b6b4c8c49 100644 --- a/src/main/java/net/minecraft/server/VillagePlaceType.java +++ b/src/main/java/net/minecraft/server/VillagePlaceType.java @@ -44,7 +44,7 @@ public class VillagePlaceType { @@ -95,10 +95,10 @@ index a5718af9b6..b6b4c8c491 100644 return this.E; } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 8366d36300..0b3a605a2f 100644 +index c6f253d17..9b04b97b7 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java -@@ -2561,6 +2561,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -2560,6 +2560,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } } @@ -107,7 +107,7 @@ index 8366d36300..0b3a605a2f 100644 return this.getChunkProvider().j(); } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f01c74a2ea..b32f4d74a0 100644 +index f01c74a2e..b32f4d74a 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -118,6 +118,15 @@ public class PurpurWorldConfig { diff --git a/patches/server/0045-Cows-eat-mushrooms.patch b/patches/server/0045-Cows-eat-mushrooms.patch index 5049f72ab..e9a717fff 100644 --- a/patches/server/0045-Cows-eat-mushrooms.patch +++ b/patches/server/0045-Cows-eat-mushrooms.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Cows eat mushrooms diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 0822179590..9c781fed14 100644 +index 870868f0e..1987555a9 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -2777,6 +2777,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2766,6 +2766,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.invulnerable = flag; } @@ -17,7 +17,7 @@ index 0822179590..9c781fed14 100644 this.setPositionRotation(entity.locX(), entity.locY(), entity.locZ(), entity.yaw, entity.pitch); } diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java -index 42e6761c8b..cfb009c811 100644 +index 42e6761c8..cfb009c81 100644 --- a/src/main/java/net/minecraft/server/EntityCow.java +++ b/src/main/java/net/minecraft/server/EntityCow.java @@ -16,6 +16,7 @@ public class EntityCow extends EntityAnimal { @@ -110,7 +110,7 @@ index 42e6761c8b..cfb009c811 100644 public EntityCow createChild(WorldServer worldserver, EntityAgeable entityageable) { return (EntityCow) EntityTypes.COW.a((World) worldserver); diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java -index 76ff4dd90e..5c79c50acd 100644 +index 76ff4dd90..5c79c50ac 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -81,7 +81,7 @@ public abstract class EntityLiving extends Entity { @@ -123,7 +123,7 @@ index 76ff4dd90e..5c79c50acd 100644 public float aC; public float aD; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b32f4d74a0..d8bd7ffd5f 100644 +index b32f4d74a..d8bd7ffd5 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -132,6 +132,11 @@ public class PurpurWorldConfig { diff --git a/patches/server/0054-Controllable-Minecarts.patch b/patches/server/0054-Controllable-Minecarts.patch index 061e5b370..d5166d093 100644 --- a/patches/server/0054-Controllable-Minecarts.patch +++ b/patches/server/0054-Controllable-Minecarts.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Controllable Minecarts diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java -index 2291135eae..bc61aaff65 100644 +index 2291135ea..bc61aaff6 100644 --- a/src/main/java/net/minecraft/server/BlockPosition.java +++ b/src/main/java/net/minecraft/server/BlockPosition.java @@ -36,6 +36,12 @@ public class BlockPosition extends BaseBlockPosition { @@ -22,7 +22,7 @@ index 2291135eae..bc61aaff65 100644 super(i, j, k); } diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java -index 5c79c50acd..4f75fe92bb 100644 +index 5c79c50ac..4f75fe92b 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -99,9 +99,9 @@ public abstract class EntityLiving extends Entity { @@ -39,7 +39,7 @@ index 5c79c50acd..4f75fe92bb 100644 protected double aV; protected double aW; diff --git a/src/main/java/net/minecraft/server/EntityMinecartAbstract.java b/src/main/java/net/minecraft/server/EntityMinecartAbstract.java -index 9af2bb3928..28808712e2 100644 +index 9af2bb392..28808712e 100644 --- a/src/main/java/net/minecraft/server/EntityMinecartAbstract.java +++ b/src/main/java/net/minecraft/server/EntityMinecartAbstract.java @@ -445,16 +445,62 @@ public abstract class EntityMinecartAbstract extends Entity { @@ -106,10 +106,10 @@ index 9af2bb3928..28808712e2 100644 this.move(EnumMoveType.SELF, this.getMot()); if (!this.onGround) { diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index b514dddbe6..8077a3af19 100644 +index 652ee3d3d..ed667f727 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -822,6 +822,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -990,6 +990,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { if (this.isInvulnerable(damagesource)) { return false; } else { @@ -118,7 +118,7 @@ index b514dddbe6..8077a3af19 100644 if (!flag && isSpawnInvulnerable() && damagesource != DamageSource.OUT_OF_WORLD) { // Purpur diff --git a/src/main/java/net/minecraft/server/ItemMinecart.java b/src/main/java/net/minecraft/server/ItemMinecart.java -index ceef7aaf92..002651aaf3 100644 +index ceef7aaf9..002651aaf 100644 --- a/src/main/java/net/minecraft/server/ItemMinecart.java +++ b/src/main/java/net/minecraft/server/ItemMinecart.java @@ -103,8 +103,10 @@ public class ItemMinecart extends Item { @@ -143,7 +143,7 @@ index ceef7aaf92..002651aaf3 100644 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 3721bd745e..4c3e13cf66 100644 +index 3721bd745..4c3e13cf6 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -1,5 +1,7 @@ diff --git a/patches/server/0056-Players-should-not-cram-to-death.patch b/patches/server/0056-Players-should-not-cram-to-death.patch index 1bca517b6..ca1e124ab 100644 --- a/patches/server/0056-Players-should-not-cram-to-death.patch +++ b/patches/server/0056-Players-should-not-cram-to-death.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Players should not cram to death diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 8077a3af19..add0a4d600 100644 +index ed667f727..287a496ee 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -1224,7 +1224,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -1392,7 +1392,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { @Override public boolean isInvulnerable(DamageSource damagesource) { diff --git a/patches/server/0060-Fix-the-dead-lagging-the-server.patch b/patches/server/0060-Fix-the-dead-lagging-the-server.patch index aacbd8ba6..920d9716d 100644 --- a/patches/server/0060-Fix-the-dead-lagging-the-server.patch +++ b/patches/server/0060-Fix-the-dead-lagging-the-server.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix the dead lagging the server diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 41cba0914..0bc12fa32 100644 +index 1987555a9..01f4be387 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -1529,6 +1529,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1518,6 +1518,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.pitch = MathHelper.a(f1, -90.0F, 90.0F) % 360.0F; this.lastYaw = this.yaw; this.lastPitch = this.pitch; diff --git a/patches/server/0082-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0082-Climbing-should-not-bypass-cramming-gamerule.patch index 4ff6515d9..a0db29944 100644 --- a/patches/server/0082-Climbing-should-not-bypass-cramming-gamerule.patch +++ b/patches/server/0082-Climbing-should-not-bypass-cramming-gamerule.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Climbing should not bypass cramming gamerule diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 4763dca41..6a9318318 100644 +index e0532f145..763625294 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -1728,6 +1728,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1717,6 +1717,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } public boolean isCollidable() { @@ -131,10 +131,10 @@ index 1c682a62b..f7b92078e 100644 @Override diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 7902cc7f1..a761bfea4 100644 +index 287a496ee..9159601da 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -1776,8 +1776,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -1944,8 +1944,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } @Override diff --git a/patches/server/0085-Item-entity-immunities.patch b/patches/server/0085-Item-entity-immunities.patch index 7e2a82c7f..6ada2cebd 100644 --- a/patches/server/0085-Item-entity-immunities.patch +++ b/patches/server/0085-Item-entity-immunities.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Item entity immunities diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 6a9318318..e14aa4fd9 100644 +index 763625294..f03890d40 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -1478,6 +1478,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1467,6 +1467,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } diff --git a/patches/server/0088-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/server/0088-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch index 7932a8cbd..c22542a37 100644 --- a/patches/server/0088-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch +++ b/patches/server/0088-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch @@ -17,10 +17,10 @@ index 6fe5678cf..bd0267ee4 100644 return (new EntityDamageSourceIndirect("indirectMagic", entity, entity1)).setIgnoreArmor().setMagic(); } diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index e14aa4fd9..fb850c99c 100644 +index f03890d40..1ae6dd961 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -2143,8 +2143,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2132,8 +2132,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return this.a(new ItemStack(imaterial), (float) i); } diff --git a/patches/server/0104-Fix-death-message-colors.patch b/patches/server/0104-Fix-death-message-colors.patch index ef04d8846..2f8449f21 100644 --- a/patches/server/0104-Fix-death-message-colors.patch +++ b/patches/server/0104-Fix-death-message-colors.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix death message colors diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index a761bfea4..6a2f9d831 100644 +index 9159601da..cf7739164 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -670,7 +670,24 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -838,7 +838,24 @@ public class EntityPlayer extends EntityHuman implements ICrafting { IChatBaseComponent defaultMessage = this.getCombatTracker().getDeathMessage(); diff --git a/patches/server/0106-Populator-seed-controls.patch b/patches/server/0106-Populator-seed-controls.patch index d54cc6517..e50f84ccd 100644 --- a/patches/server/0106-Populator-seed-controls.patch +++ b/patches/server/0106-Populator-seed-controls.patch @@ -18,7 +18,7 @@ index dae2e5d70..55b67f105 100644 } final Object val = config.get(key); diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java -index 048139d13..51a2ec468 100644 +index 42ce3b802..03e4d9d8b 100644 --- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java @@ -1,5 +1,6 @@ @@ -28,7 +28,7 @@ index 048139d13..51a2ec468 100644 import com.destroystokyo.paper.util.SneakyThrow; import net.minecraft.server.MinecraftServer; import net.minecraft.server.TicketType; -@@ -370,6 +371,19 @@ public final class TuinityConfig { +@@ -376,6 +377,19 @@ public final class TuinityConfig { this.spawnLimitAmbient = this.getInt(path + ".ambient", -1); } diff --git a/patches/server/0112-Add-no-tick-block-list.patch b/patches/server/0112-Add-no-tick-block-list.patch index c2b175ee0..aec2479a7 100644 --- a/patches/server/0112-Add-no-tick-block-list.patch +++ b/patches/server/0112-Add-no-tick-block-list.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add no-tick block list diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java -index 2760b377d..334b5e806 100644 +index 4d1ac4e6b..483756316 100644 --- a/src/main/java/net/minecraft/server/BlockBase.java +++ b/src/main/java/net/minecraft/server/BlockBase.java -@@ -576,10 +576,12 @@ public abstract class BlockBase { +@@ -655,10 +655,12 @@ public abstract class BlockBase { } public void a(WorldServer worldserver, BlockPosition blockposition, Random random) { @@ -22,7 +22,7 @@ index 2760b377d..334b5e806 100644 } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 0b3a605a2..51a25349c 100644 +index 9b04b97b7..f3bc74d35 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -313,14 +313,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { diff --git a/patches/server/0116-Stop-squids-floating-on-top-of-water.patch b/patches/server/0116-Stop-squids-floating-on-top-of-water.patch index 4d56d1ad5..af7522105 100644 --- a/patches/server/0116-Stop-squids-floating-on-top-of-water.patch +++ b/patches/server/0116-Stop-squids-floating-on-top-of-water.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Stop squids floating on top of water diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index fb850c99c..ed7a66c03 100644 +index 1ae6dd961..b433bf6e6 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -3446,8 +3446,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -3435,8 +3435,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.lastYaw = this.yaw; } diff --git a/patches/server/0117-Ridables.patch b/patches/server/0117-Ridables.patch index 9795ba3cc..0da899dd7 100644 --- a/patches/server/0117-Ridables.patch +++ b/patches/server/0117-Ridables.patch @@ -161,7 +161,7 @@ index bd0267ee4..8b36ac2b0 100644 this.B = true; return this; diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index ed7a66c03..4f16bfc7d 100644 +index b433bf6e6..ec755b0f2 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -80,7 +80,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke @@ -200,7 +200,7 @@ index ed7a66c03..4f16bfc7d 100644 private float headHeight; // CraftBukkit start public boolean persist = true; -@@ -1488,6 +1488,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1477,6 +1477,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return !this.justCreated && this.M.getDouble(TagsFluid.LAVA) > 0.0D; } @@ -208,7 +208,7 @@ index ed7a66c03..4f16bfc7d 100644 public void a(float f, Vec3D vec3d) { Vec3D vec3d1 = a(vec3d, f, this.yaw); -@@ -2243,6 +2244,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2232,6 +2233,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return this.a(entity, false); } @@ -216,7 +216,7 @@ index ed7a66c03..4f16bfc7d 100644 public boolean a(Entity entity, boolean flag) { for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) { if (entity1.vehicle == this) { -@@ -2338,6 +2340,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2327,6 +2329,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.passengers.add(entity); } @@ -230,7 +230,7 @@ index ed7a66c03..4f16bfc7d 100644 } return true; // CraftBukkit } -@@ -2378,6 +2387,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2367,6 +2376,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return false; } // Spigot end @@ -243,7 +243,7 @@ index ed7a66c03..4f16bfc7d 100644 this.passengers.remove(entity); entity.j = 60; } -@@ -2543,6 +2558,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2532,6 +2547,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.setFlag(4, flag); } @@ -251,7 +251,7 @@ index ed7a66c03..4f16bfc7d 100644 public boolean bE() { return this.glowing || this.world.isClientSide && this.getFlag(6); } -@@ -2765,6 +2781,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2754,6 +2770,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke public void setHeadRotation(float f) {} @@ -259,7 +259,7 @@ index ed7a66c03..4f16bfc7d 100644 public void n(float f) {} public boolean bL() { -@@ -3199,6 +3216,18 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -3188,6 +3205,18 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return false; } @@ -278,7 +278,7 @@ index ed7a66c03..4f16bfc7d 100644 @Override public void sendMessage(IChatBaseComponent ichatbasecomponent, UUID uuid) {} -@@ -3651,4 +3680,47 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -3640,4 +3669,47 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return ((ChunkProviderServer) world.getChunkProvider()).isInEntityTickingChunk(this); } // Paper end @@ -3189,7 +3189,7 @@ index a3a428da9..cf7de0127 100644 this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 6a2f9d831..51736dfde 100644 +index cf7739164..47f881d74 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -505,6 +505,15 @@ public class EntityPlayer extends EntityHuman implements ICrafting { diff --git a/patches/server/0119-Crying-obsidian-valid-for-portal-frames.patch b/patches/server/0119-Crying-obsidian-valid-for-portal-frames.patch index 9070a8c39..59d013c2e 100644 --- a/patches/server/0119-Crying-obsidian-valid-for-portal-frames.patch +++ b/patches/server/0119-Crying-obsidian-valid-for-portal-frames.patch @@ -17,10 +17,10 @@ index 372ee6adc..4aa2b38f1 100644 return this == block; } diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java -index 334b5e806..0e16fba76 100644 +index 483756316..5550693a4 100644 --- a/src/main/java/net/minecraft/server/BlockBase.java +++ b/src/main/java/net/minecraft/server/BlockBase.java -@@ -646,6 +646,7 @@ public abstract class BlockBase { +@@ -725,6 +725,7 @@ public abstract class BlockBase { return this.getBlock().a(tag) && predicate.test(this); } diff --git a/patches/server/0120-Entities-can-use-portals-configuration.patch b/patches/server/0120-Entities-can-use-portals-configuration.patch index 99bcf394e..e088a817f 100644 --- a/patches/server/0120-Entities-can-use-portals-configuration.patch +++ b/patches/server/0120-Entities-can-use-portals-configuration.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entities can use portals configuration diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 4f16bfc7d..f65bb0276 100644 +index ec755b0f2..14d6d2ef0 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -2419,7 +2419,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2408,7 +2408,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke public void d(BlockPosition blockposition) { if (this.ai()) { this.resetPortalCooldown(); @@ -17,7 +17,7 @@ index 4f16bfc7d..f65bb0276 100644 if (!this.world.isClientSide && !blockposition.equals(this.ac)) { this.ac = blockposition.immutableCopy(); } -@@ -2992,7 +2992,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -2981,7 +2981,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } public boolean canPortal() { diff --git a/patches/server/0134-Configurable-daylight-cycle.patch b/patches/server/0134-Configurable-daylight-cycle.patch index 8b398cb1e..0ad2acaf1 100644 --- a/patches/server/0134-Configurable-daylight-cycle.patch +++ b/patches/server/0134-Configurable-daylight-cycle.patch @@ -18,7 +18,7 @@ index 15af5927f..c9c2e9774 100644 public PacketPlayOutUpdateTime() {} diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 32d085474..63e326795 100644 +index ab4a5534e..90416b184 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -94,6 +94,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { @@ -37,7 +37,7 @@ index 32d085474..63e326795 100644 } // Tuinity start - optimise collision -@@ -1147,7 +1149,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1015,7 +1017,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { this.nextTickListBlock.nextTick(); // Paper this.nextTickListFluid.nextTick(); // Paper this.worldDataServer.u().a(this.server, i); @@ -60,7 +60,7 @@ index 32d085474..63e326795 100644 this.setDayTime(this.worldData.getDayTime() + 1L); } -@@ -1156,6 +1172,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1024,6 +1040,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { public void setDayTime(long i) { this.worldDataServer.setDayTime(i); diff --git a/patches/server/0138-Add-tablist-suffix-option-for-afk.patch b/patches/server/0138-Add-tablist-suffix-option-for-afk.patch index 84215024a..b5e8889dd 100644 --- a/patches/server/0138-Add-tablist-suffix-option-for-afk.patch +++ b/patches/server/0138-Add-tablist-suffix-option-for-afk.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add tablist suffix option for afk diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 51736dfde..2ee0fa074 100644 +index 47f881d74..81237e538 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -1790,7 +1790,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -1958,7 +1958,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } if (world.purpurConfig.idleTimeoutUpdateTabList) {