From 7cf31a28886955d0a5da10f56c12c6d6c841117e Mon Sep 17 00:00:00 2001 From: Major- Date: Sun, 22 Mar 2015 11:07:24 +0000 Subject: [PATCH] Add half-done grouped region frame stuff so Ryley can take a look. This is in master because I'm an idiot. --- data/plugins/navigation/door/constants.rb | 15 +- data/plugins/navigation/door/door.rb | 18 +- data/plugins/navigation/door/util.rb | 64 ++--- .../apollo/fs/decoder/GameObjectDecoder.java | 14 +- src/org/apollo/fs/decoder/MapFileDecoder.java | 2 +- src/org/apollo/game/GameService.java | 14 +- .../impl/ObjectActionVerificationHandler.java | 5 +- .../handler/impl/WalkMessageHandler.java | 9 +- .../game/message/impl/ClearRegionMessage.java | 53 ++++ .../impl/GroupedRegionUpdateMessage.java | 71 +++++ .../game/message/impl/PositionMessage.java | 53 ---- .../message/impl/RegionUpdateMessage.java | 42 +++ .../message/impl/RemoveObjectMessage.java | 37 ++- .../message/impl/RemoveTileItemMessage.java | 49 +++- .../game/message/impl/SendObjectMessage.java | 40 ++- ...ge.java => SendPublicTileItemMessage.java} | 62 +++-- ...mMessage.java => SendTileItemMessage.java} | 36 ++- .../message/impl/SetUpdatedRegionMessage.java | 53 ++++ .../message/impl/UpdateTileItemMessage.java | 24 +- src/org/apollo/game/model/Item.java | 16 ++ src/org/apollo/game/model/World.java | 53 ++-- .../game/model/area/EntityUpdateType.java | 20 ++ src/org/apollo/game/model/area/Region.java | 138 +++++++--- .../game/model/area/RegionCoordinates.java | 24 +- .../game/model/area/RegionListener.java | 4 +- .../game/model/area/RegionOperation.java | 25 -- .../model/area/collision/CollisionMatrix.java | 41 ++- .../area/update/ItemUpdateOperation.java | 38 +++ .../area/update/ObjectUpdateOperation.java | 38 +++ .../model/area/update/UpdateOperation.java | 101 ++++++++ .../game/model/area/update/package-info.java | 4 + .../game/model/def/ObjectDefinition.java | 2 +- src/org/apollo/game/model/entity/Entity.java | 46 +++- .../apollo/game/model/entity/GroundItem.java | 107 ++++++++ src/org/apollo/game/model/entity/Mob.java | 50 ++-- src/org/apollo/game/model/entity/Npc.java | 13 - src/org/apollo/game/model/entity/Player.java | 58 ++--- .../model/entity/obj/DynamicGameObject.java | 133 ++++++++++ .../model/entity/{ => obj}/GameObject.java | 42 ++- .../{area => entity}/obj/ObjectGroup.java | 4 +- .../{area => entity}/obj/ObjectType.java | 2 +- .../model/entity/obj/StaticGameObject.java | 41 +++ .../{area => entity}/obj/package-info.java | 2 +- .../game/sync/ParallelClientSynchronizer.java | 10 +- .../sync/SequentialClientSynchronizer.java | 11 +- .../task/PrePlayerSynchronizationTask.java | 244 ++++++++++++++++-- .../r317/AddGlobalTileItemMessageEncoder.java | 8 +- .../r317/AddTileItemMessageEncoder.java | 8 +- .../r317/ClearRegionMessageEncoder.java | 29 +++ .../GroupedRegionUpdateMessageEncoder.java | 58 +++++ .../release/r317/PositionMessageEncoder.java | 29 --- .../apollo/net/release/r317/Release317.java | 19 +- .../r317/RemoveObjectMessageEncoder.java | 3 + .../r317/SetUpdatedRegionMessageEncoder.java | 29 +++ .../r377/AddGlobalTileItemMessageEncoder.java | 8 +- .../r377/AddTileItemMessageEncoder.java | 8 +- .../r377/ClearRegionMessageEncoder.java | 29 +++ .../GroupedRegionUpdateMessageEncoder.java | 63 +++++ .../release/r377/PositionMessageEncoder.java | 30 --- .../apollo/net/release/r377/Release377.java | 43 ++- .../r377/SetUpdatedRegionMessageEncoder.java | 30 +++ src/org/apollo/util/DatabaseUtil.java | 72 ------ 62 files changed, 1818 insertions(+), 576 deletions(-) create mode 100644 src/org/apollo/game/message/impl/ClearRegionMessage.java create mode 100644 src/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java delete mode 100644 src/org/apollo/game/message/impl/PositionMessage.java create mode 100644 src/org/apollo/game/message/impl/RegionUpdateMessage.java rename src/org/apollo/game/message/impl/{AddGlobalTileItemMessage.java => SendPublicTileItemMessage.java} (64%) rename src/org/apollo/game/message/impl/{AddTileItemMessage.java => SendTileItemMessage.java} (61%) create mode 100644 src/org/apollo/game/message/impl/SetUpdatedRegionMessage.java create mode 100644 src/org/apollo/game/model/area/EntityUpdateType.java delete mode 100644 src/org/apollo/game/model/area/RegionOperation.java create mode 100644 src/org/apollo/game/model/area/update/ItemUpdateOperation.java create mode 100644 src/org/apollo/game/model/area/update/ObjectUpdateOperation.java create mode 100644 src/org/apollo/game/model/area/update/UpdateOperation.java create mode 100644 src/org/apollo/game/model/area/update/package-info.java create mode 100644 src/org/apollo/game/model/entity/GroundItem.java create mode 100644 src/org/apollo/game/model/entity/obj/DynamicGameObject.java rename src/org/apollo/game/model/entity/{ => obj}/GameObject.java (57%) rename src/org/apollo/game/model/{area => entity}/obj/ObjectGroup.java (94%) rename src/org/apollo/game/model/{area => entity}/obj/ObjectType.java (97%) create mode 100644 src/org/apollo/game/model/entity/obj/StaticGameObject.java rename src/org/apollo/game/model/{area => entity}/obj/package-info.java (52%) create mode 100644 src/org/apollo/net/release/r317/ClearRegionMessageEncoder.java create mode 100644 src/org/apollo/net/release/r317/GroupedRegionUpdateMessageEncoder.java delete mode 100644 src/org/apollo/net/release/r317/PositionMessageEncoder.java create mode 100644 src/org/apollo/net/release/r317/SetUpdatedRegionMessageEncoder.java create mode 100644 src/org/apollo/net/release/r377/ClearRegionMessageEncoder.java create mode 100644 src/org/apollo/net/release/r377/GroupedRegionUpdateMessageEncoder.java delete mode 100644 src/org/apollo/net/release/r377/PositionMessageEncoder.java create mode 100644 src/org/apollo/net/release/r377/SetUpdatedRegionMessageEncoder.java delete mode 100644 src/org/apollo/util/DatabaseUtil.java diff --git a/data/plugins/navigation/door/constants.rb b/data/plugins/navigation/door/constants.rb index ec923218..7c9e0e88 100644 --- a/data/plugins/navigation/door/constants.rb +++ b/data/plugins/navigation/door/constants.rb @@ -1,6 +1,8 @@ -java_import 'org.apollo.game.model.Direction' +require 'set' +# Contains door-related constants. module DoorConstants + # TODO: GameObjectOrientation enumeration in Apollo's core? module Orientation WEST = 0 @@ -9,17 +11,18 @@ module DoorConstants SOUTH = 3 end - # The object size of a door. + # The size of a door object. DOOR_SIZE = 1 # Door object ids that have a hinge on the left side. - LEFT_SIDE_HINGE_DOOR_IDS = [1516, 1536, 1533] + LEFT_HINGE_DOORS = Set.new [ 1516, 1536, 1533 ] # Door object ids that have a hinge on the right side. - RIGHT_SIDE_HINGE_DOOR_IDS = [1519, 1530, 4465, 4467, 3014, 3017, 3018, 3019] + RIGHT_HINGE_DOORS = Set.new [ 1519, 1530, 4465, 4467, 3014, 3017, 3018, 3019 ] - # A map of orientations that a door will translate to when opened. + # The hash of orientations that a door will translate to when opened. ORIENTATIONS = { + # Orientations for doors that have a hinge on the left side. :left_side_hinge => { Orientation::NORTH => Orientation::WEST, @@ -27,6 +30,7 @@ module DoorConstants Orientation::WEST => Orientation::SOUTH, Orientation::EAST => Orientation::NORTH }, + # Orientations for doors that have a hinge on the right side. :right_side_hinge => { Orientation::NORTH => Orientation::EAST, @@ -35,4 +39,5 @@ module DoorConstants Orientation::EAST => Orientation::SOUTH } } + end \ No newline at end of file diff --git a/data/plugins/navigation/door/door.rb b/data/plugins/navigation/door/door.rb index 8d27002d..cd00e50e 100644 --- a/data/plugins/navigation/door/door.rb +++ b/data/plugins/navigation/door/door.rb @@ -4,28 +4,30 @@ java_import 'org.apollo.game.action.DistancedAction' class OpenDoorAction < DistancedAction include DoorConstants - attr_reader :door_object + attr_reader :door - def initialize(mob, door_object) - super(0, true, mob, door_object.position, DOOR_SIZE) - @door_object = door_object + def initialize(mob, door) + super(0, true, mob, door.position, DOOR_SIZE) + @door = door end def executeAction - mob.turn_to @door_object.position - DoorUtil::toggle(@door_object, mob) + mob.turn_to(@door.position) + DoorUtil::toggle(@door, mob) stop end def equals(other) return (get_class == other.get_class && @door_object == other.door_object) end + end # Message handler for opening and closing doors. on :message, :first_object_action do |ctx, player, message| if DoorUtil::is_door?(message.id) - door_object = DoorUtil::get_door_object(message.position, message.id) - player.start_action(OpenDoorAction.new(player, door_object)) unless door_object.nil? + puts "Player: #{player.position}, door: #{message.position}" + door = DoorUtil::get_door_object(message.position, message.id) + player.start_action(OpenDoorAction.new(player, door)) unless door.nil? end end \ No newline at end of file diff --git a/data/plugins/navigation/door/util.rb b/data/plugins/navigation/door/util.rb index 1948cba9..31b23e1e 100644 --- a/data/plugins/navigation/door/util.rb +++ b/data/plugins/navigation/door/util.rb @@ -1,20 +1,22 @@ -java_import 'org.apollo.game.model.entity.GameObject' -java_import 'org.apollo.game.model.entity.Entity' -java_import 'org.apollo.game.model.Position' -java_import 'org.apollo.game.message.impl.PositionMessage' -java_import 'org.apollo.game.message.impl.RemoveObjectMessage' -java_import 'org.apollo.game.message.impl.SendObjectMessage' +require 'java' +java_import 'org.apollo.game.model.Position' +java_import 'org.apollo.game.model.area.Region' +java_import 'org.apollo.game.model.entity.Entity' +java_import 'org.apollo.game.model.entity.obj.DynamicGameObject' + +# Contains door-related utility methods. module DoorUtil include DoorConstants # A hash containing currently toggled door objects mapped to the original door objects. - TOGGLED_DOOR_REPOSITORY = {} + TOGGLED_DOORS = {} # Translates a door's position in the direction of its orientation. def self.translate_door_position(door) position = door.position orientation = door.orientation + case orientation when Orientation::WEST return Position.new(position.x - 1, position.y, position.height) @@ -25,6 +27,7 @@ module DoorUtil when Orientation::SOUTH return Position.new(position.x, position.y - 1, position.height) end + raise 'Invalid orientation for door!' end @@ -32,68 +35,49 @@ module DoorUtil def self.translate_door_orientation(door) object_id = door.id orientation = door.orientation - if RIGHT_SIDE_HINGE_DOOR_IDS.include?(object_id) + + if RIGHT_HINGE_DOORS.include?(object_id) return ORIENTATIONS[:right_side_hinge][orientation] - elsif LEFT_SIDE_HINGE_DOOR_IDS.include?(object_id) + elsif LEFT_HINGE_DOORS.include?(object_id) return ORIENTATIONS[:left_side_hinge][orientation] end - raise 'Given object was not registered as a door!' - end - # Replaces a door object for a given player. - # TODO: This is temporary. - def self.replace_door(player, original, new) - player.send PositionMessage.new(player.last_known_region, original.position) - player.send RemoveObjectMessage.new(original) - player.send PositionMessage.new(player.last_known_region, new.position) - player.send SendObjectMessage.new(new) + raise 'Given object was not registered as a door.' end # Toggles the given door. def self.toggle(door, player) - # First, we remove the door we're toggling (or un-toggling) from the game world. position = door.position region = $world.region_repository.from_position(position) - region.remove_entity door + region.remove_entity(door) - # Have we toggled this door already? - if TOGGLED_DOOR_REPOSITORY.include?(door) - # If we have, we get our original door. This also deletes the entry from our repository. - original_door = TOGGLED_DOOR_REPOSITORY.delete(door) + if TOGGLED_DOORS.include?(door) + original_door = TOGGLED_DOORS.delete(door) - # Now we add our new door to the game world. original_region = $world.region_repository.from_position(original_door.position) - original_region.add_entity original_door - - # TODO: This and the 'player' parameter are temporary. We still need to synchronize objects for local players. - replace_door player, door, original_door + original_region.add_entity(original_door) else - # If not, we get the translated orientation and position for this door, and create a new game object. toggled_position = translate_door_position(door) toggled_orientation = translate_door_orientation(door) - toggled_door = GameObject.new(door.id, toggled_position, door.type, toggled_orientation) + toggled_door = DynamicGameObject.createPublic(door.id, toggled_position, door.type, toggled_orientation) - # Now we add our new door to the game world. toggled_region = $world.region_repository.from_position(toggled_position) - toggled_region.add_entity toggled_door + toggled_region.add_entity(toggled_door) - # Insert our toggled door in the repository. - TOGGLED_DOOR_REPOSITORY[toggled_door] = door - - # TODO: This and the 'player' parameter are temporary. We still need to synchronize objects for local players. - replace_door player, door, toggled_door + TOGGLED_DOORS[toggled_door] = door end end # Gets the door object at the given position, if it exists. def self.get_door_object(position, object_id) - game_objects = $world.region_repository.from_position(position).get_entities(position, EntityType::GAME_OBJECT) + game_objects = $world.region_repository.from_position(position).get_entities(position, EntityType::DYNAMIC_OBJECT, EntityType::STATIC_OBJECT) game_objects.each { |game_object| return game_object if game_object.id == object_id } return nil end # Checks if the given game object id is a door. def self.is_door?(object_id) - RIGHT_SIDE_HINGE_DOOR_IDS.include?(object_id) || LEFT_SIDE_HINGE_DOOR_IDS.include?(object_id) + RIGHT_HINGE_DOORS.include?(object_id) || LEFT_HINGE_DOORS.include?(object_id) end + end \ No newline at end of file diff --git a/src/org/apollo/fs/decoder/GameObjectDecoder.java b/src/org/apollo/fs/decoder/GameObjectDecoder.java index 7eae0751..b0cf8148 100644 --- a/src/org/apollo/fs/decoder/GameObjectDecoder.java +++ b/src/org/apollo/fs/decoder/GameObjectDecoder.java @@ -14,9 +14,10 @@ import org.apollo.game.model.Position; import org.apollo.game.model.area.Region; import org.apollo.game.model.area.RegionRepository; import org.apollo.game.model.area.collision.CollisionMatrix; -import org.apollo.game.model.area.obj.ObjectType; import org.apollo.game.model.def.ObjectDefinition; -import org.apollo.game.model.entity.GameObject; +import org.apollo.game.model.entity.obj.GameObject; +import org.apollo.game.model.entity.obj.ObjectType; +import org.apollo.game.model.entity.obj.StaticGameObject; import org.apollo.util.BufferUtil; import org.apollo.util.CompressionUtil; @@ -132,7 +133,7 @@ public final class GameObjectDecoder { if (block) { for (int dx = 0; dx < definition.getWidth(); dx++) { for (int dy = 0; dy < definition.getLength(); dy++) { - int localX = (x % Region.REGION_SIZE) + dx, localY = (y % Region.REGION_SIZE) + dy; + int localX = (x % Region.SIZE) + dx, localY = (y % Region.SIZE) + dy; if (localX > 7 || localY > 7) { int nextLocalX = localX > 7 ? x + localX - 7 : x + localX; @@ -140,8 +141,7 @@ public final class GameObjectDecoder { Position nextPosition = new Position(nextLocalX, nextLocalY); Region next = regions.fromPosition(nextPosition); - int nextX = (nextPosition.getX() % Region.REGION_SIZE) + dx, nextY = (nextPosition.getY() % Region.REGION_SIZE) - + dy; + int nextX = (nextPosition.getX() % Region.SIZE) + dx, nextY = (nextPosition.getY() % Region.SIZE) + dy; if (nextX > 7) nextX -= 7; if (nextY > 7) @@ -181,7 +181,7 @@ public final class GameObjectDecoder { } if (block) { - int localX = (x % Region.REGION_SIZE), localY = (y % Region.REGION_SIZE); + int localX = (x % Region.SIZE), localY = (y % Region.SIZE); current.block(localX, localY); } } @@ -215,7 +215,7 @@ public final class GameObjectDecoder { int orientation = attributes & 0x3; Position position = new Position(x + localX, y + localY, height); - GameObject object = new GameObject(id, position, type, orientation); + GameObject object = new StaticGameObject(id, position, type, orientation); objects.add(object); block(object, position); diff --git a/src/org/apollo/fs/decoder/MapFileDecoder.java b/src/org/apollo/fs/decoder/MapFileDecoder.java index 8cb62ad0..56be7d18 100644 --- a/src/org/apollo/fs/decoder/MapFileDecoder.java +++ b/src/org/apollo/fs/decoder/MapFileDecoder.java @@ -21,7 +21,7 @@ public final class MapFileDecoder { /** * The width (and length) of a map file, in tiles. */ - public static final int MAP_FILE_WIDTH = Region.REGION_SIZE * Region.REGION_SIZE; + public static final int MAP_FILE_WIDTH = Region.SIZE * Region.SIZE; /** * The file id of the versions archive. diff --git a/src/org/apollo/game/GameService.java b/src/org/apollo/game/GameService.java index bb5d39b1..49fd689d 100644 --- a/src/org/apollo/game/GameService.java +++ b/src/org/apollo/game/GameService.java @@ -13,6 +13,7 @@ import org.apollo.Service; import org.apollo.game.message.handler.MessageHandlerChainGroup; import org.apollo.game.model.World; import org.apollo.game.model.World.RegistrationStatus; +import org.apollo.game.model.area.Region; import org.apollo.game.model.entity.Player; import org.apollo.game.sync.ClientSynchronizer; import org.apollo.io.MessageHandlerChainParser; @@ -49,7 +50,8 @@ public final class GameService extends Service { /** * The scheduled executor service. */ - private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("GameService")); + private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory( + "GameService")); /** * The {@link ClientSynchronizer}. @@ -154,10 +156,15 @@ public final class GameService extends Service { * @return A {@link RegistrationStatus}. */ public RegistrationStatus registerPlayer(Player player, GameSession session) { + World world = World.getWorld(); + synchronized (this) { - RegistrationStatus status = World.getWorld().register(player); + 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; @@ -169,7 +176,8 @@ public final class GameService extends Service { */ @Override public void start() { - scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, GameConstants.PULSE_DELAY, TimeUnit.MILLISECONDS); + scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, GameConstants.PULSE_DELAY, + TimeUnit.MILLISECONDS); } /** diff --git a/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java b/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java index 9674d496..e8899b45 100644 --- a/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java +++ b/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java @@ -12,8 +12,8 @@ import org.apollo.game.model.area.Region; import org.apollo.game.model.area.RegionRepository; import org.apollo.game.model.def.ObjectDefinition; import org.apollo.game.model.entity.Entity.EntityType; -import org.apollo.game.model.entity.GameObject; import org.apollo.game.model.entity.Player; +import org.apollo.game.model.entity.obj.GameObject; /** * A verification {@link MessageHandler} for the {@link ObjectActionMessage}. @@ -34,10 +34,11 @@ public final class ObjectActionVerificationHandler extends MessageHandler objects = region.getEntities(position, EntityType.GAME_OBJECT); + Set objects = region.getEntities(position, EntityType.STATIC_OBJECT, EntityType.DYNAMIC_OBJECT); if (!player.getPosition().isWithinDistance(position, 15) || !containsObject(id, objects)) { ctx.breakHandlerChain(); diff --git a/src/org/apollo/game/message/handler/impl/WalkMessageHandler.java b/src/org/apollo/game/message/handler/impl/WalkMessageHandler.java index d36728aa..f7a4ecea 100644 --- a/src/org/apollo/game/message/handler/impl/WalkMessageHandler.java +++ b/src/org/apollo/game/message/handler/impl/WalkMessageHandler.java @@ -19,9 +19,9 @@ public final class WalkMessageHandler extends MessageHandler { WalkingQueue queue = player.getWalkingQueue(); Position[] steps = message.getSteps(); - for (int i = 0; i < steps.length; i++) { - Position step = steps[i]; - if (i == 0) { + for (int index = 0; index < steps.length; index++) { + Position step = steps[index]; + if (index == 0) { if (!queue.addFirstStep(step)) { return; // ignore packet } @@ -31,16 +31,15 @@ public final class WalkMessageHandler extends MessageHandler { } queue.setRunningQueue(message.isRunning() || player.isRunning()); + player.getInterfaceSet().close(); if (queue.size() > 0) { player.stopAction(); } - player.getInterfaceSet().close(); if (player.getInteractingMob() != null) { player.resetInteractingMob(); } - } } \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/ClearRegionMessage.java b/src/org/apollo/game/message/impl/ClearRegionMessage.java new file mode 100644 index 00000000..f37c127e --- /dev/null +++ b/src/org/apollo/game/message/impl/ClearRegionMessage.java @@ -0,0 +1,53 @@ +package org.apollo.game.message.impl; + +import org.apollo.game.message.Message; +import org.apollo.game.model.Position; +import org.apollo.game.model.area.RegionCoordinates; + +/** + * A {@link Message} sent to the client to remove all spawned objects and items from a Region. + * + * @author Major + */ +public final class ClearRegionMessage extends Message { + + /** + * The Position of the Player. + */ + private final Position player; + + /** + * The Position in the Region being cleared. + */ + private final Position region; + + /** + * Creates the ClearRegionMessage. + * + * @param player The {@link Position} of the Player this {@link Message} is being sent to. + * @param region The {@link RegionCoordinates} of the Region being cleared. + */ + public ClearRegionMessage(Position player, RegionCoordinates region) { + this.player = player; + this.region = new Position(region.getAbsoluteX(), region.getAbsoluteY()); + } + + /** + * Gets the {@link Position} of the Player this {@link Message} is being sent to.. + * + * @return The Position. + */ + public Position getPlayerPosition() { + return player; + } + + /** + * Gets the {@link Position} of the Region being cleared. + * + * @return The Position. + */ + public Position getRegionPosition() { + return region; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java b/src/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java new file mode 100644 index 00000000..a605c9ac --- /dev/null +++ b/src/org/apollo/game/message/impl/GroupedRegionUpdateMessage.java @@ -0,0 +1,71 @@ +package org.apollo.game.message.impl; + +import java.util.List; + +import org.apollo.game.message.Message; +import org.apollo.game.model.Position; +import org.apollo.game.model.area.RegionCoordinates; + +/** + * A {@link Message} sent to the client that contains multiple + * + * @author Major + */ +public final class GroupedRegionUpdateMessage extends Message { + + /** + * The Position of the Player. + */ + private final Position player; + + /** + * The Position of the Region being updated. + */ + private final Position region; + + /** + * The List of RegionUpdateMessages to be sent. + */ + private final List messages; + + /** + * Creates the GroupedRegionUpdateMessage. + * + * @param player The {@link Position} of the Player. + * @param coordinates The {@link RegionCoordinates} of the Region being updated. + * @param messages The {@link List} of {@link RegionUpdateMessage}s. + */ + public GroupedRegionUpdateMessage(Position player, RegionCoordinates coordinates, List messages) { + this.player = player; + this.region = new Position(coordinates.getAbsoluteX(), coordinates.getAbsoluteY()); + this.messages = messages; + } + + /** + * Gets the {@link Position} of the Player. + * + * @return The Position. + */ + public Position getPlayerPosition() { + return player; + } + + /** + * Gets the {@link List} of {@link RegionUpdateMessage}s. + * + * @return The Collection. + */ + public List getMessages() { + return messages; + } + + /** + * Gets the {@link Position} of the Region these updates affect. + * + * @return The Position. + */ + public Position getRegionPosition() { + return region; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/PositionMessage.java b/src/org/apollo/game/message/impl/PositionMessage.java deleted file mode 100644 index 3995d84b..00000000 --- a/src/org/apollo/game/message/impl/PositionMessage.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.apollo.game.message.impl; - -import org.apollo.game.message.Message; -import org.apollo.game.model.Position; - -/** - * A {@link Message} sent to the client to focus on a specific {@link Position} (on which an action should be - * performed). - * - * @author Chris Fletcher - */ -public final class PositionMessage extends Message { - - /** - * The base position. - */ - private final Position base; - - /** - * The target position. - */ - private final Position position; - - /** - * Creates a new position message. - * - * @param base The base from which the position is being focused on. - * @param position The position to focus on. - */ - public PositionMessage(Position base, Position position) { - this.base = base; - this.position = position; - } - - /** - * Gets the base position. - * - * @return The position. - */ - public Position getBase() { - return base; - } - - /** - * Gets the position to focus on. - * - * @return The target position. - */ - public Position getPosition() { - return position; - } - -} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/RegionUpdateMessage.java b/src/org/apollo/game/message/impl/RegionUpdateMessage.java new file mode 100644 index 00000000..33a631c1 --- /dev/null +++ b/src/org/apollo/game/message/impl/RegionUpdateMessage.java @@ -0,0 +1,42 @@ +package org.apollo.game.message.impl; + +import org.apollo.game.message.Message; + +/** + * A {@link Message} that can be grouped with other Messages as part of a {@link GroupedRegionUpdateMessage}. + *

