public class CustomZombie extends Zombie { private final ZombiesPlugin main; public CustomZombie(Location location, ZombiesPlugin main) { super(EntityType.ZOMBIE, getWorld(location.getWorld())); this.main = main; setPos(location.getX(), location.getY(), location.getZ()); getBukkitMob().getAttribute(Attribute.GENERIC_FOLLOW_RANGE).setBaseValue(400); level.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM); } @Override public void playSound(SoundEvent sound, float volume, float pitch) { Game game = main.getGameManager().getGameFromMob(getBukkitMob()); if (game == null) { super.playSound(sound, volume, pitch); } else { game.playSound(getBukkitMob().getLocation(), sound.getLocation().toString(), volume, pitch); } } @Override public boolean isSunSensitive() { return false; } @Override public boolean isSunBurnTick() { return false; } public static boolean getCollisions(ZombiesPlugin main, final CollisionGetter view, final Entity entity, final AABB aabb, final List into, final boolean loadChunks, final boolean collidesWithUnloadedChunks, final boolean checkBorder, final boolean checkOnly, final BiPredicate blockPredicate, final Predicate entityPredicate) { if (checkOnly) { return getCollisionsForBlocksOrWorldBorder(main, view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); } else { return getCollisionsForBlocksOrWorldBorder(main, view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); } } public static boolean getCollisionsForBlocksOrWorldBorder(ZombiesPlugin main, final CollisionGetter getter, final Entity entity, final AABB aabb, final List into, final boolean loadChunks, final boolean collidesWithUnloaded, final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) { boolean ret = false; if (checkBorder) { if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) { if (checkOnly) { return true; } else { CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into); ret = true; } } } final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; final int minSection = WorldUtil.getMinSection(getter); final int maxSection = WorldUtil.getMaxSection(getter); final int minBlock = minSection << 4; final int maxBlock = (maxSection << 4) | 15; final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); CollisionContext collisionShape = null; // special cases: if (minBlockY > maxBlock || maxBlockY < minBlock) { // no point in checking return ret; } final int minYIterate = Math.max(minBlock, minBlockY); final int maxYIterate = Math.min(maxBlock, maxBlockY); final int minChunkX = minBlockX >> 4; final int maxChunkX = maxBlockX >> 4; final int minChunkY = minBlockY >> 4; final int maxChunkY = maxBlockY >> 4; final int minChunkYIterate = minYIterate >> 4; final int maxChunkYIterate = maxYIterate >> 4; final int minChunkZ = minBlockZ >> 4; final int maxChunkZ = maxBlockZ >> 4; final ServerChunkCache chunkProvider; if (getter instanceof WorldGenRegion) { chunkProvider = null; } else if (getter instanceof ServerLevel) { chunkProvider = ((ServerLevel) getter).getChunkSource(); } else { chunkProvider = null; } for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk final int chunkXGlobalPos = currChunkX << 4; final int chunkZGlobalPos = currChunkZ << 4; final ChunkAccess chunk; if (chunkProvider == null) { chunk = (ChunkAccess) getter.getChunkForCollisions(currChunkX, currChunkZ); } else { chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); } if (chunk == null) { if (collidesWithUnloaded) { if (checkOnly) { return true; } else { into.add(getBoxForChunk(currChunkX, currChunkZ)); ret = true; } } continue; } final LevelChunkSection[] sections = chunk.getSections(); // bound y for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) { final LevelChunkSection section = sections[currChunkY - minSection]; if (section == null || section.hasOnlyAir()) { // empty continue; } final PalettedContainer blocks = section.states; final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk final int chunkYGlobalPos = currChunkY << 4; final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks(); final int minXIterate; final int maxXIterate; final int minZIterate; final int maxZIterate; final int minYIterateLocal; final int maxYIterateLocal; if (!sectionHasSpecial) { minXIterate = currChunkX == minChunkX ? minX + 1 : minX; maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX; minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ; maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ; minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY; maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY; if (minXIterate > maxXIterate || minZIterate > maxZIterate) { continue; } } else { minXIterate = minX; maxXIterate = maxX; minZIterate = minZ; maxZIterate = maxZ; minYIterateLocal = minY; maxYIterateLocal = maxY; } for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) { long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15); for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ, collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) { // From getKnownBlockInfoHorizontalRaw: // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1 // the even localZ is the lower 32 bits, the odd is the upper 32 bits // We want to use a bitset to only iterate over non-empty blocks. // We need to build a bitset mask to and out the other collisions we just don't care at all about // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis // It's important to note that the iterate values can be outside [0, 15], but if they are, // then none of the x or z loops would meet their conditions. So we can assume they are never // out of bounds here final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32 long bitset = (1L << xAxisBits) - 1; // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd) int shift = (currZ & 1) << 5; // this will be a LEFT shift // Now we need to offset shift so that the bitset first position is at minXIterate shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ... // all done bitset = bitset << shift; /* -- ZOMBIES if ((collisionForHorizontal & bitset) == 0L) { if(debugged) { System.out.println("EMPTY"); continue; } debugged = true; System.out.println("EMPTY BLOCK"); System.out.println("currY: " + currY); System.out.println("currZ: " + currZ); System.out.println("minX: " + minX); System.out.println("maxX: " + maxX); System.out.println("minY: " + minY); System.out.println("maxY: " + maxY); System.out.println("minZ: " + minZ); System.out.println("maxZ: " + maxZ); System.out.println("minChunkX: " + minChunkX); System.out.println("maxChunkX: " + maxChunkX); System.out.println("minChunkZ: " + minChunkZ); System.out.println("maxChunkZ: " + maxChunkZ); System.out.println("minChunkY: " + minChunkY); System.out.println("maxChunkY: " + maxChunkY); System.out.println("minSection: " + minSection); System.out.println("maxSection: " + maxSection); System.out.println("minXIterate: " + minXIterate); System.out.println("maxXIterate: " + maxXIterate); System.out.println("minZIterate: " + minZIterate); System.out.println("maxZIterate: " + maxZIterate); System.out.println("minYIterateLocal: " + minYIterateLocal); System.out.println("maxYIterateLocal: " + maxYIterateLocal); System.out.println("currChunkX: " + currChunkX); System.out.println("currChunkZ: " + currChunkZ); System.out.println("currChunkY: " + currChunkY); System.out.println("sectionHasSpecial: " + sectionHasSpecial); System.out.println("collisionForHorizontal: " + collisionForHorizontal); System.out.println("bitset: " + bitset); System.out.println("shift: " + shift); System.out.println(); System.out.println("-----------------------------------------------------"); continue; } */ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8); int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal); if (blockInfo == KNOWN_EMPTY_BLOCK) { // -- ZOMBIES START double blockX = currX | chunkXGlobalPos; double blockY = currY | chunkYGlobalPos; double blockZ = currZ | chunkZGlobalPos; Vector blockPos = new Vector(blockX, blockY, blockZ); Location blockLoc = new Location(entity.getBukkitEntity().getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ()); Game game = main.getGameManager().getGameFromMob(entity.getBukkitEntity()); if (game == null) { continue; } GameMapData mapData = game.getGameMapData(); GameDoor door = mapData.getDoor(blockLoc); if (door == null) { continue; } if (mapData.getOpenDoors().contains(door)) { continue; } blockInfo = (int) KNOWN_FULL_BLOCK; // -- ZOMBIES END } switch (blockInfo) { case (int) CollisionUtil.KNOWN_EMPTY_BLOCK -> { continue; } case (int) CollisionUtil.KNOWN_FULL_BLOCK -> { double blockX = currX | chunkXGlobalPos; double blockY = currY | chunkYGlobalPos; double blockZ = currZ | chunkZGlobalPos; final AABB blockBox = new AABB( blockX, blockY, blockZ, blockX + 1.0, blockY + 1.0, blockZ + 1.0, true ); if (predicate != null) { if (!CollisionUtil.voxelShapeIntersect(aabb, blockBox)) { continue; } // fall through to get the block for the predicate } else { if (CollisionUtil.voxelShapeIntersect(aabb, blockBox)) { if (checkOnly) { return true; } else { into.add(blockBox); ret = true; } } continue; } } // default: fall through to standard logic } int blockX = currX | chunkXGlobalPos; int blockY = currY | chunkYGlobalPos; int blockZ = currZ | chunkZGlobalPos; int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); if (edgeCount == 3) { continue; } BlockState blockData = blocks.get(localBlockIndex); if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { mutablePos.set(blockX, blockY, blockZ); if (collisionShape == null) { collisionShape = new CollisionUtil.LazyEntityCollisionContext(entity); } VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape); if (voxelshape2 != Shapes.empty()) { VoxelShape voxelshape3 = voxelshape2.move(blockX, blockY, blockZ); if (predicate != null && !predicate.test(blockData, mutablePos)) { continue; } if (checkOnly) { if (voxelshape3.intersects(aabb)) { return true; } } else { ret |= CollisionUtil.addBoxesToIfIntersects(voxelshape3, aabb, into); } } } } } } } } } return ret; } @Override public void move(MoverType movementType, Vec3 movement) { // Paper start - detailed watchdog information io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); // Paper end - detailed watchdog information if (this.noPhysics) { this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); } else { this.wasOnFire = this.isOnFire(); if (movementType == MoverType.PISTON) { this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper movement = this.limitPistonMovement(movement); if (movement.equals(Vec3.ZERO)) { return; } } this.level.getProfiler().push("move"); if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { movement = movement.multiply(this.stuckSpeedMultiplier); this.stuckSpeedMultiplier = Vec3.ZERO; this.setDeltaMovement(Vec3.ZERO); } // Paper start - ignore movement changes while inactive. if (isTemporarilyActive && movement == getDeltaMovement() && movementType == MoverType.SELF) { setDeltaMovement(Vec3.ZERO); this.level.getProfiler().pop(); return; } // Paper end movement = this.maybeBackOffFromEdge(movement, movementType); Vec3 vec3d1 = collide(movement); if (vec3d1.lengthSqr() > 1.0E-7D) { this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z); } this.level.getProfiler().pop(); this.level.getProfiler().push("rest"); this.horizontalCollision = !Mth.equal(movement.x, vec3d1.x) || !Mth.equal(movement.z, vec3d1.z); this.verticalCollision = movement.y != vec3d1.y; if (this.horizontalCollision) { this.minorHorizontalCollision = this.isHorizontalCollisionMinor(vec3d1); } else { this.minorHorizontalCollision = false; } this.onGround = this.verticalCollision && movement.y < 0.0D; BlockPos blockposition = this.getOnPos(); BlockState iblockdata = this.level.getBlockState(blockposition); this.checkFallDamage(vec3d1.y, this.onGround, iblockdata, blockposition); if (this.isRemoved()) { this.level.getProfiler().pop(); } else { Vec3 vec3d2 = this.getDeltaMovement(); if (movement.x != vec3d1.x) { this.setDeltaMovement(0.0D, vec3d2.y, vec3d2.z); } if (movement.z != vec3d1.z) { this.setDeltaMovement(vec3d2.x, vec3d2.y, 0.0D); } Block block = iblockdata.getBlock(); if (movement.y != vec3d1.y) { block.updateEntityAfterFallOn(this.level, this); } if (this.onGround && !this.isSteppingCarefully()) { block.stepOn(this.level, blockposition, iblockdata, this); } Entity.MovementEmission entity_movementemission = this.getMovementEmission(); if (entity_movementemission.emitsAnything() && !this.isPassenger()) { double d0 = vec3d1.x; double d1 = vec3d1.y; double d2 = vec3d1.z; this.flyDist = (float) ((double) this.flyDist + vec3d1.length() * 0.6D); if (!iblockdata.is(BlockTags.CLIMBABLE) && !iblockdata.is(Blocks.POWDER_SNOW)) { d1 = 0.0D; } this.walkDist += (float) vec3d1.horizontalDistance() * 0.6F; this.moveDist += (float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 0.6F; } this.tryCheckInsideBlocks(); float f2 = this.getBlockSpeedFactor(); this.setDeltaMovement(this.getDeltaMovement().multiply(f2, 1.0D, f2)); // Paper start - remove expensive streams from here this.level.getProfiler().pop(); } } // Paper start - detailed watchdog information // Paper end - detailed watchdog information } @Override protected void moveTowardsClosestSpace(double x, double y, double z) { BlockPos blockposition = new BlockPos(x, y, z); Vec3 vec3d = new Vec3(x - (double) blockposition.getX(), y - (double) blockposition.getY(), z - (double) blockposition.getZ()); BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); Direction enumdirection = Direction.UP; double d3 = Double.MAX_VALUE; Direction[] aenumdirection = new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, Direction.UP}; // --- SECTION START | ZOMBIES --- org.bukkit.entity.Zombie bukkitZombie = (org.bukkit.entity.Zombie) this.getBukkitEntity(); Game game = main.getGameManager().getGameFromMob(bukkitZombie); // --- SECTION END | ZOMBIES --- for (Direction enumdirection1 : aenumdirection) { blockposition_mutableblockposition.setWithOffset(blockposition, enumdirection1); // --- SECTION START | ZOMBIES --- Location location = new Location(bukkitZombie.getWorld(), blockposition_mutableblockposition.getX(), blockposition_mutableblockposition.getY(), blockposition_mutableblockposition.getZ()); boolean isDoor = game != null && game.getGameMapData().getDoor(location) != null; // --- SECTION END | ZOMBIES --- if (!isDoor /* Zombies */ && !this.level.getBlockState(blockposition_mutableblockposition).isCollisionShapeFullBlock(this.level, blockposition_mutableblockposition)) { double d4 = vec3d.get(enumdirection1.getAxis()); double d5 = enumdirection1.getAxisDirection() == Direction.AxisDirection.POSITIVE ? 1.0D - d4 : d4; if (d5 < d3) { d3 = d5; enumdirection = enumdirection1; } } } float f = this.random.nextFloat() * 0.2F + 0.1F; float f1 = (float) enumdirection.getAxisDirection().getStep(); Vec3 vec3d1 = this.getDeltaMovement().scale(0.75D); if (enumdirection.getAxis() == Direction.Axis.X) { this.setDeltaMovement(f1 * f, vec3d1.y, vec3d1.z); } else if (enumdirection.getAxis() == Direction.Axis.Y) { this.setDeltaMovement(vec3d1.x, f1 * f, vec3d1.z); } else if (enumdirection.getAxis() == Direction.Axis.Z) { this.setDeltaMovement(vec3d1.x, vec3d1.y, f1 * f); } } private Vec3 collide(Vec3 movement) { // Paper start - optimise collisions // This is a copy of vanilla's except that it uses strictly AABB math if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) { return movement; } final Level world = this.level; final AABB currBoundingBox = this.getBoundingBox(); if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) { return movement; } final List potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList(); try { final double stepHeight = this.maxUpStep; final AABB collisionBox; if (movement.x == 0.0 && movement.z == 0.0 && movement.y != 0.0) { if (movement.y > 0.0) { collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y); } else { collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y); } } else { if (stepHeight > 0.0 && (this.onGround || (movement.y < 0.0)) && (movement.x != 0.0 || movement.z != 0.0)) { // don't bother getting the collisions if we don't need them. if (movement.y <= 0.0) { collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); } else { collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); } } else { collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); } } getCollisions(main, world, this, collisionBox, potentialCollisions, false, true, false, false, null, null); if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) { io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions); } final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisions); if (stepHeight > 0.0 && (this.onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisions); final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisions); if (vec3d3.y < stepHeight) { final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3); if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { vec3d2 = vec3d4; } } if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions)); } } return limitedMoveVector; } finally { io.papermc.paper.util.CachedLists.returnTempCollisionList(potentialCollisions); } // Paper end - optimise collisions } private static ServerLevel getWorld(org.bukkit.World bukkitWorld) { return ((CraftWorld) bukkitWorld).getHandle(); } }