From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: JellySquid 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 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, 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 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, Object> classGroup; //value unused, no set variant available + private final Function, Boolean> classFitEvaluator; + + public EntityClassGroup(Function, 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> 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 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 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 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 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 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 getEntityCollisions(IEntityAccess view, Entity entity, AxisAlignedBB box, Predicate 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 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 getEntityCollisionProducer(IEntityAccess view, Entity entity, AxisAlignedBB box, Predicate predicate, boolean loadChunks) { + return new Producer() { + private Iterator it; + + @Override + public boolean computeNext(Consumer 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 { + /** + * 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 consumer); + + default Producer map(Function mapper) { + return consumer -> { + Consumer con = (t) -> consumer.accept(mapper.apply(t)); + return Producer.this.computeNext(con); + }; + } + + static Stream asStream(Producer producer) { + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) { + @Override + public boolean tryAdvance(Consumer action) { + return producer.computeNext(action); + } + }, false); + } + + // WARNING: does not check contains, you have to do that in the add function + static void fillList(Producer producer, List list, BiConsumer> addFunction) { + HoldingConsumer 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 Producer empty() { + return (Producer) 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 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 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 list_1) { + EntitySlice[] 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 { + Collection 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; private final ChunkConverter i; public final Map tileEntities; - public final List[] entitySlices; // Spigot + public final EntitySlice[] entitySlices; // Spigot // Yatopia - md_5 is dumb private final Map, StructureStart> l; private final Map, 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 nextSlice = this.entitySlices[k]; // the next list to be added to - List currentSlice = entity.entitySlice; + EntitySlice nextSlice = this.entitySlices[k]; // the next list to be added to // Yatopia - paper not stupid, md_5 is + EntitySlice 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 entityslice = this.entitySlices[k]; // Spigot - List list1 = entityslice; // Spigot + EntitySlice entityslice = this.entitySlices[k]; // Spigot // Yatopia - md_5 is stupid + EntitySlice 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[] getEntitySlices() { // Spigot - return this.entitySlices; + // Yatopia start + List[] ret = new List[entitySlices.length]; + for (int i = 0, len = entitySlices.length; i < len; i++) { + EntitySlice slice = entitySlices[i]; + ret[i] = slice.toList(); + } + return ret; } + public EntitySlice[] 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[] 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 entitySlice = null; + EntitySlice 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 extends AbstractCollection { +// 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 extends AbstractCollection implements me.jellysquid.mods.lithium.common.world.chunk.ClassGroupFilterableList { // Yatopia private final Map, List> a = Maps.newHashMap(); private final Class b; - private final List c = Lists.newArrayList(); + private final List c = new net.yatopia.server.list.GlueList<>(); // Yatopia + + private final Reference2ReferenceArrayMap> entitiesByGroup; // Yatopia public EntitySlice(Class 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> 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 extends AbstractCollection { } public boolean remove(Object object) { + // Yatopia start + for (Map.Entry> 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 extends AbstractCollection { return this.a(object.getClass()).contains(object); } + @SuppressWarnings("unchecked") // Yatopia public Collection a(Class oclass) { if (!this.b.isAssignableFrom(oclass)) { throw new IllegalArgumentException("Don't know how to search for " + oclass); } else { + // Yatopia start - replace this + /* List list = (List) this.a.computeIfAbsent(oclass, (oclass1) -> { Stream stream = this.c.stream(); @@ -73,6 +98,18 @@ public class EntitySlice extends AbstractCollection { }); return Collections.unmodifiableCollection(list); + */ + if (a.containsKey(oclass)) { + return (Collection) Collections.unmodifiableCollection(a.get(oclass)); + } + List 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) Collections.unmodifiableCollection(list); } } @@ -81,10 +118,44 @@ public class EntitySlice extends AbstractCollection { } public List 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 getAllOfGroupType(EntityClassGroup type) { + Collection collection = this.entitiesByGroup.get(type); + + if (collection == null) { + collection = this.createAllOfGroupType(type); + } + + return Collections.unmodifiableCollection(collection); + } + + private Collection createAllOfGroupType(EntityClassGroup type) { + ReferenceLinkedOpenHashSet 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 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 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 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 b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, BiPredicate 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 c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate 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 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 list = null; - List[] aentityslice = chunk.getEntitySlices(); // Spigot + EntitySlice[] aentityslice = chunk.getAsSlices(); // Spigot // Yatopia - md_5 is stupid int i = aentityslice.length; for (int j = 0; j < i; ++j) { - List entityslice = aentityslice[j]; // Spigot + EntitySlice 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 getBoundingBoxesRepresentation() { return this.d(); } // Tuinity - OBFHELPER public List d() { - List list = Lists.newArrayList(); + List 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 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 219a33195e5309a1bac279154c9be0ed7dbf2563..bc46696d7e397684025affcc7b59028596df7f5e 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 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[] aentityslice = chunk.getAsSlices(); // Spigot // Yatopia - md_5 is stupid int i = aentityslice.length; java.util.List toMoveChunks = new java.util.ArrayList<>(); // Paper for (int j = 0; j < i; ++j) { - List entityslice = aentityslice[j]; // Spigot + EntitySlice 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 implements Consumer { + + private T value; + + @Override + public void accept(T t) { + this.value = t; + } + + public T getValue() { + return value; + } +}