package net.minecraft.client.multiplayer;

import net.minecraft.world.Difficulty;
import net.minecraft.world.level.biome.AmbientParticleSettings;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.core.Cursor3D;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.ReportedException;
import net.minecraft.tags.TagManager;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.EmptyTickList;
import net.minecraft.world.level.TickList;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.network.protocol.Packet;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.FireworkParticles;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.resources.sounds.EntityBoundSoundInstance;
import net.minecraft.sounds.SoundSource;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.CrashReportCategory;
import net.minecraft.CrashReport;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.level.block.Block;
import net.minecraft.tags.Tag;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import java.util.Random;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.Collection;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.util.Mth;
import java.util.Iterator;
import net.minecraft.core.Registry;
import net.minecraft.world.entity.player.Player;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.GameRules;
import java.util.function.BooleanSupplier;
import net.minecraft.core.BlockPos;
import net.minecraft.Util;
import net.minecraft.client.renderer.BiomeColors;
import com.google.common.collect.Maps;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.util.profiling.ProfilerFiller;
import java.util.function.Supplier;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.resources.ResourceKey;
import net.minecraft.client.color.block.BlockTintCache;
import net.minecraft.world.level.ColorResolver;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import java.util.Map;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.client.player.AbstractClientPlayer;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.world.entity.Entity;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.world.level.Level;

public class ClientLevel extends Level {
    private final Int2ObjectMap<Entity> entitiesById;
    private final ClientPacketListener connection;
    private final LevelRenderer levelRenderer;
    private final ClientLevelData clientLevelData;
    private final DimensionSpecialEffects effects;
    private final Minecraft minecraft;
    private final List<AbstractClientPlayer> players;
    private Scoreboard scoreboard;
    private final Map<String, MapItemSavedData> mapData;
    private int skyFlashTime;
    private final Object2ObjectArrayMap<ColorResolver, BlockTintCache> tintCaches;
    private final ClientChunkCache chunkSource;
    
    public ClientLevel(final ClientPacketListener dyn, final ClientLevelData a, final ResourceKey<Level> ug3, final ResourceKey<DimensionType> ug4, final DimensionType cif, final int integer, final Supplier<ProfilerFiller> supplier, final LevelRenderer ebx, final boolean boolean9, final long long10) {
        super(a, ug3, ug4, cif, supplier, true, boolean9, long10);
        this.entitiesById = (Int2ObjectMap<Entity>)new Int2ObjectOpenHashMap();
        this.minecraft = Minecraft.getInstance();
        this.players = Lists.newArrayList();
        this.scoreboard = new Scoreboard();
        this.mapData = Maps.newHashMap();
        this.tintCaches = Util.make(new Object2ObjectArrayMap(3), object2ObjectArrayMap -> {
            object2ObjectArrayMap.put(BiomeColors.GRASS_COLOR_RESOLVER, new BlockTintCache());
            object2ObjectArrayMap.put(BiomeColors.FOLIAGE_COLOR_RESOLVER, new BlockTintCache());
            object2ObjectArrayMap.put(BiomeColors.WATER_COLOR_RESOLVER, new BlockTintCache());
            return;
        });
        this.chunkSource = new ClientChunkCache(this, integer);
        this.clientLevelData = a;
        this.connection = dyn;
        this.levelRenderer = ebx;
        this.effects = DimensionSpecialEffects.forType(dyn.registryAccess().dimensionTypes().getResourceKey(cif));
        this.setDefaultSpawnPos(new BlockPos(8, 64, 8));
        this.updateSkyBrightness();
        this.prepareWeather();
    }
    
    public DimensionSpecialEffects effects() {
        return this.effects;
    }
    
    public void tick(final BooleanSupplier booleanSupplier) {
        this.getWorldBorder().tick();
        this.tickTime();
        this.getProfiler().push("blocks");
        this.chunkSource.tick(booleanSupplier);
        this.getProfiler().pop();
    }
    
