package net.minecraft.world.level;

import java.util.function.Function;
import net.minecraft.resources.ResourceLocation;
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.level.storage.LevelData;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.phys.AABB;
import java.io.IOException;
import net.minecraft.world.damagesource.DamageSource;
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 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.level.block.BaseFireBlock;
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.util.Mth;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.block.state.BlockState;
import java.util.function.Predicate;
import net.minecraft.core.BlockPos;
import javax.annotation.Nullable;
import net.minecraft.server.MinecraftServer;
import com.google.common.collect.Lists;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.util.profiling.ProfilerFiller;
import java.util.function.Supplier;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.level.dimension.DimensionType;
import java.util.Random;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.List;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceKey;
import com.mojang.serialization.Codec;
import org.apache.logging.log4j.Logger;

public abstract class Level implements LevelAccessor, AutoCloseable {
    protected static final Logger LOGGER;
    public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC;
    public static final ResourceKey<Level> OVERWORLD;
    public static final ResourceKey<Level> NETHER;
    public static final ResourceKey<Level> END;
    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 final boolean isDebug;
    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;
    private final DimensionType dimensionType;
    protected final WritableLevelData levelData;
    private final Supplier<ProfilerFiller> profiler;
    public final boolean isClientSide;
    protected boolean updatingBlockEntities;
    private final WorldBorder worldBorder;
    private final BiomeManager biomeManager;
    private final ResourceKey<Level> dimension;
    private final ResourceKey<DimensionType> dimensionTypeKey;
    
    protected Level(final WritableLevelData dam, final ResourceKey<Level> ug2, final ResourceKey<DimensionType> ug3, final DimensionType cif, final Supplier<ProfilerFiller> supplier, final boolean boolean6, final boolean boolean7, final long long8) {
        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 = supplier;
        this.levelData = dam;
        this.dimensionType = cif;
        this.dimension = ug2;
        this.dimensionTypeKey = ug3;
        this.isClientSide = boolean6;
        if (cif.shrunk()) {
            this.worldBorder = new WorldBorder() {
                @Override
                public double getCenterX() {
                    return super.getCenterX() / 8.0;
                }
                
                @Override
                public double getCenterZ() {
                    return super.getCenterZ() / 8.0;
                }
            };
        }
        else {
            this.worldBorder = new WorldBorder();
        }
        this.thread = Thread.currentThread();
        this.biomeManager = new BiomeManager(this, long8, cif.getBiomeZoomer());
        this.isDebug = boolean7;
    }
    
    @Override
    public boolean isClientSide() {
        return this.isClientSide;
    }
    
    @Nullable
    public MinecraftServer getServer() {
        return null;
    }
    
    public static boolean isInWorldBounds(final BlockPos fu) {
        return !isOutsideBuildHeight(fu) && isInWorldBoundsHorizontal(fu);
    }
    
    public static boolean isInSpawnableBounds(final BlockPos fu) {
        return !isOutsideSpawnableHeight(fu.getY()) && isInWorldBoundsHorizontal(fu);
    }
    
    private static boolean isInWorldBoundsHorizontal(final BlockPos fu) {
        return fu.getX() >= -30000000 && fu.getZ() >= -30000000 && fu.getX() < 30000000 && fu.getZ() < 30000000;
    }
    
    private static boolean isOutsideSpawnableHeight(final int integer) {
        return integer < -20000000 || integer >= 20000000;
    }
    
    public static boolean isOutsideBuildHeight(final BlockPos fu) {
        return isOutsideBuildHeight(fu.getY());
    }
    
    public static boolean isOutsideBuildHeight(final int integer) {
        return integer < 0 || integer >= 256;
    }
    
    public double getRelativeFloorHeight(final BlockPos fu) {
        return this.getRelativeFloorHeight(fu, cfj -> false);
    }
    
    public double getRelativeFloorHeight(final BlockPos fu, final Predicate<BlockState> predicate) {
        final BlockState cfj4 = this.getBlockState(fu);
        final VoxelShape dfg5 = predicate.test(cfj4) ? Shapes.empty() : cfj4.getCollisionShape(this, fu);
        if (!dfg5.isEmpty()) {
            return dfg5.max(Direction.Axis.Y);
        }
        final BlockPos fu2 = fu.below();
        final BlockState cfj5 = this.getBlockState(fu2);
        final VoxelShape dfg6 = predicate.test(cfj5) ? Shapes.empty() : cfj5.getCollisionShape(this, fu2);
        final double double9 = dfg6.max(Direction.Axis.Y);
        if (double9 >= 1.0) {
            return double9 - 1.0;
        }
        return Double.NEGATIVE_INFINITY;
    }
    
