mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-27 19:47:44 +01:00
Merge branch 'master' of https://github.com/Minestom/Minestom
This commit is contained in:
commit
a934df2af2
@ -456,15 +456,14 @@ public final class MinecraftServer {
|
||||
|
||||
final Collection<Player> players = connectionManager.getOnlinePlayers();
|
||||
|
||||
UpdateViewDistancePacket updateViewDistancePacket = new UpdateViewDistancePacket();
|
||||
updateViewDistancePacket.viewDistance = chunkViewDistance;
|
||||
|
||||
// Send packet to all online players
|
||||
PacketUtils.sendGroupedPacket(players, updateViewDistancePacket);
|
||||
|
||||
players.forEach(player -> {
|
||||
final Chunk playerChunk = player.getChunk();
|
||||
if (playerChunk != null) {
|
||||
|
||||
UpdateViewDistancePacket updateViewDistancePacket = new UpdateViewDistancePacket();
|
||||
updateViewDistancePacket.viewDistance = player.getChunkRange();
|
||||
player.getPlayerConnection().sendPacket(updateViewDistancePacket);
|
||||
|
||||
player.refreshVisibleChunks(playerChunk);
|
||||
}
|
||||
});
|
||||
|
@ -28,6 +28,7 @@ import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.callback.OptionalCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.entity.EntityUtils;
|
||||
import net.minestom.server.utils.player.PlayerUtils;
|
||||
@ -229,31 +230,36 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* {@link Instance#hasEnabledAutoChunkLoad()} returns true.
|
||||
*
|
||||
* @param position the teleport position
|
||||
* @param chunks the chunk indexes to load before teleporting the entity,
|
||||
* indexes are from {@link ChunkUtils#getChunkIndex(int, int)},
|
||||
* can be null or empty to only load the chunk at {@code position}
|
||||
* @param callback the optional callback executed, even if auto chunk is not enabled
|
||||
* @throws IllegalStateException if you try to teleport an entity before settings its instance
|
||||
*/
|
||||
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
|
||||
public void teleport(@NotNull Position position, @Nullable long[] chunks, @Nullable Runnable callback) {
|
||||
Check.notNull(position, "Teleport position cannot be null");
|
||||
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
|
||||
|
||||
final Runnable runnable = () -> {
|
||||
if (!this.position.isSimilar(position)) {
|
||||
refreshPosition(position.getX(), position.getY(), position.getZ());
|
||||
}
|
||||
if (!this.position.hasSimilarView(position)) {
|
||||
refreshView(position.getYaw(), position.getPitch());
|
||||
}
|
||||
final ChunkCallback endCallback = (chunk) -> {
|
||||
refreshPosition(position.getX(), position.getY(), position.getZ());
|
||||
refreshView(position.getYaw(), position.getPitch());
|
||||
|
||||
sendSynchronization();
|
||||
|
||||
OptionalCallback.execute(callback);
|
||||
};
|
||||
|
||||
if (instance.hasEnabledAutoChunkLoad()) {
|
||||
instance.loadChunk(position, chunk -> runnable.run());
|
||||
if (chunks == null || chunks.length == 0) {
|
||||
instance.loadOptionalChunk(position, endCallback);
|
||||
} else {
|
||||
runnable.run();
|
||||
ChunkUtils.optionalLoadAll(instance, chunks, null, endCallback);
|
||||
}
|
||||
}
|
||||
|
||||
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
|
||||
teleport(position, null, callback);
|
||||
}
|
||||
|
||||
public void teleport(@NotNull Position position) {
|
||||
teleport(position, null);
|
||||
}
|
||||
@ -1020,13 +1026,14 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
|
||||
final Instance instance = getInstance();
|
||||
if (instance != null) {
|
||||
|
||||
// Needed to refresh the client chunks when connecting for the first time
|
||||
final boolean forceUpdate = this instanceof Player && getAliveTicks() == 0;
|
||||
|
||||
final Chunk lastChunk = instance.getChunkAt(lastX, lastZ);
|
||||
final Chunk newChunk = instance.getChunkAt(x, z);
|
||||
if (lastChunk != null && newChunk != null && lastChunk != newChunk) {
|
||||
synchronized (instance) {
|
||||
instance.removeEntityFromChunk(this, lastChunk);
|
||||
instance.addEntityToChunk(this, newChunk);
|
||||
}
|
||||
if (forceUpdate || (lastChunk != null && newChunk != null && lastChunk != newChunk)) {
|
||||
instance.switchEntityChunk(this, lastChunk, newChunk);
|
||||
if (this instanceof Player) {
|
||||
// Refresh player view
|
||||
final Player player = (Player) this;
|
||||
|
@ -693,55 +693,42 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
// true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance)
|
||||
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance);
|
||||
|
||||
// true if the player needs every chunk around its position
|
||||
final boolean needChunkLoad = !firstSpawn || firstSpawn &&
|
||||
ChunkUtils.getChunkCoordinate((int) getRespawnPoint().getX()) == 0 &&
|
||||
ChunkUtils.getChunkCoordinate((int) getRespawnPoint().getZ()) == 0;
|
||||
|
||||
if (needWorldRefresh && needChunkLoad) {
|
||||
if (needWorldRefresh) {
|
||||
// Remove all previous viewable chunks (from the previous instance)
|
||||
for (Chunk viewableChunk : viewableChunks) {
|
||||
viewableChunk.removeViewer(this);
|
||||
}
|
||||
|
||||
// Send the new dimension
|
||||
if (this.instance != null) {
|
||||
final DimensionType instanceDimensionType = instance.getDimensionType();
|
||||
if (dimensionType != instanceDimensionType)
|
||||
sendDimension(instanceDimensionType);
|
||||
}
|
||||
// Load all the required chunks
|
||||
final Position pos = firstSpawn ? getRespawnPoint() : position;
|
||||
final long[] visibleChunks = ChunkUtils.getChunksInRange(pos, getChunkRange());
|
||||
|
||||
final long[] visibleChunks = ChunkUtils.getChunksInRange(position, getChunkRange());
|
||||
final int length = visibleChunks.length;
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
for (long visibleChunk : visibleChunks) {
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk);
|
||||
|
||||
final ChunkCallback callback = (chunk) -> {
|
||||
if (chunk != null) {
|
||||
chunk.addViewer(this);
|
||||
if (chunk.getChunkX() == ChunkUtils.getChunkCoordinate((int) getPosition().getX()) &&
|
||||
chunk.getChunkZ() == ChunkUtils.getChunkCoordinate((int) getPosition().getZ())) {
|
||||
updateViewPosition(chunk);
|
||||
}
|
||||
final ChunkCallback eachCallback = chunk -> {
|
||||
if (chunk != null) {
|
||||
final int chunkX = ChunkUtils.getChunkCoordinate((int) pos.getX());
|
||||
final int chunkZ = ChunkUtils.getChunkCoordinate((int) pos.getZ());
|
||||
if (chunk.getChunkX() == chunkX &&
|
||||
chunk.getChunkZ() == chunkZ) {
|
||||
updateViewPosition(chunkX, chunkZ);
|
||||
}
|
||||
final boolean isLast = counter.get() == length - 1;
|
||||
if (isLast) {
|
||||
// This is the last chunk to be loaded , spawn player
|
||||
spawnPlayer(instance, firstSpawn);
|
||||
} else {
|
||||
// Increment the counter of current loaded chunks
|
||||
counter.incrementAndGet();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
final ChunkCallback endCallback = chunk -> {
|
||||
// This is the last chunk to be loaded , spawn player
|
||||
spawnPlayer(instance, firstSpawn);
|
||||
};
|
||||
|
||||
// Chunk 0;0 always needs to be loaded
|
||||
instance.loadChunk(0, 0, chunk ->
|
||||
// Load all the required chunks
|
||||
ChunkUtils.optionalLoadAll(instance, visibleChunks, eachCallback, endCallback));
|
||||
|
||||
// WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck.
|
||||
instance.loadOptionalChunk(chunkX, chunkZ, callback);
|
||||
}
|
||||
} else if (!needChunkLoad) {
|
||||
// The player always believe that his position is 0;0 so this is a pretty hacky fix
|
||||
instance.loadOptionalChunk(0, 0, chunk -> spawnPlayer(instance, true));
|
||||
} else {
|
||||
// The player already has the good version of all the chunks.
|
||||
// We just need to refresh his entity viewing list and add him to the instance
|
||||
@ -758,7 +745,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
*
|
||||
* @param firstSpawn true if this is the player first spawn
|
||||
*/
|
||||
private void spawnPlayer(Instance instance, boolean firstSpawn) {
|
||||
private void spawnPlayer(@NotNull Instance instance, boolean firstSpawn) {
|
||||
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
||||
|
||||
super.setInstance(instance);
|
||||
@ -767,7 +754,6 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
teleport(getRespawnPoint());
|
||||
}
|
||||
|
||||
|
||||
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
||||
callEvent(PlayerSpawnEvent.class, spawnEvent);
|
||||
}
|
||||
@ -1545,7 +1531,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
}
|
||||
|
||||
// Update client render distance
|
||||
updateViewPosition(newChunk);
|
||||
updateViewPosition(newChunk.getChunkX(), newChunk.getChunkZ());
|
||||
|
||||
// Load new chunks
|
||||
for (int index : newChunks) {
|
||||
@ -1610,13 +1596,19 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
|
||||
super.teleport(position, () -> {
|
||||
public void teleport(@NotNull Position position, @Nullable long[] chunks, @Nullable Runnable callback) {
|
||||
super.teleport(position, chunks, () -> {
|
||||
updatePlayerPosition();
|
||||
OptionalCallback.execute(callback);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
|
||||
final long[] chunks = ChunkUtils.getChunksInRange(position, getChunkRange());
|
||||
teleport(position, chunks, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(@NotNull Position position) {
|
||||
teleport(position, null);
|
||||
@ -1948,12 +1940,13 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
/**
|
||||
* Sends a {@link UpdateViewPositionPacket} to the player.
|
||||
*
|
||||
* @param chunk the chunk to update the view
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
*/
|
||||
public void updateViewPosition(@NotNull Chunk chunk) {
|
||||
public void updateViewPosition(int chunkX, int chunkZ) {
|
||||
UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket();
|
||||
updateViewPositionPacket.chunkX = chunk.getChunkX();
|
||||
updateViewPositionPacket.chunkZ = chunk.getChunkZ();
|
||||
updateViewPositionPacket.chunkX = chunkX;
|
||||
updateViewPositionPacket.chunkZ = chunkZ;
|
||||
playerConnection.sendPacket(updateViewPositionPacket);
|
||||
}
|
||||
|
||||
|
@ -7,13 +7,13 @@ import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class Encrypter extends MessageToByteEncoder<ByteBuf> {
|
||||
private final CipherBase cipher;
|
||||
private final CipherBase cipher;
|
||||
|
||||
public Encrypter(Cipher cipher) {
|
||||
this.cipher = new CipherBase(cipher);
|
||||
}
|
||||
public Encrypter(Cipher cipher) {
|
||||
this.cipher = new CipherBase(cipher);
|
||||
}
|
||||
|
||||
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn, ByteBuf byteBufOut) throws Exception {
|
||||
this.cipher.encrypt(byteBufIn, byteBufOut);
|
||||
}
|
||||
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn, ByteBuf byteBufOut) throws Exception {
|
||||
this.cipher.encrypt(byteBufIn, byteBufOut);
|
||||
}
|
||||
}
|
||||
|
@ -409,13 +409,17 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
final boolean result = this.viewers.add(player);
|
||||
|
||||
// Send the chunk data & light packets to the player
|
||||
sendChunk(player);
|
||||
// Add to the viewable chunks set
|
||||
player.getViewableChunks().add(this);
|
||||
|
||||
PlayerChunkLoadEvent playerChunkLoadEvent = new PlayerChunkLoadEvent(player, chunkX, chunkZ);
|
||||
player.callEvent(PlayerChunkLoadEvent.class, playerChunkLoadEvent);
|
||||
if (result) {
|
||||
// Send the chunk data & light packets to the player
|
||||
sendChunk(player);
|
||||
|
||||
PlayerChunkLoadEvent playerChunkLoadEvent = new PlayerChunkLoadEvent(player, chunkX, chunkZ);
|
||||
player.callEvent(PlayerChunkLoadEvent.class, playerChunkLoadEvent);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -433,8 +437,11 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
// Remove from the viewable chunks set
|
||||
player.getViewableChunks().remove(this);
|
||||
|
||||
PlayerChunkUnloadEvent playerChunkUnloadEvent = new PlayerChunkUnloadEvent(player, chunkX, chunkZ);
|
||||
player.callEvent(PlayerChunkUnloadEvent.class, playerChunkUnloadEvent);
|
||||
if (result) {
|
||||
PlayerChunkUnloadEvent playerChunkUnloadEvent = new PlayerChunkUnloadEvent(player, chunkX, chunkZ);
|
||||
player.callEvent(PlayerChunkUnloadEvent.class, playerChunkUnloadEvent);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -464,9 +471,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
// Only send loaded chunk
|
||||
if (!isLoaded())
|
||||
return;
|
||||
// Only send chunk to netty client (because it sends raw ByteBuf buffer)
|
||||
if (!PlayerUtils.isNettyClient(player))
|
||||
return;
|
||||
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
|
||||
|
@ -26,9 +26,7 @@ import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Represents a {@link Chunk} which store each individual block in memory.
|
||||
@ -55,7 +53,7 @@ public class DynamicChunk extends Chunk {
|
||||
protected final Int2LongMap updatableBlocksLastUpdate = new Int2LongOpenHashMap();
|
||||
|
||||
// Block entities
|
||||
protected final Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
|
||||
protected final IntSet blockEntities = new IntOpenHashSet();
|
||||
|
||||
private long lastChangeTime;
|
||||
|
||||
@ -384,12 +382,12 @@ public class DynamicChunk extends Chunk {
|
||||
@Override
|
||||
protected ChunkDataPacket createFreshPacket() {
|
||||
ChunkDataPacket fullDataPacket = new ChunkDataPacket(getIdentifier(), getLastChangeTime());
|
||||
fullDataPacket.biomes = biomes.clone();
|
||||
fullDataPacket.biomes = biomes;
|
||||
fullDataPacket.chunkX = chunkX;
|
||||
fullDataPacket.chunkZ = chunkZ;
|
||||
fullDataPacket.paletteStorage = blockPalette.copy();
|
||||
fullDataPacket.customBlockPaletteStorage = customBlockPalette.copy();
|
||||
fullDataPacket.blockEntities = new HashSet<>(blockEntities);
|
||||
fullDataPacket.blockEntities = new IntOpenHashSet(blockEntities);
|
||||
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
|
||||
return fullDataPacket;
|
||||
}
|
||||
|
@ -893,6 +893,19 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronized method to execute {@link #removeEntityFromChunk(Entity, Chunk)}
|
||||
* and {@link #addEntityToChunk(Entity, Chunk)} simultaneously.
|
||||
*
|
||||
* @param entity the entity to change its chunk
|
||||
* @param lastChunk the last entity chunk
|
||||
* @param newChunk the new entity chunk
|
||||
*/
|
||||
public synchronized void switchEntityChunk(@NotNull Entity entity, @NotNull Chunk lastChunk, @NotNull Chunk newChunk) {
|
||||
removeEntityFromChunk(entity, lastChunk);
|
||||
addEntityToChunk(entity, newChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified {@link Entity} to the instance entities cache.
|
||||
* <p>
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.network.packet.server.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.data.Data;
|
||||
import net.minestom.server.instance.block.BlockManager;
|
||||
@ -21,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
@ -36,7 +36,7 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
public PaletteStorage paletteStorage;
|
||||
public PaletteStorage customBlockPaletteStorage;
|
||||
|
||||
public Set<Integer> blockEntities;
|
||||
public IntSet blockEntities;
|
||||
public Int2ObjectMap<Data> blocksData;
|
||||
|
||||
public int[] sections;
|
||||
|
@ -80,9 +80,9 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
public void setEncryptionKey(@NotNull SecretKey secretKey) {
|
||||
Check.stateCondition(encrypted, "Encryption is already enabled!");
|
||||
this.encrypted = true;
|
||||
channel.pipeline().addBefore(NettyServer.FRAMER_HANDLER_NAME, NettyServer.DECRYPT_HANDLER_NAME,
|
||||
channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.DECRYPT_HANDLER_NAME,
|
||||
new Decrypter(MojangCrypt.getCipher(2, secretKey)));
|
||||
channel.pipeline().addBefore(NettyServer.FRAMER_HANDLER_NAME, NettyServer.ENCRYPT_HANDLER_NAME,
|
||||
channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.ENCRYPT_HANDLER_NAME,
|
||||
new Encrypter(MojangCrypt.getCipher(1, secretKey)));
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public class BinaryReader extends InputStream {
|
||||
*/
|
||||
public String readSizedString(int maxLength) {
|
||||
final int length = readVarInt();
|
||||
Check.stateCondition(length >= maxLength,
|
||||
Check.stateCondition(length > maxLength,
|
||||
"String length (" + length + ") was higher than the max length of " + maxLength);
|
||||
|
||||
final byte[] bytes = readBytes(length);
|
||||
|
@ -7,15 +7,57 @@ import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.callback.OptionalCallback;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class ChunkUtils {
|
||||
|
||||
private ChunkUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes {@link Instance#loadOptionalChunk(int, int, ChunkCallback)} for the array of chunks {@code chunks}
|
||||
* with multiple callbacks, {@code eachCallback} which is executed each time a new chunk is loaded and
|
||||
* {@code endCallback} when all the chunks in the array have been loaded.
|
||||
* <p>
|
||||
* Be aware that {@link Instance#loadOptionalChunk(int, int, ChunkCallback)} can give a null chunk in the callback
|
||||
* if {@link Instance#hasEnabledAutoChunkLoad()} returns false and the chunk is not already loaded.
|
||||
*
|
||||
* @param instance the instance to load the chunks from
|
||||
* @param chunks the chunks to loaded, long value from {@link #getChunkIndex(int, int)}
|
||||
* @param eachCallback the optional callback when a chunk get loaded
|
||||
* @param endCallback the optional callback when all the chunks have been loaded
|
||||
*/
|
||||
public static void optionalLoadAll(@NotNull Instance instance, @NotNull long[] chunks,
|
||||
@Nullable ChunkCallback eachCallback, @Nullable ChunkCallback endCallback) {
|
||||
final int length = chunks.length;
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
for (long visibleChunk : chunks) {
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk);
|
||||
|
||||
final ChunkCallback callback = (chunk) -> {
|
||||
OptionalCallback.execute(eachCallback, chunk);
|
||||
final boolean isLast = counter.get() == length - 1;
|
||||
if (isLast) {
|
||||
// This is the last chunk to be loaded , spawn player
|
||||
OptionalCallback.execute(endCallback, chunk);
|
||||
} else {
|
||||
// Increment the counter of current loaded chunks
|
||||
counter.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
// WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck.
|
||||
instance.loadOptionalChunk(chunkX, chunkZ, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if a chunk is loaded.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user