mirror of
https://github.com/YatopiaMC/Yatopia.git
synced 2024-08-23 14:22:47 +02:00
1128 lines
51 KiB
Diff
1128 lines
51 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..b400a5870b64b9eb92b6a8311793f5c8ea8df9af
|
|
--- /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 ArrayList<>();
|
|
+
|
|
+ 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 ac6e5e3309affc830d4db07fd9b8d809c3085033..b7fcbef8d38c9406a891d64f8016bc16572f00d9 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;
|
|
@@ -149,7 +149,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;
|
|
@@ -165,7 +165,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;
|
|
@@ -615,8 +615,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
|
|
@@ -930,12 +930,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) {
|
|
@@ -1067,8 +1067,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/Entity.java b/src/main/java/net/minecraft/server/Entity.java
|
|
index 798ce0029c980d340d74dff53d702ef3cd00e33e..eec6dbda1b5a34122a75f11dfa82506f9763779c 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..e0cbcc0a15f8089c29957badc20b0a786831c64b 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.Reference2ReferenceOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
|
+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 Reference2ReferenceOpenHashMap<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> entitiesByGroup; // Yatopia
|
|
+
|
|
public EntitySlice(Class<T> oclass) {
|
|
this.b = oclass;
|
|
this.a.put(oclass, this.c);
|
|
+ this.entitiesByGroup = new Reference2ReferenceOpenHashMap<>(); // 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();
|
|
|
|
@@ -65,11 +87,16 @@ public class EntitySlice<T> extends AbstractCollection<T> {
|
|
if (!this.b.isAssignableFrom(oclass)) {
|
|
throw new IllegalArgumentException("Don't know how to search for " + oclass);
|
|
} else {
|
|
- List<T> list = (List) this.a.computeIfAbsent(oclass, (oclass1) -> {
|
|
- Stream stream = this.c.stream();
|
|
-
|
|
- oclass1.getClass();
|
|
- return (List) stream.filter(oclass1::isInstance).collect(Collectors.toList());
|
|
+ List<S> list = (List<S>) this.a.computeIfAbsent(oclass, (oclass1) -> { // Yatopia - decompile fix
|
|
+ // Yatopia start - how about we nuke stream?
|
|
+ List<T> ret = Lists.newArrayList();
|
|
+ for (T t : c) {
|
|
+ if (oclass1.isInstance(t)) {
|
|
+ ret.add(t);
|
|
+ }
|
|
+ }
|
|
+ return ret;
|
|
+ // Yatopia end
|
|
});
|
|
|
|
return Collections.unmodifiableCollection(list);
|
|
@@ -87,4 +114,38 @@ public class EntitySlice<T> extends AbstractCollection<T> {
|
|
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 882b82d8952d34f6e3c639404d1a1521dedf1bb0..ccf1416000354b78ccef78b072062ce081826e1a 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 c3eb85c71539ebdb1b6c9a386e4de9ba717f030e..9f32a26fdbfaf024cfe5c0996c2253f2dd581d5e 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -1343,11 +1343,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()) {
|
|
@@ -1640,7 +1640,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) -> {
|
|
int sum = 0;
|
|
- for (List<Entity> entities : chunk.getEntitySlices()) {
|
|
+ for (EntitySlice<Entity> entities : chunk.getAsSlices()) { // Yatopia - this was caused due to md_5 being stupid
|
|
int size = entities.size();
|
|
sum += size;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
index f9a1f5e92a7559a50dfb72d7455a8fc03dbad25f..3094ce00b3fa5b266f5d0ad0875f160e80c62e18 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;
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index e86d439a7a340e31071c5b2f6531abd1537db06d..00930eeea75758645b173f4bc2fbf635e1d426ba 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -649,8 +649,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
|
|
@@ -1743,12 +1749,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;
|
|
+ }
|
|
+}
|