    public double getRelativeCeilingHeight(final BlockPos fu, final double double2) {
        final BlockPos.MutableBlockPos a5 = fu.mutable();
        final int integer6 = Mth.ceil(double2);
        int integer7 = 0;
        while (integer7 < integer6) {
            final VoxelShape dfg8 = this.getBlockState(a5).getCollisionShape(this, a5);
            if (!dfg8.isEmpty()) {
                return integer7 + dfg8.min(Direction.Axis.Y);
            }
            ++integer7;
            a5.move(Direction.UP);
        }
        return Double.POSITIVE_INFINITY;
    }
    
    public LevelChunk getChunkAt(final BlockPos fu) {
        return this.getChunk(fu.getX() >> 4, fu.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 chc, final boolean boolean4) {
        final ChunkAccess cgy6 = this.getChunkSource().getChunk(integer1, integer2, chc, boolean4);
        if (cgy6 == null && boolean4) {
            throw new IllegalStateException("Should always be able to create a chunk!");
        }
        return cgy6;
    }
    
    @Override
    public boolean setBlock(final BlockPos fu, final BlockState cfj, final int integer) {
        return this.setBlock(fu, cfj, integer, 512);
    }
    
    @Override
    public boolean setBlock(final BlockPos fu, final BlockState cfj, final int integer3, final int integer4) {
        if (isOutsideBuildHeight(fu)) {
            return false;
        }
        if (!this.isClientSide && this.isDebug()) {
            return false;
        }
        final LevelChunk chj6 = this.getChunkAt(fu);
        final Block bvr7 = cfj.getBlock();
        final BlockState cfj2 = chj6.setBlockState(fu, cfj, (integer3 & 0x40) != 0x0);
        if (cfj2 != null) {
            final BlockState cfj3 = this.getBlockState(fu);
            if (cfj3 != cfj2 && (cfj3.getLightBlock(this, fu) != cfj2.getLightBlock(this, fu) || cfj3.getLightEmission() != cfj2.getLightEmission() || cfj3.useShapeForLightOcclusion() || cfj2.useShapeForLightOcclusion())) {
                this.getProfiler().push("queueCheckLight");
                this.getChunkSource().getLightEngine().checkBlock(fu);
                this.getProfiler().pop();
            }
            if (cfj3 == cfj) {
                if (cfj2 != cfj3) {
                    this.setBlocksDirty(fu, cfj2, cfj3);
                }
                if ((integer3 & 0x2) != 0x0 && (!this.isClientSide || (integer3 & 0x4) == 0x0) && (this.isClientSide || (chj6.getFullStatus() != null && chj6.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) {
                    this.sendBlockUpdated(fu, cfj2, cfj, integer3);
                }
                if ((integer3 & 0x1) != 0x0) {
                    this.blockUpdated(fu, cfj2.getBlock());
                    if (!this.isClientSide && cfj.hasAnalogOutputSignal()) {
                        this.updateNeighbourForOutputSignal(fu, bvr7);
                    }
                }
                if ((integer3 & 0x10) == 0x0 && integer4 > 0) {
                    final int integer5 = integer3 & 0xFFFFFFDE;
                    cfj2.updateIndirectNeighbourShapes(this, fu, integer5, integer4 - 1);
                    cfj.updateNeighbourShapes(this, fu, integer5, integer4 - 1);
                    cfj.updateIndirectNeighbourShapes(this, fu, integer5, integer4 - 1);
                }
                this.onBlockStateChange(fu, cfj2, cfj3);
            }
            return true;
        }
        return false;
    }
    
    public void onBlockStateChange(final BlockPos fu, final BlockState cfj2, final BlockState cfj3) {
    }
    
    @Override
    public boolean removeBlock(final BlockPos fu, final boolean boolean2) {
        final FluidState cxa4 = this.getFluidState(fu);
        return this.setBlock(fu, cxa4.createLegacyBlock(), 0x3 | (boolean2 ? 64 : 0));
    }
    
    @Override
    public boolean destroyBlock(final BlockPos fu, final boolean boolean2, @Nullable final Entity aom, final int integer) {
        final BlockState cfj6 = this.getBlockState(fu);
        if (cfj6.isAir()) {
            return false;
        }
        final FluidState cxa7 = this.getFluidState(fu);
        if (!(cfj6.getBlock() instanceof BaseFireBlock)) {
            this.levelEvent(2001, fu, Block.getId(cfj6));
        }
        if (boolean2) {
            final BlockEntity cdl8 = cfj6.getBlock().isEntityBlock() ? this.getBlockEntity(fu) : null;
            Block.dropResources(cfj6, this, fu, cdl8, aom, ItemStack.EMPTY);
        }
        return this.setBlock(fu, cxa7.createLegacyBlock(), 3, integer);
    }
    
    public boolean setBlockAndUpdate(final BlockPos fu, final BlockState cfj) {
        return this.setBlock(fu, cfj, 3);
    }
    
    public abstract void sendBlockUpdated(final BlockPos fu, final BlockState cfj2, final BlockState cfj3, final int integer);
    
    public void setBlocksDirty(final BlockPos fu, final BlockState cfj2, final BlockState cfj3) {
    }
    
    public void updateNeighborsAt(final BlockPos fu, final Block bvr) {
        this.neighborChanged(fu.west(), bvr, fu);
        this.neighborChanged(fu.east(), bvr, fu);
        this.neighborChanged(fu.below(), bvr, fu);
        this.neighborChanged(fu.above(), bvr, fu);
        this.neighborChanged(fu.north(), bvr, fu);
        this.neighborChanged(fu.south(), bvr, fu);
    }
    
    public void updateNeighborsAtExceptFromFacing(final BlockPos fu, final Block bvr, final Direction fz) {
        if (fz != Direction.WEST) {
            this.neighborChanged(fu.west(), bvr, fu);
        }
        if (fz != Direction.EAST) {
            this.neighborChanged(fu.east(), bvr, fu);
        }
        if (fz != Direction.DOWN) {
            this.neighborChanged(fu.below(), bvr, fu);
        }
        if (fz != Direction.UP) {
            this.neighborChanged(fu.above(), bvr, fu);
        }
        if (fz != Direction.NORTH) {
            this.neighborChanged(fu.north(), bvr, fu);
        }
        if (fz != Direction.SOUTH) {
            this.neighborChanged(fu.south(), bvr, fu);
        }
    }
    
    public void neighborChanged(final BlockPos fu1, final Block bvr, final BlockPos fu3) {
        if (this.isClientSide) {
            return;
        }
        final BlockState cfj5 = this.getBlockState(fu1);
        try {
            cfj5.neighborChanged(this, fu1, bvr, fu3, false);
        }
        catch (Throwable throwable6) {
            final CrashReport j7 = CrashReport.forThrowable(throwable6, "Exception while updating neighbours");
            final CrashReportCategory k8 = j7.addCategory("Block being updated");
            k8.setDetail("Source block type", () -> {
                try {
                    return String.format("ID #%s (%s // %s)", Registry.BLOCK.getKey(bvr), bvr.getDescriptionId(), bvr.getClass().getCanonicalName());
                }
                catch (Throwable throwable7) {
                    return "ID #" + Registry.BLOCK.getKey(bvr);
                }
            });
            CrashReportCategory.populateBlockDetails(k8, fu1, cfj5);
            throw new ReportedException(j7);
        }
    }
    
    @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 fu) {
        if (isOutsideBuildHeight(fu)) {
            return Blocks.VOID_AIR.defaultBlockState();
        }
        final LevelChunk chj3 = this.getChunk(fu.getX() >> 4, fu.getZ() >> 4);
        return chj3.getBlockState(fu);
    }
    
    @Override
    public FluidState getFluidState(final BlockPos fu) {
        if (isOutsideBuildHeight(fu)) {
            return Fluids.EMPTY.defaultFluidState();
        }
        final LevelChunk chj3 = this.getChunkAt(fu);
        return chj3.getFluidState(fu);
    }
    
    public boolean isDay() {
        return !this.dimensionType().hasFixedTime() && this.skyDarken < 4;
    }
    
    public boolean isNight() {
        return !this.dimensionType().hasFixedTime() && !this.isDay();
    }
    
    @Override
    public void playSound(@Nullable final Player bec, final BlockPos fu, final SoundEvent ack, final SoundSource acm, final float float5, final float float6) {
        this.playSound(bec, fu.getX() + 0.5, fu.getY() + 0.5, fu.getZ() + 0.5, ack, acm, float5, float6);
    }
    
    public abstract void playSound(@Nullable final Player bec, final double double2, final double double3, final double double4, final SoundEvent ack, final SoundSource acm, final float float7, final float float8);
    
    public abstract void playSound(@Nullable final Player bec, final Entity aom, final SoundEvent ack, final SoundSource acm, final float float5, final float float6);
    
    public void playLocalSound(final double double1, final double double2, final double double3, final SoundEvent ack, final SoundSource acm, final float float6, final float float7, final boolean boolean8) {
    }
    
    @Override
    public void addParticle(final ParticleOptions hf, final double double2, final double double3, final double double4, final double double5, final double double6, final double double7) {
    }
    
    public void addParticle(final ParticleOptions hf, 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 hf, final double double2, final double double3, final double double4, final double double5, final double double6, final double double7) {
    }
    
    public void addAlwaysVisibleParticle(final ParticleOptions hf, 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 cdl) {
        if (this.updatingBlockEntities) {
            Level.LOGGER.error("Adding block entity while ticking: {} @ {}", new org.apache.logging.log4j.util.Supplier[] { () -> Registry.BLOCK_ENTITY_TYPE.getKey(cdl.getType()), cdl::getBlockPos });
        }
        final boolean boolean3 = this.blockEntityList.add(cdl);
        if (boolean3 && cdl instanceof TickableBlockEntity) {
            this.tickableBlockEntities.add(cdl);
        }
        if (this.isClientSide) {
            final BlockPos fu4 = cdl.getBlockPos();
            final BlockState cfj5 = this.getBlockState(fu4);
            this.sendBlockUpdated(fu4, cfj5, cfj5, 2);
        }
        return boolean3;
    }
    
    public void addAllPendingBlockEntities(final Collection<BlockEntity> collection) {
        if (this.updatingBlockEntities) {
            this.pendingBlockEntities.addAll(collection);
        }
        else {
            for (final BlockEntity cdl4 : collection) {
                this.addBlockEntity(cdl4);
            }
        }
    }
    
    public void tickBlockEntities() {
        final ProfilerFiller ami2 = this.getProfiler();
        ami2.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 cdl4 = iterator3.next();
            if (!cdl4.isRemoved() && cdl4.hasLevel()) {
                final BlockPos fu5 = cdl4.getBlockPos();
                if (this.getChunkSource().isTickingChunk(fu5) && this.getWorldBorder().isWithinBounds(fu5)) {
                    try {
                        ami2.push(() -> String.valueOf(BlockEntityType.getKey(cdl4.getType())));
                        if (cdl4.getType().isValid(this.getBlockState(fu5).getBlock())) {
                            ((TickableBlockEntity)cdl4).tick();
                        }
                        else {
                            cdl4.logInvalidState();
                        }
                        ami2.pop();
                    }
                    catch (Throwable throwable6) {
                        final CrashReport j7 = CrashReport.forThrowable(throwable6, "Ticking block entity");
                        final CrashReportCategory k8 = j7.addCategory("Block entity being ticked");
                        cdl4.fillCrashReportCategory(k8);
                        throw new ReportedException(j7);
                    }
                }
            }
            if (cdl4.isRemoved()) {
                iterator3.remove();
                this.blockEntityList.remove(cdl4);
                if (!this.hasChunkAt(cdl4.getBlockPos())) {
                    continue;
                }
                this.getChunkAt(cdl4.getBlockPos()).removeBlockEntity(cdl4.getBlockPos());
            }
        }
        this.updatingBlockEntities = false;
        ami2.popPush("pendingBlockEntities");
        if (!this.pendingBlockEntities.isEmpty()) {
            for (int integer4 = 0; integer4 < this.pendingBlockEntities.size(); ++integer4) {
                final BlockEntity cdl5 = this.pendingBlockEntities.get(integer4);
                if (!cdl5.isRemoved()) {
                    if (!this.blockEntityList.contains(cdl5)) {
                        this.addBlockEntity(cdl5);
                    }
                    if (this.hasChunkAt(cdl5.getBlockPos())) {
                        final LevelChunk chj6 = this.getChunkAt(cdl5.getBlockPos());
                        final BlockState cfj7 = chj6.getBlockState(cdl5.getBlockPos());
                        chj6.setBlockEntity(cdl5.getBlockPos(), cdl5);
                        this.sendBlockUpdated(cdl5.getBlockPos(), cfj7, cfj7, 3);
                    }
                }
            }
            this.pendingBlockEntities.clear();
        }
        ami2.pop();
    }
    
    public void guardEntityTick(final Consumer<Entity> consumer, final Entity aom) {
        try {
            consumer.accept(aom);
        }
        catch (Throwable throwable4) {
            final CrashReport j5 = CrashReport.forThrowable(throwable4, "Ticking entity");
            final CrashReportCategory k6 = j5.addCategory("Entity being ticked");
            aom.fillCrashReportCategory(k6);
            throw new ReportedException(j5);
        }
    }
    
    public Explosion explode(@Nullable final Entity aom, final double double2, final double double3, final double double4, final float float5, final Explosion.BlockInteraction a) {
        return this.explode(aom, null, null, double2, double3, double4, float5, false, a);
    }
    
    public Explosion explode(@Nullable final Entity aom, final double double2, final double double3, final double double4, final float float5, final boolean boolean6, final Explosion.BlockInteraction a) {
        return this.explode(aom, null, null, double2, double3, double4, float5, boolean6, a);
    }
    
    public Explosion explode(@Nullable final Entity aom, @Nullable final DamageSource anw, @Nullable final ExplosionDamageCalculator bpu, final double double4, final double double5, final double double6, final float float7, final boolean boolean8, final Explosion.BlockInteraction a) {
        final Explosion bpt14 = new Explosion(this, aom, anw, bpu, double4, double5, double6, float7, boolean8, a);
        bpt14.explode();
        bpt14.finalizeExplosion(true);
        return bpt14;
    }
    
    public String gatherChunkSourceStats() {
        return this.getChunkSource().gatherStats();
    }
    
    @Nullable
    @Override
    public BlockEntity getBlockEntity(final BlockPos fu) {
        if (isOutsideBuildHeight(fu)) {
            return null;
        }
        if (!this.isClientSide && Thread.currentThread() != this.thread) {
            return null;
        }
        BlockEntity cdl3 = null;
        if (this.updatingBlockEntities) {
            cdl3 = this.getPendingBlockEntityAt(fu);
        }
        if (cdl3 == null) {
            cdl3 = this.getChunkAt(fu).getBlockEntity(fu, LevelChunk.EntityCreationType.IMMEDIATE);
        }
        if (cdl3 == null) {
            cdl3 = this.getPendingBlockEntityAt(fu);
        }
        return cdl3;
    }
    
    @Nullable
    private BlockEntity getPendingBlockEntityAt(final BlockPos fu) {
        for (int integer3 = 0; integer3 < this.pendingBlockEntities.size(); ++integer3) {
            final BlockEntity cdl4 = this.pendingBlockEntities.get(integer3);
            if (!cdl4.isRemoved() && cdl4.getBlockPos().equals(fu)) {
                return cdl4;
            }
        }
        return null;
    }
    
    public void setBlockEntity(final BlockPos fu, @Nullable final BlockEntity cdl) {
        if (isOutsideBuildHeight(fu)) {
            return;
        }
        if (cdl != null && !cdl.isRemoved()) {
            if (this.updatingBlockEntities) {
                cdl.setLevelAndPosition(this, fu);
                final Iterator<BlockEntity> iterator4 = this.pendingBlockEntities.iterator();
                while (iterator4.hasNext()) {
                    final BlockEntity cdl2 = iterator4.next();
                    if (cdl2.getBlockPos().equals(fu)) {
                        cdl2.setRemoved();
                        iterator4.remove();
                    }
                }
                this.pendingBlockEntities.add(cdl);
            }
            else {
                this.getChunkAt(fu).setBlockEntity(fu, cdl);
                this.addBlockEntity(cdl);
            }
        }
    }
    
    public void removeBlockEntity(final BlockPos fu) {
        final BlockEntity cdl3 = this.getBlockEntity(fu);
        if (cdl3 != null && this.updatingBlockEntities) {
            cdl3.setRemoved();
            this.pendingBlockEntities.remove(cdl3);
        }
        else {
            if (cdl3 != null) {
                this.pendingBlockEntities.remove(cdl3);
                this.blockEntityList.remove(cdl3);
                this.tickableBlockEntities.remove(cdl3);
            }
            this.getChunkAt(fu).removeBlockEntity(fu);
        }
    }
    
    public boolean isLoaded(final BlockPos fu) {
        return !isOutsideBuildHeight(fu) && this.getChunkSource().hasChunk(fu.getX() >> 4, fu.getZ() >> 4);
    }
    
    public boolean loadedAndEntityCanStandOnFace(final BlockPos fu, final Entity aom, final Direction fz) {
        if (isOutsideBuildHeight(fu)) {
            return false;
        }
        final ChunkAccess cgy5 = this.getChunk(fu.getX() >> 4, fu.getZ() >> 4, ChunkStatus.FULL, false);
        return cgy5 != null && cgy5.getBlockState(fu).entityCanStandOnFace(this, fu, aom, fz);
    }
    
    public boolean loadedAndEntityCanStandOn(final BlockPos fu, final Entity aom) {
        return this.loadedAndEntityCanStandOnFace(fu, aom, Direction.UP);
    }
    
    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.getChunkSource().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 aom, final AABB deg, @Nullable final Predicate<? super Entity> predicate) {
        this.getProfiler().incrementCounter("getEntities");
        final List<Entity> list5 = Lists.newArrayList();
        final int integer6 = Mth.floor((deg.minX - 2.0) / 16.0);
        final int integer7 = Mth.floor((deg.maxX + 2.0) / 16.0);
        final int integer8 = Mth.floor((deg.minZ - 2.0) / 16.0);
        final int integer9 = Mth.floor((deg.maxZ + 2.0) / 16.0);
        final ChunkSource chb10 = this.getChunkSource();
        for (int integer10 = integer6; integer10 <= integer7; ++integer10) {
            for (int integer11 = integer8; integer11 <= integer9; ++integer11) {
                final LevelChunk chj13 = chb10.getChunk(integer10, integer11, false);
                if (chj13 != null) {
                    chj13.getEntities(aom, deg, list5, predicate);
                }
            }
        }
        return list5;
    }
    
