org.diorite.impl.world.WorldImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.diorite.impl.world.WorldImpl.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016. Diorite (by Bartomiej Mazur (aka GotoFinal))
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.diorite.impl.world;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.IntStream;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.diorite.impl.DioriteCore;
import org.diorite.impl.Tickable;
import org.diorite.impl.connection.packets.Packet;
import org.diorite.impl.connection.packets.play.clientbound.PacketPlayClientboundUpdateTime;
import org.diorite.impl.entity.IEntity;
import org.diorite.impl.entity.IItem;
import org.diorite.impl.entity.IPlayer;
import org.diorite.impl.entity.tracker.BaseTracker;
import org.diorite.impl.entity.tracker.EntityTrackers;
import org.diorite.impl.world.chunk.ChunkImpl;
import org.diorite.impl.world.chunk.ChunkManagerImpl;
import org.diorite.impl.world.chunk.ChunkManagerImpl.ChunkLock;
import org.diorite.impl.world.io.ChunkIOService;
import org.diorite.impl.world.io.requests.Request;
import org.diorite.BlockLocation;
import org.diorite.Difficulty;
import org.diorite.Diorite;
import org.diorite.GameMode;
import org.diorite.ILocation;
import org.diorite.ImmutableLocation;
import org.diorite.Location;
import org.diorite.Particle;
import org.diorite.cfg.WorldsConfig.WorldConfig;
import org.diorite.entity.Player;
import org.diorite.inventory.item.ItemStack;
import org.diorite.material.BlockMaterialData;
import org.diorite.nbt.NbtNamedTagContainer;
import org.diorite.nbt.NbtOutputStream;
import org.diorite.nbt.NbtTagCompound;
import org.diorite.utils.math.DioriteMathUtils;
import org.diorite.utils.math.DioriteRandom;
import org.diorite.utils.math.DioriteRandomUtils;
import org.diorite.utils.math.endian.BigEndianUtils;
import org.diorite.world.Biome;
import org.diorite.world.Block;
import org.diorite.world.Dimension;
import org.diorite.world.HardcoreSettings;
import org.diorite.world.World;
import org.diorite.world.WorldType;
import org.diorite.world.chunk.Chunk;
import org.diorite.world.chunk.ChunkPos;
import org.diorite.world.generator.WorldGenerator;
import org.diorite.world.generator.WorldGenerators;

import it.unimi.dsi.fastutil.longs.LongCollection;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;

public class WorldImpl implements World, Tickable {

    private static final float ITEM_DROP_MOD_1 = 0.7F;
    private static final double ITEM_DROP_MOD_2 = 0.35D;
    private static final int CHUNK_FLAG = (Chunk.CHUNK_SIZE - 1);
    public static final int DEFAULT_AUTOSAVE_TIME = 20 * 60 * 5; // 5 min, TODO: load it from config

    private final DioriteCore core;
    private final Logger worldLoaderLogger;
    protected final String name;
    protected final WorldGroupImpl worldGroup;
    protected final ChunkManagerImpl chunkManager;
    protected final Dimension dimension;
    protected final WorldType worldType;
    protected final EntityTrackers entityTrackers;
    protected final WorldBorderImpl worldBorder = new WorldBorderImpl(this);
    protected boolean vanillaCompatible = false;
    protected Difficulty difficulty = Difficulty.NORMAL;
    protected HardcoreSettings hardcore = new HardcoreSettings(false);
    protected GameMode defaultGameMode = GameMode.SURVIVAL;
    protected int maxHeight = Chunk.CHUNK_FULL_HEIGHT - 1;
    protected byte forceLoadedRadius = 5;
    private final LongCollection activeChunks = new LongOpenHashSet(1000);
    protected long seed;
    protected boolean raining;
    protected boolean thundering;
    protected int clearWeatherTime;
    protected int rainTime;
    protected int thunderTime;
    protected ILocation spawn;
    protected WorldGenerator generator;
    protected long time;
    protected final ChunkLock spawnLock;
    protected boolean noUpdateMode = true;
    protected final DioriteRandom random = DioriteRandomUtils.newRandom();
    protected int saveTimer = DEFAULT_AUTOSAVE_TIME;
    protected boolean autosave = true;

