package net.minecraft.world.level;

import org.apache.logging.log4j.LogManager;
import net.minecraft.tags.TagManager;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.network.protocol.Packet;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.entity.EntityType;
import java.util.function.Predicate;
import java.io.IOException;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.level.block.state.predicate.BlockMaterialPredicate;
import net.minecraft.world.level.material.Material;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.AABB;
import java.util.function.Consumer;
import net.minecraft.world.level.block.entity.BlockEntityType;
import java.util.Iterator;
import java.util.Collection;
import net.minecraft.world.level.block.entity.TickableBlockEntity;
import org.apache.logging.log4j.util.Supplier;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.sounds.SoundSource;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.ReportedException;
import net.minecraft.CrashReportCategory;
import net.minecraft.core.Registry;
import net.minecraft.CrashReport;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.block.Block;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import javax.annotation.Nullable;
import net.minecraft.server.MinecraftServer;
import com.google.common.collect.Lists;
import java.util.function.BiFunction;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.dimension.Dimension;
import java.util.Random;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.List;
import net.minecraft.core.Direction;
import org.apache.logging.log4j.Logger;

public abstract class Level implements LevelAccessor, AutoCloseable {
    protected static final Logger LOGGER;
    private static final Direction[] DIRECTIONS;
    public final List<BlockEntity> blockEntityList;
    public final List<BlockEntity> tickableBlockEntities;
    protected final List<BlockEntity> pendingBlockEntities;
    protected final List<BlockEntity> blockEntitiesToUnload;
    private final Thread thread;
    private int skyDarken;
    protected int randValue;
    protected final int addend = 1013904223;
    protected float oRainLevel;
    protected float rainLevel;
    protected float oThunderLevel;
    protected float thunderLevel;
    public final Random random;
    public final Dimension dimension;
    protected final ChunkSource chunkSource;
    protected final LevelData levelData;
    private final ProfilerFiller profiler;
    public final boolean isClientSide;
    protected boolean updatingBlockEntities;
    private final WorldBorder worldBorder;
    private final BiomeManager biomeManager;
    
    protected Level(final LevelData crm, final DimensionType cbi, final BiFunction<Level, Dimension, ChunkSource> biFunction, final ProfilerFiller aio, final boolean boolean5) {
        this.blockEntityList = Lists.newArrayList();
        this.tickableBlockEntities = Lists.newArrayList();
        this.pendingBlockEntities = Lists.newArrayList();
        this.blockEntitiesToUnload = Lists.newArrayList();
        this.randValue = new Random().nextInt();
        this.random = new Random();
        this.profiler = aio;
        this.levelData = crm;
        this.dimension = cbi.create(this);
        this.chunkSource = biFunction.apply(this, this.dimension);
        this.isClientSide = boolean5;
        this.worldBorder = this.dimension.createWorldBorder();
        this.thread = Thread.currentThread();
        this.biomeManager = new BiomeManager(this, boolean5 ? crm.getSeed() : LevelData.obfuscateSeed(crm.getSeed()), cbi.getBiomeZoomer());
    }
    
    @Override
    public boolean isClientSide() {
        return this.isClientSide;
    }
    
    @Nullable
    public MinecraftServer getServer() {
        return null;
    }
    
    public void validateSpawn() {
        this.setSpawnPos(new BlockPos(8, 64, 8));
    }
    
    public BlockState getTopBlockState(final BlockPos fk) {
        BlockPos fk2;
        for (fk2 = new BlockPos(fk.getX(), this.getSeaLevel(), fk.getZ()); !this.isEmptyBlock(fk2.above()); fk2 = fk2.above()) {}
        return this.getBlockState(fk2);
    }
    
    public static boolean isInWorldBounds(final BlockPos fk) {
        return !isOutsideBuildHeight(fk) && fk.getX() >= -30000000 && fk.getZ() >= -30000000 && fk.getX() < 30000000 && fk.getZ() < 30000000;
    }
    
    public static boolean isOutsideBuildHeight(final BlockPos fk) {
        return isOutsideBuildHeight(fk.getY());
    }
    
    public static boolean isOutsideBuildHeight(final int integer) {
        return integer < 0 || integer >= 256;
    }
    
    public LevelChunk getChunkAt(final BlockPos fk) {
        return this.getChunk(fk.getX() >> 4, fk.getZ() >> 4);
    }
    
    @Override
    public LevelChunk getChunk(final int integer1, final int integer2) {
        return (LevelChunk)this.getChunk(integer1, integer2, ChunkStatus.FULL);
    }
    
    @Override
    public ChunkAccess getChunk(final int integer1, final int integer2, final ChunkStatus cae, final boolean boolean4) {
        final ChunkAccess bzy6 = this.chunkSource.getChunk(integer1, integer2, cae, boolean4);
        if (bzy6 == null && boolean4) {
            throw new IllegalStateException("Should always be able to create a chunk!");
        }
        return bzy6;
    }
    
