minecraft-src/net/minecraft/world/entity/LightningBolt.java
2025-07-04 02:49:36 +03:00

254 lines
8.1 KiB
Java

package net.minecraft.world.entity;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.SynchedEntityData.Builder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseFireBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LightningRodBlock;
import net.minecraft.world.level.block.WeatheringCopper;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class LightningBolt extends Entity {
private static final int START_LIFE = 2;
private static final double DAMAGE_RADIUS = 3.0;
private static final double DETECTION_RADIUS = 15.0;
private int life;
public long seed;
private int flashes;
private boolean visualOnly;
@Nullable
private ServerPlayer cause;
private final Set<Entity> hitEntities = Sets.<Entity>newHashSet();
private int blocksSetOnFire;
public LightningBolt(EntityType<? extends LightningBolt> entityType, Level level) {
super(entityType, level);
this.life = 2;
this.seed = this.random.nextLong();
this.flashes = this.random.nextInt(3) + 1;
}
public void setVisualOnly(boolean visualOnly) {
this.visualOnly = visualOnly;
}
@Override
public SoundSource getSoundSource() {
return SoundSource.WEATHER;
}
@Nullable
public ServerPlayer getCause() {
return this.cause;
}
public void setCause(@Nullable ServerPlayer cause) {
this.cause = cause;
}
private void powerLightningRod() {
BlockPos blockPos = this.getStrikePosition();
BlockState blockState = this.level().getBlockState(blockPos);
if (blockState.is(Blocks.LIGHTNING_ROD)) {
((LightningRodBlock)blockState.getBlock()).onLightningStrike(blockState, this.level(), blockPos);
}
}
@Override
public void tick() {
super.tick();
if (this.life == 2) {
if (this.level().isClientSide()) {
this.level()
.playLocalSound(
this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false
);
this.level()
.playLocalSound(
this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false
);
} else {
Difficulty difficulty = this.level().getDifficulty();
if (difficulty == Difficulty.NORMAL || difficulty == Difficulty.HARD) {
this.spawnFire(4);
}
this.powerLightningRod();
clearCopperOnLightningStrike(this.level(), this.getStrikePosition());
this.gameEvent(GameEvent.LIGHTNING_STRIKE);
}
}
this.life--;
if (this.life < 0) {
if (this.flashes == 0) {
if (this.level() instanceof ServerLevel) {
List<Entity> list = this.level()
.getEntities(
this,
new AABB(this.getX() - 15.0, this.getY() - 15.0, this.getZ() - 15.0, this.getX() + 15.0, this.getY() + 6.0 + 15.0, this.getZ() + 15.0),
entityx -> entityx.isAlive() && !this.hitEntities.contains(entityx)
);
for (ServerPlayer serverPlayer : ((ServerLevel)this.level()).getPlayers(serverPlayerx -> serverPlayerx.distanceTo(this) < 256.0F)) {
CriteriaTriggers.LIGHTNING_STRIKE.trigger(serverPlayer, this, list);
}
}
this.discard();
} else if (this.life < -this.random.nextInt(10)) {
this.flashes--;
this.life = 1;
this.seed = this.random.nextLong();
this.spawnFire(0);
}
}
if (this.life >= 0) {
if (!(this.level() instanceof ServerLevel)) {
this.level().setSkyFlashTime(2);
} else if (!this.visualOnly) {
List<Entity> list = this.level()
.getEntities(
this, new AABB(this.getX() - 3.0, this.getY() - 3.0, this.getZ() - 3.0, this.getX() + 3.0, this.getY() + 6.0 + 3.0, this.getZ() + 3.0), Entity::isAlive
);
for (Entity entity : list) {
entity.thunderHit((ServerLevel)this.level(), this);
}
this.hitEntities.addAll(list);
if (this.cause != null) {
CriteriaTriggers.CHANNELED_LIGHTNING.trigger(this.cause, list);
}
}
}
}
private BlockPos getStrikePosition() {
Vec3 vec3 = this.position();
return BlockPos.containing(vec3.x, vec3.y - 1.0E-6, vec3.z);
}
private void spawnFire(int extraIgnitions) {
if (!this.visualOnly && this.level() instanceof ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
BlockPos blockPos = this.blockPosition();
BlockState blockState = BaseFireBlock.getState(this.level(), blockPos);
if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) {
this.level().setBlockAndUpdate(blockPos, blockState);
this.blocksSetOnFire++;
}
for (int i = 0; i < extraIgnitions; i++) {
BlockPos blockPos2 = blockPos.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);
blockState = BaseFireBlock.getState(this.level(), blockPos2);
if (this.level().getBlockState(blockPos2).isAir() && blockState.canSurvive(this.level(), blockPos2)) {
this.level().setBlockAndUpdate(blockPos2, blockState);
this.blocksSetOnFire++;
}
}
}
}
private static void clearCopperOnLightningStrike(Level level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos);
BlockPos blockPos;
BlockState blockState2;
if (blockState.is(Blocks.LIGHTNING_ROD)) {
blockPos = pos.relative(((Direction)blockState.getValue(LightningRodBlock.FACING)).getOpposite());
blockState2 = level.getBlockState(blockPos);
} else {
blockPos = pos;
blockState2 = blockState;
}
if (blockState2.getBlock() instanceof WeatheringCopper) {
level.setBlockAndUpdate(blockPos, WeatheringCopper.getFirst(level.getBlockState(blockPos)));
BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
int i = level.random.nextInt(3) + 3;
for (int j = 0; j < i; j++) {
int k = level.random.nextInt(8) + 1;
randomWalkCleaningCopper(level, blockPos, mutableBlockPos, k);
}
}
}
private static void randomWalkCleaningCopper(Level level, BlockPos pos, BlockPos.MutableBlockPos mutable, int steps) {
mutable.set(pos);
for (int i = 0; i < steps; i++) {
Optional<BlockPos> optional = randomStepCleaningCopper(level, mutable);
if (optional.isEmpty()) {
break;
}
mutable.set((Vec3i)optional.get());
}
}
private static Optional<BlockPos> randomStepCleaningCopper(Level level, BlockPos pos) {
for (BlockPos blockPos : BlockPos.randomInCube(level.random, 10, pos, 1)) {
BlockState blockState = level.getBlockState(blockPos);
if (blockState.getBlock() instanceof WeatheringCopper) {
WeatheringCopper.getPrevious(blockState).ifPresent(blockStatex -> level.setBlockAndUpdate(blockPos, blockStatex));
level.levelEvent(3002, blockPos, -1);
return Optional.of(blockPos);
}
}
return Optional.empty();
}
@Override
public boolean shouldRenderAtSqrDistance(double distance) {
double d = 64.0 * getViewScale();
return distance < d * d;
}
@Override
protected void defineSynchedData(Builder builder) {
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
}
public int getBlocksSetOnFire() {
return this.blocksSetOnFire;
}
public Stream<Entity> getHitEntities() {
return this.hitEntities.stream().filter(Entity::isAlive);
}
@Override
public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
return false;
}
}