Fix travelback implementation.

This commit is contained in:
Major-
2015-08-24 14:41:19 +01:00
parent d9c34cd9ed
commit ed160ef70e
9 changed files with 264 additions and 320 deletions
@@ -13,7 +13,7 @@ import org.apollo.game.service.GameService;
public final class GamePulseHandler implements Runnable {
/**
* The logger for this class.
* The Logger for this class.
*/
private static final Logger logger = Logger.getLogger(GamePulseHandler.class.getName());
@@ -23,7 +23,7 @@ public final class GamePulseHandler implements Runnable {
private final GameService service;
/**
* Creates the game pulse handler object.
* Creates the GamePulseHandler.
*
* @param service The {@link GameService}.
*/
@@ -35,8 +35,8 @@ public final class GamePulseHandler implements Runnable {
public void run() {
try {
service.pulse();
} catch (Throwable reason) {
logger.log(Level.SEVERE, "Exception occured during pulse!", reason);
} catch (Throwable throwable) {
logger.log(Level.SEVERE, "Exception occurred during pulse!", throwable);
}
}
@@ -30,15 +30,13 @@ public final class WalkMessageHandler extends MessageHandler<WalkMessage> {
for (int index = 0; index < steps.length; index++) {
Position step = steps[index];
if (index == 0) {
if (!queue.addFirstStep(step)) {
return; // ignore packet
}
queue.addFirstStep(step);
} else {
queue.addStep(step);
}
}
queue.setRunningQueue(message.isRunning() || player.isRunning());
queue.setRunning(message.isRunning() || player.isRunning());
player.getInterfaceSet().close();
if (queue.size() > 0) {
@@ -58,48 +58,43 @@ public enum Direction {
public static final Direction[] EMPTY_DIRECTION_ARRAY = new Direction[0];
/**
* Creates a direction from the differences between X and Y.
* Gets the Direction between the two {@link Position}s..
*
* @param deltaX The difference between two X coordinates.
* @param deltaY The difference between two Y coordinates.
* @param current The difference between two X coordinates.
* @param next The difference between two Y coordinates.
* @return The direction.
*/
public static Direction fromDeltas(int deltaX, int deltaY) {
public static Direction between(Position current, Position next) {
int deltaX = next.getX() - current.getX();
int deltaY = next.getY() - current.getY();
if (deltaY == 1) {
if (deltaX == 1) {
return Direction.NORTH_EAST;
return NORTH_EAST;
} else if (deltaX == 0) {
return Direction.NORTH;
return NORTH;
} else if (deltaX == -1) {
return NORTH_WEST;
}
return Direction.NORTH_WEST;
} else if (deltaY == -1) {
if (deltaX == 1) {
return Direction.SOUTH_EAST;
return SOUTH_EAST;
} else if (deltaX == 0) {
return Direction.SOUTH;
}
return Direction.SOUTH_WEST;
} else {
if (deltaX == 1) {
return Direction.EAST;
return SOUTH;
} else if (deltaX == -1) {
return Direction.WEST;
return SOUTH_WEST;
}
} else if (deltaY == 0) {
if (deltaX == 1) {
return EAST;
} else if (deltaX == 0) {
return NONE;
} else if (deltaX == -1) {
return WEST;
}
}
return Direction.NONE;
}
/**
* Checks if the direction represented by the two delta values can connect two points together in a single
* direction.
*
* @param deltaX The difference in X coordinates.
* @param deltaY The difference in X coordinates.
* @return {@code true} if so, {@code false} if not.
*/
public static boolean isConnectable(int deltaX, int deltaY) {
return Math.abs(deltaX) == Math.abs(deltaY) || deltaX == 0 || deltaY == 0;
throw new IllegalArgumentException("Difference between Positions must be [-1, 1].");
}
/**
@@ -112,7 +107,7 @@ public enum Direction {
*
* @param intValue The direction as an integer.
*/
private Direction(int intValue) {
Direction(int intValue) {
this.intValue = intValue;
}
@@ -67,16 +67,36 @@ public final class Player extends Mob {
AttributeMap.define("run_energy", AttributeDefinition.forInt(100, AttributePersistence.PERSISTENT));
}
/**
* The player's appearance.
*/
private Appearance appearance = Appearance.DEFAULT_APPEARANCE;
/**
* This player's bank.
*/
private final Inventory bank = new Inventory(InventoryConstants.BANK_CAPACITY, StackMode.STACK_ALWAYS);
/**
* This player's credentials.
*/
private final PlayerCredentials credentials;
/**
* This player's interface set.
*/
private final InterfaceSet interfaceSet = new InterfaceSet(this);
/**
* The Set of DynamicGameObjects that are visible to this Player.
*/
private final Set<DynamicGameObject> localObjects = new HashSet<>();
/**
* A temporary queue of messages sent during the login process.
*/
private final Deque<Message> queuedMessages = new ArrayDeque<>();
/**
* The player's appearance.
*/
private Appearance appearance = Appearance.DEFAULT_APPEARANCE;
/**
* The privacy state of this player's public chat.
*/
@@ -87,11 +107,6 @@ public final class Player extends Mob {
*/
private Deque<Point> clicks = new ArrayDeque<>();
/**
* This player's credentials.
*/
private final PlayerCredentials credentials;
/**
* A flag which indicates there are npcs that couldn't be added.
*/
@@ -122,11 +137,6 @@ public final class Player extends Mob {
*/
private List<String> ignores = new ArrayList<>();
/**
* This player's interface set.
*/
private final InterfaceSet interfaceSet = new InterfaceSet(this);
/**
* Whether or not the player is skulled.
*/
@@ -137,11 +147,6 @@ public final class Player extends Mob {
*/
private Position lastKnownRegion;
/**
* The Set of DynamicGameObjects that are visible to this Player.
*/
private final Set<DynamicGameObject> localObjects = new HashSet<>();
/**
* The MembershipStatus of this Player.
*/
@@ -157,11 +162,6 @@ public final class Player extends Mob {
*/
private PrivilegeLevel privilegeLevel = PrivilegeLevel.STANDARD;
/**
* A temporary queue of messages sent during the login process.
*/
private final Deque<Message> queuedMessages = new ArrayDeque<>();
/**
* A flag indicating if the region changed in the last cycle.
*/
@@ -506,11 +506,6 @@ public final class Player extends Mob {
return worldId;
}
@Override
public int hashCode() {
return credentials.hashCode();
}
/**
* Indicates whether or not the player with the specified username is on this player's ignore list.
*
@@ -539,6 +534,11 @@ public final class Player extends Mob {
return regionChanged;
}
@Override
public int hashCode() {
return credentials.hashCode();
}
/**
* Increments this player's viewing distance if it is less than the maximum viewing distance.
*/
@@ -790,15 +790,6 @@ public final class Player extends Mob {
this.chatPrivacy = chatPrivacy;
}
/**
* Sets the value denoting the client's modified version.
*
* @param version The client version.
*/
public void setClientVersion(int version) {
attributes.set("client_version", new NumericalAttribute(version));
}
/**
* Sets the friend {@link PrivacyState}.
*
@@ -939,6 +930,12 @@ public final class Player extends Mob {
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel)
.toString();
}
/**
* Toggles the message filter.
*
@@ -953,16 +950,10 @@ public final class Player extends Mob {
*/
public void toggleRunning() {
running = !running;
walkingQueue.setRunningQueue(running);
walkingQueue.setRunning(running);
send(new ConfigMessage(173, running ? 1 : 0));
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel)
.toString();
}
/**
* Initialises this player.
*/
@@ -7,8 +7,6 @@ import java.util.Queue;
import org.apollo.game.model.Direction;
import org.apollo.game.model.Position;
import com.google.common.base.MoreObjects;
/**
* A queue of {@link Direction}s which a {@link Mob} will follow.
*
@@ -17,162 +15,167 @@ import com.google.common.base.MoreObjects;
public final class WalkingQueue {
/**
* Represents a single point in the queue.
*
* @author Graham
*/
private static final class Point {
/**
* The direction to walk to this point.
*/
private final Direction direction;
/**
* The point's position.
*/
private final Position position;
/**
* Creates a point.
*
* @param position The position.
* @param direction The direction.
*/
public Point(Position position, Direction direction) {
this.position = position;
this.direction = direction;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("direction", direction).add("position", position)
.toString();
}
}
/**
* The maximum size of the queue. If any additional steps are added, they are discarded.
*/
private static final int MAXIMUM_SIZE = 128;
/**
* The mob whose walking queue this is.
* The Mob this WalkingQueue belongs to.
*/
private final Mob mob;
/**
* The old queue of directions.
* The Deque of active points in this WalkingQueue.
*/
private final Deque<Point> oldPoints = new ArrayDeque<>();
private final Deque<Position> points = new ArrayDeque<>();
/**
* The queue of directions.
* The Deque of previous points in this WalkingQueue.
*/
private final Deque<Point> points = new ArrayDeque<>();
private final Deque<Position> previousPoints = new ArrayDeque<>();
/**
* Flag indicating if this queue (only) should be ran.
* The running status of this WalkingQueue.
*/
private boolean runningQueue;
private boolean running;
/**
* Creates a walking queue for the specified mob.
* Creates the WalkingQueue.
*
* @param mob The mob.
* @param mob The {@link Mob} the WalkingQueue is for.
*/
public WalkingQueue(Mob mob) {
this.mob = mob;
}
/**
* Adds the first step to the queue, attempting to connect the server and client position by looking at the
* previous queue.
* Adds a first step into this WalkingQueue.
*
* @param clientPosition The first step.
* @return {@code true} if the queues could be connected correctly, {@code false} if not.
* @param next The {@link Position} of the step.
*/
public boolean addFirstStep(Position clientPosition) {
Position serverPosition = mob.getPosition();
public void addFirstStep(Position next) {
points.clear();
running = false;
int deltaX = clientPosition.getX() - serverPosition.getX();
int deltaY = clientPosition.getY() - serverPosition.getY();
/*
* We need to connect 'current' and 'next' whilst accounting for the
* fact that the client and server might be out of sync (i.e. what the
* client thinks is 'current' is different to what the server thinks is
* 'current').
*
* First try to connect them via points from the previous queue.
*/
Queue<Position> backtrack = new ArrayDeque<>();
if (Direction.isConnectable(deltaX, deltaY)) {
points.clear();
oldPoints.clear();
while (!previousPoints.isEmpty()) {
Position position = previousPoints.pollLast();
backtrack.add(position);
addStep(clientPosition);
return true;
}
Queue<Position> travelBackQueue = new ArrayDeque<>();
Point oldPoint;
while ((oldPoint = oldPoints.pollLast()) != null) {
Position oldPosition = oldPoint.position;
deltaX = oldPosition.getX() - serverPosition.getX();
deltaY = oldPosition.getX() - serverPosition.getY();
travelBackQueue.add(oldPosition);
if (Direction.isConnectable(deltaX, deltaY)) {
points.clear();
oldPoints.clear();
travelBackQueue.forEach(this::addStep);
addStep(clientPosition);
return true;
if (position.equals(next)) {
backtrack.forEach(this::addStep);
previousPoints.clear();
return;
}
}
oldPoints.clear();
return false;
/* If that doesn't work, connect the points directly. */
previousPoints.clear();
addStep(next);
}
/**
* Adds a step.
* Adds a step to this WalkingQueue.
*
* @param x The x coordinate of this step.
* @param y The y coordinate of this step.
* @param next The {@link Position} of the step.
*/
private void addStep(int x, int y) {
if (points.size() >= MAXIMUM_SIZE) {
return;
public void addStep(Position next) {
Position current = points.peekLast();
/*
* If current equals next, addFirstStep doesn't end up adding anything points queue. This makes peekLast()
* return null. If it does, the correct behaviour is to fill it in with mob.getPosition().
*/
if (current == null) {
current = mob.getPosition();
}
Point last = getLast();
int deltaX = x - last.position.getX();
int deltaY = y - last.position.getY();
Direction direction = Direction.fromDeltas(deltaX, deltaY);
if (direction != Direction.NONE) {
Point point = new Point(new Position(x, y, mob.getPosition().getHeight()), direction);
points.add(point);
oldPoints.add(point);
}
addStep(current, next);
}
/**
* Adds a step to the queue.
*
* @param step The step to add.
* Clears this WalkingQueue.
*/
public void addStep(Position step) {
int x = step.getX(), y = step.getY();
Point last = getLast();
public void clear() {
points.clear();
running = false;
previousPoints.clear();
}
int deltaX = x - last.position.getX();
int deltaY = y - last.position.getY();
/**
* Returns whether or not this WalkingQueue has running enabled.
*
* @return {@code true} iff this WalkingQueue has running enabled.
*/
public boolean isRunning() {
return running;
}
/**
* Pulses this WalkingQueue.
*/
public void pulse() {
Position position = mob.getPosition();
Direction firstDirection = Direction.NONE;
Direction secondDirection = Direction.NONE;
Position next = points.poll();
if (next != null) {
previousPoints.add(next);
firstDirection = Direction.between(position, next);
position = next;
if (running) {
next = points.poll();
if (next != null) {
previousPoints.add(next);
secondDirection = Direction.between(position, next);
position = next;
}
}
}
mob.setDirections(firstDirection, secondDirection);
mob.setPosition(position);
}
/**
* Sets the running flag status of this WalkingQueue.
*
* @param running The running flag.
*/
public void setRunning(boolean running) {
this.running = running;
}
/**
* Gets the size of this WalkingQueue, which is the number of points remaining in it.
*
* @return The size.
*/
public int size() {
return points.size();
}
/**
* Adds the {@code next} step to this WalkingQueue.
*
* @param current The current {@link Position}.
* @param next The next Position.
*/
private void addStep(Position current, Position next) {
int nextX = next.getX(), nextY = next.getY(), height = next.getHeight();
int deltaX = nextX - current.getX();
int deltaY = nextY - current.getY();
int max = Math.max(Math.abs(deltaX), Math.abs(deltaY));
for (int i = 0; i < max; i++) {
for (int count = 0; count < max; count++) {
if (deltaX < 0) {
deltaX++;
} else if (deltaX > 0) {
@@ -185,70 +188,8 @@ public final class WalkingQueue {
deltaY--;
}
addStep(x - deltaX, y - deltaY);
points.add(new Position(nextX - deltaX, nextY - deltaY, height));
}
}
/**
* Clears the walking queue.
*/
public void clear() {
points.clear();
oldPoints.clear();
}
/**
* Gets the last point.
*
* @return The last point.
*/
private Point getLast() {
return points.isEmpty() ? new Point(mob.getPosition(), Direction.NONE) : points.peekLast();
}
/**
* Called every pulse, updates the queue.
*/
public void pulse() {
Position position = mob.getPosition();
Direction first = Direction.NONE, second = Direction.NONE;
Point next = points.poll();
if (next != null) {
first = next.direction;
position = next.position;
if (runningQueue /* and enough energy */) {
next = points.poll();
if (next != null) {
second = next.direction;
position = next.position;
}
}
mob.setPosition(position);
}
mob.setDirections(first, second);
}
/**
* Sets the running queue flag.
*
* @param running The running queue flag.
*/
public void setRunningQueue(boolean running) {
runningQueue = running;
}
/**
* Gets the size of the queue.
*
* @return The size of the queue.
*/
public int size() {
return points.size();
}
}
@@ -93,7 +93,9 @@ public final class NpcMovementTask extends ScheduledTask {
WalkingQueue queue = npc.getWalkingQueue();
Position first = positions.pollFirst();
if (first != null && queue.addFirstStep(first)) {
if (first != null) {
queue.addFirstStep(first);
positions.forEach(queue::addStep);
}
@@ -33,11 +33,6 @@ import org.xml.sax.SAXException;
*/
public final class GameService extends Service {
/**
* The World this Service is for.
*/
protected final World world;
/**
* The number of times to unregister players per cycle. This is to ensure the saving threads don't get swamped with
* requests and slow everything down.
@@ -45,9 +40,9 @@ public final class GameService extends Service {
private static final int UNREGISTERS_PER_CYCLE = 50;
/**
* The {@link MessageHandlerChainSet}.
* The World this Service is for.
*/
private MessageHandlerChainSet messageHandlerChainSet;
protected final World world;
/**
* A queue of players to remove.
@@ -60,6 +55,11 @@ public final class GameService extends Service {
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(ThreadUtil
.create("GameService"));
/**
* The {@link MessageHandlerChainSet}.
*/
private MessageHandlerChainSet handlers;
/**
* The {@link ClientSynchronizer}.
*/
@@ -90,30 +90,28 @@ public final class GameService extends Service {
/**
* Gets the MessageHandlerChainSet
*
* @return The set of MessageHandlerChain's.
* @return The MessageHandlerChainSet.
*/
public MessageHandlerChainSet getMessageHandlerChainSet() {
return messageHandlerChainSet;
return handlers;
}
/**
* Called every pulse.
*/
public void pulse() {
synchronized (this) {
finalizeUnregisters();
public synchronized void pulse() {
finalizeUnregistrations();
MobRepository<Player> players = world.getPlayerRepository();
for (Player player : players) {
GameSession session = player.getSession();
if (session != null) {
session.handlePendingMessages(messageHandlerChainSet);
}
MobRepository<Player> players = world.getPlayerRepository();
for (Player player : players) {
GameSession session = player.getSession();
if (session != null) {
session.handlePendingMessages(handlers);
}
world.pulse();
synchronizer.synchronize(players, world.getNpcRepository());
}
world.pulse();
synchronizer.synchronize(players, world.getNpcRepository());
}
/**
@@ -123,18 +121,16 @@ public final class GameService extends Service {
* @param session The {@link GameSession} of the Player.
* @return A {@link RegistrationStatus}.
*/
public RegistrationStatus registerPlayer(Player player, GameSession session) {
synchronized (this) {
RegistrationStatus status = world.register(player);
if (status == RegistrationStatus.OK) {
player.setSession(session);
public synchronized RegistrationStatus registerPlayer(Player player, GameSession session) {
RegistrationStatus status = world.register(player);
if (status == RegistrationStatus.OK) {
player.setSession(session);
Region region = world.getRegionRepository().get(player.getPosition().getRegionCoordinates());
region.addEntity(player);
}
return status;
Region region = world.getRegionRepository().fromPosition(player.getPosition());
region.addEntity(player);
}
return status;
}
/**
@@ -165,7 +161,7 @@ public final class GameService extends Service {
/**
* Finalizes the unregistration of Player's queued to be unregistered.
*/
private void finalizeUnregisters() {
private void finalizeUnregistrations() {
LoginService loginService = context.getLoginService();
for (int count = 0; count < UNREGISTERS_PER_CYCLE; count++) {
@@ -188,7 +184,7 @@ public final class GameService extends Service {
private void init() throws IOException, SAXException, ReflectiveOperationException {
try (InputStream input = new FileInputStream("data/messages.xml")) {
MessageHandlerChainSetParser chainSetParser = new MessageHandlerChainSetParser(input);
messageHandlerChainSet = chainSetParser.parse(world);
handlers = chainSetParser.parse(world);
}
try (InputStream input = new FileInputStream("data/synchronizer.xml")) {
@@ -67,9 +67,10 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
int oldLocalPlayers = localPlayers.size();
List<SynchronizationSegment> segments = new ArrayList<>();
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext();) {
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext(); ) {
Player other = it.next();
if (!other.isActive() || other.isTeleporting() || other.getPosition().getLongestDelta(player.getPosition()) > player.getViewingDistance() || !other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance())) {
if (removePlayer(other)) {
it.remove();
segments.add(new RemoveMobSegment());
} else {
@@ -103,8 +104,27 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
}
}
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, player.getPosition(), regionChanged, segment, oldLocalPlayers, segments);
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, player.getPosition(),
regionChanged, segment, oldLocalPlayers, segments);
player.send(message);
}
/**
* Returns whether or not the specified {@link Player} should be removed.
*
* @param other The Player being tested.
* @return {@code true} iff the specified Player should be removed.
*/
private boolean removePlayer(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);
}
}
@@ -66,7 +66,8 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
private final Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots;
/**
* The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain the updates for a Region a Player can
* The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain the updates for a Region a Player
* can
* already view.
*/
private final Map<RegionCoordinates, List<RegionUpdateMessage>> updates;
@@ -78,7 +79,8 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
* @param updates The {@link Map} containing {@link Region} updates.
* @param snapshots The Map containing Region snapshots.
*/
public PrePlayerSynchronizationTask(Player player, Map<RegionCoordinates, List<RegionUpdateMessage>> updates, Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots) {
public PrePlayerSynchronizationTask(Player player, Map<RegionCoordinates, List<RegionUpdateMessage>> updates,
Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots) {
this.player = player;
this.updates = updates;
this.snapshots = snapshots;
@@ -156,6 +158,22 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
return coordinates;
}
/**
* Checks if a region update is required.
*
* @return {@code true} if a Region update is required, {@code false} if not.
*/
private boolean isRegionUpdateRequired() {
Position current = player.getPosition();
Position last = player.getLastKnownRegion();
int deltaX = current.getLocalX(last);
int deltaY = current.getLocalY(last);
return deltaX <= Position.MAX_DISTANCE || deltaX >= VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1
|| deltaY <= Position.MAX_DISTANCE || deltaY >= VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1;
}
/**
* Gets the {@link List} of {@link GroupedRegionUpdateMessage}s.
*
@@ -181,31 +199,13 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
player.send(new ClearRegionMessage(position, coordinates));
}
Optional<GroupedRegionUpdateMessage> message = toUpdateMessage(local, player.getLastKnownRegion(), coordinates, repository);
if (message.isPresent()) {
messages.add(message.get());
}
toUpdateMessage(local, player.getLastKnownRegion(), coordinates, repository).ifPresent(messages::add);
}
}
messages.forEach(player::send);
}
/**
* Checks if a region update is required.
*
* @return {@code true} if a Region update is required, {@code false} if not.
*/
private boolean isRegionUpdateRequired() {
Position current = player.getPosition();
Position last = player.getLastKnownRegion();
int deltaX = current.getLocalX(last);
int deltaY = current.getLocalY(last);
return deltaX <= Position.MAX_DISTANCE || deltaX >= VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1 || deltaY <= Position.MAX_DISTANCE || deltaY >= VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1;
}
/**
* Creates a {@link GroupedRegionUpdateMessage} using the specified {@link RegionUpdateMode}, returning
* {@link Optional#empty()} if no update message is required.
@@ -216,7 +216,8 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask {
* @param repository The {@link RegionRepository} containing the Regions.
* @return The Optional containing the GroupedRegionUpdateMessage.
*/
private Optional<GroupedRegionUpdateMessage> toUpdateMessage(RegionUpdateMode mode, Position lastKnownRegion, RegionCoordinates coordinates, RegionRepository repository) {
private Optional<GroupedRegionUpdateMessage> toUpdateMessage(RegionUpdateMode mode, Position lastKnownRegion,
RegionCoordinates coordinates, RegionRepository repository) {
List<RegionUpdateMessage> messages;
/*