339 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			339 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.level;
 | |
| 
 | |
| import it.unimi.dsi.fastutil.objects.ObjectArrayList;
 | |
| import java.util.ArrayList;
 | |
| import java.util.HashMap;
 | |
| import java.util.HashSet;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| import java.util.Optional;
 | |
| import java.util.Set;
 | |
| import net.minecraft.Util;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.server.level.ServerLevel;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.util.profiling.Profiler;
 | |
| import net.minecraft.util.profiling.ProfilerFiller;
 | |
| import net.minecraft.world.damagesource.DamageSource;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| 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.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 org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| public class ServerExplosion implements Explosion {
 | |
| 	private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator();
 | |
| 	private static final int MAX_DROPS_PER_COMBINED_STACK = 16;
 | |
| 	private static final float LARGE_EXPLOSION_RADIUS = 2.0F;
 | |
| 	private final boolean fire;
 | |
| 	private final Explosion.BlockInteraction blockInteraction;
 | |
| 	private final ServerLevel level;
 | |
| 	private final Vec3 center;
 | |
| 	@Nullable
 | |
| 	private final Entity source;
 | |
| 	private final float radius;
 | |
| 	private final DamageSource damageSource;
 | |
| 	private final ExplosionDamageCalculator damageCalculator;
 | |
| 	private final Map<Player, Vec3> hitPlayers = new HashMap();
 | |
| 
 | |
| 	public ServerExplosion(
 | |
| 		ServerLevel level,
 | |
| 		@Nullable Entity source,
 | |
| 		@Nullable DamageSource damageSource,
 | |
| 		@Nullable ExplosionDamageCalculator damageCalculator,
 | |
| 		Vec3 center,
 | |
| 		float radius,
 | |
| 		boolean fire,
 | |
| 		Explosion.BlockInteraction blockInteraction
 | |
| 	) {
 | |
| 		this.level = level;
 | |
| 		this.source = source;
 | |
| 		this.radius = radius;
 | |
| 		this.center = center;
 | |
| 		this.fire = fire;
 | |
| 		this.blockInteraction = blockInteraction;
 | |
| 		this.damageSource = damageSource == null ? level.damageSources().explosion(this) : damageSource;
 | |
| 		this.damageCalculator = damageCalculator == null ? this.makeDamageCalculator(source) : damageCalculator;
 | |
| 	}
 | |
| 
 | |
| 	private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
 | |
| 		return (ExplosionDamageCalculator)(entity == null ? EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity));
 | |
| 	}
 | |
| 
 | |
