mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-06 18:50:51 +01:00
eb626e1176
This fixes exploits that let players destroy bedrock by Pistons, explosions and Mushrooom/Tree generation. These blocks are designed to not be broken except by creative players/commands. So protect them from a multitude of methods of destroying them. A config is provided if you rather let players use these exploits, and let them destroy the worlds End Portals and get on top of the nether easy.
342 lines
18 KiB
Diff
342 lines
18 KiB
Diff
--- a/net/minecraft/world/level/ServerExplosion.java
|
|
+++ b/net/minecraft/world/level/ServerExplosion.java
|
|
@@ -22,18 +22,27 @@
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
+import net.minecraft.world.entity.boss.EnderDragonPart;
|
|
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.item.PrimedTnt;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.block.BaseFireBlock;
|
|
import net.minecraft.world.level.block.Block;
|
|
-import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.material.FluidState;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.HitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
+import net.minecraft.world.level.block.Blocks;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import org.bukkit.craftbukkit.event.CraftEventFactory;
|
|
+import org.bukkit.craftbukkit.util.CraftLocation;
|
|
+import org.bukkit.event.entity.EntityExplodeEvent;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.event.block.BlockExplodeEvent;
|
|
+// CraftBukkit end
|
|
|
|
public class ServerExplosion implements Explosion {
|
|
|
|
@@ -50,16 +59,22 @@
|
|
private final DamageSource damageSource;
|
|
private final ExplosionDamageCalculator damageCalculator;
|
|
private final Map<Player, Vec3> hitPlayers = new HashMap();
|
|
+ // CraftBukkit - add field
|
|
+ public boolean wasCanceled = false;
|
|
+ public float yield;
|
|
+ // CraftBukkit end
|
|
+ public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source
|
|
|
|
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;
|
|
this.source = entity;
|
|
- this.radius = power;
|
|
+ this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values
|
|
this.center = pos;
|
|
this.fire = createFire;
|
|
this.blockInteraction = destructionType;
|
|
this.damageSource = damageSource == null ? world.damageSources().explosion(this) : damageSource;
|
|
this.damageCalculator = behavior == null ? this.makeDamageCalculator(entity) : behavior;
|
|
+ this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
|
|
}
|
|
|
|
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
|
|
@@ -135,7 +150,8 @@
|
|
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
|
|
BlockPos blockposition = BlockPos.containing(d4, d5, d6);
|
|
BlockState iblockdata = this.level.getBlockState(blockposition);
|
|
- FluidState fluid = this.level.getFluidState(blockposition);
|
|
+ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
|
|
+ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
|
|
|
|
if (!this.level.isInWorldBounds(blockposition)) {
|
|
break;
|
|
@@ -149,6 +165,15 @@
|
|
|
|
if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) {
|
|
set.add(blockposition);
|
|
+ // Paper start - prevent headless pistons from forming
|
|
+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
|
|
+ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition);
|
|
+ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
|
|
+ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
|
|
+ set.add(blockposition.relative(direction.getOpposite()));
|
|
+ }
|
|
+ }
|
|
+ // Paper end - prevent headless pistons from forming
|
|
}
|
|
|
|
d4 += d0 * 0.30000001192092896D;
|
|
@@ -171,7 +196,7 @@
|
|
int l = Mth.floor(this.center.y + (double) f + 1.0D);
|
|
int i1 = Mth.floor(this.center.z - (double) f - 1.0D);
|
|
int j1 = Mth.floor(this.center.z + (double) f + 1.0D);
|
|
- List<Entity> list = this.level.getEntities(this.source, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1));
|
|
+ List<Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate<Entity>) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
|
|
Iterator iterator = list.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
@@ -192,10 +217,38 @@
|
|
d3 /= d4;
|
|
boolean flag = this.damageCalculator.shouldDamageEntity(this, entity);
|
|
float f1 = this.damageCalculator.getKnockbackMultiplier(entity);
|
|
- float f2 = !flag && f1 == 0.0F ? 0.0F : ServerExplosion.getSeenPercent(this.center, entity);
|
|
+ float f2 = !flag && f1 == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions
|
|
|
|
if (flag) {
|
|
- entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
|
|
+ // CraftBukkit start
|
|
+
|
|
+ // Special case ender dragon only give knockback if no damage is cancelled
|
|
+ // Thinks to note:
|
|
+ // - Setting a velocity to a ComplexEntityPart is ignored (and therefore not needed)
|
|
+ // - Damaging ComplexEntityPart while forward the damage to EntityEnderDragon
|
|
+ // - Damaging EntityEnderDragon does nothing
|
|
+ // - EntityEnderDragon hitbock always covers the other parts and is therefore always present
|
|
+ if (entity instanceof EnderDragonPart) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ entity.lastDamageCancelled = false;
|
|
+
|
|
+ if (entity instanceof EnderDragon) {
|
|
+ for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
|
|
+ // Calculate damage separately for each EntityComplexPart
|
|
+ if (list.contains(entityComplexPart)) {
|
|
+ entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
|
|
+ }
|
|
+
|
|
+ if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
|
|
+ continue;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
double d5 = (1.0D - d0) * (double) f2 * (double) f1;
|
|
@@ -204,7 +257,7 @@
|
|
if (entity instanceof LivingEntity) {
|
|
LivingEntity entityliving = (LivingEntity) entity;
|
|
|
|
- d6 = d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE));
|
|
+ d6 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); // Paper
|
|
} else {
|
|
d6 = d5;
|
|
}
|
|
@@ -214,11 +267,19 @@
|
|
d3 *= d6;
|
|
Vec3 vec3d = new Vec3(d1, d2, d3);
|
|
|
|
+ // CraftBukkit start - Call EntityKnockbackEvent
|
|
+ if (entity instanceof LivingEntity) {
|
|
+ // Paper start - knockback events
|
|
+ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d6, vec3d);
|
|
+ vec3d = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback());
|
|
+ // Paper end - knockback events
|
|
+ }
|
|
+ // CraftBukkit end
|
|
entity.push(vec3d);
|
|
if (entity instanceof Player) {
|
|
Player entityhuman = (Player) entity;
|
|
|
|
- if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying)) {
|
|
+ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback
|
|
this.hitPlayers.put(entityhuman, vec3d);
|
|
}
|
|
}
|
|
@@ -235,10 +296,62 @@
|
|
List<ServerExplosion.StackCollector> list1 = new ArrayList();
|
|
|
|
Util.shuffle(positions, this.level.random);
|
|
+ // CraftBukkit start
|
|
+ org.bukkit.World bworld = this.level.getWorld();
|
|
+ Location location = CraftLocation.toBukkit(this.center, bworld);
|
|
+
|
|
+ List<org.bukkit.block.Block> blockList = new ObjectArrayList<>();
|
|
+ for (int i1 = positions.size() - 1; i1 >= 0; i1--) {
|
|
+ BlockPos cpos = positions.get(i1);
|
|
+ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ());
|
|
+ if (!bblock.getType().isAir()) {
|
|
+ blockList.add(bblock);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ List<org.bukkit.block.Block> bukkitBlocks;
|
|
+
|
|
+ if (this.source != null) {
|
|
+ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blockList, this.yield, this.getBlockInteraction());
|
|
+ this.wasCanceled = event.isCancelled();
|
|
+ bukkitBlocks = event.blockList();
|
|
+ this.yield = event.getYield();
|
|
+ } else {
|
|
+ org.bukkit.block.Block block = location.getBlock();
|
|
+ org.bukkit.block.BlockState blockState = (this.damageSource.getDirectBlockState() != null) ? this.damageSource.getDirectBlockState() : block.getState();
|
|
+ BlockExplodeEvent event = CraftEventFactory.callBlockExplodeEvent(block, blockState, blockList, this.yield, this.getBlockInteraction());
|
|
+ this.wasCanceled = event.isCancelled();
|
|
+ bukkitBlocks = event.blockList();
|
|
+ this.yield = event.getYield();
|
|
+ }
|
|
+
|
|
+ positions.clear();
|
|
+
|
|
+ for (org.bukkit.block.Block bblock : bukkitBlocks) {
|
|
+ BlockPos coords = new BlockPos(bblock.getX(), bblock.getY(), bblock.getZ());
|
|
+ positions.add(coords);
|
|
+ }
|
|
+
|
|
+ if (this.wasCanceled) {
|
|
+ return;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
Iterator iterator = positions.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
BlockPos blockposition = (BlockPos) iterator.next();
|
|
+ // CraftBukkit start - TNTPrimeEvent
|
|
+ BlockState iblockdata = this.level.getBlockState(blockposition);
|
|
+ Block block = iblockdata.getBlock();
|
|
+ if (block instanceof net.minecraft.world.level.block.TntBlock) {
|
|
+ Entity sourceEntity = this.source == null ? null : this.source;
|
|
+ BlockPos sourceBlock = sourceEntity == null ? BlockPos.containing(this.center) : null;
|
|
+ if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) {
|
|
+ this.level.sendBlockUpdated(blockposition, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
|
|
this.level.getBlockState(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> {
|
|
ServerExplosion.addOrAppendStack(list1, itemstack, blockposition1);
|
|
@@ -262,13 +375,22 @@
|
|
BlockPos blockposition = (BlockPos) iterator.next();
|
|
|
|
if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockposition).isAir() && this.level.getBlockState(blockposition.below()).isSolidRender()) {
|
|
- this.level.setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level, blockposition));
|
|
+ // CraftBukkit start - Ignition by explosion
|
|
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockposition, this).isCancelled()) {
|
|
+ this.level.setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level, blockposition));
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public void explode() {
|
|
+ // CraftBukkit start
|
|
+ if (this.radius < 0.1F) {
|
|
+ return;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center);
|
|
List<BlockPos> list = this.calculateExplodedPositions();
|
|
|
|
@@ -288,6 +410,7 @@
|
|
}
|
|
|
|
private static void addOrAppendStack(List<ServerExplosion.StackCollector> droppedItemsOut, ItemStack item, BlockPos pos) {
|
|
+ if (item.isEmpty()) return; // CraftBukkit - SPIGOT-5425
|
|
Iterator iterator = droppedItemsOut.iterator();
|
|
|
|
do {
|
|
@@ -372,4 +495,85 @@
|
|
|
|
}
|
|
}
|
|
+
|
|
+ // Paper start - Optimize explosions
|
|
+ private float getBlockDensity(Vec3 vec3d, Entity entity) {
|
|
+ if (!this.level.paperConfig().environment.optimizeExplosions) {
|
|
+ return getSeenPercent(vec3d, entity);
|
|
+ }
|
|
+ CacheKey key = new CacheKey(this, entity.getBoundingBox());
|
|
+ Float blockDensity = this.level.explosionDensityCache.get(key);
|
|
+ if (blockDensity == null) {
|
|
+ blockDensity = getSeenPercent(vec3d, entity);
|
|
+ this.level.explosionDensityCache.put(key, blockDensity);
|
|
+ }
|
|
+
|
|
+ return blockDensity;
|
|
+ }
|
|
+
|
|
+ static class CacheKey {
|
|
+ private final Level world;
|
|
+ private final double posX, posY, posZ;
|
|
+ private final double minX, minY, minZ;
|
|
+ private final double maxX, maxY, maxZ;
|
|
+
|
|
+ public CacheKey(Explosion explosion, AABB aabb) {
|
|
+ this.world = explosion.level();
|
|
+ this.posX = explosion.center().x;
|
|
+ this.posY = explosion.center().y;
|
|
+ this.posZ = explosion.center().z;
|
|
+ this.minX = aabb.minX;
|
|
+ this.minY = aabb.minY;
|
|
+ this.minZ = aabb.minZ;
|
|
+ this.maxX = aabb.maxX;
|
|
+ this.maxY = aabb.maxY;
|
|
+ this.maxZ = aabb.maxZ;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean equals(Object o) {
|
|
+ if (this == o) return true;
|
|
+ if (o == null || getClass() != o.getClass()) return false;
|
|
+
|
|
+ CacheKey cacheKey = (CacheKey) o;
|
|
+
|
|
+ if (Double.compare(cacheKey.posX, posX) != 0) return false;
|
|
+ if (Double.compare(cacheKey.posY, posY) != 0) return false;
|
|
+ if (Double.compare(cacheKey.posZ, posZ) != 0) return false;
|
|
+ if (Double.compare(cacheKey.minX, minX) != 0) return false;
|
|
+ if (Double.compare(cacheKey.minY, minY) != 0) return false;
|
|
+ if (Double.compare(cacheKey.minZ, minZ) != 0) return false;
|
|
+ if (Double.compare(cacheKey.maxX, maxX) != 0) return false;
|
|
+ if (Double.compare(cacheKey.maxY, maxY) != 0) return false;
|
|
+ if (Double.compare(cacheKey.maxZ, maxZ) != 0) return false;
|
|
+ return world.equals(cacheKey.world);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int hashCode() {
|
|
+ int result;
|
|
+ long temp;
|
|
+ result = world.hashCode();
|
|
+ temp = Double.doubleToLongBits(posX);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(posY);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(posZ);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(minX);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(minY);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(minZ);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(maxX);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(maxY);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ temp = Double.doubleToLongBits(maxZ);
|
|
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
|
|
+ return result;
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
}
|