    private void tickTime() {
        this.setGameTime(this.levelData.getGameTime() + 1L);
        if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
            this.setDayTime(this.levelData.getDayTime() + 1L);
        }
    }
    
    public void setGameTime(final long long1) {
        this.clientLevelData.setGameTime(long1);
    }
    
    public void setDayTime(long long1) {
        if (long1 < 0L) {
            long1 = -long1;
            this.getGameRules().<GameRules.BooleanValue>getRule(GameRules.RULE_DAYLIGHT).set(false, null);
        }
        else {
            this.getGameRules().<GameRules.BooleanValue>getRule(GameRules.RULE_DAYLIGHT).set(true, null);
        }
        this.clientLevelData.setDayTime(long1);
    }
    
    public Iterable<Entity> entitiesForRendering() {
        return (Iterable<Entity>)this.entitiesById.values();
    }
    
    public void tickEntities() {
        final ProfilerFiller ami2 = this.getProfiler();
        ami2.push("entities");
        final ObjectIterator<Int2ObjectMap.Entry<Entity>> objectIterator3 = (ObjectIterator<Int2ObjectMap.Entry<Entity>>)this.entitiesById.int2ObjectEntrySet().iterator();
        while (objectIterator3.hasNext()) {
            final Int2ObjectMap.Entry<Entity> entry4 = (Int2ObjectMap.Entry<Entity>)objectIterator3.next();
            final Entity aom5 = (Entity)entry4.getValue();
            if (aom5.isPassenger()) {
                continue;
            }
            ami2.push("tick");
            if (!aom5.removed) {
                this.guardEntityTick(this::tickNonPassenger, aom5);
            }
            ami2.pop();
            ami2.push("remove");
            if (aom5.removed) {
                objectIterator3.remove();
                this.onEntityRemoved(aom5);
            }
            ami2.pop();
        }
        this.tickBlockEntities();
        ami2.pop();
    }
    
    public void tickNonPassenger(final Entity aom) {
        if (!(aom instanceof Player) && !this.getChunkSource().isEntityTickingChunk(aom)) {
            this.updateChunkPos(aom);
            return;
        }
        aom.setPosAndOldPos(aom.getX(), aom.getY(), aom.getZ());
        aom.yRotO = aom.yRot;
        aom.xRotO = aom.xRot;
        if (aom.inChunk || aom.isSpectator()) {
            ++aom.tickCount;
            this.getProfiler().push(() -> Registry.ENTITY_TYPE.getKey(aom.getType()).toString());
            aom.tick();
            this.getProfiler().pop();
        }
        this.updateChunkPos(aom);
        if (aom.inChunk) {
            for (final Entity aom2 : aom.getPassengers()) {
                this.tickPassenger(aom, aom2);
            }
        }
    }
    
    public void tickPassenger(final Entity aom1, final Entity aom2) {
        if (aom2.removed || aom2.getVehicle() != aom1) {
            aom2.stopRiding();
            return;
        }
        if (!(aom2 instanceof Player) && !this.getChunkSource().isEntityTickingChunk(aom2)) {
            return;
        }
        aom2.setPosAndOldPos(aom2.getX(), aom2.getY(), aom2.getZ());
        aom2.yRotO = aom2.yRot;
        aom2.xRotO = aom2.xRot;
        if (aom2.inChunk) {
            ++aom2.tickCount;
            aom2.rideTick();
        }
        this.updateChunkPos(aom2);
        if (aom2.inChunk) {
            for (final Entity aom3 : aom2.getPassengers()) {
                this.tickPassenger(aom2, aom3);
            }
        }
    }
    
    private void updateChunkPos(final Entity aom) {
        if (!aom.checkAndResetUpdateChunkPos()) {
            return;
        }
        this.getProfiler().push("chunkCheck");
        final int integer3 = Mth.floor(aom.getX() / 16.0);
        final int integer4 = Mth.floor(aom.getY() / 16.0);
        final int integer5 = Mth.floor(aom.getZ() / 16.0);
        if (!aom.inChunk || aom.xChunk != integer3 || aom.yChunk != integer4 || aom.zChunk != integer5) {
            if (aom.inChunk && this.hasChunk(aom.xChunk, aom.zChunk)) {
                this.getChunk(aom.xChunk, aom.zChunk).removeEntity(aom, aom.yChunk);
            }
            if (aom.checkAndResetForcedChunkAdditionFlag() || this.hasChunk(integer3, integer5)) {
                this.getChunk(integer3, integer5).addEntity(aom);
            }
            else {
                if (aom.inChunk) {
                    ClientLevel.LOGGER.warn("Entity {} left loaded chunk area", aom);
                }
                aom.inChunk = false;
            }
        }
        this.getProfiler().pop();
    }
    
    public void unload(final LevelChunk chj) {
        this.blockEntitiesToUnload.addAll(chj.getBlockEntities().values());
        this.chunkSource.getLightEngine().enableLightSources(chj.getPos(), false);
    }
    
    public void onChunkLoaded(final int integer1, final int integer2) {
        this.tintCaches.forEach((colorResolver, dmo) -> dmo.invalidateForChunk(integer1, integer2));
    }
    
    public void clearTintCaches() {
        this.tintCaches.forEach((colorResolver, dmo) -> dmo.invalidateAll());
    }
    
    @Override
    public boolean hasChunk(final int integer1, final int integer2) {
        return true;
    }
    
    public int getEntityCount() {
        return this.entitiesById.size();
    }
    
    public void addPlayer(final int integer, final AbstractClientPlayer ebc) {
        this.addEntity(integer, ebc);
        this.players.add(ebc);
    }
    
    public void putNonPlayerEntity(final int integer, final Entity aom) {
        this.addEntity(integer, aom);
    }
    
    private void addEntity(final int integer, final Entity aom) {
        this.removeEntity(integer);
        this.entitiesById.put(integer, aom);
        this.getChunkSource().getChunk(Mth.floor(aom.getX() / 16.0), Mth.floor(aom.getZ() / 16.0), ChunkStatus.FULL, true).addEntity(aom);
    }
    
    public void removeEntity(final int integer) {
        final Entity aom3 = (Entity)this.entitiesById.remove(integer);
        if (aom3 != null) {
            aom3.remove();
            this.onEntityRemoved(aom3);
        }
    }
    
    private void onEntityRemoved(final Entity aom) {
        aom.unRide();
        if (aom.inChunk) {
            this.getChunk(aom.xChunk, aom.zChunk).removeEntity(aom);
        }
        this.players.remove(aom);
    }
    
    public void reAddEntitiesToChunk(final LevelChunk chj) {
        for (final Int2ObjectMap.Entry<Entity> entry4 : this.entitiesById.int2ObjectEntrySet()) {
            final Entity aom5 = (Entity)entry4.getValue();
            final int integer6 = Mth.floor(aom5.getX() / 16.0);
            final int integer7 = Mth.floor(aom5.getZ() / 16.0);
            if (integer6 == chj.getPos().x && integer7 == chj.getPos().z) {
                chj.addEntity(aom5);
            }
        }
    }
    
    @Nullable
    @Override
    public Entity getEntity(final int integer) {
        return (Entity)this.entitiesById.get(integer);
    }
    
    public void setKnownState(final BlockPos fu, final BlockState cfj) {
        this.setBlock(fu, cfj, 19);
    }
    
    @Override
    public void disconnect() {
        this.connection.getConnection().disconnect(new TranslatableComponent("multiplayer.status.quitting"));
    }
    
    public void animateTick(final int integer1, final int integer2, final int integer3) {
        final int integer4 = 32;
        final Random random6 = new Random();
        boolean boolean7 = false;
        if (this.minecraft.gameMode.getPlayerMode() == GameType.CREATIVE) {
            for (final ItemStack bki9 : this.minecraft.player.getHandSlots()) {
                if (bki9.getItem() == Blocks.BARRIER.asItem()) {
                    boolean7 = true;
                    break;
                }
            }
        }
        final BlockPos.MutableBlockPos a8 = new BlockPos.MutableBlockPos();
        for (int integer5 = 0; integer5 < 667; ++integer5) {
            this.doAnimateTick(integer1, integer2, integer3, 16, random6, boolean7, a8);
            this.doAnimateTick(integer1, integer2, integer3, 32, random6, boolean7, a8);
        }
    }
    
    public void doAnimateTick(final int integer1, final int integer2, final int integer3, final int integer4, final Random random, final boolean boolean6, final BlockPos.MutableBlockPos a) {
        final int integer5 = integer1 + this.random.nextInt(integer4) - this.random.nextInt(integer4);
        final int integer6 = integer2 + this.random.nextInt(integer4) - this.random.nextInt(integer4);
        final int integer7 = integer3 + this.random.nextInt(integer4) - this.random.nextInt(integer4);
        a.set(integer5, integer6, integer7);
        final BlockState cfj12 = this.getBlockState(a);
        cfj12.getBlock().animateTick(cfj12, this, a, random);
        final FluidState cxa13 = this.getFluidState(a);
        if (!cxa13.isEmpty()) {
            cxa13.animateTick(this, a, random);
            final ParticleOptions hf14 = cxa13.getDripParticle();
            if (hf14 != null && this.random.nextInt(10) == 0) {
                final boolean boolean7 = cfj12.isFaceSturdy(this, a, Direction.DOWN);
                final BlockPos fu16 = a.below();
                this.trySpawnDripParticles(fu16, this.getBlockState(fu16), hf14, boolean7);
            }
        }
        if (boolean6 && cfj12.is(Blocks.BARRIER)) {
            this.addParticle(ParticleTypes.BARRIER, integer5 + 0.5, integer6 + 0.5, integer7 + 0.5, 0.0, 0.0, 0.0);
        }
        if (!cfj12.isCollisionShapeFullBlock(this, a)) {
            this.getBiome(a).getAmbientParticle().ifPresent(bqx -> {
                if (bqx.canSpawn(this.random)) {
                    this.addParticle(bqx.getOptions(), a.getX() + this.random.nextDouble(), a.getY() + this.random.nextDouble(), a.getZ() + this.random.nextDouble(), 0.0, 0.0, 0.0);
                }
            });
        }
    }
    
    private void trySpawnDripParticles(final BlockPos fu, final BlockState cfj, final ParticleOptions hf, final boolean boolean4) {
        if (!cfj.getFluidState().isEmpty()) {
            return;
        }
        final VoxelShape dfg6 = cfj.getCollisionShape(this, fu);
        final double double7 = dfg6.max(Direction.Axis.Y);
        if (double7 < 1.0) {
            if (boolean4) {
                this.spawnFluidParticle(fu.getX(), fu.getX() + 1, fu.getZ(), fu.getZ() + 1, fu.getY() + 1 - 0.05, hf);
            }
        }
        else if (!cfj.is(BlockTags.IMPERMEABLE)) {
            final double double8 = dfg6.min(Direction.Axis.Y);
            if (double8 > 0.0) {
                this.spawnParticle(fu, hf, dfg6, fu.getY() + double8 - 0.05);
            }
            else {
                final BlockPos fu2 = fu.below();
                final BlockState cfj2 = this.getBlockState(fu2);
                final VoxelShape dfg7 = cfj2.getCollisionShape(this, fu2);
                final double double9 = dfg7.max(Direction.Axis.Y);
                if (double9 < 1.0 && cfj2.getFluidState().isEmpty()) {
                    this.spawnParticle(fu, hf, dfg6, fu.getY() - 0.05);
                }
            }
        }
    }
    
    private void spawnParticle(final BlockPos fu, final ParticleOptions hf, final VoxelShape dfg, final double double4) {
        this.spawnFluidParticle(fu.getX() + dfg.min(Direction.Axis.X), fu.getX() + dfg.max(Direction.Axis.X), fu.getZ() + dfg.min(Direction.Axis.Z), fu.getZ() + dfg.max(Direction.Axis.Z), double4, hf);
    }
    
    private void spawnFluidParticle(final double double1, final double double2, final double double3, final double double4, final double double5, final ParticleOptions hf) {
        this.addParticle(hf, Mth.lerp(this.random.nextDouble(), double1, double2), double5, Mth.lerp(this.random.nextDouble(), double3, double4), 0.0, 0.0, 0.0);
    }
    
    public void removeAllPendingEntityRemovals() {
        final ObjectIterator<Int2ObjectMap.Entry<Entity>> objectIterator2 = (ObjectIterator<Int2ObjectMap.Entry<Entity>>)this.entitiesById.int2ObjectEntrySet().iterator();
        while (objectIterator2.hasNext()) {
            final Int2ObjectMap.Entry<Entity> entry3 = (Int2ObjectMap.Entry<Entity>)objectIterator2.next();
            final Entity aom4 = (Entity)entry3.getValue();
            if (aom4.removed) {
                objectIterator2.remove();
                this.onEntityRemoved(aom4);
            }
        }
    }
    
    @Override
    public CrashReportCategory fillReportDetails(final CrashReport j) {
        final CrashReportCategory k3 = super.fillReportDetails(j);
        k3.setDetail("Server brand", () -> this.minecraft.player.getServerBrand());
        k3.setDetail("Server type", () -> (this.minecraft.getSingleplayerServer() == null) ? "Non-integrated multiplayer server" : "Integrated singleplayer server");
        return k3;
    }
    
    @Override
    public 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) {
        if (bec == this.minecraft.player) {
            this.playLocalSound(double2, double3, double4, ack, acm, float7, float8, false);
        }
    }
    
    @Override
    public void playSound(@Nullable final Player bec, final Entity aom, final SoundEvent ack, final SoundSource acm, final float float5, final float float6) {
        if (bec == this.minecraft.player) {
            this.minecraft.getSoundManager().play(new EntityBoundSoundInstance(ack, acm, aom));
        }
    }
    
    public void playLocalSound(final BlockPos fu, final SoundEvent ack, final SoundSource acm, final float float4, final float float5, final boolean boolean6) {
        this.playLocalSound(fu.getX() + 0.5, fu.getY() + 0.5, fu.getZ() + 0.5, ack, acm, float4, float5, boolean6);
    }
    
    @Override
    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) {
        final double double4 = this.minecraft.gameRenderer.getMainCamera().getPosition().distanceToSqr(double1, double2, double3);
        final SimpleSoundInstance eoi15 = new SimpleSoundInstance(ack, acm, float6, float7, double1, double2, double3);
        if (boolean8 && double4 > 100.0) {
            final double double5 = Math.sqrt(double4) / 40.0;
            this.minecraft.getSoundManager().playDelayed(eoi15, (int)(double5 * 20.0));
        }
        else {
            this.minecraft.getSoundManager().play(eoi15);
        }
    }
    
    @Override
    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) {
        this.minecraft.particleEngine.add(new FireworkParticles.Starter(this, double1, double2, double3, double4, double5, double6, this.minecraft.particleEngine, le));
    }
    
    @Override
    public void sendPacketToServer(final Packet<?> ni) {
        this.connection.send(ni);
    }
    
    @Override
    public RecipeManager getRecipeManager() {
        return this.connection.getRecipeManager();
    }
    
    public void setScoreboard(final Scoreboard dfm) {
        this.scoreboard = dfm;
    }
    
    @Override
    public TickList<Block> getBlockTicks() {
        return EmptyTickList.empty();
    }
    
    @Override
    public TickList<Fluid> getLiquidTicks() {
        return EmptyTickList.empty();
    }
    
    @Override
    public ClientChunkCache getChunkSource() {
        return this.chunkSource;
    }
    
    @Nullable
    @Override
    public MapItemSavedData getMapData(final String string) {
        return this.mapData.get(string);
    }
    
    @Override
    public void setMapData(final MapItemSavedData czv) {
        this.mapData.put(czv.getId(), czv);
    }
    
    @Override
    public int getFreeMapId() {
        return 0;
    }
    
    @Override
    public Scoreboard getScoreboard() {
        return this.scoreboard;
    }
    
    @Override
    public TagManager getTagManager() {
        return this.connection.getTags();
    }
    
    @Override
    public void sendBlockUpdated(final BlockPos fu, final BlockState cfj2, final BlockState cfj3, final int integer) {
        this.levelRenderer.blockChanged(this, fu, cfj2, cfj3, integer);
    }
    
    @Override
    public void setBlocksDirty(final BlockPos fu, final BlockState cfj2, final BlockState cfj3) {
        this.levelRenderer.setBlockDirty(fu, cfj2, cfj3);
    }
    
    public void setSectionDirtyWithNeighbors(final int integer1, final int integer2, final int integer3) {
        this.levelRenderer.setSectionDirtyWithNeighbors(integer1, integer2, integer3);
    }
    
    @Override
    public void destroyBlockProgress(final int integer1, final BlockPos fu, final int integer3) {
        this.levelRenderer.destroyBlockProgress(integer1, fu, integer3);
    }
    
    @Override
    public void globalLevelEvent(final int integer1, final BlockPos fu, final int integer3) {
        this.levelRenderer.globalLevelEvent(integer1, fu, integer3);
    }
    
    @Override
    public void levelEvent(@Nullable final Player bec, final int integer2, final BlockPos fu, final int integer4) {
        try {
            this.levelRenderer.levelEvent(bec, integer2, fu, integer4);
        }
        catch (Throwable throwable6) {
            final CrashReport j7 = CrashReport.forThrowable(throwable6, "Playing level event");
            final CrashReportCategory k8 = j7.addCategory("Level event being played");
            k8.setDetail("Block coordinates", CrashReportCategory.formatLocation(fu));
            k8.setDetail("Event source", bec);
            k8.setDetail("Event type", integer2);
            k8.setDetail("Event data", integer4);
            throw new ReportedException(j7);
        }
    }
    
    @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) {
        this.levelRenderer.addParticle(hf, hf.getType().getOverrideLimiter(), double2, double3, double4, double5, double6, double7);
    }
    
    @Override
    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) {
        this.levelRenderer.addParticle(hf, hf.getType().getOverrideLimiter() || boolean2, double3, double4, double5, double6, double7, double8);
    }
    
    @Override
    public void addAlwaysVisibleParticle(final ParticleOptions hf, final double double2, final double double3, final double double4, final double double5, final double double6, final double double7) {
        this.levelRenderer.addParticle(hf, false, true, double2, double3, double4, double5, double6, double7);
    }
    
    @Override
    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) {
        this.levelRenderer.addParticle(hf, hf.getType().getOverrideLimiter() || boolean2, true, double3, double4, double5, double6, double7, double8);
    }
    
    @Override
    public List<AbstractClientPlayer> players() {
        return this.players;
    }
    
    @Override
    public Biome getUncachedNoiseBiome(final int integer1, final int integer2, final int integer3) {
        return Biomes.PLAINS;
    }
    
    public float getSkyDarken(final float float1) {
        final float float2 = this.getTimeOfDay(float1);
        float float3 = 1.0f - (Mth.cos(float2 * 6.2831855f) * 2.0f + 0.2f);
        float3 = Mth.clamp(float3, 0.0f, 1.0f);
        float3 = 1.0f - float3;
        float3 *= (float)(1.0 - this.getRainLevel(float1) * 5.0f / 16.0);
        float3 *= (float)(1.0 - this.getThunderLevel(float1) * 5.0f / 16.0);
        return float3 * 0.8f + 0.2f;
    }
    
    public Vec3 getSkyColor(final BlockPos fu, final float float2) {
        final float float3 = this.getTimeOfDay(float2);
        float float4 = Mth.cos(float3 * 6.2831855f) * 2.0f + 0.5f;
        float4 = Mth.clamp(float4, 0.0f, 1.0f);
        final Biome bre6 = this.getBiome(fu);
        final int integer7 = bre6.getSkyColor();
        float float5 = (integer7 >> 16 & 0xFF) / 255.0f;
        float float6 = (integer7 >> 8 & 0xFF) / 255.0f;
        float float7 = (integer7 & 0xFF) / 255.0f;
        float5 *= float4;
        float6 *= float4;
        float7 *= float4;
        final float float8 = this.getRainLevel(float2);
        if (float8 > 0.0f) {
            final float float9 = (float5 * 0.3f + float6 * 0.59f + float7 * 0.11f) * 0.6f;
            final float float10 = 1.0f - float8 * 0.75f;
            float5 = float5 * float10 + float9 * (1.0f - float10);
            float6 = float6 * float10 + float9 * (1.0f - float10);
            float7 = float7 * float10 + float9 * (1.0f - float10);
        }
        final float float9 = this.getThunderLevel(float2);
        if (float9 > 0.0f) {
            final float float10 = (float5 * 0.3f + float6 * 0.59f + float7 * 0.11f) * 0.2f;
            final float float11 = 1.0f - float9 * 0.75f;
            float5 = float5 * float11 + float10 * (1.0f - float11);
            float6 = float6 * float11 + float10 * (1.0f - float11);
            float7 = float7 * float11 + float10 * (1.0f - float11);
        }
        if (this.skyFlashTime > 0) {
            float float10 = this.skyFlashTime - float2;
            if (float10 > 1.0f) {
                float10 = 1.0f;
            }
            float10 *= 0.45f;
            float5 = float5 * (1.0f - float10) + 0.8f * float10;
            float6 = float6 * (1.0f - float10) + 0.8f * float10;
            float7 = float7 * (1.0f - float10) + 1.0f * float10;
        }
        return new Vec3(float5, float6, float7);
    }
    
    public Vec3 getCloudColor(final float float1) {
        final float float2 = this.getTimeOfDay(float1);
        float float3 = Mth.cos(float2 * 6.2831855f) * 2.0f + 0.5f;
        float3 = Mth.clamp(float3, 0.0f, 1.0f);
        float float4 = 1.0f;
        float float5 = 1.0f;
        float float6 = 1.0f;
        final float float7 = this.getRainLevel(float1);
        if (float7 > 0.0f) {
            final float float8 = (float4 * 0.3f + float5 * 0.59f + float6 * 0.11f) * 0.6f;
            final float float9 = 1.0f - float7 * 0.95f;
            float4 = float4 * float9 + float8 * (1.0f - float9);
            float5 = float5 * float9 + float8 * (1.0f - float9);
            float6 = float6 * float9 + float8 * (1.0f - float9);
        }
        float4 *= float3 * 0.9f + 0.1f;
        float5 *= float3 * 0.9f + 0.1f;
        float6 *= float3 * 0.85f + 0.15f;
        final float float8 = this.getThunderLevel(float1);
        if (float8 > 0.0f) {
            final float float9 = (float4 * 0.3f + float5 * 0.59f + float6 * 0.11f) * 0.2f;
            final float float10 = 1.0f - float8 * 0.95f;
            float4 = float4 * float10 + float9 * (1.0f - float10);
            float5 = float5 * float10 + float9 * (1.0f - float10);
            float6 = float6 * float10 + float9 * (1.0f - float10);
        }
        return new Vec3(float4, float5, float6);
    }
    
    public float getStarBrightness(final float float1) {
        final float float2 = this.getTimeOfDay(float1);
        float float3 = 1.0f - (Mth.cos(float2 * 6.2831855f) * 2.0f + 0.25f);
        float3 = Mth.clamp(float3, 0.0f, 1.0f);
        return float3 * float3 * 0.5f;
    }
    
    public int getSkyFlashTime() {
        return this.skyFlashTime;
    }
    
    @Override
    public void setSkyFlashTime(final int integer) {
        this.skyFlashTime = integer;
    }
    
    @Override
    public float getShade(final Direction fz, final boolean boolean2) {
        final boolean boolean3 = this.effects().constantAmbientLight();
        if (!boolean2) {
            return boolean3 ? 0.9f : 1.0f;
        }
        switch (fz) {
            case DOWN: {
                return boolean3 ? 0.9f : 0.5f;
            }
            case UP: {
                return boolean3 ? 0.9f : 1.0f;
            }
            case NORTH:
            case SOUTH: {
                return 0.8f;
            }
            case WEST:
            case EAST: {
                return 0.6f;
            }
            default: {
                return 1.0f;
            }
        }
    }
    
    @Override
    public int getBlockTint(final BlockPos fu, final ColorResolver colorResolver) {
        final BlockTintCache dmo4 = (BlockTintCache)this.tintCaches.get(colorResolver);
        return dmo4.getColor(fu, () -> this.calculateBlockTint(fu, colorResolver));
    }
    
    public int calculateBlockTint(final BlockPos fu, final ColorResolver colorResolver) {
        final int integer4 = Minecraft.getInstance().options.biomeBlendRadius;
        if (integer4 == 0) {
            return colorResolver.getColor(this.getBiome(fu), fu.getX(), fu.getZ());
        }
        final int integer5 = (integer4 * 2 + 1) * (integer4 * 2 + 1);
        int integer6 = 0;
        int integer7 = 0;
        int integer8 = 0;
        final Cursor3D fx9 = new Cursor3D(fu.getX() - integer4, fu.getY(), fu.getZ() - integer4, fu.getX() + integer4, fu.getY(), fu.getZ() + integer4);
        final BlockPos.MutableBlockPos a10 = new BlockPos.MutableBlockPos();
        while (fx9.advance()) {
            a10.set(fx9.nextX(), fx9.nextY(), fx9.nextZ());
            final int integer9 = colorResolver.getColor(this.getBiome(a10), a10.getX(), a10.getZ());
            integer6 += (integer9 & 0xFF0000) >> 16;
            integer7 += (integer9 & 0xFF00) >> 8;
            integer8 += (integer9 & 0xFF);
        }
        return (integer6 / integer5 & 0xFF) << 16 | (integer7 / integer5 & 0xFF) << 8 | (integer8 / integer5 & 0xFF);
    }
    
    public BlockPos getSharedSpawnPos() {
        BlockPos fu2 = new BlockPos(this.levelData.getXSpawn(), this.levelData.getYSpawn(), this.levelData.getZSpawn());
        if (!this.getWorldBorder().isWithinBounds(fu2)) {
            fu2 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, new BlockPos(this.getWorldBorder().getCenterX(), 0.0, this.getWorldBorder().getCenterZ()));
        }
        return fu2;
    }
    
    public void setDefaultSpawnPos(final BlockPos fu) {
        this.levelData.setSpawn(fu);
    }
    
    @Override
    public String toString() {
        return "ClientLevel";
    }
    
    @Override
    public ClientLevelData getLevelData() {
        return this.clientLevelData;
    }
    
    public static class ClientLevelData implements WritableLevelData {
        private final boolean hardcore;
        private final GameRules gameRules;
        private final boolean isFlat;
        private int xSpawn;
        private int ySpawn;
        private int zSpawn;
        private long gameTime;
        private long dayTime;
        private boolean raining;
        private Difficulty difficulty;
        private boolean difficultyLocked;
        
        public ClientLevelData(final Difficulty and, final boolean boolean2, final boolean boolean3) {
            this.difficulty = and;
            this.hardcore = boolean2;
            this.isFlat = boolean3;
            this.gameRules = new GameRules();
        }
        
        @Override
        public int getXSpawn() {
            return this.xSpawn;
        }
        
        @Override
        public int getYSpawn() {
            return this.ySpawn;
        }
        
        @Override
        public int getZSpawn() {
            return this.zSpawn;
        }
        
        @Override
        public long getGameTime() {
            return this.gameTime;
        }
        
        @Override
        public long getDayTime() {
            return this.dayTime;
        }
        
        @Override
        public void setXSpawn(final int integer) {
            this.xSpawn = integer;
        }
        
        @Override
        public void setYSpawn(final int integer) {
            this.ySpawn = integer;
        }
        
        @Override
        public void setZSpawn(final int integer) {
            this.zSpawn = integer;
        }
        
        public void setGameTime(final long long1) {
            this.gameTime = long1;
        }
        
        public void setDayTime(final long long1) {
            this.dayTime = long1;
        }
        
        @Override
        public void setSpawn(final BlockPos fu) {
            this.xSpawn = fu.getX();
            this.ySpawn = fu.getY();
            this.zSpawn = fu.getZ();
        }
        
        @Override
        public boolean isThundering() {
            return false;
        }
        
        @Override
        public boolean isRaining() {
            return this.raining;
        }
        
        @Override
        public void setRaining(final boolean boolean1) {
            this.raining = boolean1;
        }
        
        @Override
        public boolean isHardcore() {
            return this.hardcore;
        }
        
        @Override
        public GameRules getGameRules() {
            return this.gameRules;
        }
        
        @Override
        public Difficulty getDifficulty() {
            return this.difficulty;
        }
        
        @Override
        public boolean isDifficultyLocked() {
            return this.difficultyLocked;
        }
        
        @Override
        public void fillCrashReportCategory(final CrashReportCategory k) {
            super.fillCrashReportCategory(k);
        }
        
        public void setDifficulty(final Difficulty and) {
            this.difficulty = and;
        }
        
        public void setDifficultyLocked(final boolean boolean1) {
            this.difficultyLocked = boolean1;
        }
        
        public double getHorizonHeight() {
            if (this.isFlat) {
                return 0.0;
            }
            return 63.0;
        }
        
        public double getClearColorScale() {
            if (this.isFlat) {
                return 1.0;
            }
            return 0.03125;
        }
    }
}
