mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-07 16:49:12 +00:00
Merge pull request #222 from apollo-rsps/bugfix/collision-detection
Add support for building and updating collision flags on the fly
This commit is contained in:
@@ -1,135 +0,0 @@
|
|||||||
package org.apollo.cache.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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes {@link MapDefinition}s from the {@link IndexedFileSystem}.
|
|
||||||
*
|
|
||||||
* @author Ryley
|
|
||||||
* @author Major
|
|
||||||
*/
|
|
||||||
public final class MapFileDecoder {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A definition for a region.
|
|
||||||
*/
|
|
||||||
public static final class MapDefinition {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether or not this map is members-only.
|
|
||||||
*/
|
|
||||||
private final boolean members;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The object file id.
|
|
||||||
*/
|
|
||||||
private final int objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The packed coordinates.
|
|
||||||
*/
|
|
||||||
private final int packedCoordinates;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The terrain file id.
|
|
||||||
*/
|
|
||||||
private final int terrain;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 id of the file containing the object data.
|
|
||||||
*
|
|
||||||
* @return The file id.
|
|
||||||
*/
|
|
||||||
public int getObjectFile() {
|
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The width (and length) of a map file, in tiles.
|
|
||||||
*/
|
|
||||||
public static final int MAP_FILE_WIDTH = 64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public static Map<Integer, MapDefinition> decode(IndexedFileSystem fs) throws IOException {
|
|
||||||
Archive archive = fs.getArchive(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 id = buffer.getShort() & 0xFFFF;
|
|
||||||
int terrain = buffer.getShort() & 0xFFFF;
|
|
||||||
int objects = buffer.getShort() & 0xFFFF;
|
|
||||||
boolean members = buffer.get() == 1;
|
|
||||||
|
|
||||||
definitions.put(id, new MapDefinition(id, terrain, objects, members));
|
|
||||||
}
|
|
||||||
|
|
||||||
return definitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains {@link MapFile}-related constants.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class MapConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index containing the map files.
|
||||||
|
*/
|
||||||
|
public static final int MAP_INDEX = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width (and length) of a {@link MapFile} in {@link Tile}s.
|
||||||
|
*/
|
||||||
|
public static final int MAP_WIDTH = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of planes in a MapFile.
|
||||||
|
*/
|
||||||
|
public static final int MAP_PLANES = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The multiplicand for height values.
|
||||||
|
*/
|
||||||
|
static final int HEIGHT_MULTIPLICAND = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lowest type value that will result in the decoding of a Tile being continued.
|
||||||
|
*/
|
||||||
|
static final int LOWEST_CONTINUED_TYPE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum type that specifies the Tile attributes.
|
||||||
|
*/
|
||||||
|
static final int MINIMUM_ATTRIBUTES_TYPE = 81;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum type that specifies the Tile underlay id.
|
||||||
|
*/
|
||||||
|
static final int MINIMUM_OVERLAY_TYPE = 49;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of possible overlay orientations.
|
||||||
|
*/
|
||||||
|
static final int ORIENTATION_COUNT = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height difference between two planes.
|
||||||
|
*/
|
||||||
|
static final int PLANE_HEIGHT_DIFFERENCE = 240;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sole private constructor to prevent instantiation.
|
||||||
|
*/
|
||||||
|
private MapConstants() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 3-dimensional 64x64 area of the map.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class MapFile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The array of MapPlanes.
|
||||||
|
*/
|
||||||
|
private final MapPlane[] planes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the MapFile.
|
||||||
|
*
|
||||||
|
* @param planes The {@link MapPlane}s.
|
||||||
|
*/
|
||||||
|
public MapFile(MapPlane[] planes) {
|
||||||
|
this.planes = planes.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link MapPlane} with the specified level.
|
||||||
|
*
|
||||||
|
* @param plane The plane.
|
||||||
|
* @return The MapPlane.
|
||||||
|
* @throws ArrayIndexOutOfBoundsException If {@code plane} is out of bounds.
|
||||||
|
*/
|
||||||
|
public MapPlane getPlane(int plane) {
|
||||||
|
int length = planes.length;
|
||||||
|
Preconditions.checkElementIndex(plane, length, "Plane index out of bounds, must be [0, " + length + ").");
|
||||||
|
return planes[plane];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all of the {@link MapPlane}s in this MapFile.
|
||||||
|
*
|
||||||
|
* @return The MapPlanes.
|
||||||
|
*/
|
||||||
|
public MapPlane[] getPlanes() {
|
||||||
|
return planes.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
import org.apollo.cache.IndexedFileSystem;
|
||||||
|
import org.apollo.util.CompressionUtil;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decoder for the terrain data stored in {@link MapFile}s.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public class MapFileDecoder {
|
||||||
|
/**
|
||||||
|
* Creates a MapFileDecoder for the specified map file.
|
||||||
|
*
|
||||||
|
* @param fs The {@link IndexedFileSystem} to get the file from.
|
||||||
|
* @param index The {@link MapIndex} to get the file index from.
|
||||||
|
* @return The MapFileDecoder.
|
||||||
|
* @throws IOException If there is an error reading or decompressing the file.
|
||||||
|
*/
|
||||||
|
public static MapFileDecoder create(IndexedFileSystem fs, MapIndex index) throws IOException {
|
||||||
|
ByteBuffer compressed = fs.getFile(MapConstants.MAP_INDEX, index.getMapFile());
|
||||||
|
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(compressed));
|
||||||
|
|
||||||
|
return new MapFileDecoder(decompressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DataBuffer containing the MapFile data.
|
||||||
|
*/
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the MapIndexDecoder.
|
||||||
|
* <p>
|
||||||
|
* This constructor expects the {@link ByteBuffer} to <strong>not</strong> be compressed.
|
||||||
|
*
|
||||||
|
* @param buffer The DataBuffer containing the MapFile data.
|
||||||
|
*/
|
||||||
|
public MapFileDecoder(ByteBuffer buffer) {
|
||||||
|
this.buffer = buffer.asReadOnlyBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the data into a {@link MapFile}.
|
||||||
|
*
|
||||||
|
* @return The MapFile.
|
||||||
|
*/
|
||||||
|
public MapFile decode() {
|
||||||
|
MapPlane[] planes = new MapPlane[MapConstants.MAP_PLANES];
|
||||||
|
|
||||||
|
for (int level = 0; level < MapConstants.MAP_PLANES; level++) {
|
||||||
|
planes[level] = decodePlane(planes, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MapFile(planes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a {@link MapPlane} with the specified level.
|
||||||
|
*
|
||||||
|
* @param planes The previously-decoded {@link MapPlane}s, for calculating the height of the tiles.
|
||||||
|
* @param level The level.
|
||||||
|
* @return The MapPlane.
|
||||||
|
*/
|
||||||
|
private MapPlane decodePlane(MapPlane[] planes, int level) {
|
||||||
|
Tile[][] tiles = new Tile[MapConstants.MAP_WIDTH][MapConstants.MAP_WIDTH];
|
||||||
|
|
||||||
|
for (int x = 0; x < MapConstants.MAP_WIDTH; x++) {
|
||||||
|
for (int z = 0; z < MapConstants.MAP_WIDTH; z++) {
|
||||||
|
tiles[x][z] = decodeTile(planes, level, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MapPlane(level, tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the data into a {@link Tile}.
|
||||||
|
*
|
||||||
|
* @param planes The previously-decoded {@link MapPlane}s, for calculating the height of the Tile.
|
||||||
|
* @param level The level the Tile is on.
|
||||||
|
* @param x The x coordinate of the Tile.
|
||||||
|
* @param z The z coordinate of the Tile.
|
||||||
|
* @return The MapFile.
|
||||||
|
*/
|
||||||
|
private Tile decodeTile(MapPlane[] planes, int level, int x, int z) {
|
||||||
|
Tile.Builder builder = Tile.builder(x, z, level);
|
||||||
|
|
||||||
|
int type;
|
||||||
|
do {
|
||||||
|
type = buffer.get() & 0xFF;
|
||||||
|
|
||||||
|
if (type == 0) {
|
||||||
|
if (level == 0) {
|
||||||
|
builder.setHeight(TileUtils.calculateHeight(x, z));
|
||||||
|
} else {
|
||||||
|
Tile below = planes[level - 1].getTile(x, z);
|
||||||
|
builder.setHeight(below.getHeight() + MapConstants.PLANE_HEIGHT_DIFFERENCE);
|
||||||
|
}
|
||||||
|
} else if (type == 1) {
|
||||||
|
int height = buffer.get();
|
||||||
|
int below = (level == 0) ? 0 : planes[level - 1].getTile(x, z).getHeight();
|
||||||
|
|
||||||
|
builder.setHeight((height == 1 ? 0 : height) * MapConstants.HEIGHT_MULTIPLICAND + below);
|
||||||
|
} else if (type <= MapConstants.MINIMUM_OVERLAY_TYPE) {
|
||||||
|
builder.setOverlay(buffer.get());
|
||||||
|
builder.setOverlayType((type - MapConstants.LOWEST_CONTINUED_TYPE)
|
||||||
|
/ MapConstants.ORIENTATION_COUNT);
|
||||||
|
builder.setOverlayOrientation(type - MapConstants.LOWEST_CONTINUED_TYPE
|
||||||
|
% MapConstants.ORIENTATION_COUNT);
|
||||||
|
} else if (type <= MapConstants.MINIMUM_ATTRIBUTES_TYPE) {
|
||||||
|
builder.setAttributes(type - MapConstants.MINIMUM_OVERLAY_TYPE);
|
||||||
|
} else {
|
||||||
|
builder.setUnderlay(type - MapConstants.MINIMUM_ATTRIBUTES_TYPE);
|
||||||
|
}
|
||||||
|
} while (type >= MapConstants.LOWEST_CONTINUED_TYPE);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
import org.apollo.cache.def.ItemDefinition;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A definition for a map.
|
||||||
|
*/
|
||||||
|
public final class MapIndex {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not this map is members-only.
|
||||||
|
*/
|
||||||
|
private final boolean members;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object file id.
|
||||||
|
*/
|
||||||
|
private final int objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The packed coordinates.
|
||||||
|
*/
|
||||||
|
private final int packedCoordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The terrain file id.
|
||||||
|
*/
|
||||||
|
private final int terrain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping of region ids to {@link MapIndex}es.
|
||||||
|
*/
|
||||||
|
private static Map<Integer, MapIndex> indices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the class with the specified set of indices.
|
||||||
|
*/
|
||||||
|
public static void init(Map<Integer, MapIndex> indices) {
|
||||||
|
MapIndex.indices = Collections.unmodifiableMap(indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@code Map} of {@link MapIndex} instances.
|
||||||
|
*
|
||||||
|
* @return The map of {@link MapIndex} instances.
|
||||||
|
*/
|
||||||
|
public static Map<Integer, MapIndex> getIndices() {
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the {@link MapIndex}.
|
||||||
|
*
|
||||||
|
* @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 MapIndex(int packedCoordinates, int terrain, int objects, boolean members) {
|
||||||
|
this.packedCoordinates = packedCoordinates;
|
||||||
|
this.terrain = terrain;
|
||||||
|
this.objects = objects;
|
||||||
|
this.members = members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the id of the file containing the object data.
|
||||||
|
*
|
||||||
|
* @return The file id.
|
||||||
|
*/
|
||||||
|
public int getObjectFile() {
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 getMapFile() {
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the X coordinate of this map.
|
||||||
|
*
|
||||||
|
* @return The X coordinate of this map.
|
||||||
|
*/
|
||||||
|
public int getX() {
|
||||||
|
return (packedCoordinates >> 8 & 0xFF) * MapConstants.MAP_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Y coordinate of this map.
|
||||||
|
*
|
||||||
|
* @return The y coordinate of this map.
|
||||||
|
*/
|
||||||
|
public int getY() {
|
||||||
|
return (packedCoordinates & 0xFF) * MapConstants.MAP_WIDTH;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this MapIndex is for a members-only area of the world.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this MapIndex is for a members-only area, {@code false} if not.
|
||||||
|
*/
|
||||||
|
public boolean isMembersOnly() {
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
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.cache.map.MapIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes {@link MapIndex}s from the {@link IndexedFileSystem}.
|
||||||
|
*
|
||||||
|
* @author Ryley
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class MapIndexDecoder implements Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file id of the versions archive.
|
||||||
|
*/
|
||||||
|
private static final int VERSIONS_ARCHIVE_FILE_ID = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The IndexedFileSystem.
|
||||||
|
*/
|
||||||
|
private final IndexedFileSystem fs;
|
||||||
|
|
||||||
|
public MapIndexDecoder(IndexedFileSystem fs) {
|
||||||
|
this.fs = fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes {@link MapIndex}s from the specified {@link IndexedFileSystem}.
|
||||||
|
*
|
||||||
|
* @return A {@link Map} of packed coordinates to their MapDefinitions.
|
||||||
|
* @throws IOException If there is an error reading or decoding the Archive.
|
||||||
|
*/
|
||||||
|
public Map<Integer, MapIndex> decode() throws IOException {
|
||||||
|
Archive archive = fs.getArchive(0, VERSIONS_ARCHIVE_FILE_ID);
|
||||||
|
ArchiveEntry entry = archive.getEntry("map_index");
|
||||||
|
Map<Integer, MapIndex> definitions = new HashMap<>();
|
||||||
|
|
||||||
|
ByteBuffer buffer = entry.getBuffer();
|
||||||
|
int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES);
|
||||||
|
|
||||||
|
for (int times = 0; times < count; times++) {
|
||||||
|
int id = buffer.getShort() & 0xFFFF;
|
||||||
|
int terrain = buffer.getShort() & 0xFFFF;
|
||||||
|
int objects = buffer.getShort() & 0xFFFF;
|
||||||
|
boolean members = buffer.get() == 1;
|
||||||
|
|
||||||
|
definitions.put(id, new MapIndex(id, terrain, objects, members));
|
||||||
|
}
|
||||||
|
|
||||||
|
return definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
MapIndex.init(decode());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a static world object in a map file.
|
||||||
|
*/
|
||||||
|
public final class MapObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object definition id of this {@code MapObject}.
|
||||||
|
*/
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The packed coordinates (local XY and height) for this object.
|
||||||
|
*/
|
||||||
|
private int packedCoordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this object.
|
||||||
|
*/
|
||||||
|
private final int type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The orientation of this object.
|
||||||
|
*/
|
||||||
|
private final int orientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code MapObject}.
|
||||||
|
*
|
||||||
|
* @param id The object ID of this map object.
|
||||||
|
* @param packedCoordinates A packed integer containing the coordinates of this map object.
|
||||||
|
* @param type The type of object.
|
||||||
|
* @param orientation The object facing direction.
|
||||||
|
*/
|
||||||
|
public MapObject(int id, int packedCoordinates, int type, int orientation) {
|
||||||
|
this.id = id;
|
||||||
|
this.packedCoordinates = packedCoordinates;
|
||||||
|
this.type = type;
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@code MapObject}.
|
||||||
|
*
|
||||||
|
* @param id The object ID of this map object.
|
||||||
|
* @param x The local X coordinate of this object.
|
||||||
|
* @param y The local Y coordinate of this object.
|
||||||
|
* @param height The height level of this object.
|
||||||
|
* @param type The type of this object.
|
||||||
|
* @param orientation The orientation of this object.
|
||||||
|
*/
|
||||||
|
public MapObject(int id, int x, int y, int height, int type, int orientation) {
|
||||||
|
this(id, (height & 0x3f) << 12 | (x & 0x3f) << 6 | (y & 0x3f), type, orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the object ID of this map object.
|
||||||
|
*
|
||||||
|
* @return The object ID for {@link org.apollo.cache.def.ObjectDefinition} lookups.
|
||||||
|
*/
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the plane this map object exists on.
|
||||||
|
*
|
||||||
|
* @return The plane this map object is on.
|
||||||
|
*/
|
||||||
|
public int getHeight() {
|
||||||
|
return packedCoordinates >> 12 & 0x3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the X coordinate of this object relative to the map position.
|
||||||
|
*
|
||||||
|
* @return The local X coordinate.
|
||||||
|
*/
|
||||||
|
public int getLocalX() {
|
||||||
|
return packedCoordinates >> 6 & 0x3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Y coordinate of this object relative to the map position.
|
||||||
|
*
|
||||||
|
* @return The local Y coordinate.
|
||||||
|
*/
|
||||||
|
public int getLocalY() {
|
||||||
|
return packedCoordinates & 0x3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the integer representation of this objects orientation (0 indexed, starting West-North-East-South).
|
||||||
|
*
|
||||||
|
* @return The orientation of this object.
|
||||||
|
*/
|
||||||
|
public int getOrientation() {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a packed integer containing the x/y coordinates and height for this object.
|
||||||
|
*
|
||||||
|
* @return The packed coordinates.
|
||||||
|
*/
|
||||||
|
public int getPackedCoordinates() {
|
||||||
|
return packedCoordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of this object.
|
||||||
|
*
|
||||||
|
* @return The type of this object.
|
||||||
|
*/
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
import org.apollo.cache.IndexedFileSystem;
|
||||||
|
import org.apollo.cache.map.MapIndex;
|
||||||
|
import org.apollo.cache.map.MapConstants;
|
||||||
|
import org.apollo.cache.map.MapObject;
|
||||||
|
import org.apollo.util.BufferUtil;
|
||||||
|
import org.apollo.util.CompressionUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decoder for reading the map objects for a given map.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class MapObjectsDecoder {
|
||||||
|
/**
|
||||||
|
* Creates a MapObjectsDecoder for the specified map file.
|
||||||
|
*
|
||||||
|
* @param fs The {@link IndexedFileSystem} to get the file from.
|
||||||
|
* @param index The map index to decode objects for.
|
||||||
|
* @return The MapObjectsDecoder.
|
||||||
|
* @throws IOException If there is an error reading or decompressing the file.
|
||||||
|
*/
|
||||||
|
public static MapObjectsDecoder create(IndexedFileSystem fs, MapIndex index) throws IOException {
|
||||||
|
ByteBuffer compressed = fs.getFile(MapConstants.MAP_INDEX, index.getObjectFile());
|
||||||
|
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(compressed));
|
||||||
|
|
||||||
|
return new MapObjectsDecoder(decompressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buffer to decode {@link MapObject}s from.
|
||||||
|
*/
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link MapObjectsDecoder} from the given buffer and map coordinates.
|
||||||
|
*
|
||||||
|
* @param buffer The decompressed object file buffer.
|
||||||
|
*/
|
||||||
|
public MapObjectsDecoder(ByteBuffer buffer) {
|
||||||
|
this.buffer = buffer.asReadOnlyBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the data in the {@code buffer} to a list of {@link MapObject}s.
|
||||||
|
*
|
||||||
|
* @return A list of decoded {@link MapObject}s.
|
||||||
|
*/
|
||||||
|
public List<MapObject> decode() {
|
||||||
|
List<MapObject> objects = new ArrayList<>();
|
||||||
|
|
||||||
|
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 attributes = buffer.get() & 0xFF;
|
||||||
|
int type = attributes >> 2;
|
||||||
|
int orientation = attributes & 0x3;
|
||||||
|
objects.add(new MapObject(id, packed, type, orientation));
|
||||||
|
|
||||||
|
positionOffset = BufferUtil.readSmart(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
idOffset = BufferUtil.readSmart(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plane of a map, which is a distinct height level.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class MapPlane {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a shallow copy of the specified 2-dimensional array.
|
||||||
|
*
|
||||||
|
* @param array The array to copy. Must not be {@code null}.
|
||||||
|
* @return The copy.
|
||||||
|
*/
|
||||||
|
private static <T> T[][] clone(T[][] array) {
|
||||||
|
T[][] copy = array.clone();
|
||||||
|
for (int index = 0; index < copy.length; index++) {
|
||||||
|
copy[index] = array[index].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The level of this MapPlane.
|
||||||
|
*/
|
||||||
|
private final int level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 2-dimensional array of Tiles.
|
||||||
|
*/
|
||||||
|
private final Tile[][] tiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the MapPlane.
|
||||||
|
*
|
||||||
|
* @param level The level of the MapPlane.
|
||||||
|
* @param tiles The 2D array of {@link Tile}s. Must not be {@code null}. Must be square.
|
||||||
|
*/
|
||||||
|
public MapPlane(int level, Tile[][] tiles) {
|
||||||
|
this.level = level;
|
||||||
|
this.tiles = clone(tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the level of this MapPlane.
|
||||||
|
*
|
||||||
|
* @return The level.
|
||||||
|
*/
|
||||||
|
public int getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the amount of tiles in this MapPlane.
|
||||||
|
*
|
||||||
|
* @return The amount of tiles.
|
||||||
|
*/
|
||||||
|
public int getSize() {
|
||||||
|
return tiles.length * tiles[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link Tile} at the specified (x, z) coordinate.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param z The z coordinate.
|
||||||
|
* @return The Tile.
|
||||||
|
*/
|
||||||
|
public Tile getTile(int x, int z) {
|
||||||
|
return tiles[x][z];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link Tile}s in this MapPlane.
|
||||||
|
* <p>
|
||||||
|
* This method returns the Tiles according on a column-based ordering: for a 2x2 tile set, the order will be
|
||||||
|
* {@code (0, 0), (0, 1), (1, 0), (1, 1)}.
|
||||||
|
*
|
||||||
|
* @return The Tiles.
|
||||||
|
*/
|
||||||
|
public Stream<Tile> getTiles() {
|
||||||
|
return Arrays.stream(tiles).flatMap(Arrays::stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+295
@@ -0,0 +1,295 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single tile on the map.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class Tile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder class for a Tile.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes of the Tile.
|
||||||
|
*/
|
||||||
|
private int attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of the Tile.
|
||||||
|
*/
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay id of the Tile.
|
||||||
|
*/
|
||||||
|
private int overlay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay orientation of the Tile.
|
||||||
|
*/
|
||||||
|
private int overlayOrientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay type of the Tile.
|
||||||
|
*/
|
||||||
|
private int overlayType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x coordinate of the Tile.
|
||||||
|
*/
|
||||||
|
private int x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y coordinate of the Tile.
|
||||||
|
*/
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlay id of the Tile.
|
||||||
|
*/
|
||||||
|
private int underlay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Builder.
|
||||||
|
*
|
||||||
|
* @param x The x position of the Tile.
|
||||||
|
* @param y The y position of the Tile.
|
||||||
|
* @param height The height level of the Tile.
|
||||||
|
*/
|
||||||
|
public Builder(int x, int y, int height) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the contents of this Builder into a Tile.
|
||||||
|
*
|
||||||
|
* @return The Tile.
|
||||||
|
*/
|
||||||
|
public Tile build() {
|
||||||
|
return new Tile(x, y, attributes, height, overlay, overlayType, overlayOrientation, underlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the attributes of the Tile.
|
||||||
|
*
|
||||||
|
* @param attributes The attributes.
|
||||||
|
*/
|
||||||
|
public void setAttributes(int attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the height of the Tile.
|
||||||
|
*
|
||||||
|
* @param height The height.
|
||||||
|
*/
|
||||||
|
public void setHeight(int height) {
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the overlay id of the Tile.
|
||||||
|
*
|
||||||
|
* @param overlay The overlay id.
|
||||||
|
*/
|
||||||
|
public void setOverlay(int overlay) {
|
||||||
|
this.overlay = overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the overlay orientation of the Tile.
|
||||||
|
*
|
||||||
|
* @param orientation The overlay orientation.
|
||||||
|
*/
|
||||||
|
public void setOverlayOrientation(int orientation) {
|
||||||
|
this.overlayOrientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the overlay type of the Tile.
|
||||||
|
*
|
||||||
|
* @param type The overlay type.
|
||||||
|
*/
|
||||||
|
public void setOverlayType(int type) {
|
||||||
|
this.overlayType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the position of the Tile.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the Tile.
|
||||||
|
* @param y the y coordinate of the Tile
|
||||||
|
* @param height The height level of the Tile.
|
||||||
|
*/
|
||||||
|
public void setPosition(int x, int y, int height) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the underlay id of the Tile.
|
||||||
|
*
|
||||||
|
* @param underlay The underlay.
|
||||||
|
*/
|
||||||
|
public void setUnderlay(int underlay) {
|
||||||
|
this.underlay = underlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Builder} for a Tile.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the Tile.
|
||||||
|
* @param y the y coordinate of the Tile.
|
||||||
|
* @param height The height level of the Tile.
|
||||||
|
* @return The Builder.
|
||||||
|
*/
|
||||||
|
public static Builder builder(int x, int y, int height) {
|
||||||
|
return new Builder(x, y, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes of this Tile.
|
||||||
|
*/
|
||||||
|
private final int attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of this Tile.
|
||||||
|
*/
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay id of this Tile.
|
||||||
|
*/
|
||||||
|
private final int overlay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay orientation of this Tile.
|
||||||
|
*/
|
||||||
|
private final int overlayOrientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay type of this Tile.
|
||||||
|
*/
|
||||||
|
private final int overlayType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x coordinate of this Tile.
|
||||||
|
*/
|
||||||
|
private final int x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y coordinate of this Tile.
|
||||||
|
*/
|
||||||
|
private final int y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlay id of this Tile.
|
||||||
|
*/
|
||||||
|
private final int underlay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Tile.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the Tile.
|
||||||
|
* @param y The y coordinate of the Tile.
|
||||||
|
* @param attributes The attributes.
|
||||||
|
* @param height The height.
|
||||||
|
* @param overlay The overlay id.
|
||||||
|
* @param overlayType The overlay type.
|
||||||
|
* @param overlayOrientation The overlay orientation.
|
||||||
|
* @param underlay The underlay id.
|
||||||
|
*/
|
||||||
|
public Tile(int x, int y, int attributes, int height, int overlay, int overlayType, int overlayOrientation,
|
||||||
|
int underlay) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.attributes = attributes;
|
||||||
|
this.height = height;
|
||||||
|
this.overlay = overlay;
|
||||||
|
this.overlayType = overlayType;
|
||||||
|
this.overlayOrientation = overlayOrientation;
|
||||||
|
this.underlay = underlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attributes of this Tile.
|
||||||
|
*
|
||||||
|
* @return The attributes.
|
||||||
|
*/
|
||||||
|
public int getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the height of this Tile.
|
||||||
|
*
|
||||||
|
* @return The height.
|
||||||
|
*/
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the overlay id of this Tile.
|
||||||
|
*
|
||||||
|
* @return The overlay id.
|
||||||
|
*/
|
||||||
|
public int getOverlay() {
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the overlay orientation of this Tile.
|
||||||
|
*
|
||||||
|
* @return The overlay orientation.
|
||||||
|
*/
|
||||||
|
public int getOverlayOrientation() {
|
||||||
|
return overlayOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the overlay type of this Tile.
|
||||||
|
*
|
||||||
|
* @return The overlay types.
|
||||||
|
*/
|
||||||
|
public int getOverlayType() {
|
||||||
|
return overlayType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the underlay id of this Tile.
|
||||||
|
*
|
||||||
|
* @return The underlay id.
|
||||||
|
*/
|
||||||
|
public int getUnderlay() {
|
||||||
|
return underlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the x coordinate of this Tile.
|
||||||
|
*
|
||||||
|
* @return The x coordinate.
|
||||||
|
*/
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the y coordinate of this Tile.
|
||||||
|
*
|
||||||
|
* @return The y coordinate.
|
||||||
|
*/
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
package org.apollo.cache.map;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012-2013 Jonathan Edgecombe <jonathanedgecombe@gmail.com>
|
||||||
|
* Copyright (c) 2015 Major
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains tile-related utility methods.
|
||||||
|
*
|
||||||
|
* @author Johnny
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class TileUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x coordinate offset, used for computing the Tile height.
|
||||||
|
*/
|
||||||
|
static final int TILE_HEIGHT_X_OFFSET = 0xe3b7b;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The z coordinate offset, used for computing the Tile height.
|
||||||
|
*/
|
||||||
|
static final int TILE_HEIGHT_Z_OFFSET = 0x87cce;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cosine table used for interpolation.
|
||||||
|
*/
|
||||||
|
private static final int[] COSINE = new int[2048];
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int index = 0; index < COSINE.length; index++) {
|
||||||
|
COSINE[index] = (int) (65536 * Math.cos(2 * Math.PI * index / COSINE.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the height offset for the specified coordinate pair.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the Tile.
|
||||||
|
* @param z The z coordinate of the Tile.
|
||||||
|
* @return The height offset.
|
||||||
|
*/
|
||||||
|
public static int calculateHeight(int x, int z) {
|
||||||
|
int regionSize = 8;
|
||||||
|
int regionOffset = 6;
|
||||||
|
int offset = regionOffset * regionSize;
|
||||||
|
|
||||||
|
int baseX = x - offset;
|
||||||
|
int baseZ = z - offset;
|
||||||
|
|
||||||
|
return computeHeight(x + TILE_HEIGHT_X_OFFSET - baseX, z + TILE_HEIGHT_Z_OFFSET - baseZ)
|
||||||
|
* MapConstants.HEIGHT_MULTIPLICAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the height offset for the specified coordinate pair.
|
||||||
|
*
|
||||||
|
* @param x The offset-x coordinate of the tile.
|
||||||
|
* @param z The offset-z coordinate of the tile.
|
||||||
|
* @return The tile height offset.
|
||||||
|
*/
|
||||||
|
private static int computeHeight(int x, int z) {
|
||||||
|
int total = interpolatedNoise(x + 45365, z + 91923, 4) - 128;
|
||||||
|
|
||||||
|
total += (interpolatedNoise(x + 10294, z + 37821, 2) - 128) / 2;
|
||||||
|
total += (interpolatedNoise(x, z, 1) - 128) / 4;
|
||||||
|
|
||||||
|
total = (int) Math.max(total * 0.3 + 35, 10);
|
||||||
|
return Math.min(total, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolates two smooth noise values.
|
||||||
|
*
|
||||||
|
* @param a The first smooth noise value.
|
||||||
|
* @param b The second smooth noise value.
|
||||||
|
* @param theta The angle.
|
||||||
|
* @param reciprocal The frequency reciprocal.
|
||||||
|
* @return The interpolated value.
|
||||||
|
*/
|
||||||
|
private static int interpolate(int a, int b, int theta, int reciprocal) {
|
||||||
|
int cosine = 65536 - COSINE[theta * COSINE.length / (2 * reciprocal)] / 2;
|
||||||
|
return (a * (65536 - cosine)) / 65536 + (b * cosine) / 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets interpolated noise for the specified coordinate pair, using the specified frequency reciprocal.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param z The z coordinate.
|
||||||
|
* @param reciprocal The frequency reciprocal.
|
||||||
|
* @return The interpolated noise.
|
||||||
|
*/
|
||||||
|
private static int interpolatedNoise(int x, int z, int reciprocal) {
|
||||||
|
int xt = x % reciprocal;
|
||||||
|
int zt = z % reciprocal;
|
||||||
|
|
||||||
|
x /= reciprocal;
|
||||||
|
z /= reciprocal;
|
||||||
|
|
||||||
|
int c = smoothNoise(x, z);
|
||||||
|
int e = smoothNoise(x + 1, z);
|
||||||
|
int ce = interpolate(c, e, xt, reciprocal);
|
||||||
|
|
||||||
|
int n = smoothNoise(x, z + 1);
|
||||||
|
int ne = smoothNoise(x + 1, z + 1);
|
||||||
|
int u = interpolate(n, ne, xt, reciprocal);
|
||||||
|
|
||||||
|
return interpolate(ce, u, zt, reciprocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes noise for the specified coordinate pair.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param z The z coordinate.
|
||||||
|
* @return The noise.
|
||||||
|
*/
|
||||||
|
private static int noise(int x, int z) {
|
||||||
|
int n = x + z * 57;
|
||||||
|
n = (n << 13) ^ n;
|
||||||
|
n = (n * (n * n * 15731 + 789221) + 1376312589) & Integer.MAX_VALUE;
|
||||||
|
return (n >> 19) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes smooth noise for the specified coordinate pair.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param z The z coordinate.
|
||||||
|
* @return The smooth noise.
|
||||||
|
*/
|
||||||
|
private static int smoothNoise(int x, int z) {
|
||||||
|
int corners = noise(x - 1, z - 1) + noise(x + 1, z - 1) + noise(x - 1, z + 1) + noise(x + 1, z + 1);
|
||||||
|
int sides = noise(x - 1, z) + noise(x + 1, z) + noise(x, z - 1) + noise(x, z + 1);
|
||||||
|
int center = noise(x, z);
|
||||||
|
|
||||||
|
return corners / 16 + sides / 8 + center / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sole private constructor to prevent instantiation.
|
||||||
|
*/
|
||||||
|
private TileUtils() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
package org.apollo.game.fs.decoder;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apollo.cache.IndexedFileSystem;
|
|
||||||
import org.apollo.cache.decoder.MapFileDecoder;
|
|
||||||
import org.apollo.cache.decoder.MapFileDecoder.MapDefinition;
|
|
||||||
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
|
|
||||||
import org.apollo.cache.def.ObjectDefinition;
|
|
||||||
import org.apollo.game.io.player.PlayerSerializer;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses static object definitions, which include map tiles and landscapes.
|
|
||||||
*
|
|
||||||
* @author Ryley
|
|
||||||
* @author Major
|
|
||||||
*/
|
|
||||||
public final class GameObjectDecoder implements Runnable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The World to place the objects in.
|
|
||||||
*/
|
|
||||||
private final World world;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The most-recently used Region.
|
|
||||||
*/
|
|
||||||
private Region previous;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the GameObjectDecoder.
|
|
||||||
*
|
|
||||||
* @param fs The {@link IndexedFileSystem}.
|
|
||||||
* @param world The {@link World} to place the objects in.
|
|
||||||
*/
|
|
||||||
public GameObjectDecoder(IndexedFileSystem fs, World world) {
|
|
||||||
this.fs = fs;
|
|
||||||
this.world = world;
|
|
||||||
regions = world.getRegionRepository();
|
|
||||||
previous = regions.fromPosition(PlayerSerializer.TUTORIAL_ISLAND_SPAWN); // dummy, so 'previous' is never null.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ObjectDefinitionDecoder decoder = new ObjectDefinitionDecoder(fs);
|
|
||||||
decoder.run();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
|
|
||||||
|
|
||||||
for (MapDefinition definition : definitions.values()) {
|
|
||||||
int packed = definition.getPackedCoordinates();
|
|
||||||
int x = (packed >> 8 & 0xFF) * (Region.SIZE * Region.SIZE);
|
|
||||||
int y = (packed & 0xFF) * (Region.SIZE * Region.SIZE);
|
|
||||||
|
|
||||||
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
|
|
||||||
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
|
|
||||||
decodeObjects(decompressed, x, y);
|
|
||||||
|
|
||||||
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
|
|
||||||
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
|
|
||||||
decodeTerrain(decompressed, x, y);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Error decoding StaticGameObjects.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
objects.forEach(object -> regions.fromPosition(object.getPosition()).addEntity(object, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
|
|
||||||
int x = position.getX(), y = position.getY(), height = position.getHeight();
|
|
||||||
|
|
||||||
if (!previous.contains(position)) {
|
|
||||||
previous = regions.fromPosition(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
CollisionMatrix matrix = previous.getMatrix(height);
|
|
||||||
if (unwalkable(definition, type)) {
|
|
||||||
int width = definition.getWidth(), length = definition.getLength();
|
|
||||||
|
|
||||||
for (int dx = 0; dx < width; dx++) {
|
|
||||||
for (int dy = 0; dy < length; 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;
|
|
||||||
Region next = regions.fromPosition(new Position(nextLocalX, nextLocalY));
|
|
||||||
|
|
||||||
int nextX = nextLocalX % Region.SIZE + dx;
|
|
||||||
int nextY = nextLocalY % 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 x The x coordinate of the tile the attributes belong to.
|
|
||||||
* @param y The y coordinate of the tile the attributes belong to.
|
|
||||||
* @param height The level level of the tile the attributes belong to.
|
|
||||||
*/
|
|
||||||
private void decodeAttributes(int attributes, int x, int y, int height) {
|
|
||||||
boolean block = false;
|
|
||||||
if ((attributes & BLOCKED_TILE) != 0) {
|
|
||||||
block = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((attributes & BRIDGE_TILE) != 0 && height >0) {
|
|
||||||
block = true;
|
|
||||||
height--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block) {
|
|
||||||
int localX = x % Region.SIZE, localY = y % Region.SIZE;
|
|
||||||
Position position = new Position(x, y, height);
|
|
||||||
|
|
||||||
if (!previous.contains(position)) {
|
|
||||||
previous = regions.fromPosition(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
previous.getMatrix(height).block(localX, localY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes object data stored in the specified {@link ByteBuffer}.
|
|
||||||
*
|
|
||||||
* @param buffer The ByteBuffer to decode data from.
|
|
||||||
* @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(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 < Position.HEIGHT_LEVELS; height++) {
|
|
||||||
for (int localX = 0; localX < Region.SIZE * Region.SIZE; localX++) {
|
|
||||||
for (int localY = 0; localY < Region.SIZE * Region.SIZE; localY++) {
|
|
||||||
int attributes = 0;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
int attributeId = buffer.get() & 0xFF;
|
|
||||||
|
|
||||||
if (attributeId == 0) {
|
|
||||||
decodeAttributes(attributes, x + localX, y + localY, height);
|
|
||||||
break;
|
|
||||||
} else if (attributeId == 1) {
|
|
||||||
buffer.get();
|
|
||||||
decodeAttributes(attributes, x + localX, y + localY, height);
|
|
||||||
break;
|
|
||||||
} else if (attributeId <= 49) {
|
|
||||||
buffer.get();
|
|
||||||
} else if (attributeId <= 81) {
|
|
||||||
attributes = attributeId - 49;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not an object with the specified {@link ObjectDefinition} and {@code type} should result in
|
|
||||||
* the tile(s) it is located on being blocked.
|
|
||||||
*
|
|
||||||
* @param definition The {@link ObjectDefinition} of the object.
|
|
||||||
* @param type The type of the object.
|
|
||||||
* @return {@code true} iff the tile(s) the object is on should be blocked.
|
|
||||||
*/
|
|
||||||
private boolean unwalkable(ObjectDefinition definition, int type) {
|
|
||||||
// TODO figure out the other ObjectTypes and get rid of all the getValue() calls
|
|
||||||
return (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) ||
|
|
||||||
(type >= ObjectType.LENGTHWISE_WALL.getValue() && type <= ObjectType.RECTANGULAR_CORNER.getValue()) ||
|
|
||||||
(type > ObjectType.DIAGONAL_INTERACTABLE.getValue() && type < ObjectType.FLOOR_DECORATION.getValue()) ||
|
|
||||||
(type == ObjectType.INTERACTABLE.getValue() && definition.isSolid()) ||
|
|
||||||
type == ObjectType.DIAGONAL_WALL.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package org.apollo.game.fs.decoder;
|
||||||
|
|
||||||
|
import org.apollo.cache.IndexedFileSystem;
|
||||||
|
import org.apollo.cache.map.MapConstants;
|
||||||
|
import org.apollo.cache.map.MapFile;
|
||||||
|
import org.apollo.cache.map.MapFileDecoder;
|
||||||
|
import org.apollo.cache.map.MapIndex;
|
||||||
|
import org.apollo.cache.map.MapPlane;
|
||||||
|
import org.apollo.cache.map.Tile;
|
||||||
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decoder which loads {@link MapFile}s and notifies the {@link CollisionManager} of tiles which are blocked,
|
||||||
|
* or on a bridge.
|
||||||
|
*/
|
||||||
|
public final class WorldMapDecoder implements Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bit flag that indicates that the tile at the current Position is blocked.
|
||||||
|
*/
|
||||||
|
private static final int BLOCKED_TILE = 0x1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bit flag that indicates that the tile at the current Position is a bridge tile.
|
||||||
|
*/
|
||||||
|
private static final int BRIDGE_TILE = 0x2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link IndexedFileSystem}.
|
||||||
|
*/
|
||||||
|
private IndexedFileSystem fs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CollisionManager} to notify of bridged / blocked tiles.
|
||||||
|
*/
|
||||||
|
private CollisionManager collisionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link WorldMapDecoder}.
|
||||||
|
*
|
||||||
|
* @param fs The {@link IndexedFileSystem} to load {@link MapFile}s. from.
|
||||||
|
* @param collisionManager The {@link CollisionManager} to register tiles with.
|
||||||
|
*/
|
||||||
|
public WorldMapDecoder(IndexedFileSystem fs, CollisionManager collisionManager) {
|
||||||
|
this.fs = fs;
|
||||||
|
this.collisionManager = collisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode all {@link MapFile}s and notify the {@link CollisionManager} of any tiles that are
|
||||||
|
* flagged as blocked or on a bridge.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Map<Integer, MapIndex> mapIndices = MapIndex.getIndices();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (MapIndex index : mapIndices.values()) {
|
||||||
|
MapFileDecoder decoder = MapFileDecoder.create(fs, index);
|
||||||
|
MapFile mapFile = decoder.decode();
|
||||||
|
MapPlane[] mapPlanes = mapFile.getPlanes();
|
||||||
|
|
||||||
|
int mapX = index.getX(), mapY = index.getY();
|
||||||
|
for (MapPlane plane : mapPlanes) {
|
||||||
|
markTiles(mapX, mapY, plane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark any tiles in the given {@link MapPlane} as blocked or bridged in the {@link CollisionManager}.
|
||||||
|
*
|
||||||
|
* @param mapX The X coordinate of the map file.
|
||||||
|
* @param mapY The Y coordinate of the map file.
|
||||||
|
* @param plane The {@link MapPlane} to load tiles from.
|
||||||
|
*/
|
||||||
|
private void markTiles(int mapX, int mapY, MapPlane plane) {
|
||||||
|
for (int x = 0; x < MapConstants.MAP_WIDTH; x++) {
|
||||||
|
for (int y = 0; y < MapConstants.MAP_WIDTH; y++) {
|
||||||
|
Tile tile = plane.getTile(x, y);
|
||||||
|
Position position = new Position(mapX + x, mapY + y, plane.getLevel());
|
||||||
|
|
||||||
|
if ((tile.getAttributes() & BLOCKED_TILE) == BLOCKED_TILE) {
|
||||||
|
collisionManager.markBlocked(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tile.getAttributes() & BRIDGE_TILE) == BRIDGE_TILE) {
|
||||||
|
collisionManager.markBridged(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package org.apollo.game.fs.decoder;
|
||||||
|
|
||||||
|
import org.apollo.cache.IndexedFileSystem;
|
||||||
|
import org.apollo.cache.map.MapIndex;
|
||||||
|
import org.apollo.cache.map.MapObject;
|
||||||
|
import org.apollo.cache.map.MapObjectsDecoder;
|
||||||
|
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.entity.obj.StaticGameObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decoder which decodes {@link MapObject}s and registers them with the game world.
|
||||||
|
*/
|
||||||
|
public final class WorldObjectsDecoder implements Runnable {
|
||||||
|
/**
|
||||||
|
* The IndexedFileSystem.
|
||||||
|
*/
|
||||||
|
private final IndexedFileSystem fs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RegionRepository} to lookup {@link Region}s from.
|
||||||
|
*/
|
||||||
|
private final RegionRepository regionRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link World} to register {@link StaticGameObject}s with.
|
||||||
|
*/
|
||||||
|
private final World world;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link WorldObjectsDecoder}.
|
||||||
|
*
|
||||||
|
* @param fs The {@link IndexedFileSystem} to load object files from.
|
||||||
|
* @param world The {@link World} to register objects with.
|
||||||
|
* @param regionRepository The {@link RegionRepository} to lookup {@link Region}s from.
|
||||||
|
*/
|
||||||
|
public WorldObjectsDecoder(IndexedFileSystem fs, World world, RegionRepository regionRepository) {
|
||||||
|
this.fs = fs;
|
||||||
|
this.world = world;
|
||||||
|
this.regionRepository = regionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the {@code MapObject}s from the cache and register them with the world.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Map<Integer, MapIndex> mapIndices = MapIndex.getIndices();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (MapIndex index : mapIndices.values()) {
|
||||||
|
MapObjectsDecoder decoder = MapObjectsDecoder.create(fs, index);
|
||||||
|
List<MapObject> objects = decoder.decode();
|
||||||
|
|
||||||
|
int mapX = index.getX(), mapY = index.getY();
|
||||||
|
|
||||||
|
for (MapObject object : objects) {
|
||||||
|
Position position = new Position(mapX + object.getLocalX(), mapY + object.getLocalY(),
|
||||||
|
object.getHeight());
|
||||||
|
|
||||||
|
StaticGameObject gameObject = new StaticGameObject(world, object.getId(), position,
|
||||||
|
object.getType(), object.getOrientation());
|
||||||
|
|
||||||
|
regionRepository.fromPosition(position).addEntity(gameObject, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,23 @@ public enum Direction {
|
|||||||
*/
|
*/
|
||||||
public static final Direction[] EMPTY_DIRECTION_ARRAY = new Direction[0];
|
public static final Direction[] EMPTY_DIRECTION_ARRAY = new Direction[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of directions without any diagonal directions.
|
||||||
|
*/
|
||||||
|
public final static Direction[] NESW = { NORTH, EAST, SOUTH, WEST };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of directions without any diagonal directions, and one step counter-clockwise, as used by
|
||||||
|
* the clients collision mapping.
|
||||||
|
*/
|
||||||
|
public final static Direction[] WNES = { WEST, NORTH, EAST, SOUTH };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of diagonal directions, and one step counter-clockwise, as used by the clients collision
|
||||||
|
* mapping.
|
||||||
|
*/
|
||||||
|
public final static Direction[] WNES_DIAGONAL = { NORTH_WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the Direction between the two {@link Position}s..
|
* Gets the Direction between the two {@link Position}s..
|
||||||
*
|
*
|
||||||
@@ -68,6 +85,17 @@ public enum Direction {
|
|||||||
int deltaX = next.getX() - current.getX();
|
int deltaX = next.getX() - current.getX();
|
||||||
int deltaY = next.getY() - current.getY();
|
int deltaY = next.getY() - current.getY();
|
||||||
|
|
||||||
|
return fromDeltas(deltaX, deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a direction from the differences between X and Y.
|
||||||
|
*
|
||||||
|
* @param deltaX The difference between two X coordinates.
|
||||||
|
* @param deltaY The difference between two Y coordinates.
|
||||||
|
* @return The direction.
|
||||||
|
*/
|
||||||
|
public static Direction fromDeltas(int deltaX, int deltaY) {
|
||||||
if (deltaY == 1) {
|
if (deltaY == 1) {
|
||||||
if (deltaX == 1) {
|
if (deltaX == 1) {
|
||||||
return NORTH_EAST;
|
return NORTH_EAST;
|
||||||
@@ -97,6 +125,27 @@ public enum Direction {
|
|||||||
throw new IllegalArgumentException("Difference between Positions must be [-1, 1].");
|
throw new IllegalArgumentException("Difference between Positions must be [-1, 1].");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the 2 directions which make up a diagonal direction (i.e., NORTH and EAST for NORTH_EAST).
|
||||||
|
*
|
||||||
|
* @param direction The direction to get the components for.
|
||||||
|
* @return The components for the given direction.
|
||||||
|
*/
|
||||||
|
public static Direction[] diagonalComponents(Direction direction) {
|
||||||
|
switch (direction) {
|
||||||
|
case NORTH_EAST:
|
||||||
|
return new Direction[] { NORTH, EAST };
|
||||||
|
case NORTH_WEST:
|
||||||
|
return new Direction[] { NORTH, WEST };
|
||||||
|
case SOUTH_EAST:
|
||||||
|
return new Direction[] { SOUTH, EAST };
|
||||||
|
case SOUTH_WEST:
|
||||||
|
return new Direction[] { SOUTH, WEST };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Must provide a diagonal direction.");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The direction as an integer.
|
* The direction as an integer.
|
||||||
*/
|
*/
|
||||||
@@ -111,6 +160,83 @@ public enum Direction {
|
|||||||
this.intValue = intValue;
|
this.intValue = intValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the opposite direction of the this direction.
|
||||||
|
*
|
||||||
|
* @return The opposite direction.
|
||||||
|
*/
|
||||||
|
public Direction opposite() {
|
||||||
|
switch (this) {
|
||||||
|
case NORTH:
|
||||||
|
return SOUTH;
|
||||||
|
case SOUTH:
|
||||||
|
return NORTH;
|
||||||
|
case EAST:
|
||||||
|
return WEST;
|
||||||
|
case WEST:
|
||||||
|
return EAST;
|
||||||
|
case NORTH_WEST:
|
||||||
|
return SOUTH_EAST;
|
||||||
|
case NORTH_EAST:
|
||||||
|
return SOUTH_WEST;
|
||||||
|
case SOUTH_EAST:
|
||||||
|
return NORTH_WEST;
|
||||||
|
case SOUTH_WEST:
|
||||||
|
return NORTH_EAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the X delta from a {@link Position} of (0, 0).
|
||||||
|
*
|
||||||
|
* @return The delta of X from (0, 0).
|
||||||
|
*/
|
||||||
|
public int deltaX() {
|
||||||
|
switch (this) {
|
||||||
|
case SOUTH_EAST:
|
||||||
|
case NORTH_EAST:
|
||||||
|
case EAST:
|
||||||
|
return 1;
|
||||||
|
case SOUTH_WEST:
|
||||||
|
case NORTH_WEST:
|
||||||
|
case WEST:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Y delta from a {@link Position} of (0, 0).
|
||||||
|
*
|
||||||
|
* @return The delta of Y from (0, 0).
|
||||||
|
*/
|
||||||
|
public int deltaY() {
|
||||||
|
switch (this) {
|
||||||
|
case NORTH_WEST:
|
||||||
|
case NORTH_EAST:
|
||||||
|
case NORTH:
|
||||||
|
return 1;
|
||||||
|
case SOUTH_WEST:
|
||||||
|
case SOUTH_EAST:
|
||||||
|
case SOUTH:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this direction is a diagonal direction.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this direction is a diagonal direction, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isDiagonal() {
|
||||||
|
return this == SOUTH_EAST || this == SOUTH_WEST || this == NORTH_EAST || this == NORTH_WEST;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the direction as an integer which the client can understand.
|
* Gets the direction as an integer which the client can understand.
|
||||||
*
|
*
|
||||||
@@ -120,4 +246,28 @@ public enum Direction {
|
|||||||
return intValue;
|
return intValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the direction as an integer as used orientation in the client maps (WNES as opposed to NESW).
|
||||||
|
*
|
||||||
|
* @return The direction as an integer.
|
||||||
|
*/
|
||||||
|
public int toOrientationInteger() {
|
||||||
|
switch(this) {
|
||||||
|
case WEST:
|
||||||
|
case NORTH_WEST:
|
||||||
|
return 0;
|
||||||
|
case NORTH:
|
||||||
|
case NORTH_EAST:
|
||||||
|
return 1;
|
||||||
|
case EAST:
|
||||||
|
case SOUTH_EAST:
|
||||||
|
return 2;
|
||||||
|
case SOUTH:
|
||||||
|
case SOUTH_WEST:
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Only a valid direction can have an orientation value");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -224,9 +224,19 @@ public final class Position {
|
|||||||
return deltaX <= distance && deltaY <= distance && getHeight() == other.getHeight();
|
return deltaX <= distance && deltaY <= distance && getHeight() == other.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String toString() {
|
* Creates a new position {@code num} steps from this position in the given direction.
|
||||||
return MoreObjects.toStringHelper(this).add("x", getX()).add("y", getY()).add("height", getHeight()).add("region", getRegionCoordinates()).toString();
|
*
|
||||||
|
* @param num The number of steps to make.
|
||||||
|
* @param direction The direction to make steps in.
|
||||||
|
* @return A new {@code Position} that is {@code num} steps in {@code direction} ahead of this one.
|
||||||
|
*/
|
||||||
|
public Position step(int num, Direction direction) {
|
||||||
|
return new Position(getX() + (num * direction.deltaX()), getY() + (num * direction.deltaY()), getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this).add("x", getX()).add("y", getY()).add("height", getHeight()).add("map", getRegionCoordinates()).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
package org.apollo.game.model;
|
package org.apollo.game.model;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
@@ -12,12 +8,17 @@ import org.apollo.Service;
|
|||||||
import org.apollo.cache.IndexedFileSystem;
|
import org.apollo.cache.IndexedFileSystem;
|
||||||
import org.apollo.cache.decoder.ItemDefinitionDecoder;
|
import org.apollo.cache.decoder.ItemDefinitionDecoder;
|
||||||
import org.apollo.cache.decoder.NpcDefinitionDecoder;
|
import org.apollo.cache.decoder.NpcDefinitionDecoder;
|
||||||
|
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
|
||||||
|
import org.apollo.cache.map.MapIndexDecoder;
|
||||||
import org.apollo.game.command.CommandDispatcher;
|
import org.apollo.game.command.CommandDispatcher;
|
||||||
import org.apollo.game.fs.decoder.GameObjectDecoder;
|
|
||||||
import org.apollo.game.fs.decoder.SynchronousDecoder;
|
import org.apollo.game.fs.decoder.SynchronousDecoder;
|
||||||
|
import org.apollo.game.fs.decoder.WorldMapDecoder;
|
||||||
|
import org.apollo.game.fs.decoder.WorldObjectsDecoder;
|
||||||
import org.apollo.game.io.EquipmentDefinitionParser;
|
import org.apollo.game.io.EquipmentDefinitionParser;
|
||||||
import org.apollo.game.model.area.Region;
|
import org.apollo.game.model.area.Region;
|
||||||
import org.apollo.game.model.area.RegionRepository;
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
|
import org.apollo.game.model.area.collision.GameObjectCollisionUpdateListener;
|
||||||
import org.apollo.game.model.entity.Entity;
|
import org.apollo.game.model.entity.Entity;
|
||||||
import org.apollo.game.model.entity.EntityType;
|
import org.apollo.game.model.entity.EntityType;
|
||||||
import org.apollo.game.model.entity.MobRepository;
|
import org.apollo.game.model.entity.MobRepository;
|
||||||
@@ -110,6 +111,11 @@ public final class World {
|
|||||||
*/
|
*/
|
||||||
private final RegionRepository regions = RegionRepository.immutable();
|
private final RegionRepository regions = RegionRepository.immutable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This world's {@link CollisionManager}.
|
||||||
|
*/
|
||||||
|
private final CollisionManager collisionManager = new CollisionManager(regions);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The scheduler.
|
* The scheduler.
|
||||||
*/
|
*/
|
||||||
@@ -130,6 +136,13 @@ public final class World {
|
|||||||
*/
|
*/
|
||||||
private int releaseNumber;
|
private int releaseNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the collision manager.
|
||||||
|
*
|
||||||
|
* @return The collision manager
|
||||||
|
*/
|
||||||
|
public CollisionManager getCollisionManager() { return collisionManager; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the command dispatcher.
|
* Gets the command dispatcher.
|
||||||
*
|
*
|
||||||
@@ -207,13 +220,28 @@ public final class World {
|
|||||||
public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception {
|
public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception {
|
||||||
releaseNumber = release;
|
releaseNumber = release;
|
||||||
|
|
||||||
SynchronousDecoder decoder = new SynchronousDecoder(new ItemDefinitionDecoder(fs),
|
SynchronousDecoder firstStageDecoder = new SynchronousDecoder(
|
||||||
new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
|
new NpcDefinitionDecoder(fs),
|
||||||
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
|
new ItemDefinitionDecoder(fs),
|
||||||
|
new ObjectDefinitionDecoder(fs),
|
||||||
|
new MapIndexDecoder(fs),
|
||||||
|
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat")
|
||||||
|
);
|
||||||
|
|
||||||
decoder.block();
|
firstStageDecoder.block();
|
||||||
|
|
||||||
npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues.
|
SynchronousDecoder secondStageDecoder = new SynchronousDecoder(
|
||||||
|
new WorldObjectsDecoder(fs, this, regions),
|
||||||
|
new WorldMapDecoder(fs, collisionManager)
|
||||||
|
);
|
||||||
|
|
||||||
|
secondStageDecoder.block();
|
||||||
|
|
||||||
|
// Build collision matrices for the first time
|
||||||
|
collisionManager.build(false);
|
||||||
|
regions.addRegionListener(new GameObjectCollisionUpdateListener(collisionManager));
|
||||||
|
|
||||||
|
npcMovement = new NpcMovementTask(collisionManager); // Must be exactly here because of ordering issues.
|
||||||
scheduler.schedule(npcMovement);
|
scheduler.schedule(npcMovement);
|
||||||
|
|
||||||
manager.start();
|
manager.start();
|
||||||
|
|||||||
@@ -163,6 +163,10 @@ public final class Region {
|
|||||||
addEntity(entity, true);
|
addEntity(entity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addListener(RegionListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if this Region contains the specified Entity.
|
* Checks if this Region contains the specified Entity.
|
||||||
*
|
*
|
||||||
@@ -296,6 +300,15 @@ public final class Region {
|
|||||||
return matrices[height];
|
return matrices[height];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all {@link CollisionMatrix}'s in this {@code Region}.
|
||||||
|
*
|
||||||
|
* @return The collision matrices of this region.
|
||||||
|
*/
|
||||||
|
public CollisionMatrix[] getMatrices() {
|
||||||
|
return matrices;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link Set} of {@link RegionUpdateMessage}s that have occurred in the last pulse. This method can
|
* Gets the {@link Set} of {@link RegionUpdateMessage}s that have occurred in the last pulse. This method can
|
||||||
* only be called <strong>once</strong> per pulse.
|
* only be called <strong>once</strong> per pulse.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.apollo.game.model.area;
|
package org.apollo.game.model.area;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -48,6 +49,11 @@ public final class RegionRepository {
|
|||||||
*/
|
*/
|
||||||
private final Map<RegionCoordinates, Region> regions = new HashMap<>();
|
private final Map<RegionCoordinates, Region> regions = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of default {@link RegionListener}s which will be added to {@link Region}s upon creation.
|
||||||
|
*/
|
||||||
|
private final List<RegionListener> defaultRegionListeners = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new RegionRepository.
|
* Creates a new RegionRepository.
|
||||||
*
|
*
|
||||||
@@ -71,9 +77,24 @@ public final class RegionRepository {
|
|||||||
throw new UnsupportedOperationException("Cannot add a Region with the same coordinates as an existing Region.");
|
throw new UnsupportedOperationException("Cannot add a Region with the same coordinates as an existing Region.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultRegionListeners.forEach(region::addListener);
|
||||||
regions.put(region.getCoordinates(), region);
|
regions.put(region.getCoordinates(), region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link RegionListener} to be registered as a default listener with all newly created {@link Region}s and
|
||||||
|
* associated with any existing instances.
|
||||||
|
*
|
||||||
|
* @param listener The listener to add.
|
||||||
|
*/
|
||||||
|
public void addRegionListener(RegionListener listener) {
|
||||||
|
for (Region region : regions.values()) {
|
||||||
|
region.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRegionListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether the supplied value (i.e. the {@link Region}) has a mapping.
|
* Indicates whether the supplied value (i.e. the {@link Region}) has a mapping.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -9,45 +9,85 @@ import org.apollo.game.model.entity.EntityType;
|
|||||||
*/
|
*/
|
||||||
public enum CollisionFlag {
|
public enum CollisionFlag {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The walk north west flag.
|
||||||
|
*/
|
||||||
|
MOB_NORTH_WEST(1),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The walk north flag.
|
* The walk north flag.
|
||||||
*/
|
*/
|
||||||
MOB_NORTH(0),
|
MOB_NORTH(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The walk north east flag.
|
||||||
|
*/
|
||||||
|
MOB_NORTH_EAST(3),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The walk east flag.
|
* The walk east flag.
|
||||||
*/
|
*/
|
||||||
MOB_EAST(1),
|
MOB_EAST(4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The walk south east flag.
|
||||||
|
*/
|
||||||
|
MOB_SOUTH_EAST(5),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The walk south flag.
|
* The walk south flag.
|
||||||
*/
|
*/
|
||||||
MOB_SOUTH(2),
|
MOB_SOUTH(6),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The walk south west flag.
|
||||||
|
*/
|
||||||
|
MOB_SOUTH_WEST(7),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The walk west flag.
|
* The walk west flag.
|
||||||
*/
|
*/
|
||||||
MOB_WEST(3),
|
MOB_WEST(8),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projectile north west flag.
|
||||||
|
*/
|
||||||
|
PROJECTILE_NORTH_WEST(9),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The projectile north flag.
|
* The projectile north flag.
|
||||||
*/
|
*/
|
||||||
PROJECTILE_NORTH(4),
|
PROJECTILE_NORTH(10),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projectile north east flag.
|
||||||
|
*/
|
||||||
|
PROJECTILE_NORTH_EAST(11),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The projectile east flag.
|
* The projectile east flag.
|
||||||
*/
|
*/
|
||||||
PROJECTILE_EAST(5),
|
PROJECTILE_EAST(12),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projectile south east flag.
|
||||||
|
*/
|
||||||
|
PROJECTILE_SOUTH_EAST(13),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The projectile south flag.
|
* The projectile south flag.
|
||||||
*/
|
*/
|
||||||
PROJECTILE_SOUTH(6),
|
PROJECTILE_SOUTH(14),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projectile south west flag.
|
||||||
|
*/
|
||||||
|
PROJECTILE_SOUTH_WEST(15),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The projectile west flag.
|
* The projectile west flag.
|
||||||
*/
|
*/
|
||||||
PROJECTILE_WEST(7);
|
PROJECTILE_WEST(16);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of CollisionFlags that indicate if the specified {@link EntityType} can be positioned on a tile.
|
* Returns an array of CollisionFlags that indicate if the specified {@link EntityType} can be positioned on a tile.
|
||||||
@@ -65,7 +105,16 @@ public enum CollisionFlag {
|
|||||||
* @return The array of CollisionFlags.
|
* @return The array of CollisionFlags.
|
||||||
*/
|
*/
|
||||||
public static CollisionFlag[] mobs() {
|
public static CollisionFlag[] mobs() {
|
||||||
return new CollisionFlag[] { MOB_NORTH, MOB_EAST, MOB_SOUTH, MOB_WEST };
|
return new CollisionFlag[] {
|
||||||
|
MOB_NORTH_WEST,
|
||||||
|
MOB_NORTH,
|
||||||
|
MOB_NORTH_EAST,
|
||||||
|
MOB_WEST,
|
||||||
|
MOB_EAST,
|
||||||
|
MOB_SOUTH_WEST,
|
||||||
|
MOB_SOUTH,
|
||||||
|
MOB_SOUTH_EAST
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +123,16 @@ public enum CollisionFlag {
|
|||||||
* @return The array of CollisionFlags.
|
* @return The array of CollisionFlags.
|
||||||
*/
|
*/
|
||||||
public static CollisionFlag[] projectiles() {
|
public static CollisionFlag[] projectiles() {
|
||||||
return new CollisionFlag[] { PROJECTILE_NORTH, PROJECTILE_EAST, PROJECTILE_SOUTH, PROJECTILE_WEST };
|
return new CollisionFlag[] {
|
||||||
|
PROJECTILE_NORTH_WEST,
|
||||||
|
PROJECTILE_NORTH,
|
||||||
|
PROJECTILE_NORTH_EAST,
|
||||||
|
PROJECTILE_WEST,
|
||||||
|
PROJECTILE_EAST,
|
||||||
|
PROJECTILE_SOUTH_WEST,
|
||||||
|
PROJECTILE_SOUTH,
|
||||||
|
PROJECTILE_SOUTH_EAST
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,12 +150,12 @@ public enum CollisionFlag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets this CollisionFlag, as a {@code byte}.
|
* Gets this CollisionFlag, as a {@code short}.
|
||||||
*
|
*
|
||||||
* @return The value, as a {@code byte}.
|
* @return The value, as a {@code short}.
|
||||||
*/
|
*/
|
||||||
public byte asByte() {
|
public short asShort() {
|
||||||
return (byte) (1 << bit);
|
return (short) (1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package org.apollo.game.model.area.collision;
|
||||||
|
|
||||||
|
import org.apollo.game.model.Direction;
|
||||||
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.area.Region;
|
||||||
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionUpdate.DirectionFlag;
|
||||||
|
import org.apollo.game.model.entity.EntityType;
|
||||||
|
import org.apollo.game.model.entity.obj.GameObject;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages applying {@link CollisionUpdate}s to the respective {@link CollisionMatrix} instances, and keeping
|
||||||
|
* a record of collision state (i.e., which tiles are bridged).
|
||||||
|
*/
|
||||||
|
public final class CollisionManager {
|
||||||
|
/**
|
||||||
|
* A comparator which sorts {@link Position}s by their X coordinate, then Y, then height.
|
||||||
|
*/
|
||||||
|
private static final Comparator<Position> POSITION_COMPARATOR =
|
||||||
|
Comparator.comparingInt(Position::getX).thenComparingInt(Position::getY).thenComparingInt(Position::getHeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code SortedSet} of positions where the tile is part of a bridged structure.
|
||||||
|
*/
|
||||||
|
private final SortedSet<Position> bridgeTiles = new TreeSet<>(POSITION_COMPARATOR);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code SortedSet} of positions where the tile is completely blocked.
|
||||||
|
*/
|
||||||
|
private final SortedSet<Position> blockedTiles = new TreeSet<>(POSITION_COMPARATOR);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RegionRepository} containing {@link Region}s, used to lookup {@link CollisionMatrix}'s.
|
||||||
|
*/
|
||||||
|
private final RegionRepository regionRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code CollisionManager}.
|
||||||
|
*
|
||||||
|
* @param regionRepository The {@link RegionRepository} to lookup {@link Region} and {@link CollisionMatrix} instances
|
||||||
|
* from.
|
||||||
|
*/
|
||||||
|
public CollisionManager(RegionRepository regionRepository) {
|
||||||
|
this.regionRepository = regionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apples the first initial {@link CollisionUpdate} to the {@link CollisionMatrix}es for all objects and tiles loaded from
|
||||||
|
* the cache.
|
||||||
|
*
|
||||||
|
* @param rebuilding A flag indicating whether {@link CollisionMatrix}es are being rebuilt, or built for the first time.
|
||||||
|
*/
|
||||||
|
public void build(boolean rebuilding) {
|
||||||
|
if (rebuilding) {
|
||||||
|
for (Region region : regionRepository.getRegions()) {
|
||||||
|
for (CollisionMatrix matrix : region.getMatrices()) {
|
||||||
|
matrix.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionUpdate.Builder tileUpdateBuilder = new CollisionUpdate.Builder();
|
||||||
|
tileUpdateBuilder.type(CollisionUpdateType.ADDING);
|
||||||
|
|
||||||
|
for (Position tile : blockedTiles) {
|
||||||
|
int x = tile.getX(), y = tile.getY();
|
||||||
|
int height = tile.getHeight();
|
||||||
|
|
||||||
|
if (bridgeTiles.contains(new Position(x, y, 1))) {
|
||||||
|
height--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height >= 0) {
|
||||||
|
tileUpdateBuilder.tile(new Position(x, y, height), false, Direction.NESW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionUpdate tileUpdate = tileUpdateBuilder.build();
|
||||||
|
apply(tileUpdate);
|
||||||
|
|
||||||
|
for (Region region : regionRepository.getRegions()) {
|
||||||
|
CollisionUpdate.Builder regionObjectsUpdateBuilder = new CollisionUpdate.Builder();
|
||||||
|
regionObjectsUpdateBuilder.type(CollisionUpdateType.ADDING);
|
||||||
|
|
||||||
|
region.getEntities(EntityType.STATIC_OBJECT, EntityType.DYNAMIC_OBJECT)
|
||||||
|
.forEach(entity -> regionObjectsUpdateBuilder.object((GameObject) entity));
|
||||||
|
|
||||||
|
CollisionUpdate regionObjectsUpdate = regionObjectsUpdateBuilder.build();
|
||||||
|
apply(regionObjectsUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a {@link CollisionUpdate} to the game world.
|
||||||
|
*
|
||||||
|
* @param update The update to apply.
|
||||||
|
*/
|
||||||
|
public void apply(CollisionUpdate update) {
|
||||||
|
Region prev = null;
|
||||||
|
|
||||||
|
CollisionUpdateType type = update.getType();
|
||||||
|
Map<Position, Collection<DirectionFlag>> flags = update.getFlags().asMap();
|
||||||
|
|
||||||
|
for (Map.Entry<Position, Collection<DirectionFlag>> flag : flags.entrySet()) {
|
||||||
|
Position position = flag.getKey();
|
||||||
|
Collection<DirectionFlag> directionFlags = flag.getValue();
|
||||||
|
|
||||||
|
int height = position.getHeight();
|
||||||
|
if (bridgeTiles.contains(new Position(position.getX(), position.getY(), 1))) {
|
||||||
|
if (--height < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == null || !prev.contains(position)) {
|
||||||
|
prev = regionRepository.fromPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
int localX = position.getX() % Region.SIZE, localY = position.getY() % Region.SIZE;
|
||||||
|
|
||||||
|
CollisionMatrix matrix = prev.getMatrix(height);
|
||||||
|
CollisionFlag[] mobs = CollisionFlag.mobs();
|
||||||
|
CollisionFlag[] projectiles = CollisionFlag.projectiles();
|
||||||
|
|
||||||
|
for (DirectionFlag directionFlag : directionFlags) {
|
||||||
|
Direction direction = directionFlag.getDirection();
|
||||||
|
if (direction == Direction.NONE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int orientation = direction.toInteger();
|
||||||
|
if (directionFlag.isImpenetrable()) {
|
||||||
|
flag(type, matrix, localX, localY, projectiles[orientation]);
|
||||||
|
}
|
||||||
|
|
||||||
|
flag(type, matrix, localX, localY, mobs[orientation]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a {@link CollisionUpdate} flag to a {@link CollisionMatrix}.
|
||||||
|
*
|
||||||
|
* @param type The type of update to apply.
|
||||||
|
* @param matrix The matrix the update is being applied to.
|
||||||
|
* @param localX The local X position of the tile the flag represents.
|
||||||
|
* @param localY The local Y position of the tile the flag represents.
|
||||||
|
* @param flag The flag to update.
|
||||||
|
*/
|
||||||
|
private void flag(CollisionUpdateType type, CollisionMatrix matrix, int localX, int localY, CollisionFlag flag) {
|
||||||
|
if (type == CollisionUpdateType.ADDING) {
|
||||||
|
matrix.flag(localX, localY, flag);
|
||||||
|
} else {
|
||||||
|
matrix.clear(localX, localY, flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a tile as completely untraversable from all directions.
|
||||||
|
*
|
||||||
|
* @param position The {@link Position} of the tile.
|
||||||
|
*/
|
||||||
|
public void markBlocked(Position position) {
|
||||||
|
blockedTiles.add(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a tile as part of a bridge.
|
||||||
|
*
|
||||||
|
* @param position The {@link Position} of the tile.
|
||||||
|
*/
|
||||||
|
public void markBridged(Position position) {
|
||||||
|
bridgeTiles.add(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given {@link EntityType} can traverse to the next tile from {@code position} in the given
|
||||||
|
* {@code direction}.
|
||||||
|
*
|
||||||
|
* @param position The current position of the entity.
|
||||||
|
* @param type The type of the entity.
|
||||||
|
* @param direction The direction the entity is travelling.
|
||||||
|
* @return {@code true} if next tile is traversable, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean traversable(Position position, EntityType type, Direction direction) {
|
||||||
|
Position next = position.step(1, direction);
|
||||||
|
Region region = regionRepository.fromPosition(next);
|
||||||
|
|
||||||
|
if (!region.traversable(next, type, direction)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction.isDiagonal()) {
|
||||||
|
for (Direction component : Direction.diagonalComponents(direction)) {
|
||||||
|
next = position.step(1, component);
|
||||||
|
|
||||||
|
if (!region.contains(next)) {
|
||||||
|
region = regionRepository.fromPosition(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!region.traversable(next, type, component)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,12 +18,17 @@ public final class CollisionMatrix {
|
|||||||
/**
|
/**
|
||||||
* Indicates that all types of traversal are allowed.
|
* Indicates that all types of traversal are allowed.
|
||||||
*/
|
*/
|
||||||
private static final byte ALL_ALLOWED = 0b0000_0000;
|
private static final short ALL_ALLOWED = 0b00000000_00000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that no types of traversal are allowed.
|
* Indicates that no types of traversal are allowed.
|
||||||
*/
|
*/
|
||||||
private static final byte ALL_BLOCKED = (byte) 0b1111_1111;
|
private static final short ALL_BLOCKED = (short) 0b11111111_11111111;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that projectiles may traverse this tile, but mobs may not.
|
||||||
|
*/
|
||||||
|
private static final short ALL_MOBS_BLOCKED = (short) 0b11111111_00000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an array of CollisionMatrix objects, all of the specified width and length.
|
* Creates an array of CollisionMatrix objects, all of the specified width and length.
|
||||||
@@ -45,9 +50,9 @@ public final class CollisionMatrix {
|
|||||||
private final int length;
|
private final int length;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The collision matrix, as a {@code byte} array.
|
* The collision matrix, as a {@code short} array.
|
||||||
*/
|
*/
|
||||||
private final byte[] matrix;
|
private final short[] matrix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the matrix.
|
* The width of the matrix.
|
||||||
@@ -63,7 +68,7 @@ public final class CollisionMatrix {
|
|||||||
public CollisionMatrix(int width, int length) {
|
public CollisionMatrix(int width, int length) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
matrix = new byte[width * length];
|
matrix = new short[width * length];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,6 +109,18 @@ public final class CollisionMatrix {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely blocks the tile at the specified coordinate pair, while optionally allowing projectiles
|
||||||
|
* to pass through.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param y The y coordinate.
|
||||||
|
* @param impenetrable If projectiles should be permitted to traverse this tile.
|
||||||
|
*/
|
||||||
|
public void block(int x, int y, boolean impenetrable) {
|
||||||
|
set(x, y, impenetrable ? ALL_BLOCKED : ALL_MOBS_BLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completely blocks the tile at the specified coordinate pair.
|
* Completely blocks the tile at the specified coordinate pair.
|
||||||
*
|
*
|
||||||
@@ -111,7 +128,7 @@ public final class CollisionMatrix {
|
|||||||
* @param y The y coordinate.
|
* @param y The y coordinate.
|
||||||
*/
|
*/
|
||||||
public void block(int x, int y) {
|
public void block(int x, int y) {
|
||||||
set(x, y, ALL_BLOCKED);
|
block(x, y, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +140,18 @@ public final class CollisionMatrix {
|
|||||||
* @param flag The CollisionFlag.
|
* @param flag The CollisionFlag.
|
||||||
*/
|
*/
|
||||||
public void clear(int x, int y, CollisionFlag flag) {
|
public void clear(int x, int y, CollisionFlag flag) {
|
||||||
set(x, y, (byte) ~flag.asByte());
|
set(x, y, (short) (matrix[indexOf(x, y)] & ~flag.asShort()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an additional {@link CollisionFlag} for the specified coordinate pair.
|
||||||
|
*
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param y The y coordinate.
|
||||||
|
* @param flag The CollisionFlag.
|
||||||
|
*/
|
||||||
|
public void flag(int x, int y, CollisionFlag flag) {
|
||||||
|
matrix[indexOf(x, y)] |= flag.asShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +163,7 @@ public final class CollisionMatrix {
|
|||||||
* @return {@code true} iff the CollisionFlag is set.
|
* @return {@code true} iff the CollisionFlag is set.
|
||||||
*/
|
*/
|
||||||
public boolean flagged(int x, int y, CollisionFlag flag) {
|
public boolean flagged(int x, int y, CollisionFlag flag) {
|
||||||
return (get(x, y) & flag.asByte()) != 0;
|
return (get(x, y) & flag.asShort()) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,7 +174,7 @@ public final class CollisionMatrix {
|
|||||||
* @return The value.
|
* @return The value.
|
||||||
*/
|
*/
|
||||||
public int get(int x, int y) {
|
public int get(int x, int y) {
|
||||||
return matrix[indexOf(x, y)] & 0xFF;
|
return matrix[indexOf(x, y)] & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,6 +187,17 @@ public final class CollisionMatrix {
|
|||||||
set(x, y, ALL_ALLOWED);
|
set(x, y, ALL_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all cells in this matrix.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
reset(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets (i.e. sets to {@code true}) the value of the specified {@link CollisionFlag} for the specified coordinate
|
* Sets (i.e. sets to {@code true}) the value of the specified {@link CollisionFlag} for the specified coordinate
|
||||||
* pair.
|
* pair.
|
||||||
@@ -168,13 +207,13 @@ public final class CollisionMatrix {
|
|||||||
* @param flag The CollisionFlag.
|
* @param flag The CollisionFlag.
|
||||||
*/
|
*/
|
||||||
public void set(int x, int y, CollisionFlag flag) {
|
public void set(int x, int y, CollisionFlag flag) {
|
||||||
set(x, y, flag.asByte());
|
set(x, y, flag.asShort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return MoreObjects.toStringHelper(this).add("width", width).add("length", length)
|
return MoreObjects.toStringHelper(this).add("width", width).add("length", length)
|
||||||
.add("matrix", Arrays.toString(matrix)).toString();
|
.add("matrix", Arrays.toString(matrix)).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,23 +228,23 @@ public final class CollisionMatrix {
|
|||||||
*/
|
*/
|
||||||
public boolean untraversable(int x, int y, EntityType entity, Direction direction) {
|
public boolean untraversable(int x, int y, EntityType entity, Direction direction) {
|
||||||
CollisionFlag[] flags = CollisionFlag.forType(entity);
|
CollisionFlag[] flags = CollisionFlag.forType(entity);
|
||||||
int north = 0, east = 1, south = 2, west = 3;
|
int northwest = 0, north = 1, northeast = 2, west = 3, east = 4, southwest = 5, south = 6, southeast = 7;
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case NORTH_WEST:
|
case NORTH_WEST:
|
||||||
return flagged(x, y, flags[south]) || flagged(x, y, flags[east]);
|
return flagged(x, y, flags[southeast]) || flagged(x, y, flags[south]) || flagged(x, y, flags[east]);
|
||||||
case NORTH:
|
case NORTH:
|
||||||
return flagged(x, y, flags[south]);
|
return flagged(x, y, flags[south]);
|
||||||
case NORTH_EAST:
|
case NORTH_EAST:
|
||||||
return flagged(x, y, flags[south]) || flagged(x, y, flags[west]);
|
return flagged(x, y, flags[southwest]) || flagged(x, y, flags[south]) || flagged(x, y, flags[west]);
|
||||||
case EAST:
|
case EAST:
|
||||||
return flagged(x, y, flags[west]);
|
return flagged(x, y, flags[west]);
|
||||||
case SOUTH_EAST:
|
case SOUTH_EAST:
|
||||||
return flagged(x, y, flags[north]) || flagged(x, y, flags[west]);
|
return flagged(x, y, flags[northwest]) || flagged(x, y, flags[north]) || flagged(x, y, flags[west]);
|
||||||
case SOUTH:
|
case SOUTH:
|
||||||
return flagged(x, y, flags[north]);
|
return flagged(x, y, flags[north]);
|
||||||
case SOUTH_WEST:
|
case SOUTH_WEST:
|
||||||
return flagged(x, y, flags[north]) || flagged(x, y, flags[east]);
|
return flagged(x, y, flags[northeast]) || flagged(x, y, flags[north]) || flagged(x, y, flags[east]);
|
||||||
case WEST:
|
case WEST:
|
||||||
return flagged(x, y, flags[east]);
|
return flagged(x, y, flags[east]);
|
||||||
default:
|
default:
|
||||||
@@ -234,7 +273,7 @@ public final class CollisionMatrix {
|
|||||||
* @param y The y coordinate.
|
* @param y The y coordinate.
|
||||||
* @param value The value.
|
* @param value The value.
|
||||||
*/
|
*/
|
||||||
private void set(int x, int y, byte value) {
|
private void set(int x, int y, short value) {
|
||||||
matrix[indexOf(x, y)] = value;
|
matrix[indexOf(x, y)] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
package org.apollo.game.model.area.collision;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.*;
|
||||||
|
import org.apollo.cache.def.ObjectDefinition;
|
||||||
|
import org.apollo.game.model.Direction;
|
||||||
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.entity.obj.GameObject;
|
||||||
|
import org.apollo.game.model.entity.obj.ObjectType;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global update to the collision matrices.
|
||||||
|
*/
|
||||||
|
public final class CollisionUpdate {
|
||||||
|
/**
|
||||||
|
* The type of this update.
|
||||||
|
*/
|
||||||
|
private final CollisionUpdateType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping of {@link Position}s to a set of their {@link DirectionFlag}s.
|
||||||
|
*/
|
||||||
|
private final Multimap<Position, DirectionFlag> flags;
|
||||||
|
|
||||||
|
public CollisionUpdate(CollisionUpdateType type, Multimap<Position, DirectionFlag> flags) {
|
||||||
|
this.type = type;
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of this update (ADDING, or REMOVING).
|
||||||
|
*
|
||||||
|
* @return The type of this update.
|
||||||
|
*/
|
||||||
|
public CollisionUpdateType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mapping of tiles -> flags contained in this update.
|
||||||
|
*
|
||||||
|
* @return The flags contained in this update.
|
||||||
|
*/
|
||||||
|
public Multimap<Position, DirectionFlag> getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directional flag in a {@code CollisionUpdate}. Consists of a {@code direction} and a flag indicating whether
|
||||||
|
* that tile is impenetrable as well as untraversable.
|
||||||
|
*/
|
||||||
|
public static final class DirectionFlag {
|
||||||
|
private final boolean impenetrable;
|
||||||
|
private final Direction direction;
|
||||||
|
|
||||||
|
public DirectionFlag(boolean impenetrable, Direction direction) {
|
||||||
|
this.impenetrable = impenetrable;
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
DirectionFlag that = (DirectionFlag) o;
|
||||||
|
|
||||||
|
if (impenetrable != that.impenetrable) return false;
|
||||||
|
return direction == that.direction;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (impenetrable ? 1 : 0);
|
||||||
|
result = 31 * result + direction.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this flag represents an impenetrable direction.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this flag represents an impenetrable direction, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isImpenetrable() {
|
||||||
|
return impenetrable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the direction this flag represents.
|
||||||
|
*
|
||||||
|
* @return The direction this flag represents.
|
||||||
|
*/
|
||||||
|
public Direction getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final Multimap<Position, DirectionFlag> flags;
|
||||||
|
private CollisionUpdateType type;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
this.flags = MultimapBuilder.hashKeys().hashSetValues().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the type of the {@link CollisionUpdate}. Can only be called once.
|
||||||
|
*
|
||||||
|
* @param type The type of collision update to use.
|
||||||
|
*/
|
||||||
|
public void type(CollisionUpdateType type) {
|
||||||
|
Preconditions.checkState(type != null, "update type has already been set");
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tile at the given {@code position} as untraversable in the given directions.
|
||||||
|
*
|
||||||
|
* @param position The world position of the tile.
|
||||||
|
* @param directions The directions that are untraversable from this tile.
|
||||||
|
*/
|
||||||
|
public void tile(Position position, boolean impenetrable, Direction... directions) {
|
||||||
|
if (directions.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream.of(directions).forEach(direction -> flags.put(position, new DirectionFlag(impenetrable, direction)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag a wall in the CollisionUpdate. When constructing a CollisionMatrix, the flags for a wall are represented
|
||||||
|
* as the tile the wall exists on and the tile one step in the facing direction. So for a wall facing south,
|
||||||
|
* the tile one step to the south be flagged as untraversable from the north.
|
||||||
|
*
|
||||||
|
* @param position The position of the wall.
|
||||||
|
* @param impenetrable If projectiles can pass through this wall.
|
||||||
|
* @param orientation The facing direction of this wall.
|
||||||
|
*/
|
||||||
|
public void wall(Position position, boolean impenetrable, Direction orientation) {
|
||||||
|
tile(position, impenetrable, orientation);
|
||||||
|
tile(position.step(1, orientation), impenetrable, orientation.opposite());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag a larger corner wall in the CollisionUpdate. A corner is represented by the 2 directions that it faces,
|
||||||
|
* and the 2 tiles in both directions. For example, when a tile is facing north its facing directions
|
||||||
|
* are north and east, so the position of the object will be untraversable from those directions. Additionally,
|
||||||
|
* the tile 1 step to the north, and 1 step to the east will be untraversable from the opposite directions of
|
||||||
|
* north and east respectively.
|
||||||
|
* <p>
|
||||||
|
* todo: "large corner wall", is that really what this is?
|
||||||
|
*
|
||||||
|
* @param position The position of the corner wall.
|
||||||
|
* @param impenetrable If projectiles can pass through this corner wall.
|
||||||
|
* @param orientation The direction of this corner wall
|
||||||
|
*/
|
||||||
|
public void largeCornerWall(Position position, boolean impenetrable, Direction orientation) {
|
||||||
|
Direction[] directions = Direction.diagonalComponents(orientation);
|
||||||
|
|
||||||
|
tile(position, impenetrable, directions);
|
||||||
|
|
||||||
|
for (Direction direction : directions) {
|
||||||
|
tile(position.step(1, direction), impenetrable, direction.opposite());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag a collision update for the given {@link GameObject}.
|
||||||
|
*
|
||||||
|
* @param object The object to update collision flags for.
|
||||||
|
*/
|
||||||
|
public void object(GameObject object) {
|
||||||
|
ObjectDefinition definition = object.getDefinition();
|
||||||
|
Position position = object.getPosition();
|
||||||
|
int type = object.getType();
|
||||||
|
|
||||||
|
if (!unwalkable(definition, type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = position.getX(), y = position.getY(), height = position.getHeight();
|
||||||
|
int width = definition.getWidth(), length = definition.getLength();
|
||||||
|
boolean impenetrable = definition.isImpenetrable();
|
||||||
|
int orientation = object.getOrientation();
|
||||||
|
|
||||||
|
// north / south for walls, north east / south west for corners
|
||||||
|
if (orientation == 1 || orientation == 3) {
|
||||||
|
width = definition.getLength();
|
||||||
|
length = definition.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == ObjectType.FLOOR_DECORATION.getValue()) {
|
||||||
|
if (definition.isInteractive() && definition.isSolid()) {
|
||||||
|
tile(new Position(x, y, height), impenetrable, Direction.NESW);
|
||||||
|
}
|
||||||
|
} else if (type >= ObjectType.DIAGONAL_WALL.getValue() && type < ObjectType.FLOOR_DECORATION.getValue()) {
|
||||||
|
for (int dx = 0; dx < width; dx++) {
|
||||||
|
for (int dy = 0; dy < length; dy++) {
|
||||||
|
tile(new Position(x + dx, y + dy, height), impenetrable, Direction.NESW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == ObjectType.LENGTHWISE_WALL.getValue()) {
|
||||||
|
wall(position, impenetrable, Direction.WNES[orientation]);
|
||||||
|
} else if (type == ObjectType.TRIANGULAR_CORNER.getValue()
|
||||||
|
|| type == ObjectType.RECTANGULAR_CORNER.getValue()) {
|
||||||
|
wall(position, impenetrable, Direction.WNES_DIAGONAL[orientation]);
|
||||||
|
} else if (type == ObjectType.WALL_CORNER.getValue()) {
|
||||||
|
largeCornerWall(position, impenetrable, Direction.WNES_DIAGONAL[orientation]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link CollisionUpdate}.
|
||||||
|
*
|
||||||
|
* @return A new CollisionUpdate with the flags in this builder.
|
||||||
|
*/
|
||||||
|
public CollisionUpdate build() {
|
||||||
|
Preconditions.checkNotNull(type, "update type must not be null");
|
||||||
|
return new CollisionUpdate(type, Multimaps.unmodifiableMultimap(flags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not an object with the specified {@link ObjectDefinition} and {@code type} should result in
|
||||||
|
* the tile(s) it is located on being blocked.
|
||||||
|
*
|
||||||
|
* @param definition The {@link ObjectDefinition} of the object.
|
||||||
|
* @param type The type of the object.
|
||||||
|
* @return {@code true} iff the tile(s) the object is on should be blocked.
|
||||||
|
*/
|
||||||
|
private static boolean unwalkable(ObjectDefinition definition, int type) {
|
||||||
|
boolean isSolidFloorDecoration = type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive();
|
||||||
|
|
||||||
|
boolean isWall = type >= ObjectType.LENGTHWISE_WALL.getValue()
|
||||||
|
&& type <= ObjectType.RECTANGULAR_CORNER.getValue() || type == ObjectType.DIAGONAL_WALL.getValue();
|
||||||
|
|
||||||
|
boolean isRoof = type > ObjectType.DIAGONAL_INTERACTABLE.getValue()
|
||||||
|
&& type < ObjectType.FLOOR_DECORATION.getValue();
|
||||||
|
|
||||||
|
boolean isSolidInteractable = (type == ObjectType.DIAGONAL_INTERACTABLE.getValue()
|
||||||
|
|| type == ObjectType.INTERACTABLE.getValue()) && definition.isSolid();
|
||||||
|
|
||||||
|
return isWall || isRoof || isSolidInteractable || isSolidFloorDecoration;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.apollo.game.model.area.collision;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum which represents the type of a {@link CollisionUpdate}.
|
||||||
|
*/
|
||||||
|
public enum CollisionUpdateType {
|
||||||
|
/**
|
||||||
|
* Indicates that a {@link CollisionUpdate} will be adding new flags to collision matrices.
|
||||||
|
*/
|
||||||
|
ADDING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a {@link CollisionUpdate} will be clearing existing flags from collision matrices.
|
||||||
|
*/
|
||||||
|
REMOVING
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package org.apollo.game.model.area.collision;
|
||||||
|
|
||||||
|
import org.apollo.game.model.area.EntityUpdateType;
|
||||||
|
import org.apollo.game.model.area.Region;
|
||||||
|
import org.apollo.game.model.area.RegionListener;
|
||||||
|
import org.apollo.game.model.entity.Entity;
|
||||||
|
import org.apollo.game.model.entity.EntityType;
|
||||||
|
import org.apollo.game.model.entity.obj.GameObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link RegionListener} which listens on object addition / removal events and applies
|
||||||
|
* the respective {@link CollisionUpdate}.
|
||||||
|
*/
|
||||||
|
public final class GameObjectCollisionUpdateListener implements RegionListener {
|
||||||
|
/**
|
||||||
|
* The {@link CollisionManager} to apply updates to.
|
||||||
|
*/
|
||||||
|
private CollisionManager collisionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link GameObjectCollisionUpdateListener}.
|
||||||
|
*
|
||||||
|
* @param collisionManager The {@link CollisionManager} that collision updates will be applied to.
|
||||||
|
*/
|
||||||
|
public GameObjectCollisionUpdateListener(CollisionManager collisionManager) {
|
||||||
|
this.collisionManager = collisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Region region, Entity entity, EntityUpdateType type) {
|
||||||
|
EntityType entityType = entity.getEntityType();
|
||||||
|
|
||||||
|
if (entityType != EntityType.STATIC_OBJECT && entityType != EntityType.DYNAMIC_OBJECT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionUpdate.Builder objectUpdateBuilder = new CollisionUpdate.Builder();
|
||||||
|
if (type == EntityUpdateType.ADD) {
|
||||||
|
objectUpdateBuilder.type(CollisionUpdateType.ADDING);
|
||||||
|
} else {
|
||||||
|
objectUpdateBuilder.type(CollisionUpdateType.REMOVING);
|
||||||
|
}
|
||||||
|
|
||||||
|
objectUpdateBuilder.object((GameObject) entity);
|
||||||
|
|
||||||
|
CollisionUpdate objectUpdate = objectUpdateBuilder.build();
|
||||||
|
collisionManager.apply(objectUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
package org.apollo.game.model.entity;
|
package org.apollo.game.model.entity;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
import org.apollo.game.model.Direction;
|
import org.apollo.game.model.Direction;
|
||||||
import org.apollo.game.model.Position;
|
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.Region;
|
||||||
import org.apollo.game.model.area.RegionRepository;
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionFlag;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionMatrix;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A queue of {@link Direction}s which a {@link Mob} will follow.
|
* A queue of {@link Direction}s which a {@link Mob} will follow.
|
||||||
@@ -126,19 +133,33 @@ public final class WalkingQueue {
|
|||||||
|
|
||||||
Direction firstDirection = Direction.NONE;
|
Direction firstDirection = Direction.NONE;
|
||||||
Direction secondDirection = Direction.NONE;
|
Direction secondDirection = Direction.NONE;
|
||||||
|
World world = mob.getWorld();
|
||||||
|
CollisionManager collisionManager = world.getCollisionManager();
|
||||||
|
|
||||||
Position next = points.poll();
|
Position next = points.poll();
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
previousPoints.add(next);
|
|
||||||
firstDirection = Direction.between(position, next);
|
firstDirection = Direction.between(position, next);
|
||||||
position = new Position(next.getX(), next.getY(), height);
|
|
||||||
|
|
||||||
if (running) {
|
if (!collisionManager.traversable(position, EntityType.NPC, firstDirection)) {
|
||||||
next = points.poll();
|
clear();
|
||||||
if (next != null) {
|
firstDirection = Direction.NONE;
|
||||||
previousPoints.add(next);
|
} else {
|
||||||
secondDirection = Direction.between(position, next);
|
previousPoints.add(next);
|
||||||
position = new Position(next.getX(), next.getY(), height);
|
position = new Position(next.getX(), next.getY(), height);
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
next = points.poll();
|
||||||
|
if (next != null) {
|
||||||
|
secondDirection = Direction.between(position, next);
|
||||||
|
|
||||||
|
if (!collisionManager.traversable(position, EntityType.NPC, secondDirection)) {
|
||||||
|
clear();
|
||||||
|
secondDirection = Direction.NONE;
|
||||||
|
} else {
|
||||||
|
previousPoints.add(next);
|
||||||
|
position = new Position(next.getX(), next.getY(), height);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.apollo.game.model.Direction;
|
import org.apollo.game.model.Direction;
|
||||||
import org.apollo.game.model.Position;
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.World;
|
||||||
import org.apollo.game.model.area.RegionRepository;
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link PathfindingAlgorithm} that utilises the A* algorithm to find a solution.
|
* A {@link PathfindingAlgorithm} that utilises the A* algorithm to find a solution.
|
||||||
@@ -35,11 +37,12 @@ public final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
/**
|
/**
|
||||||
* Creates the A* pathfinding algorithm with the specified {@link Heuristic}.
|
* Creates the A* pathfinding algorithm with the specified {@link Heuristic}.
|
||||||
*
|
*
|
||||||
* @param repository The {@link RegionRepository}.
|
* @param collisionManager The {@link CollisionManager} used to check if there is a collision
|
||||||
|
* between two {@link Position}s in a path.
|
||||||
* @param heuristic The Heuristic.
|
* @param heuristic The Heuristic.
|
||||||
*/
|
*/
|
||||||
public AStarPathfindingAlgorithm(RegionRepository repository, Heuristic heuristic) {
|
public AStarPathfindingAlgorithm(CollisionManager collisionManager, Heuristic heuristic) {
|
||||||
super(repository);
|
super(collisionManager);
|
||||||
this.heuristic = heuristic;
|
this.heuristic = heuristic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import org.apollo.game.model.Direction;
|
import org.apollo.game.model.Direction;
|
||||||
import org.apollo.game.model.Position;
|
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.Region;
|
||||||
import org.apollo.game.model.area.RegionRepository;
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
import org.apollo.game.model.entity.EntityType;
|
import org.apollo.game.model.entity.EntityType;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
@@ -18,18 +20,16 @@ import com.google.common.base.Preconditions;
|
|||||||
*/
|
*/
|
||||||
abstract class PathfindingAlgorithm {
|
abstract class PathfindingAlgorithm {
|
||||||
|
|
||||||
/**
|
private final CollisionManager collisionManager;
|
||||||
* The RegionRepository.
|
|
||||||
*/
|
|
||||||
private final RegionRepository repository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the PathfindingAlgorithm.
|
* Creates the PathfindingAlgorithm.
|
||||||
*
|
*
|
||||||
* @param repository The {@link RegionRepository}.
|
* @param collisionManager The {@link CollisionManager} used to check if there is a collision
|
||||||
|
* between two {@link Position}s in a path.
|
||||||
*/
|
*/
|
||||||
public PathfindingAlgorithm(RegionRepository repository) {
|
public PathfindingAlgorithm(CollisionManager collisionManager) {
|
||||||
this.repository = repository;
|
this.collisionManager = collisionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,9 +84,7 @@ abstract class PathfindingAlgorithm {
|
|||||||
x--;
|
x--;
|
||||||
}
|
}
|
||||||
|
|
||||||
Position next = new Position(x, y, height);
|
if (collisionManager.traversable(current, EntityType.NPC, direction)) {
|
||||||
Region region = repository.get(next.getRegionCoordinates());
|
|
||||||
if (region.traversable(next, EntityType.NPC, direction) && (positions.length == 0 || inside(next, positions))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import org.apollo.game.model.Direction;
|
import org.apollo.game.model.Direction;
|
||||||
import org.apollo.game.model.Position;
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.World;
|
||||||
import org.apollo.game.model.area.RegionRepository;
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A very simple pathfinding algorithm that simply walks in the direction of the target until it either reaches it or is
|
* A very simple pathfinding algorithm that simply walks in the direction of the target until it either reaches it or is
|
||||||
@@ -19,10 +21,11 @@ public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
/**
|
/**
|
||||||
* Creates the SimplePathfindingAlgorithm.
|
* Creates the SimplePathfindingAlgorithm.
|
||||||
*
|
*
|
||||||
* @param repository The {@link RegionRepository}.
|
* @param collisionManager The {@link CollisionManager} used to check if there is a collision
|
||||||
|
* between two {@link Position}s in a path.
|
||||||
*/
|
*/
|
||||||
public SimplePathfindingAlgorithm(RegionRepository repository) {
|
public SimplePathfindingAlgorithm(CollisionManager collisionManager) {
|
||||||
super(repository);
|
super(collisionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +56,7 @@ public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
|
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
|
||||||
* <p>
|
* <p/>
|
||||||
* This method:
|
* This method:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Adds positions horizontally until we are either horizontally aligned with the target, or the next step is not
|
* <li>Adds positions horizontally until we are either horizontally aligned with the target, or the next step is not
|
||||||
@@ -97,7 +100,7 @@ public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the necessary and possible vertical {@link Position}s to the existing {@link Deque}.
|
* Adds the necessary and possible vertical {@link Position}s to the existing {@link Deque}.
|
||||||
* <p>
|
* <p/>
|
||||||
* This method:
|
* This method:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Adds positions vertically until we are either vertically aligned with the target, or the next step is not
|
* <li>Adds positions vertically until we are either vertically aligned with the target, or the next step is not
|
||||||
@@ -133,7 +136,7 @@ public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
|
|
||||||
Position last = new Position(x, y, height);
|
Position last = new Position(x, y, height);
|
||||||
if (!last.equals(target) && dx != 0
|
if (!last.equals(target) && dx != 0
|
||||||
&& traversable(last, boundaries, dx > 0 ? Direction.WEST : Direction.EAST)) {
|
&& traversable(last, boundaries, dx > 0 ? Direction.WEST : Direction.EAST)) {
|
||||||
return addHorizontal(last, target, positions);
|
return addHorizontal(last, target, positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import java.util.Queue;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.apollo.game.model.Position;
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.World;
|
||||||
import org.apollo.game.model.area.RegionRepository;
|
import org.apollo.game.model.area.RegionRepository;
|
||||||
|
import org.apollo.game.model.area.collision.CollisionManager;
|
||||||
import org.apollo.game.model.entity.Npc;
|
import org.apollo.game.model.entity.Npc;
|
||||||
import org.apollo.game.model.entity.WalkingQueue;
|
import org.apollo.game.model.entity.WalkingQueue;
|
||||||
import org.apollo.game.model.entity.path.SimplePathfindingAlgorithm;
|
import org.apollo.game.model.entity.path.SimplePathfindingAlgorithm;
|
||||||
@@ -50,11 +52,11 @@ public final class NpcMovementTask extends ScheduledTask {
|
|||||||
/**
|
/**
|
||||||
* Creates the NpcMovementTask.
|
* Creates the NpcMovementTask.
|
||||||
*
|
*
|
||||||
* @param repository The {@link RegionRepository}.
|
* @param collisionManager The {@link CollisionManager} used to check if an {@link Npc} movement is valid.
|
||||||
*/
|
*/
|
||||||
public NpcMovementTask(RegionRepository repository) {
|
public NpcMovementTask(CollisionManager collisionManager) {
|
||||||
super(DELAY, false);
|
super(DELAY, false);
|
||||||
algorithm = new SimplePathfindingAlgorithm(repository);
|
algorithm = new SimplePathfindingAlgorithm(collisionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package org.apollo.game.model.area.collision;
|
||||||
|
|
||||||
|
import org.apollo.cache.def.ObjectDefinition;
|
||||||
|
import org.apollo.cache.map.MapObject;
|
||||||
|
import org.apollo.game.model.Direction;
|
||||||
|
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.entity.EntityType;
|
||||||
|
import org.apollo.game.model.entity.obj.ObjectType;
|
||||||
|
import org.apollo.game.model.entity.obj.StaticGameObject;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the {@link CollisionManager}.
|
||||||
|
*/
|
||||||
|
public final class CollisionManagerTests {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of a simple wall object.
|
||||||
|
*/
|
||||||
|
private static final int WALL = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of a square 2x2 solid object.
|
||||||
|
*/
|
||||||
|
private static final int SQUARE_OBJECT = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup some simple object definitions to use in collision tests.
|
||||||
|
*/
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupObjectDefinitions() {
|
||||||
|
ObjectDefinition wall = new ObjectDefinition(WALL);
|
||||||
|
|
||||||
|
ObjectDefinition squareObject = new ObjectDefinition(SQUARE_OBJECT);
|
||||||
|
squareObject.setLength(2);
|
||||||
|
squareObject.setWidth(2);
|
||||||
|
|
||||||
|
ObjectDefinition.init(new ObjectDefinition[]{
|
||||||
|
wall, squareObject
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a wall is untraversable from the front and back, for the facing direction and opposite facing direction
|
||||||
|
* respectively. For a simple grid, with a wall on 0,1 we end up with a collision matrix looking like:
|
||||||
|
* <pre>
|
||||||
|
* (0,2) |---------|
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* | xxx |
|
||||||
|
* (0,1) |---------|
|
||||||
|
* | xxx |
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* (0,0) |---------|
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* |---------|
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Where {@code xxx} denotes that you cannot walk into that tile from that direction (in this case you south on
|
||||||
|
* (0,2) and north on (0,1)). For the grid above you can't walk north into {@code (0, 2)} because {@code (0, 2)} is
|
||||||
|
* untraversable from the south, and as expectred you can't walk south into {@code (0,1)} because ({@code (0, 1)} is
|
||||||
|
* untraversable from the north.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void wall() {
|
||||||
|
Position front = new Position(0, 1, 0);
|
||||||
|
Position back = front.step(1, Direction.NORTH);
|
||||||
|
|
||||||
|
CollisionManager collisionManager = createCollisionManager(
|
||||||
|
createMapObject(WALL, front, ObjectType.LENGTHWISE_WALL, Direction.NORTH)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertUntraversable(collisionManager, front, Direction.NORTH);
|
||||||
|
assertUntraversable(collisionManager, back, Direction.SOUTH);
|
||||||
|
assertUntraversable(collisionManager, front, Direction.NORTH_EAST);
|
||||||
|
assertUntraversable(collisionManager, back, Direction.SOUTH_EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a corner wall is untraversable from the sides that it blocks. Corners are much like walls, with
|
||||||
|
* the only difference being their orientation. Instead of {@link Direction#WNES}, they have diagonal directions.
|
||||||
|
* With a corner wall at (0, 1) facing to the north-east we end up with a grid looking like this:
|
||||||
|
* <p>
|
||||||
|
* <pre>
|
||||||
|
* (0,2) |---------|---------|
|
||||||
|
* | | |
|
||||||
|
* | | |
|
||||||
|
* | |xxx |
|
||||||
|
* (0,1) |---------|---------|
|
||||||
|
* | xxx| |
|
||||||
|
* | | |
|
||||||
|
* | | |
|
||||||
|
* (0,0) |---------|---------|
|
||||||
|
* (1,0) (2,0) (3,0)
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Where you can walk north and east through the tile the corner occupies, as well as south and west through the
|
||||||
|
* adjacent tile, but not north-east or south-west.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void cornerWall() {
|
||||||
|
Position front = new Position(0, 1, 0);
|
||||||
|
Position back = front.step(1, Direction.NORTH_EAST);
|
||||||
|
|
||||||
|
CollisionManager collisionManager = createCollisionManager(
|
||||||
|
createMapObject(WALL, front, ObjectType.TRIANGULAR_CORNER, Direction.NORTH_EAST)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTraversable(collisionManager, front, Direction.NORTH, Direction.EAST);
|
||||||
|
assertUntraversable(collisionManager, front, Direction.NORTH_EAST);
|
||||||
|
assertTraversable(collisionManager, back, Direction.SOUTH, Direction.WEST);
|
||||||
|
assertUntraversable(collisionManager, back, Direction.SOUTH_WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the tiles occupied by a 2x2 square object are not traversable. When an interactable object is added
|
||||||
|
* to a collision update, all tiles spanning its with and length from its origin position will be marked as completely
|
||||||
|
* blocked off, which with a 2x2 object at (1,1) produces a grid like this:
|
||||||
|
* <pre>
|
||||||
|
* (0,3) |---------|---------|---------|---------|
|
||||||
|
* | |xxxxxxxxx|xxxxxxxxx| |
|
||||||
|
* | |x x|x x| |
|
||||||
|
* | |xxxxxxxxx|xxxxxxxxx| |
|
||||||
|
* (0,2) |---------|---------|---------|---------|
|
||||||
|
* | |xxxxxxxxx|xxxxxxxxx| |
|
||||||
|
* | |x x|x x| |
|
||||||
|
* | |xxxxxxxxx|xxxxxxxxx| |
|
||||||
|
* (0,1) |---------|---------|---------|---------|
|
||||||
|
* | | | | |
|
||||||
|
* | | | | |
|
||||||
|
* | | | | |
|
||||||
|
* (0,0) |---------|---------|---------|---------|
|
||||||
|
* (0,0) (1,0) (2,0) (3,0) (4,0)
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Where every tile that is occupied by the object is untraversable in every direction.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void object() {
|
||||||
|
Position origin = new Position(1, 1, 0);
|
||||||
|
|
||||||
|
CollisionManager collisionManager = createCollisionManager(
|
||||||
|
createMapObject(SQUARE_OBJECT, origin, ObjectType.INTERACTABLE, Direction.NORTH)
|
||||||
|
);
|
||||||
|
|
||||||
|
Position west = origin.step(1, Direction.WEST);
|
||||||
|
Position east = origin.step(2, Direction.EAST);
|
||||||
|
Position northEast = origin.step(2, Direction.NORTH).step(1, Direction.EAST);
|
||||||
|
Position southEast = origin.step(1, Direction.SOUTH_EAST);
|
||||||
|
|
||||||
|
assertUntraversable(collisionManager, west, Direction.EAST);
|
||||||
|
assertUntraversable(collisionManager, east, Direction.WEST);
|
||||||
|
assertUntraversable(collisionManager, northEast, Direction.SOUTH_EAST, Direction.SOUTH_WEST);
|
||||||
|
assertUntraversable(collisionManager, southEast, Direction.NORTH_EAST, Direction.NORTH_WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for creating {@code org.apollo.cache} {@link MapObject}s using data structures from
|
||||||
|
* {@code org.apollo.game}.
|
||||||
|
*
|
||||||
|
* @param id The id of the object. Must be one of the {@link ObjectDefinition}s defined above.
|
||||||
|
* @param position The position of the object.
|
||||||
|
* @param type The object type.
|
||||||
|
* @param direction The orientation of the object.
|
||||||
|
* @return A new {@link MapObject}.
|
||||||
|
*/
|
||||||
|
private static MapObject createMapObject(int id, Position position, ObjectType type, Direction direction) {
|
||||||
|
return new MapObject(id, position.getX(), position.getY(), position.getHeight(), type.getValue(),
|
||||||
|
direction.toOrientationInteger());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up dependencies for and creates a stub {@link CollisionManager}, then builds the collision matrices
|
||||||
|
* using the {@code objects} given.
|
||||||
|
*
|
||||||
|
* @param objects The objects to build collision matrices from.
|
||||||
|
* @return A new {@link CollisionManager} with a valid {@link RegionRepository} and every {@link CollisionMatrix}
|
||||||
|
* built.
|
||||||
|
*/
|
||||||
|
private static CollisionManager createCollisionManager(MapObject... objects) {
|
||||||
|
World world = new World();
|
||||||
|
RegionRepository regions = world.getRegionRepository();
|
||||||
|
CollisionManager collisionManager = world.getCollisionManager();
|
||||||
|
|
||||||
|
for (MapObject object : objects) {
|
||||||
|
// treat local coordinates as absolute for simplicity
|
||||||
|
int x = object.getLocalX(), y = object.getLocalY();
|
||||||
|
int height = object.getHeight();
|
||||||
|
|
||||||
|
Position position = new Position(x, y, height);
|
||||||
|
Region region = regions.fromPosition(position);
|
||||||
|
region.addEntity(new StaticGameObject(world, object.getId(), position, object.getType(),
|
||||||
|
object.getOrientation()), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
collisionManager.build(false);
|
||||||
|
return collisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper test assertion that a position is untraversable in a given direction.
|
||||||
|
*
|
||||||
|
* @param collisionManager The {@link CollisionManager} to check.
|
||||||
|
* @param position The {@link Position}.
|
||||||
|
* @param directions The {@link Direction}s to assert.
|
||||||
|
*/
|
||||||
|
private static void assertUntraversable(CollisionManager collisionManager, Position position, Direction... directions) {
|
||||||
|
for (Direction direction : directions) {
|
||||||
|
boolean traversable = collisionManager.traversable(position, EntityType.NPC, direction);
|
||||||
|
String message = String.format("Can walk %s from tile at (%d,%d), should not be able to",
|
||||||
|
direction.toString(), position.getX(), position.getY());
|
||||||
|
|
||||||
|
Assert.assertFalse(message, traversable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper test assertion that a position is traversable in a given direction.
|
||||||
|
*
|
||||||
|
* @param collisionManager The {@link CollisionManager} to check.
|
||||||
|
* @param position The {@link Position}.
|
||||||
|
* @param directions The {@link Direction}s to assert.
|
||||||
|
*/
|
||||||
|
private static void assertTraversable(CollisionManager collisionManager, Position position, Direction... directions) {
|
||||||
|
for (Direction direction : directions) {
|
||||||
|
boolean traversable = collisionManager.traversable(position, EntityType.NPC, direction);
|
||||||
|
String message = String.format("Cannot walk %s from tile at (%d,%d), should be able to",
|
||||||
|
direction.toString(), position.getX(), position.getY());
|
||||||
|
|
||||||
|
Assert.assertTrue(message, traversable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user