    // TODO: add some method allowing to set multiple blocks without calling getChunk so often

    public WorldImpl(final DioriteCore core, final ChunkIOService chunkIO, final String name,
            final WorldGroupImpl group, final Dimension dimension, final WorldType worldType,
            final String generator, final Map<String, Object> generatorOptions) {
        this.core = core;
        this.worldLoaderLogger = LoggerFactory.getLogger("[WorldLoader][" + name + "]");
        this.name = name;
        this.worldGroup = group;
        this.dimension = dimension;
        this.worldType = worldType;
        this.generator = WorldGenerators.getGenerator(generator, this, generatorOptions);
        this.chunkManager = new ChunkManagerImpl(core, this, chunkIO,
                WorldGenerators.getGenerator(generator, this, generatorOptions));

        this.spawnLock = this.createLock("spawn loader");
        this.entityTrackers = new EntityTrackers(this);

        chunkIO.start(this);
    }

    public WorldImpl(final DioriteCore core, final ChunkIOService chunkIO, final String name,
            final WorldGroupImpl group, final Dimension dimension, final WorldType worldType,
            final String generator) {
        this(core, chunkIO, name, group, dimension, worldType, generator, null);
    }

    public EntityTrackers getEntityTrackers() {
        return this.entityTrackers;
    }

    static void forChunksParallel(final int r, final ChunkPos center, final Consumer<ChunkPos> action) {
        if (r == 0) {
            action.accept(center);
            return;
        }
        IntStream.rangeClosed(-r, r).parallel().forEach(x -> {
            if ((x == r) || (x == -r)) {
                IntStream.rangeClosed(-r, r).parallel().forEach(z -> action.accept(center.add(x, z)));
                return;
            }
            action.accept(center.add(x, r));
            action.accept(center.add(x, -r));
        });
    }

    private static class LoadInfo {
        private final AtomicInteger loadedChunks = new AtomicInteger();
        private long lastTime = System.currentTimeMillis();

