Optimise definition decoding for faster start-up.

This commit is contained in:
Major-
2015-08-29 19:57:57 +01:00
parent b134f6fdf5
commit 38af001083
15 changed files with 726 additions and 934 deletions
@@ -1,6 +1,7 @@
package org.apollo.game.fs.decoder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -11,6 +12,7 @@ import java.util.function.Predicate;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.decoder.MapFileDecoder;
import org.apollo.cache.decoder.MapFileDecoder.MapDefinition;
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.model.Position;
import org.apollo.game.model.World;
@@ -31,7 +33,7 @@ import com.google.common.collect.Iterables;
* @author Ryley
* @author Major
*/
public final class GameObjectDecoder {
public final class GameObjectDecoder implements Runnable {
/**
* A bit flag that indicates that the tile at the current Position is blocked.
@@ -58,44 +60,51 @@ public final class GameObjectDecoder {
*/
private final RegionRepository regions;
/**
* The World to place the objects in.
*/
private final World world;
/**
* Creates the GameObjectDecoder.
*
* @param fs The {@link IndexedFileSystem}.
* @param regions The {@link RegionRepository}.
* @param world The {@link World} to place the objects in.
*/
public GameObjectDecoder(IndexedFileSystem fs, RegionRepository regions) {
public GameObjectDecoder(IndexedFileSystem fs, World world) {
this.fs = fs;
this.regions = regions;
this.world = world;
regions = world.getRegionRepository();
}
/**
* Decodes the GameObjects from their MapDefinitions.
*
* @param world The {@link World} containing the StaticGameObjects.
* @return The decoded objects.
* @throws IOException If there is an error decoding the {@link MapDefinition}s.
*/
public GameObject[] decode(World world) throws IOException {
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
@Override
public void run() {
ObjectDefinitionDecoder decoder = new ObjectDefinitionDecoder(fs);
decoder.run();
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
MapDefinition definition = entry.getValue();
try {
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
int packed = definition.getPackedCoordinates();
int x = (packed >> 8 & 0xFF) * 64;
int y = (packed & 0xFF) * 64;
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
MapDefinition definition = entry.getValue();
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
decodeObjects(world, decompressed, x, y);
int packed = definition.getPackedCoordinates();
int x = (packed >> 8 & 0xFF) * 64;
int y = (packed & 0xFF) * 64;
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
decodeTerrain(decompressed, x, y);
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
decodeObjects(world, decompressed, x, y);
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
decodeTerrain(decompressed, x, y);
}
} catch (IOException e) {
throw new UncheckedIOException("Error decoding StaticGameObjects.", e);
}
return Iterables.toArray(objects, GameObject.class);
objects.forEach(object -> regions.fromPosition(object.getPosition()).addEntity(object, false));
}
/**
@@ -112,24 +121,21 @@ public final class GameObjectDecoder {
int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix matrix = region.getMatrix(height);
boolean block = false;
if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) {
block = true;
}
Predicate<Integer> walls = (value) -> value >= ObjectType.LENGTHWISE_WALL.getValue()
// TODO figure out the other ObjectTypes and get rid of all the getValue() calls
Predicate<Integer> walls = value -> value >= ObjectType.LENGTHWISE_WALL.getValue()
&& value <= ObjectType.RECTANGULAR_CORNER.getValue() || value == ObjectType.DIAGONAL_WALL.getValue();
Predicate<Integer> roofs = (value) -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
Predicate<Integer> roofs = value -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
&& value < ObjectType.FLOOR_DECORATION.getValue();
if (walls.test(type) || roofs.test(type)) {
block = true;
}
if (type == 10 && definition.isSolid()) {
if (walls.test(type) || roofs.test(type) || type == ObjectType.INTERACTABLE.getValue() && definition.isSolid()) {
block = true;
}
@@ -144,11 +150,13 @@ public final class GameObjectDecoder {
Position nextPosition = new Position(nextLocalX, nextLocalY);
Region next = regions.fromPosition(nextPosition);
int nextX = nextPosition.getX() % Region.SIZE + dx, nextY = nextPosition.getY() % Region.SIZE
+ dy;
int nextX = nextPosition.getX() % Region.SIZE + dx;
int nextY = nextPosition.getY() % Region.SIZE + dy;
if (nextX > 7) {
nextX -= 7;
}
if (nextY > 7) {
nextY -= 7;
}
@@ -173,12 +181,11 @@ public final class GameObjectDecoder {
Region region = regions.fromPosition(position);
int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix current = region.getMatrix(height);
boolean block = false;
if ((attributes & BLOCKED_TILE) != 0) {
block = true;
}
if ((attributes & BRIDGE_TILE) != 0) {
if (height > 0) {
block = true;
@@ -188,7 +195,7 @@ public final class GameObjectDecoder {
if (block) {
int localX = x % Region.SIZE, localY = y % Region.SIZE;
current.block(localX, localY);
region.getMatrix(height).block(localX, localY);
}
}
@@ -249,6 +256,7 @@ public final class GameObjectDecoder {
int attributes = 0;
while (true) {
int attributeId = buffer.get() & 0xFF;
if (attributeId == 0) {
decodeAttributes(attributes, position);
break;
@@ -0,0 +1,54 @@
package org.apollo.game.fs.decoder;
import org.apollo.util.ThreadUtil;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* A composite decoder that executes each child in parallel.
*
* @author Major
*/
public final class SynchronousDecoder {
/**
* The time to wait before cancelling the decoding.
*/
private static final int TIMEOUT = 15_000;
/**
* The Executor used to execute the Runnable(s).
*/
private final ExecutorService executor = Executors.newFixedThreadPool(ThreadUtil.AVAILABLE_PROCESSORS,
ThreadUtil.create("SynchronousDecoder"));
/**
* The List of Runnables.
*/
private final List<Runnable> runnables;
/**
* Creates the SynchronousDecoder.
*
* @param runnables The {@link Runnable}s to execute.
*/
public SynchronousDecoder(Runnable... runnables) {
this.runnables = Arrays.asList(runnables);
}
/**
* Starts this SynchronousDecoder.
*
* @throws InterruptedException If a decoder is still running after {@link #TIMEOUT} ms.
*/
public void block() throws InterruptedException {
runnables.forEach(executor::submit);
executor.shutdown();
executor.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS);
}
}
@@ -1,67 +1,83 @@
package org.apollo.game.io;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import org.apollo.cache.def.EquipmentDefinition;
/**
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link EquipmentDefinition}s.
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link
* EquipmentDefinition}s.
*
* @author Graham
*/
public final class EquipmentDefinitionParser {
public final class EquipmentDefinitionParser implements Runnable {
/**
* The input stream.
* Creates an {@link EquipmentDefinitionParser} that reads from the file located at the specified {@code path}.
*
* @param path The path to the file.
* @return The EquipmentDefinitionParser.
* @throws UncheckedIOException If there is an error creating the {@link FileInputStream}.
*/
public static EquipmentDefinitionParser fromFile(String path) {
try {
return new EquipmentDefinitionParser(new BufferedInputStream(new FileInputStream(path)));
} catch (IOException e) {
throw new UncheckedIOException("Error creating EquipmentDefinitionParser.", e);
}
}
/**
* The InputStream.
*/
private final InputStream is;
/**
* Creates the equipment definition parser.
* Creates the EquipmentDefinitionParser.
*
* @param is The input stream.
* @param is The {@link InputStream}.
*/
public EquipmentDefinitionParser(InputStream is) {
this.is = is;
}
/**
* Parses the input stream.
*
* @return The equipment definition array.
* @throws IOException If an I/O error occurs.
*/
public EquipmentDefinition[] parse() throws IOException {
DataInputStream dis = new DataInputStream(is);
@Override
public void run() {
try (DataInputStream in = new DataInputStream(is)) {
int count = in.readShort() & 0xFFFF;
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
int count = dis.readShort() & 0xFFFF;
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
for (int id = 0; id < count; id++) {
int slot = in.readByte() & 0xFF;
if (slot != 0xFF) {
boolean twoHanded = in.readBoolean();
boolean fullBody = in.readBoolean();
boolean fullHat = in.readBoolean();
boolean fullMask = in.readBoolean();
int attack = in.readByte() & 0xFF;
int strength = in.readByte() & 0xFF;
int defence = in.readByte() & 0xFF;
int ranged = in.readByte() & 0xFF;
int magic = in.readByte() & 0xFF;
for (int id = 0; id < count; id++) {
int slot = dis.readByte() & 0xFF;
if (slot != 0xFF) {
boolean twoHanded = dis.readBoolean();
boolean fullBody = dis.readBoolean();
boolean fullHat = dis.readBoolean();
boolean fullMask = dis.readBoolean();
int attack = dis.readByte() & 0xFF;
int strength = dis.readByte() & 0xFF;
int defence = dis.readByte() & 0xFF;
int ranged = dis.readByte() & 0xFF;
int magic = dis.readByte() & 0xFF;
EquipmentDefinition definition = new EquipmentDefinition(id);
definition.setLevels(attack, strength, defence, ranged, magic);
definition.setSlot(slot);
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
EquipmentDefinition definition = new EquipmentDefinition(id);
definition.setLevels(attack, strength, defence, ranged, magic);
definition.setSlot(slot);
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
definitions[id] = definition;
definitions[id] = definition;
}
}
}
return definitions;
EquipmentDefinition.init(definitions);
} catch (IOException e) {
throw new UncheckedIOException("Error parsing EquipmentDefinitions.", e);
}
}
}
+18 -61
View File
@@ -1,24 +1,18 @@
package org.apollo.game.model;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.apollo.Server;
import org.apollo.Service;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.decoder.ItemDefinitionDecoder;
import org.apollo.cache.decoder.NpcDefinitionDecoder;
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
import org.apollo.cache.def.EquipmentDefinition;
import org.apollo.cache.def.ItemDefinition;
import org.apollo.cache.def.NpcDefinition;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.command.CommandDispatcher;
import org.apollo.game.fs.decoder.GameObjectDecoder;
import org.apollo.game.fs.decoder.SynchronousDecoder;
import org.apollo.game.io.EquipmentDefinitionParser;
import org.apollo.game.model.area.Region;
import org.apollo.game.model.area.RegionRepository;
@@ -27,7 +21,6 @@ import org.apollo.game.model.entity.EntityType;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Npc;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.obj.GameObject;
import org.apollo.game.model.event.Event;
import org.apollo.game.model.event.EventListener;
import org.apollo.game.model.event.EventListenerChainSet;
@@ -87,11 +80,6 @@ public final class World {
*/
private final EventListenerChainSet events = new EventListenerChainSet();
/**
* The ScheduledTask that moves Npcs.
*/
private NpcMovementTask npcMovement;
/**
* The {@link MobRepository} of {@link Npc}s.
*/
@@ -107,32 +95,30 @@ public final class World {
*/
private final Map<Long, Player> players = new HashMap<>();
/**
* The {@link PluginManager}.
*/
private PluginManager pluginManager;
/**
* This world's {@link RegionRepository}.
*/
private final RegionRepository regions = RegionRepository.immutable();
/**
* The release number (i.e. version) of this world.
*/
private int releaseNumber;
/**
* The scheduler.
*/
private final Scheduler scheduler = new Scheduler();
/**
* Creates the world.
* The ScheduledTask that moves Npcs.
*/
public World() {
private NpcMovementTask npcMovement;
}
/**
* The {@link PluginManager}.
*/
private PluginManager pluginManager;
/**
* The release number (i.e. version) of this world.
*/
private int releaseNumber;
/**
* Gets the command dispatcher.
@@ -210,32 +196,11 @@ public final class World {
public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception {
releaseNumber = release;
ItemDefinitionDecoder itemDecoder = new ItemDefinitionDecoder(fs);
ItemDefinition[] items = itemDecoder.decode();
ItemDefinition.init(items);
logger.fine("Loaded " + items.length + " item definitions.");
SynchronousDecoder decoder = new SynchronousDecoder(new ItemDefinitionDecoder(fs),
new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
try (InputStream is = new BufferedInputStream(new FileInputStream("data/equipment-" + release + ".dat"))) {
EquipmentDefinitionParser parser = new EquipmentDefinitionParser(is);
EquipmentDefinition[] defs = parser.parse();
EquipmentDefinition.init(defs);
logger.fine("Loaded " + defs.length + " equipment definitions.");
}
NpcDefinitionDecoder npcDecoder = new NpcDefinitionDecoder(fs);
NpcDefinition[] npcs = npcDecoder.decode();
NpcDefinition.init(npcs);
logger.fine("Loaded " + npcs.length + " npc definitions.");
ObjectDefinitionDecoder objectDecoder = new ObjectDefinitionDecoder(fs);
ObjectDefinition[] objectDefs = objectDecoder.decode();
ObjectDefinition.init(objectDefs);
logger.fine("Loaded " + objectDefs.length + " object definitions.");
GameObjectDecoder staticDecoder = new GameObjectDecoder(fs, regions);
GameObject[] objects = staticDecoder.decode(this);
placeEntities(objects);
logger.fine("Loaded " + objects.length + " static objects.");
decoder.block();
npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues.
scheduler.schedule(npcMovement);
@@ -291,6 +256,7 @@ public final class World {
} else {
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
}
return success;
}
@@ -384,13 +350,4 @@ public final class World {
}
}
/**
* Adds entities to regions in the {@link RegionRepository}. By default, we do not notify listeners.
*
* @param entities The entities.
*/
private void placeEntities(Entity... entities) {
Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity, false));
}
}
@@ -21,8 +21,13 @@ import org.apollo.game.sync.seg.SynchronizationSegment;
public final class NpcSynchronizationTask extends SynchronizationTask {
/**
* The maximum number of npcs to load per cycle. This prevents the update packet from becoming too large (the client
* uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
* The maximum amount of local npcs.
*/
private static final int MAXIMUM_LOCAL_NPCS = 255;
/**
* The maximum number of npcs to load per cycle. This prevents the update packet from becoming too large (the
* client uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
*/
private static final int NEW_NPCS_PER_CYCLE = 20;
@@ -42,41 +47,49 @@ public final class NpcSynchronizationTask extends SynchronizationTask {
@Override
public void run() {
List<Npc> localNpcs = player.getLocalNpcList();
List<Npc> locals = player.getLocalNpcList();
List<SynchronizationSegment> segments = new ArrayList<>();
int oldLocalNpcs = localNpcs.size();
int originalCount = locals.size();
final Position playerPosition = player.getPosition();
for (Iterator<Npc> it = localNpcs.iterator(); it.hasNext();) {
Npc npc = it.next();
if (!npc.isActive() || npc.isTeleporting() || npc.getPosition().getLongestDelta(playerPosition) > player.getViewingDistance() || !npc.getPosition().isWithinDistance(playerPosition, player.getViewingDistance())) {
it.remove();
int distance = player.getViewingDistance();
for (Iterator<Npc> iterator = locals.iterator(); iterator.hasNext(); ) {
Npc npc = iterator.next();
Position position = npc.getPosition();
if (!npc.isActive() || npc.isTeleporting() || position.getLongestDelta(playerPosition) > distance
|| !position.isWithinDistance(playerPosition, distance)) {
iterator.remove();
segments.add(new RemoveMobSegment());
} else {
segments.add(new MovementSegment(npc.getBlockSet(), npc.getDirections()));
}
}
int added = 0;
int added = 0, count = locals.size();
for (Npc npc : player.getWorld().getNpcRepository()) {
if (localNpcs.size() >= 255) {
if (count >= MAXIMUM_LOCAL_NPCS) {
player.flagExcessiveNpcs();
break;
} else if (added >= NEW_NPCS_PER_CYCLE) {
break;
}
Position npcPosition = npc.getPosition();
if (npcPosition.isWithinDistance(playerPosition, player.getViewingDistance()) && !localNpcs.contains(npc)) {
localNpcs.add(npc);
Position position = npc.getPosition();
if (position.isWithinDistance(playerPosition, distance) && !locals.contains(npc)) {
locals.add(npc);
count++;
added++;
npc.turnTo(npc.getFacingPosition());
segments.add(new AddNpcSegment(npc.getBlockSet(), npc.getIndex(), npcPosition, npc.getId()));
segments.add(new AddNpcSegment(npc.getBlockSet(), npc.getIndex(), position, npc.getId()));
}
}
player.send(new NpcSynchronizationMessage(playerPosition, segments, oldLocalNpcs));
player.send(new NpcSynchronizationMessage(playerPosition, segments, originalCount));
}
}
@@ -6,7 +6,6 @@ import java.util.List;
import org.apollo.game.message.impl.PlayerSynchronizationMessage;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Player;
import org.apollo.game.sync.block.AppearanceBlock;
import org.apollo.game.sync.block.ChatBlock;
@@ -25,6 +24,11 @@ import org.apollo.game.sync.seg.TeleportSegment;
*/
public final class PlayerSynchronizationTask extends SynchronizationTask {
/**
* The maximum amount of local players.
*/
private static final int MAXIMUM_LOCAL_PLAYERS = 255;
/**
* The maximum number of players to load per cycle. This prevents the update packet from becoming too large (the
* client uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
@@ -32,14 +36,14 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
private static final int NEW_PLAYERS_PER_CYCLE = 20;
/**
* The player.
* The Player.
*/
private final Player player;
/**
* Creates the {@link PlayerSynchronizationTask} for the specified player.
* Creates the {@link PlayerSynchronizationTask} for the specified {@link Player}.
*
* @param player The player.
* @param player The Player.
*/
public PlayerSynchronizationTask(Player player) {
this.player = player;
@@ -50,80 +54,80 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
Position lastKnownRegion = player.getLastKnownRegion();
boolean regionChanged = player.hasRegionChanged();
SynchronizationSegment segment;
SynchronizationBlockSet blockSet = player.getBlockSet();
if (blockSet.contains(ChatBlock.class)) {
blockSet = blockSet.clone();
blockSet.remove(ChatBlock.class);
}
if (player.isTeleporting() || player.hasRegionChanged()) {
segment = new TeleportSegment(blockSet, player.getPosition());
} else {
segment = new MovementSegment(blockSet, player.getDirections());
}
Position position = player.getPosition();
SynchronizationSegment segment = (player.isTeleporting() || player.hasRegionChanged()) ?
new TeleportSegment(blockSet, position) : new MovementSegment(blockSet, player.getDirections());
List<Player> localPlayers = player.getLocalPlayerList();
int oldLocalPlayers = localPlayers.size();
int oldCount = localPlayers.size();
List<SynchronizationSegment> segments = new ArrayList<>();
int distance = player.getViewingDistance();
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext(); ) {
Player other = it.next();
for (Iterator<Player> iterator = localPlayers.iterator(); iterator.hasNext(); ) {
Player other = iterator.next();
if (removePlayer(other)) {
it.remove();
if (removeable(position, distance, other)) {
iterator.remove();
segments.add(new RemoveMobSegment());
} else {
segments.add(new MovementSegment(other.getBlockSet(), other.getDirections()));
}
}
int added = 0;
int added = 0, count = localPlayers.size();
MobRepository<Player> repository = player.getWorld().getPlayerRepository();
for (Player other : repository) {
if (localPlayers.size() >= 255) {
for (Player other : player.getWorld().getPlayerRepository()) {
if (count >= MAXIMUM_LOCAL_PLAYERS) {
player.flagExcessivePlayers();
break;
} else if (added >= NEW_PLAYERS_PER_CYCLE) {
break;
}
if (other != player && other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) && !localPlayers.contains(other)) {
Position local = other.getPosition();
if (other != player && local.isWithinDistance(position, distance) && !localPlayers.contains(other)) {
localPlayers.add(other);
count++;
added++;
blockSet = other.getBlockSet();
if (!blockSet.contains(AppearanceBlock.class)) {
// TODO check if client has cached appearance
if (!blockSet.contains(AppearanceBlock.class)) { // TODO check if client has cached appearance
blockSet = blockSet.clone();
blockSet.add(SynchronizationBlock.createAppearanceBlock(other));
}
segments.add(new AddPlayerSegment(blockSet, other.getIndex(), other.getPosition()));
segments.add(new AddPlayerSegment(blockSet, other.getIndex(), local));
}
}
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, player.getPosition(),
regionChanged, segment, oldLocalPlayers, segments);
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, position,
regionChanged, segment, oldCount, segments);
player.send(message);
}
/**
* Returns whether or not the specified {@link Player} should be removed.
*
* @param position The {@link Position} of the Player being updated.
* @param other The Player being tested.
* @return {@code true} iff the specified Player should be removed.
*/
private boolean removePlayer(Player other) {
private boolean removeable(Position position, int distance, Player other) {
if (other.isTeleporting() || !other.isActive()) {
return true;
}
Position position = player.getPosition();
Position otherPosition = other.getPosition();
int distance = player.getViewingDistance();
return otherPosition.getLongestDelta(position) > distance || !otherPosition.isWithinDistance(position, distance);
}
@@ -28,6 +28,7 @@ public final class PostPlayerSynchronizationTask extends SynchronizationTask {
player.setTeleporting(false);
player.setRegionChanged(false);
player.resetBlockSet();
if (!player.isExcessivePlayersSet()) {
player.incrementViewingDistance();
} else {