package net.minecraft.server.management;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S21PacketChunkData;
import net.minecraft.network.play.server.S22PacketMultiBlockChange;
import net.minecraft.network.play.server.S23PacketBlockChange;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.LongHashMap;
import net.minecraft.util.MathHelper;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.WorldProvider;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class PlayerManager
{
private static final Logger field_152627_a = LogManager.getLogger();
private final WorldServer theWorldServer;
/** players in the current instance */
private final List players = new ArrayList();
/** the hash of all playerInstances created */
private final LongHashMap playerInstances = new LongHashMap();
/** the playerInstances(chunks) that need to be updated */
private final List playerInstancesToUpdate = new ArrayList();
/** This field is using when chunk should be processed (every 8000 ticks) */
private final List playerInstanceList = new ArrayList();
/** Number of chunks the server sends to the client. Valid 3<=x<=15. In server.properties. */
private int playerViewRadius;
/** time what is using to check if InhabitedTime should be calculated */
private long previousTotalWorldTime;
/** x, z direction vectors: east, south, west, north */
private final int[][] xzDirectionsConst = new int[][] {{1, 0}, {0, 1}, { -1, 0}, {0, -1}};
private static final String __OBFID = "CL_00001434";
public PlayerManager(WorldServer p_i1176_1_)
{
this.theWorldServer = p_i1176_1_;
this.func_152622_a(p_i1176_1_.func_73046_m().getConfigurationManager().getViewDistance());
}
/**
* Returns the MinecraftServer associated with the PlayerManager.
*/
public WorldServer getMinecraftServer()
{
return this.theWorldServer;
}
/**
* updates all the player instances that need to be updated
*/
public void updatePlayerInstances()
{
long i = this.theWorldServer.getTotalWorldTime();
int j;
PlayerManager.PlayerInstance playerinstance;
if (i - this.previousTotalWorldTime > 8000L)
{
this.previousTotalWorldTime = i;
for (j = 0; j < this.playerInstanceList.size(); ++j)
{
playerinstance = (PlayerManager.PlayerInstance)this.playerInstanceList.get(j);
playerinstance.onUpdate();
playerinstance.processChunk();
}
}
else
{
for (j = 0; j < this.playerInstancesToUpdate.size(); ++j)
{
playerinstance = (PlayerManager.PlayerInstance)this.playerInstancesToUpdate.get(j);
playerinstance.onUpdate();
}
}
this.playerInstancesToUpdate.clear();
if (this.players.isEmpty())
{
WorldProvider worldprovider = this.theWorldServer.provider;
if (!worldprovider.canRespawnHere())
{
this.theWorldServer.theChunkProviderServer.unloadAllChunks();
}
}
}
public boolean func_152621_a(int p_152621_1_, int p_152621_2_)
{
long k = (long)p_152621_1_ + 2147483647L | (long)p_152621_2_ + 2147483647L << 32;
return this.playerInstances.getValueByKey(k) != null;
}
/**
* passi n the chunk x and y and a flag as to whether or not the instance should be made if it doesnt exist
*/
private PlayerManager.PlayerInstance getPlayerInstance(int p_72690_1_, int p_72690_2_, boolean p_72690_3_)
{
long k = (long)p_72690_1_ + 2147483647L | (long)p_72690_2_ + 2147483647L << 32;
PlayerManager.PlayerInstance playerinstance = (PlayerManager.PlayerInstance)this.playerInstances.getValueByKey(k);
if (playerinstance == null && p_72690_3_)
{
playerinstance = new PlayerManager.PlayerInstance(p_72690_1_, p_72690_2_);
this.playerInstances.add(k, playerinstance);
this.playerInstanceList.add(playerinstance);
}
return playerinstance;
}
public void markBlockForUpdate(int p_151250_1_, int p_151250_2_, int p_151250_3_)
{
int l = p_151250_1_ >> 4;
int i1 = p_151250_3_ >> 4;
PlayerManager.PlayerInstance playerinstance = this.getPlayerInstance(l, i1, false);
if (playerinstance != null)
{
playerinstance.flagChunkForUpdate(p_151250_1_ & 15, p_151250_2_, p_151250_3_ & 15);
}
}
/**
* Adds an EntityPlayerMP to the PlayerManager and to all player instances within player visibility
*/
public void addPlayer(EntityPlayerMP p_72683_1_)
{
int i = (int)p_72683_1_.posX >> 4;
int j = (int)p_72683_1_.posZ >> 4;
p_72683_1_.managedPosX = p_72683_1_.posX;
p_72683_1_.managedPosZ = p_72683_1_.posZ;
// Load nearby chunks first
List<ChunkCoordIntPair> chunkList = new ArrayList<ChunkCoordIntPair>();
for (int k = i - this.playerViewRadius; k <= i + this.playerViewRadius; ++k)
{
for (int l = j - this.playerViewRadius; l <= j + this.playerViewRadius; ++l)
{
chunkList.add(new ChunkCoordIntPair(k, l));
}
}
java.util.Collections.sort(chunkList, new net.minecraftforge.common.util.ChunkCoordComparator(p_72683_1_));
for (ChunkCoordIntPair pair : chunkList)
{
this.getPlayerInstance(pair.chunkXPos, pair.chunkZPos, true).addPlayer(p_72683_1_);
}
this.players.add(p_72683_1_);
this.filterChunkLoadQueue(p_72683_1_);
}
/**
* Removes all chunks from the given player's chunk load queue that are not in viewing range of the player.
*/
public void filterChunkLoadQueue(EntityPlayerMP p_72691_1_)
{
ArrayList arraylist = new ArrayList(p_72691_1_.loadedChunks);
int i = 0;
int j = this.playerViewRadius;
int k = (int)p_72691_1_.posX >> 4;
int l = (int)p_72691_1_.posZ >> 4;
int i1 = 0;
int j1 = 0;
ChunkCoordIntPair chunkcoordintpair = this.getPlayerInstance(k, l, true).currentChunk;
p_72691_1_.loadedChunks.clear();
if (arraylist.contains(chunkcoordintpair))
{
p_72691_1_.loadedChunks.add(chunkcoordintpair);
}
int k1;
for (k1 = 1; k1 <= j * 2; ++k1)
{
for (int l1 = 0; l1 < 2; ++l1)
{
int[] aint = this.xzDirectionsConst[i++ % 4];
for (int i2 = 0; i2 < k1; ++i2)
{
i1 += aint[0];
j1 += aint[1];
chunkcoordintpair = this.getPlayerInstance(k + i1, l + j1, true).currentChunk;
if (arraylist.contains(chunkcoordintpair))
{
p_72691_1_.loadedChunks.add(chunkcoordintpair);
}
}
}
}
i %= 4;
for (k1 = 0; k1 < j * 2; ++k1)
{
i1 += this.xzDirectionsConst[i][0];
j1 += this.xzDirectionsConst[i][1];
chunkcoordintpair = this.getPlayerInstance(k + i1, l + j1, true).currentChunk;
if (arraylist.contains(chunkcoordintpair))
{
p_72691_1_.loadedChunks.add(chunkcoordintpair);
}
}
}
/**
* Removes an EntityPlayerMP from the PlayerManager.
*/
public void removePlayer(EntityPlayerMP p_72695_1_)
{
int i = (int)p_72695_1_.managedPosX >> 4;
int j = (int)p_72695_1_.managedPosZ >> 4;
for (int k = i - this.playerViewRadius; k <= i + this.playerViewRadius; ++k)
{
for (int l = j - this.playerViewRadius; l <= j + this.playerViewRadius; ++l)
{
PlayerManager.PlayerInstance playerinstance = this.getPlayerInstance(k, l, false);
if (playerinstance != null)
{
playerinstance.removePlayer(p_72695_1_);
}
}
}
this.players.remove(p_72695_1_);
}
/**
* Determine if two rectangles centered at the given points overlap for the provided radius. Arguments: x1, z1, x2,
* z2, radius.
*/
private boolean overlaps(int p_72684_1_, int p_72684_2_, int p_72684_3_, int p_72684_4_, int p_72684_5_)
{
int j1 = p_72684_1_ - p_72684_3_;
int k1 = p_72684_2_ - p_72684_4_;
return j1 >= -p_72684_5_ && j1 <= p_72684_5_ ? k1 >= -p_72684_5_ && k1 <= p_72684_5_ : false;
}
/**
* update chunks around a player being moved by server logic (e.g. cart, boat)
*/
public void updateMountedMovingPlayer(EntityPlayerMP p_72685_1_)
{
int i = (int)p_72685_1_.posX >> 4;
int j = (int)p_72685_1_.posZ >> 4;
double d0 = p_72685_1_.managedPosX - p_72685_1_.posX;
double d1 = p_72685_1_.managedPosZ - p_72685_1_.posZ;
double d2 = d0 * d0 + d1 * d1;
if (d2 >= 64.0D)
{
int k = (int)p_72685_1_.managedPosX >> 4;
int l = (int)p_72685_1_.managedPosZ >> 4;
int i1 = this.playerViewRadius;
int j1 = i - k;
int k1 = j - l;
List<ChunkCoordIntPair> chunksToLoad = new ArrayList<ChunkCoordIntPair>();
if (j1 != 0 || k1 != 0)
{
for (int l1 = i - i1; l1 <= i + i1; ++l1)
{
for (int i2 = j - i1; i2 <= j + i1; ++i2)
{
if (!this.overlaps(l1, i2, k, l, i1))
{
chunksToLoad.add(new ChunkCoordIntPair(l1, i2));
}
if (!this.overlaps(l1 - j1, i2 - k1, i, j, i1))
{
PlayerManager.PlayerInstance playerinstance = this.getPlayerInstance(l1 - j1, i2 - k1, false);
if (playerinstance != null)
{
playerinstance.removePlayer(p_72685_1_);
}
}
}
}
this.filterChunkLoadQueue(p_72685_1_);
p_72685_1_.managedPosX = p_72685_1_.posX;
p_72685_1_.managedPosZ = p_72685_1_.posZ;
// send nearest chunks first
java.util.Collections.sort(chunksToLoad, new net.minecraftforge.common.util.ChunkCoordComparator(p_72685_1_));
for (ChunkCoordIntPair pair : chunksToLoad)
{
this.getPlayerInstance(pair.chunkXPos, pair.chunkZPos, true).addPlayer(p_72685_1_);
}
if (i1 > 1 || i1 < -1 || j1 > 1 || j1 < -1)
{
java.util.Collections.sort(p_72685_1_.loadedChunks, new net.minecraftforge.common.util.ChunkCoordComparator(p_72685_1_));
}
}
}
}
public boolean isPlayerWatchingChunk(EntityPlayerMP p_72694_1_, int p_72694_2_, int p_72694_3_)
{
PlayerManager.PlayerInstance playerinstance = this.getPlayerInstance(p_72694_2_, p_72694_3_, false);
return playerinstance != null && playerinstance.playersWatchingChunk.contains(p_72694_1_) && !p_72694_1_.loadedChunks.contains(playerinstance.currentChunk);
}
public void func_152622_a(int p_152622_1_)
{
p_152622_1_ = MathHelper.clamp_int(p_152622_1_, 3, 20);
if (p_152622_1_ != this.playerViewRadius)
{
int j = p_152622_1_ - this.playerViewRadius;
Iterator iterator = this.players.iterator();
while (iterator.hasNext())
{
EntityPlayerMP entityplayermp = (EntityPlayerMP)iterator.next();
int k = (int)entityplayermp.posX >> 4;
int l = (int)entityplayermp.posZ >> 4;
int i1;
int j1;
if (j > 0)
{
for (i1 = k - p_152622_1_; i1 <= k + p_152622_1_; ++i1)
{
for (j1 = l - p_152622_1_; j1 <= l + p_152622_1_; ++j1)
{
PlayerManager.PlayerInstance playerinstance = this.getPlayerInstance(i1, j1, true);
if (!playerinstance.playersWatchingChunk.contains(entityplayermp))
{
playerinstance.addPlayer(entityplayermp);
}
}
}
}
else
{
for (i1 = k - this.playerViewRadius; i1 <= k + this.playerViewRadius; ++i1)
{
for (j1 = l - this.playerViewRadius; j1 <= l + this.playerViewRadius; ++j1)
{
if (!this.overlaps(i1, j1, k, l, p_152622_1_))
{
this.getPlayerInstance(i1, j1, true).removePlayer(entityplayermp);
}
}
}
}
}
this.playerViewRadius = p_152622_1_;
}
}
/**
* Get the furthest viewable block given player's view distance
*/
public static int getFurthestViewableBlock(int p_72686_0_)
{
return p_72686_0_ * 16 - 16;
}
class PlayerInstance
{
/** the list of all players in this instance (chunk) */
private final List playersWatchingChunk = new ArrayList();
/** the chunk the player currently resides in */
private final ChunkCoordIntPair currentChunk;
private short[] locationOfBlockChange = new short[64];
/** the number of blocks that need to be updated next tick */
private int numBlocksToUpdate;
/** Integer field where each bit means to make update 16x16x16 division of chunk (from bottom). */
private int flagsYAreasToUpdate;
/** time what is using when chunk InhabitedTime is being calculated */
private long previousWorldTime;
private final java.util.HashMap<EntityPlayerMP, Runnable> players = new java.util.HashMap<EntityPlayerMP, Runnable>();
private boolean loaded = false;
private Runnable loadedRunnable = new Runnable()
{
public void run()
{
PlayerInstance.this.loaded = true;
}
};
private static final String __OBFID = "CL_00001435";
public PlayerInstance(int p_i1518_2_, int p_i1518_3_)
{
this.currentChunk = new ChunkCoordIntPair(p_i1518_2_, p_i1518_3_);
PlayerManager.this.theWorldServer.theChunkProviderServer.loadChunk(p_i1518_2_, p_i1518_3_, this.loadedRunnable);
}
public void addPlayer(final EntityPlayerMP p_73255_1_)
{
if (this.playersWatchingChunk.contains(p_73255_1_))
{
PlayerManager.field_152627_a.debug("Failed to add player. {} already is in chunk {}, {}", new Object[] {p_73255_1_, Integer.valueOf(this.currentChunk.chunkXPos), Integer.valueOf(this.currentChunk.chunkZPos)});
}
else
{
if (this.playersWatchingChunk.isEmpty())
{
this.previousWorldTime = PlayerManager.this.theWorldServer.getTotalWorldTime();
}
this.playersWatchingChunk.add(p_73255_1_);
Runnable playerRunnable;
if (this.loaded)
{
playerRunnable = null;
p_73255_1_.loadedChunks.add(this.currentChunk);
}
else
{
playerRunnable = new Runnable()
{
public void run()
{
p_73255_1_.loadedChunks.add(PlayerInstance.this.currentChunk);
}
};
PlayerManager.this.getMinecraftServer().theChunkProviderServer.loadChunk(this.currentChunk.chunkXPos, this.currentChunk.chunkZPos, playerRunnable);
}
this.players.put(p_73255_1_, playerRunnable);
}
}
public void removePlayer(EntityPlayerMP p_73252_1_)
{
if (this.playersWatchingChunk.contains(p_73252_1_))
{
// If we haven't loaded yet don't load the chunk just so we can clean it up
if (!this.loaded)
{
net.minecraftforge.common.chunkio.ChunkIOExecutor.dropQueuedChunkLoad(PlayerManager.this.getMinecraftServer(), this.currentChunk.chunkXPos, this.currentChunk.chunkZPos, this.players.get(p_73252_1_));
this.playersWatchingChunk.remove(p_73252_1_);
this.players.remove(p_73252_1_);
if (this.playersWatchingChunk.isEmpty())
{
net.minecraftforge.common.chunkio.ChunkIOExecutor.dropQueuedChunkLoad(PlayerManager.this.getMinecraftServer(), this.currentChunk.chunkXPos, this.currentChunk.chunkZPos, this.loadedRunnable);
long i = (long) this.currentChunk.chunkXPos + 2147483647L | (long) this.currentChunk.chunkZPos + 2147483647L << 32;
PlayerManager.this.playerInstances.remove(i);
PlayerManager.this.playerInstanceList.remove(this);
}
return;
}
Chunk chunk = PlayerManager.this.theWorldServer.getChunkFromChunkCoords(this.currentChunk.chunkXPos, this.currentChunk.chunkZPos);
if (chunk.func_150802_k())
{
p_73252_1_.playerNetServerHandler.sendPacket(new S21PacketChunkData(chunk, true, 0));
}
this.players.remove(p_73252_1_);
this.playersWatchingChunk.remove(p_73252_1_);
p_73252_1_.loadedChunks.remove(this.currentChunk);
net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.ChunkWatchEvent.UnWatch(currentChunk, p_73252_1_));
if (this.playersWatchingChunk.isEmpty())
{
long i = (long)this.currentChunk.chunkXPos + 2147483647L | (long)this.currentChunk.chunkZPos + 2147483647L << 32;
this.increaseInhabitedTime(chunk);
PlayerManager.this.playerInstances.remove(i);
PlayerManager.this.playerInstanceList.remove(this);
if (this.numBlocksToUpdate > 0)
{
PlayerManager.this.playerInstancesToUpdate.remove(this);
}
PlayerManager.this.getMinecraftServer().theChunkProviderServer.dropChunk(this.currentChunk.chunkXPos, this.currentChunk.chunkZPos);
}
}
}
/**
* This method currently only increases chunk inhabited time. Extension is possible in next versions
*/
public void processChunk()
{
this.increaseInhabitedTime(PlayerManager.this.theWorldServer.getChunkFromChunkCoords(this.currentChunk.chunkXPos, this.currentChunk.chunkZPos));
}
/**
* Increases chunk inhabited time every 8000 ticks
*/
private void increaseInhabitedTime(Chunk p_111196_1_)
{
p_111196_1_.inhabitedTime += PlayerManager.this.theWorldServer.getTotalWorldTime() - this.previousWorldTime;
this.previousWorldTime = PlayerManager.this.theWorldServer.getTotalWorldTime();
}
public void flagChunkForUpdate(int p_151253_1_, int p_151253_2_, int p_151253_3_)
{
if (this.numBlocksToUpdate == 0)
{
PlayerManager.this.playerInstancesToUpdate.add(this);
}
this.flagsYAreasToUpdate |= 1 << (p_151253_2_ >> 4);
//if (this.numberOfTilesToUpdate < 64) //Forge; Cache everything, so always run
{
short short1 = (short)(p_151253_1_ << 12 | p_151253_3_ << 8 | p_151253_2_);
for (int l = 0; l < this.numBlocksToUpdate; ++l)
{
if (this.locationOfBlockChange[l] == short1)
{
return;
}
}
if (numBlocksToUpdate == locationOfBlockChange.length)
{
locationOfBlockChange = java.util.Arrays.copyOf(locationOfBlockChange, locationOfBlockChange.length << 1);
}
this.locationOfBlockChange[this.numBlocksToUpdate++] = short1;
}
}
public void sendToAllPlayersWatchingChunk(Packet p_151251_1_)
{
for (int i = 0; i < this.playersWatchingChunk.size(); ++i)
{
EntityPlayerMP entityplayermp = (EntityPlayerMP)this.playersWatchingChunk.get(i);
if (!entityplayermp.loadedChunks.contains(this.currentChunk))
{
entityplayermp.playerNetServerHandler.sendPacket(p_151251_1_);
}
}
}
@SuppressWarnings("unused")
public void onUpdate()
{
if (this.numBlocksToUpdate != 0)
{
int i;
int j;
int k;
if (this.numBlocksToUpdate == 1)
{
i = this.currentChunk.chunkXPos * 16 + (this.locationOfBlockChange[0] >> 12 & 15);
j = this.locationOfBlockChange[0] & 255;
k = this.currentChunk.chunkZPos * 16 + (this.locationOfBlockChange[0] >> 8 & 15);
this.sendToAllPlayersWatchingChunk(new S23PacketBlockChange(i, j, k, PlayerManager.this.theWorldServer));
if (PlayerManager.this.theWorldServer.getBlock(i, j, k).hasTileEntity(PlayerManager.this.theWorldServer.getBlockMetadata(i, j, k)))
{
this.sendTileToAllPlayersWatchingChunk(PlayerManager.this.theWorldServer.getTileEntity(i, j, k));
}
}
else
{
int l;
if (this.numBlocksToUpdate >= net.minecraftforge.common.ForgeModContainer.clumpingThreshold)
{
i = this.currentChunk.chunkXPos * 16;
j = this.currentChunk.chunkZPos * 16;
this.sendToAllPlayersWatchingChunk(new S21PacketChunkData(PlayerManager.this.theWorldServer.getChunkFromChunkCoords(this.currentChunk.chunkXPos, this.currentChunk.chunkZPos), false, this.flagsYAreasToUpdate));
// Forge: Grabs ALL tile entities is costly on a modded server, only send needed ones
for (k = 0; false && k < 16; ++k)
{
if ((this.flagsYAreasToUpdate & 1 << k) != 0)
{
l = k << 4;
List list = PlayerManager.this.theWorldServer.func_147486_a(i, l, j, i + 16, l + 16, j + 16);
for (int i1 = 0; i1 < list.size(); ++i1)
{
this.sendTileToAllPlayersWatchingChunk((TileEntity)list.get(i1));
}
}
}
}
else
{
this.sendToAllPlayersWatchingChunk(new S22PacketMultiBlockChange(this.numBlocksToUpdate, this.locationOfBlockChange, PlayerManager.this.theWorldServer.getChunkFromChunkCoords(this.currentChunk.chunkXPos, this.currentChunk.chunkZPos)));
}
{ //Forge: Send only the tile entities that are updated, Adding this brace lets us keep the indent and the patch small
WorldServer world = PlayerManager.this.theWorldServer;
for (i = 0; i < this.numBlocksToUpdate; ++i)
{
j = this.currentChunk.chunkXPos * 16 + (this.locationOfBlockChange[i] >> 12 & 15);
k = this.locationOfBlockChange[i] & 255;
l = this.currentChunk.chunkZPos * 16 + (this.locationOfBlockChange[i] >> 8 & 15);
if (world.getBlock(j, k, l).hasTileEntity(world.getBlockMetadata(j, k, l)))
{
this.sendTileToAllPlayersWatchingChunk(PlayerManager.this.theWorldServer.getTileEntity(j, k, l));
}
}
}
}
this.numBlocksToUpdate = 0;
this.flagsYAreasToUpdate = 0;
}
}
private void sendTileToAllPlayersWatchingChunk(TileEntity p_151252_1_)
{
if (p_151252_1_ != null)
{
Packet packet = p_151252_1_.getDescriptionPacket();
if (packet != null)
{
this.sendToAllPlayersWatchingChunk(packet);
}
}
}
}
}