From 7dbdd69077f57fff8c5f7a12067f1606aa074c77 Mon Sep 17 00:00:00 2001 From: Samsuik Date: Thu, 16 Jan 2025 23:48:05 +0000 Subject: [PATCH] Half way through feature patches --- .gitignore | 4 +- ...0017-Replace-explosion-density-cache.patch | 0 ...mise-explosions-in-protected-regions.patch | 0 .../0019-Specialised-Explosions.patch | 0 ...0020-Optimise-cannon-entity-movement.patch | 0 .../0025-Add-maxSearch-to-getEntities.patch | 0 ...6-Optimise-LivingEntity-pushEntities.patch | 0 .../0029-Explosion-Durable-Blocks.patch | 0 .../0030-Destroy-Waterlogged-Blocks.patch | 0 ...0007-Replace-explosion-density-cache.patch | 164 +++++++++++++ ...mise-explosions-in-protected-regions.patch | 45 ++++ .../0009-Specialised-Explosions.patch | 165 +++++++++++++ ...0010-Optimise-cannon-entity-movement.patch | 220 ++++++++++++++++++ .../0011-Add-maxSearch-to-getEntities.patch | 117 ++++++++++ ...Collision-limit-for-entity-retrieval.patch | 24 ++ .../0013-Explosion-Durable-Blocks.patch | 105 +++++++++ .../0014-Destroy-Waterlogged-Blocks.patch | 22 ++ .../0003-Specialised-Explosions.patch | 23 ++ .../explosion/density/BlockDensityCache.java | 62 +++++ .../sakura/explosion/density/DensityData.java | 47 ++++ .../durable/DurableBlockManager.java | 43 ++++ .../special/SpecialisedExplosion.java | 203 ++++++++++++++++ .../explosion/special/TntExplosion.java | 201 ++++++++++++++++ sakura-server/src/minecraft/java | 2 +- sakura-server/src/minecraft/resources | 2 +- 25 files changed, 1445 insertions(+), 4 deletions(-) rename migrate/server/feature/{ => applied}/0017-Replace-explosion-density-cache.patch (100%) rename migrate/server/feature/{ => applied}/0018-Optimise-explosions-in-protected-regions.patch (100%) rename migrate/server/feature/{ => applied}/0019-Specialised-Explosions.patch (100%) rename migrate/server/feature/{ => applied}/0020-Optimise-cannon-entity-movement.patch (100%) rename migrate/server/feature/{ => applied}/0025-Add-maxSearch-to-getEntities.patch (100%) rename migrate/server/feature/{ => applied}/0026-Optimise-LivingEntity-pushEntities.patch (100%) rename migrate/server/feature/{ => applied}/0029-Explosion-Durable-Blocks.patch (100%) rename migrate/server/feature/{ => applied}/0030-Destroy-Waterlogged-Blocks.patch (100%) create mode 100644 sakura-server/minecraft-patches/features/0007-Replace-explosion-density-cache.patch create mode 100644 sakura-server/minecraft-patches/features/0008-Optimise-explosions-in-protected-regions.patch create mode 100644 sakura-server/minecraft-patches/features/0009-Specialised-Explosions.patch create mode 100644 sakura-server/minecraft-patches/features/0010-Optimise-cannon-entity-movement.patch create mode 100644 sakura-server/minecraft-patches/features/0011-Add-maxSearch-to-getEntities.patch create mode 100644 sakura-server/minecraft-patches/features/0012-Use-maxEntityCollision-limit-for-entity-retrieval.patch create mode 100644 sakura-server/minecraft-patches/features/0013-Explosion-Durable-Blocks.patch create mode 100644 sakura-server/minecraft-patches/features/0014-Destroy-Waterlogged-Blocks.patch create mode 100644 sakura-server/paper-patches/features/0003-Specialised-Explosions.patch create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java diff --git a/.gitignore b/.gitignore index dad87f4..2169c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ paper-api-generator build.gradle.kts.rej sakura-api/build.gradle.kts sakura-server/build.gradle.kts -src/minecraft/ -src/vanilla/ +sakura-server/src/minecraft/ +sakura-server/src/vanilla/ !gradle/wrapper/gradle-wrapper.jar diff --git a/migrate/server/feature/0017-Replace-explosion-density-cache.patch b/migrate/server/feature/applied/0017-Replace-explosion-density-cache.patch similarity index 100% rename from migrate/server/feature/0017-Replace-explosion-density-cache.patch rename to migrate/server/feature/applied/0017-Replace-explosion-density-cache.patch diff --git a/migrate/server/feature/0018-Optimise-explosions-in-protected-regions.patch b/migrate/server/feature/applied/0018-Optimise-explosions-in-protected-regions.patch similarity index 100% rename from migrate/server/feature/0018-Optimise-explosions-in-protected-regions.patch rename to migrate/server/feature/applied/0018-Optimise-explosions-in-protected-regions.patch diff --git a/migrate/server/feature/0019-Specialised-Explosions.patch b/migrate/server/feature/applied/0019-Specialised-Explosions.patch similarity index 100% rename from migrate/server/feature/0019-Specialised-Explosions.patch rename to migrate/server/feature/applied/0019-Specialised-Explosions.patch diff --git a/migrate/server/feature/0020-Optimise-cannon-entity-movement.patch b/migrate/server/feature/applied/0020-Optimise-cannon-entity-movement.patch similarity index 100% rename from migrate/server/feature/0020-Optimise-cannon-entity-movement.patch rename to migrate/server/feature/applied/0020-Optimise-cannon-entity-movement.patch diff --git a/migrate/server/feature/0025-Add-maxSearch-to-getEntities.patch b/migrate/server/feature/applied/0025-Add-maxSearch-to-getEntities.patch similarity index 100% rename from migrate/server/feature/0025-Add-maxSearch-to-getEntities.patch rename to migrate/server/feature/applied/0025-Add-maxSearch-to-getEntities.patch diff --git a/migrate/server/feature/0026-Optimise-LivingEntity-pushEntities.patch b/migrate/server/feature/applied/0026-Optimise-LivingEntity-pushEntities.patch similarity index 100% rename from migrate/server/feature/0026-Optimise-LivingEntity-pushEntities.patch rename to migrate/server/feature/applied/0026-Optimise-LivingEntity-pushEntities.patch diff --git a/migrate/server/feature/0029-Explosion-Durable-Blocks.patch b/migrate/server/feature/applied/0029-Explosion-Durable-Blocks.patch similarity index 100% rename from migrate/server/feature/0029-Explosion-Durable-Blocks.patch rename to migrate/server/feature/applied/0029-Explosion-Durable-Blocks.patch diff --git a/migrate/server/feature/0030-Destroy-Waterlogged-Blocks.patch b/migrate/server/feature/applied/0030-Destroy-Waterlogged-Blocks.patch similarity index 100% rename from migrate/server/feature/0030-Destroy-Waterlogged-Blocks.patch rename to migrate/server/feature/applied/0030-Destroy-Waterlogged-Blocks.patch diff --git a/sakura-server/minecraft-patches/features/0007-Replace-explosion-density-cache.patch b/sakura-server/minecraft-patches/features/0007-Replace-explosion-density-cache.patch new file mode 100644 index 0000000..30999a6 --- /dev/null +++ b/sakura-server/minecraft-patches/features/0007-Replace-explosion-density-cache.patch @@ -0,0 +1,164 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Mon, 22 Apr 2024 23:01:26 +0100 +Subject: [PATCH] Replace explosion density cache + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index ae1fad06d85de83f53884449cff21fc0ae62bf97..6d8c513e78fa4efd8c7f6f534cf3958d46448efb 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1751,6 +1751,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= bb.maxX ++ && this.minY <= bb.minY && this.maxY >= bb.maxY ++ && this.minZ <= bb.minZ && this.maxZ >= bb.maxZ; ++ } ++ ++ public final boolean isVec3InBounds(Vec3 p) { ++ return this.minX <= p.x && this.maxX >= p.x ++ && this.minY <= p.y && this.maxY >= p.y ++ && this.minZ <= p.z && this.maxZ >= p.z; ++ } ++ ++ public final AABB expand(Vec3 pos) { ++ double minX = Math.min(this.minX, pos.x); ++ double minY = Math.min(this.minY, pos.y); ++ double minZ = Math.min(this.minZ, pos.z); ++ double maxX = Math.max(this.maxX, pos.x); ++ double maxY = Math.max(this.maxY, pos.y); ++ double maxZ = Math.max(this.maxZ, pos.z); ++ return new AABB(minX, minY, minZ, maxX, maxY, maxZ); ++ } ++ // Sakura end - explosion density cache + } diff --git a/sakura-server/minecraft-patches/features/0008-Optimise-explosions-in-protected-regions.patch b/sakura-server/minecraft-patches/features/0008-Optimise-explosions-in-protected-regions.patch new file mode 100644 index 0000000..19e0244 --- /dev/null +++ b/sakura-server/minecraft-patches/features/0008-Optimise-explosions-in-protected-regions.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 3 May 2024 15:18:58 +0100 +Subject: [PATCH] Optimise explosions in protected regions + + +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 6d3e03b3c9fed817808de6ee08b531069d342dd5..76697d12a12eeedbaab6aef169080320791aff6e 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -345,6 +345,22 @@ public class ServerExplosion implements Explosion { + return rays; + } + // Sakura end - optimise paper explosions ++ // Sakura start - optimise explosion protected regions ++ protected final boolean isRegionUnprotected() { ++ // optimisation: We check if a plugin has cancelled the event or cleared the blockList. ++ // It tells us if the result was thrown away, so we can avoid the block searching logic. ++ // As a side effect the event is called twice which may interfere with some plugins. ++ if (this.source != null && this.level.sakuraConfig().cannons.explosion.optimiseProtectedRegions) { ++ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z); ++ List blocks = new ObjectArrayList<>(1); ++ blocks.add(location.getBlock()); ++ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blocks, 0.0f, this.blockInteraction); ++ return !event.isCancelled() && !event.blockList().isEmpty(); ++ } ++ ++ return true; ++ } ++ // Sakura end - optimise explosion protected regions + + public ServerExplosion( + ServerLevel level, +@@ -449,6 +465,11 @@ public class ServerExplosion implements Explosion { + return ret; + } + // Sakura end - optimise paper explosions ++ // Sakura start - optimise protected explosions ++ if (!this.isRegionUnprotected()) { ++ return ret; ++ } ++ // Sakura end - optimise protected explosions + + // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of + // a 16x16x16 cube diff --git a/sakura-server/minecraft-patches/features/0009-Specialised-Explosions.patch b/sakura-server/minecraft-patches/features/0009-Specialised-Explosions.patch new file mode 100644 index 0000000..cd74f91 --- /dev/null +++ b/sakura-server/minecraft-patches/features/0009-Specialised-Explosions.patch @@ -0,0 +1,165 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 3 May 2024 15:04:31 +0100 +Subject: [PATCH] Specialised Explosions + + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 83bd1ddec7edb09e9a28eead178d7d07cbde8749..ee44ba44773f245d351aac9461bd6cff18204f01 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1866,7 +1866,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + case STANDARD -> Explosion.BlockInteraction.DESTROY; // CraftBukkit - handle custom explosion type + }; + Vec3 vec3 = new Vec3(x, y, z); +- ServerExplosion serverExplosion = new ServerExplosion(this, source, damageSource, damageCalculator, vec3, radius, fire, blockInteraction); ++ // Sakura start - specialised explosions ++ final ServerExplosion serverExplosion; ++ if (source instanceof net.minecraft.world.entity.item.PrimedTnt tnt) { ++ serverExplosion = new me.samsuik.sakura.explosion.special.TntExplosion(this, tnt, damageSource, damageCalculator, vec3, radius, fire, blockInteraction, self -> { ++ this.notifyPlayersOfExplosion(self, self.center(), smallExplosionParticles, largeExplosionParticles, explosionSound); ++ }); ++ } else { ++ serverExplosion = new ServerExplosion(this, source, damageSource, damageCalculator, vec3, radius, fire, blockInteraction); ++ } ++ // Sakura end - specialised explosions + if (configurator != null) configurator.accept(serverExplosion);// Paper - Allow explosions to damage source + serverExplosion.explode(); + // CraftBukkit start +@@ -1874,6 +1883,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return serverExplosion; + } + // CraftBukkit end ++ // Sakura start - specialised explosions ++ this.notifyPlayersOfExplosion(serverExplosion, vec3, smallExplosionParticles, largeExplosionParticles, explosionSound); ++ return serverExplosion; ++ } ++ ++ private void notifyPlayersOfExplosion(ServerExplosion serverExplosion, Vec3 vec3, ++ ParticleOptions smallExplosionParticles, ParticleOptions largeExplosionParticles, ++ Holder explosionSound) { ++ // Sakura end - specialised explosions + ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles; + + for (ServerPlayer serverPlayer : this.players) { +@@ -1894,7 +1912,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + } + +- return serverExplosion; // CraftBukkit ++ // Sakura - specialised explosions; return moved up into explode0 + } + + private Explosion.BlockInteraction getDestroyType(GameRules.Key decayGameRule) { +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index b378b4c4930c4ebd55795591aca173fd1fee46c9..c88fe6c244f6a88f1e42822dd0795187dcc3b655 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -77,20 +77,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak + + @Override + public final void respawnEntity(int count) { +- PrimedTnt tnt = new PrimedTnt(EntityType.TNT, this.level()); +- tnt.updateBukkitHandle(this); // update handle for plugins +- while (count-- > 1) { +- this.setFuse(100); // Prevent unwanted explosions while ticking +- +- // Cause an explosion to affect this entity +- tnt.setPos(this.position()); +- tnt.setDeltaMovement(this.getDeltaMovement()); +- this.entityState().apply(this); +- tnt.explode(); +- this.storeEntityState(); +- +- this.tick(); +- } ++ this.mergeData.setCount(count); // Sakura - specialised explosions + } + // Sakura end - merge cannon entities + +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 76697d12a12eeedbaab6aef169080320791aff6e..0285e19d6265056fb5ff9855a5dd41bd16bbc082 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -361,6 +361,38 @@ public class ServerExplosion implements Explosion { + return true; + } + // Sakura end - optimise explosion protected regions ++ // Sakura start - specialised explosions ++ protected final void createBlockCache() { ++ // Paper start - collision optimisations ++ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); ++ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); ++ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ this.directMappedBlockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; ++ this.mutablePos = new BlockPos.MutableBlockPos(); ++ // Paper end - collision optimisations ++ } ++ ++ protected final void markBlocksInCacheAsExplodable(List explodedPositions) { ++ for (BlockPos blow : explodedPositions) { ++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache cache = this.blockCache.get(blow.asLong()); ++ // May be null if the blockCache is cleared then retrieved from the recent block cache ++ if (cache != null) { ++ cache.shouldExplode = null; ++ } ++ } ++ } ++ ++ protected final void clearBlockCache() { ++ // Paper start - collision optimisations ++ this.blockCache = null; ++ this.chunkPosCache = null; ++ this.chunkCache = null; ++ this.directMappedBlockCache = null; ++ this.mutablePos = null; ++ // Paper end - collision optimisations ++ } ++ // Sakura end - specialised explosions + + public ServerExplosion( + ServerLevel level, +@@ -670,7 +702,10 @@ public class ServerExplosion implements Explosion { + if (entity instanceof Player) { + Player player = (Player)entity; + if (!player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback +- this.hitPlayers.put(player, vec3); ++ // Sakura start - specialised explosions; tally player velocity ++ final Vec3 explosionImpact = vec3; ++ this.hitPlayers.compute(player, (p, v) -> v != null ? v.add(explosionImpact) : explosionImpact); ++ // Sakura end - specialised explosions; tally player velocity + } + } + +@@ -779,14 +814,7 @@ public class ServerExplosion implements Explosion { + return; + } + // CraftBukkit end +- // Paper start - collision optimisations +- this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); +- this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; +- java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); +- this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; +- this.directMappedBlockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; +- this.mutablePos = new BlockPos.MutableBlockPos(); +- // Paper end - collision optimisations ++ this.createBlockCache(); // Sakura - specialised explosions + this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center); + List list = this.calculateExplodedPositions(); + this.hurtEntities(); +@@ -800,13 +828,7 @@ public class ServerExplosion implements Explosion { + if (this.fire) { + this.createFire(list); + } +- // Paper start - collision optimisations +- this.blockCache = null; +- this.chunkPosCache = null; +- this.chunkCache = null; +- this.directMappedBlockCache = null; +- this.mutablePos = null; +- // Paper end - collision optimisations ++ this.clearBlockCache(); // Sakura - specialised explosions + } + + private static void addOrAppendStack(List stackCollectors, ItemStack stack, BlockPos pos) { diff --git a/sakura-server/minecraft-patches/features/0010-Optimise-cannon-entity-movement.patch b/sakura-server/minecraft-patches/features/0010-Optimise-cannon-entity-movement.patch new file mode 100644 index 0000000..251328f --- /dev/null +++ b/sakura-server/minecraft-patches/features/0010-Optimise-cannon-entity-movement.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 13 Oct 2023 14:36:19 +0100 +Subject: [PATCH] Optimise cannon entity movement + + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 2a617bd6d5d14cd69b149d6c5f82f8b2c3bc2d5d..8c3e0ca06f89e4d8c08d30272475cdeaca20b3ef 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -1141,6 +1141,75 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return this.moveStartZ; + } + // Paper end - detailed watchdog information ++ // Sakura start - optimise cannon entity movement; stripped back movement method ++ public final void moveStripped(MoverType movementType, Vec3 movement) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main"); ++ if (this.noPhysics) { ++ this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); ++ } else { ++ if (movementType == MoverType.PISTON) { ++ movement = this.limitPistonMovement(movement); ++ if (movement.equals(Vec3.ZERO)) { ++ return; ++ } ++ } ++ ++ ProfilerFiller gameprofilerfiller = Profiler.get(); ++ gameprofilerfiller.push("move"); ++ if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { ++ movement = movement.multiply(this.stuckSpeedMultiplier); ++ this.stuckSpeedMultiplier = Vec3.ZERO; ++ this.setDeltaMovement(Vec3.ZERO); ++ } ++ ++ Vec3 vec3d1 = this.sakura_collide(movement); ++ double d0 = vec3d1.lengthSqr(); ++ ++ if (d0 > 1.0E-7D || movement.lengthSqr() - d0 < 1.0E-7D) { ++ if (this.fallDistance != 0.0F && d0 >= 1.0D && !this.isFallingBlock) { ++ BlockHitResult clipResult = this.level().clip(new ClipContext(this.position(), this.position().add(vec3d1), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this)); ++ if (clipResult.getType() != HitResult.Type.MISS) { ++ this.resetFallDistance(); ++ } ++ } ++ ++ this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z); ++ } ++ ++ gameprofilerfiller.pop(); ++ gameprofilerfiller.push("rest"); ++ boolean movedX = !Mth.equal(movement.x, vec3d1.x); ++ boolean movedZ = !Mth.equal(movement.z, vec3d1.z); ++ ++ this.horizontalCollision = movedX || movedZ; ++ this.verticalCollision = movement.y != vec3d1.y; ++ this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0D; ++ ++ this.setOnGroundWithMovement(this.verticalCollisionBelow, this.horizontalCollision, vec3d1); ++ BlockPos blockPosBelow = this.getOnPosLegacy(); ++ BlockState blockstate = this.level().getBlockState(blockPosBelow); ++ ++ this.checkFallDamage(vec3d1.y, this.onGround(), blockstate, blockPosBelow); ++ if (this.isRemoved()) { ++ gameprofilerfiller.pop(); ++ } else { ++ if (this.horizontalCollision) { ++ Vec3 vec3d2 = this.getDeltaMovement(); ++ this.setDeltaMovement(movedX ? 0.0D : vec3d2.x, vec3d2.y, movedZ ? 0.0D : vec3d2.z); ++ } ++ ++ Block block = blockstate.getBlock(); ++ if (movement.y != vec3d1.y) { // remove y momentum ++ block.updateEntityMovementAfterFallOn(this.level(), this); ++ } ++ ++ float f = this.getBlockSpeedFactor(); ++ this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f)); ++ gameprofilerfiller.pop(); ++ } ++ } ++ } ++ // Sakura end - optimise cannon entity movement; stripped back movement method + + public void move(MoverType type, Vec3 movement) { + final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity +@@ -1488,6 +1557,107 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return distance; + } + ++ // Sakura start - optimise cannon entity movement ++ private Vec3 sakura_collide(Vec3 movement) { ++ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) { ++ return movement; ++ } ++ ++ List potentialCollisionsVoxel = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(0); ++ List potentialCollisionsBB = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(4); ++ AABB currBoundingBox = this.getBoundingBox(); ++ ++ if (movement.lengthSqr() >= 12.0) { // axis scan on large movement ++ return this.collideAxisScan(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ } else { ++ return this.collideCube(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ } ++ } ++ ++ private Vec3 collideCube(Vec3 movement, AABB currBoundingBox, List voxelList, List bbList) { ++ final AABB bb; ++ if (movement.x() == 0.0 && movement.z() == 0.0) { ++ if (movement.y > 0.0) { ++ bb = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y); ++ } else { ++ bb = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y); ++ } ++ } else { ++ bb = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); ++ } ++ this.collectCollisions(bb, voxelList, bbList); ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, voxelList, bbList); ++ } ++ ++ private Vec3 collideAxisScan(Vec3 movement, AABB currBoundingBox, List voxelList, List bbList) { ++ double x = movement.x; ++ double y = movement.y; ++ double z = movement.z; ++ ++ boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (y != 0.0) { ++ y = this.scanY(currBoundingBox, y, voxelList, bbList); ++ if (y != 0.0) { ++ currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetY(currBoundingBox, y); ++ } ++ } ++ ++ if (xSmaller && z != 0.0) { ++ z = this.scanZ(currBoundingBox, z, voxelList, bbList); ++ if (z != 0.0) { ++ currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetZ(currBoundingBox, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = this.scanX(currBoundingBox, x, voxelList, bbList); ++ if (x != 0.0) { ++ currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetX(currBoundingBox, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = this.scanZ(currBoundingBox, z, voxelList, bbList); ++ } ++ ++ return new Vec3(x, y, z); ++ } ++ ++ private void collectCollisions(AABB collisionBox, List voxelList, List bbList) { ++ // Copied from the collide method below ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder( ++ this.level, this, collisionBox, voxelList, bbList, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | this.getExtraCollisionFlags(), null // Sakura - load chunks on movement ++ ); ++ ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions( ++ this.level, this, collisionBox, bbList, 0, null ++ ); ++ } ++ ++ private double scanX(AABB currBoundingBox, double x, List voxelList, List bbList) { ++ AABB scanBox = currBoundingBox.expandTowards(x, 0.0, 0.0); ++ this.collectCollisions(scanBox, voxelList, bbList); ++ x = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performAABBCollisionsX(currBoundingBox, x, bbList); ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performVoxelCollisionsX(currBoundingBox, x, voxelList); ++ } ++ ++ private double scanY(AABB currBoundingBox, double y, List voxelList, List bbList) { ++ AABB scanBox = currBoundingBox.expandTowards(0.0, y, 0.0); ++ this.collectCollisions(scanBox, voxelList, bbList); ++ y = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performAABBCollisionsY(currBoundingBox, y, bbList); ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performVoxelCollisionsY(currBoundingBox, y, voxelList); ++ } ++ ++ private double scanZ(AABB currBoundingBox, double z, List voxelList, List bbList) { ++ AABB scanBox = currBoundingBox.expandTowards(0.0, 0.0, z); ++ this.collectCollisions(scanBox, voxelList, bbList); ++ z = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performAABBCollisionsZ(currBoundingBox, z, bbList); ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performVoxelCollisionsZ(currBoundingBox, z, voxelList); ++ } ++ // Sakura end - optimise cannon entity movement ++ + // Paper start - optimise collisions + protected Vec3 collide(Vec3 movement) { + final boolean xZero = movement.x == 0.0; +diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java +index 1d9afcf995ee734f13803e26956439e5c3450f44..c6836ab9a2789520931d2119aeebeaf2179f27fa 100644 +--- a/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -213,7 +213,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti + Block block = this.blockState.getBlock(); + this.time++; + this.applyGravity(); +- this.move(MoverType.SELF, this.getDeltaMovement()); ++ this.moveStripped(MoverType.SELF, this.getDeltaMovement()); // Sakura - optimise cannon entity movement + this.applyEffectsFromBlocks(); + // Paper start - Configurable falling blocks height nerf + if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index c88fe6c244f6a88f1e42822dd0795187dcc3b655..2ee04093d7c8b61a48913bd4c929528e357aa971 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -148,7 +148,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak + // Sakura - remove max tnt per tick + this.handlePortal(); + this.applyGravity(); +- this.move(MoverType.SELF, this.getDeltaMovement()); ++ this.moveStripped(MoverType.SELF, this.getDeltaMovement()); // Sakura - optimise cannon entity movement + this.applyEffectsFromBlocks(); + // Paper start - Configurable TNT height nerf + if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { diff --git a/sakura-server/minecraft-patches/features/0011-Add-maxSearch-to-getEntities.patch b/sakura-server/minecraft-patches/features/0011-Add-maxSearch-to-getEntities.patch new file mode 100644 index 0000000..f46c612 --- /dev/null +++ b/sakura-server/minecraft-patches/features/0011-Add-maxSearch-to-getEntities.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 27 Jun 2024 17:02:32 +0100 +Subject: [PATCH] Add maxSearch to getEntities + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +index 39148fe540d616cdd4d63c4d1a02b422cab0f6eb..50a551d5bcac9b2b6644d7b43e625185090657a6 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +@@ -314,7 +314,14 @@ public final class ChunkEntitySlices { + + public boolean getEntities(final Entity except, final AABB box, final List into, final Predicate predicate, + final int maxCount) { +- return this.allEntities.getEntitiesLimited(except, box, into, predicate, maxCount); ++ // Sakura start - add maxSearch to getEntities ++ return this.getEntities(except, box, into, predicate, maxCount, Integer.MAX_VALUE); ++ } ++ ++ public boolean getEntities(final Entity except, final AABB box, final List into, final Predicate predicate, ++ final int maxCount, final int maxSearch) { ++ return this.allEntities.getEntitiesLimited(except, box, into, predicate, maxCount, maxSearch); ++ // Sakura end - add maxSearch to getEntities + } + + public void getEntities(final EntityType type, final AABB box, final List into, +@@ -570,6 +577,13 @@ public final class ChunkEntitySlices { + + public boolean getEntitiesLimited(final Entity except, final AABB box, final List into, final Predicate predicate, + final int maxCount) { ++ // Sakura start - add maxSearch to getEntities ++ return this.getEntitiesLimited(except, box, into, predicate, maxCount, Integer.MAX_VALUE); ++ } ++ ++ public boolean getEntitiesLimited(final Entity except, final AABB box, final List into, final Predicate predicate, ++ final int maxCount, final int maxSearch) { ++ // Sakura end - add maxSearch to getEntities + if (this.count == 0) { + return false; + } +@@ -591,8 +605,14 @@ public final class ChunkEntitySlices { + + final Entity[] storage = list.storage; + +- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { +- final Entity entity = storage[i]; ++ // Sakura start - add maxSearch to getEntities ++ final int len = Math.min(storage.length, list.size()); ++ final int offset = this.slices.world.random.nextInt(len); ++ for (int i = 0; i < len; ++i) { ++ final int pos = (i + offset) % len; ++ final Entity entity = storage[pos]; ++ if (i > maxSearch) break; ++ // Sakura end - add maxSearch to getEntities + + if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { + continue; +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +index 7554c109c35397bc1a43dd80e87764fd78645bbf..d60f30f7afb15cc90c1bd4b816136d00b23a53e4 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +@@ -722,6 +722,13 @@ public abstract class EntityLookup implements LevelEntityGetter { + + public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate, + final int maxCount) { ++ // Sakura start - add maxSearch to getEntities ++ this.getEntities(except, box, into, predicate, maxCount, Integer.MAX_VALUE); ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate, ++ final int maxCount, final int maxSearch) { ++ // Sakura end - add maxSearch to getEntities + final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; + final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; + final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; +@@ -753,7 +760,7 @@ public abstract class EntityLookup implements LevelEntityGetter { + continue; + } + +- if (chunk.getEntities(except, box, into, predicate, maxCount)) { ++ if (chunk.getEntities(except, box, into, predicate, maxCount, maxSearch)) { // Sakura - add maxSearch to getEntities + return; + } + } +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 4dd2c4110d85bb7e6575b325196329900d719c64..b4a8a81f1fa091e45f1f39fdb69c61871d7dc6b9 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -1791,10 +1791,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.getEntities(entityTypeTest, bounds, predicate, output, Integer.MAX_VALUE); + } + +- // Paper start - rewrite chunk system ++ // Sakura start - add maxSearch to getEntities + public void getEntities(final EntityTypeTest entityTypeTest, + final AABB boundingBox, final Predicate predicate, + final List into, final int maxCount) { ++ this.getEntities(entityTypeTest, boundingBox, predicate, into, maxCount, Integer.MAX_VALUE); ++ } ++ ++ // Paper start - rewrite chunk system ++ public void getEntities(final EntityTypeTest entityTypeTest, ++ final AABB boundingBox, final Predicate predicate, ++ final List into, final int maxCount, final int maxSearch) { ++ // Sakura end - add maxSearch to getEntities + Profiler.get().incrementCounter("getEntities"); + + if (entityTypeTest instanceof net.minecraft.world.entity.EntityType byType) { +@@ -1811,7 +1819,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + if (entityTypeTest == null) { + if (maxCount != Integer.MAX_VALUE) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount); ++ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount, maxSearch); // Sakura - add maxSearch to getEntities + ca.spottedleaf.moonrise.common.PlatformHooks.get().addToGetEntities((Level)(Object)this, entityTypeTest, boundingBox, predicate, into, maxCount); + return; + } else { diff --git a/sakura-server/minecraft-patches/features/0012-Use-maxEntityCollision-limit-for-entity-retrieval.patch b/sakura-server/minecraft-patches/features/0012-Use-maxEntityCollision-limit-for-entity-retrieval.patch new file mode 100644 index 0000000..ebcb988 --- /dev/null +++ b/sakura-server/minecraft-patches/features/0012-Use-maxEntityCollision-limit-for-entity-retrieval.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 23 Sep 2021 18:50:13 +0100 +Subject: [PATCH] Use maxEntityCollision limit for entity retrieval + + +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 7ed355448ff6fbcf585d82a88d456908f9eb3ae6..e7b92681e5d65892da00699cc29c713219c9db8c 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -3640,7 +3640,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + return; + } + // Paper end - don't run getEntities if we're not going to use its result +- List entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule ++ // Sakura start - use maxEntityCollision limit for entity retrieval ++ int limit = Math.max(_int, this.level().paperConfig().collisions.maxEntityCollisions); ++ int search = limit * limit; ++ List entities = new ArrayList<>(); ++ this.level().getEntities(null, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule), entities, limit, search); // Paper - Climbing should not bypass cramming gamerule ++ // Sakura end - use maxEntityCollision limit for entity retrieval + if (!entities.isEmpty()) { + // Paper - don't run getEntities if we're not going to use its result; moved up + if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) { diff --git a/sakura-server/minecraft-patches/features/0013-Explosion-Durable-Blocks.patch b/sakura-server/minecraft-patches/features/0013-Explosion-Durable-Blocks.patch new file mode 100644 index 0000000..24814cf --- /dev/null +++ b/sakura-server/minecraft-patches/features/0013-Explosion-Durable-Blocks.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Wed, 15 Nov 2023 23:18:38 +0000 +Subject: [PATCH] Explosion Durable Blocks + + +diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java +index 68e50c6ade879d263424f244070677cb81c34c33..8467af4ee57b6699227370ada7bf15ca41fb91c3 100644 +--- a/net/minecraft/world/item/BlockItem.java ++++ b/net/minecraft/world/item/BlockItem.java +@@ -45,8 +45,31 @@ public class BlockItem extends Item { + this.block = block; + } + ++ // Sakura start - explosion durable blocks ++ private void sendBlockDurabilityToPlayer(UseOnContext context) { ++ Player player = context.getPlayer(); ++ BlockState state = context.getLevel().getBlockState(context.getClickedPos()); ++ Block block = state.getBlock(); ++ me.samsuik.sakura.explosion.durable.DurableMaterial material = context.getLevel().localConfig().config(context.getClickedPos()).durableMaterials.get(block); ++ ++ if (material != null) { ++ int remaining = context.getLevel().durabilityManager.durability(context.getClickedPos(), material); ++ int durability = material.durability(); ++ ++ player.getBukkitEntity().sendRichMessage( ++ me.samsuik.sakura.configuration.GlobalConfiguration.get().messages.durableBlockInteraction, ++ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.unparsed("remaining", String.valueOf(remaining)), ++ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.unparsed("durability", String.valueOf(durability)) ++ ); ++ } ++ } ++ + @Override + public InteractionResult useOn(UseOnContext context) { ++ if (this.getBlock() == net.minecraft.world.level.block.Blocks.POTATOES && context.getPlayer() != null) { ++ this.sendBlockDurabilityToPlayer(context); ++ } ++ // Sakura end - explosion durable blocks + InteractionResult interactionResult = this.place(new BlockPlaceContext(context)); + return !interactionResult.consumesAction() && context.getItemInHand().has(DataComponents.CONSUMABLE) + ? super.use(context.getLevel(), context.getPlayer(), context.getHand()) +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index b4a8a81f1fa091e45f1f39fdb69c61871d7dc6b9..19aa5010b019e343d0fb085359eac98bcb5b5efa 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -839,6 +839,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + // Paper end - optimise random ticking + public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities + public final me.samsuik.sakura.explosion.density.BlockDensityCache densityCache = new me.samsuik.sakura.explosion.density.BlockDensityCache(); // Sakura - explosion density cache ++ public final me.samsuik.sakura.explosion.durable.DurableBlockManager durabilityManager = new me.samsuik.sakura.explosion.durable.DurableBlockManager(); // Sakura - explosion durable blocks + + protected Level( + WritableLevelData levelData, +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 0285e19d6265056fb5ff9855a5dd41bd16bbc082..58c14cc2d041348f42e8466fc93f18736c2618da 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -133,7 +133,7 @@ public class ServerExplosion implements Explosion { + BlockState blockState = ((ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk)chunk).moonrise$getBlock(x, y, z); + FluidState fluidState = blockState.getFluidState(); + +- Optional resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState); ++ Optional resistance = !calculateResistance ? Optional.empty() : this.calculateBlockResistance(blockState, fluidState, pos); // Sakura - explosion durable blocks + + ret = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache( + key, pos, blockState, fluidState, +@@ -393,6 +393,20 @@ public class ServerExplosion implements Explosion { + // Paper end - collision optimisations + } + // Sakura end - specialised explosions ++ // Sakura start - explosion durable blocks ++ private Optional calculateBlockResistance(BlockState blockState, FluidState fluidState, BlockPos pos) { ++ if (!blockState.isAir()) { ++ Block block = blockState.getBlock(); ++ me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().config(pos).durableMaterials.get(block); ++ ++ if (material != null && material.resistance() >= 0.0f && (this.level.sakuraConfig().cannons.explosion.allowNonTntBreakingDurableBlocks || this.source instanceof net.minecraft.world.entity.item.PrimedTnt)) { ++ return Optional.of(material.resistance()); ++ } ++ } ++ ++ return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState); ++ } ++ // Sakura end - explosion durable blocks + + public ServerExplosion( + ServerLevel level, +@@ -779,6 +793,16 @@ public class ServerExplosion implements Explosion { + } + } + // CraftBukkit end ++ // Sakura start - explosion durable blocks ++ if (this.level.sakuraConfig().cannons.explosion.allowNonTntBreakingDurableBlocks || this.source instanceof net.minecraft.world.entity.item.PrimedTnt) { ++ me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().config(blockposition).durableMaterials.get(block); ++ ++ if (material != null && material.durability() >= 0 && !this.level.durabilityManager.damage(blockposition, material)) { ++ iterator.remove(); ++ continue; ++ } ++ } ++ // Sakura end - explosion durable blocks + + this.level + .getBlockState(blockPos) diff --git a/sakura-server/minecraft-patches/features/0014-Destroy-Waterlogged-Blocks.patch b/sakura-server/minecraft-patches/features/0014-Destroy-Waterlogged-Blocks.patch new file mode 100644 index 0000000..3e62e8e --- /dev/null +++ b/sakura-server/minecraft-patches/features/0014-Destroy-Waterlogged-Blocks.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 16 Nov 2023 00:59:04 +0000 +Subject: [PATCH] Destroy Waterlogged Blocks + + +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 58c14cc2d041348f42e8466fc93f18736c2618da..f74aee504fa048a43272a3c3e9d1c6f9fa4a5913 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -402,6 +402,11 @@ public class ServerExplosion implements Explosion { + if (material != null && material.resistance() >= 0.0f && (this.level.sakuraConfig().cannons.explosion.allowNonTntBreakingDurableBlocks || this.source instanceof net.minecraft.world.entity.item.PrimedTnt)) { + return Optional.of(material.resistance()); + } ++ // Sakura start - destroy water logged blocks ++ if (!fluidState.isEmpty() && !blockState.liquid() && this.level.sakuraConfig().cannons.explosion.destroyWaterloggedBlocks) { ++ return Optional.of(ZERO_RESISTANCE); ++ } ++ // Sakura end - destroy water logged blocks + } + + return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState); diff --git a/sakura-server/paper-patches/features/0003-Specialised-Explosions.patch b/sakura-server/paper-patches/features/0003-Specialised-Explosions.patch new file mode 100644 index 0000000..8c62225 --- /dev/null +++ b/sakura-server/paper-patches/features/0003-Specialised-Explosions.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Fri, 3 May 2024 15:04:31 +0100 +Subject: [PATCH] Specialised Explosions + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +index c21e00812f1aaa1279834a0562d360d6b89e146c..1e1329adde1457898a3002279b53b1bbb91c36d2 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +@@ -107,6 +107,12 @@ public final class IteratorSafeOrderedReferenceSet { + } + } + ++ // Sakura start - specialised explosions; add indexOf method ++ public int indexOf(final E element) { ++ return this.indexMap.getInt(element); ++ } ++ // Sakura end - specialised explosions; add indexOf method ++ + public boolean remove(final E element) { + final int index = this.indexMap.removeInt(element); + if (index >= 0) { diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java new file mode 100644 index 0000000..35454e1 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java @@ -0,0 +1,62 @@ +package me.samsuik.sakura.explosion.density; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; + +/** + * This is a replacement for papers explosion density cache to be more lenient and efficient. + */ +public final class BlockDensityCache { + public static final float UNKNOWN_DENSITY = -1.0f; + + private final Int2ObjectOpenHashMap densityDataMap = new Int2ObjectOpenHashMap<>(); + private DensityData data; + private int key; + private boolean knownSource; + + public float getDensity(Vec3 explosion, Entity entity) { + int key = getKey(explosion, entity); + DensityData data = this.densityDataMap.get(key); + + if (data != null && data.hasPosition(explosion, entity.getBoundingBox())) { + return data.density(); + } else { + this.knownSource = data != null && data.complete() && data.isExplosionPosition(explosion); + this.data = data; + this.key = key; + return UNKNOWN_DENSITY; + } + } + + public float getKnownDensity(Vec3 point) { + if (this.knownSource && this.data.isKnownPosition(point)) { + return this.data.density(); + } else { + return UNKNOWN_DENSITY; + } + } + + public void putDensity(Vec3 explosion, Entity entity, float density) { + if (this.data == null || !this.data.complete()) { + this.densityDataMap.put(this.key, new DensityData(explosion, entity, density)); + } else if (this.data.density() == density) { + this.data.expand(explosion, entity); + } + } + + public void invalidate() { + this.densityDataMap.clear(); + } + + private static int getKey(Vec3 explosion, Entity entity) { + int key = Mth.floor(explosion.x()); + key = 31 * key + Mth.floor(explosion.y()); + key = 31 * key + Mth.floor(explosion.z()); + key = 31 * key + entity.getBlockX(); + key = 31 * key + entity.getBlockY(); + key = 31 * key + entity.getBlockZ(); + return key; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java new file mode 100644 index 0000000..d7e2463 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java @@ -0,0 +1,47 @@ +package me.samsuik.sakura.explosion.density; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public final class DensityData { + private AABB source; + private AABB known; + private AABB entity; + private final float density; + private final boolean complete; + + public DensityData(Vec3 explosion, Entity entity, float density) { + this.source = new AABB(explosion, explosion); + this.known = new AABB(entity.position(), entity.position()); + this.entity = entity.getBoundingBox(); + this.density = density; + this.complete = Math.abs(density - 0.5f) == 0.5f; + } + + public float density() { + return this.density; + } + + public boolean complete() { + return this.complete; + } + + public boolean hasPosition(Vec3 explosion, AABB entity) { + return this.isExplosionPosition(explosion) && this.entity.isAABBInBounds(entity); + } + + public boolean isKnownPosition(Vec3 point) { + return this.entity.isVec3InBounds(point); + } + + public boolean isExplosionPosition(Vec3 explosion) { + return this.source.isVec3InBounds(explosion); + } + + public void expand(Vec3 explosion, Entity entity) { + this.source = this.source.expand(explosion); + this.known = this.known.expand(entity.position()); + this.entity = this.entity.minmax(entity.getBoundingBox()); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java new file mode 100644 index 0000000..e52cae2 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java @@ -0,0 +1,43 @@ +package me.samsuik.sakura.explosion.durable; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import net.minecraft.core.BlockPos; + +import java.util.concurrent.TimeUnit; + +public final class DurableBlockManager { + private final Cache durableBlocks = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.MINUTES) + .maximumSize(65534) + .build(); + + public boolean damage(BlockPos pos, DurableMaterial material) { + DurableBlock block = this.durableBlocks.getIfPresent(pos); + if (block == null) { + this.durableBlocks.put(pos, block = new DurableBlock(material.durability())); + } + return block.damage(); + } + + public int durability(BlockPos pos, DurableMaterial material) { + final DurableBlock block = this.durableBlocks.getIfPresent(pos); + return block != null ? block.durability() : material.durability(); + } + + private static final class DurableBlock { + private int durability; + + public DurableBlock(int durability) { + this.durability = durability; + } + + public int durability() { + return this.durability; + } + + public boolean damage() { + return --this.durability <= 0; + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java new file mode 100644 index 0000000..ce04a5d --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java @@ -0,0 +1,203 @@ +package me.samsuik.sakura.explosion.special; + +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ExplosionDamageCalculator; +import net.minecraft.world.level.ServerExplosion; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Consumer; + +public abstract class SpecialisedExplosion extends ServerExplosion { + private static final double ENTITY_DISPATCH_DISTANCE = Math.pow(32.0, 2.0); + + protected final T cause; // preferred over source + private Vec3 impactPosition; + protected final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); + private final Consumer> applyEffects; + + public SpecialisedExplosion(ServerLevel level, T entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 center, float power, boolean createFire, BlockInteraction destructionType, Consumer> applyEffects) { + super(level, entity, damageSource, behavior, center, power, createFire, destructionType); + this.cause = entity; + this.impactPosition = center; + this.applyEffects = applyEffects; + } + + protected double getExplosionOffset() { + return (double) this.cause.getBbHeight() * 0.0625D; + } + + protected abstract int getExplosionCount(); + + protected abstract void startExplosion(); + + @Override + public final void explode() { + if (this.radius() < 0.1F) { + // (radius < 0.1F) in bukkit is assumed to not be able to find any blocks or entities. + for (int i = this.getExplosionCount() - 1; i >= 0; --i) { + this.finalizeExplosionAndParticles(List.of()); + } + } else { + this.createBlockCache(); + this.startExplosion(); // search for blocks, impact entities, finalise if necessary + this.clearBlockCache(); + } + } + + protected final boolean requiresImpactEntities(List blocks, Vec3 center) { + if (this.impactPosition.distanceToSqr(center) > ENTITY_DISPATCH_DISTANCE) { + this.impactPosition = center; + return true; + } + return !blocks.isEmpty(); + } + + protected final boolean finalizeExplosionAndParticles(List blocks) { + this.wasCanceled = false; + List explodedPositions = new ObjectArrayList<>(blocks); + this.interactWithBlocks(explodedPositions); + + if (!this.wasCanceled) { + this.applyEffects.accept(this); + this.getHitPlayers().clear(); + } + + return !explodedPositions.isEmpty(); + } + + protected void postExplosion(List foundBlocks, boolean destroyedBlocks) { + // optimisation: Keep the block cache across explosions and invalidate any found blocks. + // This can help reduce block retrievals while block searching when there's a durable block, + // and when ray tracing for obstructions. This is disabled by default because plugins can + // change blocks in the explosion event. + if (this.level().sakuraConfig().cannons.explosion.useBlockCacheAcrossExplosions && !foundBlocks.isEmpty() && !destroyedBlocks) { + this.markBlocksInCacheAsExplodable(foundBlocks); + } else { + super.blockCache.clear(); + } + + java.util.Arrays.fill(this.directMappedBlockCache, null); + } + + protected final void recalculateExplosionPosition() { + this.recalculateExplosionPosition(this.cause); + } + + protected final void recalculateExplosionPosition(T entity) { + double x = entity.getX(); + double y = entity.getY() + this.getExplosionOffset(); + double z = entity.getZ(); + this.center = new Vec3(x, y, z); + } + + protected final void forEachEntitySliceInBounds(AABB bb, Consumer sliceConsumer) { + int minSection = WorldUtil.getMinSection(this.level()); + int maxSection = WorldUtil.getMaxSection(this.level()); + + int minChunkX = Mth.floor(bb.minX) >> 4; + int minChunkY = Mth.clamp(Mth.floor(bb.minY) >> 4, minSection, maxSection); + int minChunkZ = Mth.floor(bb.minZ) >> 4; + int maxChunkX = Mth.floor(bb.maxX) >> 4; + int maxChunkY = Mth.clamp(Mth.floor(bb.maxY) >> 4, minSection, maxSection); + int maxChunkZ = Mth.floor(bb.maxZ) >> 4; + + EntityLookup entityLookup = this.level().moonrise$getEntityLookup(); + for (int chunkX = minChunkX; chunkX <= maxChunkX; ++chunkX) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; ++chunkZ) { + ChunkEntitySlices chunk = entityLookup.getChunk(chunkX, chunkZ); + + if (chunk == null) { + continue; + } + + for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) { + sliceConsumer.accept(chunk.getSectionEntities(chunkY)); + } + } + } + } + + protected final void impactEntitiesFromPosition(Entity[] entities, Vec3 position, int potential, double radius) { + for (int i = 0; i < entities.length; ++i) { + Entity entity = entities[i]; + if (entity == null) break; + + if (entity != this.source && !entity.ignoreExplosion(this)) { + this.impactEntity(entity, position, potential, radius); + } + + if (entities[i] != entity) { + i--; + } + } + } + + protected final void impactEntity(Entity entity, Vec3 pos, int potential, double radius) { + if (this.excludeSourceFromDamage && entity == this.source) { + return; // for paper api + } + if (entity.isPrimedTNT || entity.isFallingBlock) { + this.impactCannonEntity(entity, pos, potential, radius); + } else { + for (int i = 0; i < potential; ++i) { + super.impactEntity((float) radius, entity); + } + } + } + + protected final void impactCannonEntity(Entity entity, Vec3 pos, int potential, double radius) { + double distanceFromBottom = Math.sqrt(entity.distanceToSqr(pos)) / radius; + + if (distanceFromBottom <= 1.0) { + double x = entity.getX() - pos.x; + double y = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - pos.y; + double z = entity.getZ() - pos.z; + double distance = Math.sqrt(x * x + y * y + z * z); + + if (distance != 0.0D) { + x /= distance; + y /= distance; + z /= distance; + double density = this.getBlockDensity(pos, entity); // Paper - Optimize explosions + double exposure = (1.0D - distanceFromBottom) * density; + + if (exposure == 0.0) { + return; + } + + x *= exposure; + y *= exposure; + z *= exposure; + + this.applyEntityVelocity(entity, x, y, z, potential); + } + } + } + + protected final void applyEntityVelocity(Entity entity, double x, double y, double z, int potential) { + Vec3 movement = entity.getDeltaMovement(); + + double moveX = movement.x(); + double moveY = movement.y(); + double moveZ = movement.z(); + + for (int i = 0; i < potential; ++i) { + moveX += x; + moveY += y; + moveZ += z; + } + + entity.setDeltaMovement(moveX, moveY, moveZ); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java new file mode 100644 index 0000000..ebe5f0c --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java @@ -0,0 +1,201 @@ +package me.samsuik.sakura.explosion.special; + +import ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import me.samsuik.sakura.entity.EntityState; +import me.samsuik.sakura.entity.merge.MergeLevel; +import me.samsuik.sakura.entity.merge.MergeableEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.level.ExplosionDamageCalculator; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.util.CraftVector; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Consumer; + +public final class TntExplosion extends SpecialisedExplosion { + private static final int ALL_DIRECTIONS = 0b111; + private static final int FOUND_ALL_BLOCKS = ALL_DIRECTIONS + 12; + + private final Vec3 originalPosition; + private final List explosions = new ObjectArrayList<>(); + private AABB bounds; + private int wrapped = 0; + private boolean moved = false; + + public TntExplosion(ServerLevel level, PrimedTnt tnt, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 center, float power, boolean createFire, BlockInteraction destructionType, Consumer> applyEffects) { + super(level, tnt, damageSource, behavior, center, power, createFire, destructionType, applyEffects); + this.originalPosition = center; + this.bounds = new AABB(center, center); + } + + @Override + protected int getExplosionCount() { + if (this.cause.getMergeEntityData().getMergeLevel() == MergeLevel.NONE) { + this.mergeEntitiesBeforeExplosion(); + } + return this.cause.getMergeEntityData().getCount(); + } + + @Override + protected void startExplosion() { + for (int i = this.getExplosionCount() - 1; i >= 0; --i) { + Vec3 previousMomentum = this.cause.entityState().momentum(); + boolean lastCycle = i == 0; + List toBlow = this.midExplosion(previousMomentum, lastCycle); // search for blocks and impact entities + boolean destroyedBlocks = this.finalizeExplosionAndParticles(toBlow); + + if (!lastCycle) { + EntityState entityState = this.nextSourceVelocity(); + this.postExplosion(toBlow, destroyedBlocks); // update wrapped, clear recent block cache + this.updateExplosionPosition(entityState, destroyedBlocks); + } + } + } + + private List midExplosion(Vec3 previousMomentum, boolean lastCycle) { + final List explodedPositions; + if (this.wrapped < FOUND_ALL_BLOCKS) { + explodedPositions = this.calculateExplodedPositions(); + } else { + explodedPositions = List.of(); + } + + if (this.wrapped < ALL_DIRECTIONS) { + Vec3 momentum = this.cause.entityState().momentum(); + for (Direction.Axis axis : Direction.Axis.VALUES) { + double current = momentum.get(axis); + double previous = previousMomentum.get(axis); + if (current == previous || current * previous < 0) { + this.wrapped |= 1 << axis.ordinal(); + } + } + } + + this.bounds = this.bounds.expand(this.center); + this.explosions.add(this.center); + + if (lastCycle || this.requiresImpactEntities(explodedPositions, this.center)) { + this.locateAndImpactEntitiesInBounds(); + this.bounds = new AABB(this.center, this.center); + this.explosions.clear(); + } + + return explodedPositions; + } + + @Override + protected void postExplosion(List foundBlocks, boolean destroyedBlocks) { + super.postExplosion(foundBlocks, destroyedBlocks); + // Update wrapped, this is for tracking swinging and if blocks are found + if (this.wrapped >= ALL_DIRECTIONS) { + if (!destroyedBlocks && this.level().sakuraConfig().cannons.explosion.avoidRedundantBlockSearches) { + this.wrapped++; + } else { + this.wrapped = ALL_DIRECTIONS; + } + } + } + + private Vector getCauseOrigin() { + Vector origin = this.cause.getOriginVector(); + return origin == null ? CraftVector.toBukkit(this.center) : origin; + } + + private EntityState nextSourceVelocity() { + Vector origin = this.getCauseOrigin(); // valid position to use while creating a temporary entity + PrimedTnt tnt = new PrimedTnt(this.level(), origin.getX(), origin.getY(), origin.getZ(), null); + this.cause.entityState().apply(tnt); + this.impactCannonEntity(tnt, this.center, 1, this.radius() * 2.0f); + return EntityState.of(tnt); + } + + private void updateExplosionPosition(EntityState entityState, boolean destroyedBlocks) { + // Before setting entity state, otherwise we might cause issues. + final boolean hasMoved; + if (this.moved) { + hasMoved = true; + } else if (this.center.equals(this.cause.position())) { + hasMoved = false; + } else { + double newMomentum = entityState.momentum().lengthSqr(); + double oldMomentum = this.cause.entityState().momentum().lengthSqr(); + hasMoved = oldMomentum <= Math.pow(this.radius() * 2.0 + 1.0, 2.0) || newMomentum <= oldMomentum; + } + + // Keep track of entity state + entityState.apply(this.cause); + this.cause.storeEntityState(); + + // Ticking is always required after destroying a block. + if (destroyedBlocks || hasMoved) { + this.cause.setFuse(100); + this.cause.tick(); + this.moved |= !this.center.equals(this.originalPosition); + this.recalculateExplosionPosition(); + } + } + + private void mergeEntitiesBeforeExplosion() { + IteratorSafeOrderedReferenceSet entities = this.level().entityTickList.entities; + int index = entities.indexOf(this.cause); + + entities.createRawIterator(); + // iterate over the entityTickList to find entities that are exploding in the same position. + while ((index = entities.advanceRawIterator(index)) != -1) { + Entity foundEntity = entities.rawGet(index); + if (!(foundEntity instanceof MergeableEntity mergeEntity) || foundEntity.isRemoved() || !foundEntity.compareState(this.cause) || !mergeEntity.isSafeToMergeInto(this.cause, true)) + break; + this.level().mergeHandler.mergeEntity(mergeEntity, this.cause); + } + entities.finishRawIterator(); + } + + private void locateAndImpactEntitiesInBounds() { + double radius = this.radius() * 2.0f; + AABB bb = this.bounds; + + Vec3 center = bb.getCenter(); + double change = Math.max(bb.getXsize(), Math.max(bb.getYsize(), bb.getZsize())); + double maxDistanceSqr = Math.pow(radius + change, 2.0); + boolean moved = (change != 0.0); + + this.forEachEntitySliceInBounds(bb.inflate(radius), entities -> { + if (moved) { + this.impactEntitiesSwinging(entities, center, radius, maxDistanceSqr); + } else { + this.impactEntitiesFromPosition(entities, this.explosions.getFirst(), this.explosions.size(), radius); + } + }); + } + + private void impactEntitiesSwinging(Entity[] entities, Vec3 center, double radius, double maxDistanceSqr) { + for (int i = 0; i < entities.length; ++i) { + Entity entity = entities[i]; + if (entity == null) break; + + if (entity != this.source && !entity.ignoreExplosion(this) && entity.distanceToSqr(center.x, center.y, center.z) <= maxDistanceSqr) { + this.impactEntity(entity, radius); + } + + if (entities[i] != entity) { + i--; + } + } + } + + private void impactEntity(Entity entity, double radius) { + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < this.explosions.size(); i++) { + this.impactEntity(entity, this.explosions.get(i), 1, radius); + } + } +} diff --git a/sakura-server/src/minecraft/java b/sakura-server/src/minecraft/java index 51af061..6e26d2a 160000 --- a/sakura-server/src/minecraft/java +++ b/sakura-server/src/minecraft/java @@ -1 +1 @@ -Subproject commit 51af06117fa091fdea173dad22bb5774479c0907 +Subproject commit 6e26d2a9a1f70b4a6ae41f86ebd4f319a01989d0 diff --git a/sakura-server/src/minecraft/resources b/sakura-server/src/minecraft/resources index e35daf6..6f9aaa6 160000 --- a/sakura-server/src/minecraft/resources +++ b/sakura-server/src/minecraft/resources @@ -1 +1 @@ -Subproject commit e35daf625db43b9f5ace619d51fda5a104aa9f3e +Subproject commit 6f9aaa646da2dafea1b862ddb45bf0ac5b6e8a63