mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-29 03:39:07 +00:00
Half way through feature patches
This commit is contained in:
@@ -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<DensityData> 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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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<BlockPos, DurableBlock> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T extends Entity> 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<SpecialisedExplosion<T>> applyEffects;
|
||||
|
||||
public SpecialisedExplosion(ServerLevel level, T entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 center, float power, boolean createFire, BlockInteraction destructionType, Consumer<SpecialisedExplosion<T>> 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<BlockPos> blocks, Vec3 center) {
|
||||
if (this.impactPosition.distanceToSqr(center) > ENTITY_DISPATCH_DISTANCE) {
|
||||
this.impactPosition = center;
|
||||
return true;
|
||||
}
|
||||
return !blocks.isEmpty();
|
||||
}
|
||||
|
||||
protected final boolean finalizeExplosionAndParticles(List<BlockPos> blocks) {
|
||||
this.wasCanceled = false;
|
||||
List<BlockPos> explodedPositions = new ObjectArrayList<>(blocks);
|
||||
this.interactWithBlocks(explodedPositions);
|
||||
|
||||
if (!this.wasCanceled) {
|
||||
this.applyEffects.accept(this);
|
||||
this.getHitPlayers().clear();
|
||||
}
|
||||
|
||||
return !explodedPositions.isEmpty();
|
||||
}
|
||||
|
||||
protected void postExplosion(List<BlockPos> 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<Entity[]> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<PrimedTnt> {
|
||||
private static final int ALL_DIRECTIONS = 0b111;
|
||||
private static final int FOUND_ALL_BLOCKS = ALL_DIRECTIONS + 12;
|
||||
|
||||
private final Vec3 originalPosition;
|
||||
private final List<Vec3> 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<SpecialisedExplosion<PrimedTnt>> 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<BlockPos> 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<BlockPos> midExplosion(Vec3 previousMomentum, boolean lastCycle) {
|
||||
final List<BlockPos> 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<BlockPos> 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<Entity> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user