Modularise! Also add some unit tests.

This commit is contained in:
Major-
2015-05-26 13:49:27 +01:00
parent 902a203861
commit e4778105f5
658 changed files with 1532 additions and 1004 deletions
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>apollo</groupId>
<artifactId>org.apollo</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>game</artifactId>
<version>0.0.1</version>
<name>Apollo Game</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>src/main</sourceDirectory>
<testSourceDirectory>src/test</testSourceDirectory>
</build>
<dependencies>
<dependency>
<groupId>apollo</groupId>
<artifactId>cache</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>apollo</groupId>
<artifactId>net</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>apollo</groupId>
<artifactId>util</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
+160
View File
@@ -0,0 +1,160 @@
package org.apollo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.game.model.World;
import org.apollo.game.plugin.PluginContext;
import org.apollo.game.plugin.PluginManager;
import org.apollo.game.release.r317.Release317;
import org.apollo.game.session.ApolloHandler;
import org.apollo.net.HttpChannelInitializer;
import org.apollo.net.JagGrabChannelInitializer;
import org.apollo.net.NetworkConstants;
import org.apollo.net.ServiceChannelInitializer;
import org.apollo.net.release.Release;
import com.google.common.base.Stopwatch;
/**
* The core class of the Apollo server.
*
* @author Graham
*/
public final class Server {
/**
* The logger for this class.
*/
private static final Logger logger = Logger.getLogger(Server.class.getName());
/**
* The entry point of the Apollo server application.
*
* @param args The command-line arguments passed to the application.
*/
public static void main(String[] args) {
Stopwatch stopwatch = Stopwatch.createStarted();
try {
Server server = new Server();
server.init(args.length == 1 ? args[0] : Release317.class.getName());
SocketAddress service = new InetSocketAddress(NetworkConstants.SERVICE_PORT);
SocketAddress http = new InetSocketAddress(NetworkConstants.HTTP_PORT);
SocketAddress jaggrab = new InetSocketAddress(NetworkConstants.JAGGRAB_PORT);
server.bind(service, http, jaggrab);
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error whilst starting server.", t);
}
logger.fine("Starting apollo took " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms.");
}
/**
* The {@link ServerBootstrap} for the HTTP listener.
*/
private final ServerBootstrap httpBootstrap = new ServerBootstrap();
/**
* The {@link ServerBootstrap} for the JAGGRAB listener.
*/
private final ServerBootstrap jagGrabBootstrap = new ServerBootstrap();
/**
* The {@link ServerBootstrap} for the service listener.
*/
private final ServerBootstrap serviceBootstrap = new ServerBootstrap();
/**
* The event loop group.
*/
private final EventLoopGroup loopGroup = new NioEventLoopGroup();
/**
* Creates the Apollo server.
*/
public Server() {
logger.info("Starting Apollo...");
}
/**
* Binds the server to the specified address.
*
* @param serviceAddress The service address to bind to.
* @param httpAddress The HTTP address to bind to.
* @param jagGrabAddress The JAGGRAB address to bind to.
*/
public void bind(SocketAddress serviceAddress, SocketAddress httpAddress, SocketAddress jagGrabAddress) {
try {
logger.fine("Binding service listener to address: " + serviceAddress + "...");
serviceBootstrap.bind(serviceAddress).sync();
logger.fine("Binding HTTP listener to address: " + httpAddress + "...");
httpBootstrap.bind(httpAddress).sync();
logger.fine("Binding JAGGRAB listener to address: " + jagGrabAddress + "...");
jagGrabBootstrap.bind(jagGrabAddress).sync();
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Binding to a port failed: ensure apollo isn't already running.", e);
System.exit(1);
}
logger.info("Ready for connections.");
}
/**
* Initialises the server.
*
* @param releaseClassName The class name of the current active {@link Release}.
* @throws Exception If an error occurs.
*/
public void init(String releaseClassName) throws Exception {
Class<?> clazz = Class.forName(releaseClassName);
Release release = (Release) clazz.newInstance();
int releaseNo = release.getReleaseNumber();
logger.info("Initialized release #" + releaseNo + ".");
serviceBootstrap.group(loopGroup);
httpBootstrap.group(loopGroup);
jagGrabBootstrap.group(loopGroup);
World world = new World();
ServiceManager services = new ServiceManager(world);
IndexedFileSystem fs = new IndexedFileSystem(Paths.get("data/fs", Integer.toString(releaseNo)), true);
ServerContext context = new ServerContext(release, services, fs);
ApolloHandler handler = new ApolloHandler(context);
ChannelInitializer<SocketChannel> serviceInitializer = new ServiceChannelInitializer(handler);
serviceBootstrap.channel(NioServerSocketChannel.class);
serviceBootstrap.childHandler(serviceInitializer);
ChannelInitializer<SocketChannel> httpInitializer = new HttpChannelInitializer(handler);
httpBootstrap.channel(NioServerSocketChannel.class);
httpBootstrap.childHandler(httpInitializer);
ChannelInitializer<SocketChannel> jagGrabInitializer = new JagGrabChannelInitializer(handler);
jagGrabBootstrap.channel(NioServerSocketChannel.class);
jagGrabBootstrap.childHandler(jagGrabInitializer);
PluginManager manager = new PluginManager(world, new PluginContext(context));
services.startAll();
world.init(releaseNo, fs, manager);
}
}
@@ -0,0 +1,95 @@
package org.apollo;
import java.util.Objects;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.game.service.GameService;
import org.apollo.game.service.LoginService;
import org.apollo.game.service.UpdateService;
import org.apollo.net.release.Release;
/**
* A {@link ServerContext} is created along with the {@link Server} object. The primary difference is that a reference
* to the current context should be passed around within the server. The {@link Server} should not be as it allows
* access to some methods such as {@link Server#bind} which user scripts/code should not be able to access.
*
* @author Graham
* @author Major
*/
public final class ServerContext {
/**
* The IndexedFileSystem.
*/
private final IndexedFileSystem fileSystem;
/**
* The current release.
*/
private final Release release;
/**
* The service manager.
*/
private final ServiceManager services;
/**
* Creates a new server context.
*
* @param release The current release.
* @param services The service manager.
* @param fileSystem The indexed file system.
*/
protected ServerContext(Release release, ServiceManager services, IndexedFileSystem fileSystem) {
this.release = Objects.requireNonNull(release);
this.services = Objects.requireNonNull(services);
this.services.setContext(this);
this.fileSystem = Objects.requireNonNull(fileSystem);
}
/**
* Gets the IndexeFileSystem
*
* @return The IndexedFileSystem.
*/
public IndexedFileSystem getFileSystem() {
return fileSystem;
}
/**
* Gets the {@link GameService}.
*
* @return The GameService.
*/
public GameService getGameService() {
return services.getGame();
}
/**
* Gets the {@link LoginService}.
*
* @return The LoginService.
*/
public LoginService getLoginService() {
return services.getLogin();
}
/**
* Gets the current release.
*
* @return The current release.
*/
public Release getRelease() {
return release;
}
/**
* Gets the {@link UpdateService}.
*
* @return The UpdateService.
*/
public UpdateService getUpdateService() {
return services.getUpdate();
}
}
+39
View File
@@ -0,0 +1,39 @@
package org.apollo;
/**
* Represents a service that the server provides for a World.
*
* @author Graham
*/
public abstract class Service {
/**
* The server context.
*/
protected ServerContext context;
/**
* Gets the {@link ServerContext}.
*
* @return The context.
*/
public final ServerContext getContext() {
return context;
}
/**
* Sets the {@link ServerContext}.
*
* @param context The context.
*/
public final void setContext(ServerContext context) {
this.context = context;
}
/**
* Starts the service.
*/
public abstract void start();
}
@@ -0,0 +1,97 @@
package org.apollo;
import java.util.logging.Logger;
import org.apollo.game.model.World;
import org.apollo.game.service.GameService;
import org.apollo.game.service.LoginService;
import org.apollo.game.service.UpdateService;
/**
* A class which manages {@link Service}s.
*
* @author Graham
* @author Major
*/
public final class ServiceManager {
/**
* The Logger for this class.
*/
private static final Logger logger = Logger.getLogger(ServiceManager.class.getName());
/**
* The GameService.
*/
private final GameService game;
/**
* The LoginService.
*/
private final LoginService login;
/**
* The UpdateService.
*/
private final UpdateService update = new UpdateService();
/**
* Creates and initializes the {@link ServiceManager}.
*
* @param world The {@link World} to create the {@link Service}s for.
* @throws Exception If there is an error creating the Services.
*/
public ServiceManager(World world) throws Exception {
game = new GameService(world);
login = new LoginService(world);
}
/**
* Gets the {@link GameService}.
*
* @return The GameService.
*/
public GameService getGame() {
return game;
}
/**
* Gets the {@link LoginService}.
*
* @return The LoginService.
*/
public LoginService getLogin() {
return login;
}
/**
* Gets the {@link UpdateService}.
*
* @return The UpdateService.
*/
public UpdateService getUpdate() {
return update;
}
/**
* Sets the context of all services.
*
* @param context The server context.
*/
public void setContext(ServerContext context) {
game.setContext(context);
login.setContext(context);
update.setContext(context);
}
/**
* Starts all the services.
*/
public void startAll() {
logger.info("Starting services...");
game.start();
login.start();
update.start();
}
}
@@ -0,0 +1,27 @@
package org.apollo.game;
/**
* Contains game-related constants.
*
* @author Graham
*/
public final class GameConstants {
/**
* The maximum amount of messages to process per pulse (per session).
*/
public static final int MESSAGES_PER_PULSE = 10;
/**
* The delay between consecutive pulses, in milliseconds.
*/
public static final int PULSE_DELAY = 600;
/**
* Default private constructor to prevent instantiation by other classes.
*/
private GameConstants() {
}
}
@@ -0,0 +1,43 @@
package org.apollo.game;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apollo.game.service.GameService;
/**
* A class which handles the logic for each pulse of the {@link GameService}.
*
* @author Graham
*/
public final class GamePulseHandler implements Runnable {
/**
* The logger for this class.
*/
private static final Logger logger = Logger.getLogger(GamePulseHandler.class.getName());
/**
* The {@link GameService}.
*/
private final GameService service;
/**
* Creates the game pulse handler object.
*
* @param service The {@link GameService}.
*/
public GamePulseHandler(GameService service) {
this.service = service;
}
@Override
public void run() {
try {
service.pulse();
} catch (Throwable reason) {
logger.log(Level.SEVERE, "Exception occured during pulse!", reason);
}
}
}
@@ -0,0 +1,58 @@
package org.apollo.game.action;
import org.apollo.game.model.entity.Mob;
import org.apollo.game.scheduling.ScheduledTask;
/**
* An action is a specialised {@link ScheduledTask} that is specific to a {@link Mob}.
* <p>
* <strong>ALL</strong> actions <strong>MUST</strong> implement the {@link #equals(Object)} method. This is to check if
* two actions are identical: if they are, then the new action does not replace the old one (so spam/accidental clicking
* won't cancel your action, and start another from scratch).
*
* @author Graham
* @param <T> The type of mob.
*/
public abstract class Action<T extends Mob> extends ScheduledTask {
/**
* The mob performing the action.
*/
protected final T mob;
/**
* A flag indicating if this action is stopping.
*/
private boolean stopping = false;
/**
* Creates a new action.
*
* @param delay The delay in pulses.
* @param immediate A flag indicating if the action should happen immediately.
* @param mob The mob performing the action.
*/
public Action(int delay, boolean immediate, T mob) {
super(delay, immediate);
this.mob = mob;
}
/**
* Gets the mob which performed the action.
*
* @return The mob.
*/
public final T getMob() {
return mob;
}
@Override
public void stop() {
super.stop();
if (!stopping) {
stopping = true;
mob.stopAction();
}
}
}
@@ -0,0 +1,77 @@
package org.apollo.game.action;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.Mob;
/**
* An @{link Action} which fires when a distance requirement is met.
*
* @author Blake
* @author Graham
* @param <T> The type of {@link Mob}.
*/
public abstract class DistancedAction<T extends Mob> extends Action<T> {
/**
* The delay once the threshold is reached.
*/
private final int delay;
/**
* The minimum distance before the action fires.
*/
private final int distance;
/**
* A flag indicating if this action fires immediately after the threshold is reached.
*/
private final boolean immediate;
/**
* The position to distance check with.
*/
private final Position position;
/**
* A flag indicating if the distance has been reached yet.
*/
private boolean reached = false;
/**
* Creates a new DistancedAction.
*
* @param delay The delay between executions once the distance threshold is reached.
* @param immediate Whether or not this action fires immediately after the distance threshold is reached.
* @param mob The mob.
* @param position The position.
* @param distance The distance.
*/
public DistancedAction(int delay, boolean immediate, T mob, Position position, int distance) {
super(0, true, mob);
this.position = position;
this.distance = distance;
this.delay = delay;
this.immediate = immediate;
}
@Override
public final void execute() {
if (reached) { // Don't check again in case the player has moved away since it was reached
executeAction();
// TODO checking the walking queue size is a really cheap fix, and relies on the client not
// being edited... this class needs to be completely re-written.
} else if (mob.getPosition().getDistance(position) <= distance && mob.getWalkingQueue().size() == 0) {
reached = true;
setDelay(delay);
if (immediate) {
executeAction();
}
}
}
/**
* Executes the actual action. Called when the distance requirement is met.
*/
protected abstract void executeAction();
}
@@ -0,0 +1,4 @@
/**
* Contains classes related to actions, specialised scheduled tasks which mobs perform.
*/
package org.apollo.game.action;
@@ -0,0 +1,49 @@
package org.apollo.game.command;
/**
* Represents a command.
*
* @author Graham
*/
public final class Command {
/**
* The command's arguments.
*/
private final String[] arguments;
/**
* The name of the command.
*/
private final String name;
/**
* Creates the command.
*
* @param name The name of the command.
* @param arguments The command's arguments.
*/
public Command(String name, String[] arguments) {
this.name = name;
this.arguments = arguments;
}
/**
* Gets the command's arguments.
*
* @return The command's arguments.
*/
public String[] getArguments() {
return arguments;
}
/**
* Gets the name of the command.
*
* @return The name of the command.
*/
public String getName() {
return name;
}
}
@@ -0,0 +1,53 @@
package org.apollo.game.command;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apollo.game.model.entity.Player;
/**
* A class that dispatches {@link Command}s to {@link CommandListener}s.
*
* @author Graham
*/
public final class CommandDispatcher {
/**
* A map of command strings to command listeners.
*/
private final Map<String, CommandListener> listeners = new HashMap<>();
/**
* Initialises this CommandDispatcher.
*
* @param authors The {@link Set} of plugin authors.
*/
public void init(Set<String> authors) {
listeners.put("credits", new CreditsCommandListener(authors));
}
/**
* Dispatches a command to the appropriate listener.
*
* @param player The player.
* @param command The command.
*/
public void dispatch(Player player, Command command) {
CommandListener listener = listeners.get(command.getName().toLowerCase());
if (listener != null) {
listener.executePrivileged(player, command);
}
}
/**
* Registers a listener with the dispatcher.
*
* @param command The command's name.
* @param listener The listener.
*/
public void register(String command, CommandListener listener) {
listeners.put(command.toLowerCase(), listener);
}
}
@@ -0,0 +1,55 @@
package org.apollo.game.command;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.setting.PrivilegeLevel;
/**
* An interface which should be implemented to listen to {@link Command}s.
*
* @author Graham
* @author Major
*/
public abstract class CommandListener {
/**
* The minimum privilege level.
*/
private final PrivilegeLevel level;
/**
* Creates a new command listener with a {@link PrivilegeLevel#STANDARD} requirement.
*/
public CommandListener() {
this(PrivilegeLevel.STANDARD);
}
/**
* Creates a new command listener.
*
* @param level The required {@link PrivilegeLevel}.
*/
public CommandListener(PrivilegeLevel level) {
this.level = level;
}
/**
* Executes the action for this command.
*
* @param player The player.
* @param command The command.
*/
public abstract void execute(Player player, Command command);
/**
* Executes a privileged command.
*
* @param player The player.
* @param command The command.
*/
public final void executePrivileged(Player player, Command command) {
if (player.getPrivilegeLevel().toInteger() >= level.toInteger()) {
execute(player, command);
}
}
}
@@ -0,0 +1,61 @@
package org.apollo.game.command;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apollo.game.model.entity.Player;
import com.google.common.collect.ImmutableSet;
/**
* Implements a {@code ::credits} command that lists the authors of all plugins used in the server.
*
* @author Graham
*/
public final class CreditsCommandListener extends CommandListener {
/**
* The Set of authors.
*/
private final Set<String> authors;
/**
* Creates the CreditsCommandListener.
*
* @param authors The {@link Set} of authors.
*/
public CreditsCommandListener(Set<String> authors) {
this.authors = ImmutableSet.copyOf(authors);
}
/*
* If you are considering removing this command, please bear in mind that Apollo took several people thousands of
* hours to create. We released it to the world for free and it isn't much to ask to leave this command in. It isn't
* very obtrusive and gives us some well-deserved recognition for the work we have done. Thank you!
*
* The list of authors is generated from the plugin manager. If you create a custom plugin, make sure you add your
* name to the plugin.xml file and it'll appear here automatically!
*/
@Override
public void execute(Player player, Command command) {
List<String> text = new ArrayList<>(12 + authors.size());
text.add("@dre@Apollo");
text.add("@dre@Introduction");
text.add("");
text.add("This server is based on Apollo, a lightweight, fast, secure");
text.add("and open-source RuneScape emulator. For more");
text.add("information about Apollo, visit the website at:");
text.add("@dbl@https://github.com/apollo-rsps/apollo");
text.add("");
text.add("Apollo is released under the terms of the ISC license.");
text.add("");
text.add("@dre@Credits");
text.add("");
text.addAll(authors);
player.sendQuestInterface(text);
}
}
@@ -0,0 +1,4 @@
/**
* Contains classes related to in-game commands.
*/
package org.apollo.game.command;
@@ -0,0 +1,269 @@
package org.apollo.game.fs.decoder;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.fs.decoder.MapFileDecoder.MapDefinition;
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.RegionRepository;
import org.apollo.game.model.area.collision.CollisionMatrix;
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;
import com.google.common.collect.Iterables;
/**
* Parses static object definitions, which include map tiles and landscapes.
*
* @author Ryley
* @author Major
*/
public final class GameObjectDecoder {
/**
* A bit flag that indicates that the tile at the current Position is blocked.
*/
private static final int BLOCKED_TILE = 1;
/**
* A bit flag that indicates that the tile at the current Position is a bridge tile.
*/
private static final int BRIDGE_TILE = 2;
/**
* The {@link IndexedFileSystem}.
*/
private final IndexedFileSystem fs;
/**
* A {@link List} of decoded GameObjects.
*/
private final List<GameObject> objects = new ArrayList<>();
/**
* The RegionRepository.
*/
private final RegionRepository regions;
/**
* Creates the GameObjectDecoder.
*
* @param fs The {@link IndexedFileSystem}.
* @param regions The {@link RegionRepository}.
*/
public GameObjectDecoder(IndexedFileSystem fs, RegionRepository regions) {
this.fs = fs;
this.regions = regions;
}
/**
* Decodes the GameObjects from their MapDefinitions.
*
* @param world The {@link World} containing the StaticGameObjects.
* @return The decoded objects.
* @throws IOException If there is an error decoding the {@link MapDefinition}s.
*/
public GameObject[] decode(World world) throws IOException {
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
MapDefinition definition = entry.getValue();
int packed = definition.getPackedCoordinates();
int x = (packed >> 8 & 0xFF) * 64;
int y = (packed & 0xFF) * 64;
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
decodeObjects(world, decompressed, x, y);
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
decodeTerrain(decompressed, x, y);
}
return Iterables.toArray(objects, GameObject.class);
}
/**
* Blocks tiles covered by a GameObject, if applicable.
*
* @param object The {@link GameObject}.
* @param position The position of the GameObject.
*/
private void block(GameObject object, Position position) {
ObjectDefinition definition = ObjectDefinition.lookup(object.getId());
int type = object.getType();
Region region = regions.fromPosition(position);
int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix matrix = region.getMatrix(height);
boolean block = false;
if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) {
block = true;
}
Predicate<Integer> walls = (value) -> value >= ObjectType.LENGTHWISE_WALL.getValue()
&& value <= ObjectType.RECTANGULAR_CORNER.getValue() || value == ObjectType.DIAGONAL_WALL.getValue();
Predicate<Integer> roofs = (value) -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
&& value < ObjectType.FLOOR_DECORATION.getValue();
if (walls.test(type) || roofs.test(type)) {
block = true;
}
if (type == 10 && definition.isSolid()) {
block = true;
}
if (block) {
for (int dx = 0; dx < definition.getWidth(); dx++) {
for (int dy = 0; dy < definition.getLength(); 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;
int nextLocalY = localY > 7 ? y + localY - 7 : y - localY;
Position nextPosition = new Position(nextLocalX, nextLocalY);
Region next = regions.fromPosition(nextPosition);
int nextX = nextPosition.getX() % Region.SIZE + dx, nextY = nextPosition.getY() % Region.SIZE
+ dy;
if (nextX > 7) {
nextX -= 7;
}
if (nextY > 7) {
nextY -= 7;
}
next.getMatrix(height).block(nextX, nextY);
continue;
}
matrix.block(localX, localY);
}
}
}
}
/**
* Decodes the attributes of a terrain file, blocking the tile if necessary.
*
* @param attributes The terrain attributes.
* @param position The {@link Position} of the tile whose attributes are being decoded.
*/
private void decodeAttributes(int attributes, Position position) {
Region region = regions.fromPosition(position);
int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix current = region.getMatrix(height);
boolean block = false;
if ((attributes & BLOCKED_TILE) != 0) {
block = true;
}
if ((attributes & BRIDGE_TILE) != 0) {
if (height > 0) {
block = true;
height--;
}
}
if (block) {
int localX = x % Region.SIZE, localY = y % Region.SIZE;
current.block(localX, localY);
}
}
/**
* Decodes object data stored in the specified {@link ByteBuffer}.
*
* @param world The {@link World} containing the StaticGameObjects.
* @param buffer The ByteBuffer.
* @param x The x coordinate of the top left tile of the map file.
* @param y The y coordinate of the top left tile of the map file.
*/
private void decodeObjects(World world, ByteBuffer buffer, int x, int y) {
int id = -1;
int idOffset = BufferUtil.readSmart(buffer);
while (idOffset != 0) {
id += idOffset;
int packed = 0;
int positionOffset = BufferUtil.readSmart(buffer);
while (positionOffset != 0) {
packed += positionOffset - 1;
int localY = packed & 0x3F;
int localX = packed >> 6 & 0x3F;
int height = packed >> 12 & 0x3;
int attributes = buffer.get() & 0xFF;
int type = attributes >> 2;
int orientation = attributes & 0x3;
Position position = new Position(x + localX, y + localY, height);
GameObject object = new StaticGameObject(world, id, position, type, orientation);
objects.add(object);
block(object, position);
positionOffset = BufferUtil.readSmart(buffer);
}
idOffset = BufferUtil.readSmart(buffer);
}
}
/**
* Decodes terrain data stored in the specified {@link ByteBuffer}.
*
* @param buffer The ByteBuffer.
* @param x The x coordinate of the top left tile of the map file.
* @param y The y coordinate of the top left tile of the map file.
*/
private void decodeTerrain(ByteBuffer buffer, int x, int y) {
for (int height = 0; height < 4; height++) {
for (int localX = 0; localX < 64; localX++) {
for (int localY = 0; localY < 64; localY++) {
Position position = new Position(x + localX, y + localY, height);
int attributes = 0;
while (true) {
int attributeId = buffer.get() & 0xFF;
if (attributeId == 0) {
decodeAttributes(attributes, position);
break;
} else if (attributeId == 1) {
buffer.get();
decodeAttributes(attributes, position);
break;
} else if (attributeId <= 49) {
buffer.get();
} else if (attributeId <= 81) {
attributes = attributeId - 49;
}
}
}
}
}
}
}
@@ -0,0 +1,136 @@
package org.apollo.game.fs.decoder;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.archive.Archive;
import org.apollo.cache.archive.ArchiveEntry;
import org.apollo.game.model.area.Region;
/**
* Decodes {@link MapDefinition}s from the {@link IndexedFileSystem}.
*
* @author Ryley
* @author Major
*/
public final class MapFileDecoder {
/**
* The width (and length) of a map file, in tiles.
*/
public static final int MAP_FILE_WIDTH = Region.SIZE * Region.SIZE;
/**
* The file id of the versions archive.
*/
private static final int VERSIONS_ARCHIVE_FILE_ID = 5;
/**
* Decodes {@link MapDefinition}s from the specified {@link IndexedFileSystem}.
*
* @param fs The IndexedFileSystem.
* @return A {@link Map} of packed coordinates to their MapDefinitions.
* @throws IOException If there is an error reading or decoding the Archive.
*/
protected static Map<Integer, MapDefinition> decode(IndexedFileSystem fs) throws IOException {
Archive archive = Archive.decode(fs.getFile(0, VERSIONS_ARCHIVE_FILE_ID));
ArchiveEntry entry = archive.getEntry("map_index");
Map<Integer, MapDefinition> definitions = new HashMap<>();
ByteBuffer buffer = entry.getBuffer();
int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES);
for (int times = 0; times < count; times++) {
int packed = buffer.getShort() & 0xFFFF;
int terrain = buffer.getShort() & 0xFFFF;
int objects = buffer.getShort() & 0xFFFF;
boolean members = buffer.get() == 1;
definitions.put(packed, new MapDefinition(packed, terrain, objects, members));
}
return definitions;
}
/**
* A definition for a region.
*/
public static final class MapDefinition {
/**
* The packed coordinates.
*/
private final int packedCoordinates;
/**
* The terrain file id.
*/
private final int terrain;
/**
* The object file id.
*/
private final int objects;
/**
* Indicates whether or not this map is members-only.
*/
private final boolean members;
/**
* Creates the {@link MapDefinition}.
*
* @param packedCoordinates The packed coordinates.
* @param terrain The terrain file id.
* @param objects The object file id.
* @param members Indicates whether or not this map is members-only.
*/
public MapDefinition(int packedCoordinates, int terrain, int objects, boolean members) {
this.packedCoordinates = packedCoordinates;
this.terrain = terrain;
this.objects = objects;
this.members = members;
}
/**
* Gets the packed coordinates.
*
* @return The packed coordinates.
*/
public int getPackedCoordinates() {
return packedCoordinates;
}
/**
* Gets the id of the file containing the terrain data.
*
* @return The file id.
*/
public int getTerrainFile() {
return terrain;
}
/**
* Gets the id of the file containing the object data.
*
* @return The file id.
*/
public int getObjectFile() {
return objects;
}
/**
* Returns whether or not this MapDefinition is for a members-only area of the world.
*
* @return {@code true} if this MapDefinition is for a members-only area, {@code false} if not.
*/
public boolean isMembersOnly() {
return members;
}
}
}
@@ -0,0 +1,4 @@
/**
* Contains decoders.
*/
package org.apollo.game.fs.decoder;
@@ -0,0 +1,67 @@
package org.apollo.game.io;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apollo.cache.def.EquipmentDefinition;
/**
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link EquipmentDefinition}s.
*
* @author Graham
*/
public final class EquipmentDefinitionParser {
/**
* The input stream.
*/
private final InputStream is;
/**
* Creates the equipment definition parser.
*
* @param is The input stream.
*/
public EquipmentDefinitionParser(InputStream is) {
this.is = is;
}
/**
* Parses the input stream.
*
* @return The equipment definition array.
* @throws IOException If an I/O error occurs.
*/
public EquipmentDefinition[] parse() throws IOException {
DataInputStream dis = new DataInputStream(is);
int count = dis.readShort() & 0xFFFF;
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
for (int id = 0; id < count; id++) {
int slot = dis.readByte() & 0xFF;
if (slot != 0xFF) {
boolean twoHanded = dis.readBoolean();
boolean fullBody = dis.readBoolean();
boolean fullHat = dis.readBoolean();
boolean fullMask = dis.readBoolean();
int attack = dis.readByte() & 0xFF;
int strength = dis.readByte() & 0xFF;
int defence = dis.readByte() & 0xFF;
int ranged = dis.readByte() & 0xFF;
int magic = dis.readByte() & 0xFF;
EquipmentDefinition definition = new EquipmentDefinition(id);
definition.setLevels(attack, strength, defence, ranged, magic);
definition.setSlot(slot);
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
definitions[id] = definition;
}
}
return definitions;
}
}
@@ -0,0 +1,100 @@
package org.apollo.game.io;
import java.io.IOException;
import java.io.InputStream;
import org.apollo.game.message.handler.MessageHandler;
import org.apollo.game.message.handler.MessageHandlerChain;
import org.apollo.game.message.handler.MessageHandlerChainSet;
import org.apollo.game.model.World;
import org.apollo.net.message.Message;
import org.apollo.util.xml.XmlNode;
import org.apollo.util.xml.XmlParser;
import org.xml.sax.SAXException;
/**
* A class that parses the {@code messages.xml} file to produce {@link MessageHandlerChainSet}s.
*
* @author Graham
*/
public final class MessageHandlerChainSetParser {
/**
* The source {@link InputStream}.
*/
private final InputStream is;
/**
* The {@link XmlParser} instance.
*/
private final XmlParser parser = new XmlParser();
/**
* Creates the message chain parser.
*
* @param is The source {@link InputStream}.
* @throws SAXException If a SAX error occurs.
*/
public MessageHandlerChainSetParser(InputStream is) throws SAXException {
this.is = is;
}
/**
* Parses the XML and produces a group of {@link MessageHandlerChain}s.
*
* @param world The {@link World} this MessageHandlerChainGroup is for.
* @return A {@link MessageHandlerChainSet}.
* @throws IOException If an I/O error occurs.
* @throws SAXException If a SAX error occurs.
* @throws ReflectiveOperationException If a reflection error occurs.
*/
@SuppressWarnings("unchecked")
public MessageHandlerChainSet parse(World world) throws IOException, SAXException, ReflectiveOperationException {
XmlNode messages = parser.parse(is);
if (!messages.getName().equals("messages")) {
throw new IOException("Root node name is not 'messages'.");
}
MessageHandlerChainSet chainSet = new MessageHandlerChainSet();
for (XmlNode message : messages) {
if (!message.getName().equals("message")) {
throw new IOException("Only expected nodes named 'message' beneath the root node.");
}
XmlNode typeNode = message.getChild("type");
if (typeNode == null) {
throw new IOException("No node named 'type' beneath current message node.");
}
XmlNode chainNode = message.getChild("chain");
if (chainNode == null) {
throw new IOException("No node named 'chain' beneath current message node.");
}
String messageClassName = typeNode.getValue();
if (messageClassName == null) {
throw new IOException("Type node must have a value.");
}
Class<? extends Message> messageClass = (Class<? extends Message>) Class.forName(messageClassName);
for (XmlNode handlerNode : chainNode) {
if (!handlerNode.getName().equals("handler")) {
throw new IOException("Only expected nodes named 'handler' beneath the root node.");
}
String handlerClassName = handlerNode.getValue();
if (handlerClassName == null) {
throw new IOException("Handler node must have a value.");
}
Class<? extends MessageHandler<? extends Message>> handlerClass = (Class<? extends MessageHandler<? extends Message>>) Class.forName(handlerClassName);
MessageHandler<? extends Message> handler = handlerClass.getConstructor(World.class).newInstance(world);
chainSet.putHandler(messageClass, handler);
}
}
return chainSet;
}
}
@@ -0,0 +1,121 @@
package org.apollo.game.io;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import org.apollo.game.plugin.PluginMetaData;
import org.apollo.util.xml.XmlNode;
import org.apollo.util.xml.XmlParser;
import org.xml.sax.SAXException;
/**
* A class that parses {@code plugin.xml} files into {@link PluginMetaData} objects.
*
* @author Graham
*/
public final class PluginMetaDataParser {
/**
* An empty xml node array.
*/
private static final XmlNode[] EMPTY_NODE_ARRAY = new XmlNode[0];
/**
* The input stream.
*/
private final InputStream is;
/**
* The XML parser.
*/
private final XmlParser parser;
/**
* Creates the plugin meta data parser.
*
* @param is The input stream.
* @throws SAXException If a SAX error occurs.
*/
public PluginMetaDataParser(InputStream is) throws SAXException {
this.is = is;
parser = new XmlParser();
}
/**
* Gets the specified child element, if it exists.
*
* @param node The root node.
* @param name The element name.
* @return The node object.
* @throws IOException If the element does not exist.
*/
private static XmlNode getElement(XmlNode node, String name) throws IOException {
return Objects.requireNonNull(node.getChild(name), "No " + name + " element found.");
}
/**
* Parses the XML and creates a meta data object.
*
* @param base The base path for this plugin
* @return The meta data object.
* @throws SAXException If a SAX error occurs.
* @throws IOException If an I/O error occurs.
*/
public PluginMetaData parse(File base) throws IOException, SAXException {
XmlNode rootNode = parser.parse(is);
if (!rootNode.getName().equals("plugin")) {
throw new IOException("Root node must be named plugin.");
}
XmlNode idNode = getElement(rootNode, "id");
XmlNode nameNode = getElement(rootNode, "name");
XmlNode descriptionNode = getElement(rootNode, "description");
XmlNode authorsNode = getElement(rootNode, "authors");
XmlNode scriptsNode = getElement(rootNode, "scripts");
XmlNode dependenciesNode = getElement(rootNode, "dependencies");
XmlNode versionNode = getElement(rootNode, "version");
String id = idNode.getValue();
String name = nameNode.getValue();
String description = descriptionNode.getValue();
double version = Double.parseDouble(versionNode.getValue());
if (id == null || name == null || description == null) {
throw new IOException("Id, name and description must have values.");
}
XmlNode[] authorNodes = authorsNode.getChildren().toArray(EMPTY_NODE_ARRAY);
XmlNode[] scriptNodes = scriptsNode.getChildren().toArray(EMPTY_NODE_ARRAY);
XmlNode[] dependencyNodes = dependenciesNode.getChildren().toArray(EMPTY_NODE_ARRAY);
String[] authors = new String[authorNodes.length];
String[] scripts = new String[scriptNodes.length];
String[] dependencies = new String[dependencyNodes.length];
for (int i = 0; i < authorNodes.length; i++) {
authors[i] = authorNodes[i].getValue();
if (authors[i] == null) {
throw new IOException("Author elements must have values.");
}
}
for (int i = 0; i < scriptNodes.length; i++) {
scripts[i] = scriptNodes[i].getValue();
if (scripts[i] == null) {
throw new IOException("Script elements must have values.");
}
}
for (int i = 0; i < dependencyNodes.length; i++) {
dependencies[i] = dependencyNodes[i].getValue();
if (dependencies[i] == null) {
throw new IOException("Dependency elements must have values.");
}
}
return new PluginMetaData(id, base, name, description, authors, scripts, dependencies, version);
}
}
@@ -0,0 +1,4 @@
/**
* Contains classes which deal with input/output.
*/
package org.apollo.game.io;
@@ -0,0 +1,56 @@
package org.apollo.game.io.player;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apollo.util.NameUtil;
/**
* A utility class with common functionality used by the binary player loader/ savers.
*
* @author Graham
* @author Major
*/
public final class BinaryFileUtils {
/**
* The Path to the saved games directory.
*/
private static final Path SAVED_GAMES_DIRECTORY = Paths.get("data/savedGames");
/**
* Creates the saved games directory if it does not exist.
*/
static {
try {
if (!Files.exists(SAVED_GAMES_DIRECTORY)) {
Files.createDirectory(SAVED_GAMES_DIRECTORY);
}
} catch (IOException e) {
throw new UncheckedIOException("Error creating saved games directory.", e);
}
}
/**
* Gets the save {@link File} for the specified player.
*
* @param username The username of the player.
* @return The file.
*/
public static Path getFile(String username) {
String filtered = NameUtil.decodeBase37(NameUtil.encodeBase37(username));
return SAVED_GAMES_DIRECTORY.resolve(filtered + ".dat");
}
/**
* Sole private constructor to prevent instantiation.
*/
private BinaryFileUtils() {
}
}
@@ -0,0 +1,339 @@
package org.apollo.game.io.player;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apollo.game.model.Appearance;
import org.apollo.game.model.Item;
import org.apollo.game.model.Position;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.Skill;
import org.apollo.game.model.entity.SkillSet;
import org.apollo.game.model.entity.attr.Attribute;
import org.apollo.game.model.entity.attr.AttributeMap;
import org.apollo.game.model.entity.attr.AttributePersistence;
import org.apollo.game.model.entity.attr.AttributeType;
import org.apollo.game.model.entity.attr.BooleanAttribute;
import org.apollo.game.model.entity.attr.NumericalAttribute;
import org.apollo.game.model.entity.attr.StringAttribute;
import org.apollo.game.model.entity.setting.Gender;
import org.apollo.game.model.entity.setting.MembershipStatus;
import org.apollo.game.model.entity.setting.PrivacyState;
import org.apollo.game.model.entity.setting.PrivilegeLevel;
import org.apollo.game.model.entity.setting.ScreenBrightness;
import org.apollo.game.model.inv.Inventory;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.util.NameUtil;
import org.apollo.util.StreamUtil;
import org.apollo.util.security.PlayerCredentials;
import com.lambdaworks.crypto.SCryptUtil;
/**
* A {@link PlayerSerializer} implementation that uses a binary file to store player data.
*
* @author Graham
* @author Major
*/
public final class BinaryPlayerSerializer extends PlayerSerializer {
/**
* Creates the BinaryPlayerSerializer.
*
* @param world The {@link World} to place the {@link Player}s in.
*/
public BinaryPlayerSerializer(World world) {
super(world);
}
/**
* The Path to the saved games directory.
*/
private static final Path SAVED_GAMES_DIRECTORY = Paths.get("data/savedGames");
static {
try {
if (!Files.exists(SAVED_GAMES_DIRECTORY)) {
Files.createDirectory(SAVED_GAMES_DIRECTORY);
}
} catch (IOException e) {
throw new UncheckedIOException("Error creating saved games directory.", e);
}
}
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws IOException {
Path path = getFile(credentials.getUsername());
if (!Files.exists(path)) {
Player player = new Player(world, credentials, TUTORIAL_ISLAND_SPAWN);
credentials.setPassword(SCryptUtil.scrypt(credentials.getPassword(), 16384, 8, 1));
return new PlayerLoaderResponse(LoginConstants.STATUS_OK, player);
}
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(path)))) {
String name = StreamUtil.readString(in);
String password = StreamUtil.readString(in);
if (!name.equalsIgnoreCase(credentials.getUsername()) || !SCryptUtil.check(credentials.getPassword(), password)) {
return new PlayerLoaderResponse(LoginConstants.STATUS_INVALID_CREDENTIALS);
}
credentials.setPassword(password); // Update password to the hashed one.
PrivilegeLevel privilege = PrivilegeLevel.valueOf(in.readByte());
MembershipStatus members = MembershipStatus.valueOf(in.readByte());
PrivacyState chatPrivacy = PrivacyState.valueOf(in.readByte(), true);
PrivacyState friendPrivacy = PrivacyState.valueOf(in.readByte(), false);
PrivacyState tradePrivacy = PrivacyState.valueOf(in.readByte(), false);
ScreenBrightness brightness = ScreenBrightness.valueOf(in.readByte());
int x = in.readUnsignedShort();
int y = in.readUnsignedShort();
int height = in.readUnsignedByte();
Gender gender = in.readUnsignedByte() == Gender.MALE.toInteger() ? Gender.MALE : Gender.FEMALE;
int[] style = new int[7];
for (int slot = 0; slot < style.length; slot++) {
style[slot] = in.readUnsignedByte();
}
int[] colors = new int[5];
for (int slot = 0; slot < colors.length; slot++) {
colors[slot] = in.readUnsignedByte();
}
Player player = new Player(world, credentials, new Position(x, y, height));
player.setPrivilegeLevel(privilege);
player.setMembers(members);
player.setChatPrivacy(chatPrivacy);
player.setFriendPrivacy(friendPrivacy);
player.setTradePrivacy(tradePrivacy);
player.setScreenBrightness(brightness);
player.setAppearance(new Appearance(gender, style, colors));
readInventory(in, player.getInventory());
readInventory(in, player.getEquipment());
readInventory(in, player.getBank());
int size = in.readUnsignedByte();
SkillSet skills = player.getSkillSet();
skills.stopFiringEvents();
try {
for (int i = 0; i < size; i++) {
int level = in.readUnsignedByte();
double experience = in.readDouble();
skills.setSkill(i, new Skill(experience, level, SkillSet.getLevelForExperience(experience)));
}
} finally {
skills.calculateCombatLevel();
skills.startFiringEvents();
}
int friendCount = in.readByte();
List<String> friends = new ArrayList<>(friendCount);
for (int i = 0; i < friendCount; i++) {
friends.add(NameUtil.decodeBase37(in.readLong()));
}
player.setFriendUsernames(friends);
int ignoreCount = in.readByte();
List<String> ignores = new ArrayList<>(ignoreCount);
for (int times = 0; times < ignoreCount; times++) {
ignores.add(NameUtil.decodeBase37(in.readLong()));
}
player.setIgnoredUsernames(ignores);
Map<String, Attribute<?>> attributes = readAttributes(in);
attributes.forEach(player::setAttribute);
return new PlayerLoaderResponse(LoginConstants.STATUS_OK, player);
}
}
@Override
public void savePlayer(Player player) throws IOException {
Path file = getFile(player.getUsername());
try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(file))) {
StreamUtil.writeString(out, player.getUsername());
StreamUtil.writeString(out, player.getCredentials().getPassword());
out.writeByte(player.getPrivilegeLevel().toInteger());
out.writeByte(player.getMembershipStatus().getValue());
out.writeByte(player.getChatPrivacy().toInteger(true));
out.writeByte(player.getFriendPrivacy().toInteger(false));
out.writeByte(player.getTradePrivacy().toInteger(false));
out.writeByte(player.getScreenBrightness().toInteger());
Position position = player.getPosition();
out.writeShort(position.getX());
out.writeShort(position.getY());
out.writeByte(position.getHeight());
Appearance appearance = player.getAppearance();
out.writeByte(appearance.getGender().toInteger());
int[] style = appearance.getStyle();
for (int element : style) {
out.writeByte(element);
}
int[] colors = appearance.getColors();
for (int color : colors) {
out.writeByte(color);
}
writeInventory(out, player.getInventory());
writeInventory(out, player.getEquipment());
writeInventory(out, player.getBank());
SkillSet skills = player.getSkillSet();
out.writeByte(skills.size());
for (int id = 0; id < skills.size(); id++) {
Skill skill = skills.getSkill(id);
out.writeByte(skill.getCurrentLevel());
out.writeDouble(skill.getExperience());
}
List<String> usernames = player.getFriendUsernames();
out.writeByte(usernames.size());
for (String username : usernames) {
out.writeLong(NameUtil.encodeBase37(username));
}
usernames = player.getIgnoredUsernames();
out.writeByte(usernames.size());
for (String username : usernames) {
out.writeLong(NameUtil.encodeBase37(username));
}
Set<Entry<String, Attribute<?>>> attributes = player.getAttributes().entrySet();
attributes.removeIf(e -> AttributeMap.getDefinition(e.getKey()).getPersistence() != AttributePersistence.PERSISTENT);
out.writeInt(attributes.size());
for (Entry<String, Attribute<?>> entry : attributes) {
String name = entry.getKey();
StreamUtil.writeString(out, name);
Attribute<?> attribute = entry.getValue();
out.writeByte(attribute.getType().getValue());
out.write(attribute.encode());
}
}
}
/**
* Gets the save {@link File} for the specified player.
*
* @param username The username of the player.
* @return The file.
*/
private Path getFile(String username) {
String filtered = NameUtil.decodeBase37(NameUtil.encodeBase37(username));
return SAVED_GAMES_DIRECTORY.resolve(filtered + ".dat");
}
/**
* Reads the player's {@link Attribute}s.
*
* @param in The input stream.
* @return The {@link Map} of attribute names to attributes.
* @throws IOException If there is an error reading from the stream.
*/
private Map<String, Attribute<?>> readAttributes(DataInputStream in) throws IOException {
int count = in.readInt();
Map<String, Attribute<?>> attributes = new HashMap<>(count);
for (int times = 0; times < count; times++) {
String name = StreamUtil.readString(in);
AttributeType type = AttributeType.valueOf(in.read());
Attribute<?> attribute;
switch (type) {
case BOOLEAN:
attribute = new BooleanAttribute(in.read() == 1);
break;
case DOUBLE:
attribute = new NumericalAttribute(in.readDouble());
break;
case LONG:
attribute = new NumericalAttribute(in.readLong());
break;
case STRING:
case SYMBOL:
attribute = new StringAttribute(StreamUtil.readString(in), type == AttributeType.SYMBOL);
break;
default:
throw new IllegalArgumentException("Undefined attribute type: " + type + ".");
}
attributes.put(name, attribute);
}
return attributes;
}
/**
* Reads an inventory from the input stream.
*
* @param in The input stream.
* @param inventory The inventory.
* @throws IOException If an I/O error occurs.
*/
private void readInventory(DataInputStream in, Inventory inventory) throws IOException {
int capacity = in.readUnsignedShort();
inventory.stopFiringEvents();
try {
for (int slot = 0; slot < capacity; slot++) {
int id = in.readUnsignedShort();
int amount = in.readInt();
if (id != 0) {
inventory.set(slot, new Item(id - 1, amount));
} else {
inventory.reset(slot);
}
}
} finally {
inventory.startFiringEvents();
}
}
/**
* Writes an inventory to the specified output stream.
*
* @param out The output stream.
* @param inventory The inventory.
* @throws IOException If an I/O error occurs.
*/
private void writeInventory(DataOutputStream out, Inventory inventory) throws IOException {
int capacity = inventory.capacity();
out.writeShort(capacity);
for (int slot = 0; slot < capacity; slot++) {
Item item = inventory.get(slot);
if (item != null) {
out.writeShort(item.getId() + 1);
out.writeInt(item.getAmount());
} else {
out.writeShort(0);
out.writeInt(0);
}
}
}
}
@@ -0,0 +1,43 @@
package org.apollo.game.io.player;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.setting.MembershipStatus;
import org.apollo.game.model.entity.setting.PrivilegeLevel;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.util.security.PlayerCredentials;
/**
* A {@link PlayerSerializer} that saves no data and returns an administrator member account, ideal for debugging.
*
* @author Graham
* @author Major
*/
public final class DummyPlayerSerializer extends PlayerSerializer {
/**
* Creates the DummyPlayerSerializer.
*
* @param world The {@link World} to place the {@link Player}s in.
*/
public DummyPlayerSerializer(World world) {
super(world);
}
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) {
int status = LoginConstants.STATUS_OK;
Player player = new Player(world, credentials, TUTORIAL_ISLAND_SPAWN);
player.setPrivilegeLevel(PrivilegeLevel.ADMINISTRATOR);
player.setMembers(MembershipStatus.PAID);
return new PlayerLoaderResponse(status, player);
}
@Override
public void savePlayer(Player player) {
/* discard player */
}
}
@@ -0,0 +1,33 @@
package org.apollo.game.io.player;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.util.security.PlayerCredentials;
/**
* A {@link PlayerSerializer} that utilises {@code JDBC} to communicate with an SQL database containing player data.
*
* @author Major
*/
public final class JdbcPlayerSerializer extends PlayerSerializer {
/**
* Creates the JdbcPlayerSerializer.
*
* @param world The {@link World} to place the {@link Player}s in.
*/
public JdbcPlayerSerializer(World world) {
super(world);
}
@Override
public void savePlayer(Player player) throws Exception {
throw new UnsupportedOperationException("JDBC saving is not supported at this time.");
}
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception {
throw new UnsupportedOperationException("JDBC loading is not supported at this time.");
}
}
@@ -0,0 +1,73 @@
package org.apollo.game.io.player;
import java.util.Optional;
import org.apollo.game.model.entity.Player;
import org.apollo.net.codec.login.LoginConstants;
import com.google.common.base.Preconditions;
/**
* A response for the {@link PlayerSerializer#loadPlayer} call.
*
* @author Graham
* @author Major
*/
public final class PlayerLoaderResponse {
/**
* The player.
*/
private final Optional<Player> player;
/**
* The status code.
*/
private final int status;
/**
* Creates a {@link PlayerLoaderResponse} with only a status code.
*
* @param status The status code.
* @throws IllegalArgumentException If the status code is {@link LoginConstants#STATUS_OK} or
* {@link LoginConstants#STATUS_RECONNECTION_OK}.
*/
public PlayerLoaderResponse(int status) {
Preconditions.checkArgument(status != LoginConstants.STATUS_OK && status != LoginConstants.STATUS_RECONNECTION_OK, "Player required for this status code.");
this.status = status;
player = Optional.empty();
}
/**
* Creates a {@link PlayerLoaderResponse} with a status code and {@link Player}.
*
* @param status The status code.
* @param player The player.
* @throws IllegalArgumentException If the status code does not need a player.
* @throws NullPointerException If the specified player is null.
*/
public PlayerLoaderResponse(int status, Player player) {
Preconditions.checkArgument(status == LoginConstants.STATUS_OK || status == LoginConstants.STATUS_RECONNECTION_OK, "Player not required for this status code.");
this.status = status;
this.player = Optional.of(player);
}
/**
* Gets the player.
*
* @return The player, wrapped in an {@link Optional}.
*/
public Optional<Player> getPlayer() {
return player;
}
/**
* Gets the status code.
*
* @return The status code.
*/
public int getStatus() {
return status;
}
}
@@ -0,0 +1,53 @@
package org.apollo.game.io.player;
import org.apollo.game.model.Position;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.util.security.PlayerCredentials;
/**
* An interface which may be implemented by others which are capable of serializing and deserializing players. For
* example, implementations might include text-based, binary and SQL serializers.
*
* @author Graham
* @author Major
*/
public abstract class PlayerSerializer {
/**
* The spawn point for Players, on Tutorial Island.
*/
protected static final Position TUTORIAL_ISLAND_SPAWN = new Position(3093, 3104);
/**
* The World this PlayerSerializer is for.
*/
protected final World world;
/**
* Creates the PlayerSerializer.
*
* @param world The {@link World} this PlayerSerializer is for.
*/
public PlayerSerializer(World world) {
this.world = world;
}
/**
* Loads a {@link Player}.
*
* @param credentials The {@link PlayerCredentials}.
* @return The {@link PlayerLoaderResponse}.
* @throws Exception If an error occurs.
*/
public abstract PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception;
/**
* Saves a {@link Player}.
*
* @param player The Player to save.
* @throws Exception If an error occurs.
*/
public abstract void savePlayer(Player player) throws Exception;
}
@@ -0,0 +1,4 @@
/**
* Contains classes which deal with loading and saving player files.
*/
package org.apollo.game.io.player;
@@ -0,0 +1,63 @@
package org.apollo.game.login;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apollo.game.io.player.PlayerLoaderResponse;
import org.apollo.game.io.player.PlayerSerializer;
import org.apollo.game.session.LoginSession;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.net.codec.login.LoginRequest;
/**
* A class which processes a single login request.
*
* @author Graham
*/
public final class PlayerLoaderWorker implements Runnable {
/**
* The logger for this class.
*/
private static final Logger logger = Logger.getLogger(PlayerLoaderWorker.class.getName());
/**
* The PlayerSerializer.
*/
private final PlayerSerializer loader;
/**
* The request.
*/
private final LoginRequest request;
/**
* The session that submitted the request.
*/
private final LoginSession session;
/**
* Creates a {@link PlayerLoaderWorker} which will do the work for a single player load request.
*
* @param loader The {@link PlayerSerializer}.
* @param session The {@link LoginSession} which initiated the request.
* @param request The {@link LoginRequest} object.
*/
public PlayerLoaderWorker(PlayerSerializer loader, LoginSession session, LoginRequest request) {
this.loader = loader;
this.session = session;
this.request = request;
}
@Override
public void run() {
try {
PlayerLoaderResponse response = loader.loadPlayer(request.getCredentials());
session.handlePlayerLoaderResponse(request, response);
} catch (Exception e) {
logger.log(Level.SEVERE, "Unable to load player's game.", e);
session.handlePlayerLoaderResponse(request, new PlayerLoaderResponse(LoginConstants.STATUS_COULD_NOT_COMPLETE));
}
}
}
@@ -0,0 +1,61 @@
package org.apollo.game.login;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apollo.game.io.player.PlayerSerializer;
import org.apollo.game.model.entity.Player;
import org.apollo.game.session.GameSession;
/**
* A class which processes a single save request.
*
* @author Graham
*/
public final class PlayerSaverWorker implements Runnable {
/**
* The logger for this class.
*/
private static final Logger logger = Logger.getLogger(PlayerSaverWorker.class.getName());
/**
* The player to save.
*/
private final Player player;
/**
* The player saver.
*/
private final PlayerSerializer saver;
/**
* The game session.
*/
private final GameSession session;
/**
* Creates the player saver worker.
*
* @param saver The player saver.
* @param session The game session.
* @param player The player to save.
*/
public PlayerSaverWorker(PlayerSerializer saver, GameSession session, Player player) {
this.saver = saver;
this.session = session;
this.player = player;
}
@Override
public void run() {
try {
saver.savePlayer(player);
session.handlePlayerSaverResponse(true);
} catch (Exception e) {
logger.log(Level.SEVERE, "Unable to save player's game.", e);
session.handlePlayerSaverResponse(false);
}
}
}
@@ -0,0 +1,4 @@
/**
* Contains classes related to the login service.
*/
package org.apollo.game.login;
@@ -0,0 +1,42 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ButtonMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
/**
* A {@link MessageHandler} that responds to {@link ButtonMessage}s for withdrawing items as notes.
*
* @author Graham
*/
public final class BankButtonMessageHandler extends MessageHandler<ButtonMessage> {
/**
* Creates the BankButtonMessageHandler.
*
* @param world The {@link World} the {@link ButtonMessage} occurred in.
*/
public BankButtonMessageHandler(World world) {
super(world);
}
/**
* The withdraw as item button id.
*/
private static final int WITHDRAW_AS_ITEM = 5387;
/**
* The withdraw as note button id.
*/
private static final int WITHDRAW_AS_NOTE = 5386;
@Override
public void handle(Player player, ButtonMessage message) {
if (message.getWidgetId() == WITHDRAW_AS_ITEM) {
player.setWithdrawingNotes(false);
} else if (message.getWidgetId() == WITHDRAW_AS_NOTE) {
player.setWithdrawingNotes(true);
}
}
}
@@ -0,0 +1,97 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ItemActionMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.EnterAmountListener;
import org.apollo.game.model.inter.bank.BankConstants;
import org.apollo.game.model.inter.bank.BankDepositEnterAmountListener;
import org.apollo.game.model.inter.bank.BankUtils;
import org.apollo.game.model.inter.bank.BankWithdrawEnterAmountListener;
/**
* A {@link MessageHandler} that handles withdrawing and depositing items from/to a player's bank.
*
* @author Graham
*/
public final class BankMessageHandler extends MessageHandler<ItemActionMessage> {
/**
* Converts an option to an amount.
*
* @param option The option.
* @return The amount.
* @throws IllegalArgumentException If the option is invalid.
*/
private static int optionToAmount(int option) {
switch (option) {
case 1:
return 1;
case 2:
return 5;
case 3:
return 10;
case 4:
return Integer.MAX_VALUE;
case 5:
return -1;
}
throw new IllegalArgumentException("Invalid option supplied.");
}
/**
* Creates the BankMessageHandler.
*
* @param world The {@link World} the {@link ItemActionMessage} occurred in.
*/
public BankMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ItemActionMessage message) {
if (player.getInterfaceSet().contains(BankConstants.BANK_WINDOW_ID)) {
if (message.getInterfaceId() == BankConstants.SIDEBAR_INVENTORY_ID) {
deposit(player, message);
} else if (message.getInterfaceId() == BankConstants.BANK_INVENTORY_ID) {
withdraw(player, message);
}
}
}
/**
* Handles a deposit action.
*
* @param player The player.
* @param message The message.
*/
private void deposit(Player player, ItemActionMessage message) {
int amount = optionToAmount(message.getOption());
if (amount == -1) {
EnterAmountListener listener = new BankDepositEnterAmountListener(player, message.getSlot(), message.getId());
player.getInterfaceSet().openEnterAmountDialogue(listener);
} else if (!BankUtils.deposit(player, message.getSlot(), message.getId(), amount)) {
message.terminate();
}
}
/**
* Handles a withdraw action.
*
* @param player The player.
* @param message The message.
*/
private void withdraw(Player player, ItemActionMessage message) {
int amount = optionToAmount(message.getOption());
if (amount == -1) {
EnterAmountListener listener = new BankWithdrawEnterAmountListener(player, message.getSlot(), message.getId());
player.getInterfaceSet().openEnterAmountDialogue(listener);
} else if (!BankUtils.withdraw(player, message.getSlot(), message.getId(), amount)) {
message.terminate();
}
}
}
@@ -0,0 +1,29 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ChatMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.sync.block.SynchronizationBlock;
/**
* A {@link MessageHandler} that broadcasts public chat messages.
*
* @author Graham
*/
public final class ChatMessageHandler extends MessageHandler<ChatMessage> {
/**
* Creates the ChatMessageHandler.
*
* @param world The {@link World} the {@link ChatMessage} occurred in.
*/
public ChatMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ChatMessage message) {
player.getBlockSet().add(SynchronizationBlock.createChatBlock(player, message));
}
}
@@ -0,0 +1,32 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ChatMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
/**
* A {@link MessageHandler} that verifies {@link ChatMessage}s.
*
* @author Graham
*/
public final class ChatVerificationHandler extends MessageHandler<ChatMessage> {
/**
* Creates the ChatVerificationHandler.
*
* @param world The {@link World} the {@link ChatMessage} occurred in.
*/
public ChatVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ChatMessage message) {
int color = message.getTextColor();
int effects = message.getTextEffects();
if (color < 0 || color > 11 || effects < 0 || effects > 5) {
message.terminate();
}
}
}
@@ -0,0 +1,28 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ClosedInterfaceMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
/**
* A {@link MessageHandler} for the {@link ClosedInterfaceMessage}.
*
* @author Graham
*/
public final class ClosedInterfaceMessageHandler extends MessageHandler<ClosedInterfaceMessage> {
/**
* Creates the ClosedInterfaceMessageHandler.
*
* @param world The {@link World} the {@link ClosedInterfaceMessage} occurred in.
*/
public ClosedInterfaceMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ClosedInterfaceMessage message) {
player.getInterfaceSet().interfaceClosed();
}
}
@@ -0,0 +1,37 @@
package org.apollo.game.message.handler;
import org.apollo.game.command.Command;
import org.apollo.game.message.impl.CommandMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.net.message.Message;
/**
* A {@link MessageHandler} that dispatches {@link CommandMessage}s.
*
* @author Graham
*/
public final class CommandMessageHandler extends MessageHandler<CommandMessage> {
/**
* Creates the CommandMessageHandler.
*
* @param world The {@link World} the {@link Message} occurred in.
*/
public CommandMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, CommandMessage message) {
String[] components = message.getCommand().split(" ");
String name = components[0];
String[] arguments = new String[components.length - 1];
System.arraycopy(components, 1, arguments, 0, arguments.length);
Command command = new Command(name, arguments);
world.getCommandDispatcher().dispatch(player, command);
}
}
@@ -0,0 +1,36 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ButtonMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.InterfaceType;
/**
* A {@link MessageHandler} which intercepts button clicks on dialogues, and forwards the message to the current
* listener.
*
* @author Chris Fletcher
*/
public final class DialogueButtonHandler extends MessageHandler<ButtonMessage> {
/**
* Creates the DialogueButtonHandler.
*
* @param world The {@link World} the {@link ButtonMessage} occurred in.
*/
public DialogueButtonHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ButtonMessage message) {
if (player.getInterfaceSet().contains(InterfaceType.DIALOGUE)) {
boolean terminate = player.getInterfaceSet().buttonClicked(message.getWidgetId());
if (terminate) {
message.terminate();
}
}
}
}
@@ -0,0 +1,31 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.DialogueContinueMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.InterfaceType;
/**
* A {@link MessageHandler} for the {@link DialogueContinueMessage}.
*
* @author Chris Fletcher
*/
public final class DialogueContinueMessageHandler extends MessageHandler<DialogueContinueMessage> {
/**
* Creates the DialogueContinueMessageHandler.
*
* @param world The {@link World} the {@link DialogueContinueMessage} occurred in.
*/
public DialogueContinueMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, DialogueContinueMessage message) {
if (player.getInterfaceSet().contains(InterfaceType.DIALOGUE)) {
player.getInterfaceSet().continueRequested();
}
}
}
@@ -0,0 +1,28 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.EnteredAmountMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
/**
* A {@link MessageHandler} for the {@link EnteredAmountMessage}.
*
* @author Graham
*/
public final class EnteredAmountMessageHandler extends MessageHandler<EnteredAmountMessage> {
/**
* Creates the EnteredAmountMessageHandler.
*
* @param world The {@link World} the {@link EnteredAmountMessage} occurred in.
*/
public EnteredAmountMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, EnteredAmountMessage message) {
player.getInterfaceSet().enteredAmount(message.getAmount());
}
}
@@ -0,0 +1,139 @@
package org.apollo.game.message.handler;
import org.apollo.cache.def.EquipmentDefinition;
import org.apollo.game.message.impl.ItemOptionMessage;
import org.apollo.game.model.Item;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.EquipmentConstants;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.Skill;
import org.apollo.game.model.inv.Inventory;
import org.apollo.game.model.inv.SynchronizationInventoryListener;
import org.apollo.util.LanguageUtil;
/**
* A {@link MessageHandler} that equips items.
*
* @author Major
* @author Graham
* @author Ryley
*/
public final class EquipItemHandler extends MessageHandler<ItemOptionMessage> {
/**
* The option used when equipping an item.
*/
private static final int EQUIP_OPTION = 2;
/**
* Creates the EquipItemHandler.
*
* @param world The {@link World} the {@link ItemOptionMessage} occurred in.
*/
public EquipItemHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ItemOptionMessage message) {
if (message.getOption() != EQUIP_OPTION || message.getInterfaceId() != SynchronizationInventoryListener.INVENTORY_ID) {
return;
}
int inventorySlot = message.getSlot();
Item equipping = player.getInventory().get(inventorySlot);
int equippingId = equipping.getId();
EquipmentDefinition definition = EquipmentDefinition.lookup(equippingId);
if (definition == null) {
// We don't break the chain here or any item option messages won't work!
return;
}
for (int id = 0; id < 5; id++) {
int requirement = definition.getLevel(id);
if (player.getSkillSet().getMaximumLevel(id) < requirement) {
String name = Skill.getName(id);
String article = LanguageUtil.getIndefiniteArticle(name);
player.sendMessage("You need " + article + " " + name + " level of " + requirement + " to equip this item.");
message.terminate();
return;
}
}
Inventory inventory = player.getInventory();
Inventory equipment = player.getEquipment();
int equipmentSlot = definition.getSlot();
Item weapon = equipment.get(EquipmentConstants.WEAPON);
Item shield = equipment.get(EquipmentConstants.SHIELD);
// XXX: This is still pretty ugly in some parts, improve.
if (definition.isTwoHanded()) {
int slotsRequired = weapon != null && shield != null ? 1 : 0;
if (inventory.freeSlots() < slotsRequired) {
message.terminate();
return;
}
// Reset the weapon and the shield slots.
equipment.reset(EquipmentConstants.WEAPON);
equipment.reset(EquipmentConstants.SHIELD);
// Set the two-handed weapon and clear it from the inventory.
equipment.set(EquipmentConstants.WEAPON, inventory.reset(inventorySlot));
// Add previous shield or weapon, if present.
if (shield != null) {
inventory.add(shield);
}
if (weapon != null) {
inventory.add(weapon);
}
return;
}
if (definition.getSlot() == EquipmentConstants.SHIELD && weapon != null && EquipmentDefinition.lookup(weapon.getId()).isTwoHanded()) {
equipment.set(EquipmentConstants.SHIELD, inventory.reset(inventorySlot));
inventory.add(equipment.reset(EquipmentConstants.WEAPON));
return;
}
Item current = equipment.get(equipmentSlot);
if (current != null && current.getId() == equipping.getId() && current.getDefinition().isStackable()) {
long total = (long) current.getAmount() + equipping.getAmount();
// If the total has not over flown and we can add to the existing stack, do so.
if (total <= Integer.MAX_VALUE && !equipment.add(inventory.reset(inventorySlot)).isPresent()) {
return;
}
int remaining = (int) (total - Integer.MAX_VALUE);
int removed = equipping.getAmount() - remaining;
if (remaining == equipping.getAmount()) {
equipment.set(equipmentSlot, equipping);
inventory.set(inventorySlot, current);
return;
}
inventory.remove(equipping.getId(), removed);
equipment.add(equipping.getId(), removed);
return;
}
Item previous = equipment.reset(equipmentSlot);
inventory.remove(equipping);
equipment.set(equipmentSlot, equipping);
if (previous != null) {
inventory.set(inventorySlot, previous);
}
}
}
@@ -0,0 +1,59 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ItemOnItemMessage;
import org.apollo.game.model.Item;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.bank.BankConstants;
import org.apollo.game.model.inv.Inventory;
import org.apollo.game.model.inv.SynchronizationInventoryListener;
/**
* A {@link MessageHandler} that verifies the target item in {@link ItemOnItemMessage}s.
*
* @author Chris Fletcher
*/
public final class ItemOnItemVerificationHandler extends MessageHandler<ItemOnItemMessage> {
/**
* Creates the ItemOnItemVerificationHandler.
*
* @param world The {@link World} the {@link ItemOnItemMessage} occurred in.
*/
public ItemOnItemVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ItemOnItemMessage message) {
Inventory inventory;
switch (message.getInterfaceId()) {
case SynchronizationInventoryListener.INVENTORY_ID:
case BankConstants.SIDEBAR_INVENTORY_ID:
inventory = player.getInventory();
break;
case SynchronizationInventoryListener.EQUIPMENT_ID:
inventory = player.getEquipment();
break;
case BankConstants.BANK_INVENTORY_ID:
inventory = player.getBank();
break;
default:
message.terminate();
return;
}
int slot = message.getTargetSlot();
if (slot < 0 || slot >= inventory.capacity()) {
message.terminate();
return;
}
Item item = inventory.get(slot);
if (item == null || item.getId() != message.getTargetId()) {
message.terminate();
}
}
}
@@ -0,0 +1,49 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ItemOnObjectMessage;
import org.apollo.game.model.Item;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.bank.BankConstants;
import org.apollo.game.model.inv.Inventory;
import org.apollo.game.model.inv.SynchronizationInventoryListener;
/**
* A {@link MessageHandler} that verifies {@link ItemOnObjectMessage}s.
*
* @author Major
*/
public final class ItemOnObjectVerificationHandler extends MessageHandler<ItemOnObjectMessage> {
/**
* Creates the ItemOnObjectVerificationHandler.
*
* @param world The {@link World} the {@link ItemOnObjectMessage} occurred in.
*/
public ItemOnObjectVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ItemOnObjectMessage message) {
if (message.getInterfaceId() != SynchronizationInventoryListener.INVENTORY_ID && message.getInterfaceId() != BankConstants.SIDEBAR_INVENTORY_ID) {
message.terminate();
return;
}
Inventory inventory = player.getInventory();
int slot = message.getSlot();
if (slot < 0 || slot >= inventory.capacity()) {
message.terminate();
return;
}
Item item = inventory.get(slot);
if (item == null || item.getId() != message.getId()) {
message.terminate();
return;
}
}
}
@@ -0,0 +1,94 @@
package org.apollo.game.message.handler;
import java.util.HashMap;
import java.util.Map;
import org.apollo.game.message.impl.InventoryItemMessage;
import org.apollo.game.model.Item;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.bank.BankConstants;
import org.apollo.game.model.inv.Inventory;
import org.apollo.game.model.inv.SynchronizationInventoryListener;
/**
* A {@link MessageHandler} that verifies {@link InventoryItemMessage}s.
*
* @author Chris Fletcher
* @author Major
*/
public final class ItemVerificationHandler extends MessageHandler<InventoryItemMessage> {
/**
* Creates the ItemVerificationHandler.
*
* @param world The {@link World} the {@link InventoryItemMessage} occurred in.
*/
public ItemVerificationHandler(World world) {
super(world);
}
/**
* A supplier for an {@link Inventory}.
*
* @author Major
*/
@FunctionalInterface
public static interface InventorySupplier {
/**
* Gets the appropriate {@link Inventory}.
*
* @param player The {@link Player} who prompted the verification call.
* @return The inventory. Must not be {@code null}.
*/
public Inventory getInventory(Player player);
}
/**
* The map of interface ids to inventories.
*/
private static final Map<Integer, InventorySupplier> inventories = new HashMap<>();
static {
inventories.put(SynchronizationInventoryListener.INVENTORY_ID, Player::getInventory);
inventories.put(BankConstants.SIDEBAR_INVENTORY_ID, Player::getInventory);
inventories.put(SynchronizationInventoryListener.EQUIPMENT_ID, Player::getEquipment);
inventories.put(BankConstants.BANK_INVENTORY_ID, Player::getBank);
}
/**
* Adds an {@link Inventory} with the specified interface id to the {@link Map} of supported ones,
* <strong>iff</strong> the specified id does <strong>not</strong> already have a mapping.
*
* @param id The id of the interface.
* @param supplier The {@link InventorySupplier}.
*/
public static void addInventory(int id, InventorySupplier supplier) {
inventories.putIfAbsent(id, supplier);
}
@Override
public void handle(Player player, InventoryItemMessage message) {
InventorySupplier supplier = inventories.get(message.getInterfaceId());
if (supplier == null) {
message.terminate();
return;
}
Inventory inventory = supplier.getInventory(player);
int slot = message.getSlot();
if (slot < 0 || slot >= inventory.capacity()) {
message.terminate();
return;
}
Item item = inventory.get(slot);
if (item == null || item.getId() != message.getId()) {
message.terminate();
}
}
}
@@ -0,0 +1,38 @@
package org.apollo.game.message.handler;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.net.message.Message;
/**
* Listens for {@link Message}s received from the client.
*
* @author Graham
* @author Ryley
* @param <M> The type of Message this class is listening for.
*/
public abstract class MessageHandler<M extends Message> {
/**
* The World the Message occurred in.
*/
protected final World world;
/**
* Creates the MessageListener.
*
* @param world The {@link World} the {@link Message} occurred in.
*/
public MessageHandler(World world) {
this.world = world;
}
/**
* Handles the Message that was received.
*
* @param player The player to handle the Message for.
* @param message The Message.
*/
public abstract void handle(Player player, M message);
}
@@ -0,0 +1,73 @@
package org.apollo.game.message.handler;
import java.util.ArrayList;
import java.util.List;
import org.apollo.game.model.entity.Player;
import org.apollo.net.message.Message;
import com.google.common.base.MoreObjects;
/**
* A chain of {@link MessageHandler}s
*
* @author Graham
* @author Ryley
* @param <M> The Message type this chain represents.
*/
public final class MessageHandlerChain<M extends Message> {
/**
* The handlers.
*/
private final List<MessageHandler<M>> handlers = new ArrayList<>();
/**
* The Class type of this chain.
*/
private final Class<M> type;
/**
* Constructs a new {@link MessageHandlerChain}.
*
* @param type The Class type of this chain.
*/
public MessageHandlerChain(Class<M> type) {
this.type = type;
}
/**
* Adds the specified {@link MessageHandler} to this chain.
*
* @param handler The MessageHandler.
*/
public void addHandler(MessageHandler<M> handler) {
handlers.add(handler);
}
/**
* Notifies each {@link MessageHandler} in this chain that a {@link Message} has been received.
*
* @param player The Player to handle this message for.
* @param message The Message.
* @return {@code true} if and only if the Message propagated down the chain without being terminated, otherwise
* {@code false}.
*/
public boolean notify(Player player, M message) {
for (MessageHandler<M> handler : handlers) {
handler.handle(player, message);
if (message.terminated()) {
return false;
}
}
return true;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("type", type).add("handlers", handlers).toString();
}
}
@@ -0,0 +1,54 @@
package org.apollo.game.message.handler;
import java.util.HashMap;
import java.util.Map;
import org.apollo.game.model.entity.Player;
import org.apollo.net.message.Message;
/**
* A group of {@link MessageHandlerChain}s classified by the {@link Message} type.
*
* @author Graham
* @author Ryley
* @author Major
*/
public final class MessageHandlerChainSet {
/**
* The {@link Map} of Message types to {@link MessageHandlerChain}s
*/
private final Map<Class<? extends Message>, MessageHandlerChain<? extends Message>> chains = new HashMap<>();
/**
* Notifies the appropriate {@link MessageHandlerChain} that a {@link Message} has been received.
*
* @param player The {@link Player} receiving the Message.
* @param message The Message.
* @return {@code true} if the Message propagated down the chain without being terminated or if the chain for the
* Message was not found, otherwise {@code false}.
*/
@SuppressWarnings("unchecked")
public <M extends Message> boolean notify(Player player, M message) {
Class<M> clazz = (Class<M>) message.getClass();
while (clazz.getSuperclass() != Message.class) {
clazz = (Class<M>) clazz.getSuperclass();
}
MessageHandlerChain<M> chain = (MessageHandlerChain<M>) chains.computeIfAbsent(clazz, MessageHandlerChain::new);
return chain.notify(player, message);
}
/**
* Places the {@link MessageHandlerChain} into this set.
*
* @param clazz The {@link Class} to associate the MessageHandlerChain with.
* @param handler The MessageHandlerChain.
*/
@SuppressWarnings("unchecked")
public <M extends Message> void putHandler(Class<M> clazz, MessageHandler<? extends Message> handler) {
MessageHandlerChain<M> chain = (MessageHandlerChain<M>) chains.computeIfAbsent(clazz, MessageHandlerChain::new);
chain.addHandler((MessageHandler<M>) handler);
}
}
@@ -0,0 +1,52 @@
package org.apollo.game.message.handler;
import org.apollo.cache.def.NpcDefinition;
import org.apollo.game.message.impl.NpcActionMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Npc;
import org.apollo.game.model.entity.Player;
/**
* A verification {@link MessageHandler} for the {@link NpcActionMessage}.
*
* @author Stuart
* @author Major
*/
public final class NpcActionVerificationHandler extends MessageHandler<NpcActionMessage> {
/**
* Creates the NpcActionVerificationHandler.
*
* @param world The {@link World} the {@link NpcActionMessage} occurred in.
*/
public NpcActionVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, NpcActionMessage message) {
int index = message.getIndex();
MobRepository<Npc> repository = world.getNpcRepository();
if (index < 0 || index >= repository.capacity()) {
message.terminate();
return;
}
Npc npc = repository.get(index);
if (npc == null || !player.getPosition().isWithinDistance(npc.getPosition(), player.getViewingDistance() + 1)) {
// +1 in case it was decremented after the player clicked the action.
message.terminate();
return;
}
NpcDefinition definition = npc.getDefinition();
if (message.getOption() >= definition.getInteractions().length) {
message.terminate();
return;
}
}
}
@@ -0,0 +1,66 @@
package org.apollo.game.message.handler;
import java.util.List;
import java.util.Set;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.message.impl.ObjectActionMessage;
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.EntityType;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.obj.GameObject;
/**
* A verification {@link MessageHandler} for the {@link ObjectActionMessage}.
*
* @author Major
*/
public final class ObjectActionVerificationHandler extends MessageHandler<ObjectActionMessage> {
/**
* Indicates whether or not the {@link List} of {@link GameObject}s contains the object with the specified id.
*
* @param id The id of the object.
* @param objects The list of objects.
* @return {@code true} if the list does contain the object with the specified id, otherwise {@code false}.
*/
private static boolean containsObject(int id, Set<GameObject> objects) {
return objects.stream().anyMatch(object -> object.getId() == id);
}
/**
* Creates the ObjectActionVerificationHandler.
*
* @param world The {@link World} the {@link ObjectActionMessage} occurred in.
*/
public ObjectActionVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ObjectActionMessage message) {
int id = message.getId();
if (id < 0 || id >= ObjectDefinition.count()) {
message.terminate();
return;
}
Position position = message.getPosition();
Region region = world.getRegionRepository().fromPosition(position);
Set<GameObject> objects = region.getEntities(position, EntityType.STATIC_OBJECT, EntityType.DYNAMIC_OBJECT);
if (!player.getPosition().isWithinDistance(position, 15) || !containsObject(id, objects)) {
message.terminate();
return;
}
ObjectDefinition definition = ObjectDefinition.lookup(id);
if (message.getOption() >= definition.getMenuActions().length) {
message.terminate();
return;
}
}
}
@@ -0,0 +1,42 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.PlayerActionMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Player;
/**
* A verification {@link MessageHandler} for the {@link PlayerActionMessage}.
*
* @author Major
*/
public final class PlayerActionVerificationHandler extends MessageHandler<PlayerActionMessage> {
/**
* Creates the PlayerActionVerificationHandler.
*
* @param world The {@link World} the {@link PlayerActionMessage} occurred in.
*/
public PlayerActionVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, PlayerActionMessage message) {
int index = message.getIndex();
MobRepository<Player> repository = world.getPlayerRepository();
if (index < 0 || index >= repository.capacity()) {
message.terminate();
return;
}
Player other = repository.get(index);
if (other == null || !player.getPosition().isWithinDistance(other.getPosition(), player.getViewingDistance() + 1)) {
// +1 in case it was decremented after the player clicked the action.
message.terminate();
return;
}
}
}
@@ -0,0 +1,30 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.CloseInterfaceMessage;
import org.apollo.game.message.impl.PlayerDesignMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
/**
* A {@link MessageHandler} that handles {@link PlayerDesignMessage}s.
*
* @author Graham
*/
public final class PlayerDesignMessageHandler extends MessageHandler<PlayerDesignMessage> {
/**
* Creates the PlayerDesignMessageHandler.
*
* @param world The {@link World} the {@link PlayerDesignMessage} occurred in.
*/
public PlayerDesignMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, PlayerDesignMessage message) {
player.setAppearance(message.getAppearance());
player.send(new CloseInterfaceMessage());
}
}
@@ -0,0 +1,98 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.PlayerDesignMessage;
import org.apollo.game.model.Appearance;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.setting.Gender;
/**
* A {@link MessageHandler} that verifies {@link PlayerDesignMessage}s.
*
* @author Graham
*/
public final class PlayerDesignVerificationHandler extends MessageHandler<PlayerDesignMessage> {
/**
* Creates the PlayerDesignVerificationHandler.
*
* @param world The {@link World} the {@link PlayerDesignMessage} occurred in.
*/
public PlayerDesignVerificationHandler(World world) {
super(world);
}
@Override
public void handle(Player player, PlayerDesignMessage message) {
if (!valid(message.getAppearance())) {
message.terminate();
}
}
/**
* Checks if an appearance combination is valid.
*
* @param appearance The appearance combination.
* @return {@code true} if so, {@code false} if not.
*/
private boolean valid(Appearance appearance) {
int[] colors = appearance.getColors();
int[] maxColors = new int[] { 11, 15, 15, 5, 7 };
for (int i = 0; i < colors.length; i++) {
if (colors[i] < 0 || colors[i] > maxColors[i]) {
return false;
}
}
switch (appearance.getGender()) {
case FEMALE:
return validFemaleStyle(appearance);
case MALE:
return validMaleStyle(appearance);
}
throw new IllegalArgumentException("Player can only be either male or female.");
}
/**
* Checks if a {@link Gender#FEMALE} style combination is valid.
*
* @param appearance The appearance combination.
* @return {@code true} if so, {@code false} if not.
*/
private boolean validFemaleStyle(Appearance appearance) {
int[] styles = appearance.getStyle();
int[] minStyles = new int[] { 45, 255, 56, 61, 67, 70, 79 };
int[] maxStyles = new int[] { 54, 255, 60, 65, 68, 77, 80 };
for (int i = 0; i < styles.length; i++) {
if (styles[i] < minStyles[i] || styles[i] > maxStyles[i]) {
return false;
}
}
return true;
}
/**
* Checks if a {@link Gender#MALE} style combination is valid.
*
* @param appearance The appearance combination.
* @return {@code true} if so, {@code false} if not.
*/
private boolean validMaleStyle(Appearance appearance) {
int[] styles = appearance.getStyle();
int[] minStyles = new int[] { 0, 10, 18, 26, 33, 36, 42 };
int[] maxStyles = new int[] { 8, 17, 25, 31, 34, 40, 43 };
for (int i = 0; i < styles.length; i++) {
if (styles[i] < minStyles[i] || styles[i] > maxStyles[i]) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,66 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.ItemActionMessage;
import org.apollo.game.model.Item;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inv.Inventory;
import org.apollo.game.model.inv.SynchronizationInventoryListener;
/**
* A {@link MessageHandler} that removes equipped items.
*
* @author Graham
* @author Major
*/
public final class RemoveEquippedItemHandler extends MessageHandler<ItemActionMessage> {
/**
* Creates the RemoveEquippedItemHandler.
*
* @param world The {@link World} the {@link ItemActionMessage} occurred in.
*/
public RemoveEquippedItemHandler(World world) {
super(world);
}
@Override
public void handle(Player player, ItemActionMessage message) {
if (message.getOption() == 1 && message.getInterfaceId() == SynchronizationInventoryListener.EQUIPMENT_ID) {
Inventory inventory = player.getInventory();
Inventory equipment = player.getEquipment();
int slot = message.getSlot();
Item item = equipment.get(slot);
int id = item.getId();
if (inventory.freeSlots() == 0 && !item.getDefinition().isStackable()) {
inventory.forceCapacityExceeded();
message.terminate();
return;
}
boolean removed = true;
inventory.stopFiringEvents();
equipment.stopFiringEvents();
try {
int remaining = inventory.add(id, item.getAmount());
removed = remaining == 0;
equipment.set(slot, removed ? null : new Item(id, remaining));
} finally {
inventory.startFiringEvents();
equipment.startFiringEvents();
}
if (removed) {
inventory.forceRefresh();
equipment.forceRefresh(slot);
} else {
inventory.forceCapacityExceeded();
}
}
}
}
@@ -0,0 +1,55 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.SwitchItemMessage;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.inter.bank.BankConstants;
import org.apollo.game.model.inv.Inventory;
import org.apollo.game.model.inv.SynchronizationInventoryListener;
/**
* A {@link MessageHandler} which updates an {@link Inventory} when the client sends a {@link SwitchItemMessage} to the
* server.
*
* @author Graham
*/
public final class SwitchItemMessageHandler extends MessageHandler<SwitchItemMessage> {
/**
* Creates the SwitchItemMessageHandler.
*
* @param world The {@link World} the {@link SwitchItemMessage} occurred in.
*/
public SwitchItemMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, SwitchItemMessage message) {
Inventory inventory;
boolean insertPermitted = false;
switch (message.getInterfaceId()) {
case SynchronizationInventoryListener.INVENTORY_ID:
case BankConstants.SIDEBAR_INVENTORY_ID:
inventory = player.getInventory();
break;
case SynchronizationInventoryListener.EQUIPMENT_ID:
inventory = player.getEquipment();
break;
case BankConstants.BANK_INVENTORY_ID:
inventory = player.getBank();
insertPermitted = true;
break;
default:
return; // not a known inventory, ignore
}
int old = message.getOldSlot(), next = message.getNewSlot();
if (old >= 0 && next >= 0 && old < inventory.capacity() && next < inventory.capacity()) {
// events must be fired for it to work if a sidebar inventory overlay is used
inventory.swap(insertPermitted && message.isInserting(), old, next);
}
}
}
@@ -0,0 +1,53 @@
package org.apollo.game.message.handler;
import org.apollo.game.message.impl.WalkMessage;
import org.apollo.game.model.Position;
import org.apollo.game.model.World;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.WalkingQueue;
/**
* A {@link MessageHandler} that handles {@link WalkMessage}s.
*
* @author Graham
*/
public final class WalkMessageHandler extends MessageHandler<WalkMessage> {
/**
* Creates the WalkMessageHandler.
*
* @param world The {@link World} the {@link WalkMessage} occurred in.
*/
public WalkMessageHandler(World world) {
super(world);
}
@Override
public void handle(Player player, WalkMessage message) {
WalkingQueue queue = player.getWalkingQueue();
Position[] steps = message.getSteps();
for (int index = 0; index < steps.length; index++) {
Position step = steps[index];
if (index == 0) {
if (!queue.addFirstStep(step)) {
return; // ignore packet
}
} else {
queue.addStep(step);
}
}
queue.setRunningQueue(message.isRunning() || player.isRunning());
player.getInterfaceSet().close();
if (queue.size() > 0) {
player.stopAction();
}
if (player.getInteractingMob() != null) {
player.resetInteractingMob();
}
}
}
@@ -0,0 +1,4 @@
/**
* Contains message handler implementations.
*/
package org.apollo.game.message.handler;
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when a player adds someone to their friends list.
*
* @author Major
*/
public final class AddFriendMessage extends Message {
/**
* The username of the befriended player.
*/
private final String username;
/**
* Creates a new befriend user message.
*
* @param username The befriended player's username.
*/
public AddFriendMessage(String username) {
this.username = username;
}
/**
* Gets the username of the befriended player.
*
* @return The username.
*/
public String getUsername() {
return username;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when a player adds someone to their ignore list.
*
* @author Major
*/
public final class AddIgnoreMessage extends Message {
/**
* The username of the ignored player.
*/
private final String username;
/**
* Creates a new ignore player message.
*
* @param username The ignored player's username.
*/
public AddIgnoreMessage(String username) {
this.username = username;
}
/**
* Gets the username of the ignored player.
*
* @return The username.
*/
public String getUsername() {
return username;
}
}
@@ -0,0 +1,51 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when the user has pressed an arrow key.
*
* @author Major
*/
public final class ArrowKeyMessage extends Message {
/**
* The camera roll.
*/
private final int roll;
/**
* The camera yaw.
*/
private final int yaw;
/**
* Creates a new arrow key message.
*
* @param roll The camera roll.
* @param yaw The camera yaw.
*/
public ArrowKeyMessage(int roll, int yaw) {
this.roll = roll;
this.yaw = yaw;
}
/**
* Gets the roll of the camera.
*
* @return The roll.
*/
public int getRoll() {
return roll;
}
/**
* Gets the yaw of the camera.
*
* @return The yaw.
*/
public int getYaw() {
return yaw;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when a player clicks a button.
*
* @author Graham
*/
public final class ButtonMessage extends Message {
/**
* The widget id.
*/
private final int widgetId;
/**
* Creates the button message.
*
* @param widgetId The widget id.
*/
public ButtonMessage(int widgetId) {
this.widgetId = widgetId;
}
/**
* Gets the widget id.
*
* @return The widget id.
*/
public int getWidgetId() {
return widgetId;
}
}
@@ -0,0 +1,83 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client to send a public chat message to other players.
*
* @author Graham
*/
public final class ChatMessage extends Message {
/**
* The text color.
*/
private final int color;
/**
* The compressed message.
*/
private final byte[] compressedMessage;
/**
* The text effects.
*/
private final int effects;
/**
* The message.
*/
private final String message;
/**
* Creates a new chat message.
*
* @param message The message.
* @param compressedMessage The compressed message.
* @param color The text color.
* @param effects The text effects.
*/
public ChatMessage(String message, byte[] compressedMessage, int color, int effects) {
this.message = message;
this.compressedMessage = compressedMessage;
this.color = color;
this.effects = effects;
}
/**
* Gets the compressed message.
*
* @return The compressed message.
*/
public byte[] getCompressedMessage() {
return compressedMessage;
}
/**
* Gets the message.
*
* @return The message.
*/
public String getMessage() {
return message;
}
/**
* Gets the text color.
*
* @return The text color.
*/
public int getTextColor() {
return color;
}
/**
* Gets the text effects.
*
* @return The text effects.
*/
public int getTextEffects() {
return effects;
}
}
@@ -0,0 +1,53 @@
package org.apollo.game.message.impl;
import org.apollo.game.model.Position;
import org.apollo.game.model.area.RegionCoordinates;
import org.apollo.net.message.Message;
/**
* 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,12 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client that closes the open interface.
*
* @author Graham
*/
public final class CloseInterfaceMessage extends Message {
}
@@ -0,0 +1,12 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when the current interface is closed.
*
* @author Graham
*/
public final class ClosedInterfaceMessage extends Message {
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client to send a {@code ::} command.
*
* @author Graham
*/
public final class CommandMessage extends Message {
/**
* The command.
*/
private final String command;
/**
* Creates the command message.
*
* @param command The command.
*/
public CommandMessage(String command) {
this.command = command;
}
/**
* Gets the command.
*
* @return The command.
*/
public String getCommand() {
return command;
}
}
@@ -0,0 +1,51 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client to adjust a certain config or attribute setting.
*
* @author Chris Fletcher
*/
public final class ConfigMessage extends Message {
/**
* The identifier.
*/
private final int id;
/**
* The value.
*/
private final int value;
/**
* Creates a new config message.
*
* @param id The config's identifier.
* @param value The value.
*/
public ConfigMessage(int id, int value) {
this.id = id;
this.value = value;
}
/**
* Gets the config's identifier.
*
* @return The config id.
*/
public int getId() {
return id;
}
/**
* Gets the config's value.
*
* @return The config value.
*/
public int getValue() {
return value;
}
}
@@ -0,0 +1,36 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when the player clicks the "Click here to continue" button on a dialogue
* interface.
*
* @author Chris Fletcher
*/
public final class DialogueContinueMessage extends Message {
/**
* The interface id.
*/
private final int interfaceId;
/**
* Creates a new dialogue continue message.
*
* @param interfaceId The interface id.
*/
public DialogueContinueMessage(int interfaceId) {
this.interfaceId = interfaceId;
}
/**
* Gets the interface id of the button.
*
* @return The interface id.
*/
public int getInterfaceId() {
return interfaceId;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client to display crossbones when the player enters a multi-combat zone.
*
* @author Major
*/
public final class DisplayCrossbonesMessage extends Message {
/**
* Whether or not the crossbones should be displayed.
*/
private final boolean display;
/**
* Creates a display crossbones message.
*
* @param display Whether or not the crossbones should be displayed.
*/
public DisplayCrossbonesMessage(boolean display) {
this.display = display;
}
/**
* Indicates whether the crossbones will be displayed.
*
* @return {@code true} if the crossbones will be displayed, otherwise {@code false}.
*/
public boolean isDisplayed() {
return display;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client to change the currently displayed tab interface.
*
* @author Chris Fletcher
*/
public final class DisplayTabInterfaceMessage extends Message {
/**
* The tab index.
*/
private final int tab;
/**
* Creates a new display tab interface message.
*
* @param tab The index of the tab to display.
*/
public DisplayTabInterfaceMessage(int tab) {
this.tab = tab;
}
/**
* Gets the index of the tab to display.
*
* @return The tab index.
*/
public int getTab() {
return tab;
}
}
@@ -0,0 +1,12 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client to open up the enter amount interface.
*
* @author Graham
*/
public final class EnterAmountMessage extends Message {
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when the player has entered an amount.
*
* @author Graham
*/
public final class EnteredAmountMessage extends Message {
/**
* The amount.
*/
private final int amount;
/**
* Creates the entered amount message.
*
* @param amount The amount.
*/
public EnteredAmountMessage(int amount) {
this.amount = amount;
}
/**
* Gets the amount.
*
* @return The amount.
*/
public int getAmount() {
return amount;
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.message.impl;
/**
* The fifth {@link ItemActionMessage}.
*
* @author Graham
*/
public final class FifthItemActionMessage extends ItemActionMessage {
/**
* Creates the fifth item action message.
*
* @param interfaceId The interface id.
* @param id The item id.
* @param slot The item slot.
*/
public FifthItemActionMessage(int interfaceId, int id, int slot) {
super(5, interfaceId, id, slot);
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.message.impl;
/**
* The fifth {@link ItemOptionMessage}.
*
* @author Chris Fletcher
*/
public final class FifthItemOptionMessage extends ItemOptionMessage {
/**
* Creates the fifth item option message.
*
* @param interfaceId The interface id.
* @param id The id.
* @param slot The slot.
*/
public FifthItemOptionMessage(int interfaceId, int id, int slot) {
super(5, interfaceId, id, slot);
}
}
@@ -0,0 +1,20 @@
package org.apollo.game.message.impl;
/**
* The fifth {@link NpcActionMessage}.
*
* @author Major
* @author Stuart
*/
public final class FifthNpcActionMessage extends NpcActionMessage {
/**
* Creates the FifthNpcActionMessage.
*
* @param index The index of the Npc.
*/
public FifthNpcActionMessage(int index) {
super(5, index);
}
}
@@ -0,0 +1,19 @@
package org.apollo.game.message.impl;
/**
* The fifth {@link PlayerActionMessage}.
*
* @author Major
*/
public final class FifthPlayerActionMessage extends PlayerActionMessage {
/**
* Creates a fifth player action message.
*
* @param playerIndex The index of the clicked player.
*/
public FifthPlayerActionMessage(int playerIndex) {
super(5, playerIndex);
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.message.impl;
/**
* The first {@link ItemActionMessage}.
*
* @author Graham
*/
public final class FirstItemActionMessage extends ItemActionMessage {
/**
* Creates the first item action message.
*
* @param interfaceId The interface id.
* @param id The item id.
* @param slot The item slot.
*/
public FirstItemActionMessage(int interfaceId, int id, int slot) {
super(1, interfaceId, id, slot);
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.message.impl;
/**
* The first {@link ItemOptionMessage}.
*
* @author Chris Fletcher
*/
public final class FirstItemOptionMessage extends ItemOptionMessage {
/**
* Creates the first item option message.
*
* @param interfaceId The interface id.
* @param id The id.
* @param slot The slot.
*/
public FirstItemOptionMessage(int interfaceId, int id, int slot) {
super(1, interfaceId, id, slot);
}
}
@@ -0,0 +1,19 @@
package org.apollo.game.message.impl;
/**
* The first {@link NpcActionMessage}.
*
* @author Major
*/
public final class FirstNpcActionMessage extends NpcActionMessage {
/**
* Creates a new first npc action message.
*
* @param index The index of the npc.
*/
public FirstNpcActionMessage(int index) {
super(1, index);
}
}
@@ -0,0 +1,22 @@
package org.apollo.game.message.impl;
import org.apollo.game.model.Position;
/**
* The first {@link ObjectActionMessage}.
*
* @author Graham
*/
public final class FirstObjectActionMessage extends ObjectActionMessage {
/**
* Creates the first object action message.
*
* @param id The id.
* @param position The position.
*/
public FirstObjectActionMessage(int id, Position position) {
super(1, id, position);
}
}
@@ -0,0 +1,19 @@
package org.apollo.game.message.impl;
/**
* The first {@link PlayerActionMessage}.
*
* @author Major
*/
public final class FirstPlayerActionMessage extends PlayerActionMessage {
/**
* Creates a first player action message.
*
* @param playerIndex The index of the clicked player.
*/
public FirstPlayerActionMessage(int playerIndex) {
super(1, playerIndex);
}
}
@@ -0,0 +1,85 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when the player clicks with their mouse (or mousekeys etc).
*
* @author Major
*/
public final class FlaggedMouseEventMessage extends Message {
/**
* The number of clicks on this point (i.e. the point ({@link #x}, {@link #y})).
*/
private final int clickCount;
/**
* The x coordinate of the mouse click.
*/
private final int x;
/**
* The y coordinate of the mouse click.
*/
private final int y;
/**
* Indicates whether the {@link #x} and {@link #y} values represent the deviation from the last click or an actual
* point.
*/
private final boolean delta;
/**
* Creates a new mouse click message.
*
* @param clickCount The number of clicks on this point.
* @param x The x coordinate of the mouse click.
* @param y The y coordinate of the mouse click.
* @param delta If the coordinates represent a change in x/y, rather than the values themselves.
*/
public FlaggedMouseEventMessage(int clickCount, int x, int y, boolean delta) {
this.clickCount = clickCount;
this.x = x;
this.y = y;
this.delta = delta;
}
/**
* Gets the number of clicks on this point - maximum value of 2047.
*
* @return The number of clicks.
*/
public int getClickCount() {
return clickCount;
}
/**
* The x coordinate of the click.
*
* @return The x coordinate.
*/
public int getX() {
return x;
}
/**
* The y coordinate of the click.
*
* @return The y coordinate.
*/
public int getY() {
return y;
}
/**
* Gets the value indicating whether the {@link #x} and {@link #y} values represent the deviation from the last
* click or an actual point.
*
* @return The value.
*/
public boolean getDelta() {
return delta;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client to
*
* @author Major
*/
public final class FlashTabInterfaceMessage extends Message {
/**
* The id of the tab to flash.
*/
private final int tab;
/**
* Creates the FlashTabInterfaceMessage.
*
* @param tab The id of the tab to flash.
*/
public FlashTabInterfaceMessage(int tab) {
this.tab = tab;
}
/**
* Gets the id of the tab to flash.
*
* @return The id.
*/
public int getTab() {
return tab;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client indicating a flashing tab has been clicked.
*
* @author Major
*/
public final class FlashingTabClickedMessage extends Message {
/**
* The tab that was clicked.
*/
private final int tab;
/**
* Creates the FlashingTabClickedMessage.
*
* @param tab The tab that was clicked.
*/
public FlashingTabClickedMessage(int tab) {
this.tab = tab;
}
/**
* Gets the index of the tab that was clicked.
*
* @return The tab index.
*/
public int getTab() {
return tab;
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client to indicate a change in the client's focus (i.e. if it is the active window).
*
* @author Major
*/
public final class FocusUpdateMessage extends Message {
/**
* Indicates whether the client is focused or not.
*/
private final boolean focused;
/**
* Creates a new focus update message.
*
* @param focused The data received.
*/
public FocusUpdateMessage(boolean focused) {
this.focused = focused;
}
/**
* Indicates whether or not the client is focused.
*
* @return {@code true} if the client is focused, otherwise {@code false}.
*/
public boolean isFocused() {
return focused;
}
}
@@ -0,0 +1,68 @@
package org.apollo.game.message.impl;
import org.apollo.game.model.entity.setting.PrivilegeLevel;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client that forwards a private chat.
*
* @author Major
*/
public final class ForwardPrivateChatMessage extends Message {
/**
* The username of the player sending the message.
*/
private final String username;
/**
* The privilege level of the player.
*/
private final PrivilegeLevel privilege;
/**
* The message.
*/
private final byte[] message;
/**
* Creates a new forward private message message.
*
* @param username The username of the player sending the message.
* @param level The {@link PrivilegeLevel} of the player sending the message.
* @param message The compressed message.
*/
public ForwardPrivateChatMessage(String username, PrivilegeLevel level, byte[] message) {
this.username = username;
privilege = level;
this.message = message;
}
/**
* Gets the username of the sender.
*
* @return The username.
*/
public String getSenderUsername() {
return username;
}
/**
* Gets the {@link PrivilegeLevel} of the sender.
*
* @return The privilege level.
*/
public PrivilegeLevel getSenderPrivilege() {
return privilege;
}
/**
* Gets the compressed message.
*
* @return The message.
*/
public byte[] getCompressedMessage() {
return message;
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.message.impl;
/**
* The fourth {@link ItemActionMessage}.
*
* @author Graham
*/
public final class FourthItemActionMessage extends ItemActionMessage {
/**
* Creates the fourth item action message.
*
* @param interfaceId The interface id.
* @param id The item id.
* @param slot The item slot.
*/
public FourthItemActionMessage(int interfaceId, int id, int slot) {
super(4, interfaceId, id, slot);
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.message.impl;
/**
* The fourth {@link ItemOptionMessage}.
*
* @author Chris Fletcher
*/
public final class FourthItemOptionMessage extends ItemOptionMessage {
/**
* Creates the fourth item option message.
*
* @param interfaceId The interface id.
* @param id The id.
* @param slot The slot.
*/
public FourthItemOptionMessage(int interfaceId, int id, int slot) {
super(4, interfaceId, id, slot);
}
}
@@ -0,0 +1,20 @@
package org.apollo.game.message.impl;
/**
* The fourth {@link NpcActionMessage}.
*
* @author Major
* @author Stuart
*/
public final class FourthNpcActionMessage extends NpcActionMessage {
/**
* Creates the FourthNpcActionMessage.
*
* @param index The index of the Npc.
*/
public FourthNpcActionMessage(int index) {
super(4, index);
}
}
@@ -0,0 +1,19 @@
package org.apollo.game.message.impl;
/**
* The fourth {@link PlayerActionMessage}.
*
* @author Major
*/
public final class FourthPlayerActionMessage extends PlayerActionMessage {
/**
* Creates a fourth player action message.
*
* @param playerIndex The index of the clicked player.
*/
public FourthPlayerActionMessage(int playerIndex) {
super(4, playerIndex);
}
}
@@ -0,0 +1,36 @@
package org.apollo.game.message.impl;
import org.apollo.game.model.entity.setting.ServerStatus;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client to update the friend server status.
*
* @author Major
*/
public final class FriendServerStatusMessage extends Message {
/**
* The status code of the friend server.
*/
private final int status;
/**
* Creates a new friend server status message.
*
* @param status The status.
*/
public FriendServerStatusMessage(ServerStatus status) {
this.status = status.getCode();
}
/**
* Gets the status code of the friend server.
*
* @return The status code.
*/
public int getStatusCode() {
return status;
}
}
@@ -0,0 +1,71 @@
package org.apollo.game.message.impl;
import java.util.List;
import org.apollo.game.model.Position;
import org.apollo.game.model.area.RegionCoordinates;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client that contains multiple
*
* @author Major
*/
public final class GroupedRegionUpdateMessage extends Message {
/**
* The last known region Position of the Player.
*/
private final Position lastKnownRegion;
/**
* 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 lastKnownRegion The last known region {@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 lastKnownRegion, RegionCoordinates coordinates, List<RegionUpdateMessage> messages) {
this.lastKnownRegion = lastKnownRegion;
region = new Position(coordinates.getAbsoluteX(), coordinates.getAbsoluteY());
this.messages = messages;
}
/**
* Gets the {@link Position} of the Player.
*
* @return The Position.
*/
public Position getLastKnownRegion() {
return lastKnownRegion;
}
/**
* 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;
}
}
@@ -0,0 +1,154 @@
package org.apollo.game.message.impl;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.OptionalInt;
import org.apollo.game.model.Position;
import org.apollo.net.message.Message;
/**
* A {@link Message} that displays a hint icon over an Npc, tile, or player.
*
* @author Major
*/
public final class HintIconMessage extends Message {
// TODO identify the other types.
/**
* The type of a HintIcon.
*/
public enum Type {
/**
* A HintIcon that hovers over an Npc.
*/
NPC(1),
/**
* A HintIcon that hovers over a Player.
*/
PLAYER(10);
/**
* The integer value of this type.
*/
private final int value;
/**
* Creates the Type.
*
* @param value The value.
*/
private Type(int value) {
this.value = value;
}
/**
* Gets the value of this type.
*
* @return The value.
*/
public int getValue() {
return value;
}
}
/**
* Creates a HintIconMessage for the Npc with the specified index.
*
* @param index The index of the Npc.
* @return The HintIconMessage.
*/
public static HintIconMessage forNpc(int index) {
return new HintIconMessage(Type.NPC, OptionalInt.of(index), Optional.empty());
}
/**
* Creates a HintIconMessage for the Player with the specified index.
*
* @param index The index of the Player.
* @return The HintIconMessage.
*/
public static HintIconMessage forPlayer(int index) {
return new HintIconMessage(Type.PLAYER, OptionalInt.of(index), Optional.empty());
}
/**
* Creates a HintIconMessage that removes the current Npc hint icon.
*
* @return The HintIconMessage.
*/
public static HintIconMessage resetNpc() {
return forNpc(-1);
}
/**
* Creates a HintIconMessage that removes the current Player hint icon.
*
* @return The HintIconMessage.
*/
public static HintIconMessage resetPlayer() {
return forPlayer(-1);
}
/**
* The index of the Mob, if applicable.
*/
private final OptionalInt index;
/**
* The Position of the tile, if applicable.
*/
private final Optional<Position> position;
/**
* The Type of entity this HintIconMessage is directed at.
*/
private final Type type;
/**
* Creates the HintIconMessage.
*
* @param type The {@link Type} of this HintIconMessage.
* @param index The index of the Mob, if applicable.
* @param position The Position of the tile, if applicable.
*/
private HintIconMessage(Type type, OptionalInt index, Optional<Position> position) {
this.type = type;
this.index = index;
this.position = position;
}
/**
* Gets the index of the entity, if applicable.
*
* @return The index.
* @throws NoSuchElementException If no index is available for this HintIcon.
*/
public int getIndex() {
return index.getAsInt();
}
/**
* Gets the {@link Position} of the tile, if applicable.
*
* @return The Position.
* @throws NoSuchElementException If no Position is available for this HintIcon.
*/
public Position getPosition() {
return position.get();
}
/**
* Gets the type this HintIconMessage is directed at.
*
* @return The type.
*/
public Type getType() {
return type;
}
}
@@ -0,0 +1,52 @@
package org.apollo.game.message.impl;
import org.apollo.game.model.entity.setting.MembershipStatus;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client that specifies the local id and membership status of the current player.
*
* @author Graham
*/
public final class IdAssignmentMessage extends Message {
/**
* The id of this player.
*/
private final int id;
/**
* The MembershipStatus.
*/
private final MembershipStatus members;
/**
* Creates the local id message.
*
* @param id The id.
* @param members The MembershipStatus.
*/
public IdAssignmentMessage(int id, MembershipStatus members) {
this.id = id;
this.members = members;
}
/**
* Gets the id.
*
* @return The id.
*/
public int getId() {
return id;
}
/**
* Gets whether or not the Player is a {@link MembershipStatus#PAID paying member}.
*
* @return {@code true} if the Player is a paying member, {@code false} if not.
*/
public boolean isMembers() {
return members == MembershipStatus.PAID;
}
}
@@ -0,0 +1,37 @@
package org.apollo.game.message.impl;
import java.util.List;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent to the client that updates the ignored user list.
*
* @author Major
*/
public final class IgnoreListMessage extends Message {
/**
* The list of ignored player usernames.
*/
private final List<String> usernames;
/**
* Creates a new ignore list message.
*
* @param usernames The {@link List} of usernames to send.
*/
public IgnoreListMessage(List<String> usernames) {
this.usernames = usernames;
}
/**
* Gets the list of ignored usernames.
*
* @return The usernames.
*/
public List<String> getUsernames() {
return usernames;
}
}
@@ -0,0 +1,84 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} that represents some sort of action on an item in an inventory. Note that this is the parent of
* both item option and item action message, and so cannot be used to determine when one of those messages is fired.
*
* @author Chris Fletcher
*/
public abstract class InventoryItemMessage extends Message {
/**
* The item id.
*/
private final int id;
/**
* The interface id.
*/
private final int interfaceId;
/**
* The option number (1-5).
*/
private final int option;
/**
* The item's slot.
*/
private final int slot;
/**
* Creates the item action message.
*
* @param option The option number.
* @param interfaceId The interface id.
* @param id The id.
* @param slot The slot.
*/
protected InventoryItemMessage(int option, int interfaceId, int id, int slot) {
this.option = option;
this.interfaceId = interfaceId;
this.id = id;
this.slot = slot;
}
/**
* Gets the item id.
*
* @return The item id.
*/
public final int getId() {
return id;
}
/**
* Gets the interface id.
*
* @return The interface id.
*/
public final int getInterfaceId() {
return interfaceId;
}
/**
* Gets the option number.
*
* @return The option number.
*/
public final int getOption() {
return option;
}
/**
* Gets the slot.
*
* @return The slot.
*/
public final int getSlot() {
return slot;
}
}
@@ -0,0 +1,26 @@
package org.apollo.game.message.impl;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client that represents some sort of action on an item. Note that the actual message
* sent by the client is one of the five item action messages, but this is the message that should be intercepted (and
* the option verified).
*
* @author Chris Fletcher
*/
public abstract class ItemActionMessage extends InventoryItemMessage {
/**
* Creates the item action message.
*
* @param option The option number.
* @param interfaceId The interface id.
* @param id The id.
* @param slot The slot.
*/
public ItemActionMessage(int option, int interfaceId, int id, int slot) {
super(option, interfaceId, id, slot);
}
}
@@ -0,0 +1,69 @@
package org.apollo.game.message.impl;
/**
* A {@link InventoryItemMessage} sent by the client when a player uses one inventory item on another.
*
* @author Chris Fletcher
*/
public final class ItemOnItemMessage extends InventoryItemMessage {
/**
* The id of the target item.
*/
private final int targetId;
/**
* The interface id of the target item.
*/
private final int targetInterface;
/**
* The slot of the target item.
*/
private final int targetSlot;
/**
* Creates a new item-on-item message.
*
* @param usedInterface The interface id of the used item.
* @param usedId The id of the used item.
* @param usedSlot The slot of the target item.
* @param targetInterface The interface id of the target item.
* @param targetId The id of the target item.
* @param targetSlot The slot of the target item.
*/
public ItemOnItemMessage(int usedInterface, int usedId, int usedSlot, int targetInterface, int targetId, int targetSlot) {
super(0, usedInterface, usedId, usedSlot);
this.targetInterface = targetInterface;
this.targetSlot = targetSlot;
this.targetId = targetId;
}
/**
* Gets the id of the target item.
*
* @return The target item's interface id.
*/
public int getTargetId() {
return targetId;
}
/**
* Gets the interface id of the target item.
*
* @return The target item's interface id.
*/
public int getTargetInterfaceId() {
return targetInterface;
}
/**
* Gets the slot of the target item.
*
* @return The slot of the target item.
*/
public int getTargetSlot() {
return targetSlot;
}
}
@@ -0,0 +1,57 @@
package org.apollo.game.message.impl;
import org.apollo.game.model.Position;
import org.apollo.net.message.Message;
/**
* A {@link Message} sent by the client when an item is used on an object.
*
* @author Major
*/
public final class ItemOnObjectMessage extends InventoryItemMessage {
/**
* The object id the item was used on.
*/
private final int objectId;
/**
* The position of the object.
*/
private final Position position;
/**
* Creates an item on object message.
*
* @param interfaceId The interface id.
* @param itemId The item id.
* @param itemSlot The slot the item is in.
* @param objectId The object id.
* @param x The x coordinate.
* @param y The y coordinate.
*/
public ItemOnObjectMessage(int interfaceId, int itemId, int itemSlot, int objectId, int x, int y) {
super(0, interfaceId, itemId, itemSlot);
this.objectId = objectId;
position = new Position(x, y);
}
/**
* Gets the object id.
*
* @return The object id.
*/
public int getObjectId() {
return objectId;
}
/**
* Gets the position of the object.
*
* @return The position.
*/
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,24 @@
package org.apollo.game.message.impl;
/**
* An {@link InventoryItemMessage} sent by the client when an item's option is clicked (e.g. equip, eat, drink, etc).
* Note that the actual message sent by the client is one of the five item option messages, but this is the message that
* should be intercepted (and the option verified).
*
* @author Chris Fletcher
*/
public abstract class ItemOptionMessage extends InventoryItemMessage {
/**
* Creates the item option message.
*
* @param option The option number.
* @param interfaceId The interface id.
* @param id The id.
* @param slot The slot.
*/
public ItemOptionMessage(int option, int interfaceId, int id, int slot) {
super(option, interfaceId, id, slot);
}
}

Some files were not shown because too many files have changed in this diff Show More