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/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java b/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java new file mode 100644 index 0000000000000000000000000000000000000000..ce04a5d9aaef3ca8ba8d7b988bdf0497285f90c1 --- /dev/null +++ b/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/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java b/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java new file mode 100644 index 0000000000000000000000000000000000000000..ebe5f0c8c2f09920b5f5ef734e63f5e7cd8bd3a1 --- /dev/null +++ b/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/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 1e940c5b9ce60dd1dd7ec03e83766e26d1112d73..1973da88ac7c3a36cc2db48aeb7d4eab788be500 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -1913,7 +1913,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe Explosion.BlockInteraction explosion_effect1 = explosion_effect; Vec3 vec3d = new Vec3(d0, d1, d2); - ServerExplosion serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1); + // Sakura start - specialised explosions + final ServerExplosion serverexplosion; + if (entity instanceof net.minecraft.world.entity.item.PrimedTnt tnt) { + serverexplosion = new me.samsuik.sakura.explosion.special.TntExplosion(this, tnt, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1, self -> { + this.notifyPlayersOfExplosion(self, self.center(), particleparam, particleparam1, holder); + }); + } else { + serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1); + } + // Sakura end - specialised explosions if (configurator != null) configurator.accept(serverexplosion);// Paper - Allow explosions to damage source serverexplosion.explode(); @@ -1922,6 +1931,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return serverexplosion; } // CraftBukkit end + // Sakura start - specialised explosions + this.notifyPlayersOfExplosion(serverexplosion, vec3d, particleparam, particleparam1, holder); + return serverexplosion; + } + + private void notifyPlayersOfExplosion(ServerExplosion serverexplosion, Vec3 vec3d, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder) { + // Sakura end - specialised explosions ParticleOptions particleparam2 = serverexplosion.isSmall() ? particleparam : particleparam1; Iterator iterator = this.players.iterator(); @@ -1946,7 +1962,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } } - return serverexplosion; // CraftBukkit + // Sakura - specialised explosions; return moved up into explode } private Explosion.BlockInteraction getDestroyType(GameRules.Key decayRule) { diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java index f7d8e2ba0ee9b8dd27f20a3e75992b107d07fbf0..187cb36e139ce66497e4e20ce75c0a5c4309632e 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -78,20 +78,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/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java index 190eca0e25a8761f88d15a5eac02d9a67b5635de..74abe5fe92b3f1fe0139f4879fe5efa10d0623e0 100644 --- a/src/main/java/net/minecraft/world/level/ServerExplosion.java +++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java @@ -52,9 +52,9 @@ public class ServerExplosion implements Explosion { private final boolean fire; private final Explosion.BlockInteraction blockInteraction; private final ServerLevel level; - private final Vec3 center; + protected Vec3 center; // Sakura - specialised explosions; private final -> protected @Nullable - private final Entity source; + protected final Entity source; // Sakura - specialised explosions; private -> protected private final float radius; private final DamageSource damageSource; private final ExplosionDamageCalculator damageCalculator; @@ -103,13 +103,13 @@ public class ServerExplosion implements Explosion { // resistance = (res + 0.3F) * 0.3F; // so for resistance = 0, we need res = -0.3F private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f); - private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; + protected it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; // Sakura - specialised explosions; private -> protected private long[] chunkPosCache = null; private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null; - private ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] directMappedBlockCache; + protected ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] directMappedBlockCache; // Sakura - specialised explosions; private -> protected private BlockPos.MutableBlockPos mutablePos; - private ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, + protected final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, // Sakura - specialised explosions; private -> protected final long key, final boolean calculateResistance) { ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache ret = this.blockCache.get(key); if (ret != null) { @@ -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 world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) { this.level = world; @@ -432,7 +464,7 @@ public class ServerExplosion implements Explosion { return this.center; } - private List calculateExplodedPositions() { + protected final List calculateExplodedPositions() { // Sakura - specialised explosions; private -> protected // Paper start - collision optimisations final ObjectArrayList ret = new ObjectArrayList<>(); @@ -670,7 +702,10 @@ public class ServerExplosion implements Explosion { Player entityhuman = (Player) entity; if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback - this.hitPlayers.put(entityhuman, vec3d); + // Sakura start - specialised explosions; tally player velocity + final Vec3 explosionImpact = vec3d; + this.hitPlayers.compute(entityhuman, (p, v) -> v != null ? v.add(explosionImpact) : explosionImpact); + // Sakura end - specialised explosions; tally player velocity } } @@ -682,7 +717,7 @@ public class ServerExplosion implements Explosion { } - private void interactWithBlocks(List positions) { + protected final void interactWithBlocks(List positions) { // Sakura - specialised explosions; private -> protected List list1 = new ArrayList(); Util.shuffle(positions, this.level.random); @@ -787,14 +822,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, (Holder) GameEvent.EXPLODE, this.center); List list = this.calculateExplodedPositions(); @@ -810,13 +838,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 } @@ -837,7 +859,7 @@ public class ServerExplosion implements Explosion { } - private boolean interactsWithBlocks() { + protected final boolean interactsWithBlocks() { // Sakura - specialised explosions; private -> protected return this.blockInteraction != Explosion.BlockInteraction.KEEP; } @@ -908,7 +930,7 @@ public class ServerExplosion implements Explosion { } // Paper start - Optimize explosions - private float getBlockDensity(Vec3 vec3d, Entity entity) { + protected final float getBlockDensity(Vec3 vec3d, Entity entity) { // Sakura - specialised explosions; private -> protected // Sakura start - replace density cache float blockDensity = this.level.densityCache.getDensity(vec3d, entity); if (blockDensity == me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {