Add half-done grouped region frame stuff so Ryley can take a look. This is in master because I'm an idiot.

This commit is contained in:
Major-
2015-03-22 11:07:24 +00:00
parent 30045a6f10
commit 7cf31a2888
62 changed files with 1818 additions and 576 deletions
+10 -5
View File
@@ -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
+10 -8
View File
@@ -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
+24 -40
View File
@@ -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
@@ -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);
@@ -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.
+11 -3
View File
@@ -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);
}
/**
@@ -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<Object
ctx.breakHandlerChain();
return;
}
Position position = message.getPosition();
Region region = repository.fromPosition(position);
Set<GameObject> objects = region.getEntities(position, EntityType.GAME_OBJECT);
Set<GameObject> objects = region.getEntities(position, EntityType.STATIC_OBJECT, EntityType.DYNAMIC_OBJECT);
if (!player.getPosition().isWithinDistance(position, 15) || !containsObject(id, objects)) {
ctx.breakHandlerChain();
@@ -19,9 +19,9 @@ public final class WalkMessageHandler extends MessageHandler<WalkMessage> {
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<WalkMessage> {
}
queue.setRunningQueue(message.isRunning() || player.isRunning());
player.getInterfaceSet().close();
if (queue.size() > 0) {
player.stopAction();
}
player.getInterfaceSet().close();
if (player.getInteractingMob() != null) {
player.resetInteractingMob();
}
}
}
@@ -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;
}
}
@@ -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<RegionUpdateMessage> 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<RegionUpdateMessage> 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<RegionUpdateMessage> getMessages() {
return messages;
}
/**
* Gets the {@link Position} of the Region these updates affect.
*
* @return The Position.
*/
public Position getRegionPosition() {
return region;
}
}
@@ -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;
}
}
@@ -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}.
* <p>
* 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<RegionUpdateMessage> {
/**
* 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();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
+16
View File
@@ -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();
+34 -19
View File
@@ -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..
*
@@ -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;
}
+101 -37
View File
@@ -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<List<RegionUpdateMessage>> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS);
/**
* The Set containing UpdateOperations.
*/
private final List<List<RegionUpdateMessage>> 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<Entity> 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 <T extends Entity> Set<T> getEntities(Position position, EntityType type) {
public <T extends Entity> Set<T> getEntities(Position position, EntityType... types) {
Set<Entity> local = entities.get(position);
if (local == null) {
return ImmutableSet.of();
}
Set<EntityType> set = new HashSet<>(Arrays.asList(types));
@SuppressWarnings("unchecked")
Set<T> filtered = (Set<T>) local.stream().filter(Entity -> Entity.getEntityType() == type).collect(Collectors.toSet());
Set<T> filtered = (Set<T>) 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.
* <p>
* 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<RegionUpdateMessage> getSnapshot(int height) {
List<RegionUpdateMessage> copy = new ArrayList<>(snapshots.get(height));
Collections.sort(copy);
return ImmutableList.copyOf(copy);
}
Set<Entity> 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<RegionUpdateMessage> getUpdates(int height) {
List<RegionUpdateMessage> original = this.updates.get(height);
List<RegionUpdateMessage> 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);
}
}
@@ -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).
*
@@ -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);
}
@@ -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;
}
@@ -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;
}
}
@@ -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<GroundItem> {
/**
* 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);
}
}
@@ -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<GameObject> {
/**
* 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);
}
}
@@ -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 <E> The type of {@link Entity} in this type.
*/
public abstract class UpdateOperation<E extends Entity> {
/**
* 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;
}
}
@@ -0,0 +1,4 @@
/**
* Contains snapshot-related classes.
*/
package org.apollo.game.model.area.update;
@@ -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;
+35 -11
View File
@@ -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);
}
@@ -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<GroundItem> toUpdateOperation(Region region, EntityUpdateType operation) {
return new ItemUpdateOperation(region, operation, this);
}
}
+27 -23
View File
@@ -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.
* <p>
* This method may be intercepted using a {@link MobPositionUpdateEvent}, which can be terminated like any other.
* Plugins that intercept this Event <strong>must</strong> 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<Entity> 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}.
*
-13
View File
@@ -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);
}
}
+27 -31
View File
@@ -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<Message> 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);
@@ -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<Player> {
/**
* 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<WeakPlayerReference> 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));
}
}
@@ -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);
}
@@ -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),
@@ -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
@@ -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);
}
}
@@ -1,4 +1,4 @@
/**
* Contains object-related classes.
*/
package org.apollo.game.model.area.obj;
package org.apollo.game.model.entity.obj;
@@ -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<RegionCoordinates, List<RegionUpdateMessage>> updates = new HashMap<>();
Map<RegionCoordinates, List<RegionUpdateMessage>> 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();
@@ -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<Player> players = World.getWorld().getPlayerRepository();
MobRepository<Npc> npcs = World.getWorld().getNpcRepository();
Map<RegionCoordinates, List<RegionUpdateMessage>> updates = new HashMap<>();
Map<RegionCoordinates, List<RegionUpdateMessage>> snapshots = new HashMap<>();
for (Player player : players) {
SynchronizationTask task = new PrePlayerSynchronizationTask(player);
SynchronizationTask task = new PrePlayerSynchronizationTask(player, updates, snapshots);
task.run();
}
@@ -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<RegionCoordinates, List<RegionUpdateMessage>> snapshots;
/**
* The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain the updates for a Region a Player can
* already view.
*/
private final Map<RegionCoordinates, List<RegionUpdateMessage>> updates;
/**
* 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<RegionCoordinates, List<RegionUpdateMessage>> updates,
Map<RegionCoordinates, List<RegionUpdateMessage>> 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<RegionCoordinates> 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<RegionCoordinates> getNewRegions(Position old, Position next) {
RegionCoordinates oldRegion = old.getRegionCoordinates();
RegionCoordinates nextRegion = next.getRegionCoordinates();
if (oldRegion.equals(nextRegion)) {
return ImmutableSet.of();
}
Set<RegionCoordinates> 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<RegionCoordinates> newRegions) {
Position position = player.getPosition();
RegionCoordinates base = position.getRegionCoordinates();
int baseX = base.getX(), baseY = base.getY();
RegionRepository repository = World.getWorld().getRegionRepository();
List<GroupedRegionUpdateMessage> 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<GroupedRegionUpdateMessage> 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<GroupedRegionUpdateMessage> toUpdateMessage(RegionUpdateMode mode, Position position,
RegionCoordinates coordinates, RegionRepository repository) {
List<RegionUpdateMessage> 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<RegionUpdateMessage> 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));
}
}
@@ -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<AddGlobalTileItemMessage> {
public final class AddGlobalTileItemMessageEncoder extends MessageEncoder<SendPublicTileItemMessage> {
@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());
@@ -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<AddTileItemMessage> {
public final class AddTileItemMessageEncoder extends MessageEncoder<SendTileItemMessage> {
@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());
@@ -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<ClearRegionMessage> {
@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();
}
}
@@ -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<GroupedRegionUpdateMessage> {
/**
* 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<RegionUpdateMessage> encoder = (MessageEncoder<RegionUpdateMessage>) release.getMessageEncoder(update
.getClass());
GamePacket packet = encoder.encode(update);
builder.put(DataType.BYTE, packet.getOpcode());
builder.putBytes(packet.getPayload());
}
return builder.toGamePacket();
}
}
@@ -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<PositionMessage> {
@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();
}
}
@@ -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());
@@ -19,6 +19,9 @@ public final class RemoveObjectMessageEncoder extends MessageEncoder<RemoveObjec
GamePacketBuilder builder = new GamePacketBuilder(101);
builder.put(DataType.BYTE, DataTransformation.NEGATE, message.getType() << 2 | message.getOrientation());
builder.put(DataType.BYTE, message.getPositionOffset());
System.out.println("Sending rm obj: type=" + message.getType() + ", orient=" + message.getOrientation() + ",posoff="
+ Integer.toBinaryString(message.getPositionOffset()));
return builder.toGamePacket();
}
@@ -0,0 +1,29 @@
package org.apollo.net.release.r317;
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
*/
final class SetUpdatedRegionMessageEncoder extends MessageEncoder<SetUpdatedRegionMessage> {
@Override
public GamePacket encode(SetUpdatedRegionMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(85);
Position player = message.getPlayerPosition(), region = message.getRegionPosition();
builder.put(DataType.BYTE, DataTransformation.NEGATE, region.getLocalY(player));
builder.put(DataType.BYTE, DataTransformation.NEGATE, region.getLocalX(player));
return builder.toGamePacket();
}
}
@@ -1,6 +1,6 @@
package org.apollo.net.release.r377;
import org.apollo.game.message.impl.AddGlobalTileItemMessage;
import org.apollo.game.message.impl.SendPublicTileItemMessage;
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 AddGlobalTileItemMessage}.
* A {@link MessageEncoder} for the {@link SendPublicTileItemMessage}.
*
* @author Major
*/
public final class AddGlobalTileItemMessageEncoder extends MessageEncoder<AddGlobalTileItemMessage> {
public final class AddGlobalTileItemMessageEncoder extends MessageEncoder<SendPublicTileItemMessage> {
@Override
public GamePacket encode(AddGlobalTileItemMessage message) {
public GamePacket encode(SendPublicTileItemMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(106);
builder.put(DataType.BYTE, DataTransformation.ADD, message.getPositionOffset());
builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, message.getAmount());
@@ -1,6 +1,6 @@
package org.apollo.net.release.r377;
import org.apollo.game.message.impl.AddTileItemMessage;
import org.apollo.game.message.impl.SendTileItemMessage;
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 AddTileItemMessage}.
* A {@link MessageEncoder} for the {@link SendTileItemMessage}.
*
* @author Major
*/
public final class AddTileItemMessageEncoder extends MessageEncoder<AddTileItemMessage> {
public final class AddTileItemMessageEncoder extends MessageEncoder<SendTileItemMessage> {
@Override
public GamePacket encode(AddTileItemMessage message) {
public GamePacket encode(SendTileItemMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(107);
builder.put(DataType.SHORT, message.getId());
builder.put(DataType.BYTE, DataTransformation.NEGATE, message.getPositionOffset());
@@ -0,0 +1,29 @@
package org.apollo.net.release.r377;
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<ClearRegionMessage> {
@Override
public GamePacket encode(ClearRegionMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(40);
Position player = message.getPlayerPosition(), region = message.getRegionPosition();
builder.put(DataType.BYTE, DataTransformation.SUBTRACT, region.getLocalY(player));
builder.put(DataType.BYTE, DataTransformation.NEGATE, region.getLocalX(player));
return builder.toGamePacket();
}
}
@@ -0,0 +1,63 @@
package org.apollo.net.release.r377;
import java.util.Map;
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 com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
/**
* A {@link MessageEncoder} for the {@link GroupedRegionUpdateMessage}.
*
* @author Major
*/
public final class GroupedRegionUpdateMessageEncoder extends MessageEncoder<GroupedRegionUpdateMessage> {
/**
* The Map of RegionUpdateMessages to MessageEncoders.
*/
private final Map<Class<? extends RegionUpdateMessage>, MessageEncoder<? extends RegionUpdateMessage>> encoders;
/**
* Creates the GroupedRegionUpdateMessageEncoder.
*
* @param encoders The Map of RegionUpdateMessages to MessageEncoders.
*/
public GroupedRegionUpdateMessageEncoder(
Map<Class<? extends RegionUpdateMessage>, MessageEncoder<? extends RegionUpdateMessage>> encoders) {
this.encoders = ImmutableMap.copyOf(encoders);
}
@Override
public GamePacket encode(GroupedRegionUpdateMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(183, PacketType.VARIABLE_SHORT);
Position base = message.getPlayerPosition(), region = message.getRegionPosition();
builder.put(DataType.BYTE, region.getLocalX(base));
builder.put(DataType.BYTE, DataTransformation.ADD, region.getLocalY(base));
for (RegionUpdateMessage update : message.getMessages()) {
@SuppressWarnings("unchecked")
MessageEncoder<RegionUpdateMessage> encoder = (MessageEncoder<RegionUpdateMessage>) 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();
}
}
@@ -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<PositionMessage> {
@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();
}
}
@@ -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<Class<? extends RegionUpdateMessage>, MessageEncoder<? extends RegionUpdateMessage>> 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<Class<? extends RegionUpdateMessage>, MessageEncoder<? extends RegionUpdateMessage>> entry : regionUpdates
.entrySet()) {
@SuppressWarnings("unchecked")
Class<RegionUpdateMessage> clazz = (Class<RegionUpdateMessage>) entry.getKey();
@SuppressWarnings("unchecked")
MessageEncoder<RegionUpdateMessage> encoder = (MessageEncoder<RegionUpdateMessage>) 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());
@@ -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<SetUpdatedRegionMessage> {
@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();
}
}
-72
View File
@@ -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();
}
}