| 	public static float getSeenPercent(Vec3 explosionVector, Entity entity) {
 | |
| 		AABB aABB = entity.getBoundingBox();
 | |
| 		double d = 1.0 / ((aABB.maxX - aABB.minX) * 2.0 + 1.0);
 | |
| 		double e = 1.0 / ((aABB.maxY - aABB.minY) * 2.0 + 1.0);
 | |
| 		double f = 1.0 / ((aABB.maxZ - aABB.minZ) * 2.0 + 1.0);
 | |
| 		double g = (1.0 - Math.floor(1.0 / d) * d) / 2.0;
 | |
| 		double h = (1.0 - Math.floor(1.0 / f) * f) / 2.0;
 | |
| 		if (!(d < 0.0) && !(e < 0.0) && !(f < 0.0)) {
 | |
| 			int i = 0;
 | |
| 			int j = 0;
 | |
| 
 | |
| 			for (double k = 0.0; k <= 1.0; k += d) {
 | |
| 				for (double l = 0.0; l <= 1.0; l += e) {
 | |
| 					for (double m = 0.0; m <= 1.0; m += f) {
 | |
| 						double n = Mth.lerp(k, aABB.minX, aABB.maxX);
 | |
| 						double o = Mth.lerp(l, aABB.minY, aABB.maxY);
 | |
| 						double p = Mth.lerp(m, aABB.minZ, aABB.maxZ);
 | |
| 						Vec3 vec3 = new Vec3(n + g, o, p + h);
 | |
| 						if (entity.level().clip(new ClipContext(vec3, explosionVector, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)).getType()
 | |
| 							== HitResult.Type.MISS) {
 | |
| 							i++;
 | |
| 						}
 | |
| 
 | |
| 						j++;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return (float)i / j;
 | |
| 		} else {
 | |
| 			return 0.0F;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public float radius() {
 | |
| 		return this.radius;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public Vec3 center() {
 | |
| 		return this.center;
 | |
| 	}
 | |
| 
 | |
| 	private List<BlockPos> calculateExplodedPositions() {
 | |
| 		Set<BlockPos> set = new HashSet();
 | |
| 		int i = 16;
 | |
| 
 | |
| 		for (int j = 0; j < 16; j++) {
 | |
| 			for (int k = 0; k < 16; k++) {
 | |
| 				for (int l = 0; l < 16; l++) {
 | |
| 					if (j == 0 || j == 15 || k == 0 || k == 15 || l == 0 || l == 15) {
 | |
| 						double d = j / 15.0F * 2.0F - 1.0F;
 | |
| 						double e = k / 15.0F * 2.0F - 1.0F;
 | |
| 						double f = l / 15.0F * 2.0F - 1.0F;
 | |
| 						double g = Math.sqrt(d * d + e * e + f * f);
 | |
| 						d /= g;
 | |
| 						e /= g;
 | |
| 						f /= g;
 | |
| 						float h = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
 | |
| 						double m = this.center.x;
 | |
| 						double n = this.center.y;
 | |
| 						double o = this.center.z;
 | |
| 
 | |
| 						for (float p = 0.3F; h > 0.0F; h -= 0.22500001F) {
 | |
| 							BlockPos blockPos = BlockPos.containing(m, n, o);
 | |
| 							BlockState blockState = this.level.getBlockState(blockPos);
 | |
| 							FluidState fluidState = this.level.getFluidState(blockPos);
 | |
| 							if (!this.level.isInWorldBounds(blockPos)) {
 | |
| 								break;
 | |
| 							}
 | |
| 
 | |
| 							Optional<Float> optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockPos, blockState, fluidState);
 | |
| 							if (optional.isPresent()) {
 | |
| 								h -= (optional.get() + 0.3F) * 0.3F;
 | |
| 							}
 | |
| 
 | |
| 							if (h > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, h)) {
 | |
| 								set.add(blockPos);
 | |
| 							}
 | |
| 
 | |
| 							m += d * 0.3F;
 | |
| 							n += e * 0.3F;
 | |
| 							o += f * 0.3F;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return new ObjectArrayList<>(set);
 | |
| 	}
 | |
| 
 | |
| 	private void hurtEntities() {
 | |
| 		float f = this.radius * 2.0F;
 | |
| 		int i = Mth.floor(this.center.x - f - 1.0);
 | |
| 		int j = Mth.floor(this.center.x + f + 1.0);
 | |
| 		int k = Mth.floor(this.center.y - f - 1.0);
 | |
| 		int l = Mth.floor(this.center.y + f + 1.0);
 | |
| 		int m = Mth.floor(this.center.z - f - 1.0);
 | |
| 		int n = Mth.floor(this.center.z + f + 1.0);
 | |
| 
 | |
| 		for (Entity entity : this.level.getEntities(this.source, new AABB(i, k, m, j, l, n))) {
 | |
| 			if (!entity.ignoreExplosion(this)) {
 | |
| 				double d = Math.sqrt(entity.distanceToSqr(this.center)) / f;
 | |
| 				if (d <= 1.0) {
 | |
| 					double e = entity.getX() - this.center.x;
 | |
| 					double g = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.center.y;
 | |
| 					double h = entity.getZ() - this.center.z;
 | |
| 					double o = Math.sqrt(e * e + g * g + h * h);
 | |
| 					if (o != 0.0) {
 | |
| 						e /= o;
 | |
| 						g /= o;
 | |
| 						h /= o;
 | |
| 						boolean bl = this.damageCalculator.shouldDamageEntity(this, entity);
 | |
| 						float p = this.damageCalculator.getKnockbackMultiplier(entity);
 | |
| 						float q = !bl && p == 0.0F ? 0.0F : getSeenPercent(this.center, entity);
 | |
| 						if (bl) {
 | |
| 							entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, q));
 | |
| 						}
 | |
| 
 | |
| 						double r = (1.0 - d) * q * p;
 | |
| 						double s;
 | |
| 						if (entity instanceof LivingEntity livingEntity) {
 | |
| 							s = r * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE));
 | |
| 						} else {
 | |
| 							s = r;
 | |
| 						}
 | |
| 
 | |
| 						e *= s;
 | |
| 						g *= s;
 | |
| 						h *= s;
 | |
| 						Vec3 vec3 = new Vec3(e, g, h);
 | |
| 						entity.push(vec3);
 | |
| 						if (entity instanceof Player player && !player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying)) {
 | |
| 							this.hitPlayers.put(player, vec3);
 | |
| 						}
 | |
| 
 | |
| 						entity.onExplosionHit(this.source);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void interactWithBlocks(List<BlockPos> blocks) {
 | |
| 		List<ServerExplosion.StackCollector> list = new ArrayList();
 | |
| 		Util.shuffle(blocks, this.level.random);
 | |
| 
 | |
| 		for (BlockPos blockPos : blocks) {
 | |
| 			this.level.getBlockState(blockPos).onExplosionHit(this.level, blockPos, this, (itemStack, blockPosx) -> addOrAppendStack(list, itemStack, blockPosx));
 | |
| 		}
 | |
| 
 | |
| 		for (ServerExplosion.StackCollector stackCollector : list) {
 | |
| 			Block.popResource(this.level, stackCollector.pos, stackCollector.stack);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void createFire(List<BlockPos> blocks) {
 | |
| 		for (BlockPos blockPos : blocks) {
 | |
| 			if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockPos).isAir() && this.level.getBlockState(blockPos.below()).isSolidRender()) {
 | |
| 				this.level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level, blockPos));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void explode() {
 | |
| 		this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center);
 | |
| 		List<BlockPos> list = this.calculateExplodedPositions();
 | |
| 		this.hurtEntities();
 | |
| 		if (this.interactsWithBlocks()) {
 | |
| 			ProfilerFiller profilerFiller = Profiler.get();
 | |
| 			profilerFiller.push("explosion_blocks");
 | |
| 			this.interactWithBlocks(list);
 | |
| 			profilerFiller.pop();
 | |
| 		}
 | |
| 
 | |
| 		if (this.fire) {
 | |
| 			this.createFire(list);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static void addOrAppendStack(List<ServerExplosion.StackCollector> stackCollectors, ItemStack stack, BlockPos pos) {
 | |
| 		for (ServerExplosion.StackCollector stackCollector : stackCollectors) {
 | |
| 			stackCollector.tryMerge(stack);
 | |
| 			if (stack.isEmpty()) {
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		stackCollectors.add(new ServerExplosion.StackCollector(pos, stack));
 | |
| 	}
 | |
| 
 | |
| 	private boolean interactsWithBlocks() {
 | |
| 		return this.blockInteraction != Explosion.BlockInteraction.KEEP;
 | |
| 	}
 | |
| 
 | |
| 	public Map<Player, Vec3> getHitPlayers() {
 | |
| 		return this.hitPlayers;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public ServerLevel level() {
 | |
| 		return this.level;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public LivingEntity getIndirectSourceEntity() {
 | |
| 		return Explosion.getIndirectSourceEntity(this.source);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public Entity getDirectSourceEntity() {
 | |
| 		return this.source;
 | |
| 	}
 | |
| 
 | |
| 	public DamageSource getDamageSource() {
 | |
| 		return this.damageSource;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public Explosion.BlockInteraction getBlockInteraction() {
 | |
| 		return this.blockInteraction;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean canTriggerBlocks() {
 | |
| 		if (this.blockInteraction != Explosion.BlockInteraction.TRIGGER_BLOCK) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			return this.source != null && this.source.getType() == EntityType.BREEZE_WIND_CHARGE
 | |
| 				? this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
 | |
| 				: true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean shouldAffectBlocklikeEntities() {
 | |
| 		boolean bl = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
 | |
| 		boolean bl2 = this.source == null || this.source.getType() != EntityType.BREEZE_WIND_CHARGE && this.source.getType() != EntityType.WIND_CHARGE;
 | |
| 		return bl ? bl2 : this.blockInteraction.shouldAffectBlocklikeEntities() && bl2;
 | |
| 	}
 | |
| 
 | |
| 	public boolean isSmall() {
 | |
| 		return this.radius < 2.0F || !this.interactsWithBlocks();
 | |
| 	}
 | |
| 
 | |
| 	static class StackCollector {
 | |
| 		final BlockPos pos;
 | |
| 		ItemStack stack;
 | |
| 
 | |
| 		StackCollector(BlockPos pos, ItemStack stack) {
 | |
| 			this.pos = pos;
 | |
| 			this.stack = stack;
 | |
| 		}
 | |
| 
 | |
| 		public void tryMerge(ItemStack stack) {
 | |
| 			if (ItemEntity.areMergable(this.stack, stack)) {
 | |
| 				this.stack = ItemEntity.merge(this.stack, stack, 16);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |