Yatopia/patches/server/0035-lithium-collision-optimizations.patch
ishland 0ee1f14d69 Updated Upstream and Sidestream(s) (Tuinity/EMC/Purpur) (FIRST 1.16.4)
Upstream/An Sidestream has released updates that appears to apply and compile correctly
This update has NOT been tested by YatopiaMC and as with ANY update, please do your own testing.

Tuinity Changes:
d7f2b6e Updated Upstream (Paper)
0850468 Properly ignite entities that stand in fire
4ff2d15 More cleanup to delay chunk unloads
52f4d08 Cleanup post dev branch merge
f2eef4a Fixup dev branch patches and store reverted patches in revert folder
85aba13 Merge branch 'dev/some-opts'
c3a9e41 Remove deprecated function usage in github actions (#200)
dde7028 Merge branch 'master' into dev/some-opts
083e162 Updated Upstream (Paper) - Update to 1.16.4
576e2cc Rate limit incoming packets
7f251e0 Updated Upstream (Paper)
7d45836 Updated Upstream (Paper)
cff9ec0 Updated Upstream (Paper)
c3d5d24 Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.3
c12d582 Merge branch 'master' into dev/some-opts
cbffdcc Do not mark entities in unloaded chunks as being in blocks
988e550 Updated Upstream (Paper)
84aecdb Merge https://github.com/Spottedleaf/Tuinity into ver/1.16.3
f7b8e25 Split dev branch
3200930 Merge branch 'master' into dev/some-opts
82e5dfb Updated Upstream (Paper)
d41103d Updated Upstream (Paper)
1d73f6c Updated Upstream (Paper)
8ba1bab Updated Upstream (Paper)
ac6b72e Updated Upstream (Paper)
8d982c4 Updated Upstream (Paper)
2c7bad9 Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.3
903f580 Merge remote-tracking branch 'origin/dev/some-opts' into dev/some-opts
0179ea8 Re-Add region manager and notify patch
6886867 Merge branch 'master' into dev/some-opts
fb292c1 Updated Upstream (Paper)
442890b Fix decompression with Velocity natives (#191)
57fed71 Fix decompression with Velocity natives
102d60b Rebuild patches
968512b Add Velocity natives for encryption and compression (#188)
e2dcdd1 Correct return value for ChunkCache#getCubes
4a99f3e Updated Upstream (Paper)
f5d537e Merge https://github.com/Spottedleaf/Tuinity into ver/1.16.3
784b838 Some fixes
be0b91f Merge branch 'master' into dev/some-opts
2cb36ca Optimise non-flush packet sending
be68938 Hey git, may I know what happened here?
1cac0a2 Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.3
d72e062 Merge branch 'master' into dev/some-opts
76777f0 Updated Upstream (Paper)
6465aba Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.3
d82174a Merge branch 'master' into dev/some-opts
460581d Fix getClosestEntity not working
d39cda0 Updated Upstream (Paper)
2110847 Rewrite getClosestEntity
3e45700 Do not return complex parts for entity by class lookup
c9cfdba Updated Upstream (Paper)
5994cb0 Updated Upstream (Paper)
7dfe18c Merge https://github.com/Spottedleaf/Tuinity into ver/1.16.3
363973d Merge branch 'master' into dev/some-opts
1c05858 Updated Upstream (Paper)
adaafb4 Git merge doesn't update submodules
ab71ded Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.3
54db338 Merge branch 'master' into dev/some-opts
483289c Updated Upstream (Paper)
e36359e Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.2
ef8cd34 Fix NPE
b770af9 Merge branch 'master' into dev/some-opts
d479e12 Merge https://github.com/Spottedleaf/Tuinity into ver/1.16.2
a1b90f8 Merge branch 'master' into dev/some-opts
30c5ca5 Merge branch 'master' into dev/some-opts
e59b60b Updated Upstream (Paper)
09f62a7 Rebuild patches
b041d11 Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into ver/1.16.2
27fca2d Merge branch 'master' into dev/some-opts
a218f5d Updated Upstream (Paper)
f7c5428 Updated Upstream (Paper)
a17dc2c Attempt to fix incorrect nearest village distance tracker updating
8830cef Remove streams for poi searching in some zombie pathfinding
89276ac Fix villagers aggressively looking at people
db64f14 Make sure to despawn entities if they are outside the player general area range
ba0bfda Updated Upstream (Paper)
200f825 Actually unload POI data
3dc7b9e Updated Upstream (Paper)
30f6ca0 Merge branch 'master' of https://github.com/Spottedleaf/Tuinity into 1.16.2tmp
3b008f5 Optimisations
c628aa4 Updated Upstream (Paper)
1ed41fc Updated Upstream (Paper)
193d9bf Updated Upstream (Paper)
f2ac649 Updated Upstream (Paper)
a63298f Upstream
a12e1f6 Merge branch 'ver/1.16.2' of https://github.com/Spottedleaf/Tuinity into 1.16.2tmp
9eed723 Rebuild patches
d03dbcc Updated paper
c1002c5 Merge branch 'ver/1.16.2' of https://github.com/Spottedleaf/Tuinity into 1.16.2tmp
27084eb Updated paper
bfb0ded Merge branch 'ver/1.16.2' of https://github.com/Spottedleaf/Tuinity into 1.16.2tmp
763aa6c Updated Upstream (Paper)
49898d0 Merge branch 'ver/1.16.2' of https://github.com/Spottedleaf/Tuinity into 1.16.2tmp
199edf5 Merge branch 'ver/1.16.2' of https://github.com/Spottedleaf/Tuinity into 1.16.2tmp
78bfcfb Update everything
bbbe82e change upstream

EMC Changes:
4da58340 clear more entity tasks with reloads
2f3d567f Improve temp meta reloading
efd7ea89 Updated Paper
25426b02 Updated Paper
35099319 Fix blockbreaknaturally event not always having the loc stored

Purpur Changes:
306e9ac Add missing repos for deps
67be68c Updated Upstream (Tuinity)
cf3e3e7 Fix last patch :3
a43bb36 Arrows should not reset despawn counter
f0b8bd7 Updated Upstream (Paper)
f15d3f0 Updated Upstream (Tuinity)
39ce110 Fix missed obf method rename in Tuinity patches
bae5baa Updated Upstream (Paper)
5569d02 Updated Upstream (Paper)
72c4c5e Updated Upstream (Paper)
7fa8baf Update readme
03654a6 Update to 1.16.4 ^_^
0fa478d Updated Upstream (Paper & Tuinity)
3fec5da Rebuild patches
2f7a0a0 Fix client lag on advancement loading
3f325b8 Fix #86 Move pathfinders into NMS package to satisfy Paper's AI API
7cfd3d7 Updated Upstream (Paper)
cc6c167 Updated Upstream (Paper)
2020-11-07 15:03:31 +08:00

1200 lines
55 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: JellySquid <jellysquid+atwork@protonmail.com>
Date: Thu, 20 Aug 2020 15:26:53 +0300
Subject: [PATCH] lithium collision optimizations
Original code by JellySquid, licensed under LGPLv3
you can find the original code on https://github.com/jellysquid3/lithium-fabric/ (Yarn mappings)
Co-authored-by: Ivan Pekov <ivan@mrivanplays.com>
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/EntityClassGroup.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/EntityClassGroup.java
new file mode 100644
index 0000000000000000000000000000000000000000..91608928dc5cd2f600f1ce57ef7931078f354ea6
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/EntityClassGroup.java
@@ -0,0 +1,101 @@
+package me.jellysquid.mods.lithium.common.entity;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import net.minecraft.server.Entity;
+
+/**
+ * Class for grouping Entity classes that meet some requirement for use in TypeFilterableList
+ * Designed to allow create groups of entity classes that are updated when mods add new entities that fit into the group.
+ *
+ * @author 2No2Name
+ */
+public class EntityClassGroup {
+ //Keep a set of classes that were already added to matching class groups, so we only analyse them once.
+ private static final Map<Class<?>, Object> knownEntityClasses = new ConcurrentHashMap<>(); //value unused, no set variant available
+ //Keep track of available class groups for updating them in case an entity class is instantiated for the first time
+ private static final List<EntityClassGroup> entityClassGroups = new net.yatopia.server.list.GlueList<>(); // Yatopia - GlueList
+
+ public static final EntityClassGroup COLLISION_BOX_OVERRIDE = new EntityClassGroup(
+ (entityClass) -> {
+ boolean overwritten;
+ while (entityClass != null && entityClass != Entity.class) {
+ try {
+ overwritten = true;
+ entityClass.getDeclaredMethod("aY");
+ } catch (NoSuchMethodException e) {
+ overwritten = false;
+ entityClass = entityClass.getSuperclass();
+ }
+ if (overwritten) {
+ return true;
+ }
+ }
+ return false;
+ }
+ );
+ public static final EntityClassGroup HARD_COLLISION_BOX_OVERRIDE = new EntityClassGroup(
+ (entityClass) -> {
+ boolean overwritten;
+ while (entityClass != null && entityClass != Entity.class) {
+ try {
+ overwritten = true;
+ entityClass.getDeclaredMethod("j", Entity.class);
+ } catch (NoSuchMethodException e) {
+ overwritten = false;
+ entityClass = entityClass.getSuperclass();
+ }
+ if (overwritten)
+ return true;
+ }
+ return false;
+ }
+ );
+
+ private final Map<Class<?>, Object> classGroup; //value unused, no set variant available
+ private final Function<Class<?>, Boolean> classFitEvaluator;
+
+ public EntityClassGroup(Function<Class<?>, Boolean> classFitEvaluator) {
+ this.classGroup = new ConcurrentHashMap<>();
+ EntityClassGroup.entityClassGroups.add(this);
+ this.classFitEvaluator = classFitEvaluator;
+ }
+
+ public EntityClassGroup add(Class<?> entityClass) {
+ this.classGroup.put(entityClass, entityClass);
+ return this;
+ }
+
+ public boolean contains(Class<?> entityClass) {
+ EntityClassGroup.analyseEntityClass(entityClass);
+ return this.classGroup.containsKey(entityClass);
+ }
+
+ public Collection<Class<?>> getCollection() {
+ return this.classGroup.keySet();
+ }
+
+ public void addClassIfFitting(Class<?> discoveredEntityClass) {
+ if (this.classGroup.containsKey(discoveredEntityClass)) {
+ return;
+ }
+ if (this.classFitEvaluator != null && this.classFitEvaluator.apply(discoveredEntityClass)) {
+ this.classGroup.put(discoveredEntityClass, discoveredEntityClass);
+ }
+ }
+
+ public static void analyseEntityClass(Class<?> entityClass) {
+ if (EntityClassGroup.knownEntityClasses.containsKey(entityClass)) {
+ return;
+ }
+ EntityClassGroup.knownEntityClasses.put(entityClass, entityClass);
+
+ for (EntityClassGroup entityClassGroup : EntityClassGroup.entityClassGroups) {
+ entityClassGroup.addClassIfFitting(entityClass);
+ }
+ }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/EntityClassGroupHelper.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/EntityClassGroupHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..20f80ae80de91615ea02f0771f7c020c3e4c6d1e
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/EntityClassGroupHelper.java
@@ -0,0 +1,32 @@
+package me.jellysquid.mods.lithium.common.entity;
+
+import java.util.List;
+import me.jellysquid.mods.lithium.common.world.WorldHelper;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Entity;
+import net.minecraft.server.IEntityAccess;
+import net.minecraft.server.World;
+
+public class EntityClassGroupHelper {
+
+ /**
+ * Partial [VanillaCopy] Classes overriding Entity.getHardCollisionBox(Entity other) or Entity.getCollisionBox()
+ * The returned entity list is only used to call getCollisionBox and getHardCollisionBox. As most entities return null
+ * for both of these methods, getting those is not necessary. This is why we only get entities when they overwrite
+ * getCollisionBox
+ *
+ * @param entityView the world
+ * @param selection the box the entities have to collide with
+ * @param entity the entity that is searching for the colliding entities
+ * @return list of entities with collision boxes
+ */
+ public static List<Entity> getEntitiesWithCollisionBoxForEntity(IEntityAccess entityView, AxisAlignedBB selection, Entity entity, boolean loadChunks) {
+ if (entity != null && EntityClassGroup.HARD_COLLISION_BOX_OVERRIDE.contains(entity.getClass()) || !(entityView instanceof World)) {
+ //use vanilla code when method_30949 (previously getHardCollisionBox(Entity other)) is overwritten, as every entity could be relevant as argument of getHardCollisionBox
+ return entityView.getEntities(entity, selection);
+ } else {
+ //only get entities that overwrite method_30948 (previously getCollisionBox)
+ return WorldHelper.getEntitiesOfClassGroup((World) entityView, entity, EntityClassGroup.COLLISION_BOX_OVERRIDE, selection, loadChunks);
+ }
+ }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2e08aafd31b3adbbbcc96df6aea7a647fb8bdf8
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java
@@ -0,0 +1,188 @@
+package me.jellysquid.mods.lithium.common.entity;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import me.jellysquid.mods.lithium.common.entity.movement.ChunkAwareBlockCollisionSweeper;
+import me.jellysquid.mods.lithium.common.util.Producer;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Entity;
+import net.minecraft.server.ICollisionAccess;
+import net.minecraft.server.IEntityAccess;
+import net.minecraft.server.VoxelShape;
+import net.minecraft.server.VoxelShapes;
+import net.minecraft.server.WorldBorder;
+import net.yatopia.server.EntityFilter;
+
+public class LithiumEntityCollisions {
+ public static final double EPSILON = 1.0E-7D;
+
+ /**
+ * [VanillaCopy] CollisionView#getBlockCollisions(Entity, Box)
+ * This is a much, much faster implementation which uses simple collision testing against full-cube block shapes.
+ * Checks against the world border are replaced with our own optimized functions which do not go through the
+ * VoxelShape system.
+ */
+ public static Stream<VoxelShape> getBlockCollisions(ICollisionAccess world, Entity entity, AxisAlignedBB box) {
+ if (isBoxEmpty(box)) {
+ return Stream.empty();
+ }
+
+ return Producer.asStream(getBlockCollisionProducer(world, entity, box));
+ }
+
+ public static void fillBlockCollisionsList(ICollisionAccess world, List<AxisAlignedBB> filled, Entity entity, AxisAlignedBB box) {
+ if (isBoxEmpty(box)) {
+ return;
+ }
+
+ Producer.fillList(
+ getBlockCollisionProducer(world, entity, box),
+ filled,
+ (voxelShape, axisAlignedBBS) -> VoxelShapes.addBoxesToIfIntersects(voxelShape, box, axisAlignedBBS)
+ );
+ }
+
+ public static Producer<VoxelShape> getBlockCollisionProducer(ICollisionAccess world, Entity entity, AxisAlignedBB box) {
+ final ChunkAwareBlockCollisionSweeper sweeper = new ChunkAwareBlockCollisionSweeper(world, entity, box);
+
+ return consumer -> {
+ VoxelShape shape = sweeper.getNextCollidedShape();
+ if (shape != null) {
+ consumer.accept(shape);
+ return true;
+ }
+ return false;
+ };
+ }
+
+ /**
+ * See {@link LithiumEntityCollisions#getBlockCollisions(ICollisionAccess, Entity, AxisAlignedBB)}
+ *
+ * @return True if the box (possibly that of an entity's) collided with any blocks
+ */
+ public static boolean doesBoxCollideWithBlocks(ICollisionAccess world, Entity entity, AxisAlignedBB box) {
+ if (isBoxEmpty(box)) {
+ return false;
+ }
+
+ final ChunkAwareBlockCollisionSweeper sweeper = new ChunkAwareBlockCollisionSweeper(world, entity, box);
+
+ VoxelShape shape = sweeper.getNextCollidedShape();
+ return shape != null;
+ }
+
+ /**
+ * See {@link LithiumEntityCollisions#getEntityCollisions(IEntityAccess, Entity, AxisAlignedBB, Predicate)}
+ *
+ * @return True if the box (possibly that of an entity's) collided with any other entities
+ */
+ public static boolean doesBoxCollideWithEntities(IEntityAccess view, Entity entity, AxisAlignedBB box, Predicate<Entity> predicate) {
+ if (isBoxEmpty(box)) {
+ return false;
+ }
+
+ return getEntityCollisionProducer(view, entity, box.grow(EPSILON), predicate, false).computeNext(null);
+ }
+
+ /**
+ * Returns a stream of entity collision boxes.
+ */
+ public static Stream<VoxelShape> getEntityCollisions(IEntityAccess view, Entity entity, AxisAlignedBB box, Predicate<Entity> predicate) {
+ if (isBoxEmpty(box)) {
+ return Stream.empty();
+ }
+
+ return Producer.asStream(getEntityCollisionProducer(view, entity, box.grow(EPSILON), predicate, false));
+ }
+
+ public static void fillEntityCollisionsList(IEntityAccess view, Entity entity, List<AxisAlignedBB> filled, AxisAlignedBB box, boolean loadChunks) {
+ if (isBoxEmpty(box)) {
+ return;
+ }
+
+ Producer.fillList(
+ getEntityCollisionProducer(view, entity, box.grow(EPSILON), EntityFilter.getFilter(entity), loadChunks),
+ filled,
+ (voxelShape, axisAlignedBBS) -> VoxelShapes.addBoxesToIfIntersects(voxelShape, box, axisAlignedBBS)
+ );
+ }
+
+ /**
+ * [VanillaCopy] EntityView#getEntityCollisions
+ * Re-implements the function named above without stream code or unnecessary allocations. This can provide a small
+ * boost in some situations (such as heavy entity crowding) and reduces the allocation rate significantly.
+ */
+ public static Producer<VoxelShape> getEntityCollisionProducer(IEntityAccess view, Entity entity, AxisAlignedBB box, Predicate<Entity> predicate, boolean loadChunks) {
+ return new Producer<VoxelShape>() {
+ private Iterator<Entity> it;
+
+ @Override
+ public boolean computeNext(Consumer<? super VoxelShape> consumer) {
+ if (it == null) {
+ /*
+ * In case entity's class is overriding method_30949, all types of entities may be (=> are assumed to be) required.
+ * Otherwise only get entities that override method_30948 are required, as other entities cannot collide.
+ */
+ this.it = EntityClassGroupHelper.getEntitiesWithCollisionBoxForEntity(view, box, entity, loadChunks).iterator();
+ }
+
+ while (this.it.hasNext()) {
+ Entity otherEntity = this.it.next();
+
+ if (!predicate.test(otherEntity)) {
+ continue;
+ }
+
+ if (entity == null) {
+ if (!otherEntity.collisionBoxIsHard()) {
+ continue;
+ }
+ } else if (!entity.hardCollidesWith(otherEntity)) {
+ continue;
+ }
+
+ if (consumer != null) {
+ consumer.accept(VoxelShapes.of(otherEntity.getBoundingBox()));
+ }
+ return true;
+ }
+
+ return false;
+ }
+ };
+ }
+
+ /**
+ * This provides a faster check for seeing if an entity is within the world border as it avoids going through
+ * the slower shape system.
+ *
+ * @return True if the {@param box} is fully within the {@param border}, otherwise false.
+ */
+ public static boolean isWithinWorldBorder(WorldBorder border, AxisAlignedBB box) {
+ double wboxMinX = Math.floor(border.getMinX());
+ double wboxMinZ = Math.floor(border.getMinZ());
+
+ double wboxMaxX = Math.ceil(border.getMaxX());
+ double wboxMaxZ = Math.ceil(border.getMaxZ());
+
+ return box.minX >= wboxMinX && box.minX < wboxMaxX && box.minZ >= wboxMinZ && box.minZ < wboxMaxZ &&
+ box.maxX >= wboxMinX && box.maxX < wboxMaxX && box.maxZ >= wboxMinZ && box.maxZ < wboxMaxZ;
+ }
+
+ public static boolean canEntityCollideWithWorldBorder(ICollisionAccess world, Entity entity) {
+ WorldBorder border = world.getWorldBorder();
+
+ boolean isInsideBorder = isWithinWorldBorder(border, entity.getBoundingBox().shrink(EPSILON));
+ boolean isCrossingBorder = isWithinWorldBorder(border, entity.getBoundingBox().grow(EPSILON));
+
+ return !isInsideBorder && isCrossingBorder;
+ }
+
+ private static boolean isBoxEmpty(AxisAlignedBB box) {
+ return box.getAverageSideLength() <= EPSILON;
+ }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ed343cfb3130446c85dab2ca04d60f91e2c94fb
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java
@@ -0,0 +1,278 @@
+package me.jellysquid.mods.lithium.common.entity.movement;
+
+import me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.BlockPosition;
+import net.minecraft.server.Blocks;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.ChunkSection;
+import net.minecraft.server.Entity;
+import net.minecraft.server.IBlockData;
+import net.minecraft.server.IChunkAccess;
+import net.minecraft.server.ICollisionAccess;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.OperatorBoolean;
+import net.minecraft.server.VoxelShape;
+import net.minecraft.server.VoxelShapeCollision;
+import net.minecraft.server.VoxelShapes;
+
+import static me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.EPSILON;
+
+/**
+ * ChunkAwareBlockCollisionSweeper iterates over blocks in one chunk section at a time. Together with the chunk
+ * section keeping track of the amount of oversized blocks inside the number of iterations can often be reduced.
+ */
+public class ChunkAwareBlockCollisionSweeper {
+ private static final boolean OVERSIZED_BLOCK_COUNTING_ENABLED = OversizedBlocksCounter.class.isAssignableFrom(ChunkSection.class);
+
+ private final BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition();
+
+ /**
+ * The collision box being swept through the world.
+ */
+ private final AxisAlignedBB box;
+
+ /**
+ * The VoxelShape of the collision box being swept through the world.
+ */
+ private final VoxelShape shape;
+
+ private final ICollisionAccess view;
+ private final VoxelShapeCollision context;
+
+ private final Entity entity;
+
+ //limits of the area without extension for oversized blocks
+ private final int minX, minY, minZ, maxX, maxY, maxZ;
+
+ //variables prefixed with c refer to the iteration of the currently cached chunk section
+ private int chunkX, chunkY, chunkZ;
+ private int cStartX, cStartZ;
+ private int cEndX, cEndZ;
+ private int cX, cY, cZ;
+
+ private int cTotalSize;
+ private int cIterated;
+
+ private boolean sectionOversizedBlocks;
+ private IChunkAccess cachedChunk;
+ private ChunkSection cachedChunkSection;
+ private boolean needEntityCollisionCheck;
+
+ public ChunkAwareBlockCollisionSweeper(ICollisionAccess view, Entity entity, AxisAlignedBB box) {
+ this.box = box;
+ this.shape = VoxelShapes.of(box);
+ this.context = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity);
+ this.view = view;
+ this.entity = entity;
+ this.needEntityCollisionCheck = entity != null;
+
+ this.minX = MathHelper.floor(box.minX - EPSILON);
+ this.maxX = MathHelper.floor(box.maxX + EPSILON);
+ this.minY = MathHelper.clamp((int) (box.minY - EPSILON), 0, 255);
+ this.maxY = MathHelper.clamp((int) (box.maxY + EPSILON), 0, 255);
+ this.minZ = MathHelper.floor(box.minZ - EPSILON);
+ this.maxZ = MathHelper.floor(box.maxZ + EPSILON);
+
+ this.chunkX = (this.minX - 1) >> 4;
+ this.chunkZ = (this.minZ - 1) >> 4;
+
+ this.cIterated = 0;
+ this.cTotalSize = 0;
+
+ //decrement as first nextSection call will increment it again
+ this.chunkX--;
+ }
+
+ private boolean nextSection() {
+ do {
+ do {
+ //find the coordinates of the next section inside the area expanded by 1 block on all sides
+ //note: this.minX, maxX etc are not expanded, so there are lots of +1 and -1 around.
+ if (this.cachedChunk != null && this.chunkY < 15 && this.chunkY < ((this.maxY + 1) >> 4)) {
+ this.chunkY++;
+ this.cachedChunkSection = this.cachedChunk.getSections()[this.chunkY];
+ } else {
+ this.chunkY = MathHelper.clamp((this.minY - 1) >> 4, 0, 15);
+
+ if ((this.chunkX < ((this.maxX + 1) >> 4))) {
+ //first initialization takes this branch
+ this.chunkX++;
+ } else {
+ this.chunkX = (this.minX - 1) >> 4;
+
+ if (this.chunkZ < ((this.maxZ + 1) >> 4)) {
+ this.chunkZ++;
+ } else {
+ return false; //no more sections to iterate
+ }
+ }
+ //Casting to Chunk is not checked, together with other mods this could cause a ClassCastException
+ this.cachedChunk = (IChunkAccess) this.view.c(this.chunkX, this.chunkZ);
+ if (this.cachedChunk != null) {
+ this.cachedChunkSection = this.cachedChunk.getSections()[this.chunkY];
+ }
+ }
+ //skip empty chunks and empty chunk sections
+ } while (this.cachedChunk == null || ChunkSection.isEmpty(this.cachedChunkSection));
+
+ this.sectionOversizedBlocks = hasChunkSectionOversizedBlocks(this.cachedChunk, this.chunkY);
+
+ int sizeExtension = this.sectionOversizedBlocks ? 1 : 0;
+
+ this.cEndX = Math.min(this.maxX + sizeExtension, 15 + (this.chunkX << 4));
+ int cEndY = Math.min(this.maxY + sizeExtension, 15 + (this.chunkY << 4));
+ this.cEndZ = Math.min(this.maxZ + sizeExtension, 15 + (this.chunkZ << 4));
+
+ this.cStartX = Math.max(this.minX - sizeExtension, this.chunkX << 4);
+ int cStartY = Math.max(this.minY - sizeExtension, this.chunkY << 4);
+ this.cStartZ = Math.max(this.minZ - sizeExtension, this.chunkZ << 4);
+ this.cX = this.cStartX;
+ this.cY = cStartY;
+ this.cZ = this.cStartZ;
+
+ this.cTotalSize = (this.cEndX - this.cStartX + 1) * (cEndY - cStartY + 1) * (this.cEndZ - this.cStartZ + 1);
+ //skip completely empty section iterations
+ } while (this.cTotalSize == 0);
+ this.cIterated = 0;
+
+ return true;
+ }
+
+ public VoxelShape getNextCollidedShape() {
+ VoxelShape shape = null;
+
+ if (this.needEntityCollisionCheck) {
+ shape = this.getNextEntityCollision();
+
+ this.needEntityCollisionCheck = false;
+ }
+
+ if (shape == null) {
+ shape = this.getNextBlockCollision();
+ }
+
+ return shape;
+ }
+
+
+ /**
+ * Advances the sweep forward until finding a block with a box-colliding VoxelShape
+ *
+ * @return null if no VoxelShape is left in the area, otherwise the next VoxelShape
+ */
+ private VoxelShape getNextBlockCollision() {
+ while (true) {
+ if (this.cIterated >= this.cTotalSize) {
+ if (!this.nextSection()) {
+ break;
+ }
+ }
+ this.cIterated++;
+
+
+ final int x = this.cX;
+ final int y = this.cY;
+ final int z = this.cZ;
+
+ //The iteration order within a chunk section is chosen so that it causes a mostly linear array access in the storage.
+ //In net.minecraft.world.chunk.PalettedContainer.toIndex x gets the 4 least significant bits, z the 4 above, and y the 4 even higher ones.
+ //Linearly accessing arrays might be slightly faster than other access patterns.
+ //This code hasn't been benchmarked in comparison to another access order.
+ if (this.cX < this.cEndX) {
+ this.cX++;
+ } else if (this.cZ < this.cEndZ) {
+ this.cX = this.cStartX;
+ this.cZ++;
+ } else {
+ this.cX = this.cStartX;
+ this.cZ = this.cStartZ;
+ this.cY++;
+ //stop condition was already checked using this.cIterated at the start of the method
+ }
+
+ //using < minX and > maxX instead of <= and >= in vanilla, because minX, maxX are the coordinates
+ //of the box that wasn't extended for oversized blocks yet.
+ final int edgesHit = this.sectionOversizedBlocks ?
+ (x < this.minX || x > this.maxX ? 1 : 0) +
+ (y < this.minY || y > this.maxY ? 1 : 0) +
+ (z < this.minZ || z > this.maxZ ? 1 : 0) : 0;
+
+ if (edgesHit == 3) {
+ continue;
+ }
+
+ final IBlockData state = this.cachedChunkSection.getType(x & 15, y & 15, z & 15);
+
+ if (canInteractWithBlock(state, edgesHit)) {
+ this.pos.setValues(x, y, z);
+ VoxelShape collisionShape = state.getCollisionShape(this.view, this.pos, this.context);
+
+ if (collisionShape != VoxelShapes.empty()) {
+ VoxelShape collidedShape = getCollidedShape(this.box, this.shape, collisionShape, x, y, z);
+ if (collidedShape != null) {
+ return collidedShape;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private VoxelShape getNextEntityCollision() {
+ if (LithiumEntityCollisions.canEntityCollideWithWorldBorder(this.view, this.entity)) {
+ return this.view.getWorldBorder().asVoxelShape();
+ }
+
+ return null;
+ }
+
+ /**
+ * This is an artifact from vanilla which is used to avoid testing shapes in the extended portion of a volume
+ * unless they are a shape which exceeds their voxel. Pistons must be special-cased here.
+ *
+ * @return True if the shape can be interacted with at the given edge boundary
+ */
+ private static boolean canInteractWithBlock(IBlockData state, int edgesHit) {
+ return (edgesHit != 1 || state.shapeExceedsCube()) && (edgesHit != 2 || state.getBlock() == Blocks.MOVING_PISTON);
+ }
+
+ /**
+ * Checks if the {@param entityShape} or {@param entityBox} intersects the given {@param shape} which is translated
+ * to the given position. This is a very specialized implementation which tries to avoid going through VoxelShape
+ * for full-cube shapes.
+ *
+ * @return A {@link VoxelShape} which contains the shape representing that which was collided with, otherwise null
+ */
+ private static VoxelShape getCollidedShape(AxisAlignedBB entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) {
+ if (shape.intersects(entityBox)) {
+ return shape.offset(x, y, z);
+ } else {
+ shape = shape.offset(x, y, z);
+
+ if (VoxelShapes.applyOperation(shape, entityShape, OperatorBoolean.AND)) {
+ return shape;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks the cached information whether the {@param chunkY} section of the {@param chunk} has oversized blocks.
+ *
+ * @return Whether there are any oversized blocks in the chunk section.
+ */
+ private static boolean hasChunkSectionOversizedBlocks(IChunkAccess chunk, int chunkY) {
+ if (OVERSIZED_BLOCK_COUNTING_ENABLED) {
+ ChunkSection section = chunk.getSections()[chunkY];
+ return section != null && ((OversizedBlocksCounter) section).hasOversizedBlocks();
+ }
+ return true; //like vanilla, assume that a chunk section has oversized blocks, when the section mixin isn't loaded
+ }
+
+ public interface OversizedBlocksCounter {
+ boolean hasOversizedBlocks();
+ }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/Producer.java b/src/main/java/me/jellysquid/mods/lithium/common/util/Producer.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3224ea636fef95d368b934ed4b3e9060c4b10a2
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/util/Producer.java
@@ -0,0 +1,55 @@
+package me.jellysquid.mods.lithium.common.util;
+
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import net.yatopia.server.HoldingConsumer;
+
+public interface Producer<T> {
+ /**
+ * Computes the next sequence of values in a collection. If a null value is passed for {@param consumer}, then
+ * the producer will only return whether or not elements existed.
+ *
+ * @param consumer The (nullable) consumer which will accept the computed values during this run.
+ * @return True if the producer produced any values, otherwise false
+ */
+ boolean computeNext(Consumer<? super T> consumer);
+
+ default <U> Producer<U> map(Function<T, U> mapper) {
+ return consumer -> {
+ Consumer<? super T> con = (t) -> consumer.accept(mapper.apply(t));
+ return Producer.this.computeNext(con);
+ };
+ }
+
+ static <T> Stream<T> asStream(Producer<T> producer) {
+ return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) {
+ @Override
+ public boolean tryAdvance(Consumer<? super T> action) {
+ return producer.computeNext(action);
+ }
+ }, false);
+ }
+
+ // WARNING: does not check contains, you have to do that in the add function
+ static <T, R> void fillList(Producer<T> producer, List<R> list, BiConsumer<T, List<R>> addFunction) {
+ HoldingConsumer<T> consumer = new HoldingConsumer<>();
+ while (producer.computeNext(consumer)) {
+ T value = consumer.getValue();
+ if (value == null) { continue; }
+ addFunction.accept(value, list);
+ }
+ }
+
+ Producer<?> EMPTY_PRODUCER = consumer -> false;
+
+ @SuppressWarnings("unchecked")
+ static <T> Producer<T> empty() {
+ return (Producer<T>) EMPTY_PRODUCER;
+ }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/WorldHelper.java b/src/main/java/me/jellysquid/mods/lithium/common/world/WorldHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..f858e5dcda60391fe869264f0c4a20bb14159e82
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/world/WorldHelper.java
@@ -0,0 +1,59 @@
+package me.jellysquid.mods.lithium.common.world;
+
+import com.google.common.collect.Lists;
+import java.util.List;
+import me.jellysquid.mods.lithium.common.entity.EntityClassGroup;
+import me.jellysquid.mods.lithium.common.world.chunk.ClassGroupFilterableList;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntitySlice;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.World;
+
+public class WorldHelper {
+
+ /**
+ * Method that allows getting entities of a class group.
+ * [VanillaCopy] but custom combination of: get class filtered entities together with excluding one entity
+ */
+ public static List<Entity> getEntitiesOfClassGroup(World world, Entity excluded, EntityClassGroup type, AxisAlignedBB box_1, boolean loadChunks) {
+ int int_1 = MathHelper.floor((box_1.minX - 2.0D) / 16.0D);
+ int int_2 = MathHelper.f((box_1.maxX + 2.0D) / 16.0D);
+ int int_3 = MathHelper.floor((box_1.minZ - 2.0D) / 16.0D);
+ int int_4 = MathHelper.f((box_1.maxZ + 2.0D) / 16.0D);
+ List<Entity> list_1 = Lists.newArrayList();
+
+ for (int int_5 = int_1; int_5 < int_2; ++int_5) {
+ for (int int_6 = int_3; int_6 < int_4; ++int_6) {
+ Chunk worldChunk_1 = loadChunks ? world.getChunkAt(int_5, int_6) : world.getChunkIfLoaded(int_5, int_6);
+ if (worldChunk_1 != null) {
+ WorldHelper.getEntitiesOfClassGroup(worldChunk_1, excluded, type, box_1, list_1);
+ }
+ }
+ }
+
+ return list_1;
+ }
+
+ /**
+ * Method that allows getting entities of a class group.
+ * [VanillaCopy] but custom combination of: get class filtered entities together with excluding one entity
+ */
+ public static void getEntitiesOfClassGroup(Chunk worldChunk, Entity excluded, EntityClassGroup type, AxisAlignedBB box_1, List<Entity> list_1) {
+ EntitySlice<Entity>[] entitySections = worldChunk.getAsSlices();
+ int int_1 = MathHelper.floor((box_1.minY - 2.0D) / 16.0D);
+ int int_2 = MathHelper.floor((box_1.maxY + 2.0D) / 16.0D);
+ int_1 = MathHelper.clamp(int_1, 0, entitySections.length - 1);
+ int_2 = MathHelper.clamp(int_2, 0, entitySections.length - 1);
+
+ for (int int_3 = int_1; int_3 <= int_2; ++int_3) {
+ //noinspection rawtypes
+ for (Object entity_1 : ((ClassGroupFilterableList) entitySections[int_3]).getAllOfGroupType(type)) {
+ if (entity_1 != excluded && ((Entity) entity_1).getBoundingBox().intersects(box_1)) {
+ list_1.add((Entity) entity_1);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/ClassGroupFilterableList.java b/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/ClassGroupFilterableList.java
new file mode 100644
index 0000000000000000000000000000000000000000..493103d755c222f2c6031ffb8f5c0b7a2e3fde77
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/ClassGroupFilterableList.java
@@ -0,0 +1,8 @@
+package me.jellysquid.mods.lithium.common.world.chunk;
+
+import java.util.Collection;
+import me.jellysquid.mods.lithium.common.entity.EntityClassGroup;
+
+public interface ClassGroupFilterableList<T> {
+ Collection<T> getAllOfGroupType(EntityClassGroup type);
+}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index f3702ed75ebcd78075da947acf9bdc2ec0379180..cee9bd91d679a867610a88bec5b8180423d52c1e 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -36,7 +36,7 @@ public class Chunk implements IChunkAccess {
public final Map<HeightMap.Type, HeightMap> heightMap;
private final ChunkConverter i;
public final Map<BlockPosition, TileEntity> tileEntities;
- public final List<Entity>[] entitySlices; // Spigot
+ public final EntitySlice<Entity>[] entitySlices; // Spigot // Yatopia - md_5 is dumb
private final Map<StructureGenerator<?>, StructureStart<?>> l;
private final Map<StructureGenerator<?>, LongSet> m;
private final ShortList[] n;
@@ -244,7 +244,7 @@ public class Chunk implements IChunkAccess {
this.l = Maps.newHashMap();
this.m = Maps.newHashMap();
this.n = new ShortList[16];
- this.entitySlices = (List[]) (new List[16]); // Spigot
+ this.entitySlices = new EntitySlice[16]; // Spigot // Yatopia - md_5 is stupid
this.world = (WorldServer) world; // CraftBukkit - type
this.loc = chunkcoordintpair; this.coordinateKey = MCUtil.getCoordinateKey(chunkcoordintpair); // Paper - cache coordinate key
this.i = chunkconverter;
@@ -260,7 +260,7 @@ public class Chunk implements IChunkAccess {
}
for (int l = 0; l < this.entitySlices.length; ++l) {
- this.entitySlices[l] = new org.bukkit.craftbukkit.util.UnsafeList(); // Spigot
+ this.entitySlices[l] = new EntitySlice<>(Entity.class); // Spigot // Yatopia - md_5 is stupid
}
this.d = biomestorage;
@@ -713,8 +713,8 @@ public class Chunk implements IChunkAccess {
k = this.entitySlices.length - 1;
}
// Paper - remove from any old list if its in one
- List<Entity> nextSlice = this.entitySlices[k]; // the next list to be added to
- List<Entity> currentSlice = entity.entitySlice;
+ EntitySlice<Entity> nextSlice = this.entitySlices[k]; // the next list to be added to // Yatopia - paper not stupid, md_5 is
+ EntitySlice<Entity> currentSlice = entity.entitySlice; // Yatopia - paper not stupid, md_5 is
if (nextSlice == currentSlice) {
if (World.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity was already in this chunk!" + entity, new Throwable());
return; // ??? silly plugins
@@ -1028,12 +1028,12 @@ public class Chunk implements IChunkAccess {
j = MathHelper.clamp(j, 0, this.entitySlices.length - 1);
for (int k = i; k <= j; ++k) {
- List<Entity> entityslice = this.entitySlices[k]; // Spigot
- List<Entity> list1 = entityslice; // Spigot
+ EntitySlice<Entity> entityslice = this.entitySlices[k]; // Spigot // Yatopia - md_5 is stupid
+ EntitySlice<Entity> list1 = entityslice; // Spigot // Yatopia - md_5 is stupid
int l = list1.size();
- for (int i1 = 0; i1 < l; ++i1) {
- Entity entity1 = (Entity) list1.get(i1);
+ for (Entity entity1 : list1) { // Yatopia
+ //Entity entity1 = (Entity) list1.get(i1); // Yatopia
if (entity1.shouldBeRemoved) continue; // Paper
if (entity1.getBoundingBox().c(axisalignedbb) && entity1 != entity) {
@@ -1166,8 +1166,16 @@ public class Chunk implements IChunkAccess {
}
public List<Entity>[] getEntitySlices() { // Spigot
- return this.entitySlices;
+ // Yatopia start
+ List<Entity>[] ret = new List[entitySlices.length];
+ for (int i = 0, len = entitySlices.length; i < len; i++) {
+ EntitySlice<Entity> slice = entitySlices[i];
+ ret[i] = slice.toList();
+ }
+ return ret;
}
+ public EntitySlice<Entity>[] getAsSlices() { return entitySlices; }
+ // Yatopia end
@Override
public NBTTagCompound i(BlockPosition blockposition) {
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
index 1bc8b11d999e9af16a44fe1cb62f5785afa82462..30d4589c9cdad11809fc09cb51235d6da8e03195 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -503,8 +503,11 @@ public class ChunkRegionLoader {
chunk.d(false);
- for (int j = 0; j < chunk.getEntitySlices().length; ++j) {
- Iterator iterator1 = chunk.getEntitySlices()[j].iterator();
+ // Yatopia start
+ EntitySlice<Entity>[] entitySlices = chunk.getAsSlices();
+ for (int j = 0; j < entitySlices.length; ++j) {
+ Iterator iterator1 = entitySlices[j].iterator();
+ // Yatopia end
while (iterator1.hasNext()) {
Entity entity = (Entity) iterator1.next();
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index 5beca7e82b3f2a44c376ab2fedac27ce8b7fbd3a..6b96c392fb3eadd20f87675b797337d6d30cc7be 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -73,7 +73,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
}
}
};
- List<Entity> entitySlice = null;
+ EntitySlice<Entity> entitySlice = null; // Yatopia - change to EntitySlice
public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason;
// Paper end
diff --git a/src/main/java/net/minecraft/server/EntitySlice.java b/src/main/java/net/minecraft/server/EntitySlice.java
index 1250c3cbe915815939627701c153ba6254fc05f0..cdf54580a8275f40730f9cbdc1c0c4b4a3999a90 100644
--- a/src/main/java/net/minecraft/server/EntitySlice.java
+++ b/src/main/java/net/minecraft/server/EntitySlice.java
@@ -14,18 +14,35 @@ import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class EntitySlice<T> extends AbstractCollection<T> {
+// Yatopia start
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap;
+import me.jellysquid.mods.lithium.common.entity.EntityClassGroup;
+// Yatopia end
+
+public class EntitySlice<T> extends AbstractCollection<T> implements me.jellysquid.mods.lithium.common.world.chunk.ClassGroupFilterableList<T> { // Yatopia
private final Map<Class<?>, List<T>> a = Maps.newHashMap();
private final Class<T> b;
- private final List<T> c = Lists.newArrayList();
+ private final List<T> c = new net.yatopia.server.list.GlueList<>(); // Yatopia
+
+ private final Reference2ReferenceArrayMap<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> entitiesByGroup; // Yatopia
public EntitySlice(Class<T> oclass) {
this.b = oclass;
this.a.put(oclass, this.c);
+ this.entitiesByGroup = new Reference2ReferenceArrayMap<>(); // Yatopia
}
public boolean add(T t0) {
+ // Yatopia start
+ for (Map.Entry<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> entityGroupAndSet : this.entitiesByGroup.entrySet()) {
+ EntityClassGroup entityGroup = entityGroupAndSet.getKey();
+ if (entityGroup.contains(((Entity)t0).getClass())) {
+ entityGroupAndSet.getValue().add((t0));
+ }
+ }
+ // Yatopia end
boolean flag = false;
Iterator iterator = this.a.entrySet().iterator();
@@ -41,6 +58,11 @@ public class EntitySlice<T> extends AbstractCollection<T> {
}
public boolean remove(Object object) {
+ // Yatopia start
+ for (Map.Entry<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> entityGroupAndSet : this.entitiesByGroup.entrySet()) {
+ entityGroupAndSet.getValue().remove(object);
+ }
+ // Yatopia end
boolean flag = false;
Iterator iterator = this.a.entrySet().iterator();
@@ -61,10 +83,13 @@ public class EntitySlice<T> extends AbstractCollection<T> {
return this.a(object.getClass()).contains(object);
}
+ @SuppressWarnings("unchecked") // Yatopia
public <S> Collection<S> a(Class<S> oclass) {
if (!this.b.isAssignableFrom(oclass)) {
throw new IllegalArgumentException("Don't know how to search for " + oclass);
} else {
+ // Yatopia start - replace this
+ /*
List<T> list = (List) this.a.computeIfAbsent(oclass, (oclass1) -> {
Stream stream = this.c.stream();
@@ -73,6 +98,18 @@ public class EntitySlice<T> extends AbstractCollection<T> {
});
return Collections.unmodifiableCollection(list);
+ */
+ if (a.containsKey(oclass)) {
+ return (Collection<S>) Collections.unmodifiableCollection(a.get(oclass));
+ }
+ List<T> list = new net.yatopia.server.list.GlueList<>();
+ for (T allElement : this.c) {
+ if (oclass.isInstance(allElement)) {
+ list.add(allElement);
+ }
+ }
+ a.put(oclass, list);
+ return (Collection<S>) Collections.unmodifiableCollection(list);
}
}
@@ -81,10 +118,44 @@ public class EntitySlice<T> extends AbstractCollection<T> {
}
public List<T> a() {
- return ImmutableList.copyOf(this.c);
+ return Collections.unmodifiableList(this.c); // Yatopia - avoid copying
}
public int size() {
return this.c.size();
}
+
+ // Yatopia start
+ @Override
+ public Collection<T> getAllOfGroupType(EntityClassGroup type) {
+ Collection<T> collection = this.entitiesByGroup.get(type);
+
+ if (collection == null) {
+ collection = this.createAllOfGroupType(type);
+ }
+
+ return Collections.unmodifiableCollection(collection);
+ }
+
+ private Collection<T> createAllOfGroupType(EntityClassGroup type) {
+ ReferenceLinkedOpenHashSet<T> allOfType = new ReferenceLinkedOpenHashSet<>();
+
+ for (T entity : this.c) {
+ if (type.contains(entity.getClass())) {
+ allOfType.add(entity);
+ }
+ }
+ this.entitiesByGroup.put(type, allOfType);
+ return allOfType;
+ }
+
+ /**
+ * A workaround about md_5's dumb changes
+ *
+ * @return list representation of EntitySlice
+ */
+ public List<T> toList() {
+ return c;
+ }
+ // Yatopia end
}
diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java
index b66c802d5e27518069bf42e577bcc9a26c4d873e..22728e5ba8b2dd6efc3164d06ea791693de50936 100644
--- a/src/main/java/net/minecraft/server/ICollisionAccess.java
+++ b/src/main/java/net/minecraft/server/ICollisionAccess.java
@@ -52,7 +52,14 @@ public interface ICollisionAccess extends IBlockAccess {
default boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
// Tuinity end - allow overriding in WorldServer
try { if (entity != null) entity.collisionLoadChunks = true; // Paper
- return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty);
+ // Yatopia start
+ //return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty);
+ boolean ret = !me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.doesBoxCollideWithBlocks(this, entity, axisalignedbb);
+ if (ret && this instanceof IEntityAccess) {
+ ret = !me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.doesBoxCollideWithEntities((IEntityAccess) this, entity, axisalignedbb, predicate);
+ }
+ return ret;
+ // Yatopia end
} finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
}
@@ -63,7 +70,7 @@ public interface ICollisionAccess extends IBlockAccess {
}
default Stream<VoxelShape> b(@Nullable Entity entity, AxisAlignedBB axisalignedbb) {
- return StreamSupport.stream(new VoxelShapeSpliterator(this, entity, axisalignedbb), false);
+ return me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.getBlockCollisions(this, entity, axisalignedbb); // Yatopia
}
default Stream<VoxelShape> b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, BiPredicate<IBlockData, BlockPosition> bipredicate) {
diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java
index 40ca3364d462dc7b5a4c1f8f353c758a421656bf..7657d13dd7556e4c32088bbe5735df525c0b2922 100644
--- a/src/main/java/net/minecraft/server/IEntityAccess.java
+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
@@ -62,6 +62,10 @@ public interface IEntityAccess {
// Tuinity end - optimise hard collision
default Stream<VoxelShape> c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+ // Yatopia start - replace this
+ if (predicate == null) predicate = (e) -> true; // Tuinity - allow nullable
+ return me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.getEntityCollisions(this, entity, axisalignedbb, predicate);
+ /*
if (axisalignedbb.a() < 1.0E-7D) {
return Stream.empty();
} else {
@@ -91,6 +95,7 @@ public interface IEntityAccess {
return flag;
}); return ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb1, predicate) : this.getHardCollidingEntities(entity, axisalignedbb1, predicate)).stream().map(Entity::getBoundingBox).map(VoxelShapes::a); // Tuinity - optimise entity hard collisions
}
+ */ // Yatopia end
}
default EntityHuman findNearbyPlayer(Entity entity, double d0, @Nullable Predicate<Entity> predicate) { return this.findNearbyPlayer(entity.locX(), entity.locY(), entity.locZ(), d0, predicate); } // Paper
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 2c7ee012a7ae9ee8c269bb469282202f2a846ebf..51a19bce92c801912030699def8534213d77d310 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -1363,11 +1363,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
chunk.setLoaded(true);
this.world.a(chunk.getTileEntities().values());
List<Entity> list = null;
- List<Entity>[] aentityslice = chunk.getEntitySlices(); // Spigot
+ EntitySlice<Entity>[] aentityslice = chunk.getAsSlices(); // Spigot // Yatopia - md_5 is stupid
int i = aentityslice.length;
for (int j = 0; j < i; ++j) {
- List<Entity> entityslice = aentityslice[j]; // Spigot
+ EntitySlice<Entity> entityslice = aentityslice[j]; // Spigot // Yatopia - md_5 is stupid
Iterator iterator = entityslice.iterator();
while (iterator.hasNext()) {
@@ -1648,7 +1648,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
// CraftBukkit - decompile error
csvwriter.a(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(IChunkAccess::getChunkStatus).orElse(null), optional1.map(Chunk::getState).orElse(null), a(playerchunk.c()), a(playerchunk.a()), a(playerchunk.b()), this.chunkDistanceManager.c(entry.getLongKey()), !this.isOutsideOfRange(chunkcoordintpair), optional1.map((chunk) -> {
- return Stream.of(chunk.getEntitySlices()).mapToInt(List::size).sum(); // Spigot
+ return Stream.of(chunk.getAsSlices()).mapToInt(EntitySlice::size).sum(); // Spigot // Yatopia
}).orElse(0), optional1.map((chunk) -> {
return chunk.getTileEntities().size();
}).orElse(0));
diff --git a/src/main/java/net/minecraft/server/VoxelShape.java b/src/main/java/net/minecraft/server/VoxelShape.java
index 700660dd93b3090334bb3033d5f5fdd6ab684744..98f9ded42264cb88cff701a85d22d99e203ef7cf 100644
--- a/src/main/java/net/minecraft/server/VoxelShape.java
+++ b/src/main/java/net/minecraft/server/VoxelShape.java
@@ -78,7 +78,7 @@ public abstract class VoxelShape {
public final List<AxisAlignedBB> getBoundingBoxesRepresentation() { return this.d(); } // Tuinity - OBFHELPER
public List<AxisAlignedBB> d() {
- List<AxisAlignedBB> list = Lists.newArrayList();
+ List<AxisAlignedBB> list = new net.yatopia.server.list.GlueList<>(); // Yatopia
this.b((d0, d1, d2, d3, d4, d5) -> {
list.add(new AxisAlignedBB(d0, d1, d2, d3, d4, d5));
diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java
index db735e29d427cc8f4bd4ba54c7a44daf9fed9e61..877ef81bd8285167ce11c116b8ad803fda63c6c9 100644
--- a/src/main/java/net/minecraft/server/VoxelShapes.java
+++ b/src/main/java/net/minecraft/server/VoxelShapes.java
@@ -40,7 +40,9 @@ public final class VoxelShapes {
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 (!list.contains(shapeCasted.aabb)) { // Yatopia start - make sure it doesn't contain already
list.add(shapeCasted.aabb);
+ } // Yatopia end
}
} else if (shape instanceof VoxelShapeArray) {
VoxelShapeArray shapeCasted = (VoxelShapeArray)shape;
@@ -54,14 +56,19 @@ public final class VoxelShapes {
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));
+ // Yatopia start - make sure it doesn't contain already
+ AxisAlignedBB addedBox = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ, false);
+ if (!list.contains(addedBox)) {
+ list.add(addedBox);
+ }
+ // Yatopia end
}
}
} else {
java.util.List<AxisAlignedBB> boxes = shape.getBoundingBoxesRepresentation();
for (int i = 0, len = boxes.size(); i < len; ++i) {
AxisAlignedBB box = boxes.get(i);
- if (box.voxelShapeIntersect(aabb)) {
+ if (box.voxelShapeIntersect(aabb) && !list.contains(box)) { // Yatopia - make sure it doesn't contain already
list.add(box);
}
}
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 77cdfae804f9a3a5452dc70adc7da09d53f51998..e643043398756971571a6cba8237b74385ded898 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -787,8 +787,14 @@ public class WorldServer extends World implements GeneratorAccessSeed {
}
public final void getCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<AxisAlignedBB> list, boolean loadChunks) {
+ // Yatopia start - jellysquid is better
+ /*
this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, list, loadChunks);
this.getEntityHardCollisions(entity, axisalignedbb, null, list);
+ */
+ me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.fillEntityCollisionsList(this, entity, list, axisalignedbb, loadChunks);
+ me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.fillBlockCollisionsList(this, list, entity, axisalignedbb);
+ // Yatopia end
}
@Override
@@ -1871,12 +1877,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
}
// Spigot End
this.tileEntityListUnload.addAll(chunk.getTileEntities().values());
- List[] aentityslice = chunk.getEntitySlices(); // Spigot
+ EntitySlice<Entity>[] aentityslice = chunk.getAsSlices(); // Spigot // Yatopia - md_5 is stupid
int i = aentityslice.length;
java.util.List<Entity> toMoveChunks = new java.util.ArrayList<>(); // Paper
for (int j = 0; j < i; ++j) {
- List<Entity> entityslice = aentityslice[j]; // Spigot
+ EntitySlice<Entity> entityslice = aentityslice[j]; // Spigot // Yatopia - md_5 is stupid
Iterator iterator = entityslice.iterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/net/yatopia/server/HoldingConsumer.java b/src/main/java/net/yatopia/server/HoldingConsumer.java
new file mode 100644
index 0000000000000000000000000000000000000000..44f39064fa52b8249887f83494b71699d653c4f8
--- /dev/null
+++ b/src/main/java/net/yatopia/server/HoldingConsumer.java
@@ -0,0 +1,17 @@
+package net.yatopia.server;
+
+import java.util.function.Consumer;
+
+public final class HoldingConsumer<T> implements Consumer<T> {
+
+ private T value;
+
+ @Override
+ public void accept(T t) {
+ this.value = t;
+ }
+
+ public T getValue() {
+ return value;
+ }
+}