Implements interface for changing the view distance.

Uses default view distance if player's view distance is not set

Throws an illegal argument exception if view distance is set too high
or too low.

Pushes notifications of server and world view distance changes to the player.
Move view distance functions from PlayerManger to WorldServer.
Set player minimum view distance to 1 for now.
Reset player's 'last known' position when recalculating visible chunks.

Use per-player view distance in chunk distance checks
This commit is contained in:
Andrew Ardill 2011-08-12 00:37:22 +10:00 committed by unknown
parent a6c03ded28
commit 12e377501e
7 changed files with 294 additions and 33 deletions

View file

@ -29,6 +29,16 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
private int bO = 0;
public boolean h;
// CraftBukkit start - extra variables
private boolean viewDistanceSet;
private int viewDistance; // set view distance on a per player basis
private int actualViewDistance; // when view distance in the world changes, need to know how far I could previously see
public String displayName;
public org.bukkit.Location compassTarget;
public long timeOffset = 0;
public boolean relativeTime = true;
// CraftBukkit end
public EntityPlayer(MinecraftServer minecraftserver, World world, String s, ItemInWorldManager iteminworldmanager) {
super(world);
iteminworldmanager.player = this;
@ -51,13 +61,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
this.height = 0.0F;
// CraftBukkit start
this.viewDistanceSet = false;
this.actualViewDistance = getViewDistance(); // set the 'current' view distance. This value will be updated any time the actual view distance changes
this.displayName = this.name;
// CraftBukkit end
}
public String displayName;
public org.bukkit.Location compassTarget;
// CraftBukkit end
public void spawnIn(World world) {
super.spawnIn(world);
// CraftBukkit - world fallback code, either respawn location or global spawn
@ -483,8 +492,6 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
}
// CraftBukkit start
public long timeOffset = 0;
public boolean relativeTime = true;
public long getPlayerTime() {
if (this.relativeTime) {
@ -500,5 +507,55 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
public String toString() {
return super.toString() + "(" + this.name + " at " + this.locX + "," + this.locY + "," + this.locZ + ")";
}
public void setViewDistance(int viewDistance) {
if (viewDistance < 1) { // for now must set view distance to 1 or higher. 0 might be possible, but it breaks the game at the moment
viewDistance = 1;
}
this.viewDistance = viewDistance;
this.viewDistanceSet = true;
updateViewDistance();
}
public int getViewDistance() {
if (viewDistanceSet) {
return viewDistance;
} else {
return defaultViewDistance();
}
}
private int defaultViewDistance() {
org.bukkit.World world = getBukkitEntity().getWorld();
if (world != null) {
return world.getViewDistance();
} else {
return getBukkitEntity().getServer().getViewDistance();
}
}
public void resetViewDistance() {
this.viewDistanceSet = false;
updateViewDistance();
}
public boolean isViewDistanceSet() {
return viewDistanceSet;
}
/**
* Should be called every time the view distance might have changed.
* Ensures we are always aware of the current and previous view distances.
*
* synchronized so that we are always sure that we have accurately tracked the view distance changes
*/
public synchronized void updateViewDistance() {
if (actualViewDistance == getViewDistance()) {
return;
}
// notify the player manager that our view distance may have changed
((CraftWorld) getBukkitEntity().getWorld()).getHandle().manager.updatePlayerViewDistance(this, actualViewDistance, getViewDistance());
actualViewDistance = getViewDistance();
}
// CraftBukkit end
}

View file

@ -14,15 +14,10 @@ public class PlayerManager {
private final int[][] g = new int[][] { { 1, 0}, { 0, 1}, { -1, 0}, { 0, -1}};
public PlayerManager(MinecraftServer minecraftserver, int i, int j) {
if (j > 15) {
throw new IllegalArgumentException("Too big view radius!");
} else if (j < 3) {
throw new IllegalArgumentException("Too small view radius!");
} else {
this.f = j;
this.server = minecraftserver;
this.e = i;
}
// CraftBukkit start - no longer need to track view distance here, defers to the player.
this.server = minecraftserver;
this.e = i;
// CraftBukkit end
}
public WorldServer a() {
@ -66,7 +61,7 @@ public class PlayerManager {
entityplayer.d = entityplayer.locX;
entityplayer.e = entityplayer.locZ;
int k = 0;
int l = this.f;
int l = entityplayer.getViewDistance(); // CraftBukkit - use per-player view distance rather than this.f;
int i1 = 0;
int j1 = 0;
@ -101,8 +96,11 @@ public class PlayerManager {
int i = (int) entityplayer.d >> 4;
int j = (int) entityplayer.e >> 4;
for (int k = i - this.f; k <= i + this.f; ++k) {
for (int l = j - this.f; l <= j + this.f; ++l) {
// CraftBukkit start - use per-player view distance instead of this.f
int viewDistance = entityplayer.getViewDistance();
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
// CraftBukkit end
PlayerInstance playerinstance = this.a(k, l, false);
if (playerinstance != null) {
@ -114,12 +112,14 @@ public class PlayerManager {
this.managedPlayers.remove(entityplayer);
}
private boolean a(int i, int j, int k, int l) {
// CraftBukkit start - changed signature to take a reference to a player. Allows for per-player view distance checks
private boolean a(int viewDistance, int i, int j, int k, int l) {
int i1 = i - k;
int j1 = j - l;
return i1 >= -this.f && i1 <= this.f ? j1 >= -this.f && j1 <= this.f : false;
return i1 >= -viewDistance && i1 <= viewDistance ? j1 >= -viewDistance && j1 <= viewDistance : false; // CraftBukkit - use per-player view distance
}
// CraftBukkit end
public void movePlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.locX >> 4;
@ -135,13 +135,16 @@ public class PlayerManager {
int j1 = j - l;
if (i1 != 0 || j1 != 0) {
for (int k1 = i - this.f; k1 <= i + this.f; ++k1) {
for (int l1 = j - this.f; l1 <= j + this.f; ++l1) {
if (!this.a(k1, l1, k, l)) {
// CraftBukkit start - use per-player view distance instead of this.f
int viewDistance = entityplayer.getViewDistance();
for (int k1 = i - viewDistance; k1 <= i + viewDistance; ++k1) {
for (int l1 = j - viewDistance; l1 <= j + viewDistance; ++l1) {
if (!this.a(viewDistance, k1, l1, k, l)) { // CraftBukkit - use per-player view distance
this.a(k1, l1, true).a(entityplayer);
}
if (!this.a(k1 - i1, l1 - j1, i, j)) {
if (!this.a(viewDistance, k1 - i1, l1 - j1, i, j)) { // CraftBukkit - use per-player view distance
// CraftBukkit end
PlayerInstance playerinstance = this.a(k1 - i1, l1 - j1, false);
if (playerinstance != null) {
@ -182,4 +185,87 @@ public class PlayerManager {
static List b(PlayerManager playermanager) {
return playermanager.c;
}
// CraftBukkit start
/**
* This method will update references of the EntityPlayer to ensure they are being sent all and only those chunks they can see.
* Note that no attempt is made in this method to track the distance viewable. As such, care should be taken to ensure the
* EntityPlayer could indeed see as far previously as you have specified.
*
* If the chunks which the EntityPlayer can see changes, chunks will be added or removed in a spiral fashion.
* @param entityPlayer the EntityPlayer to update
* @param oldViewDistance the previous distance they could see
* @param newViewDistance the new distance they can see
*/
public void updatePlayerViewDistance(EntityPlayer entityPlayer, int oldViewDistance, int newViewDistance) {
if (oldViewDistance == newViewDistance) {
return;
}
int chunkX = (int) entityPlayer.locX >> 4;
int chunkZ = (int) entityPlayer.locZ >> 4;
entityPlayer.d = entityPlayer.locX; // set the 'last known' position
entityPlayer.e = entityPlayer.locZ;
// Going to add/remove players from player-chunk maps in a spiral fashion
// This will send players new chunks they don't have, as well as stop sending chunks they shouldn't have
// We move in an anticlockwise fashion, and can start at any of the four corners
// 0 is [-1,-1]; 1 is [1,-1]; 2 is [1,1]; 3 is [-1,1];
int corner = 2; // TODO use the direction the player is facing to determine best start corner
int xStartOffset = this.g[(corner+3)%4][(corner+1)%2]; // calculate which offset to use based on corner we start in
int zStartOffset = this.g[(corner+2)%4][corner%2];
int deltaX;
int deltaZ;
int loop;
int loopStart;
if (newViewDistance < oldViewDistance) {
// Remove player from outer chunk loops in player-chunk map
loopStart = oldViewDistance;
for (loop = loopStart, deltaX = xStartOffset*loopStart, deltaZ = zStartOffset*loopStart;
loop > newViewDistance;
--loop, deltaX-=xStartOffset, deltaZ-=zStartOffset) {
for (int edge = 0; edge < 4; ++edge) {
int[] direction = this.g[corner++ % 4];
for (int i2 = 0; i2 < loop*2; ++i2) {
deltaX += direction[0];
deltaZ += direction[1];
this.removePlayerFromChunk(entityPlayer, chunkX + deltaX, chunkZ + deltaZ);
}
}
}
} else if (newViewDistance > oldViewDistance) {
// Add player to outer chunk loops in player-chunk map
loopStart = oldViewDistance + 1; // start adding outside the current outer loop
for (loop = loopStart, deltaX = xStartOffset*loopStart, deltaZ = zStartOffset*loopStart;
loop <= newViewDistance;
++loop, deltaX+=xStartOffset, deltaZ+=zStartOffset) {
for (int edge = 0; edge < 4; ++edge) {
int[] direction = this.g[corner++ % 4];
for (int i2 = 0; i2 < loop*2; ++i2) {
deltaX += direction[0];
deltaZ += direction[1];
this.addPlayerToChunk(entityPlayer, chunkX + deltaX, chunkZ + deltaZ);
}
}
}
}
}
private void removePlayerFromChunk(EntityPlayer entityPlayer, int chunkX, int chunkZ) {
PlayerInstance chunkPlayerMap = this.a(chunkX, chunkZ, false); // get the chunk-player map for this chunk, don't create it if it doesn't exist yet
if (chunkPlayerMap != null) {
chunkPlayerMap.b(entityPlayer); // if the chunk-player map exists, remove the player from it.
}
}
private void addPlayerToChunk(EntityPlayer entityPlayer, int chunkX, int chunkZ) {
PlayerInstance chunkPlayerMap = this.a(chunkX, chunkZ, true); // get the chunk-player map for this chunk, create it if it doesn't exist yet
chunkPlayerMap.a(entityPlayer); // add the player to the chunk-player map
}
// CraftBukkit end
}

View file

@ -46,6 +46,7 @@ public class ServerConfigurationManager {
// CraftBukkit start
private CraftServer cserver;
private int viewDistance;
public ServerConfigurationManager(MinecraftServer minecraftserver) {
minecraftserver.server = new CraftServer(minecraftserver, this);
@ -58,7 +59,7 @@ public class ServerConfigurationManager {
this.k = minecraftserver.a("banned-ips.txt");
this.l = minecraftserver.a("ops.txt");
this.m = minecraftserver.a("white-list.txt");
int i = minecraftserver.propertyManager.getInt("view-distance", 10);
this.viewDistance = minecraftserver.propertyManager.getInt("view-distance", 10); // CraftBukkit - add field viewDistance
// CraftBukkit - removed playermanagers
this.maxPlayers = minecraftserver.propertyManager.getInt("max-players", 20);
@ -95,7 +96,7 @@ public class ServerConfigurationManager {
public int a() {
// CraftBukkit start
if (this.server.worlds.size() == 0) {
return this.server.propertyManager.getInt("view-distance", 10) * 16 - 16;
return this.viewDistance * 16 - 16; // Use field value
}
return this.server.worlds.get(0).manager.getFurthestViewableBlock();
// CraftBukkit end
@ -637,4 +638,19 @@ public class ServerConfigurationManager {
entityplayer.updateInventory(entityplayer.defaultContainer);
entityplayer.C();
}
// CraftBukkit start - getters and setters for viewDistance
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public int getViewDistance() {
return viewDistance;
}
public void saveViewDistance() {
this.server.propertyManager.properties.setProperty("view-distance", Integer.toString(this.viewDistance));
this.server.propertyManager.savePropertiesFile();
}
// CraftBukkit end
}

View file

@ -21,6 +21,10 @@ public class WorldServer extends World implements BlockChangeDelegate {
public boolean canSave;
public final MinecraftServer server; // CraftBukkit - private -> public final
private EntityList G = new EntityList();
// CraftBukkit start - extra variables
private int viewDistance; // keep track of changes to view distance
private boolean viewDistanceSet; // ...and if any changes have been made
// CraftBukkit end
// CraftBukkit start - change signature
public WorldServer(MinecraftServer minecraftserver, IDataManager idatamanager, String s, int i, long j, org.bukkit.World.Environment env, ChunkGenerator gen) {
@ -29,7 +33,9 @@ public class WorldServer extends World implements BlockChangeDelegate {
this.dimension = i;
this.pvpMode = minecraftserver.pvpMode;
this.manager = new PlayerManager(minecraftserver, this.dimension, minecraftserver.propertyManager.getInt("view-distance", 10));
// use view distance from configuration manager, instead of property manager
// TODO allow saving view distance per world
this.manager = new PlayerManager(minecraftserver, this.dimension, minecraftserver.serverConfigurationManager.getViewDistance());
}
public final int dimension;
@ -182,4 +188,50 @@ public class WorldServer extends World implements BlockChangeDelegate {
// CraftBukkit end
}
}
// CraftBukkit start - add getter and setter for view distance
public int getViewDistance() {
if (viewDistanceSet) {
return viewDistance;
} else {
return getServer().getViewDistance();
}
}
/**
* This method enforces notchian view distances. Do not set it below 3 or above 15.
* It is possible to set view distance per-player to any positive value.
* @param viewDistance the number of chunks players herein managed can see by default.
* @throws IllegalArgumentException If view distance is less than 3 or greater than 15
*/
public void setViewDistance(int viewDistance) throws IllegalArgumentException{
if (viewDistance > 15) {
throw new IllegalArgumentException("Too big view radius!");
} else if (viewDistance < 3) {
throw new IllegalArgumentException("Too small view radius!");
} else {
this.viewDistance = viewDistance;
this.viewDistanceSet = true;
updateViewDistance();
}
}
public void resetViewDistance() {
viewDistanceSet = false;
updateViewDistance();
}
public boolean isViewDistanceSet() {
return viewDistanceSet;
}
public void updateViewDistance() {
// notify players that they may have to update their view distance
for (Object entityPlayerObject : this.manager.managedPlayers) {
if (entityPlayerObject instanceof EntityPlayer) {
((EntityPlayer) entityPlayerObject).updateViewDistance();
}
}
}
// CraftBukkit end
}

View file

@ -275,10 +275,6 @@ public final class CraftServer implements Server {
return this.getConfigInt("server-port", 25565);
}
public int getViewDistance() {
return this.getConfigInt("view-distance", 10);
}
public String getIp() {
return this.getConfigString("server-ip", "");
}
@ -754,6 +750,22 @@ public final class CraftServer implements Server {
return this.console.allowFlight;
}
public int getViewDistance() {
return server.getViewDistance();
}
public void setViewDistance(int viewDistance) throws IllegalArgumentException{
server.setViewDistance(viewDistance);
updateViewDistance();
server.saveViewDistance();
}
public void updateViewDistance() {
for (World world : worlds.values()) {
((CraftWorld) world).updateViewDistance();
}
}
public ChunkGenerator getGenerator(String world) {
ConfigurationNode node = configuration.getNode("worlds");
ChunkGenerator result = null;

View file

@ -811,4 +811,24 @@ public class CraftWorld implements World {
}
}
}
public int getViewDistance() {
return world.getViewDistance();
}
public void setViewDistance(int viewDistance) throws IllegalArgumentException{
world.setViewDistance(viewDistance);
}
public void resetViewDistance(){
world.resetViewDistance();
}
public boolean isViewDistanceSet() {
return world.isViewDistanceSet();
}
public void updateViewDistance() {
world.updateViewDistance();
}
}

View file

@ -1,7 +1,5 @@
package org.bukkit.craftbukkit.entity;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import net.minecraft.server.EntityHuman;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.Packet131;
@ -28,7 +26,11 @@ import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.map.MapView;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class CraftPlayer extends CraftHumanEntity implements Player {
public CraftPlayer(CraftServer server, EntityPlayer entity) {
super(server, entity);
}
@ -345,4 +347,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public void resetPlayerTime() {
setPlayerTime(0, true);
}
public void setViewDistance(int viewDistance) {
getHandle().setViewDistance(viewDistance);
}
public int getViewDistance() {
return getHandle().getViewDistance();
}
public void resetViewDistance() {
getHandle().resetViewDistance();
}
public boolean isViewDistanceSet() {
return getHandle().isViewDistanceSet();
}
}