    @Override
    public boolean setBlock(final BlockPos fk, final BlockState byj, final int integer) {
        if (isOutsideBuildHeight(fk)) {
            return false;
        }
        if (!this.isClientSide && this.levelData.getGeneratorType() == LevelType.DEBUG_ALL_BLOCK_STATES) {
            return false;
        }
        final LevelChunk cal5 = this.getChunkAt(fk);
        final Block bph6 = byj.getBlock();
        final BlockState byj2 = cal5.setBlockState(fk, byj, (integer & 0x40) != 0x0);
        if (byj2 != null) {
            final BlockState byj3 = this.getBlockState(fk);
            if (byj3 != byj2 && (byj3.getLightBlock(this, fk) != byj2.getLightBlock(this, fk) || byj3.getLightEmission() != byj2.getLightEmission() || byj3.useShapeForLightOcclusion() || byj2.useShapeForLightOcclusion())) {
                this.profiler.push("queueCheckLight");
                this.getChunkSource().getLightEngine().checkBlock(fk);
                this.profiler.pop();
            }
            if (byj3 == byj) {
                if (byj2 != byj3) {
                    this.setBlocksDirty(fk, byj2, byj3);
                }
                if ((integer & 0x2) != 0x0 && (!this.isClientSide || (integer & 0x4) == 0x0) && (this.isClientSide || (cal5.getFullStatus() != null && cal5.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) {
                    this.sendBlockUpdated(fk, byj2, byj, integer);
                }
                if (!this.isClientSide && (integer & 0x1) != 0x0) {
                    this.blockUpdated(fk, byj2.getBlock());
                    if (byj.hasAnalogOutputSignal()) {
                        this.updateNeighbourForOutputSignal(fk, bph6);
                    }
                }
                if ((integer & 0x10) == 0x0) {
                    final int integer2 = integer & 0xFFFFFFFE;
                    byj2.updateIndirectNeighbourShapes(this, fk, integer2);
                    byj.updateNeighbourShapes(this, fk, integer2);
                    byj.updateIndirectNeighbourShapes(this, fk, integer2);
                }
                this.onBlockStateChange(fk, byj2, byj3);
            }
            return true;
        }
        return false;
    }
    
    public void onBlockStateChange(final BlockPos fk, final BlockState byj2, final BlockState byj3) {
    }
    
    @Override
    public boolean removeBlock(final BlockPos fk, final boolean boolean2) {
        final FluidState coj4 = this.getFluidState(fk);
        return this.setBlock(fk, coj4.createLegacyBlock(), 0x3 | (boolean2 ? 64 : 0));
    }
    
    @Override
    public boolean destroyBlock(final BlockPos fk, final boolean boolean2, @Nullable final Entity akq) {
        final BlockState byj5 = this.getBlockState(fk);
        if (byj5.isAir()) {
            return false;
        }
        final FluidState coj6 = this.getFluidState(fk);
        this.levelEvent(2001, fk, Block.getId(byj5));
        if (boolean2) {
            final BlockEntity bwl7 = byj5.getBlock().isEntityBlock() ? this.getBlockEntity(fk) : null;
            Block.dropResources(byj5, this, fk, bwl7, akq, ItemStack.EMPTY);
        }
        return this.setBlock(fk, coj6.createLegacyBlock(), 3);
    }
    
    public boolean setBlockAndUpdate(final BlockPos fk, final BlockState byj) {
        return this.setBlock(fk, byj, 3);
    }
    
    public abstract void sendBlockUpdated(final BlockPos fk, final BlockState byj2, final BlockState byj3, final int integer);
    
    @Override
    public void blockUpdated(final BlockPos fk, final Block bph) {
        if (this.levelData.getGeneratorType() != LevelType.DEBUG_ALL_BLOCK_STATES) {
            this.updateNeighborsAt(fk, bph);
        }
    }
    
    public void setBlocksDirty(final BlockPos fk, final BlockState byj2, final BlockState byj3) {
    }
    
    public void updateNeighborsAt(final BlockPos fk, final Block bph) {
        this.neighborChanged(fk.west(), bph, fk);
        this.neighborChanged(fk.east(), bph, fk);
        this.neighborChanged(fk.below(), bph, fk);
        this.neighborChanged(fk.above(), bph, fk);
        this.neighborChanged(fk.north(), bph, fk);
        this.neighborChanged(fk.south(), bph, fk);
    }
    
    public void updateNeighborsAtExceptFromFacing(final BlockPos fk, final Block bph, final Direction fp) {
        if (fp != Direction.WEST) {
            this.neighborChanged(fk.west(), bph, fk);
        }
        if (fp != Direction.EAST) {
            this.neighborChanged(fk.east(), bph, fk);
        }
        if (fp != Direction.DOWN) {
            this.neighborChanged(fk.below(), bph, fk);
        }
        if (fp != Direction.UP) {
            this.neighborChanged(fk.above(), bph, fk);
        }
        if (fp != Direction.NORTH) {
            this.neighborChanged(fk.north(), bph, fk);
        }
        if (fp != Direction.SOUTH) {
            this.neighborChanged(fk.south(), bph, fk);
        }
    }
    
    public void neighborChanged(final BlockPos fk1, final Block bph, final BlockPos fk3) {
        if (this.isClientSide) {
            return;
        }
        final BlockState byj5 = this.getBlockState(fk1);
        try {
            byj5.neighborChanged(this, fk1, bph, fk3, false);
        }
        catch (Throwable throwable6) {
            final CrashReport h7 = CrashReport.forThrowable(throwable6, "Exception while updating neighbours");
            final CrashReportCategory i8 = h7.addCategory("Block being updated");
            i8.setDetail("Source block type", () -> {
                try {
                    return String.format("ID #%s (%s // %s)", Registry.BLOCK.getKey(bph), bph.getDescriptionId(), bph.getClass().getCanonicalName());
                }
                catch (Throwable throwable7) {
                    return "ID #" + Registry.BLOCK.getKey(bph);
                }
            });
            CrashReportCategory.populateBlockDetails(i8, fk1, byj5);
            throw new ReportedException(h7);
        }
    }
    
    @Override
    public int getHeight(final Heightmap.Types a, final int integer2, final int integer3) {
        int integer4;
        if (integer2 < -30000000 || integer3 < -30000000 || integer2 >= 30000000 || integer3 >= 30000000) {
            integer4 = this.getSeaLevel() + 1;
        }
        else if (this.hasChunk(integer2 >> 4, integer3 >> 4)) {
            integer4 = this.getChunk(integer2 >> 4, integer3 >> 4).getHeight(a, integer2 & 0xF, integer3 & 0xF) + 1;
        }
        else {
            integer4 = 0;
        }
        return integer4;
    }
    
    @Override
    public LevelLightEngine getLightEngine() {
        return this.getChunkSource().getLightEngine();
    }
    
    @Override
    public BlockState getBlockState(final BlockPos fk) {
        if (isOutsideBuildHeight(fk)) {
            return Blocks.VOID_AIR.defaultBlockState();
        }
        final LevelChunk cal3 = this.getChunk(fk.getX() >> 4, fk.getZ() >> 4);
        return cal3.getBlockState(fk);
    }
    
    @Override
    public FluidState getFluidState(final BlockPos fk) {
        if (isOutsideBuildHeight(fk)) {
            return Fluids.EMPTY.defaultFluidState();
        }
        final LevelChunk cal3 = this.getChunkAt(fk);
        return cal3.getFluidState(fk);
    }
    
    public boolean isDay() {
        return this.dimension.getType() == DimensionType.OVERWORLD && this.skyDarken < 4;
    }
    
    public boolean isNight() {
        return this.dimension.getType() == DimensionType.OVERWORLD && !this.isDay();
    }
    
    @Override
    public void playSound(@Nullable final Player ayj, final BlockPos fk, final SoundEvent aah, final SoundSource aaj, final float float5, final float float6) {
        this.playSound(ayj, fk.getX() + 0.5, fk.getY() + 0.5, fk.getZ() + 0.5, aah, aaj, float5, float6);
    }
    
    public abstract void playSound(@Nullable final Player ayj, final double double2, final double double3, final double double4, final SoundEvent aah, final SoundSource aaj, final float float7, final float float8);
    
    public abstract void playSound(@Nullable final Player ayj, final Entity akq, final SoundEvent aah, final SoundSource aaj, final float float5, final float float6);
    
    public void playLocalSound(final double double1, final double double2, final double double3, final SoundEvent aah, final SoundSource aaj, final float float6, final float float7, final boolean boolean8) {
    }
    
    @Override
    public void addParticle(final ParticleOptions gt, final double double2, final double double3, final double double4, final double double5, final double double6, final double double7) {
    }
    
    public void addParticle(final ParticleOptions gt, final boolean boolean2, final double double3, final double double4, final double double5, final double double6, final double double7, final double double8) {
    }
    
    public void addAlwaysVisibleParticle(final ParticleOptions gt, final double double2, final double double3, final double double4, final double double5, final double double6, final double double7) {
    }
    
    public void addAlwaysVisibleParticle(final ParticleOptions gt, final boolean boolean2, final double double3, final double double4, final double double5, final double double6, final double double7, final double double8) {
    }
    
    public float getSunAngle(final float float1) {
        final float float2 = this.getTimeOfDay(float1);
        return float2 * 6.2831855f;
    }
    
    public boolean addBlockEntity(final BlockEntity bwl) {
        if (this.updatingBlockEntities) {
            Level.LOGGER.error("Adding block entity while ticking: {} @ {}", new Supplier[] { () -> Registry.BLOCK_ENTITY_TYPE.getKey(bwl.getType()), bwl::getBlockPos });
        }
        final boolean boolean3 = this.blockEntityList.add(bwl);
        if (boolean3 && bwl instanceof TickableBlockEntity) {
            this.tickableBlockEntities.add(bwl);
        }
        if (this.isClientSide) {
            final BlockPos fk4 = bwl.getBlockPos();
            final BlockState byj5 = this.getBlockState(fk4);
            this.sendBlockUpdated(fk4, byj5, byj5, 2);
        }
        return boolean3;
    }
    
    public void addAllPendingBlockEntities(final Collection<BlockEntity> collection) {
        if (this.updatingBlockEntities) {
            this.pendingBlockEntities.addAll(collection);
        }
        else {
            for (final BlockEntity bwl4 : collection) {
                this.addBlockEntity(bwl4);
            }
        }
    }
    
    public void tickBlockEntities() {
        final ProfilerFiller aio2 = this.getProfiler();
        aio2.push("blockEntities");
        if (!this.blockEntitiesToUnload.isEmpty()) {
            this.tickableBlockEntities.removeAll(this.blockEntitiesToUnload);
            this.blockEntityList.removeAll(this.blockEntitiesToUnload);
            this.blockEntitiesToUnload.clear();
        }
        this.updatingBlockEntities = true;
        final Iterator<BlockEntity> iterator3 = this.tickableBlockEntities.iterator();
        while (iterator3.hasNext()) {
            final BlockEntity bwl4 = iterator3.next();
            if (!bwl4.isRemoved() && bwl4.hasLevel()) {
                final BlockPos fk5 = bwl4.getBlockPos();
                if (this.chunkSource.isTickingChunk(fk5) && this.getWorldBorder().isWithinBounds(fk5)) {
                    try {
                        aio2.push(() -> String.valueOf(BlockEntityType.getKey(bwl4.getType())));
                        if (bwl4.getType().isValid(this.getBlockState(fk5).getBlock())) {
                            ((TickableBlockEntity)bwl4).tick();
                        }
                        else {
                            bwl4.logInvalidState();
                        }
                        aio2.pop();
                    }
                    catch (Throwable throwable6) {
                        final CrashReport h7 = CrashReport.forThrowable(throwable6, "Ticking block entity");
                        final CrashReportCategory i8 = h7.addCategory("Block entity being ticked");
                        bwl4.fillCrashReportCategory(i8);
                        throw new ReportedException(h7);
                    }
                }
            }
            if (bwl4.isRemoved()) {
                iterator3.remove();
                this.blockEntityList.remove(bwl4);
                if (!this.hasChunkAt(bwl4.getBlockPos())) {
                    continue;
                }
                this.getChunkAt(bwl4.getBlockPos()).removeBlockEntity(bwl4.getBlockPos());
            }
        }
        this.updatingBlockEntities = false;
        aio2.popPush("pendingBlockEntities");
        if (!this.pendingBlockEntities.isEmpty()) {
            for (int integer4 = 0; integer4 < this.pendingBlockEntities.size(); ++integer4) {
                final BlockEntity bwl5 = this.pendingBlockEntities.get(integer4);
                if (!bwl5.isRemoved()) {
                    if (!this.blockEntityList.contains(bwl5)) {
                        this.addBlockEntity(bwl5);
                    }
                    if (this.hasChunkAt(bwl5.getBlockPos())) {
                        final LevelChunk cal6 = this.getChunkAt(bwl5.getBlockPos());
                        final BlockState byj7 = cal6.getBlockState(bwl5.getBlockPos());
                        cal6.setBlockEntity(bwl5.getBlockPos(), bwl5);
                        this.sendBlockUpdated(bwl5.getBlockPos(), byj7, byj7, 3);
                    }
                }
            }
            this.pendingBlockEntities.clear();
        }
        aio2.pop();
    }
    
    public void guardEntityTick(final Consumer<Entity> consumer, final Entity akq) {
        try {
            consumer.accept(akq);
        }
        catch (Throwable throwable4) {
            final CrashReport h5 = CrashReport.forThrowable(throwable4, "Ticking entity");
            final CrashReportCategory i6 = h5.addCategory("Entity being ticked");
            akq.fillCrashReportCategory(i6);
            throw new ReportedException(h5);
        }
    }
    
    public boolean containsAnyBlocks(final AABB cvf) {
        final int integer3 = Mth.floor(cvf.minX);
        final int integer4 = Mth.ceil(cvf.maxX);
        final int integer5 = Mth.floor(cvf.minY);
        final int integer6 = Mth.ceil(cvf.maxY);
        final int integer7 = Mth.floor(cvf.minZ);
        final int integer8 = Mth.ceil(cvf.maxZ);
        try (final BlockPos.PooledMutableBlockPos b9 = BlockPos.PooledMutableBlockPos.acquire()) {
            for (int integer9 = integer3; integer9 < integer4; ++integer9) {
                for (int integer10 = integer5; integer10 < integer6; ++integer10) {
                    for (int integer11 = integer7; integer11 < integer8; ++integer11) {
                        final BlockState byj14 = this.getBlockState(b9.set(integer9, integer10, integer11));
                        if (!byj14.isAir()) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
    
    public boolean containsFireBlock(final AABB cvf) {
        final int integer3 = Mth.floor(cvf.minX);
        final int integer4 = Mth.ceil(cvf.maxX);
        final int integer5 = Mth.floor(cvf.minY);
        final int integer6 = Mth.ceil(cvf.maxY);
        final int integer7 = Mth.floor(cvf.minZ);
        final int integer8 = Mth.ceil(cvf.maxZ);
        if (this.hasChunksAt(integer3, integer5, integer7, integer4, integer6, integer8)) {
            try (final BlockPos.PooledMutableBlockPos b9 = BlockPos.PooledMutableBlockPos.acquire()) {
                for (int integer9 = integer3; integer9 < integer4; ++integer9) {
                    for (int integer10 = integer5; integer10 < integer6; ++integer10) {
                        for (int integer11 = integer7; integer11 < integer8; ++integer11) {
                            final Block bph14 = this.getBlockState(b9.set(integer9, integer10, integer11)).getBlock();
                            if (bph14 == Blocks.FIRE || bph14 == Blocks.LAVA) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }
    
    @Nullable
    public BlockState containsBlock(final AABB cvf, final Block bph) {
        final int integer4 = Mth.floor(cvf.minX);
        final int integer5 = Mth.ceil(cvf.maxX);
        final int integer6 = Mth.floor(cvf.minY);
        final int integer7 = Mth.ceil(cvf.maxY);
        final int integer8 = Mth.floor(cvf.minZ);
        final int integer9 = Mth.ceil(cvf.maxZ);
        if (this.hasChunksAt(integer4, integer6, integer8, integer5, integer7, integer9)) {
            try (final BlockPos.PooledMutableBlockPos b10 = BlockPos.PooledMutableBlockPos.acquire()) {
                for (int integer10 = integer4; integer10 < integer5; ++integer10) {
                    for (int integer11 = integer6; integer11 < integer7; ++integer11) {
                        for (int integer12 = integer8; integer12 < integer9; ++integer12) {
                            final BlockState byj15 = this.getBlockState(b10.set(integer10, integer11, integer12));
                            if (byj15.getBlock() == bph) {
                                return byj15;
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
    
    public boolean containsMaterial(final AABB cvf, final Material coo) {
        final int integer4 = Mth.floor(cvf.minX);
        final int integer5 = Mth.ceil(cvf.maxX);
        final int integer6 = Mth.floor(cvf.minY);
        final int integer7 = Mth.ceil(cvf.maxY);
        final int integer8 = Mth.floor(cvf.minZ);
        final int integer9 = Mth.ceil(cvf.maxZ);
        final BlockMaterialPredicate byr10 = BlockMaterialPredicate.forMaterial(coo);
        return BlockPos.betweenClosedStream(integer4, integer6, integer8, integer5 - 1, integer7 - 1, integer9 - 1).anyMatch(fk -> byr10.test(this.getBlockState(fk)));
    }
    
    public Explosion explode(@Nullable final Entity akq, final double double2, final double double3, final double double4, final float float5, final Explosion.BlockInteraction a) {
        return this.explode(akq, null, double2, double3, double4, float5, false, a);
    }
    
    public Explosion explode(@Nullable final Entity akq, final double double2, final double double3, final double double4, final float float5, final boolean boolean6, final Explosion.BlockInteraction a) {
        return this.explode(akq, null, double2, double3, double4, float5, boolean6, a);
    }
    
    public Explosion explode(@Nullable final Entity akq, @Nullable final DamageSource ajz, final double double3, final double double4, final double double5, final float float6, final boolean boolean7, final Explosion.BlockInteraction a) {
        final Explosion bjp13 = new Explosion(this, akq, double3, double4, double5, float6, boolean7, a);
        if (ajz != null) {
            bjp13.setDamageSource(ajz);
        }
        bjp13.explode();
        bjp13.finalizeExplosion(true);
        return bjp13;
    }
    
    public boolean extinguishFire(@Nullable final Player ayj, BlockPos fk, final Direction fp) {
        fk = fk.relative(fp);
        if (this.getBlockState(fk).getBlock() == Blocks.FIRE) {
            this.levelEvent(ayj, 1009, fk, 0);
            this.removeBlock(fk, false);
            return true;
        }
        return false;
    }
    
    public String gatherChunkSourceStats() {
        return this.chunkSource.gatherStats();
    }
    
    @Nullable
    @Override
    public BlockEntity getBlockEntity(final BlockPos fk) {
        if (isOutsideBuildHeight(fk)) {
            return null;
        }
        if (!this.isClientSide && Thread.currentThread() != this.thread) {
            return null;
        }
        BlockEntity bwl3 = null;
        if (this.updatingBlockEntities) {
            bwl3 = this.getPendingBlockEntityAt(fk);
        }
        if (bwl3 == null) {
            bwl3 = this.getChunkAt(fk).getBlockEntity(fk, LevelChunk.EntityCreationType.IMMEDIATE);
        }
        if (bwl3 == null) {
            bwl3 = this.getPendingBlockEntityAt(fk);
        }
        return bwl3;
    }
    
    @Nullable
    private BlockEntity getPendingBlockEntityAt(final BlockPos fk) {
        for (int integer3 = 0; integer3 < this.pendingBlockEntities.size(); ++integer3) {
            final BlockEntity bwl4 = this.pendingBlockEntities.get(integer3);
            if (!bwl4.isRemoved() && bwl4.getBlockPos().equals(fk)) {
                return bwl4;
            }
        }
        return null;
    }
    
    public void setBlockEntity(final BlockPos fk, @Nullable final BlockEntity bwl) {
        if (isOutsideBuildHeight(fk)) {
            return;
        }
        if (bwl != null && !bwl.isRemoved()) {
            if (this.updatingBlockEntities) {
                bwl.setLevelAndPosition(this, fk);
                final Iterator<BlockEntity> iterator4 = this.pendingBlockEntities.iterator();
                while (iterator4.hasNext()) {
                    final BlockEntity bwl2 = iterator4.next();
                    if (bwl2.getBlockPos().equals(fk)) {
                        bwl2.setRemoved();
                        iterator4.remove();
                    }
                }
                this.pendingBlockEntities.add(bwl);
            }
            else {
                this.getChunkAt(fk).setBlockEntity(fk, bwl);
                this.addBlockEntity(bwl);
            }
        }
    }
    
    public void removeBlockEntity(final BlockPos fk) {
        final BlockEntity bwl3 = this.getBlockEntity(fk);
        if (bwl3 != null && this.updatingBlockEntities) {
            bwl3.setRemoved();
            this.pendingBlockEntities.remove(bwl3);
        }
        else {
            if (bwl3 != null) {
                this.pendingBlockEntities.remove(bwl3);
                this.blockEntityList.remove(bwl3);
                this.tickableBlockEntities.remove(bwl3);
            }
            this.getChunkAt(fk).removeBlockEntity(fk);
        }
    }
    
    public boolean isLoaded(final BlockPos fk) {
        return !isOutsideBuildHeight(fk) && this.chunkSource.hasChunk(fk.getX() >> 4, fk.getZ() >> 4);
    }
    
    public boolean loadedAndEntityCanStandOn(final BlockPos fk, final Entity akq) {
        if (isOutsideBuildHeight(fk)) {
            return false;
        }
        final ChunkAccess bzy4 = this.getChunk(fk.getX() >> 4, fk.getZ() >> 4, ChunkStatus.FULL, false);
        return bzy4 != null && bzy4.getBlockState(fk).entityCanStandOn(this, fk, akq);
    }
    
    public void updateSkyBrightness() {
        final double double2 = 1.0 - this.getRainLevel(1.0f) * 5.0f / 16.0;
        final double double3 = 1.0 - this.getThunderLevel(1.0f) * 5.0f / 16.0;
        final double double4 = 0.5 + 2.0 * Mth.clamp(Mth.cos(this.getTimeOfDay(1.0f) * 6.2831855f), -0.25, 0.25);
        this.skyDarken = (int)((1.0 - double4 * double2 * double3) * 11.0);
    }
    
    public void setSpawnSettings(final boolean boolean1, final boolean boolean2) {
        this.getChunkSource().setSpawnSettings(boolean1, boolean2);
    }
    
    protected void prepareWeather() {
        if (this.levelData.isRaining()) {
            this.rainLevel = 1.0f;
            if (this.levelData.isThundering()) {
                this.thunderLevel = 1.0f;
            }
        }
    }
    
    @Override
    public void close() throws IOException {
        this.chunkSource.close();
    }
    
    @Nullable
    @Override
    public BlockGetter getChunkForCollisions(final int integer1, final int integer2) {
        return this.getChunk(integer1, integer2, ChunkStatus.FULL, false);
    }
    
    @Override
    public List<Entity> getEntities(@Nullable final Entity akq, final AABB cvf, @Nullable final Predicate<? super Entity> predicate) {
        this.getProfiler().incrementCounter("getEntities");
        final List<Entity> list5 = Lists.newArrayList();
        final int integer6 = Mth.floor((cvf.minX - 2.0) / 16.0);
        final int integer7 = Mth.floor((cvf.maxX + 2.0) / 16.0);
        final int integer8 = Mth.floor((cvf.minZ - 2.0) / 16.0);
        final int integer9 = Mth.floor((cvf.maxZ + 2.0) / 16.0);
        for (int integer10 = integer6; integer10 <= integer7; ++integer10) {
            for (int integer11 = integer8; integer11 <= integer9; ++integer11) {
                final LevelChunk cal12 = this.getChunkSource().getChunk(integer10, integer11, false);
                if (cal12 != null) {
                    cal12.getEntities(akq, cvf, list5, predicate);
                }
            }
        }
        return list5;
    }
    
    public <T extends Entity> List<T> getEntities(@Nullable final EntityType<T> aku, final AABB cvf, final Predicate<? super T> predicate) {
        this.getProfiler().incrementCounter("getEntities");
        final int integer5 = Mth.floor((cvf.minX - 2.0) / 16.0);
        final int integer6 = Mth.ceil((cvf.maxX + 2.0) / 16.0);
        final int integer7 = Mth.floor((cvf.minZ - 2.0) / 16.0);
        final int integer8 = Mth.ceil((cvf.maxZ + 2.0) / 16.0);
        final List<T> list9 = Lists.newArrayList();
        for (int integer9 = integer5; integer9 < integer6; ++integer9) {
            for (int integer10 = integer7; integer10 < integer8; ++integer10) {
                final LevelChunk cal12 = this.getChunkSource().getChunk(integer9, integer10, false);
                if (cal12 != null) {
                    cal12.<Entity>getEntities(aku, cvf, list9, predicate);
                }
            }
        }
        return list9;
    }
    
    @Override
    public <T extends Entity> List<T> getEntitiesOfClass(final Class<? extends T> class1, final AABB cvf, @Nullable final Predicate<? super T> predicate) {
        this.getProfiler().incrementCounter("getEntities");
        final int integer5 = Mth.floor((cvf.minX - 2.0) / 16.0);
        final int integer6 = Mth.ceil((cvf.maxX + 2.0) / 16.0);
        final int integer7 = Mth.floor((cvf.minZ - 2.0) / 16.0);
        final int integer8 = Mth.ceil((cvf.maxZ + 2.0) / 16.0);
        final List<T> list9 = Lists.newArrayList();
        final ChunkSource cad10 = this.getChunkSource();
        for (int integer9 = integer5; integer9 < integer6; ++integer9) {
            for (int integer10 = integer7; integer10 < integer8; ++integer10) {
                final LevelChunk cal13 = cad10.getChunk(integer9, integer10, false);
                if (cal13 != null) {
                    cal13.<T>getEntitiesOfClass(class1, cvf, list9, predicate);
                }
            }
        }
        return list9;
    }
    
    @Override
    public <T extends Entity> List<T> getLoadedEntitiesOfClass(final Class<? extends T> class1, final AABB cvf, @Nullable final Predicate<? super T> predicate) {
        this.getProfiler().incrementCounter("getLoadedEntities");
        final int integer5 = Mth.floor((cvf.minX - 2.0) / 16.0);
        final int integer6 = Mth.ceil((cvf.maxX + 2.0) / 16.0);
        final int integer7 = Mth.floor((cvf.minZ - 2.0) / 16.0);
        final int integer8 = Mth.ceil((cvf.maxZ + 2.0) / 16.0);
        final List<T> list9 = Lists.newArrayList();
        final ChunkSource cad10 = this.getChunkSource();
        for (int integer9 = integer5; integer9 < integer6; ++integer9) {
            for (int integer10 = integer7; integer10 < integer8; ++integer10) {
                final LevelChunk cal13 = cad10.getChunkNow(integer9, integer10);
                if (cal13 != null) {
                    cal13.<T>getEntitiesOfClass(class1, cvf, list9, predicate);
                }
            }
        }
        return list9;
    }
    
    @Nullable
    public abstract Entity getEntity(final int integer);
    
    public void blockEntityChanged(final BlockPos fk, final BlockEntity bwl) {
        if (this.hasChunkAt(fk)) {
            this.getChunkAt(fk).markUnsaved();
        }
    }
    
    @Override
    public int getSeaLevel() {
        return 63;
    }
    
    @Override
    public Level getLevel() {
        return this;
    }
    
    public LevelType getGeneratorType() {
        return this.levelData.getGeneratorType();
    }
    
    public int getDirectSignalTo(final BlockPos fk) {
        int integer3 = 0;
        integer3 = Math.max(integer3, this.getDirectSignal(fk.below(), Direction.DOWN));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fk.above(), Direction.UP));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fk.north(), Direction.NORTH));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fk.south(), Direction.SOUTH));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fk.west(), Direction.WEST));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fk.east(), Direction.EAST));
        if (integer3 >= 15) {
            return integer3;
        }
        return integer3;
    }
    
    public boolean hasSignal(final BlockPos fk, final Direction fp) {
        return this.getSignal(fk, fp) > 0;
    }
    
    public int getSignal(final BlockPos fk, final Direction fp) {
        final BlockState byj4 = this.getBlockState(fk);
        if (byj4.isRedstoneConductor(this, fk)) {
            return this.getDirectSignalTo(fk);
        }
        return byj4.getSignal(this, fk, fp);
    }
    
    public boolean hasNeighborSignal(final BlockPos fk) {
        return this.getSignal(fk.below(), Direction.DOWN) > 0 || this.getSignal(fk.above(), Direction.UP) > 0 || this.getSignal(fk.north(), Direction.NORTH) > 0 || this.getSignal(fk.south(), Direction.SOUTH) > 0 || this.getSignal(fk.west(), Direction.WEST) > 0 || this.getSignal(fk.east(), Direction.EAST) > 0;
    }
    
    public int getBestNeighborSignal(final BlockPos fk) {
        int integer3 = 0;
        for (final Direction fp7 : Level.DIRECTIONS) {
            final int integer4 = this.getSignal(fk.relative(fp7), fp7);
            if (integer4 >= 15) {
                return 15;
            }
            if (integer4 > integer3) {
                integer3 = integer4;
            }
        }
        return integer3;
    }
    
    public void disconnect() {
    }
    
    public void setGameTime(final long long1) {
        this.levelData.setGameTime(long1);
    }
    
    @Override
    public long getSeed() {
        return this.levelData.getSeed();
    }
    
    public long getGameTime() {
        return this.levelData.getGameTime();
    }
    
    public long getDayTime() {
        return this.levelData.getDayTime();
    }
    
    public void setDayTime(final long long1) {
        this.levelData.setDayTime(long1);
    }
    
    protected void tickTime() {
        this.setGameTime(this.levelData.getGameTime() + 1L);
        if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
            this.setDayTime(this.levelData.getDayTime() + 1L);
        }
    }
    
    @Override
    public BlockPos getSharedSpawnPos() {
        BlockPos fk2 = new BlockPos(this.levelData.getXSpawn(), this.levelData.getYSpawn(), this.levelData.getZSpawn());
        if (!this.getWorldBorder().isWithinBounds(fk2)) {
            fk2 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, new BlockPos(this.getWorldBorder().getCenterX(), 0.0, this.getWorldBorder().getCenterZ()));
        }
        return fk2;
    }
    
    public void setSpawnPos(final BlockPos fk) {
        this.levelData.setSpawn(fk);
    }
    
    public boolean mayInteract(final Player ayj, final BlockPos fk) {
        return true;
    }
    
    public void broadcastEntityEvent(final Entity akq, final byte byte2) {
    }
    
    @Override
    public ChunkSource getChunkSource() {
        return this.chunkSource;
    }
    
    public void blockEvent(final BlockPos fk, final Block bph, final int integer3, final int integer4) {
        this.getBlockState(fk).triggerEvent(this, fk, integer3, integer4);
    }
    
    @Override
    public LevelData getLevelData() {
        return this.levelData;
    }
    
    public GameRules getGameRules() {
        return this.levelData.getGameRules();
    }
    
    public float getThunderLevel(final float float1) {
        return Mth.lerp(float1, this.oThunderLevel, this.thunderLevel) * this.getRainLevel(float1);
    }
    
    public void setThunderLevel(final float float1) {
        this.oThunderLevel = float1;
        this.thunderLevel = float1;
    }
    
    public float getRainLevel(final float float1) {
        return Mth.lerp(float1, this.oRainLevel, this.rainLevel);
    }
    
    public void setRainLevel(final float float1) {
        this.oRainLevel = float1;
        this.rainLevel = float1;
    }
    
    public boolean isThundering() {
        return this.dimension.isHasSkyLight() && !this.dimension.isHasCeiling() && this.getThunderLevel(1.0f) > 0.9;
    }
    
    public boolean isRaining() {
        return this.getRainLevel(1.0f) > 0.2;
    }
    
    public boolean isRainingAt(final BlockPos fk) {
        return this.isRaining() && this.canSeeSky(fk) && this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, fk).getY() <= fk.getY() && this.getBiome(fk).getPrecipitation() == Biome.Precipitation.RAIN;
    }
    
    public boolean isHumidAt(final BlockPos fk) {
        final Biome bkt3 = this.getBiome(fk);
        return bkt3.isHumid();
    }
    
    @Nullable
    public abstract MapItemSavedData getMapData(final String string);
    
    public abstract void setMapData(final MapItemSavedData crg);
    
    public abstract int getFreeMapId();
    
    public void globalLevelEvent(final int integer1, final BlockPos fk, final int integer3) {
    }
    
    public int getHeight() {
        return this.dimension.isHasCeiling() ? 128 : 256;
    }
    
    public CrashReportCategory fillReportDetails(final CrashReport h) {
        final CrashReportCategory i3 = h.addCategory("Affected level", 1);
        i3.setDetail("All players", () -> this.players().size() + " total; " + this.players());
        i3.setDetail("Chunk stats", this.chunkSource::gatherStats);
        i3.setDetail("Level dimension", () -> this.dimension.getType().toString());
        try {
            this.levelData.fillCrashReportCategory(i3);
        }
        catch (Throwable throwable4) {
            i3.setDetailError("Level Data Unobtainable", throwable4);
        }
        return i3;
    }
    
    public abstract void destroyBlockProgress(final int integer1, final BlockPos fk, final int integer3);
    
    public void createFireworks(final double double1, final double double2, final double double3, final double double4, final double double5, final double double6, @Nullable final CompoundTag jt) {
    }
    
    public abstract Scoreboard getScoreboard();
    
    public void updateNeighbourForOutputSignal(final BlockPos fk, final Block bph) {
        for (final Direction fp5 : Direction.Plane.HORIZONTAL) {
            BlockPos fk2 = fk.relative(fp5);
            if (this.hasChunkAt(fk2)) {
                BlockState byj7 = this.getBlockState(fk2);
                if (byj7.getBlock() == Blocks.COMPARATOR) {
                    byj7.neighborChanged(this, fk2, bph, fk, false);
                }
                else {
                    if (!byj7.isRedstoneConductor(this, fk2)) {
                        continue;
                    }
                    fk2 = fk2.relative(fp5);
                    byj7 = this.getBlockState(fk2);
                    if (byj7.getBlock() != Blocks.COMPARATOR) {
                        continue;
                    }
                    byj7.neighborChanged(this, fk2, bph, fk, false);
                }
            }
        }
    }
    
    @Override
    public DifficultyInstance getCurrentDifficultyAt(final BlockPos fk) {
        long long3 = 0L;
        float float5 = 0.0f;
        if (this.hasChunkAt(fk)) {
            float5 = this.getMoonBrightness();
            long3 = this.getChunkAt(fk).getInhabitedTime();
        }
        return new DifficultyInstance(this.getDifficulty(), this.getDayTime(), long3, float5);
    }
    
    @Override
    public int getSkyDarken() {
        return this.skyDarken;
    }
    
    public void setSkyFlashTime(final int integer) {
    }
    
    @Override
    public WorldBorder getWorldBorder() {
        return this.worldBorder;
    }
    
    public void sendPacketToServer(final Packet<?> lt) {
        throw new UnsupportedOperationException("Can't send packets to server unless you're on the client.");
    }
    
    @Override
    public Dimension getDimension() {
        return this.dimension;
    }
    
    @Override
    public Random getRandom() {
        return this.random;
    }
    
    @Override
    public boolean isStateAtPosition(final BlockPos fk, final Predicate<BlockState> predicate) {
        return predicate.test(this.getBlockState(fk));
    }
    
    public abstract RecipeManager getRecipeManager();
    
    public abstract TagManager getTagManager();
    
    public BlockPos getBlockRandomPos(final int integer1, final int integer2, final int integer3, final int integer4) {
        this.randValue = this.randValue * 3 + 1013904223;
        final int integer5 = this.randValue >> 2;
        return new BlockPos(integer1 + (integer5 & 0xF), integer2 + (integer5 >> 16 & integer4), integer3 + (integer5 >> 8 & 0xF));
    }
    
    public boolean noSave() {
        return false;
    }
    
    public ProfilerFiller getProfiler() {
        return this.profiler;
    }
    
    @Override
    public BiomeManager getBiomeManager() {
        return this.biomeManager;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        DIRECTIONS = Direction.values();
    }
}