    public <T extends Entity> List<T> getEntities(@Nullable final EntityType<T> aoq, final AABB deg, final Predicate<? super T> predicate) {
        this.getProfiler().incrementCounter("getEntities");
        final int integer5 = Mth.floor((deg.minX - 2.0) / 16.0);
        final int integer6 = Mth.ceil((deg.maxX + 2.0) / 16.0);
        final int integer7 = Mth.floor((deg.minZ - 2.0) / 16.0);
        final int integer8 = Mth.ceil((deg.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 chj12 = this.getChunkSource().getChunk(integer9, integer10, false);
                if (chj12 != null) {
                    chj12.<Entity>getEntities(aoq, deg, list9, predicate);
                }
            }
        }
        return list9;
    }
    
    @Override
    public <T extends Entity> List<T> getEntitiesOfClass(final Class<? extends T> class1, final AABB deg, @Nullable final Predicate<? super T> predicate) {
        this.getProfiler().incrementCounter("getEntities");
        final int integer5 = Mth.floor((deg.minX - 2.0) / 16.0);
        final int integer6 = Mth.ceil((deg.maxX + 2.0) / 16.0);
        final int integer7 = Mth.floor((deg.minZ - 2.0) / 16.0);
        final int integer8 = Mth.ceil((deg.maxZ + 2.0) / 16.0);
        final List<T> list9 = Lists.newArrayList();
        final ChunkSource chb10 = this.getChunkSource();
        for (int integer9 = integer5; integer9 < integer6; ++integer9) {
            for (int integer10 = integer7; integer10 < integer8; ++integer10) {
                final LevelChunk chj13 = chb10.getChunk(integer9, integer10, false);
                if (chj13 != null) {
                    chj13.<T>getEntitiesOfClass(class1, deg, list9, predicate);
                }
            }
        }
        return list9;
    }
    