+ * This is a utility class for Messages that can be grouped, as is not encoded directly. + * + * @author Major + */ +public abstract class RegionUpdateMessage extends Message implements Comparable { + + /** + * The integer value indicating this RegionUpdateMessage is a high-priority message. + */ + protected static final int HIGH_PRIORITY = 0; + + /** + * The integer value indicating this RegionUpdateMessage is a low-priority message. + */ + protected static final int LOW_PRIORITY = 1; + + @Override + public final int compareTo(RegionUpdateMessage other) { + return Integer.compare(priority(), other.priority()); + } + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); + + /** + * Gets the priority of this RegionUpdateMessage, to use when sorting. + * + * @return The priority. Should be either 1 (low) or 0 (high). + */ + public abstract int priority(); + +} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/RemoveObjectMessage.java b/src/org/apollo/game/message/impl/RemoveObjectMessage.java index 59885183..f475014f 100644 --- a/src/org/apollo/game/message/impl/RemoveObjectMessage.java +++ b/src/org/apollo/game/message/impl/RemoveObjectMessage.java @@ -1,14 +1,14 @@ package org.apollo.game.message.impl; import org.apollo.game.message.Message; -import org.apollo.game.model.entity.GameObject; +import org.apollo.game.model.entity.obj.GameObject; /** * A {@link Message} sent to the client to remove an object from a tile. * * @author Major */ -public final class RemoveObjectMessage extends Message { +public final class RemoveObjectMessage extends RegionUpdateMessage { /** * The orientation of the object. @@ -29,16 +29,7 @@ public final class RemoveObjectMessage extends Message { * Creates the RemoveObjectMessage. * * @param object The {@link GameObject} to send. - */ - public RemoveObjectMessage(GameObject object) { - this(object, 0); - } - - /** - * Creates the RemoveObjectMessage. - * - * @param object The {@link GameObject} to send. - * @param positionOffset The offset of the object's position from the region's central position. + * @param positionOffset The offset of the GameObject's Position from the Region's top-left position. */ public RemoveObjectMessage(GameObject object, int positionOffset) { this.positionOffset = positionOffset; @@ -46,6 +37,16 @@ public final class RemoveObjectMessage extends Message { this.orientation = object.getOrientation(); } + @Override + public boolean equals(Object obj) { + if (obj instanceof RemoveObjectMessage) { + RemoveObjectMessage other = (RemoveObjectMessage) obj; + return type == other.type && orientation == other.orientation && positionOffset == other.positionOffset; + } + + return false; + } + /** * Gets the orientation of the object. * @@ -73,4 +74,16 @@ public final class RemoveObjectMessage extends Message { return type; } + @Override + public int hashCode() { + final int prime = 31; + int result = prime * positionOffset + orientation; + return prime * result + type; + } + + @Override + public int priority() { + return HIGH_PRIORITY; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/RemoveTileItemMessage.java b/src/org/apollo/game/message/impl/RemoveTileItemMessage.java index c92f7f00..1117aa11 100644 --- a/src/org/apollo/game/message/impl/RemoveTileItemMessage.java +++ b/src/org/apollo/game/message/impl/RemoveTileItemMessage.java @@ -1,16 +1,17 @@ package org.apollo.game.message.impl; import org.apollo.game.message.Message; +import org.apollo.game.model.Item; /** * A {@link Message} sent to the client to remove an item from a tile. * * @author Major */ -public final class RemoveTileItemMessage extends Message { +public final class RemoveTileItemMessage extends RegionUpdateMessage { /** - * The item. + * The id of the Item to remove. */ private final int id; @@ -20,18 +21,9 @@ public final class RemoveTileItemMessage extends Message { private final int positionOffset; /** - * Creates a remove tile item message. + * Creates the RemoveTileItemMessage. * - * @param id The id of the item to remove. - */ - public RemoveTileItemMessage(int id) { - this(id, 0); - } - - /** - * Creates a remove tile item message. - * - * @param id The id of the item to remove. + * @param id The id of the {@link Item} to remove. * @param positionOffset The offset from the 'base' position. */ public RemoveTileItemMessage(int id, int positionOffset) { @@ -39,6 +31,26 @@ public final class RemoveTileItemMessage extends Message { this.positionOffset = positionOffset; } + /** + * Creates the RemoveTileItemMessage. + * + * @param item The {@link Item} to remove. + * @param positionOffset The offset from the 'base' position. + */ + public RemoveTileItemMessage(Item item, int positionOffset) { + this(item.getId(), positionOffset); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof RemoveTileItemMessage) { + RemoveTileItemMessage other = (RemoveTileItemMessage) obj; + return id == other.id && positionOffset == other.positionOffset; + } + + return false; + } + /** * Gets the id of the item to remove. * @@ -57,4 +69,15 @@ public final class RemoveTileItemMessage extends Message { return positionOffset; } + @Override + public int hashCode() { + final int prime = 31; + return prime * id + positionOffset; + } + + @Override + public int priority() { + return HIGH_PRIORITY; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/SendObjectMessage.java b/src/org/apollo/game/message/impl/SendObjectMessage.java index 8cd3146e..f075a75b 100644 --- a/src/org/apollo/game/message/impl/SendObjectMessage.java +++ b/src/org/apollo/game/message/impl/SendObjectMessage.java @@ -1,14 +1,14 @@ package org.apollo.game.message.impl; import org.apollo.game.message.Message; -import org.apollo.game.model.entity.GameObject; +import org.apollo.game.model.entity.obj.GameObject; /** * A {@link Message} sent to the client to spawn an object. * * @author Major */ -public final class SendObjectMessage extends Message { +public final class SendObjectMessage extends RegionUpdateMessage { /** * The id of the object. @@ -30,15 +30,6 @@ public final class SendObjectMessage extends Message { */ private final int type; - /** - * Creates the SendObjectMessage. - * - * @param object The {@link GameObject} to send. - */ - public SendObjectMessage(GameObject object) { - this(object, 0); - } - /** * Creates the SendObjectMessage. * @@ -52,6 +43,20 @@ public final class SendObjectMessage extends Message { this.orientation = object.getOrientation(); } + @Override + public boolean equals(Object obj) { + if (obj instanceof SendObjectMessage) { + SendObjectMessage other = (SendObjectMessage) obj; + if (id != other.id || type != other.type) { + return false; + } + + return positionOffset == other.positionOffset && type == other.type; + } + + return false; + } + /** * Gets the id of the object. * @@ -88,4 +93,17 @@ public final class SendObjectMessage extends Message { return type; } + @Override + public int hashCode() { + final int prime = 31; + int result = prime * id + orientation; + result = prime * result + type; + return prime * result + positionOffset; + } + + @Override + public int priority() { + return LOW_PRIORITY; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/AddGlobalTileItemMessage.java b/src/org/apollo/game/message/impl/SendPublicTileItemMessage.java similarity index 64% rename from src/org/apollo/game/message/impl/AddGlobalTileItemMessage.java rename to src/org/apollo/game/message/impl/SendPublicTileItemMessage.java index 1339015e..282fcd80 100644 --- a/src/org/apollo/game/message/impl/AddGlobalTileItemMessage.java +++ b/src/org/apollo/game/message/impl/SendPublicTileItemMessage.java @@ -8,7 +8,12 @@ import org.apollo.game.model.Item; * * @author Major */ -public final class AddGlobalTileItemMessage extends Message { +public final class SendPublicTileItemMessage extends RegionUpdateMessage { + + /** + * The index of the player who dropped the item. + */ + private final int index; /** * The item to add to the tile. @@ -21,40 +26,26 @@ public final class AddGlobalTileItemMessage extends Message { private final int positionOffset; /** - * The index of the player who dropped the item. - */ - private final int index; - - /** - * Creates the add global tile item message. - * - * @param item The item to add to the tile. - * @param index The index of the player who dropped the item. - */ - public AddGlobalTileItemMessage(Item item, int index) { - this(item, index, 0); - } - - /** - * Creates the add global tile item message. + * Creates the SendPublicTileItemMessage. * * @param item The item to add to the tile. * @param index The index of the player who dropped the item. * @param positionOffset The offset from the 'base' position. */ - public AddGlobalTileItemMessage(Item item, int index, int positionOffset) { + public SendPublicTileItemMessage(Item item, int index, int positionOffset) { this.item = item; this.index = index; this.positionOffset = positionOffset; } - /** - * Gets the id of the item. - * - * @return The id. - */ - public int getId() { - return item.getId(); + @Override + public boolean equals(Object obj) { + if (obj instanceof SendPublicTileItemMessage) { + SendPublicTileItemMessage other = (SendPublicTileItemMessage) obj; + return item.equals(other.item) && index == other.index && positionOffset == other.positionOffset; + } + + return false; } /** @@ -66,6 +57,15 @@ public final class AddGlobalTileItemMessage extends Message { return item.getAmount(); } + /** + * Gets the id of the item. + * + * @return The id. + */ + public int getId() { + return item.getId(); + } + /** * Gets the index of the player who dropped the item. * @@ -84,4 +84,16 @@ public final class AddGlobalTileItemMessage extends Message { return positionOffset; } + @Override + public int hashCode() { + final int prime = 31; + int result = item.hashCode() * prime + index; + return result * prime + positionOffset; + } + + @Override + public int priority() { + return LOW_PRIORITY; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/AddTileItemMessage.java b/src/org/apollo/game/message/impl/SendTileItemMessage.java similarity index 61% rename from src/org/apollo/game/message/impl/AddTileItemMessage.java rename to src/org/apollo/game/message/impl/SendTileItemMessage.java index 8eb6a22d..73018849 100644 --- a/src/org/apollo/game/message/impl/AddTileItemMessage.java +++ b/src/org/apollo/game/message/impl/SendTileItemMessage.java @@ -8,7 +8,7 @@ import org.apollo.game.model.Item; * * @author Major */ -public final class AddTileItemMessage extends Message { +public final class SendTileItemMessage extends RegionUpdateMessage { /** * The item to add to the tile. @@ -21,21 +21,12 @@ public final class AddTileItemMessage extends Message { private final int positionOffset; /** - * Creates an add tile item message. - * - * @param item The item to add to the tile. - */ - public AddTileItemMessage(Item item) { - this(item, 0); - } - - /** - * Creates an add tile item message. + * Creates the SendTileItemMessage. * * @param item The item to add to the tile. * @param positionOffset The offset from the 'base' position. */ - public AddTileItemMessage(Item item, int positionOffset) { + public SendTileItemMessage(Item item, int positionOffset) { this.item = item; this.positionOffset = positionOffset; } @@ -67,4 +58,25 @@ public final class AddTileItemMessage extends Message { return positionOffset; } + @Override + public boolean equals(Object obj) { + if (obj instanceof SendTileItemMessage) { + SendTileItemMessage other = (SendTileItemMessage) obj; + return item.equals(other.item) && positionOffset == other.positionOffset; + } + + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + return item.hashCode() * prime + positionOffset; + } + + @Override + public int priority() { + return LOW_PRIORITY; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/SetUpdatedRegionMessage.java b/src/org/apollo/game/message/impl/SetUpdatedRegionMessage.java new file mode 100644 index 00000000..c335aa96 --- /dev/null +++ b/src/org/apollo/game/message/impl/SetUpdatedRegionMessage.java @@ -0,0 +1,53 @@ +package org.apollo.game.message.impl; + +import org.apollo.game.message.Message; +import org.apollo.game.model.Position; +import org.apollo.game.model.area.RegionCoordinates; + +/** + * A {@link Message} sent to the client to set the coordinates of the Region currently being updated. + * + * @author Major + */ +public final class SetUpdatedRegionMessage extends Message { + + /** + * The Position of the Player. + */ + private final Position player; + + /** + * The Position in the Region being cleared. + */ + private final Position region; + + /** + * Creates the SetUpdatedRegionMessage. + * + * @param player The {@link Position} of the Player this {@link Message} is being sent to. + * @param region The {@link RegionCoordinates} of the Region being set. + */ + public SetUpdatedRegionMessage(Position player, RegionCoordinates region) { + this.player = player; + this.region = new Position(region.getAbsoluteX(), region.getAbsoluteY()); + } + + /** + * Gets the {@link Position} of the Player this {@link Message} is being sent to.. + * + * @return The Position. + */ + public Position getPlayerPosition() { + return player; + } + + /** + * Gets the {@link Position} of the Region being cleared. + * + * @return The Position. + */ + public Position getRegionPosition() { + return region; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/UpdateTileItemMessage.java b/src/org/apollo/game/message/impl/UpdateTileItemMessage.java index 6bdc5ab5..566d5161 100644 --- a/src/org/apollo/game/message/impl/UpdateTileItemMessage.java +++ b/src/org/apollo/game/message/impl/UpdateTileItemMessage.java @@ -8,7 +8,7 @@ import org.apollo.game.model.Item; * * @author Major */ -public final class UpdateTileItemMessage extends Message { +public final class UpdateTileItemMessage extends RegionUpdateMessage { /** * The {@link Item}. @@ -48,6 +48,16 @@ public final class UpdateTileItemMessage extends Message { this.positionOffset = positionOffset; } + @Override + public boolean equals(Object obj) { + if (obj instanceof UpdateTileItemMessage) { + UpdateTileItemMessage other = (UpdateTileItemMessage) obj; + return item.equals(other.item) && previousAmount == other.previousAmount && positionOffset == other.positionOffset; + } + + return false; + } + /** * Gets the amount of the item. * @@ -84,4 +94,16 @@ public final class UpdateTileItemMessage extends Message { return previousAmount; } + @Override + public int hashCode() { + final int prime = 31; + int result = item.hashCode() * prime + positionOffset; + return result * prime + previousAmount; + } + + @Override + public int priority() { + return LOW_PRIORITY; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/Item.java b/src/org/apollo/game/model/Item.java index 221ab368..9b51fe28 100644 --- a/src/org/apollo/game/model/Item.java +++ b/src/org/apollo/game/model/Item.java @@ -50,6 +50,16 @@ public final class Item { this.definition = ItemDefinition.lookup(id); } + @Override + public boolean equals(Object obj) { + if (obj instanceof Item) { + Item other = (Item) obj; + return id == other.id && amount == other.amount; + } + + return false; + } + /** * Gets the amount. * @@ -77,6 +87,12 @@ public final class Item { return id; } + @Override + public int hashCode() { + final int prime = 31; + return amount * prime + id; + } + @Override public String toString() { return MoreObjects.toStringHelper(this).add("id", id).add("amount", amount).toString(); diff --git a/src/org/apollo/game/model/World.java b/src/org/apollo/game/model/World.java index c27a9064..2284286a 100644 --- a/src/org/apollo/game/model/World.java +++ b/src/org/apollo/game/model/World.java @@ -22,7 +22,8 @@ import org.apollo.game.model.def.ItemDefinition; import org.apollo.game.model.def.NpcDefinition; import org.apollo.game.model.def.ObjectDefinition; import org.apollo.game.model.entity.Entity; -import org.apollo.game.model.entity.GameObject; +import org.apollo.game.model.entity.Entity.EntityType; +import org.apollo.game.model.entity.obj.GameObject; import org.apollo.game.model.entity.Npc; import org.apollo.game.model.entity.Player; import org.apollo.game.model.event.Event; @@ -36,6 +37,8 @@ import org.apollo.util.MobRepository; import org.apollo.util.NameUtil; import org.apollo.util.plugin.PluginManager; +import com.google.common.base.Preconditions; + /** * The world class is a singleton which contains objects like the {@link MobRepository} for players and NPCs. It should * only contain things relevant to the in-game world and not classes which deal with I/O and such (these may be better @@ -123,6 +126,11 @@ public final class World { */ private PluginManager pluginManager; + /** + * This world's {@link RegionRepository}. + */ + private final RegionRepository regions = RegionRepository.immutable(); + /** * The release number (i.e. version) of this world. */ @@ -133,11 +141,6 @@ public final class World { */ private final Scheduler scheduler = new Scheduler(); - /** - * This world's {@link RegionRepository}. - */ - private final RegionRepository regions = RegionRepository.immutable(); - /** * Creates the world. */ @@ -192,15 +195,6 @@ public final class World { return pluginManager; } - /** - * Gets the release number of this world. - * - * @return The release number. - */ - public int getReleaseNumber() { - return releaseNumber; - } - /** * Gets this world's {@link RegionRepository}. * @@ -210,6 +204,15 @@ public final class World { return regions; } + /** + * Gets the release number of this world. + * + * @return The release number. + */ + public int getReleaseNumber() { + return releaseNumber; + } + /** * Initialises the world by loading definitions from the specified file system. * @@ -288,7 +291,7 @@ public final class World { * @param npc The npc. * @return {@code true} if the npc registered successfully, otherwise {@code false}. */ - public boolean register(final Npc npc) { + public boolean register(Npc npc) { boolean success = npcRepository.add(npc); if (success) { @@ -310,7 +313,7 @@ public final class World { * @param player The player. * @return A {@link RegistrationStatus}. */ - public RegistrationStatus register(final Player player) { + public RegistrationStatus register(Player player) { String username = player.getUsername(); if (isPlayerOnline(username)) { return RegistrationStatus.ALREADY_ONLINE; @@ -319,8 +322,6 @@ public final class World { boolean success = playerRepository.add(player); if (success) { players.put(NameUtil.encodeBase37(username), player); - Region region = regions.fromPosition(player.getPosition()); - region.addEntity(player); logger.info("Registered player: " + player + " [count=" + playerRepository.size() + "]"); return RegistrationStatus.OK; @@ -340,6 +341,20 @@ public final class World { return scheduler.schedule(task); } + /** + * Spawns the specified {@link Entity}, which must not be a {@link Player} or an {@link Npc}, which have their own + * register methods. + * + * @param entity The Entity. + */ + public void spawn(Entity entity) { + EntityType type = entity.getEntityType(); + Preconditions.checkArgument(type != EntityType.PLAYER && type != EntityType.NPC, "Cannot spawn a Mob."); + + Region region = regions.fromPosition(entity.getPosition()); + region.addEntity(entity); + } + /** * Submits the specified {@link Event}, passing it to the listeners.. * diff --git a/src/org/apollo/game/model/area/EntityUpdateType.java b/src/org/apollo/game/model/area/EntityUpdateType.java new file mode 100644 index 00000000..4ce50c7c --- /dev/null +++ b/src/org/apollo/game/model/area/EntityUpdateType.java @@ -0,0 +1,20 @@ +package org.apollo.game.model.area; + +/** + * A type of update that an Entity in a {@link Region} may have. + * + * @author Major + */ +public enum EntityUpdateType { + + /** + * The add type, when an Entity has been added to a {@link Region}. + */ + ADD, + + /** + * The remove type, when an Entity has been removed from a {@link Region}. + */ + REMOVE; + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/Region.java b/src/org/apollo/game/model/area/Region.java index df14d7f9..c04da299 100644 --- a/src/org/apollo/game/model/area/Region.java +++ b/src/org/apollo/game/model/area/Region.java @@ -1,6 +1,8 @@ package org.apollo.game.model.area; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -8,14 +10,18 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.apollo.game.message.impl.RegionUpdateMessage; import org.apollo.game.model.Direction; import org.apollo.game.model.Position; import org.apollo.game.model.area.collision.CollisionMatrix; +import org.apollo.game.model.area.update.UpdateOperation; import org.apollo.game.model.entity.Entity; import org.apollo.game.model.entity.Entity.EntityType; +import org.apollo.game.model.entity.obj.GameObject; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; /** @@ -25,10 +31,30 @@ import com.google.common.collect.ImmutableSet; */ public final class Region { + /** + * A {@link RegionListener} for {@link UpdateOperation}s. + * + * @author Major + */ + private static final class UpdateRegionListener implements RegionListener { + + @Override + public void execute(Region region, Entity entity, EntityUpdateType update) { + EntityType type = entity.getEntityType(); + if (type != EntityType.PLAYER && type != EntityType.NPC + && (type != EntityType.STATIC_OBJECT || update == EntityUpdateType.REMOVE)) { + region.record(entity, update); + } + } + + } + /** * The width and length of a Region, in tiles. */ - public static final int REGION_SIZE = 8; + public static final int SIZE = 8; + + static final long start = System.currentTimeMillis(); /** * The default size of newly-created sets, to reduce memory usage. @@ -53,7 +79,17 @@ public final class Region { /** * The CollisionMatrix. */ - private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, REGION_SIZE, REGION_SIZE); + private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SIZE, SIZE); + + /** + * The Set containing RegionUpdateMessages which can be sent to add every non-Mob Entity in this Region. + */ + private final List> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS); + + /** + * The Set containing UpdateOperations. + */ + private final List> updates = new ArrayList<>(Position.HEIGHT_LEVELS); /** * Creates a new Region. @@ -72,6 +108,12 @@ public final class Region { */ public Region(RegionCoordinates coordinates) { this.coordinates = coordinates; + listeners.add(new UpdateRegionListener()); + + for (int height = 0; height < Position.HEIGHT_LEVELS; height++) { + snapshots.add(new ArrayList<>()); + updates.add(new ArrayList<>(DEFAULT_SET_SIZE)); + } } /** @@ -88,7 +130,11 @@ public final class Region { Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); local.add(entity); - notifyListeners(entity, RegionOperation.ADD); + if ((System.currentTimeMillis() - start) / 1000 > 10 && (entity instanceof GameObject)) { + System.out.println("Adding entity " + entity + " to " + entity.getPosition()); + } + + notifyListeners(entity, EntityUpdateType.ADD); } /** @@ -128,22 +174,24 @@ public final class Region { } /** - * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}. The returned + * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}(s). The returned * type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference * correspond, or this method will fail at runtime. * * @param position The {@link Position} containing the entities. - * @param type The {@link EntityType}. + * @param types The {@link EntityType}s. * @return The set of entities. */ - public Set getEntities(Position position, EntityType type) { + public Set getEntities(Position position, EntityType... types) { Set local = entities.get(position); if (local == null) { return ImmutableSet.of(); } + Set set = new HashSet<>(Arrays.asList(types)); @SuppressWarnings("unchecked") - Set filtered = (Set) local.stream().filter(Entity -> Entity.getEntityType() == type).collect(Collectors.toSet()); + Set filtered = (Set) local.stream().filter(entity -> set.contains(entity.getEntityType())) + .collect(Collectors.toSet()); return ImmutableSet.copyOf(filtered); } @@ -159,40 +207,41 @@ public final class Region { } /** - * Moves the {@link Entity} that was in the specified {@code old} {@link Position}, to the current position of the - * Entity. - *

- * Both the {@code old} and current positions of the Entity must belong to this Region. + * Gets a {@link Set} containing {@link RegionUpdateMessage}s that add every {@link Entity} in this Region. * - * @param old The old position of the Entity. - * @param entity The Entity to move. - * @throws IllegalArgumentException If either of the positions do not belong to this Region. + * @param height The height level to get the Set of RegionUpdateMessages for. + * @return The Set of RegionUpdateMessages. */ - public void moveEntity(Position old, Entity entity) { - Position position = entity.getPosition(); - checkPosition(old); - checkPosition(position); + public List getSnapshot(int height) { + List copy = new ArrayList<>(snapshots.get(height)); + Collections.sort(copy); + return ImmutableList.copyOf(copy); + } - Set local = entities.get(old); + /** + * Gets the updates that have occurred in the last tick in this Region, as a {@link Set} of + * {@link RegionUpdateMessage}s. + * + * @param height The height level to get the Set of RegionUpdateMessages for. + * @return The Set of RegionUpdateMessages. + */ + public List getUpdates(int height) { + List original = this.updates.get(height); + List updates = new ArrayList<>(original); + original.clear(); - if (local == null || !local.remove(entity)) { - throw new IllegalArgumentException("Entity belongs in this Region (" + this + ") but does not exist."); - } - - local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); - - local.add(entity); - notifyListeners(entity, RegionOperation.MOVE); + Collections.sort(updates); + return ImmutableList.copyOf(updates); } /** * Notifies the {@link RegionListener}s registered to this Region that an update has occurred. * * @param entity The {@link Entity} that was updated. - * @param operation The {@link RegionOperation} that occurred. + * @param type The {@link EntityUpdateType} that occurred. */ - public void notifyListeners(Entity entity, RegionOperation operation) { - listeners.forEach(listener -> listener.execute(this, entity, operation)); + public void notifyListeners(Entity entity, EntityUpdateType type) { + listeners.forEach(listener -> listener.execute(this, entity, type)); } /** @@ -211,7 +260,12 @@ public final class Region { throw new IllegalArgumentException("Entity belongs in this Region (" + this + ") but does not exist."); } - notifyListeners(entity, RegionOperation.REMOVE); + notifyListeners(entity, EntityUpdateType.REMOVE); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString(); } /** @@ -227,12 +281,7 @@ public final class Region { CollisionMatrix matrix = matrices[position.getHeight()]; int x = position.getX(), y = position.getY(); - return !matrix.untraversable(x % REGION_SIZE, y % REGION_SIZE, entity, direction); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString(); + return !matrix.untraversable(x % SIZE, y % SIZE, entity, direction); } /** @@ -246,4 +295,19 @@ public final class Region { "Position is not included in this Region."); } + /** + * Records the specified {@link Entity} as being updated this pulse. + * + * @param entity The Entity. + * @param type The {@link EntityUpdateType}. + * @throws UnsupportedOperationException If the specified Entity cannot be operated on in this manner. + */ + private void record(Entity entity, EntityUpdateType type) { + RegionUpdateMessage message = entity.toUpdateOperation(this, type).toMessage(); + int height = entity.getPosition().getHeight(); + + updates.get(height).add(message); + snapshots.get(height).add(message); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/RegionCoordinates.java b/src/org/apollo/game/model/area/RegionCoordinates.java index 37d00ce3..68f4c66a 100644 --- a/src/org/apollo/game/model/area/RegionCoordinates.java +++ b/src/org/apollo/game/model/area/RegionCoordinates.java @@ -14,10 +14,10 @@ import com.google.common.base.MoreObjects; public final class RegionCoordinates { /** - * Gets a pair of region coordinates from a {@link Position}. + * Gets the RegionCoordinates for the specified {@link Position}. * - * @param position The position. - * @return The region coordinates. + * @param position The Position. + * @return The RegionCoordinates. */ public static RegionCoordinates fromPosition(Position position) { return new RegionCoordinates(position.getTopLeftRegionX(), position.getTopLeftRegionY()); @@ -54,6 +54,24 @@ public final class RegionCoordinates { return false; } + /** + * Gets the absolute x coordinate of this Region (which can be compared directly against {@link Position#getX()}. + * + * @return The absolute x coordinate. + */ + public int getAbsoluteX() { + return Region.SIZE * (x + 6); + } + + /** + * Gets the absolute y coordinate of this Region (which can be compared directly against {@link Position#getY()}. + * + * @return The absolute y coordinate. + */ + public int getAbsoluteY() { + return Region.SIZE * (y + 6); + } + /** * Gets the x coordinate (equivalent to the {@link Position#getTopLeftRegionX()} of a position within this region). * diff --git a/src/org/apollo/game/model/area/RegionListener.java b/src/org/apollo/game/model/area/RegionListener.java index a84499aa..d4c897b3 100644 --- a/src/org/apollo/game/model/area/RegionListener.java +++ b/src/org/apollo/game/model/area/RegionListener.java @@ -16,8 +16,8 @@ public interface RegionListener { * * @param region The {@link Region} that was updated. * @param entity The affected {@link Entity}. - * @param operation The type of {@link RegionOperation}. + * @param type The type of {@link EntityUpdateType}. */ - public abstract void execute(Region region, Entity entity, RegionOperation operation); + public abstract void execute(Region region, Entity entity, EntityUpdateType type); } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/RegionOperation.java b/src/org/apollo/game/model/area/RegionOperation.java deleted file mode 100644 index 639972fa..00000000 --- a/src/org/apollo/game/model/area/RegionOperation.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.apollo.game.model.area; - -/** - * An operation that can be performed by a region, used by {@link RegionListener}s to differentiate between operations. - * - * @author Major - */ -public enum RegionOperation { - - /** - * The add operation, when an entity has been added to a region. - */ - ADD, - - /** - * The move operation, when an entity has moved positions, but is still in the same region. - */ - MOVE, - - /** - * The remove operation, when an entity has been removed from a region. - */ - REMOVE; - -} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/collision/CollisionMatrix.java b/src/org/apollo/game/model/area/collision/CollisionMatrix.java index f7984a27..46c25ca1 100644 --- a/src/org/apollo/game/model/area/collision/CollisionMatrix.java +++ b/src/org/apollo/game/model/area/collision/CollisionMatrix.java @@ -76,7 +76,13 @@ public final class CollisionMatrix { * @return {@code true} if all of the CollisionFlags are set, otherwise {@code false}. */ public boolean all(int x, int y, CollisionFlag... flags) { - return Arrays.stream(flags).allMatch(flag -> (get(x, y) & flag.asByte()) != 0); + for (CollisionFlag flag : flags) { + if (!flagged(x, y, flag)) { + return false; + } + } + + return true; } /** @@ -89,7 +95,13 @@ public final class CollisionMatrix { * @return {@code true} if any of the CollisionFlags are set, otherwise {@code false}. */ public boolean any(int x, int y, CollisionFlag... flags) { - return Arrays.stream(flags).anyMatch(flag -> (get(x, y) & flag.asByte()) != 0); + for (CollisionFlag flag : flags) { + if (flagged(x, y, flag)) { + return true; + } + } + + return false; } /** @@ -147,6 +159,17 @@ public final class CollisionMatrix { set(x, y, ALL_ALLOWED); } + /** + * Sets the appropriate index for the specified coordinate pair to the specified value. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param value The value. + */ + private void set(int x, int y, byte value) { + matrix[indexOf(x, y)] = value; + } + /** * Sets (i.e. sets to {@code true}) the value of the specified {@link CollisionFlag} for the specified coordinate * pair. @@ -161,7 +184,8 @@ public final class CollisionMatrix { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("width", width).add("length", length).add("matrix", Arrays.toString(matrix)).toString(); + return MoreObjects.toStringHelper(this).add("width", width).add("length", length).add("matrix", Arrays.toString(matrix)) + .toString(); } /** @@ -215,15 +239,4 @@ public final class CollisionMatrix { return index; } - /** - * Sets the appropriate index for the specified coordinate pair to the specified value. - * - * @param x The x coordinate. - * @param y The y coordinate. - * @param value The value. - */ - public void set(int x, int y, byte value) { - matrix[indexOf(x, y)] = value; - } - } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/update/ItemUpdateOperation.java b/src/org/apollo/game/model/area/update/ItemUpdateOperation.java new file mode 100644 index 00000000..1c658b8a --- /dev/null +++ b/src/org/apollo/game/model/area/update/ItemUpdateOperation.java @@ -0,0 +1,38 @@ +package org.apollo.game.model.area.update; + +import org.apollo.game.message.impl.RegionUpdateMessage; +import org.apollo.game.message.impl.RemoveTileItemMessage; +import org.apollo.game.message.impl.SendPublicTileItemMessage; +import org.apollo.game.model.area.EntityUpdateType; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.entity.GroundItem; + +/** + * A {@link UpdateOperation} for {@link GroundItem}s. + * + * @author Major + */ +public final class ItemUpdateOperation extends UpdateOperation { + + /** + * Creates the ItemUpdateOperation. + * + * @param region The {@link Region} the type occurred in. Must not be {@code null}. + * @param type The {@link EntityUpdateType}. Must not be {@code null}. + * @param item The modified {@link GroundItem}. Must not be {@code null}. + */ + public ItemUpdateOperation(Region region, EntityUpdateType type, GroundItem item) { + super(region, type, item); + } + + @Override + protected RegionUpdateMessage add(int offset) { + return new SendPublicTileItemMessage(entity.getItem(), offset, entity.getOwnerIndex()); + } + + @Override + protected RegionUpdateMessage remove(int offset) { + return new RemoveTileItemMessage(entity.getItem(), offset); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/update/ObjectUpdateOperation.java b/src/org/apollo/game/model/area/update/ObjectUpdateOperation.java new file mode 100644 index 00000000..b0f6d370 --- /dev/null +++ b/src/org/apollo/game/model/area/update/ObjectUpdateOperation.java @@ -0,0 +1,38 @@ +package org.apollo.game.model.area.update; + +import org.apollo.game.message.impl.RegionUpdateMessage; +import org.apollo.game.message.impl.RemoveObjectMessage; +import org.apollo.game.message.impl.SendObjectMessage; +import org.apollo.game.model.area.EntityUpdateType; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.entity.obj.GameObject; + +/** + * A {@link UpdateOperation} for addition or removal of {@link GameObject}s. + * + * @author Major + */ +public final class ObjectUpdateOperation extends UpdateOperation { + + /** + * Creates the ObjectUpdateOperation. + * + * @param region The {@link Region} in which the ObjectUpdateOperation occurred. Must not be {@code null}. + * @param type The {@link EntityUpdateType}. Must not be {@code null}. + * @param object The {@linkGameObject}. Must not be {@code null}. + */ + public ObjectUpdateOperation(Region region, EntityUpdateType type, GameObject object) { + super(region, type, object); + } + + @Override + protected RegionUpdateMessage add(int offset) { + return new SendObjectMessage(entity, offset); + } + + @Override + protected RegionUpdateMessage remove(int offset) { + return new RemoveObjectMessage(entity, offset); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/update/UpdateOperation.java b/src/org/apollo/game/model/area/update/UpdateOperation.java new file mode 100644 index 00000000..32127e5b --- /dev/null +++ b/src/org/apollo/game/model/area/update/UpdateOperation.java @@ -0,0 +1,101 @@ +package org.apollo.game.model.area.update; + +import org.apollo.game.message.Message; +import org.apollo.game.message.impl.RegionUpdateMessage; +import org.apollo.game.model.Position; +import org.apollo.game.model.area.EntityUpdateType; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.RegionCoordinates; +import org.apollo.game.model.entity.Entity; + +import com.google.common.base.Preconditions; + +/** + * An type that is contained in the snapshot of a {@link Region}, which consists of an {@link Entity} being added, + * removed, or moved. + * + * @author Major + * @param The type of {@link Entity} in this type. + */ +public abstract class UpdateOperation { + + /** + * The Entity involved in this UpdateOperation. + */ + protected final E entity; + + /** + * The Region in which this type occurred. + */ + protected final Region region; + + /** + * The type of update. + */ + protected final EntityUpdateType type; + + /** + * Creates the UpdateOperation. + * + * @param region The region in which the UpdateOperation occurred. Must not be {@code null}. + * @param type The type of {@link EntityUpdateType}. Must not be {@code null}. + * @param entity The {@link Entity} being added or removed. Must not be {@code null}. + */ + public UpdateOperation(Region region, EntityUpdateType type, E entity) { + this.region = region; + this.type = type; + this.entity = entity; + } + + /** + * Returns this UpdateOperation as a {@link Message}. + * + * @return The Message. + */ + public final RegionUpdateMessage toMessage() { + int offset = getPositionOffset(entity.getPosition()); + + switch (type) { + case ADD: + return add(offset); + case REMOVE: + return remove(offset); + default: + throw new IllegalStateException("Unsupported EntityUpdateType " + type + "."); + } + } + + /** + * Returns a {@link RegionUpdateMessage} that adds the {@link Entity} in this UpdateOperation. + * + * @param offset The offset of the {@link Position} of the Entity from the Position of the {@link Region}. + * @return The RegionUpdateMessage. + */ + protected abstract RegionUpdateMessage add(int offset); + + /** + * Returns a {@link RegionUpdateMessage} that removes the {@link Entity} in this UpdateOperation. + * + * @param offset The offset of the {@link Position} of the Entity from the Position of the {@link Region}. + * @return The RegionUpdateMessage. + */ + protected abstract RegionUpdateMessage remove(int offset); + + /** + * Gets the position offset for the specified {@link Position}. + * + * @param position The Position. + * @return The position offset. + */ + private final int getPositionOffset(Position position) { + RegionCoordinates coordinates = region.getCoordinates(); + int dx = position.getX() - coordinates.getAbsoluteX(); + int dy = position.getY() - coordinates.getAbsoluteY(); + + Preconditions.checkArgument(dx >= 0 && dx < Region.SIZE, position + " not in expected Region of " + region + "."); + Preconditions.checkArgument(dy >= 0 && dy < Region.SIZE, position + " not in expected Region of " + region + "."); + + return (dx << 4) | dy; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/update/package-info.java b/src/org/apollo/game/model/area/update/package-info.java new file mode 100644 index 00000000..864c3acd --- /dev/null +++ b/src/org/apollo/game/model/area/update/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains snapshot-related classes. + */ +package org.apollo.game.model.area.update; \ No newline at end of file diff --git a/src/org/apollo/game/model/def/ObjectDefinition.java b/src/org/apollo/game/model/def/ObjectDefinition.java index de1af1c9..79de4d50 100644 --- a/src/org/apollo/game/model/def/ObjectDefinition.java +++ b/src/org/apollo/game/model/def/ObjectDefinition.java @@ -1,6 +1,6 @@ package org.apollo.game.model.def; -import org.apollo.game.model.entity.GameObject; +import org.apollo.game.model.entity.obj.GameObject; import com.google.common.base.Preconditions; diff --git a/src/org/apollo/game/model/entity/Entity.java b/src/org/apollo/game/model/entity/Entity.java index bfbd65cd..eca2bbc8 100644 --- a/src/org/apollo/game/model/entity/Entity.java +++ b/src/org/apollo/game/model/entity/Entity.java @@ -1,6 +1,9 @@ package org.apollo.game.model.entity; import org.apollo.game.model.Position; +import org.apollo.game.model.area.EntityUpdateType; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.update.UpdateOperation; /** * Represents an in-game entity, such as a mob, object, projectile, etc. @@ -11,28 +14,31 @@ public abstract class Entity { /** * Represents a type of {@link Entity}. - * - * @author Major */ public enum EntityType { /** - * An item that has been dropped on the ground. + * An Item that is positioned on the ground. */ - DROPPED_ITEM, + GROUND_ITEM, /** - * An object appearing in the game world. + * A GameObject that is loaded statically (i.e. from the game resources) at start-up. */ - GAME_OBJECT, + STATIC_OBJECT, /** - * An npc. + * A GameObject that is loaded dynamically, usually for specific Players. + */ + DYNAMIC_OBJECT, + + /** + * An Npc. */ NPC, /** - * A player. + * A Player. */ PLAYER, @@ -41,6 +47,15 @@ public abstract class Entity { */ PROJECTILE; + /** + * Returns whether or not this EntityType is for a Mob. + * + * @return {@code true} if this EntityType is for a Mob, otherwise {@code false}. + */ + public boolean isMob() { + return this == PLAYER || this == NPC; + } + } /** @@ -57,6 +72,9 @@ public abstract class Entity { this.position = position; } + @Override + public abstract boolean equals(Object obj); + /** * Gets the {@link EntityType} of this entity. * @@ -73,10 +91,16 @@ public abstract class Entity { return position; } - @Override - public abstract boolean equals(Object obj); - @Override public abstract int hashCode(); + /** + * Gets this Entity, as an {@link UpdateOperation} of a {@link Region}. + * + * @param region The Region. + * @param type The EntityUpdateType. + * @return The UpdateOperation. + */ + public abstract UpdateOperation toUpdateOperation(Region region, EntityUpdateType type); + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/GroundItem.java b/src/org/apollo/game/model/entity/GroundItem.java new file mode 100644 index 00000000..1dc322d4 --- /dev/null +++ b/src/org/apollo/game/model/entity/GroundItem.java @@ -0,0 +1,107 @@ +package org.apollo.game.model.entity; + +import org.apollo.game.model.Item; +import org.apollo.game.model.Position; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.EntityUpdateType; +import org.apollo.game.model.area.update.ItemUpdateOperation; +import org.apollo.game.model.area.update.UpdateOperation; + +/** + * An {@link Item} displayed on the ground. + * + * @author Major + */ +public final class GroundItem extends Entity { + + /** + * Creates a new GroundItem. + * + * @param position The {@link Position} of the Item. + * @param item The Item displayed on the ground. + * @return The GroundItem. + */ + public static GroundItem create(Position position, Item item) { + return new GroundItem(position, item, -1); + } + + /** + * Creates a new dropped GroundItem. + * + * @param position The {@link Position} of the Item. + * @param item The Item displayed on the ground. + * @param owner The the {@link Player} who dropped this GroundItem. + * @return The GroundItem. + */ + public static GroundItem dropped(Position position, Item item, Player owner) { + return new GroundItem(position, item, owner.getIndex()); + } + + /** + * The index of the Player who dropped this GroundItem. + */ + private final int index; + + /** + * The Item displayed on the ground. + */ + private final Item item; + + /** + * Creates the GroundItem. + * + * @param position The {@link Position} of the Item. + * @param item The Item displayed on the ground. + * @param index The index of the {@link Player} who dropped this GroundItem. + */ + private GroundItem(Position position, Item item, int index) { + super(position); + this.item = item; + this.index = index; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GroundItem) { + GroundItem other = (GroundItem) obj; + return position.equals(other.position); + } + + return false; + } + + @Override + public EntityType getEntityType() { + return EntityType.GROUND_ITEM; + } + + /** + * Gets the {@link Item} displayed on the ground. + * + * @return The Item. + */ + public Item getItem() { + return item; + } + + /** + * Gets the index of the {@link Player} who dropped this GroundItem, or {@code -1} if this GroundItem was not + * dropped by a Player. + * + * @return The index. + */ + public int getOwnerIndex() { + return index; + } + + @Override + public int hashCode() { + return position.hashCode() * 31 + item.hashCode(); + } + + @Override + public UpdateOperation toUpdateOperation(Region region, EntityUpdateType operation) { + return new ItemUpdateOperation(region, operation, this); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/Mob.java b/src/org/apollo/game/model/entity/Mob.java index d36767d3..7e56f344 100644 --- a/src/org/apollo/game/model/entity/Mob.java +++ b/src/org/apollo/game/model/entity/Mob.java @@ -11,8 +11,10 @@ import org.apollo.game.model.Direction; import org.apollo.game.model.Graphic; import org.apollo.game.model.Position; import org.apollo.game.model.World; +import org.apollo.game.model.area.EntityUpdateType; import org.apollo.game.model.area.Region; import org.apollo.game.model.area.RegionRepository; +import org.apollo.game.model.area.update.UpdateOperation; import org.apollo.game.model.def.NpcDefinition; import org.apollo.game.model.entity.attr.Attribute; import org.apollo.game.model.entity.attr.AttributeMap; @@ -194,8 +196,8 @@ public abstract class Mob extends Entity { */ public final Direction[] getDirections() { if (firstDirection != Direction.NONE) { - return secondDirection == Direction.NONE ? new Direction[] { firstDirection } : new Direction[] { - firstDirection, secondDirection }; + return secondDirection == Direction.NONE ? new Direction[] { firstDirection } : new Direction[] { firstDirection, + secondDirection }; } return Direction.EMPTY_DIRECTION_ARRAY; @@ -248,6 +250,15 @@ public abstract class Mob extends Entity { return interactingMob; } + /** + * Returns this mobs interacting index. + * + * @return The interaction index of this mob. + */ + public int getInteractionIndex() { + return index; + } + /** * Gets this mob's inventory. * @@ -404,15 +415,6 @@ public abstract class Mob extends Entity { } } - /** - * Returns this mobs interacting index. - * - * @return The interaction index of this mob. - */ - public int getInteractionIndex() { - return index; - } - /** * Updates this mob's interacting mob. * @@ -425,22 +427,19 @@ public abstract class Mob extends Entity { /** * Sets the {@link Position} of this mob. + *

+ * This method may be intercepted using a {@link MobPositionUpdateEvent}, which can be terminated like any other. + * Plugins that intercept this Event must be cautious, because movement will not be possible (even + * through mechanisms such as teleporting) if the Event is terminated. * - * @param position The position. + * @param position The Position. */ public final void setPosition(Position position) { - World.getWorld().submit(new MobPositionUpdateEvent(this, position)); - // Intentionally ignore the Event result - accidentally terminating this method would break the entire server. + if (World.getWorld().submit(new MobPositionUpdateEvent(this, position))) { + Position old = this.position; + RegionRepository repository = World.getWorld().getRegionRepository(); + Region current = repository.fromPosition(old), next = repository.fromPosition(position); - Position old = this.position; - RegionRepository repository = World.getWorld().getRegionRepository(); - Region current = repository.fromPosition(old); - - if (position.inside(current)) { - this.position = position; - current.moveEntity(old, this); - } else { - Region next = repository.fromPosition(position); current.removeEntity(this); this.position = position; // addEntity relies on the position being updated, so do that first. @@ -523,6 +522,11 @@ public abstract class Mob extends Entity { stopAction(); } + @Override + public final UpdateOperation toUpdateOperation(Region region, EntityUpdateType operation) { + throw new UnsupportedOperationException("Mobs cannot be recorded as a Region update."); + } + /** * Turns this mob to face the specified {@link Position}. * diff --git a/src/org/apollo/game/model/entity/Npc.java b/src/org/apollo/game/model/entity/Npc.java index 07c2bee1..5c3fdc88 100644 --- a/src/org/apollo/game/model/entity/Npc.java +++ b/src/org/apollo/game/model/entity/Npc.java @@ -3,8 +3,6 @@ package org.apollo.game.model.entity; import java.util.Optional; import org.apollo.game.model.Position; -import org.apollo.game.model.World; -import org.apollo.game.model.area.Region; import org.apollo.game.model.def.NpcDefinition; import org.apollo.game.sync.block.SynchronizationBlock; @@ -44,7 +42,6 @@ public final class Npc extends Mob { super(position, definition); this.boundaries = Optional.ofNullable(boundaries); - init(); } @Override @@ -122,14 +119,4 @@ public final class Npc extends Mob { blockSet.add(SynchronizationBlock.createTransformBlock(id)); } - /** - * Initialises this Npc. - */ - private void init() { - // This has to be here instead of in Mob#init because of ordering issues - the Npc cannot be added to the - // region until their credentials have been set, which is only done after the super constructors are called. - Region region = World.getWorld().getRegionRepository().get(position.getRegionCoordinates()); - region.addEntity(this); - } - } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/Player.java b/src/org/apollo/game/model/entity/Player.java index abc02818..7fdfd74c 100644 --- a/src/org/apollo/game/model/entity/Player.java +++ b/src/org/apollo/game/model/entity/Player.java @@ -18,7 +18,6 @@ import org.apollo.game.message.impl.UpdateRunEnergyMessage; import org.apollo.game.model.Appearance; import org.apollo.game.model.Position; import org.apollo.game.model.World; -import org.apollo.game.model.area.Region; import org.apollo.game.model.entity.attr.Attribute; import org.apollo.game.model.entity.attr.AttributeDefinition; import org.apollo.game.model.entity.attr.AttributeMap; @@ -155,6 +154,11 @@ public final class Player extends Mob { */ private final transient Deque queuedMessages = new ArrayDeque<>(); + /** + * A flag indicating if the region changed in the last cycle. + */ + private transient boolean regionChanged = false; + /** * A flag indicating if this player is running. */ @@ -165,11 +169,6 @@ public final class Player extends Mob { */ private ScreenBrightness screenBrightness = ScreenBrightness.NORMAL; - /** - * A flag indicating if the region changed in the last cycle. - */ - private transient boolean regionChanged = false; - /** * The {@link GameSession} currently attached to this {@link Player}. */ @@ -407,6 +406,15 @@ public final class Player extends Mob { return lastKnownRegion; } + /** + * Gets the {@link MembershipStatus} of this Player. + * + * @return The MembershipStatus. + */ + public MembershipStatus getMembershipStatus() { + return members; + } + /** * Gets the player's prayer icon. * @@ -558,15 +566,6 @@ public final class Player extends Mob { return members == MembershipStatus.PAID; } - /** - * Gets the {@link MembershipStatus} of this Player. - * - * @return The MembershipStatus. - */ - public MembershipStatus getMembershipStatus() { - return members; - } - /** * Checks if this player is running. * @@ -846,6 +845,15 @@ public final class Player extends Mob { this.privilegeLevel = privilegeLevel; } + /** + * Sets the region changed flag. + * + * @param regionChanged A flag indicating if the region has changed. + */ + public void setRegionChanged(boolean regionChanged) { + this.regionChanged = regionChanged; + } + /** * Sets the player's run energy. * @@ -865,15 +873,6 @@ public final class Player extends Mob { this.screenBrightness = brightness; } - /** - * Sets the region changed flag. - * - * @param regionChanged A flag indicating if the region has changed. - */ - public void setRegionChanged(boolean regionChanged) { - this.regionChanged = regionChanged; - } - /** * Sets the player's {@link GameSession}. * @@ -943,7 +942,8 @@ public final class Player extends Mob { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel).add("client version", getClientVersion()).toString(); + return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel) + .add("client version", getClientVersion()).toString(); } /** @@ -952,11 +952,6 @@ public final class Player extends Mob { private void init() { initInventories(); initSkills(); - - // This has to be here instead of in Mob#init because of ordering issues - the player cannot be added to the - // region until their credentials have been set, which is only done after the super constructors are called. - Region region = World.getWorld().getRegionRepository().get(position.getRegionCoordinates()); - region.addEntity(this); } /** @@ -967,7 +962,8 @@ public final class Player extends Mob { InventoryListener fullBankListener = new FullInventoryListener(this, FullInventoryListener.FULL_BANK_MESSAGE); InventoryListener appearanceListener = new AppearanceInventoryListener(this); - InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.INVENTORY_ID); + InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this, + SynchronizationInventoryListener.INVENTORY_ID); InventoryListener syncBankListener = new SynchronizationInventoryListener(this, BankConstants.BANK_INVENTORY_ID); InventoryListener syncEquipmentListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.EQUIPMENT_ID); diff --git a/src/org/apollo/game/model/entity/obj/DynamicGameObject.java b/src/org/apollo/game/model/entity/obj/DynamicGameObject.java new file mode 100644 index 00000000..004792bd --- /dev/null +++ b/src/org/apollo/game/model/entity/obj/DynamicGameObject.java @@ -0,0 +1,133 @@ +package org.apollo.game.model.entity.obj; + +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Set; + +import org.apollo.game.model.Position; +import org.apollo.game.model.entity.Player; + +/** + * A {@link GameObject} that is loaded dynamically, usually for specific Players. + * + * @author Major + */ +public final class DynamicGameObject extends GameObject { + + /** + * A {@link WeakReference} for {@link Player}s. + */ + private static final class WeakPlayerReference extends WeakReference { + + /** + * Creates the WeakPlayerReference. + * + * @param player The Player wrapped in this {@link WeakReference}. + */ + public WeakPlayerReference(Player player) { + super(player); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WeakPlayerReference) { + WeakPlayerReference other = (WeakPlayerReference) obj; + + Player player = get(); + return player != null && player.equals(other.get()); + } + + return false; + } + + @Override + public int hashCode() { + Player player = get(); + return (player == null) ? 0 : player.hashCode(); + } + + } + + /** + * Creates a DynamicGameObject that is visible only to {@link Player}s specified later. + * + * @param id The id of the DynamicGameObject + * @param position The {@link Position} of the DynamicGameObject. + * @param type The type of the DynamicGameObject. + * @param orientation The orientation of the DynamicGameObject. + * @return The DynamicGameObject. + */ + public static DynamicGameObject createLocal(int id, Position position, int type, int orientation) { + return new DynamicGameObject(id, position, type, orientation, false); + } + + /** + * Creates a DynamicGameObject that is always visible. + * + * @param id The id of the DynamicGameObject + * @param position The {@link Position} of the DynamicGameObject. + * @param type The type of the DynamicGameObject. + * @param orientation The orientation of the DynamicGameObject. + * @return The DynamicGameObject. + */ + public static DynamicGameObject createPublic(int id, Position position, int type, int orientation) { + return new DynamicGameObject(id, position, type, orientation, true); + } + + /** + * The flag indicating whether or not this DynamicGameObject is visible to every player. + */ + private final boolean alwaysVisible; + + /** + * The Set of Players that can view this DynamicGameObject. + */ + private final Set players = new HashSet<>(); + + /** + * Creates the DynamicGameObject. + * + * @param id The id of the DynamicGameObject + * @param position The {@link Position} of the DynamicGameObject. + * @param type The type of the DynamicGameObject. + * @param orientation The orientation of the DynamicGameObject. + * @param alwaysVisible The flag indicates whether or not this DynamicGameObject is visible to every player. + */ + private DynamicGameObject(int id, Position position, int type, int orientation, boolean alwaysVisible) { + super(id, position, type, orientation); + this.alwaysVisible = alwaysVisible; + } + + /** + * Adds this DynamicGameObject to the view of the specified {@link Player}. + * + * @param player The Player. + * @return {@code true} if this GameObject was not already visible to the specified Player. + */ + public boolean addTo(Player player) { + WeakPlayerReference reference = new WeakPlayerReference(player); + return players.add(reference); + } + + @Override + public EntityType getEntityType() { + return EntityType.DYNAMIC_OBJECT; + } + + /** + * Removes this DynamicGameObject from the view of the specified {@link Player}. + * + * @param player The Player. + * @return {@code true} if this GameObject was visible to the specified Player. + */ + public boolean removeFrom(Player player) { + WeakPlayerReference reference = new WeakPlayerReference(player); + return players.remove(reference); + } + + @Override + public boolean viewableBy(Player player) { + return alwaysVisible || players.contains(new WeakPlayerReference(player)); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/GameObject.java b/src/org/apollo/game/model/entity/obj/GameObject.java similarity index 57% rename from src/org/apollo/game/model/entity/GameObject.java rename to src/org/apollo/game/model/entity/obj/GameObject.java index db29efd7..e04cf80c 100644 --- a/src/org/apollo/game/model/entity/GameObject.java +++ b/src/org/apollo/game/model/entity/obj/GameObject.java @@ -1,7 +1,12 @@ -package org.apollo.game.model.entity; +package org.apollo.game.model.entity.obj; import org.apollo.game.model.Position; +import org.apollo.game.model.area.EntityUpdateType; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.update.ObjectUpdateOperation; import org.apollo.game.model.def.ObjectDefinition; +import org.apollo.game.model.entity.Entity; +import org.apollo.game.model.entity.Player; import com.google.common.base.MoreObjects; @@ -11,7 +16,7 @@ import com.google.common.base.MoreObjects; * @author Chris Fletcher * @author Major */ -public final class GameObject extends Entity { +public abstract class GameObject extends Entity { /** * The packed value that stores this object's id, type, and orientation. @@ -19,12 +24,12 @@ public final class GameObject extends Entity { private final int packed; /** - * Creates the game object. + * Creates the GameObject. * - * @param id The object's id. - * @param position The position. - * @param type The type code of the object. - * @param orientation The orientation of the object. + * @param id The id of the GameObject + * @param position The {@link Position} of the GameObject. + * @param type The type of the GameObject. + * @param orientation The orientation of the GameObject. */ public GameObject(int id, Position position, int type, int orientation) { super(position); @@ -50,18 +55,13 @@ public final class GameObject extends Entity { return ObjectDefinition.lookup(getId()); } - @Override - public EntityType getEntityType() { - return EntityType.GAME_OBJECT; - } - /** * Gets this object's id. * * @return The id. */ public int getId() { - return packed >> 8; + return packed >>> 8; } /** @@ -89,7 +89,21 @@ public final class GameObject extends Entity { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("id", getId()).add("type", getType()).add("orientation", getOrientation()).toString(); + return MoreObjects.toStringHelper(this).add("id", getId()).add("type", getType()).add("orientation", getOrientation()) + .toString(); } + @Override + public ObjectUpdateOperation toUpdateOperation(Region region, EntityUpdateType operation) { + return new ObjectUpdateOperation(region, operation, this); + } + + /** + * Returns whether or not this GameObject can be seen by the specified {@link Player}. + * + * @param player The Player. + * @return {@code true} if the Player can see this GameObject, {@code false} if not. + */ + public abstract boolean viewableBy(Player player); + } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/obj/ObjectGroup.java b/src/org/apollo/game/model/entity/obj/ObjectGroup.java similarity index 94% rename from src/org/apollo/game/model/area/obj/ObjectGroup.java rename to src/org/apollo/game/model/entity/obj/ObjectGroup.java index 4faa4211..5955f8ab 100644 --- a/src/org/apollo/game/model/area/obj/ObjectGroup.java +++ b/src/org/apollo/game/model/entity/obj/ObjectGroup.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.area.obj; +package org.apollo.game.model.entity.obj; import java.util.Arrays; @@ -21,7 +21,7 @@ public enum ObjectGroup { WALL_DECORATION(1), /** - * The interactable object group, for objects that can be clicked and interacted with. + * The interactable object group, for objects that can be clicked and interacted with. TODO rename */ INTERACTABLE_OBJECT(2), diff --git a/src/org/apollo/game/model/area/obj/ObjectType.java b/src/org/apollo/game/model/entity/obj/ObjectType.java similarity index 97% rename from src/org/apollo/game/model/area/obj/ObjectType.java rename to src/org/apollo/game/model/entity/obj/ObjectType.java index c73a989e..33d8395e 100644 --- a/src/org/apollo/game/model/area/obj/ObjectType.java +++ b/src/org/apollo/game/model/entity/obj/ObjectType.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.area.obj; +package org.apollo.game.model.entity.obj; /** * The type of an object, which affects specified behaviour (such as whether it displaces existing objects). TODO diff --git a/src/org/apollo/game/model/entity/obj/StaticGameObject.java b/src/org/apollo/game/model/entity/obj/StaticGameObject.java new file mode 100644 index 00000000..b59c20f7 --- /dev/null +++ b/src/org/apollo/game/model/entity/obj/StaticGameObject.java @@ -0,0 +1,41 @@ +package org.apollo.game.model.entity.obj; + +import org.apollo.game.model.Position; +import org.apollo.game.model.World; +import org.apollo.game.model.area.RegionCoordinates; +import org.apollo.game.model.area.RegionRepository; +import org.apollo.game.model.entity.Player; + +/** + * A {@link GameObject} that is a static part of the game world (i.e. is stored in the game resources). + * + * @author Major + */ +public final class StaticGameObject extends GameObject { + + /** + * Creates the StaticGameObject. + * + * @param id The id of the StaticGameObject + * @param position The {@link Position} of the StaticGameObject. + * @param type The type code of the StaticGameObject. + * @param orientation The orientation of the StaticGameObject. + */ + public StaticGameObject(int id, Position position, int type, int orientation) { + super(id, position, type, orientation); + } + + @Override + public EntityType getEntityType() { + return EntityType.STATIC_OBJECT; + } + + @Override + public boolean viewableBy(Player player) { + RegionRepository repository = World.getWorld().getRegionRepository(); + RegionCoordinates coordinates = position.getRegionCoordinates(); + + return repository.get(coordinates).contains(this); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/obj/package-info.java b/src/org/apollo/game/model/entity/obj/package-info.java similarity index 52% rename from src/org/apollo/game/model/area/obj/package-info.java rename to src/org/apollo/game/model/entity/obj/package-info.java index df49f8d5..5dd694f7 100644 --- a/src/org/apollo/game/model/area/obj/package-info.java +++ b/src/org/apollo/game/model/entity/obj/package-info.java @@ -1,4 +1,4 @@ /** * Contains object-related classes. */ -package org.apollo.game.model.area.obj; \ No newline at end of file +package org.apollo.game.model.entity.obj; \ No newline at end of file diff --git a/src/org/apollo/game/sync/ParallelClientSynchronizer.java b/src/org/apollo/game/sync/ParallelClientSynchronizer.java index 8a62ad92..f2620e5a 100644 --- a/src/org/apollo/game/sync/ParallelClientSynchronizer.java +++ b/src/org/apollo/game/sync/ParallelClientSynchronizer.java @@ -1,12 +1,17 @@ package org.apollo.game.sync; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Phaser; import java.util.concurrent.ThreadFactory; import org.apollo.game.GameService; +import org.apollo.game.message.impl.RegionUpdateMessage; import org.apollo.game.model.World; +import org.apollo.game.model.area.RegionCoordinates; import org.apollo.game.model.entity.Npc; import org.apollo.game.model.entity.Player; import org.apollo.game.sync.task.NpcSynchronizationTask; @@ -59,9 +64,12 @@ public final class ParallelClientSynchronizer extends ClientSynchronizer { int playerCount = players.size(); int npcCount = npcs.size(); + Map> updates = new HashMap<>(); + Map> snapshots = new HashMap<>(); + phaser.bulkRegister(playerCount); for (Player player : players) { - SynchronizationTask task = new PrePlayerSynchronizationTask(player); + SynchronizationTask task = new PrePlayerSynchronizationTask(player, updates, snapshots); executor.submit(new PhasedSynchronizationTask(phaser, task)); } phaser.arriveAndAwaitAdvance(); diff --git a/src/org/apollo/game/sync/SequentialClientSynchronizer.java b/src/org/apollo/game/sync/SequentialClientSynchronizer.java index 4a5360c9..f6671ed2 100644 --- a/src/org/apollo/game/sync/SequentialClientSynchronizer.java +++ b/src/org/apollo/game/sync/SequentialClientSynchronizer.java @@ -1,7 +1,13 @@ package org.apollo.game.sync; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.apollo.game.GameService; +import org.apollo.game.message.impl.RegionUpdateMessage; import org.apollo.game.model.World; +import org.apollo.game.model.area.RegionCoordinates; import org.apollo.game.model.entity.Npc; import org.apollo.game.model.entity.Player; import org.apollo.game.sync.task.NpcSynchronizationTask; @@ -29,8 +35,11 @@ public final class SequentialClientSynchronizer extends ClientSynchronizer { MobRepository players = World.getWorld().getPlayerRepository(); MobRepository npcs = World.getWorld().getNpcRepository(); + Map> updates = new HashMap<>(); + Map> snapshots = new HashMap<>(); + for (Player player : players) { - SynchronizationTask task = new PrePlayerSynchronizationTask(player); + SynchronizationTask task = new PrePlayerSynchronizationTask(player, updates, snapshots); task.run(); } diff --git a/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java b/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java index 458244a0..6589e69b 100644 --- a/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java +++ b/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java @@ -1,34 +1,207 @@ package org.apollo.game.sync.task; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apollo.game.message.impl.ClearRegionMessage; +import org.apollo.game.message.impl.GroupedRegionUpdateMessage; import org.apollo.game.message.impl.RegionChangeMessage; +import org.apollo.game.message.impl.RegionUpdateMessage; import org.apollo.game.model.Position; +import org.apollo.game.model.World; +import org.apollo.game.model.area.Region; +import org.apollo.game.model.area.RegionCoordinates; +import org.apollo.game.model.area.RegionRepository; import org.apollo.game.model.entity.Player; +import com.google.common.collect.ImmutableSet; + /** * A {@link SynchronizationTask} which does pre-synchronization work for the specified {@link Player}. * * @author Graham + * @author Major */ public final class PrePlayerSynchronizationTask extends SynchronizationTask { + /** + * The update mode used when sending a {@link GroupedRegionUpdateMessage}. + */ + private enum RegionUpdateMode { + + /** + * The difference update mode, which only sends updates for changes that have occurred in the last pulse. + */ + DIFFERENCE, + + /** + * The full update mode, which sends everything in the Region. + */ + FULL; + + } + + /** + * The amount of Regions that are in view of a player, in one direction. + */ + private static final int REGION_COUNT = (int) Math.ceil(Position.MAX_DISTANCE / Region.SIZE); + + /** + * The width of the viewport of every Player, in tiles. + */ + private static final int VIEWPORT_WIDTH = Region.SIZE * 13; + /** * The player. */ private final Player player; /** - * Creates the {@link PrePlayerSynchronizationTask} for the specified player. - * - * @param player The player. + * The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain all of the Entity information for a + * Region. */ - public PrePlayerSynchronizationTask(Player player) { + private final Map> snapshots; + + /** + * The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain the updates for a Region a Player can + * already view. + */ + private final Map> updates; + + /** + * Creates the {@link PrePlayerSynchronizationTask} for the specified {@link Player}. + * + * @param player The Player. + * @param updates The {@link Map} containing {@link Region} updates. + * @param snapshots The Map containing Region snapshots. + */ + public PrePlayerSynchronizationTask(Player player, Map> updates, + Map> snapshots) { this.player = player; + this.updates = updates; + this.snapshots = snapshots; + } + + @Override + public void run() { + Position old = player.getPosition(); + player.getWalkingQueue().pulse(); + + RegionUpdateMode mode = RegionUpdateMode.DIFFERENCE; + + if (player.isTeleporting()) { + player.resetViewingDistance(); + mode = RegionUpdateMode.FULL; + } + + boolean hasKnownRegion = player.hasLastKnownRegion(); + if (!hasKnownRegion) { + mode = RegionUpdateMode.FULL; + } + + if (!hasKnownRegion || isRegionUpdateRequired()) { + player.setRegionChanged(true); + + Position position = player.getPosition(); + player.setLastKnownRegion(position); + player.send(new RegionChangeMessage(position)); + } + + Set newRegions = getNewRegions(old, player.getPosition()); + sendRegionUpdates(mode, newRegions); + } + + /** + * Gets the {@link Set} of {@link RegionCoordinates} of {@link Region}s that the {@link Player} in this task has + * only just became able to view. + * + * @param old The old {@link Position} of the Player. + * @param next The new Position of the Player. + * @return The Set of RegionCoordinates. Will not be {@code null}, but may be empty. + */ + private Set getNewRegions(Position old, Position next) { + RegionCoordinates oldRegion = old.getRegionCoordinates(); + RegionCoordinates nextRegion = next.getRegionCoordinates(); + + if (oldRegion.equals(nextRegion)) { + return ImmutableSet.of(); + } + + Set coordinates = new HashSet<>(9); + int oldX = oldRegion.getX(), oldY = oldRegion.getY(); + + int dx = nextRegion.getX() - oldX; + int dy = nextRegion.getY() - oldY; + + if (dx != 0) { + int x = oldX + dx; + int maxY = oldY + REGION_COUNT; + + for (int y = oldY - REGION_COUNT; y <= maxY; y++) { + coordinates.add(new RegionCoordinates(x, y)); + } + } + + if (dy != 0) { + int y = oldY + dy; + int maxX = oldX + REGION_COUNT; + + for (int x = oldX - REGION_COUNT; x <= maxX; x++) { + coordinates.add(new RegionCoordinates(x, y)); + } + } + + return coordinates; + } + + /** + * Gets the {@link List} of {@link GroupedRegionUpdateMessage}s. + * + * @param mode The {@link RegionUpdateMode} used when creating the Messages. + * @param newRegions The {@link Set} of {@link RegionCoordinates} that should be sent as a full update. + */ + private void sendRegionUpdates(RegionUpdateMode mode, Set newRegions) { + Position position = player.getPosition(); + RegionCoordinates base = position.getRegionCoordinates(); + int baseX = base.getX(), baseY = base.getY(); + + RegionRepository repository = World.getWorld().getRegionRepository(); + List messages = new ArrayList<>(); + + for (int x = baseX - REGION_COUNT; x <= baseX + REGION_COUNT; x++) { + for (int y = baseY - REGION_COUNT; y <= baseY + REGION_COUNT; y++) { + RegionCoordinates coordinates = new RegionCoordinates(x, y); + + RegionUpdateMode local = mode; + if (mode == RegionUpdateMode.DIFFERENCE && newRegions.contains(coordinates)) { + local = RegionUpdateMode.FULL; + + player.send(new ClearRegionMessage(position, coordinates)); + } + + Optional message = toUpdateMessage(local, position, coordinates, repository); + if (message.isPresent()) { + messages.add(message.get()); + } + } + } + + if (messages.size() > 0) { + System.out.println("Sending in mode " + mode + ", new regions=" + newRegions); + + } + + messages.forEach(player::send); } /** * Checks if a region update is required. * - * @return {@code true} if so, {@code false} otherwise. + * @return {@code true} if a Region update is required, {@code false} if not. */ private boolean isRegionUpdateRequired() { Position current = player.getPosition(); @@ -37,24 +210,59 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask { int deltaX = current.getLocalX(last); int deltaY = current.getLocalY(last); - return deltaX < 16 || deltaX >= 88 || deltaY < 16 || deltaY >= 88; + return deltaX <= Position.MAX_DISTANCE || deltaX >= (VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1) + || deltaY <= Position.MAX_DISTANCE || deltaY >= (VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1); } - @Override - public void run() { - player.getWalkingQueue().pulse(); + /** + * Creates a {@link GroupedRegionUpdateMessage} using the specified {@link RegionUpdateMode}, returning + * {@link Optional#empty()} if no update message is required. + * + * @param mode The RegionUpdateMode for the Message. + * @param position The {@link Position} of the Player. + * @param coordinates The {@link RegionCoordinates} of the {@link Region}. + * @param repository The {@link RegionRepository} containing the Regions. + * @return The Optional containing the GroupedRegionUpdateMessage. + */ + private Optional toUpdateMessage(RegionUpdateMode mode, Position position, + RegionCoordinates coordinates, RegionRepository repository) { + List messages; - if (player.isTeleporting()) { - player.resetViewingDistance(); + /* + * Here we used Map#computeIfAbsent because the value may have been inserted into the Map by another thread + * after our Map#get call. This is done in two separate parts (rather than acquiring the lock every time, and + * just calling Map#computeIfAbsent immediately) for performance - once the List has been + * placed into the Map once, it will never be changed, and is therefore a read-only operation after this. The + * alternative, acquiring the lock for every access, would be very slow. + */ + + switch (mode) { + case DIFFERENCE: + messages = updates.get(coordinates); + if (messages == null) { + synchronized (updates) { + messages = updates.computeIfAbsent(coordinates, + coords -> repository.get(coords).getUpdates(position.getHeight())); + } + } + + break; + case FULL: + messages = snapshots.get(coordinates); + if (messages == null) { + synchronized (snapshots) { + messages = snapshots.computeIfAbsent(coordinates, + coords -> repository.get(coords).getSnapshot(position.getHeight())); + } + } + + break; + default: + throw new IllegalArgumentException("Unrecognised RegionUpdateMode " + mode + "."); } - if (!player.hasLastKnownRegion() || isRegionUpdateRequired()) { - player.setRegionChanged(true); - - Position position = player.getPosition(); - player.setLastKnownRegion(position); - player.send(new RegionChangeMessage(position)); - } + return messages.isEmpty() ? Optional.empty() : Optional + .of(new GroupedRegionUpdateMessage(position, coordinates, messages)); } } \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/AddGlobalTileItemMessageEncoder.java b/src/org/apollo/net/release/r317/AddGlobalTileItemMessageEncoder.java index da8879cf..9e083ce1 100644 --- a/src/org/apollo/net/release/r317/AddGlobalTileItemMessageEncoder.java +++ b/src/org/apollo/net/release/r317/AddGlobalTileItemMessageEncoder.java @@ -1,6 +1,6 @@ package org.apollo.net.release.r317; -import org.apollo.game.message.impl.AddGlobalTileItemMessage; +import org.apollo.game.message.impl.SendPublicTileItemMessage; import org.apollo.net.codec.game.DataTransformation; import org.apollo.net.codec.game.DataType; import org.apollo.net.codec.game.GamePacket; @@ -8,14 +8,14 @@ import org.apollo.net.codec.game.GamePacketBuilder; import org.apollo.net.release.MessageEncoder; /** - * A {@link MessageEncoder} for the {@link AddGlobalTileItemMessage}. + * A {@link MessageEncoder} for the {@link SendPublicTileItemMessage}. * * @author Major */ -public final class AddGlobalTileItemMessageEncoder extends MessageEncoder { +public final class AddGlobalTileItemMessageEncoder extends MessageEncoder { @Override - public GamePacket encode(AddGlobalTileItemMessage message) { + public GamePacket encode(SendPublicTileItemMessage message) { GamePacketBuilder builder = new GamePacketBuilder(215); builder.put(DataType.SHORT, DataTransformation.ADD, message.getId()); builder.put(DataType.BYTE, DataTransformation.SUBTRACT, message.getPositionOffset()); diff --git a/src/org/apollo/net/release/r317/AddTileItemMessageEncoder.java b/src/org/apollo/net/release/r317/AddTileItemMessageEncoder.java index 9e56ce3a..07a775c8 100644 --- a/src/org/apollo/net/release/r317/AddTileItemMessageEncoder.java +++ b/src/org/apollo/net/release/r317/AddTileItemMessageEncoder.java @@ -1,6 +1,6 @@ package org.apollo.net.release.r317; -import org.apollo.game.message.impl.AddTileItemMessage; +import org.apollo.game.message.impl.SendTileItemMessage; import org.apollo.net.codec.game.DataOrder; import org.apollo.net.codec.game.DataTransformation; import org.apollo.net.codec.game.DataType; @@ -9,14 +9,14 @@ import org.apollo.net.codec.game.GamePacketBuilder; import org.apollo.net.release.MessageEncoder; /** - * A {@link MessageEncoder} for the {@link AddTileItemMessage}. + * A {@link MessageEncoder} for the {@link SendTileItemMessage}. * * @author Major */ -public final class AddTileItemMessageEncoder extends MessageEncoder { +public final class AddTileItemMessageEncoder extends MessageEncoder { @Override - public GamePacket encode(AddTileItemMessage message) { + public GamePacket encode(SendTileItemMessage message) { GamePacketBuilder builder = new GamePacketBuilder(44); builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, message.getId()); builder.put(DataType.SHORT, message.getAmount()); diff --git a/src/org/apollo/net/release/r317/ClearRegionMessageEncoder.java b/src/org/apollo/net/release/r317/ClearRegionMessageEncoder.java new file mode 100644 index 00000000..abd70b17 --- /dev/null +++ b/src/org/apollo/net/release/r317/ClearRegionMessageEncoder.java @@ -0,0 +1,29 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.ClearRegionMessage; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link ClearRegionMessage}. + * + * @author Major + */ +public final class ClearRegionMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(ClearRegionMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(64); + Position player = message.getPlayerPosition(), region = message.getRegionPosition(); + + builder.put(DataType.BYTE, DataTransformation.NEGATE, region.getLocalX(player)); + builder.put(DataType.BYTE, DataTransformation.SUBTRACT, region.getLocalY(player)); + + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/GroupedRegionUpdateMessageEncoder.java b/src/org/apollo/net/release/r317/GroupedRegionUpdateMessageEncoder.java new file mode 100644 index 00000000..a2de6474 --- /dev/null +++ b/src/org/apollo/net/release/r317/GroupedRegionUpdateMessageEncoder.java @@ -0,0 +1,58 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.GroupedRegionUpdateMessage; +import org.apollo.game.message.impl.RegionUpdateMessage; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.meta.PacketType; +import org.apollo.net.release.MessageEncoder; +import org.apollo.net.release.Release; + +/** + * A {@link MessageEncoder} for the {@link GroupedRegionUpdateMessage}. + * + * @author Major + */ +public final class GroupedRegionUpdateMessageEncoder extends MessageEncoder { + + /** + * The Release containing the MessageEncoders for the RegionUpdateMessages. + */ + private final Release release; + + /** + * Creates the GroupedRegionUpdateMessageEncoder. + * + * @param release The {@link Release} containing the {@link MessageEncoder}s for the {@link RegionUpdateMessage}s. + */ + public GroupedRegionUpdateMessageEncoder(Release release) { + this.release = release; + } + + @Override + public GamePacket encode(GroupedRegionUpdateMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(60, PacketType.VARIABLE_SHORT); + Position player = message.getPlayerPosition(), region = message.getRegionPosition(); + + builder.put(DataType.BYTE, player.getLocalY(region)); + System.out.println("Grum: local x: " + player.getLocalX(region) + ", local y: " + player.getLocalY(region)); + builder.put(DataType.BYTE, DataTransformation.NEGATE, player.getLocalX(region)); + + for (RegionUpdateMessage update : message.getMessages()) { + System.out.println("==== Sending " + update + " as part of grum"); + @SuppressWarnings("unchecked") + MessageEncoder encoder = (MessageEncoder) release.getMessageEncoder(update + .getClass()); + + GamePacket packet = encoder.encode(update); + builder.put(DataType.BYTE, packet.getOpcode()); + builder.putBytes(packet.getPayload()); + } + + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/PositionMessageEncoder.java b/src/org/apollo/net/release/r317/PositionMessageEncoder.java deleted file mode 100644 index ca243e2e..00000000 --- a/src/org/apollo/net/release/r317/PositionMessageEncoder.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.apollo.net.release.r317; - -import org.apollo.game.message.impl.PositionMessage; -import org.apollo.game.model.Position; -import org.apollo.net.codec.game.DataTransformation; -import org.apollo.net.codec.game.DataType; -import org.apollo.net.codec.game.GamePacket; -import org.apollo.net.codec.game.GamePacketBuilder; -import org.apollo.net.release.MessageEncoder; - -/** - * A {@link MessageEncoder} for the {@link PositionMessage}. - * - * @author Chris Fletcher - */ -final class PositionMessageEncoder extends MessageEncoder { - - @Override - public GamePacket encode(PositionMessage message) { - GamePacketBuilder builder = new GamePacketBuilder(85); - Position base = message.getBase(), pos = message.getPosition(); - - builder.put(DataType.BYTE, DataTransformation.NEGATE, pos.getLocalY(base)); - builder.put(DataType.BYTE, DataTransformation.NEGATE, pos.getLocalX(base)); - - return builder.toGamePacket(); - } - -} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/Release317.java b/src/org/apollo/net/release/r317/Release317.java index 72d3cc3b..edeed4f9 100644 --- a/src/org/apollo/net/release/r317/Release317.java +++ b/src/org/apollo/net/release/r317/Release317.java @@ -1,7 +1,6 @@ package org.apollo.net.release.r317; -import org.apollo.game.message.impl.AddGlobalTileItemMessage; -import org.apollo.game.message.impl.AddTileItemMessage; +import org.apollo.game.message.impl.ClearRegionMessage; import org.apollo.game.message.impl.CloseInterfaceMessage; import org.apollo.game.message.impl.ConfigMessage; import org.apollo.game.message.impl.DisplayCrossbonesMessage; @@ -10,6 +9,7 @@ import org.apollo.game.message.impl.EnterAmountMessage; import org.apollo.game.message.impl.FlashTabInterfaceMessage; import org.apollo.game.message.impl.ForwardPrivateChatMessage; import org.apollo.game.message.impl.FriendServerStatusMessage; +import org.apollo.game.message.impl.GroupedRegionUpdateMessage; import org.apollo.game.message.impl.HintIconMessage; import org.apollo.game.message.impl.IdAssignmentMessage; import org.apollo.game.message.impl.IgnoreListMessage; @@ -22,15 +22,17 @@ import org.apollo.game.message.impl.OpenInterfaceSidebarMessage; import org.apollo.game.message.impl.OpenOverlayMessage; import org.apollo.game.message.impl.OpenSidebarMessage; import org.apollo.game.message.impl.PlayerSynchronizationMessage; -import org.apollo.game.message.impl.PositionMessage; import org.apollo.game.message.impl.PrivacyOptionMessage; +import org.apollo.game.message.impl.RegionChangeMessage; import org.apollo.game.message.impl.RemoveObjectMessage; import org.apollo.game.message.impl.RemoveTileItemMessage; -import org.apollo.game.message.impl.RegionChangeMessage; import org.apollo.game.message.impl.SendFriendMessage; import org.apollo.game.message.impl.SendObjectMessage; +import org.apollo.game.message.impl.SendPublicTileItemMessage; +import org.apollo.game.message.impl.SendTileItemMessage; import org.apollo.game.message.impl.ServerChatMessage; import org.apollo.game.message.impl.SetPlayerActionMessage; +import org.apollo.game.message.impl.SetUpdatedRegionMessage; import org.apollo.game.message.impl.SetWidgetItemModelMessage; import org.apollo.game.message.impl.SetWidgetModelAnimationMessage; import org.apollo.game.message.impl.SetWidgetNpcModelMessage; @@ -194,7 +196,7 @@ public final class Release317 extends Release { register(SetWidgetModelAnimationMessage.class, new SetWidgetModelAnimationMessageEncoder()); register(ConfigMessage.class, new ConfigMessageEncoder()); register(DisplayTabInterfaceMessage.class, new DisplayTabInterfaceMessageEncoder()); - register(PositionMessage.class, new PositionMessageEncoder()); + register(SetUpdatedRegionMessage.class, new SetUpdatedRegionMessageEncoder()); register(UpdateRunEnergyMessage.class, new UpdateRunEnergyMessageEncoder()); register(PrivacyOptionMessage.class, new PrivacyOptionMessageEncoder()); register(OpenDialogueInterfaceMessage.class, new OpenDialogueInterfaceMessageEncoder()); @@ -202,13 +204,16 @@ public final class Release317 extends Release { register(SetPlayerActionMessage.class, new SetPlayerActionMessageEncoder()); register(DisplayCrossbonesMessage.class, new DisplayCrossbonesMessageEncoder()); - register(AddGlobalTileItemMessage.class, new AddGlobalTileItemMessageEncoder()); - register(AddTileItemMessage.class, new AddTileItemMessageEncoder()); + register(SendPublicTileItemMessage.class, new AddGlobalTileItemMessageEncoder()); + register(SendTileItemMessage.class, new AddTileItemMessageEncoder()); register(UpdateTileItemMessage.class, new UpdateTileItemMessageEncoder()); register(RemoveTileItemMessage.class, new RemoveTileItemMessageEncoder()); register(SendObjectMessage.class, new SendObjectMessageEncoder()); register(RemoveObjectMessage.class, new RemoveObjectMessageEncoder()); + register(GroupedRegionUpdateMessage.class, new GroupedRegionUpdateMessageEncoder(this)); + register(ClearRegionMessage.class, new ClearRegionMessageEncoder()); + register(ForwardPrivateChatMessage.class, new ForwardPrivateChatMessageEncoder()); register(FriendServerStatusMessage.class, new FriendServerStatusMessageEncoder()); register(IgnoreListMessage.class, new IgnoreListMessageEncoder()); diff --git a/src/org/apollo/net/release/r317/RemoveObjectMessageEncoder.java b/src/org/apollo/net/release/r317/RemoveObjectMessageEncoder.java index bf86a1a9..e0f52106 100644 --- a/src/org/apollo/net/release/r317/RemoveObjectMessageEncoder.java +++ b/src/org/apollo/net/release/r317/RemoveObjectMessageEncoder.java @@ -19,6 +19,9 @@ public final class RemoveObjectMessageEncoder extends MessageEncoder encoder = (MessageEncoder) encoders.get(update); + + Preconditions.checkState(encoder != null, update.getClass() + + " does not have a registered encoder in GroupedRegionUpdateMessageEncoder."); + + GamePacket packet = encoder.encode(update); + builder.put(DataType.BYTE, packet.getOpcode()); + builder.putBytes(packet.getPayload()); + } + + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/PositionMessageEncoder.java b/src/org/apollo/net/release/r377/PositionMessageEncoder.java deleted file mode 100644 index 23926a20..00000000 --- a/src/org/apollo/net/release/r377/PositionMessageEncoder.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.apollo.net.release.r377; - -import org.apollo.game.message.impl.PositionMessage; -import org.apollo.game.model.Position; -import org.apollo.net.codec.game.DataTransformation; -import org.apollo.net.codec.game.DataType; -import org.apollo.net.codec.game.GamePacket; -import org.apollo.net.codec.game.GamePacketBuilder; -import org.apollo.net.release.MessageEncoder; - -/** - * A {@link MessageEncoder} for the {@link PositionMessage}. - * - * @author Chris Fletcher - * @author Major - */ -public final class PositionMessageEncoder extends MessageEncoder { - - @Override - public GamePacket encode(PositionMessage message) { - GamePacketBuilder builder = new GamePacketBuilder(75); - Position base = message.getBase(), pos = message.getPosition(); - - builder.put(DataType.BYTE, DataTransformation.NEGATE, pos.getLocalX(base)); - builder.put(DataType.BYTE, DataTransformation.ADD, pos.getLocalY(base)); - - return builder.toGamePacket(); - } - -} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/Release377.java b/src/org/apollo/net/release/r377/Release377.java index 7f979b8a..e57cc2bd 100644 --- a/src/org/apollo/net/release/r377/Release377.java +++ b/src/org/apollo/net/release/r377/Release377.java @@ -1,7 +1,9 @@ package org.apollo.net.release.r377; -import org.apollo.game.message.impl.AddGlobalTileItemMessage; -import org.apollo.game.message.impl.AddTileItemMessage; +import java.util.HashMap; +import java.util.Map; + +import org.apollo.game.message.impl.ClearRegionMessage; import org.apollo.game.message.impl.CloseInterfaceMessage; import org.apollo.game.message.impl.ConfigMessage; import org.apollo.game.message.impl.DisplayCrossbonesMessage; @@ -10,6 +12,7 @@ import org.apollo.game.message.impl.EnterAmountMessage; import org.apollo.game.message.impl.FlashTabInterfaceMessage; import org.apollo.game.message.impl.ForwardPrivateChatMessage; import org.apollo.game.message.impl.FriendServerStatusMessage; +import org.apollo.game.message.impl.GroupedRegionUpdateMessage; import org.apollo.game.message.impl.HintIconMessage; import org.apollo.game.message.impl.IdAssignmentMessage; import org.apollo.game.message.impl.IgnoreListMessage; @@ -22,15 +25,18 @@ import org.apollo.game.message.impl.OpenInterfaceSidebarMessage; import org.apollo.game.message.impl.OpenOverlayMessage; import org.apollo.game.message.impl.OpenSidebarMessage; import org.apollo.game.message.impl.PlayerSynchronizationMessage; -import org.apollo.game.message.impl.PositionMessage; import org.apollo.game.message.impl.PrivacyOptionMessage; +import org.apollo.game.message.impl.RegionChangeMessage; +import org.apollo.game.message.impl.RegionUpdateMessage; import org.apollo.game.message.impl.RemoveObjectMessage; import org.apollo.game.message.impl.RemoveTileItemMessage; -import org.apollo.game.message.impl.RegionChangeMessage; import org.apollo.game.message.impl.SendFriendMessage; import org.apollo.game.message.impl.SendObjectMessage; +import org.apollo.game.message.impl.SendPublicTileItemMessage; +import org.apollo.game.message.impl.SendTileItemMessage; import org.apollo.game.message.impl.ServerChatMessage; import org.apollo.game.message.impl.SetPlayerActionMessage; +import org.apollo.game.message.impl.SetUpdatedRegionMessage; import org.apollo.game.message.impl.SetWidgetItemModelMessage; import org.apollo.game.message.impl.SetWidgetModelAnimationMessage; import org.apollo.game.message.impl.SetWidgetNpcModelMessage; @@ -45,6 +51,7 @@ import org.apollo.game.message.impl.UpdateSlottedItemsMessage; import org.apollo.game.message.impl.UpdateTileItemMessage; import org.apollo.game.message.impl.UpdateWeightMessage; import org.apollo.net.meta.PacketMetaDataGroup; +import org.apollo.net.release.MessageEncoder; import org.apollo.net.release.Release; /** @@ -190,7 +197,7 @@ public final class Release377 extends Release { register(SetWidgetModelAnimationMessage.class, new SetWidgetModelAnimationMessageEncoder()); register(ConfigMessage.class, new ConfigMessageEncoder()); register(DisplayTabInterfaceMessage.class, new DisplayTabInterfaceMessageEncoder()); - register(PositionMessage.class, new PositionMessageEncoder()); + register(SetUpdatedRegionMessage.class, new SetUpdatedRegionMessageEncoder()); register(UpdateRunEnergyMessage.class, new UpdateRunEnergyMessageEncoder()); register(PrivacyOptionMessage.class, new PrivacyOptionMessageEncoder()); register(OpenDialogueInterfaceMessage.class, new OpenDialogueInterfaceMessageEncoder()); @@ -198,13 +205,35 @@ public final class Release377 extends Release { register(SetPlayerActionMessage.class, new SetPlayerActionMessageEncoder()); register(DisplayCrossbonesMessage.class, new DisplayCrossbonesMessageEncoder()); - register(AddGlobalTileItemMessage.class, new AddGlobalTileItemMessageEncoder()); - register(AddTileItemMessage.class, new AddTileItemMessageEncoder()); + register(SendPublicTileItemMessage.class, new AddGlobalTileItemMessageEncoder()); + register(SendTileItemMessage.class, new AddTileItemMessageEncoder()); register(UpdateTileItemMessage.class, new UpdateTileItemMessageEncoder()); register(RemoveTileItemMessage.class, new RemoveTileItemMessageEncoder()); register(SendObjectMessage.class, new SendObjectMessageEncoder()); register(RemoveObjectMessage.class, new RemoveObjectMessageEncoder()); + Map, MessageEncoder> regionUpdates = new HashMap<>(); + + regionUpdates.put(SendPublicTileItemMessage.class, new AddGlobalTileItemMessageEncoder()); + regionUpdates.put(SendTileItemMessage.class, new AddTileItemMessageEncoder()); + regionUpdates.put(UpdateTileItemMessage.class, new UpdateTileItemMessageEncoder()); + regionUpdates.put(RemoveTileItemMessage.class, new RemoveTileItemMessageEncoder()); + regionUpdates.put(SendObjectMessage.class, new SendObjectMessageEncoder()); + regionUpdates.put(RemoveObjectMessage.class, new RemoveObjectMessageEncoder()); + + for (Map.Entry, MessageEncoder> entry : regionUpdates + .entrySet()) { + @SuppressWarnings("unchecked") + Class clazz = (Class) entry.getKey(); + @SuppressWarnings("unchecked") + MessageEncoder encoder = (MessageEncoder) entry.getValue(); + + register(clazz, encoder); + } + + register(GroupedRegionUpdateMessage.class, new GroupedRegionUpdateMessageEncoder(regionUpdates)); + register(ClearRegionMessage.class, new ClearRegionMessageEncoder()); + register(ForwardPrivateChatMessage.class, new ForwardPrivateChatMessageEncoder()); register(FriendServerStatusMessage.class, new FriendServerStatusMessageEncoder()); register(IgnoreListMessage.class, new IgnoreListMessageEncoder()); diff --git a/src/org/apollo/net/release/r377/SetUpdatedRegionMessageEncoder.java b/src/org/apollo/net/release/r377/SetUpdatedRegionMessageEncoder.java new file mode 100644 index 00000000..dcb0bd9a --- /dev/null +++ b/src/org/apollo/net/release/r377/SetUpdatedRegionMessageEncoder.java @@ -0,0 +1,30 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.SetUpdatedRegionMessage; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link SetUpdatedRegionMessage}. + * + * @author Chris Fletcher + * @author Major + */ +public final class SetUpdatedRegionMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(SetUpdatedRegionMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(75); + Position base = message.getPlayerPosition(), position = message.getRegionPosition(); + + builder.put(DataType.BYTE, DataTransformation.NEGATE, position.getLocalX(base)); + builder.put(DataType.BYTE, DataTransformation.ADD, position.getLocalY(base)); + + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/util/DatabaseUtil.java b/src/org/apollo/util/DatabaseUtil.java deleted file mode 100644 index c4434a5f..00000000 --- a/src/org/apollo/util/DatabaseUtil.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.apollo.util; - -import com.mchange.v2.c3p0.ComboPooledDataSource; -import org.apollo.util.xml.XmlNode; -import org.apollo.util.xml.XmlParser; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.sql.Connection; -import java.sql.SQLException; - -/** - * - * - * @author Stuart - */ -public final class DatabaseUtil { - - /** - * - */ - private static final ComboPooledDataSource pool = new ComboPooledDataSource(); - - static { - try(InputStream is = new FileInputStream("data/database.xml")) { - XmlNode config = new XmlParser().parse(is); - if(!config.getName().equals("database")) { - throw new IOException("Root node is not 'database'."); - } - - XmlNode url = config.getChild("url"), username = config.getChild("username"), password = config.getChild("password"); - if(url == null || username == null || password == null) { - throw new IOException("Root node must contain these three children - 'url', 'username', 'password'."); - } - - pool.setJdbcUrl(url.getValue()); - pool.setUser(username.getValue()); - pool.setPassword(password.getValue()); - - // optional params - XmlNode initialPoolSize = config.getChild("initial_pool_size"), acquireIncrement = config.getChild("acquire_increment"), - maxPoolSize = config.getChild("max_pool_size"), minPoolSize = config.getChild("min_pool_size"); - - if(initialPoolSize != null) { - pool.setInitialPoolSize(Integer.parseInt(initialPoolSize.getValue())); - } - if(acquireIncrement != null) { - pool.setAcquireIncrement(Integer.parseInt(acquireIncrement.getValue())); - } - if(maxPoolSize != null) { - pool.setMaxPoolSize(Integer.parseInt(maxPoolSize.getValue())); - } - if(minPoolSize != null) { - pool.setMinPoolSize(Integer.parseInt(minPoolSize.getValue())); - } - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * - * - * @return - * @throws SQLException - */ - public static Connection getConnection() throws SQLException { - return pool.getConnection(); - } - -} \ No newline at end of file