diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index c7fa3a1842..1146605f7e 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -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 } diff --git a/src/main/java/net/minecraft/server/PlayerManager.java b/src/main/java/net/minecraft/server/PlayerManager.java index 3dbb30d348..ab606fbbcb 100644 --- a/src/main/java/net/minecraft/server/PlayerManager.java +++ b/src/main/java/net/minecraft/server/PlayerManager.java @@ -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 } diff --git a/src/main/java/net/minecraft/server/ServerConfigurationManager.java b/src/main/java/net/minecraft/server/ServerConfigurationManager.java index c7bb9f073d..de35bc2010 100644 --- a/src/main/java/net/minecraft/server/ServerConfigurationManager.java +++ b/src/main/java/net/minecraft/server/ServerConfigurationManager.java @@ -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 } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index 2005a9bcee..b5f3b8ff6f 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -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 } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 5eb2bd5368..ec5dbb77ff 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -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; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index ed09f0f33d..b527515f83 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -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(); + } } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index c86d7e5c99..f363535779 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -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(); + } }