    @Override
    public <T extends Entity> List<T> getLoadedEntitiesOfClass(final Class<? extends T> class1, final AABB deg, @Nullable final Predicate<? super T> predicate) {
        this.getProfiler().incrementCounter("getLoadedEntities");
        final int integer5 = Mth.floor((deg.minX - 2.0) / 16.0);
        final int integer6 = Mth.ceil((deg.maxX + 2.0) / 16.0);
        final int integer7 = Mth.floor((deg.minZ - 2.0) / 16.0);
        final int integer8 = Mth.ceil((deg.maxZ + 2.0) / 16.0);
        final List<T> list9 = Lists.newArrayList();
        final ChunkSource chb10 = this.getChunkSource();
        for (int integer9 = integer5; integer9 < integer6; ++integer9) {
            for (int integer10 = integer7; integer10 < integer8; ++integer10) {
                final LevelChunk chj13 = chb10.getChunkNow(integer9, integer10);
                if (chj13 != null) {
                    chj13.<T>getEntitiesOfClass(class1, deg, list9, predicate);
                }
            }
        }
        return list9;
    }
    
    @Nullable
    public abstract Entity getEntity(final int integer);
    
    public void blockEntityChanged(final BlockPos fu, final BlockEntity cdl) {
        if (this.hasChunkAt(fu)) {
            this.getChunkAt(fu).markUnsaved();
        }
    }
    