        @Override
        public String toString() {
            return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString())
                    .append("loadedChunks", this.loadedChunks.get()).append("lastTime", this.lastTime).toString();
        }
    }

    public ChunkLock createLock(final String desc) {
        return new ChunkLock(this.chunkManager, this.name + ": " + desc);
    }

    @SuppressWarnings("MagicNumber")
    public void initSpawn() {
        this.spawn = this.generator.getSpawnLocation();
        if (this.spawn == null) {
            int spawnX = DioriteRandomUtils.getRandomInt(-64, 64);
            int spawnZ = DioriteRandomUtils.getRandomInt(-64, 64);
            final ChunkImpl chunk = this.getChunkAt(spawnX >> 4, spawnZ >> 4);
            chunk.load(true);
            final int spawnY = this.getHighestBlockY(spawnX, spawnZ);
            for (int tries = 0; (tries < 1000) && !this.generator.canSpawn(spawnX, spawnY, spawnZ); ++tries) {
                spawnX += DioriteRandomUtils.getRandomInt(-64, 64);
                spawnZ += DioriteRandomUtils.getRandomInt(-64, 64);
            }
            this.spawn = new ImmutableLocation(spawnX, spawnY, spawnZ);
        }
    }

    public synchronized void loadBase(final int chunkRadius, final BlockLocation center) {
        final int toLoad = DioriteMathUtils.square((chunkRadius << 1) + 1);
        this.worldLoaderLogger.info("Loading " + toLoad + " spawn chunks.");
        final LoadInfo info = new LoadInfo();
        this.spawnLock.clear();
        if (chunkRadius > 0) {
            //            final CountDownLatch latch = new CountDownLatch(toLoad);
            //            final ForkJoinPool pool = new ForkJoinPool();
            int i = toLoad;
            final int sx = center.getX() - chunkRadius;
            final int ex = center.getX() + chunkRadius;
            final int sz = center.getZ() - chunkRadius;
            final int ez = center.getZ() + chunkRadius;
            for (int x = sx; x <= ex; x++) {
                for (int z = sz; z <= ez; z++) {
                    //                    final int cx = x;
                    //                    final int cz = z;
                    //                    pool.submit(() -> {
                    final ChunkPos pos = new ChunkPos(x, z, this);
                    this.loadChunk(pos);
                    this.spawnLock.acquire(pos.asLong());
                    final int chunkNum = info.loadedChunks.incrementAndGet();
                    if ((chunkNum % 10) == 0) {
                        final long cur = System.currentTimeMillis();
                        if ((cur - info.lastTime) >= TimeUnit.SECONDS.toMillis(1)) {
                            //noinspection HardcodedFileSeparator
                            info.lastTime = cur;
                            this.worldLoaderLogger.info("Chunk: " + chunkNum + "/" + toLoad + ", (" + i + " left)");
                        }
                    }
                    i--;
                    //                        latch.countDown();
                    //                    });
                }
            }
            //            try
            //            {
            //                latch.await();
            //            } catch (final InterruptedException e)
            //            {
            //                throw new RuntimeException("World loading was interrupted!", e);
            //            }
            //noinspection HardcodedFileSeparator
            info.lastTime = System.currentTimeMillis();
        }
        this.worldLoaderLogger.info("Loaded " + info.loadedChunks.get() + " spawn chunks.");
    }

    public NbtTagCompound writeTo(final NbtTagCompound tag) {
        tag.setString("LevelName", this.name);
        if (this.generator == null) {
            this.generator = WorldGenerators.getGenerator("diorite:default", this, null);
            assert this.generator != null;
        }
        tag.setString("generatorName", this.generator.getName());
        tag.setString("generatorOptions", this.generator.getOptions().toString());

        tag.setInt("clearWeatherTime", this.clearWeatherTime);
        tag.setBoolean("raining", this.raining);
        tag.setInt("rainTime", this.rainTime);
        tag.setBoolean("thundering", this.thundering);
        tag.setInt("thunderTime", this.thunderTime);
        tag.setLong("Time", this.time);

        tag.setDouble("BorderCenterX", this.worldBorder.getCenter().getX());
        tag.setDouble("BorderCenterZ", this.worldBorder.getCenter().getZ());
        tag.setDouble("BorderSize", this.worldBorder.getSize());
        tag.setDouble("BorderSizeLerpTarget", this.worldBorder.getTargetSize());
        tag.setLong("BorderSizeLerpTime", this.worldBorder.getTargetSizeReachTime());
        tag.setDouble("BorderSafeZone", this.worldBorder.getDamageBuffer());
        tag.setDouble("BorderDamagePerBlock", this.worldBorder.getDamageAmount());
        tag.setDouble("BorderWarningBlocks", this.worldBorder.getWarningDistance());
        tag.setDouble("BorderWarningTime", this.worldBorder.getWarningTime());

        return tag;
    }

    public void loadNBT(final NbtTagCompound tag, final WorldConfig cfg) {
        this.clearWeatherTime = tag.getInt("clearWeatherTime", 0);
        this.defaultGameMode = cfg.getGamemode();
        this.difficulty = cfg.getDifficulty();
        this.raining = tag.getBoolean("raining", false);
        this.rainTime = tag.getInt("rainTime", 0);
        this.thundering = tag.getBoolean("thundering", false);
        this.thunderTime = tag.getInt("thunderTime", 0);
        this.seed = cfg.getSeed();
        this.random.setSeed(this.seed);
        this.time = tag.getLong("Time", 0);
        this.vanillaCompatible = cfg.isVanillaCompatible();

        this.hardcore = new HardcoreSettings(cfg.isHardcore(), cfg.getHardcoreAction());
        this.forceLoadedRadius = cfg.getForceLoadedRadius();
        this.spawn = new Location(cfg.getSpawnX(), cfg.getSpawnY(), cfg.getSpawnZ(), cfg.getSpawnYaw(),
                cfg.getSpawnPitch(), this);

        this.worldBorder.setCenter(tag.getDouble("BorderCenterX", 0), tag.getDouble("BorderCenterZ", 0));
        this.worldBorder.setSize(tag.getDouble("BorderSize", WorldBorderImpl.DEFAULT_BORDER_SIZE));
        this.worldBorder.setDamageAmount(tag.getDouble("BorderDamagePerBlock", 0.2));
        this.worldBorder.setDamageBuffer(tag.getDouble("BorderSafeZone", 5));
        this.worldBorder.setWarningDistance((int) tag.getDouble("BorderWarningBlocks", 5));
        this.worldBorder.setWarningTime((int) tag.getDouble("BorderWarningTime", 15));
        this.worldBorder.setTargetSize(tag.getDouble("BorderSizeLerpTarget", this.worldBorder.getSize()));
        this.worldBorder.setTargetReachTime(tag.getLong("BorderSizeLerpTime", 0));
    }

    @Override
    public IItem dropItemNaturally(final ILocation loc, final ItemStack item) {
        final double xs = (this.random.nextFloat() * ITEM_DROP_MOD_1) - ITEM_DROP_MOD_2;
        final double ys = (this.random.nextFloat() * ITEM_DROP_MOD_1) - ITEM_DROP_MOD_2;
        final double zs = (this.random.nextFloat() * ITEM_DROP_MOD_1) - ITEM_DROP_MOD_2;
        final Location copyLoc = loc.toLocation();

        randomLocationWithinBlock(copyLoc, xs, ys, zs);
        return this.dropItem(copyLoc, item);
    }

    @Override
    public IItem dropItemNaturally(final BlockLocation loc, final ItemStack item) {
        final double xs = (this.random.nextFloat() * ITEM_DROP_MOD_1) - ITEM_DROP_MOD_2;
        final double ys = (this.random.nextFloat() * ITEM_DROP_MOD_1) - ITEM_DROP_MOD_2;
        final double zs = (this.random.nextFloat() * ITEM_DROP_MOD_1) - ITEM_DROP_MOD_2;
        final Location copyLoc = loc.toLocation();

        randomLocationWithinBlock(copyLoc, xs, ys, zs);
        return this.dropItem(copyLoc, item);
    }

    @Override
    public IItem dropItem(final ILocation loc, final ItemStack itemStack) {
        if ((itemStack == null) || (itemStack.getAmount() == 0)) {
            return null;
        }
        final IItem item = this.core.getServerManager().getEntityFactory().createEntity(IItem.class, loc);
        item.setItemStack(itemStack);
        item.setPickupDelay(IItem.DEFAULT_BLOCK_DROP_PICKUP_DELAY);

        this.addEntity(item);
        return item;
    }

    private static void randomLocationWithinBlock(final Location loc, final double xs, final double ys,
            final double zs) {
        final double prevX = loc.getX();
        final double prevY = loc.getY();
        final double prevZ = loc.getZ();
        loc.add(xs, ys, zs);
        if (loc.getX() < Math.floor(prevX)) {
            loc.setX(Math.floor(prevX));
        }
        if (loc.getX() >= Math.ceil(prevX)) {
            loc.setX(Math.ceil(prevX - DioriteMathUtils.ONE_OF_100));
        }
        if (loc.getY() < Math.floor(prevY)) {
            loc.setY(Math.floor(prevY));
        }
        if (loc.getY() >= Math.ceil(prevY)) {
            loc.setY(Math.ceil(prevY - DioriteMathUtils.ONE_OF_100));
        }
        if (loc.getZ() < Math.floor(prevZ)) {
            loc.setZ(Math.floor(prevZ));
        }
        if (loc.getZ() >= Math.ceil(prevZ)) {
            loc.setZ(Math.ceil(prevZ - DioriteMathUtils.ONE_OF_100));
        }
    }

    public WorldType getWorldType() {
        return this.worldType;
    }

    @Override
    public boolean isVanillaCompatible() {
        return this.vanillaCompatible;
    }

    @Override
    public void loadChunk(final Chunk chunk) {
        chunk.load();
    }

    @Override
    public void loadChunk(final int x, final int z) {
        this.getChunkAt(x, z).load();
    }

    @Override
    public void loadChunk(final ChunkPos pos) {
        this.getChunkAt(pos.getX(), pos.getZ()).load();
    }

    @Override
    public boolean loadChunk(final int x, final int z, final boolean generate) {
        return this.getChunkAt(x, z).load(generate);
    }

    @Override
    public boolean unloadChunk(final Chunk chunk) {
        return chunk.unload();
    }

    @Override
    public boolean unloadChunk(final int x, final int z) {
        return this.unloadChunk(x, z, true);
    }

    @Override
    public boolean unloadChunk(final int x, final int z, final boolean save) {
        return this.unloadChunk(x, z, save, true);
    }

    @Override
    public boolean unloadChunk(final int x, final int z, final boolean save, final boolean safe) {
        return !this.isChunkLoaded(x, z) || this.getChunkAt(x, z).unload(save, safe);
    }

    @Override
    public boolean regenerateChunk(final int x, final int z) {
        if (!this.chunkManager.forceRegeneration(x, z)) {
            return false;
        }
        this.refreshChunk(x, z);
        return true;
    }

    @Override
    public boolean refreshChunk(final int x, final int z) {
        //        if (! this.isChunkLoaded(x, z))
        //        {
        //            return false;
        //        }

        //        final long key = IntsToLong.pack(x, z);
        //        final boolean result = false;

        // TODO: re-send chunk

        return false;
    }

    @Override
    public boolean isChunkLoaded(final Chunk chunk) {
        return chunk.isLoaded();
    }

    @Override
    public boolean isChunkLoaded(final int x, final int z) {
        return this.chunkManager.isChunkLoaded(x, z);
    }

    @Override
    public boolean isChunkInUse(final int x, final int z) {
        return this.chunkManager.isChunkInUse(x, z);
    }

    @Override
    public File getWorldFile() {
        return this.chunkManager.getService().getWorldDataFolder();
    }

    @Override
    public WorldGroupImpl getWorldGroup() {
        return this.worldGroup;
    }

    @Override
    public DioriteRandom getRandom() {
        return this.random;
    }

    @Override
    public Dimension getDimension() {
        return this.dimension;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public ChunkManagerImpl getChunkManager() {
        return this.chunkManager;
    }

    @Override
    public long getSeed() {
        return this.seed;
    }

    @Override
    public void setSeed(final long seed) {
        this.seed = seed;
        this.random.setSeed(this.seed);
    }

    @Override
    public Difficulty getDifficulty() {
        return this.difficulty;
    }

    @Override
    public void setDifficulty(final Difficulty difficulty) {
        this.difficulty = difficulty;
    }

    @Override
    public HardcoreSettings getHardcore() {
        return this.hardcore;
    }

    @Override
    public void setHardcore(final HardcoreSettings hardcore) {
        this.hardcore = hardcore;
    }

    @Override
    public GameMode getDefaultGameMode() {
        return this.defaultGameMode;
    }

    @Override
    public void setDefaultGameMode(final GameMode defaultGameMode) {
        this.defaultGameMode = defaultGameMode;
    }

    @Override
    public boolean isRaining() {
        return this.raining;
    }

    @Override
    public void setRaining(final boolean raining) {
        this.raining = raining;
    }

    @Override
    public boolean isThundering() {
        return this.thundering;
    }

    @Override
    public void setThundering(final boolean thundering) {
        this.thundering = thundering;
    }

    @Override
    public int getClearWeatherTime() {
        return this.clearWeatherTime;
    }

    @Override
    public void setClearWeatherTime(final int clearWeatherTime) {
        this.clearWeatherTime = clearWeatherTime;
    }

    @Override
    public int getRainTime() {
        return this.rainTime;
    }

    public void setRainTime(final int rainTime) {
        this.rainTime = rainTime;
    }

    @Override
    public int getThunderTime() {
        return this.thunderTime;
    }

    public void setThunderTime(final int thunderTime) {
        this.thunderTime = thunderTime;
    }

    @Override
    public ImmutableLocation getSpawn() {
        return this.spawn.toImmutableLocation();
    }

    @Override
    public void setSpawn(final ILocation spawn) {
        this.spawn = spawn.toImmutableLocation();
    }

    @Override
    public WorldGenerator getGenerator() {
        return this.generator;
    }

    @Override
    public void setGenerator(final WorldGenerator generator) {
        this.generator = generator;
    }

    @Override
    public ChunkImpl getChunkAt(final int x, final int z) {
        return this.chunkManager.getChunk(x, z);
    }

    @Override
    public ChunkImpl getChunkAt(final ChunkPos pos) {
        return this.chunkManager.getChunk(pos.getX(), pos.getZ());
    }

    @Override
    public Block getBlock(final int x, final int y, final int z) {
        if ((y >= Chunk.CHUNK_FULL_HEIGHT) || (y < 0)) {
            throw new IllegalArgumentException("Y (" + y + ") must be in range 0-" + (Chunk.CHUNK_FULL_HEIGHT - 1));
        }
        return this.getChunkAt(x >> 4, z >> 4).getBlock((x & CHUNK_FLAG), y, (z & CHUNK_FLAG));
    }

    @Override
    public int getHighestBlockY(final int x, final int z) {
        return this.getChunkAt(x >> 4, z >> 4).getHighestBlockY(x & CHUNK_FLAG, z & CHUNK_FLAG);
    }

    @Override
    public Block getHighestBlock(final int x, final int z) {
        return this.getChunkAt(x >> 4, z >> 4).getHighestBlock(x & CHUNK_FLAG, z & CHUNK_FLAG);
    }

    @Override
    public void setBlock(final int x, final int y, final int z, final BlockMaterialData material) {
        if (y > this.maxHeight) {
            return;
        }
        this.getChunkAt(x >> 4, z >> 4).setBlock((x & CHUNK_FLAG), y, (z & CHUNK_FLAG), material.ordinal(),
                material.getType());
    }

    @Override
    public void setBlock(final BlockLocation location, final BlockMaterialData material) {
        this.setBlock(location.getX(), location.getY(), location.getZ(), material);
    }

    @Override
    public int getMaxHeight() {
        return this.maxHeight;
    }

    public void setMaxHeight(final int maxHeight) {
        this.maxHeight = maxHeight;
    }

    @Override
    public long getTime() {
        return this.time;
    }

    @Override
    public void setTime(final long time) {
        this.time = time;
        this.broadcastPacketInWorld(new PacketPlayClientboundUpdateTime(this));
    }

    @Override
    public void showParticle(final Particle particle, final boolean isLongDistance, final int x, final int y,
            final int z, final int offsetX, final int offsetY, final int offsetZ, final int particleData,
            final int particleCount, final int... data) {
        this.getPlayersInWorld().forEach(player -> player.showParticle(particle, isLongDistance, x, y, x, offsetX,
                offsetY, offsetZ, particleData, particleCount, data));
    }

    @Override
    public Biome getBiome(final int x, final int y, final int z) // y is ignored, added for future possible changes.
    {
        return this.getChunkAt(x >> 4, z >> 4).getBiome(x & CHUNK_FLAG, y, z & CHUNK_FLAG);
    }

    @Override
    public void setBiome(final int x, final int y, final int z, final Biome biome) // y is ignored, added for future possible changes.
    {
        this.getChunkAt(x >> 4, z >> 4).setBiome(x & CHUNK_FLAG, y, z & CHUNK_FLAG, biome);
    }

    @Override
    public WorldBorderImpl getWorldBorder() {
        return this.worldBorder;
    }

    @Override
    public boolean hasSkyLight() {
        return this.dimension.hasSkyLight();
    }

    @Override
    public boolean isNoUpdateMode() {
        return this.noUpdateMode;
    }

    @Override
    public void setNoUpdateMode(final boolean noUpdateMode) {
        this.noUpdateMode = noUpdateMode;
    }

    @Override
    public byte getForceLoadedRadius() {
        return this.forceLoadedRadius;
    }

    @Override
    public void setForceLoadedRadius(final byte forceLoadedRadius) {
        this.forceLoadedRadius = forceLoadedRadius;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString())
                .append("name", this.name).toString();
    }

    @SuppressWarnings("ObjectEquality")
    @Override
    public void doTick(final int tps) {
        this.worldBorder.doTick(tps);
        this.entityTrackers.doTick(tps);
        this.activeChunks.clear();
        for (final Player entity : this.getPlayersInWorld()) {
            // build a set of chunks around each player in this world, the
            // server view distance is taken here
            final int radius = entity.getRenderDistance();
            final ImmutableLocation playerLocation = entity.getLocation();
            //if (playerLocation.getWorld() == this) // TODO Redundant if?
            //{
            final ChunkPos cp = playerLocation.getChunkPos();
            final int cx = cp.getX();
            final int cz = cp.getZ();
            for (int x = cx - radius, rx = cx + radius; x <= rx; x++) {
                for (int z = cz - radius, rz = cz + radius; z <= rz; z++) {
                    if (this.isChunkLoaded(cx, cz)) {
                        this.activeChunks.add(BigEndianUtils.toLong(x, z));
                    }
                }
            }
            //}
            this.chunkManager.doTick(tps);
        }

        {
            if (this.time == 24000) {
                this.time = 0;
            }
            this.time++; // TODO scale it with server TPS
        }

        if (this.saveTimer-- <= 0) {
            this.saveTimer = DEFAULT_AUTOSAVE_TIME;
            this.chunkManager.unloadOldChunks();
            if (this.autosave) {
                this.save(true);
            }
        }
    }

    @Override
    public boolean isAutoSave() {
        return this.autosave;
    }

    @Override
    public void setAutoSave(final boolean value) {
        this.autosave = value;
    }

    @Override
    public void save() {
        this.save(false);
    }

    @Override
    public void save(final boolean async) {
        //TODO use pipeline
        //TODO do it right...
        if (async) // temp code
        {
            this.chunkManager.getLoadedChunks()
                    .forEach(c -> this.chunkManager.save(c, ChunkIOService.HIGH_PRIORITY - 1));
        } else {
            int p = ChunkIOService.HIGH_PRIORITY - 1;
            final ChunkIOService io = this.chunkManager.getService();
            Request<Void> request = null;
            for (final ChunkImpl chunk : this.chunkManager.getLoadedChunks()) {
                request = io.queueChunkSave(chunk, p--);
            }
            if (request != null) {
                request.await();
            }
        }

        { // write level.dat // TODO temp code
            try (final NbtOutputStream os = NbtOutputStream
                    .getCompressed(new File(this.getWorldFile(), "level.dat"))) {
                final NbtTagCompound nbt = new NbtTagCompound();
                this.writeTo(nbt);
                final NbtNamedTagContainer nbtData = new NbtTagCompound();
                nbtData.setTag("data", nbt);
                os.write(nbtData);
                os.flush();
                os.close();
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void addEntity(final IEntity entity) {
        final BaseTracker<?> tracker;
        if (entity instanceof IPlayer) {
            tracker = this.entityTrackers.addTracked((IPlayer) entity);
        } else {
            tracker = this.entityTrackers.addTracked(entity);
        }
        entity.updateChunk(null, this.getChunkAt(entity.getLocation().getChunkPos()));
        entity.onSpawn(tracker);
    }

    public void removeEntity(final IEntity entity) {
        this.entityTrackers.removeTracked(entity);
        entity.remove(false);
    }

    public void broadcastPacketInWorld(final Packet<?> packet) {
        //noinspection ObjectEquality
        Diorite.getCore().getOnlinePlayers().stream().filter(p -> p.getWorld() == this)
                .forEach(player -> ((IPlayer) player).getNetworkManager().sendPacket(packet));
    }
}