mirror of
https://github.com/PaperMC/Paper.git
synced 2025-02-19 11:39:50 +01:00
More more more more more more more patches
This commit is contained in:
parent
a26fb3e49a
commit
428127a055
41 changed files with 19 additions and 1016 deletions
patches
server
Add-API-for-resetting-a-single-score.patchAdd-BlockBreakBlockEvent.patchAdd-ItemFactory-getMonsterEgg-API.patchAdd-Raw-Byte-Entity-Serialization.patchAdd-advancement-display-API.patchAdd-back-EntityPortalExitEvent.patchAdd-critical-damage-API.patchAdd-isCollidable-methods-to-various-places.patchAdd-methods-to-find-targets-for-lightning-strikes.patchAdd-missing-team-sidebar-display-slots.patchAdd-more-async-catchers.patchAdd-paper-mobcaps-and-paper-playermobcaps.patchAdded-EntityDamageItemEvent.patchAllow-controlled-flushing-for-network-manager.patchChange-EnderEye-target-without-changing-other-things.patchClear-bucket-NBT-after-dispense.patchConfigurable-item-frame-map-cursor-update-interval.patchCorrectly-handle-recursion-for-chunkholder-updates.patchDo-not-allow-the-server-to-unload-chunks-at-request-.patchDo-not-allow-ticket-level-changes-when-updating-chun.patchDo-not-allow-ticket-level-changes-while-unloading-pl.patchDo-not-run-close-logic-for-inventories-on-chunk-unlo.patchDo-not-submit-profile-lookups-to-worldgen-threads.patchFix-GameProfileCache-concurrency.patchFix-block-drops-position-losing-precision-millions-o.patchFix-chunks-refusing-to-unload-at-low-TPS.patchFix-issues-with-mob-conversion.patchGet-entity-default-attributes.patchGoat-ram-API.patchLeft-handed-API.patchLog-when-the-async-catcher-is-tripped.patchMake-CallbackExecutor-strict-again.patchMake-EntityUnleashEvent-cancellable.patchMore-CommandBlock-API.patchOptimize-indirect-passenger-iteration.patchOption-to-prevent-NBT-copy-in-smithing-recipes.patchPrevent-unload-calls-removing-tickets-for-sync-loads.patchRewrite-entity-bounding-box-lookup-calls.patchSanitize-ResourceLocation-error-logging.patchVanilla-command-permission-fixes.patch
unapplied/server
|
@ -43,8 +43,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
- dropResources(iblockdata1, world, blockposition3, tileentity);
|
||||
+ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper
|
||||
world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18);
|
||||
world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1));
|
||||
if (!iblockdata1.is(BlockTags.FIRE)) {
|
||||
world.addDestroyBlockEffect(blockposition3, iblockdata1);
|
||||
diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
|
|
@ -292,8 +292,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
// Paper start - add parameters and int ret type
|
||||
public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
|
||||
// Paper start - add parameters and int ret type
|
||||
spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null);
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
@ -12,8 +12,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
return this.getItem().getMaxDamage();
|
||||
}
|
||||
|
||||
- public boolean hurt(int amount, Random random, @Nullable ServerPlayer player) {
|
||||
+ public boolean hurt(int amount, Random random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers
|
||||
- public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) {
|
||||
+ public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers
|
||||
if (!this.isDamageableItem()) {
|
||||
return false;
|
||||
} else {
|
|
@ -25,11 +25,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -0,0 +0,0 @@ public class ServerEntity {
|
||||
ItemFrame entityitemframe = (ItemFrame) this.entity;
|
||||
ItemStack itemstack = entityitemframe.getItem();
|
||||
if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
|
||||
ItemStack itemstack = entityitemframe.getItem();
|
||||
|
||||
- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
|
||||
+ if (this.level.paperConfig.mapItemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig.mapItemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
|
||||
Integer integer = MapItem.getMapId(itemstack);
|
||||
MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level);
|
||||
- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
|
||||
+ if (this.level.paperConfig.mapItemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig.mapItemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
|
||||
Integer integer = MapItem.getMapId(itemstack);
|
||||
MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level);
|
||||
|
|
@ -16,7 +16,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
// Paper end
|
||||
|
||||
+ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks
|
||||
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
|
||||
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
|
||||
super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
|
||||
this.visibleChunkMap = this.updatingChunkMap.clone();
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
@ -9,7 +9,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
|
||||
@@ -0,0 +0,0 @@ public class Goat extends Animal {
|
||||
public static boolean checkGoatSpawnRules(EntityType<? extends Animal> entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) {
|
||||
public static boolean checkGoatSpawnRules(EntityType<? extends Animal> entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
|
||||
return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos);
|
||||
}
|
||||
+
|
|
@ -58,7 +58,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
super(world, tileEntity);
|
||||
@@ -0,0 +0,0 @@ public class CraftCommandBlock extends CraftBlockEntityState<CommandBlockEntity>
|
||||
public void name(net.kyori.adventure.text.Component name) {
|
||||
getSnapshot().getCommandBlock().setName(name == null ? new net.minecraft.network.chat.TextComponent("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name));
|
||||
getSnapshot().getCommandBlock().setName(name == null ? net.minecraft.network.chat.Component.literal("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name));
|
||||
}
|
||||
+
|
||||
+ @Override
|
|
@ -923,7 +923,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
|
||||
- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage);
|
||||
+ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper
|
||||
StructureManager definedstructuremanager = minecraftserver.getStructureManager();
|
||||
StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
|
||||
int j = this.spigotConfig.viewDistance; // Spigot
|
||||
int k = this.spigotConfig.simulationDistance; // Spigot
|
||||
diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
@ -34,9 +34,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
--- a/src/main/java/net/minecraft/commands/Commands.java
|
||||
+++ b/src/main/java/net/minecraft/commands/Commands.java
|
||||
@@ -0,0 +0,0 @@ public class Commands {
|
||||
if (environment.includeIntegrated) {
|
||||
PublishCommand.register(this.dispatcher);
|
||||
}
|
||||
|
||||
-
|
||||
+ // Paper start
|
||||
+ for (final CommandNode<CommandSourceStack> node : this.dispatcher.getRoot().getChildren()) {
|
||||
+ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.<CommandSourceStack>defaultRequirement()) {
|
||||
|
@ -44,9 +45,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.dispatcher.findAmbiguities((commandnode, commandnode1, commandnode2, collection) -> {
|
||||
// CommandDispatcher.LOGGER.warn("Ambiguity between arguments {} and {} with inputs: {}", new Object[]{this.dispatcher.getPath(commandnode1), this.dispatcher.getPath(commandnode2), collection}); // CraftBukkit
|
||||
});
|
||||
// CraftBukkit start
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
|
|
@ -1,998 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 31 Jan 2021 02:29:24 -0800
|
||||
Subject: [PATCH] Optimise general POI access
|
||||
|
||||
There are a couple of problems with mojang's POI code.
|
||||
Firstly, it's all streams. Unsurprisingly, stacking
|
||||
streams on top of each other is horrible for performance
|
||||
and ultimately took up half of a villager's tick!
|
||||
|
||||
Secondly, sometime's the search radius is large and there are
|
||||
a significant number of poi entries per chunk section. Even
|
||||
removing streams at this point doesn't help much. The only solution
|
||||
is to start at the search point and iterate outwards. This
|
||||
type of approach shows massive gains for portals, simply because
|
||||
we can avoid sync loading a large area of chunks. I also tested
|
||||
a massive farm I found in JellySquid's discord, which showed
|
||||
to benefit significantly simply because the farm had so many
|
||||
portal blocks that searching through them all was very slow.
|
||||
|
||||
Great care has been taken so that behavior remains identical to
|
||||
vanilla, however I cannot account for oddball Stream API
|
||||
implementations, if they even exist (streams can technically
|
||||
be loose with iteration order in a sorted stream given its
|
||||
source stream is not tagged with ordered, and mojang does not
|
||||
tag the source stream as ordered). However in my testing on openjdk
|
||||
there showed no difference, as expected.
|
||||
|
||||
This patch also specifically optimises other areas of code to
|
||||
use PoiAccess. For example, some villager AI and portaling code
|
||||
had to be specifically modified.
|
||||
|
||||
diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package io.papermc.paper.util;
|
||||
+
|
||||
+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
||||
+import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap;
|
||||
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
||||
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
+import net.minecraft.core.BlockPos;
|
||||
+import net.minecraft.util.Mth;
|
||||
+import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
+import net.minecraft.world.entity.ai.village.poi.PoiRecord;
|
||||
+import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
||||
+import net.minecraft.world.entity.ai.village.poi.PoiType;
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.HashSet;
|
||||
+import java.util.Iterator;
|
||||
+import java.util.List;
|
||||
+import java.util.Map;
|
||||
+import java.util.Optional;
|
||||
+import java.util.Set;
|
||||
+import java.util.function.Predicate;
|
||||
+
|
||||
+/**
|
||||
+ * Provides optimised access to POI data. All returned values will be identical to vanilla.
|
||||
+ */
|
||||
+public final class PoiAccess {
|
||||
+
|
||||
+ protected static double clamp(final double val, final double min, final double max) {
|
||||
+ return (val < min ? min : (val > max ? max : val));
|
||||
+ }
|
||||
+
|
||||
+ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ,
|
||||
+ final double boxMaxX, final double boxMaxY, final double boxMaxZ,
|
||||
+
|
||||
+ final double circleX, final double circleY, final double circleZ) {
|
||||
+ // is the circle center inside the box?
|
||||
+ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) {
|
||||
+ return 0.0;
|
||||
+ }
|
||||
+
|
||||
+ final double boxWidthX = (boxMaxX - boxMinX) / 2.0;
|
||||
+ final double boxWidthY = (boxMaxY - boxMinY) / 2.0;
|
||||
+ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0;
|
||||
+
|
||||
+ final double boxCenterX = (boxMinX + boxMaxX) / 2.0;
|
||||
+ final double boxCenterY = (boxMinY + boxMaxY) / 2.0;
|
||||
+ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0;
|
||||
+
|
||||
+ double centerDiffX = circleX - boxCenterX;
|
||||
+ double centerDiffY = circleY - boxCenterY;
|
||||
+ double centerDiffZ = circleZ - boxCenterZ;
|
||||
+
|
||||
+ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX);
|
||||
+ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY);
|
||||
+ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ);
|
||||
+
|
||||
+ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ // key is:
|
||||
+ // upper 32 bits:
|
||||
+ // upper 16 bits: max y section
|
||||
+ // lower 16 bits: min y section
|
||||
+ // lower 32 bits:
|
||||
+ // upper 16 bits: section
|
||||
+ // lower 16 bits: radius
|
||||
+ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) {
|
||||
+ return (
|
||||
+ (maxSection & 0xFFFFL) << (64 - 16)
|
||||
+ | (minSection & 0xFFFFL) << (64 - 32)
|
||||
+ | (section & 0xFFFFL) << (64 - 48)
|
||||
+ | (radius & 0xFFFFL) << (64 - 64)
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ // only includes x/z axis
|
||||
+ // finds the closest poi data by distance.
|
||||
+ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load) {
|
||||
+ final PoiRecord ret = findClosestPoiDataRecord(
|
||||
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load
|
||||
+ );
|
||||
+
|
||||
+ return ret == null ? null : ret.getPos();
|
||||
+ }
|
||||
+
|
||||
+ // only includes x/z axis
|
||||
+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned.
|
||||
+ public static void findClosestPoiDataPositions(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load,
|
||||
+ final Set<BlockPos> ret) {
|
||||
+ final Set<BlockPos> positions = new HashSet<>();
|
||||
+ // pos predicate is last thing that runs before adding to ret.
|
||||
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
|
||||
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return positions.add(pos.immutable());
|
||||
+ };
|
||||
+
|
||||
+ final List<PoiRecord> toConvert = new ArrayList<>();
|
||||
+ findClosestPoiDataRecords(
|
||||
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, toConvert
|
||||
+ );
|
||||
+
|
||||
+ for (final PoiRecord record : toConvert) {
|
||||
+ ret.add(record.getPos());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // only includes x/z axis
|
||||
+ // finds the closest poi data by distance.
|
||||
+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load) {
|
||||
+ final List<PoiRecord> ret = new ArrayList<>();
|
||||
+ findClosestPoiDataRecords(
|
||||
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ret
|
||||
+ );
|
||||
+ return ret.isEmpty() ? null : ret.get(0);
|
||||
+ }
|
||||
+
|
||||
+ // only includes x/z axis
|
||||
+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned.
|
||||
+ public static void findClosestPoiDataRecords(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load,
|
||||
+ final List<PoiRecord> ret) {
|
||||
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
||||
+
|
||||
+ final List<PoiRecord> closestRecords = new ArrayList<>();
|
||||
+ double closestDistanceSquared = maxDistance * maxDistance;
|
||||
+
|
||||
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
|
||||
+ final int lowerY = WorldUtil.getMinSection(poiStorage.world);
|
||||
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
|
||||
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
|
||||
+ final int upperY = WorldUtil.getMaxSection(poiStorage.world);
|
||||
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
|
||||
+
|
||||
+ final int centerX = sourcePosition.getX() >> 4;
|
||||
+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY);
|
||||
+ final int centerZ = sourcePosition.getZ() >> 4;
|
||||
+
|
||||
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
||||
+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ));
|
||||
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
||||
+
|
||||
+ while (!queue.isEmpty()) {
|
||||
+ final long key = queue.dequeueLong();
|
||||
+ final int sectionX = CoordinateUtils.getChunkSectionX(key);
|
||||
+ final int sectionY = CoordinateUtils.getChunkSectionY(key);
|
||||
+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key);
|
||||
+
|
||||
+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) {
|
||||
+ // out of bound chunk
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final double sectionDistanceSquared = getSmallestDistanceSquared(
|
||||
+ (sectionX << 4) + 0.5,
|
||||
+ (sectionY << 4) + 0.5,
|
||||
+ (sectionZ << 4) + 0.5,
|
||||
+ (sectionX << 4) + 15.5,
|
||||
+ (sectionY << 4) + 15.5,
|
||||
+ (sectionZ << 4) + 15.5,
|
||||
+ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ()
|
||||
+ );
|
||||
+ if (sectionDistanceSquared > closestDistanceSquared) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // queue all neighbours
|
||||
+ for (int dz = -1; dz <= 1; ++dz) {
|
||||
+ for (int dx = -1; dx <= 1; ++dx) {
|
||||
+ for (int dy = -1; dy <= 1; ++dy) {
|
||||
+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many
|
||||
+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set
|
||||
+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final int neighbourX = sectionX + dx;
|
||||
+ final int neighbourY = sectionY + dy;
|
||||
+ final int neighbourZ = sectionZ + dz;
|
||||
+
|
||||
+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ);
|
||||
+ if (seen.add(neighbourKey)) {
|
||||
+ queue.enqueue(neighbourKey);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key);
|
||||
+
|
||||
+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final PoiSection poiSection = poiSectionOptional.orElse(null);
|
||||
+
|
||||
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
|
||||
+ if (sectionData.isEmpty()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // now we search the section data
|
||||
+ for (final Map.Entry<PoiType, Set<PoiRecord>> entry : sectionData.entrySet()) {
|
||||
+ if (!villagePlaceType.test(entry.getKey())) {
|
||||
+ // filter out by poi type
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // now we can look at the poi data
|
||||
+ for (final PoiRecord poiData : entry.getValue()) {
|
||||
+ if (!occupancyFilter.test(poiData)) {
|
||||
+ // filter by occupancy
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final BlockPos poiPosition = poiData.getPos();
|
||||
+
|
||||
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
|
||||
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
|
||||
+ // out of range for square radius
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped!
|
||||
+ final double dataRange = poiPosition.distSqr(sourcePosition);
|
||||
+
|
||||
+ if (dataRange > closestDistanceSquared) {
|
||||
+ // out of range for distance check
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
|
||||
+ // filter by position
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (dataRange < closestDistanceSquared) {
|
||||
+ closestRecords.clear();
|
||||
+ closestDistanceSquared = dataRange;
|
||||
+ }
|
||||
+ closestRecords.add(poiData);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // uh oh! we might have multiple records that match the distance sorting!
|
||||
+ // we need to re-order our results by the way vanilla would have iterated over them.
|
||||
+ closestRecords.sort((record1, record2) -> {
|
||||
+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section
|
||||
+ // is fine and should be preserved (this sort is stable so we're good there)
|
||||
+ // but they iterate sections by x then by z (like the following)
|
||||
+ // for (int x = -dx; x <= dx; ++x)
|
||||
+ // for (int z = -dz; z <= dz; ++z)
|
||||
+ // ....
|
||||
+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first
|
||||
+ final BlockPos pos1 = record1.getPos();
|
||||
+ final BlockPos pos2 = record2.getPos();
|
||||
+
|
||||
+ final int cx1 = pos1.getX() >> 4;
|
||||
+ final int cz1 = pos1.getZ() >> 4;
|
||||
+
|
||||
+ final int cx2 = pos2.getX() >> 4;
|
||||
+ final int cz2 = pos2.getZ() >> 4;
|
||||
+
|
||||
+ if (cz2 != cz1) {
|
||||
+ // want smaller z
|
||||
+ return Integer.compare(cz1, cz2);
|
||||
+ }
|
||||
+
|
||||
+ if (cx2 != cx1) {
|
||||
+ // want smaller x
|
||||
+ return Integer.compare(cx1, cx2);
|
||||
+ }
|
||||
+
|
||||
+ // same chunk
|
||||
+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y
|
||||
+ // so now we just compare section y, wanting smaller y
|
||||
+
|
||||
+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4);
|
||||
+ });
|
||||
+
|
||||
+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully).
|
||||
+ ret.addAll(closestRecords);
|
||||
+ }
|
||||
+
|
||||
+ // finds the closest poi entry pos.
|
||||
+ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load) {
|
||||
+ final PoiRecord ret = findNearestPoiRecord(
|
||||
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load
|
||||
+ );
|
||||
+ return ret == null ? null : ret.getPos();
|
||||
+ }
|
||||
+
|
||||
+ // finds the closest `max` poi entry positions.
|
||||
+ public static void findNearestPoiPositions(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load,
|
||||
+ final int max,
|
||||
+ final List<BlockPos> ret) {
|
||||
+ final Set<BlockPos> positions = new HashSet<>();
|
||||
+ // pos predicate is last thing that runs before adding to ret.
|
||||
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
|
||||
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return positions.add(pos.immutable());
|
||||
+ };
|
||||
+
|
||||
+ final List<PoiRecord> toConvert = new ArrayList<>();
|
||||
+ findNearestPoiRecords(
|
||||
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, max, toConvert
|
||||
+ );
|
||||
+
|
||||
+ for (final PoiRecord record : toConvert) {
|
||||
+ ret.add(record.getPos());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // finds the closest poi entry.
|
||||
+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load) {
|
||||
+ final List<PoiRecord> ret = new ArrayList<>();
|
||||
+ findNearestPoiRecords(
|
||||
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load,
|
||||
+ 1, ret
|
||||
+ );
|
||||
+ return ret.isEmpty() ? null : ret.get(0);
|
||||
+ }
|
||||
+
|
||||
+ // finds the closest `max` poi entries.
|
||||
+ public static void findNearestPoiRecords(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ // position predicate must not modify chunk POI
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final double maxDistance,
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load,
|
||||
+ final int max,
|
||||
+ final List<PoiRecord> ret) {
|
||||
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
||||
+
|
||||
+ final double maxDistanceSquared = maxDistance * maxDistance;
|
||||
+ final Double2ObjectRBTreeMap<List<PoiRecord>> closestRecords = new Double2ObjectRBTreeMap<>();
|
||||
+ int totalRecords = 0;
|
||||
+ double furthestDistanceSquared = maxDistanceSquared;
|
||||
+
|
||||
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
|
||||
+ final int lowerY = WorldUtil.getMinSection(poiStorage.world);
|
||||
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
|
||||
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
|
||||
+ final int upperY = WorldUtil.getMaxSection(poiStorage.world);
|
||||
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
|
||||
+
|
||||
+ final int centerX = sourcePosition.getX() >> 4;
|
||||
+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY);
|
||||
+ final int centerZ = sourcePosition.getZ() >> 4;
|
||||
+
|
||||
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
||||
+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ));
|
||||
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
||||
+
|
||||
+ while (!queue.isEmpty()) {
|
||||
+ final long key = queue.dequeueLong();
|
||||
+ final int sectionX = CoordinateUtils.getChunkSectionX(key);
|
||||
+ final int sectionY = CoordinateUtils.getChunkSectionY(key);
|
||||
+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key);
|
||||
+
|
||||
+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) {
|
||||
+ // out of bound chunk
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final double sectionDistanceSquared = getSmallestDistanceSquared(
|
||||
+ (sectionX << 4) + 0.5,
|
||||
+ (sectionY << 4) + 0.5,
|
||||
+ (sectionZ << 4) + 0.5,
|
||||
+ (sectionX << 4) + 15.5,
|
||||
+ (sectionY << 4) + 15.5,
|
||||
+ (sectionZ << 4) + 15.5,
|
||||
+ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ()
|
||||
+ );
|
||||
+
|
||||
+ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // queue all neighbours
|
||||
+ for (int dz = -1; dz <= 1; ++dz) {
|
||||
+ for (int dx = -1; dx <= 1; ++dx) {
|
||||
+ for (int dy = -1; dy <= 1; ++dy) {
|
||||
+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many
|
||||
+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set
|
||||
+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final int neighbourX = sectionX + dx;
|
||||
+ final int neighbourY = sectionY + dy;
|
||||
+ final int neighbourZ = sectionZ + dz;
|
||||
+
|
||||
+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ);
|
||||
+ if (seen.add(neighbourKey)) {
|
||||
+ queue.enqueue(neighbourKey);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key);
|
||||
+
|
||||
+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final PoiSection poiSection = poiSectionOptional.orElse(null);
|
||||
+
|
||||
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
|
||||
+ if (sectionData.isEmpty()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // now we search the section data
|
||||
+ for (final Map.Entry<PoiType, Set<PoiRecord>> entry : sectionData.entrySet()) {
|
||||
+ if (!villagePlaceType.test(entry.getKey())) {
|
||||
+ // filter out by poi type
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // now we can look at the poi data
|
||||
+ for (final PoiRecord poiData : entry.getValue()) {
|
||||
+ if (!occupancyFilter.test(poiData)) {
|
||||
+ // filter by occupancy
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final BlockPos poiPosition = poiData.getPos();
|
||||
+
|
||||
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
|
||||
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
|
||||
+ // out of range for square radius
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped!
|
||||
+ final double dataRange = poiPosition.distSqr(sourcePosition);
|
||||
+
|
||||
+ if (dataRange > maxDistanceSquared) {
|
||||
+ // out of range for distance check
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (dataRange > furthestDistanceSquared && totalRecords >= max) {
|
||||
+ // out of range for distance check
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
|
||||
+ // filter by position
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (dataRange > furthestDistanceSquared) {
|
||||
+ // we know totalRecords < max, so this entry is now our furthest
|
||||
+ furthestDistanceSquared = dataRange;
|
||||
+ }
|
||||
+
|
||||
+ closestRecords.computeIfAbsent(dataRange, (final double unused) -> {
|
||||
+ return new ArrayList<>();
|
||||
+ }).add(poiData);
|
||||
+
|
||||
+ if (++totalRecords >= max) {
|
||||
+ if (closestRecords.size() >= 2) {
|
||||
+ int entriesInClosest = 0;
|
||||
+ final Iterator<Double2ObjectMap.Entry<List<PoiRecord>>> iterator = closestRecords.double2ObjectEntrySet().iterator();
|
||||
+ double nextFurthestDistanceSquared = 0.0;
|
||||
+
|
||||
+ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) {
|
||||
+ final Double2ObjectMap.Entry<List<PoiRecord>> recordEntry = iterator.next();
|
||||
+ entriesInClosest += recordEntry.getValue().size();
|
||||
+ nextFurthestDistanceSquared = recordEntry.getDoubleKey();
|
||||
+ }
|
||||
+
|
||||
+ if (entriesInClosest >= max) {
|
||||
+ // the last set of entries at range wont even be considered for sure... nuke em
|
||||
+ final Double2ObjectMap.Entry<List<PoiRecord>> recordEntry = iterator.next();
|
||||
+ totalRecords -= recordEntry.getValue().size();
|
||||
+ iterator.remove();
|
||||
+
|
||||
+ furthestDistanceSquared = nextFurthestDistanceSquared;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ final List<PoiRecord> closestRecordsUnsorted = new ArrayList<>();
|
||||
+
|
||||
+ // we're done here, so now just flatten the map and sort it.
|
||||
+
|
||||
+ for (final List<PoiRecord> records : closestRecords.values()) {
|
||||
+ closestRecordsUnsorted.addAll(records);
|
||||
+ }
|
||||
+
|
||||
+ // uh oh! we might have multiple records that match the distance sorting!
|
||||
+ // we need to re-order our results by the way vanilla would have iterated over them.
|
||||
+ closestRecordsUnsorted.sort((record1, record2) -> {
|
||||
+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section
|
||||
+ // is fine and should be preserved (this sort is stable so we're good there)
|
||||
+ // but they iterate sections by x then by z (like the following)
|
||||
+ // for (int x = -dx; x <= dx; ++x)
|
||||
+ // for (int z = -dz; z <= dz; ++z)
|
||||
+ // ....
|
||||
+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first
|
||||
+ final BlockPos pos1 = record1.getPos();
|
||||
+ final BlockPos pos2 = record2.getPos();
|
||||
+
|
||||
+ final int cx1 = pos1.getX() >> 4;
|
||||
+ final int cz1 = pos1.getZ() >> 4;
|
||||
+
|
||||
+ final int cx2 = pos2.getX() >> 4;
|
||||
+ final int cz2 = pos2.getZ() >> 4;
|
||||
+
|
||||
+ if (cz2 != cz1) {
|
||||
+ // want smaller z
|
||||
+ return Integer.compare(cz1, cz2);
|
||||
+ }
|
||||
+
|
||||
+ if (cx2 != cx1) {
|
||||
+ // want smaller x
|
||||
+ return Integer.compare(cx1, cx2);
|
||||
+ }
|
||||
+
|
||||
+ // same chunk
|
||||
+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y
|
||||
+ // so now we just compare section y, wanting smaller section y
|
||||
+
|
||||
+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4);
|
||||
+ });
|
||||
+
|
||||
+ // trim out any entries exceeding our maximum
|
||||
+ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) {
|
||||
+ closestRecordsUnsorted.remove(i);
|
||||
+ }
|
||||
+
|
||||
+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully).
|
||||
+ ret.addAll(closestRecordsUnsorted);
|
||||
+ }
|
||||
+
|
||||
+ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load) {
|
||||
+ final PoiRecord ret = findAnyPoiRecord(
|
||||
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load
|
||||
+ );
|
||||
+
|
||||
+ return ret == null ? null : ret.getPos();
|
||||
+ }
|
||||
+
|
||||
+ public static void findAnyPoiPositions(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load,
|
||||
+ final int max,
|
||||
+ final List<BlockPos> ret) {
|
||||
+ final Set<BlockPos> positions = new HashSet<>();
|
||||
+ // pos predicate is last thing that runs before adding to ret.
|
||||
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
|
||||
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return positions.add(pos.immutable());
|
||||
+ };
|
||||
+
|
||||
+ final List<PoiRecord> toConvert = new ArrayList<>();
|
||||
+ findAnyPoiRecords(
|
||||
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert
|
||||
+ );
|
||||
+
|
||||
+ for (final PoiRecord record : toConvert) {
|
||||
+ ret.add(record.getPos());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load) {
|
||||
+ final List<PoiRecord> ret = new ArrayList<>();
|
||||
+ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret);
|
||||
+ return ret.isEmpty() ? null : ret.get(0);
|
||||
+ }
|
||||
+
|
||||
+ public static void findAnyPoiRecords(final PoiManager poiStorage,
|
||||
+ final Predicate<PoiType> villagePlaceType,
|
||||
+ final Predicate<BlockPos> positionPredicate,
|
||||
+ final BlockPos sourcePosition,
|
||||
+ final int range, // distance on x y z axis
|
||||
+ final PoiManager.Occupancy occupancy,
|
||||
+ final boolean load,
|
||||
+ final int max,
|
||||
+ final List<PoiRecord> ret) {
|
||||
+ // the biggest issue with the original mojang implementation is that they chain so many streams together
|
||||
+ // the amount of streams chained just rolls performance, even if nothing is iterated over
|
||||
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
||||
+ final double rangeSquared = range * range;
|
||||
+
|
||||
+ int added = 0;
|
||||
+
|
||||
+ // First up, we need to iterate the chunks
|
||||
+ // all the values here are in chunk sections
|
||||
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
|
||||
+ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4);
|
||||
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
|
||||
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
|
||||
+ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4);
|
||||
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
|
||||
+
|
||||
+ // Vanilla iterates by x until max is reached then increases z
|
||||
+ // vanilla also searches by increasing Y section value
|
||||
+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) {
|
||||
+ for (int currX = lowerX; currX <= upperX; ++currX) {
|
||||
+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need
|
||||
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) :
|
||||
+ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ));
|
||||
+ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null);
|
||||
+ if (poiSection == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
|
||||
+ if (sectionData.isEmpty()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // now we search the section data
|
||||
+ for (final Map.Entry<PoiType, Set<PoiRecord>> entry : sectionData.entrySet()) {
|
||||
+ if (!villagePlaceType.test(entry.getKey())) {
|
||||
+ // filter out by poi type
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // now we can look at the poi data
|
||||
+ for (final PoiRecord poiData : entry.getValue()) {
|
||||
+ if (!occupancyFilter.test(poiData)) {
|
||||
+ // filter by occupancy
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final BlockPos poiPosition = poiData.getPos();
|
||||
+
|
||||
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
|
||||
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
|
||||
+ // out of range for square radius
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (poiPosition.distSqr(sourcePosition) > rangeSquared) {
|
||||
+ // out of range for distance check
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
|
||||
+ // filter by position
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // found one!
|
||||
+ ret.add(poiData);
|
||||
+ if (++added >= max) {
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private PoiAccess() {
|
||||
+ throw new RuntimeException();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
||||
@@ -0,0 +0,0 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
- Set<BlockPos> set = poiManager.findAllClosestFirst(this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet());
|
||||
+ // Paper start - optimise POI access
|
||||
+ java.util.List<BlockPos> poiposes = new java.util.ArrayList<>();
|
||||
+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
|
||||
+ Set<BlockPos> set = new java.util.HashSet<>(poiposes);
|
||||
+ // Paper end - optimise POI access
|
||||
Path path = entity.getNavigation().createPath(set, this.poiType.getValidRange());
|
||||
if (path != null && path.canReach()) {
|
||||
BlockPos blockPos = path.getTarget();
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
|
||||
@@ -0,0 +0,0 @@ public class NearestBedSensor extends Sensor<Mob> {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
- Stream<BlockPos> stream = poiManager.findAll(PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY);
|
||||
- Path path = entity.getNavigation().createPath(stream, PoiType.HOME.getValidRange());
|
||||
+ // Paper start - optimise POI access
|
||||
+ java.util.List<BlockPos> poiposes = new java.util.ArrayList<>();
|
||||
+ // don't ask me why it's unbounded. ask mojang.
|
||||
+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
|
||||
+ Path path = entity.getNavigation().createPath(new java.util.HashSet<>(poiposes), PoiType.HOME.getValidRange());
|
||||
+ // Paper end - optimise POI access
|
||||
if (path != null && path.canReach()) {
|
||||
BlockPos blockPos = path.getTarget();
|
||||
Optional<PoiType> optional = poiManager.getType(blockPos);
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
||||
@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
||||
public static final int VILLAGE_SECTION_SIZE = 1;
|
||||
private final PoiManager.DistanceTracker distanceTracker;
|
||||
private final LongSet loadedChunks = new LongOpenHashSet();
|
||||
- private final net.minecraft.server.level.ServerLevel world; // Paper
|
||||
+ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public
|
||||
|
||||
public PoiManager(Path path, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) {
|
||||
super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world);
|
||||
@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
||||
}
|
||||
|
||||
public Optional<BlockPos> find(Predicate<PoiType> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
|
||||
- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst();
|
||||
+ // Paper start - re-route to faster logic
|
||||
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false);
|
||||
+ return Optional.ofNullable(ret);
|
||||
+ // Paper end - re-route to faster logic
|
||||
}
|
||||
|
||||
public Optional<BlockPos> findClosest(Predicate<PoiType> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
|
||||
- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> {
|
||||
- return blockPos2.distSqr(pos);
|
||||
- }));
|
||||
+ // Paper start - re-route to faster logic
|
||||
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius*radius, occupationStatus, false);
|
||||
+ return Optional.ofNullable(ret);
|
||||
+ // Paper end - re-route to faster logic
|
||||
}
|
||||
|
||||
public Optional<BlockPos> findClosest(Predicate<PoiType> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
|
||||
- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).filter(posPredicate).min(Comparator.comparingDouble((blockPos2) -> {
|
||||
- return blockPos2.distSqr(pos);
|
||||
- }));
|
||||
+ // Paper start - re-route to faster logic
|
||||
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false);
|
||||
+ return Optional.ofNullable(ret);
|
||||
+ // Paper end - re-route to faster logic
|
||||
}
|
||||
|
||||
public Optional<BlockPos> take(Predicate<PoiType> typePredicate, Predicate<BlockPos> positionPredicate, BlockPos pos, int radius) {
|
||||
- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> {
|
||||
- return positionPredicate.test(poi.getPos());
|
||||
- }).findFirst().map((poi) -> {
|
||||
- poi.acquireTicket();
|
||||
- return poi.getPos();
|
||||
- });
|
||||
+ // Paper start - re-route to faster logic
|
||||
+ PoiRecord ret = io.papermc.paper.util.PoiAccess.findAnyPoiRecord(
|
||||
+ this, typePredicate, positionPredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE, false
|
||||
+ );
|
||||
+ if (ret == null) {
|
||||
+ return Optional.empty();
|
||||
+ }
|
||||
+ ret.acquireTicket();
|
||||
+ return Optional.of(ret.getPos());
|
||||
+ // Paper end - re-route to faster logic
|
||||
}
|
||||
|
||||
public Optional<BlockPos> getRandom(Predicate<PoiType> typePredicate, Predicate<BlockPos> positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, Random random) {
|
||||
- List<PoiRecord> list = this.getInRange(typePredicate, pos, radius, occupationStatus).collect(Collectors.toList());
|
||||
- Collections.shuffle(list, random);
|
||||
- return list.stream().filter((poi) -> {
|
||||
- return positionPredicate.test(poi.getPos());
|
||||
- }).findFirst().map(PoiRecord::getPos);
|
||||
+ // Paper start - re-route to faster logic
|
||||
+ List<PoiRecord> list = new java.util.ArrayList<>();
|
||||
+ io.papermc.paper.util.PoiAccess.findAnyPoiRecords(
|
||||
+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list
|
||||
+ );
|
||||
+
|
||||
+ // the old method shuffled the list and then tried to find the first element in it that
|
||||
+ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a
|
||||
+ // shuffle entirely, and just pick a random element from list
|
||||
+ if (list.isEmpty()) {
|
||||
+ return Optional.empty();
|
||||
+ }
|
||||
+
|
||||
+ return Optional.of(list.get(random.nextInt(list.size())).getPos());
|
||||
+ // Paper end - re-route to faster logic
|
||||
}
|
||||
|
||||
public boolean release(BlockPos pos) {
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
|
||||
@@ -0,0 +0,0 @@ import org.slf4j.Logger;
|
||||
public class PoiSection {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Short2ObjectMap<PoiRecord> records = new Short2ObjectOpenHashMap<>();
|
||||
- private final Map<PoiType, Set<PoiRecord>> byType = Maps.newHashMap();
|
||||
+ private final Map<PoiType, Set<PoiRecord>> byType = Maps.newHashMap(); public final Map<PoiType, Set<PoiRecord>> getData() { return this.byType; } // Paper - public accessor
|
||||
private final Runnable setDirty;
|
||||
private boolean isValid;
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
||||
@@ -0,0 +0,0 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
|
||||
}
|
||||
|
||||
@Nullable
|
||||
- protected Optional<R> get(long pos) {
|
||||
+ public Optional<R> get(long pos) { // Paper - public
|
||||
return this.storage.get(pos);
|
||||
}
|
||||
|
||||
- protected Optional<R> getOrLoad(long pos) {
|
||||
+ public Optional<R> getOrLoad(long pos) { // Paper - public
|
||||
if (this.outsideStoredRange(pos)) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
|
||||
@@ -0,0 +0,0 @@ public class PortalForcer {
|
||||
// int i = flag ? 16 : 128;
|
||||
// CraftBukkit end
|
||||
|
||||
- villageplace.ensureLoadedAndValid(this.level, blockposition, i);
|
||||
- Optional<PoiRecord> optional = villageplace.getInSquare((villageplacetype) -> {
|
||||
- return villageplacetype == PoiType.NETHER_PORTAL;
|
||||
- }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> {
|
||||
- return worldborder.isWithinBounds(villageplacerecord.getPos());
|
||||
- }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error
|
||||
- return villageplacerecord.getPos().distSqr(blockposition);
|
||||
- }).thenComparingInt((villageplacerecord) -> {
|
||||
- return villageplacerecord.getPos().getY();
|
||||
- })).filter((villageplacerecord) -> {
|
||||
- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
|
||||
- }).findFirst();
|
||||
+ // Paper start - optimise portals
|
||||
+ Optional<PoiRecord> optional;
|
||||
+ java.util.List<PoiRecord> records = new java.util.ArrayList<>();
|
||||
+ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords(
|
||||
+ villageplace,
|
||||
+ (PoiType type) -> {
|
||||
+ return type == PoiType.NETHER_PORTAL;
|
||||
+ },
|
||||
+ (BlockPos pos) -> {
|
||||
+ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.ChunkStatus.EMPTY);
|
||||
+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL)
|
||||
+ && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.HEIGHTMAPS))) {
|
||||
+ // why would we generate the chunk?
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (!worldborder.isWithinBounds(pos)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
|
||||
+ },
|
||||
+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records
|
||||
+ );
|
||||
+
|
||||
+ // this gets us most of the way there, but we bias towards lower y values.
|
||||
+ PoiRecord lowestYRecord = null;
|
||||
+ for (PoiRecord record : records) {
|
||||
+ if (lowestYRecord == null) {
|
||||
+ lowestYRecord = record;
|
||||
+ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) {
|
||||
+ lowestYRecord = record;
|
||||
+ }
|
||||
+ }
|
||||
+ // now we're done
|
||||
+ optional = Optional.ofNullable(lowestYRecord);
|
||||
+ // Paper end - optimise portals
|
||||
|
||||
return optional.map((villageplacerecord) -> {
|
||||
BlockPos blockposition1 = villageplacerecord.getPos();
|
Loading…
Add table
Reference in a new issue