/*
* This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT).
*
* Copyright (c) 2015 contributors
*
* 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 cubicchunks.server;
import com.google.common.base.Predicate;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ComparisonChain;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.management.PlayerChunkMap;
import net.minecraft.server.management.PlayerChunkMapEntry;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.WorldProvider;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import cubicchunks.CubicChunks;
import cubicchunks.IConfigUpdateListener;
import cubicchunks.util.CubePos;
import cubicchunks.util.XYZMap;
import cubicchunks.util.XZMap;
import cubicchunks.visibility.CubeSelector;
import cubicchunks.visibility.CuboidalCubeSelector;
import cubicchunks.world.ICubicWorldServer;
import cubicchunks.world.column.Column;
import static cubicchunks.util.Coords.blockToCube;
import static cubicchunks.util.Coords.blockToLocal;
import static net.minecraft.util.math.MathHelper.clamp;
/**
* A cubic chunks implementation of Player Manager.
* <p>
* This class manages loading and unloading cubes for players.
*/
public class PlayerCubeMap extends PlayerChunkMap implements IConfigUpdateListener {
private static final Predicate<EntityPlayerMP> NOT_SPECTATOR = player -> player != null && !player.isSpectator();
private static final Predicate<EntityPlayerMP> CAN_GENERATE_CHUNKS = player -> player != null &&
(!player.isSpectator() || player.getServerWorld().getGameRules().getBoolean("spectatorsGenerateChunks"));
/**
* Comparator that specifies order in which cubes will be generated and sent to clients
*/
private static final Comparator<CubeWatcher> CUBE_ORDER = (watcher1, watcher2) ->
ComparisonChain.start().compare(
watcher1.getClosestPlayerDistance(),
watcher2.getClosestPlayerDistance()
).result();
/**
* Comparator that specifies order in which columns will be generated and sent to clients
*/
private static final Comparator<ColumnWatcher> COLUMN_ORDER = (watcher1, watcher2) ->
ComparisonChain.start().compare(
watcher1.getClosestPlayerDistance(),
watcher2.getClosestPlayerDistance()
).result();
/**
* Cube selector is used to find which cube positions need to be loaded/unloaded
* By default use CuboidalCubeSelector.
*/
private final CubeSelector cubeSelector = new CuboidalCubeSelector();
/**
* Mapping if entityId to PlayerCubeMap.PlayerWrapper objects.
*/
private final TIntObjectMap<PlayerWrapper> players = new TIntObjectHashMap<>();
/**
* Mapping of Cube positions to CubeWatchers (Cube equivalent of PlayerManager.PlayerInstance).
* Contains cube positions of all cubes loaded by players.
*/
private final XYZMap<CubeWatcher> cubeWatchers = new XYZMap<>(0.7f, 25*25*25);
/**
* Mapping of Column positions to ColumnWatchers.
* Contains column positions of all columns loaded by players.
* Exists for compatibility with vanilla and to send ColumnLoad/Unload packets to clients.
* Columns cannot be managed by client because they have separate data, like heightmap and biome array.
*/
private final XZMap<ColumnWatcher> columnWatchers = new XZMap<>(0.7f, 25*25);
/**
* All cubeWatchers that have pending block updates to send.
*/
private final Set<CubeWatcher> cubeWatchersToUpdate = new HashSet<>();
/**
* Contains all CubeWatchers that need to be sent to clients,
* but these cubes are not fully loaded/generated yet.
* <p>
* Note that this is not the same as cubesToGenerate list.
* Cube can be loaded while not being fully generated yet (not in the last GeneratorStageRegistry stage).
*/
private final List<CubeWatcher> cubesToSendToClients = new ArrayList<>();
/**
* Contains all CubeWatchers that still need to be loaded/generated.
* CubeWatcher constructor attempts to load cube from disk, but it won't generate it.
* Technically it can generate it, using the world's IGeneratorPipeline,
* but spectator players can't generate chunks if spectatorsGenerateChunks gamerule is set.
*/
private final List<CubeWatcher> cubesToGenerate = new ArrayList<>();
/**
* Contains all ColumnWatchers that need to be sent to clients,
* but these cubes are not fully loaded/generated yet.
* <p>
* Note that this is not the same as columnsToGenerate list.
* Columns can be loaded while not being fully generated yet
*/
private final List<ColumnWatcher> columnsToSendToClients = new ArrayList<>();
/**
* Contains all ColumnWatchers that still need to be loaded/generated.
* ColumnWatcher constructor attempts to load column from disk, but it won't generate it.
*/
private final List<ColumnWatcher> columnsToGenerate = new ArrayList<>();
private int horizontalViewDistance;
private int verticalViewDistance;
private volatile int updatedVerticalViewDistance;
/**
* This is used only to force update of all CubeWatchers every 8000 ticks
*/
private long previousWorldTime = 0;
private boolean toGenerateNeedSort = true;
private boolean toSendToClientNeedSort = true;
private CubeProviderServer cubeCache;
private volatile int maxGeneratedCubesPerTick = CubicChunks.Config.DEFAULT_MAX_GENERATED_CUBES_PER_TICK;
public PlayerCubeMap(ICubicWorldServer worldServer) {
super((WorldServer) worldServer);
this.cubeCache = getWorld().getCubeCache();
this.setPlayerViewDistance(worldServer.getMinecraftServer().getPlayerList().getViewDistance(), CubicChunks.Config.DEFAULT_VERTICAL_CUBE_LOAD_DISTANCE);
CubicChunks.addConfigChangeListener(this);
}
@Override
public void onConfigUpdate(CubicChunks.Config config) {
if (config.getVerticalCubeLoadDistance() != this.verticalViewDistance) {
this.updatedVerticalViewDistance = config.getVerticalCubeLoadDistance();
}
this.maxGeneratedCubesPerTick = config.getMaxGeneratedCubesPerTick();
}
/**
* This method exists only because vanilla needs it. It shouldn't be used anywhere else.
*/
// CHECKED: 1.10.2-12.18.1.2092
@Override
@Deprecated // Warning: Hacks! For vanilla use only! (WorldServer.updateBlocks())
public Iterator<Chunk> getChunkIterator() {
// GIVE TICKET SYSTEM FULL CONTROL
Iterator<Chunk> chunkIt = this.cubeCache.getLoadedChunks().iterator();
return new AbstractIterator<Chunk>() {
@Override protected Chunk computeNext() {
while (chunkIt.hasNext()) {
Column column = (Column) chunkIt.next();
if (column.shouldTick()) { // shouldTick is true when there Cubes with tickets the request to be ticked
return column;
}
}
return this.endOfData();
}
};
}
/**
* Updates all CubeWatchers and ColumnWatchers.
* Also sends packets to clients.
*/
// CHECKED: 1.10.2-12.18.1.2092
@Override
public void tick() {
if (this.updatedVerticalViewDistance != this.verticalViewDistance) {
this.setPlayerViewDistance(getWorld().getMinecraftServer().getPlayerList().getViewDistance(), this.updatedVerticalViewDistance);
}
getWorld().getProfiler().startSection("playerCubeMapTick");
long currentTime = this.getWorldServer().getTotalWorldTime();
getWorld().getProfiler().startSection("tickEntries");
//force update-all every 8000 ticks (400 seconds)
if (currentTime - this.previousWorldTime > 8000L) {
this.previousWorldTime = currentTime;
for (CubeWatcher playerInstance : this.cubeWatchers) {
playerInstance.update();
playerInstance.updateInhabitedTime();
}
}
//process instances to update
for (CubeWatcher playerInstance : this.cubeWatchersToUpdate) {
playerInstance.update();
}
this.cubeWatchersToUpdate.clear();
getWorld().getProfiler().endStartSection("sortToGenerate");
//sort toLoadPending if needed, but at most every 4 ticks
if (this.toGenerateNeedSort && currentTime%4L == 0L) {
this.toGenerateNeedSort = false;
Collections.sort(this.cubesToGenerate, CUBE_ORDER);
Collections.sort(this.columnsToGenerate, COLUMN_ORDER);
}
getWorld().getProfiler().endStartSection("sortToSend");
//sort cubesToSendToClients every other 4 ticks
if (this.toSendToClientNeedSort && currentTime%4L == 2L) {
this.toSendToClientNeedSort = false;
Collections.sort(this.cubesToSendToClients, CUBE_ORDER);
Collections.sort(this.columnsToSendToClients, COLUMN_ORDER);
}
getWorld().getProfiler().endStartSection("generate");
if (!this.columnsToGenerate.isEmpty()) {
getWorld().getProfiler().startSection("columns");
Iterator<ColumnWatcher> iter = this.columnsToGenerate.iterator();
while (iter.hasNext()) {
ColumnWatcher entry = iter.next();
getWorld().getProfiler().startSection("column[" + entry.getPos().chunkXPos + "," + entry.getPos().chunkZPos + "]");
boolean success = entry.getColumn() != null;
if (!success) {
boolean canGenerate = entry.hasPlayerMatching(CAN_GENERATE_CHUNKS);
getWorld().getProfiler().startSection("generate");
success = entry.providePlayerChunk(canGenerate);
getWorld().getProfiler().endSection(); // generate
}
if (success) {
iter.remove();
if (entry.sendToPlayers()) {
this.columnsToSendToClients.remove(entry);
}
}
getWorld().getProfiler().endSection(); // column[x,z]
}
getWorld().getProfiler().endSection(); // columns
}
if (!this.cubesToGenerate.isEmpty()) {
getWorld().getProfiler().startSection("cubes");
long stopTime = System.nanoTime() + 50000000L;
int chunksToGenerate = maxGeneratedCubesPerTick;
Iterator<CubeWatcher> iterator = this.cubesToGenerate.iterator();
while (iterator.hasNext() && chunksToGenerate >= 0 && System.nanoTime() < stopTime) {
CubeWatcher watcher = iterator.next();
CubePos pos = watcher.getCubePos();
getWorld().getProfiler().startSection("chunk=" + pos);
boolean success = watcher.getCube() != null && watcher.getCube().isFullyPopulated() && watcher.getCube().isInitialLightingDone();
if (!success) {
boolean canGenerate = watcher.hasPlayerMatching(CAN_GENERATE_CHUNKS);
getWorld().getProfiler().startSection("generate");
success = watcher.providePlayerCube(canGenerate);
getWorld().getProfiler().endSection();
}
if (success) {
iterator.remove();
if (watcher.sendToPlayers()) {
this.cubesToSendToClients.remove(watcher);
}
--chunksToGenerate;
}
getWorld().getProfiler().endSection();//chunk[x, y, z]
}
getWorld().getProfiler().endSection(); // chunks
}
getWorld().getProfiler().endStartSection("send");
if (!this.columnsToSendToClients.isEmpty()) {
getWorld().getProfiler().startSection("columns");
Iterator<ColumnWatcher> iter = this.columnsToSendToClients.iterator();
while (iter.hasNext()) {
ColumnWatcher next = iter.next();
if (next.sendToPlayers()) {
iter.remove();
}
}
getWorld().getProfiler().endSection(); // columns
}
if (!this.cubesToSendToClients.isEmpty()) {
getWorld().getProfiler().startSection("cubes");
int toSend = 81*8;//sending cubes, so send 8x more at once
Iterator<CubeWatcher> it = this.cubesToSendToClients.iterator();
while (it.hasNext() && toSend >= 0) {
CubeWatcher playerInstance = it.next();
if (playerInstance.sendToPlayers()) {
it.remove();
--toSend;
}
}
getWorld().getProfiler().endSection(); // cubes
}
getWorld().getProfiler().endStartSection("unload");
//if there are no players - unload everything
if (this.players.isEmpty()) {
WorldProvider worldprovider = this.getWorldServer().provider;
if (!worldprovider.canRespawnHere()) {
this.getWorldServer().getChunkProvider().unloadAllChunks();
}
}
getWorld().getProfiler().endSection();//unload
getWorld().getProfiler().endSection();//playerCubeMapTick
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public boolean contains(int cubeX, int cubeZ) {
return this.columnWatchers.get(cubeX, cubeZ) != null;
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public PlayerChunkMapEntry getEntry(int cubeX, int cubeZ) {
return this.columnWatchers.get(cubeX, cubeZ);
}
/**
* Returns existing CubeWatcher or creates new one if it doesn't exist.
* Attempts to load the cube and send it to client.
* If it can't load it or send it to client - adds it to cubesToGenerate/cubesToSendToClients
*/
private CubeWatcher getOrCreateCubeWatcher(CubePos cubePos) {
CubeWatcher cubeWatcher = this.cubeWatchers.get(cubePos.getX(), cubePos.getY(), cubePos.getZ());
if (cubeWatcher == null) {
// make a new watcher
cubeWatcher = new CubeWatcher(this, cubePos);
this.cubeWatchers.put(cubeWatcher);
if (cubeWatcher.getCube() == null ||
!cubeWatcher.getCube().isFullyPopulated() ||
!cubeWatcher.getCube().isInitialLightingDone()) {
this.cubesToGenerate.add(cubeWatcher);
}
if (!cubeWatcher.isSentToPlayers()) {
this.cubesToSendToClients.add(cubeWatcher);
}
}
return cubeWatcher;
}
/**
* Returns existing ColumnWatcher or creates new one if it doesn't exist.
* Always creates the Column.
*/
private ColumnWatcher getOrCreateColumnWatcher(ChunkPos chunkPos) {
ColumnWatcher columnWatcher = this.columnWatchers.get(chunkPos.chunkXPos, chunkPos.chunkZPos);
if (columnWatcher == null) {
columnWatcher = new ColumnWatcher(this, chunkPos);
this.columnWatchers.put(columnWatcher);
if (columnWatcher.getColumn() == null) {
this.columnsToGenerate.add(columnWatcher);
}
if (!columnWatcher.isSentToPlayers()) {
this.columnsToSendToClients.add(columnWatcher);
}
}
return columnWatcher;
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public void markBlockForUpdate(BlockPos pos) {
CubeWatcher cubeWatcher = this.getCubeWatcher(CubePos.fromBlockCoords(pos));
if (cubeWatcher != null) {
int localX = blockToLocal(pos.getX());
int localY = blockToLocal(pos.getY());
int localZ = blockToLocal(pos.getZ());
cubeWatcher.blockChanged(localX, localY, localZ);
}
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public void addPlayer(EntityPlayerMP player) {
PlayerWrapper playerWrapper = new PlayerWrapper(player);
playerWrapper.updateManagedPos();
CubePos playerCubePos = CubePos.fromEntity(player);
this.cubeSelector.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (currentPos) -> {
//create cubeWatcher and chunkWatcher
//order is important
ColumnWatcher chunkWatcher = getOrCreateColumnWatcher(currentPos.chunkPos());
//and add the player to them
if (!chunkWatcher.containsPlayer(player)) {
chunkWatcher.addPlayer(player);
}
CubeWatcher cubeWatcher = getOrCreateCubeWatcher(currentPos);
assert !cubeWatcher.containsPlayer(player);
cubeWatcher.addPlayer(player);
});
this.players.put(player.getEntityId(), playerWrapper);
this.setNeedSort();
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public void removePlayer(EntityPlayerMP player) {
PlayerWrapper playerWrapper = this.players.get(player.getEntityId());
CubePos playerCubePos = CubePos.fromEntity(playerWrapper.playerEntity);
this.cubeSelector.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (cubePos) -> {
// get the watcher
CubeWatcher watcher = getCubeWatcher(cubePos);
if (watcher == null) {
return;//continue
}
// remove from the watcher, it also removes the watcher if it becomes empty
watcher.removePlayer(player);
// remove column watchers if needed
ColumnWatcher columnWatcher = getColumnWatcher(cubePos.chunkPos());
if (columnWatcher == null) {
return;
}
if (columnWatcher.containsPlayer(player)) {
columnWatcher.removePlayer(player);
}
});
this.players.remove(player.getEntityId());
this.setNeedSort();
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public void updateMovingPlayer(EntityPlayerMP player) {
// the player moved
// if the player moved into a new chunk, update which chunks the player needs to know about
// then update the list of chunks that need to be sent to the client
// get the player info
PlayerWrapper playerWrapper = this.players.get(player.getEntityId());
// did the player move into new cube?
if (!playerWrapper.cubePosChanged()) {
return;
}
this.updatePlayer(playerWrapper, playerWrapper.getManagedCubePos(), CubePos.fromEntity(player));
playerWrapper.updateManagedPos();
this.setNeedSort();
}
private void updatePlayer(PlayerWrapper entry, CubePos oldPos, CubePos newPos) {
getWorld().getProfiler().startSection("updateMovedPlayer");
Set<CubePos> cubesToRemove = new HashSet<>();
Set<CubePos> cubesToLoad = new HashSet<>();
Set<ChunkPos> columnsToRemove = new HashSet<>();
Set<ChunkPos> columnsToLoad = new HashSet<>();
getWorld().getProfiler().startSection("findChanges");
// calculate new visibility
this.cubeSelector.findChanged(oldPos, newPos, horizontalViewDistance, verticalViewDistance, cubesToRemove, cubesToLoad, columnsToRemove, columnsToLoad);
getWorld().getProfiler().endStartSection("createColumns");
//order is important, columns first
columnsToLoad.forEach(pos -> {
ColumnWatcher columnWatcher = this.getOrCreateColumnWatcher(pos);
assert columnWatcher.getPos().equals(pos);
columnWatcher.addPlayer(entry.playerEntity);
});
getWorld().getProfiler().endStartSection("createCubes");
cubesToLoad.forEach(pos -> {
CubeWatcher cubeWatcher = this.getOrCreateCubeWatcher(pos);
assert cubeWatcher.getCubePos().equals(pos);
cubeWatcher.addPlayer(entry.playerEntity);
});
getWorld().getProfiler().endStartSection("removeCubes");
cubesToRemove.forEach(pos -> {
CubeWatcher cubeWatcher = this.getCubeWatcher(pos);
if (cubeWatcher != null) {
assert cubeWatcher.getCubePos().equals(pos);
cubeWatcher.removePlayer(entry.playerEntity);
}
});
getWorld().getProfiler().endStartSection("removeColumns");
columnsToRemove.forEach(pos -> {
ColumnWatcher columnWatcher = this.getColumnWatcher(pos);
if (columnWatcher != null) {
assert columnWatcher.getPos().equals(pos);
columnWatcher.removePlayer(entry.playerEntity);
}
});
getWorld().getProfiler().endSection();//removeColumns
getWorld().getProfiler().endSection();//updateMovedPlayer
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
public boolean isPlayerWatchingChunk(@Nonnull EntityPlayerMP player, int cubeX, int cubeZ) {
ColumnWatcher columnWatcher = this.getColumnWatcher(new ChunkPos(cubeX, cubeZ));
return columnWatcher != null &&
columnWatcher.containsPlayer(player) &&
columnWatcher.isSentToPlayers();
}
// CHECKED: 1.10.2-12.18.1.2092
@Override
@Deprecated
public final void setPlayerViewRadius(int newHorizontalViewDistance) {
this.setPlayerViewDistance(newHorizontalViewDistance, verticalViewDistance);
}
public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVerticalViewDistance) {
//this method is called by vanilla before these fields are initialized.
//and it doesn't really need to be called because in this case
//it reduces to setting the viewRadius field
if (this.players == null) {
return;
}
newHorizontalViewDistance = clamp(newHorizontalViewDistance, 3, 32);
newVerticalViewDistance = clamp(newVerticalViewDistance, 3, 32);
if (newHorizontalViewDistance == this.horizontalViewDistance && newVerticalViewDistance == this.verticalViewDistance) {
return;
}
int oldHorizontalViewDistance = this.horizontalViewDistance;
int oldVerticalViewDistance = this.verticalViewDistance;
// Somehow the view distances went in opposite directions
if ((newHorizontalViewDistance < oldHorizontalViewDistance && newVerticalViewDistance > oldVerticalViewDistance) ||
(newHorizontalViewDistance > oldHorizontalViewDistance && newVerticalViewDistance < oldVerticalViewDistance)) {
// Adjust the values separately to avoid imploding
setPlayerViewDistance(newHorizontalViewDistance, oldVerticalViewDistance);
setPlayerViewDistance(newHorizontalViewDistance, newVerticalViewDistance);
return;
}
for (PlayerWrapper playerWrapper : this.players.valueCollection()) {
EntityPlayerMP player = playerWrapper.playerEntity;
CubePos playerPos = playerWrapper.getManagedCubePos();
if (newHorizontalViewDistance > oldHorizontalViewDistance || newVerticalViewDistance > oldVerticalViewDistance) {
//if newRadius is bigger, we only need to load new cubes
this.cubeSelector.forAllVisibleFrom(playerPos, newHorizontalViewDistance, newVerticalViewDistance, pos -> {
//order is important
ColumnWatcher columnWatcher = this.getOrCreateColumnWatcher(pos.chunkPos());
if (!columnWatcher.containsPlayer(player)) {
columnWatcher.addPlayer(player);
}
CubeWatcher cubeWatcher = this.getOrCreateCubeWatcher(pos);
if (!cubeWatcher.containsPlayer(player)) {
cubeWatcher.addPlayer(player);
}
});
// either both got smaller or only one of them changed
} else {
//if it got smaller...
Set<CubePos> cubesToUnload = new HashSet<>();
Set<ChunkPos> columnsToUnload = new HashSet<>();
this.cubeSelector.findAllUnloadedOnViewDistanceDecrease(playerPos,
oldHorizontalViewDistance, newHorizontalViewDistance,
oldVerticalViewDistance, newVerticalViewDistance, cubesToUnload, columnsToUnload);
cubesToUnload.forEach(pos -> {
CubeWatcher cubeWatcher = this.getCubeWatcher(pos);
if (cubeWatcher != null && cubeWatcher.containsPlayer(player)) {
cubeWatcher.removePlayer(player);
} else {
CubicChunks.LOGGER.warn("cubeWatcher null or doesn't contain player on render distance change");
}
});
columnsToUnload.forEach(pos -> {
ColumnWatcher columnWatcher = this.getColumnWatcher(pos);
if (columnWatcher != null && columnWatcher.containsPlayer(player)) {
columnWatcher.removePlayer(player);
} else {
CubicChunks.LOGGER.warn("cubeWatcher null or doesn't contain player on render distance change");
}
});
}
}
this.horizontalViewDistance = newHorizontalViewDistance;
this.verticalViewDistance = newVerticalViewDistance;
this.setNeedSort();
}
private void setNeedSort() {
this.toGenerateNeedSort = true;
this.toSendToClientNeedSort = true;
}
@Override
public void addEntry(@Nonnull PlayerChunkMapEntry entry) {
throw new UnsupportedOperationException();
}
@Override
public void removeEntry(PlayerChunkMapEntry entry) {
throw new UnsupportedOperationException();
}
void addToUpdateEntry(CubeWatcher cubeWatcher) {
this.cubeWatchersToUpdate.add(cubeWatcher);
}
// CHECKED: 1.10.2-12.18.1.2092
void removeEntry(CubeWatcher cubeWatcher) {
CubePos cubePos = cubeWatcher.getCubePos();
cubeWatcher.updateInhabitedTime();
this.cubeWatchers.remove(cubePos.getX(), cubePos.getY(), cubePos.getZ());
this.cubeWatchersToUpdate.remove(cubeWatcher);
this.cubesToGenerate.remove(cubeWatcher);
this.cubesToSendToClients.remove(cubeWatcher);
if (cubeWatcher.getCube() != null) {
cubeWatcher.getCube().getTickets().remove(cubeWatcher); // remove the ticket, so this Cube can unload
}
//don't unload, ChunkGc unloads chunks
}
// CHECKED: 1.10.2-12.18.1.2092
public void removeEntry(ColumnWatcher entry) {
ChunkPos pos = entry.getPos();
entry.updateChunkInhabitedTime();
this.columnWatchers.remove(pos.chunkXPos, pos.chunkZPos);
this.columnsToGenerate.remove(pos);
this.columnsToSendToClients.remove(pos);
}
public CubeWatcher getCubeWatcher(CubePos pos) {
return this.cubeWatchers.get(pos.getX(), pos.getY(), pos.getZ());
}
public ColumnWatcher getColumnWatcher(ChunkPos pos) {
return this.columnWatchers.get(pos.chunkXPos, pos.chunkZPos);
}
public ICubicWorldServer getWorld() {
return (ICubicWorldServer) this.getWorldServer();
}
public boolean contains(CubePos coords) {
return this.cubeWatchers.get(coords.getX(), coords.getY(), coords.getZ()) != null;
}
private static final class PlayerWrapper {
final EntityPlayerMP playerEntity;
private double managedPosY;
PlayerWrapper(EntityPlayerMP player) {
this.playerEntity = player;
}
void updateManagedPos() {
this.playerEntity.managedPosX = playerEntity.posX;
this.managedPosY = playerEntity.posY;
this.playerEntity.managedPosZ = playerEntity.posZ;
}
int getManagedCubePosX() {
return blockToCube(this.playerEntity.managedPosX);
}
int getManagedCubePosY() {
return blockToCube(this.managedPosY);
}
int getManagedCubePosZ() {
return blockToCube(this.playerEntity.managedPosZ);
}
public CubePos getManagedCubePos() {
return new CubePos(getManagedCubePosX(), getManagedCubePosY(), getManagedCubePosZ());
}
public boolean cubePosChanged() {
// did the player move far enough to matter?
double blockDX = blockToCube(playerEntity.posX) - this.getManagedCubePosX();
double blockDY = blockToCube(playerEntity.posY) - this.getManagedCubePosY();
double blockDZ = blockToCube(playerEntity.posZ) - this.getManagedCubePosZ();
double distanceSquared = blockDX*blockDX + blockDY*blockDY + blockDZ*blockDZ;
return distanceSquared > 0.9;//0.9 instead of 1 because floating-point numbers may be weird
}
}
}