    @Override
    public int getSeaLevel() {
        return 63;
    }
    
    @Override
    public Level getLevel() {
        return this;
    }
    
    public int getDirectSignalTo(final BlockPos fu) {
        int integer3 = 0;
        integer3 = Math.max(integer3, this.getDirectSignal(fu.below(), Direction.DOWN));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fu.above(), Direction.UP));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fu.north(), Direction.NORTH));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fu.south(), Direction.SOUTH));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fu.west(), Direction.WEST));
        if (integer3 >= 15) {
            return integer3;
        }
        integer3 = Math.max(integer3, this.getDirectSignal(fu.east(), Direction.EAST));
        if (integer3 >= 15) {
            return integer3;
        }
        return integer3;
    }
    
    public boolean hasSignal(final BlockPos fu, final Direction fz) {
        return this.getSignal(fu, fz) > 0;
    }
    
    public int getSignal(final BlockPos fu, final Direction fz) {
        final BlockState cfj4 = this.getBlockState(fu);
        final int integer5 = cfj4.getSignal(this, fu, fz);
        if (cfj4.isRedstoneConductor(this, fu)) {
            return Math.max(integer5, this.getDirectSignalTo(fu));
        }
        return integer5;
    }
    
    public boolean hasNeighborSignal(final BlockPos fu) {
        return this.getSignal(fu.below(), Direction.DOWN) > 0 || this.getSignal(fu.above(), Direction.UP) > 0 || this.getSignal(fu.north(), Direction.NORTH) > 0 || this.getSignal(fu.south(), Direction.SOUTH) > 0 || this.getSignal(fu.west(), Direction.WEST) > 0 || this.getSignal(fu.east(), Direction.EAST) > 0;
    }
    
    public int getBestNeighborSignal(final BlockPos fu) {
        int integer3 = 0;
        for (final Direction fz7 : Level.DIRECTIONS) {
            final int integer4 = this.getSignal(fu.relative(fz7), fz7);
            if (integer4 >= 15) {
                return 15;
            }
            if (integer4 > integer3) {
                integer3 = integer4;
            }
        }
        return integer3;
    }
    
    public void disconnect() {
    }
    
    public long getGameTime() {
        return this.levelData.getGameTime();
    }
    
    public long getDayTime() {
        return this.levelData.getDayTime();
    }
    
    public boolean mayInteract(final Player bec, final BlockPos fu) {
        return true;
    }
    
    public void broadcastEntityEvent(final Entity aom, final byte byte2) {
    }
    
    public void blockEvent(final BlockPos fu, final Block bvr, final int integer3, final int integer4) {
        this.getBlockState(fu).triggerEvent(this, fu, 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.dimensionType().hasSkyLight() && !this.dimensionType().hasCeiling() && this.getThunderLevel(1.0f) > 0.9;
    }
    
    public boolean isRaining() {
        return this.getRainLevel(1.0f) > 0.2;
    }
    
    public boolean isRainingAt(final BlockPos fu) {
        if (!this.isRaining()) {
            return false;
        }
        if (!this.canSeeSky(fu)) {
            return false;
        }
        if (this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, fu).getY() > fu.getY()) {
            return false;
        }
        final Biome bre3 = this.getBiome(fu);
        return bre3.getPrecipitation() == Biome.Precipitation.RAIN && bre3.getTemperature(fu) >= 0.15f;
    }
    
    public boolean isHumidAt(final BlockPos fu) {
        final Biome bre3 = this.getBiome(fu);
        return bre3.isHumid();
    }
    
    @Nullable
    public abstract MapItemSavedData getMapData(final String string);
    
    public abstract void setMapData(final MapItemSavedData czv);
    
    public abstract int getFreeMapId();
    
    public void globalLevelEvent(final int integer1, final BlockPos fu, final int integer3) {
    }
    
    public CrashReportCategory fillReportDetails(final CrashReport j) {
        final CrashReportCategory k3 = j.addCategory("Affected level", 1);
        k3.setDetail("All players", () -> this.players().size() + " total; " + this.players());
        k3.setDetail("Chunk stats", this.getChunkSource()::gatherStats);
        k3.setDetail("Level dimension", () -> this.dimension().location().toString());
        try {
            this.levelData.fillCrashReportCategory(k3);
        }
        catch (Throwable throwable4) {
            k3.setDetailError("Level Data Unobtainable", throwable4);
        }
        return k3;
    }
    
    public abstract void destroyBlockProgress(final int integer1, final BlockPos fu, 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 le) {
    }
    
    public abstract Scoreboard getScoreboard();
    
    public void updateNeighbourForOutputSignal(final BlockPos fu, final Block bvr) {
        for (final Direction fz5 : Direction.Plane.HORIZONTAL) {
            BlockPos fu2 = fu.relative(fz5);
            if (this.hasChunkAt(fu2)) {
                BlockState cfj7 = this.getBlockState(fu2);
                if (cfj7.is(Blocks.COMPARATOR)) {
                    cfj7.neighborChanged(this, fu2, bvr, fu, false);
                }
                else {
                    if (!cfj7.isRedstoneConductor(this, fu2)) {
                        continue;
                    }
                    fu2 = fu2.relative(fz5);
                    cfj7 = this.getBlockState(fu2);
                    if (!cfj7.is(Blocks.COMPARATOR)) {
                        continue;
                    }
                    cfj7.neighborChanged(this, fu2, bvr, fu, false);
                }
            }
        }
    }
    
    @Override
    public DifficultyInstance getCurrentDifficultyAt(final BlockPos fu) {
        long long3 = 0L;
        float float5 = 0.0f;
        if (this.hasChunkAt(fu)) {
            float5 = this.getMoonBrightness();
            long3 = this.getChunkAt(fu).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<?> ni) {
        throw new UnsupportedOperationException("Can't send packets to server unless you're on the client.");
    }
    
    @Override
    public DimensionType dimensionType() {
        return this.dimensionType;
    }
    
    public ResourceKey<DimensionType> dimensionTypeKey() {
        return this.dimensionTypeKey;
    }
    
    public ResourceKey<Level> dimension() {
        return this.dimension;
    }
    
    @Override
    public Random getRandom() {
        return this.random;
    }
    
    @Override
    public boolean isStateAtPosition(final BlockPos fu, final Predicate<BlockState> predicate) {
        return predicate.test(this.getBlockState(fu));
    }
    
    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.get();
    }
    
    public Supplier<ProfilerFiller> getProfilerSupplier() {
        return this.profiler;
    }
    
    @Override
    public BiomeManager getBiomeManager() {
        return this.biomeManager;
    }
    
    public final boolean isDebug() {
        return this.isDebug;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        RESOURCE_KEY_CODEC = ResourceLocation.CODEC.xmap((Function)ResourceKey.<Level>elementKey(Registry.DIMENSION_REGISTRY), (Function)ResourceKey::location);
        OVERWORLD = ResourceKey.<Level>create(Registry.DIMENSION_REGISTRY, new ResourceLocation("overworld"));
        NETHER = ResourceKey.<Level>create(Registry.DIMENSION_REGISTRY, new ResourceLocation("the_nether"));
        END = ResourceKey.<Level>create(Registry.DIMENSION_REGISTRY, new ResourceLocation("the_end"));
        DIRECTIONS = Direction.values();
    }
}
