mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Optimise definition decoding for faster start-up.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -11,6 +12,7 @@ import java.util.function.Predicate;
|
||||
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.model.Position;
|
||||
import org.apollo.game.model.World;
|
||||
@@ -31,7 +33,7 @@ import com.google.common.collect.Iterables;
|
||||
* @author Ryley
|
||||
* @author Major
|
||||
*/
|
||||
public final class GameObjectDecoder {
|
||||
public final class GameObjectDecoder implements Runnable {
|
||||
|
||||
/**
|
||||
* A bit flag that indicates that the tile at the current Position is blocked.
|
||||
@@ -58,44 +60,51 @@ public final class GameObjectDecoder {
|
||||
*/
|
||||
private final RegionRepository regions;
|
||||
|
||||
/**
|
||||
* The World to place the objects in.
|
||||
*/
|
||||
private final World world;
|
||||
|
||||
/**
|
||||
* Creates the GameObjectDecoder.
|
||||
*
|
||||
* @param fs The {@link IndexedFileSystem}.
|
||||
* @param regions The {@link RegionRepository}.
|
||||
* @param world The {@link World} to place the objects in.
|
||||
*/
|
||||
public GameObjectDecoder(IndexedFileSystem fs, RegionRepository regions) {
|
||||
public GameObjectDecoder(IndexedFileSystem fs, World world) {
|
||||
this.fs = fs;
|
||||
this.regions = regions;
|
||||
this.world = world;
|
||||
regions = world.getRegionRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the GameObjects from their MapDefinitions.
|
||||
*
|
||||
* @param world The {@link World} containing the StaticGameObjects.
|
||||
* @return The decoded objects.
|
||||
* @throws IOException If there is an error decoding the {@link MapDefinition}s.
|
||||
*/
|
||||
public GameObject[] decode(World world) throws IOException {
|
||||
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
|
||||
@Override
|
||||
public void run() {
|
||||
ObjectDefinitionDecoder decoder = new ObjectDefinitionDecoder(fs);
|
||||
decoder.run();
|
||||
|
||||
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
|
||||
MapDefinition definition = entry.getValue();
|
||||
try {
|
||||
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
|
||||
|
||||
int packed = definition.getPackedCoordinates();
|
||||
int x = (packed >> 8 & 0xFF) * 64;
|
||||
int y = (packed & 0xFF) * 64;
|
||||
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
|
||||
MapDefinition definition = entry.getValue();
|
||||
|
||||
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
|
||||
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
|
||||
decodeObjects(world, decompressed, x, y);
|
||||
int packed = definition.getPackedCoordinates();
|
||||
int x = (packed >> 8 & 0xFF) * 64;
|
||||
int y = (packed & 0xFF) * 64;
|
||||
|
||||
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
|
||||
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
|
||||
decodeTerrain(decompressed, x, y);
|
||||
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
|
||||
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
|
||||
decodeObjects(world, decompressed, x, y);
|
||||
|
||||
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
|
||||
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
|
||||
decodeTerrain(decompressed, x, y);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error decoding StaticGameObjects.", e);
|
||||
}
|
||||
|
||||
return Iterables.toArray(objects, GameObject.class);
|
||||
objects.forEach(object -> regions.fromPosition(object.getPosition()).addEntity(object, false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,24 +121,21 @@ public final class GameObjectDecoder {
|
||||
int x = position.getX(), y = position.getY(), height = position.getHeight();
|
||||
|
||||
CollisionMatrix matrix = region.getMatrix(height);
|
||||
|
||||
boolean block = false;
|
||||
|
||||
if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) {
|
||||
block = true;
|
||||
}
|
||||
|
||||
Predicate<Integer> walls = (value) -> value >= ObjectType.LENGTHWISE_WALL.getValue()
|
||||
// TODO figure out the other ObjectTypes and get rid of all the getValue() calls
|
||||
|
||||
Predicate<Integer> walls = value -> value >= ObjectType.LENGTHWISE_WALL.getValue()
|
||||
&& value <= ObjectType.RECTANGULAR_CORNER.getValue() || value == ObjectType.DIAGONAL_WALL.getValue();
|
||||
|
||||
Predicate<Integer> roofs = (value) -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
|
||||
Predicate<Integer> roofs = value -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
|
||||
&& value < ObjectType.FLOOR_DECORATION.getValue();
|
||||
|
||||
if (walls.test(type) || roofs.test(type)) {
|
||||
block = true;
|
||||
}
|
||||
|
||||
if (type == 10 && definition.isSolid()) {
|
||||
if (walls.test(type) || roofs.test(type) || type == ObjectType.INTERACTABLE.getValue() && definition.isSolid()) {
|
||||
block = true;
|
||||
}
|
||||
|
||||
@@ -144,11 +150,13 @@ public final class GameObjectDecoder {
|
||||
Position nextPosition = new Position(nextLocalX, nextLocalY);
|
||||
Region next = regions.fromPosition(nextPosition);
|
||||
|
||||
int nextX = nextPosition.getX() % Region.SIZE + dx, nextY = nextPosition.getY() % Region.SIZE
|
||||
+ dy;
|
||||
int nextX = nextPosition.getX() % Region.SIZE + dx;
|
||||
int nextY = nextPosition.getY() % Region.SIZE + dy;
|
||||
|
||||
if (nextX > 7) {
|
||||
nextX -= 7;
|
||||
}
|
||||
|
||||
if (nextY > 7) {
|
||||
nextY -= 7;
|
||||
}
|
||||
@@ -173,12 +181,11 @@ public final class GameObjectDecoder {
|
||||
Region region = regions.fromPosition(position);
|
||||
int x = position.getX(), y = position.getY(), height = position.getHeight();
|
||||
|
||||
CollisionMatrix current = region.getMatrix(height);
|
||||
|
||||
boolean block = false;
|
||||
if ((attributes & BLOCKED_TILE) != 0) {
|
||||
block = true;
|
||||
}
|
||||
|
||||
if ((attributes & BRIDGE_TILE) != 0) {
|
||||
if (height > 0) {
|
||||
block = true;
|
||||
@@ -188,7 +195,7 @@ public final class GameObjectDecoder {
|
||||
|
||||
if (block) {
|
||||
int localX = x % Region.SIZE, localY = y % Region.SIZE;
|
||||
current.block(localX, localY);
|
||||
region.getMatrix(height).block(localX, localY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +256,7 @@ public final class GameObjectDecoder {
|
||||
int attributes = 0;
|
||||
while (true) {
|
||||
int attributeId = buffer.get() & 0xFF;
|
||||
|
||||
if (attributeId == 0) {
|
||||
decodeAttributes(attributes, position);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.apollo.game.fs.decoder;
|
||||
|
||||
import org.apollo.util.ThreadUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A composite decoder that executes each child in parallel.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class SynchronousDecoder {
|
||||
|
||||
/**
|
||||
* The time to wait before cancelling the decoding.
|
||||
*/
|
||||
private static final int TIMEOUT = 15_000;
|
||||
|
||||
/**
|
||||
* The Executor used to execute the Runnable(s).
|
||||
*/
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(ThreadUtil.AVAILABLE_PROCESSORS,
|
||||
ThreadUtil.create("SynchronousDecoder"));
|
||||
|
||||
/**
|
||||
* The List of Runnables.
|
||||
*/
|
||||
private final List<Runnable> runnables;
|
||||
|
||||
/**
|
||||
* Creates the SynchronousDecoder.
|
||||
*
|
||||
* @param runnables The {@link Runnable}s to execute.
|
||||
*/
|
||||
public SynchronousDecoder(Runnable... runnables) {
|
||||
this.runnables = Arrays.asList(runnables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts this SynchronousDecoder.
|
||||
*
|
||||
* @throws InterruptedException If a decoder is still running after {@link #TIMEOUT} ms.
|
||||
*/
|
||||
public void block() throws InterruptedException {
|
||||
runnables.forEach(executor::submit);
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +1,83 @@
|
||||
package org.apollo.game.io;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import org.apollo.cache.def.EquipmentDefinition;
|
||||
|
||||
/**
|
||||
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link EquipmentDefinition}s.
|
||||
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link
|
||||
* EquipmentDefinition}s.
|
||||
*
|
||||
* @author Graham
|
||||
*/
|
||||
public final class EquipmentDefinitionParser {
|
||||
public final class EquipmentDefinitionParser implements Runnable {
|
||||
|
||||
/**
|
||||
* The input stream.
|
||||
* Creates an {@link EquipmentDefinitionParser} that reads from the file located at the specified {@code path}.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return The EquipmentDefinitionParser.
|
||||
* @throws UncheckedIOException If there is an error creating the {@link FileInputStream}.
|
||||
*/
|
||||
public static EquipmentDefinitionParser fromFile(String path) {
|
||||
try {
|
||||
return new EquipmentDefinitionParser(new BufferedInputStream(new FileInputStream(path)));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error creating EquipmentDefinitionParser.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The InputStream.
|
||||
*/
|
||||
private final InputStream is;
|
||||
|
||||
/**
|
||||
* Creates the equipment definition parser.
|
||||
* Creates the EquipmentDefinitionParser.
|
||||
*
|
||||
* @param is The input stream.
|
||||
* @param is The {@link InputStream}.
|
||||
*/
|
||||
public EquipmentDefinitionParser(InputStream is) {
|
||||
this.is = is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the input stream.
|
||||
*
|
||||
* @return The equipment definition array.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
public EquipmentDefinition[] parse() throws IOException {
|
||||
DataInputStream dis = new DataInputStream(is);
|
||||
@Override
|
||||
public void run() {
|
||||
try (DataInputStream in = new DataInputStream(is)) {
|
||||
int count = in.readShort() & 0xFFFF;
|
||||
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
|
||||
|
||||
int count = dis.readShort() & 0xFFFF;
|
||||
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
|
||||
for (int id = 0; id < count; id++) {
|
||||
int slot = in.readByte() & 0xFF;
|
||||
if (slot != 0xFF) {
|
||||
boolean twoHanded = in.readBoolean();
|
||||
boolean fullBody = in.readBoolean();
|
||||
boolean fullHat = in.readBoolean();
|
||||
boolean fullMask = in.readBoolean();
|
||||
int attack = in.readByte() & 0xFF;
|
||||
int strength = in.readByte() & 0xFF;
|
||||
int defence = in.readByte() & 0xFF;
|
||||
int ranged = in.readByte() & 0xFF;
|
||||
int magic = in.readByte() & 0xFF;
|
||||
|
||||
for (int id = 0; id < count; id++) {
|
||||
int slot = dis.readByte() & 0xFF;
|
||||
if (slot != 0xFF) {
|
||||
boolean twoHanded = dis.readBoolean();
|
||||
boolean fullBody = dis.readBoolean();
|
||||
boolean fullHat = dis.readBoolean();
|
||||
boolean fullMask = dis.readBoolean();
|
||||
int attack = dis.readByte() & 0xFF;
|
||||
int strength = dis.readByte() & 0xFF;
|
||||
int defence = dis.readByte() & 0xFF;
|
||||
int ranged = dis.readByte() & 0xFF;
|
||||
int magic = dis.readByte() & 0xFF;
|
||||
EquipmentDefinition definition = new EquipmentDefinition(id);
|
||||
definition.setLevels(attack, strength, defence, ranged, magic);
|
||||
definition.setSlot(slot);
|
||||
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
|
||||
|
||||
EquipmentDefinition definition = new EquipmentDefinition(id);
|
||||
definition.setLevels(attack, strength, defence, ranged, magic);
|
||||
definition.setSlot(slot);
|
||||
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
|
||||
|
||||
definitions[id] = definition;
|
||||
definitions[id] = definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return definitions;
|
||||
EquipmentDefinition.init(definitions);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error parsing EquipmentDefinitions.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
package org.apollo.game.model;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apollo.Server;
|
||||
import org.apollo.Service;
|
||||
import org.apollo.cache.IndexedFileSystem;
|
||||
import org.apollo.cache.decoder.ItemDefinitionDecoder;
|
||||
import org.apollo.cache.decoder.NpcDefinitionDecoder;
|
||||
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
|
||||
import org.apollo.cache.def.EquipmentDefinition;
|
||||
import org.apollo.cache.def.ItemDefinition;
|
||||
import org.apollo.cache.def.NpcDefinition;
|
||||
import org.apollo.cache.def.ObjectDefinition;
|
||||
import org.apollo.game.command.CommandDispatcher;
|
||||
import org.apollo.game.fs.decoder.GameObjectDecoder;
|
||||
import org.apollo.game.fs.decoder.SynchronousDecoder;
|
||||
import org.apollo.game.io.EquipmentDefinitionParser;
|
||||
import org.apollo.game.model.area.Region;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
@@ -27,7 +21,6 @@ import org.apollo.game.model.entity.EntityType;
|
||||
import org.apollo.game.model.entity.MobRepository;
|
||||
import org.apollo.game.model.entity.Npc;
|
||||
import org.apollo.game.model.entity.Player;
|
||||
import org.apollo.game.model.entity.obj.GameObject;
|
||||
import org.apollo.game.model.event.Event;
|
||||
import org.apollo.game.model.event.EventListener;
|
||||
import org.apollo.game.model.event.EventListenerChainSet;
|
||||
@@ -87,11 +80,6 @@ public final class World {
|
||||
*/
|
||||
private final EventListenerChainSet events = new EventListenerChainSet();
|
||||
|
||||
/**
|
||||
* The ScheduledTask that moves Npcs.
|
||||
*/
|
||||
private NpcMovementTask npcMovement;
|
||||
|
||||
/**
|
||||
* The {@link MobRepository} of {@link Npc}s.
|
||||
*/
|
||||
@@ -107,32 +95,30 @@ public final class World {
|
||||
*/
|
||||
private final Map<Long, Player> players = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The {@link PluginManager}.
|
||||
*/
|
||||
private PluginManager pluginManager;
|
||||
|
||||
/**
|
||||
* This world's {@link RegionRepository}.
|
||||
*/
|
||||
private final RegionRepository regions = RegionRepository.immutable();
|
||||
|
||||
/**
|
||||
* The release number (i.e. version) of this world.
|
||||
*/
|
||||
private int releaseNumber;
|
||||
|
||||
/**
|
||||
* The scheduler.
|
||||
*/
|
||||
private final Scheduler scheduler = new Scheduler();
|
||||
|
||||
/**
|
||||
* Creates the world.
|
||||
* The ScheduledTask that moves Npcs.
|
||||
*/
|
||||
public World() {
|
||||
private NpcMovementTask npcMovement;
|
||||
|
||||
}
|
||||
/**
|
||||
* The {@link PluginManager}.
|
||||
*/
|
||||
private PluginManager pluginManager;
|
||||
|
||||
/**
|
||||
* The release number (i.e. version) of this world.
|
||||
*/
|
||||
private int releaseNumber;
|
||||
|
||||
/**
|
||||
* Gets the command dispatcher.
|
||||
@@ -210,32 +196,11 @@ public final class World {
|
||||
public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception {
|
||||
releaseNumber = release;
|
||||
|
||||
ItemDefinitionDecoder itemDecoder = new ItemDefinitionDecoder(fs);
|
||||
ItemDefinition[] items = itemDecoder.decode();
|
||||
ItemDefinition.init(items);
|
||||
logger.fine("Loaded " + items.length + " item definitions.");
|
||||
SynchronousDecoder decoder = new SynchronousDecoder(new ItemDefinitionDecoder(fs),
|
||||
new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
|
||||
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
|
||||
|
||||
try (InputStream is = new BufferedInputStream(new FileInputStream("data/equipment-" + release + ".dat"))) {
|
||||
EquipmentDefinitionParser parser = new EquipmentDefinitionParser(is);
|
||||
EquipmentDefinition[] defs = parser.parse();
|
||||
EquipmentDefinition.init(defs);
|
||||
logger.fine("Loaded " + defs.length + " equipment definitions.");
|
||||
}
|
||||
|
||||
NpcDefinitionDecoder npcDecoder = new NpcDefinitionDecoder(fs);
|
||||
NpcDefinition[] npcs = npcDecoder.decode();
|
||||
NpcDefinition.init(npcs);
|
||||
logger.fine("Loaded " + npcs.length + " npc definitions.");
|
||||
|
||||
ObjectDefinitionDecoder objectDecoder = new ObjectDefinitionDecoder(fs);
|
||||
ObjectDefinition[] objectDefs = objectDecoder.decode();
|
||||
ObjectDefinition.init(objectDefs);
|
||||
logger.fine("Loaded " + objectDefs.length + " object definitions.");
|
||||
|
||||
GameObjectDecoder staticDecoder = new GameObjectDecoder(fs, regions);
|
||||
GameObject[] objects = staticDecoder.decode(this);
|
||||
placeEntities(objects);
|
||||
logger.fine("Loaded " + objects.length + " static objects.");
|
||||
decoder.block();
|
||||
|
||||
npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues.
|
||||
scheduler.schedule(npcMovement);
|
||||
@@ -291,6 +256,7 @@ public final class World {
|
||||
} else {
|
||||
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -384,13 +350,4 @@ public final class World {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entities to regions in the {@link RegionRepository}. By default, we do not notify listeners.
|
||||
*
|
||||
* @param entities The entities.
|
||||
*/
|
||||
private void placeEntities(Entity... entities) {
|
||||
Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity, false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,13 @@ import org.apollo.game.sync.seg.SynchronizationSegment;
|
||||
public final class NpcSynchronizationTask extends SynchronizationTask {
|
||||
|
||||
/**
|
||||
* The maximum number of npcs to load per cycle. This prevents the update packet from becoming too large (the client
|
||||
* uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
|
||||
* The maximum amount of local npcs.
|
||||
*/
|
||||
private static final int MAXIMUM_LOCAL_NPCS = 255;
|
||||
|
||||
/**
|
||||
* The maximum number of npcs to load per cycle. This prevents the update packet from becoming too large (the
|
||||
* client uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
|
||||
*/
|
||||
private static final int NEW_NPCS_PER_CYCLE = 20;
|
||||
|
||||
@@ -42,41 +47,49 @@ public final class NpcSynchronizationTask extends SynchronizationTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<Npc> localNpcs = player.getLocalNpcList();
|
||||
List<Npc> locals = player.getLocalNpcList();
|
||||
List<SynchronizationSegment> segments = new ArrayList<>();
|
||||
int oldLocalNpcs = localNpcs.size();
|
||||
|
||||
int originalCount = locals.size();
|
||||
final Position playerPosition = player.getPosition();
|
||||
|
||||
for (Iterator<Npc> it = localNpcs.iterator(); it.hasNext();) {
|
||||
Npc npc = it.next();
|
||||
if (!npc.isActive() || npc.isTeleporting() || npc.getPosition().getLongestDelta(playerPosition) > player.getViewingDistance() || !npc.getPosition().isWithinDistance(playerPosition, player.getViewingDistance())) {
|
||||
it.remove();
|
||||
int distance = player.getViewingDistance();
|
||||
for (Iterator<Npc> iterator = locals.iterator(); iterator.hasNext(); ) {
|
||||
Npc npc = iterator.next();
|
||||
Position position = npc.getPosition();
|
||||
|
||||
if (!npc.isActive() || npc.isTeleporting() || position.getLongestDelta(playerPosition) > distance
|
||||
|| !position.isWithinDistance(playerPosition, distance)) {
|
||||
iterator.remove();
|
||||
|
||||
segments.add(new RemoveMobSegment());
|
||||
} else {
|
||||
segments.add(new MovementSegment(npc.getBlockSet(), npc.getDirections()));
|
||||
}
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
int added = 0, count = locals.size();
|
||||
|
||||
for (Npc npc : player.getWorld().getNpcRepository()) {
|
||||
if (localNpcs.size() >= 255) {
|
||||
if (count >= MAXIMUM_LOCAL_NPCS) {
|
||||
player.flagExcessiveNpcs();
|
||||
break;
|
||||
} else if (added >= NEW_NPCS_PER_CYCLE) {
|
||||
break;
|
||||
}
|
||||
|
||||
Position npcPosition = npc.getPosition();
|
||||
if (npcPosition.isWithinDistance(playerPosition, player.getViewingDistance()) && !localNpcs.contains(npc)) {
|
||||
localNpcs.add(npc);
|
||||
Position position = npc.getPosition();
|
||||
if (position.isWithinDistance(playerPosition, distance) && !locals.contains(npc)) {
|
||||
locals.add(npc);
|
||||
count++;
|
||||
added++;
|
||||
|
||||
npc.turnTo(npc.getFacingPosition());
|
||||
segments.add(new AddNpcSegment(npc.getBlockSet(), npc.getIndex(), npcPosition, npc.getId()));
|
||||
segments.add(new AddNpcSegment(npc.getBlockSet(), npc.getIndex(), position, npc.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
player.send(new NpcSynchronizationMessage(playerPosition, segments, oldLocalNpcs));
|
||||
player.send(new NpcSynchronizationMessage(playerPosition, segments, originalCount));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import java.util.List;
|
||||
|
||||
import org.apollo.game.message.impl.PlayerSynchronizationMessage;
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.entity.MobRepository;
|
||||
import org.apollo.game.model.entity.Player;
|
||||
import org.apollo.game.sync.block.AppearanceBlock;
|
||||
import org.apollo.game.sync.block.ChatBlock;
|
||||
@@ -25,6 +24,11 @@ import org.apollo.game.sync.seg.TeleportSegment;
|
||||
*/
|
||||
public final class PlayerSynchronizationTask extends SynchronizationTask {
|
||||
|
||||
/**
|
||||
* The maximum amount of local players.
|
||||
*/
|
||||
private static final int MAXIMUM_LOCAL_PLAYERS = 255;
|
||||
|
||||
/**
|
||||
* The maximum number of players to load per cycle. This prevents the update packet from becoming too large (the
|
||||
* client uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
|
||||
@@ -32,14 +36,14 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
|
||||
private static final int NEW_PLAYERS_PER_CYCLE = 20;
|
||||
|
||||
/**
|
||||
* The player.
|
||||
* The Player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Creates the {@link PlayerSynchronizationTask} for the specified player.
|
||||
* Creates the {@link PlayerSynchronizationTask} for the specified {@link Player}.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param player The Player.
|
||||
*/
|
||||
public PlayerSynchronizationTask(Player player) {
|
||||
this.player = player;
|
||||
@@ -50,80 +54,80 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
|
||||
Position lastKnownRegion = player.getLastKnownRegion();
|
||||
boolean regionChanged = player.hasRegionChanged();
|
||||
|
||||
SynchronizationSegment segment;
|
||||
SynchronizationBlockSet blockSet = player.getBlockSet();
|
||||
|
||||
if (blockSet.contains(ChatBlock.class)) {
|
||||
blockSet = blockSet.clone();
|
||||
blockSet.remove(ChatBlock.class);
|
||||
}
|
||||
|
||||
if (player.isTeleporting() || player.hasRegionChanged()) {
|
||||
segment = new TeleportSegment(blockSet, player.getPosition());
|
||||
} else {
|
||||
segment = new MovementSegment(blockSet, player.getDirections());
|
||||
}
|
||||
Position position = player.getPosition();
|
||||
|
||||
SynchronizationSegment segment = (player.isTeleporting() || player.hasRegionChanged()) ?
|
||||
new TeleportSegment(blockSet, position) : new MovementSegment(blockSet, player.getDirections());
|
||||
|
||||
List<Player> localPlayers = player.getLocalPlayerList();
|
||||
int oldLocalPlayers = localPlayers.size();
|
||||
int oldCount = localPlayers.size();
|
||||
|
||||
List<SynchronizationSegment> segments = new ArrayList<>();
|
||||
int distance = player.getViewingDistance();
|
||||
|
||||
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext(); ) {
|
||||
Player other = it.next();
|
||||
for (Iterator<Player> iterator = localPlayers.iterator(); iterator.hasNext(); ) {
|
||||
Player other = iterator.next();
|
||||
|
||||
if (removePlayer(other)) {
|
||||
it.remove();
|
||||
if (removeable(position, distance, other)) {
|
||||
iterator.remove();
|
||||
segments.add(new RemoveMobSegment());
|
||||
} else {
|
||||
segments.add(new MovementSegment(other.getBlockSet(), other.getDirections()));
|
||||
}
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
int added = 0, count = localPlayers.size();
|
||||
|
||||
MobRepository<Player> repository = player.getWorld().getPlayerRepository();
|
||||
for (Player other : repository) {
|
||||
if (localPlayers.size() >= 255) {
|
||||
for (Player other : player.getWorld().getPlayerRepository()) {
|
||||
if (count >= MAXIMUM_LOCAL_PLAYERS) {
|
||||
player.flagExcessivePlayers();
|
||||
break;
|
||||
} else if (added >= NEW_PLAYERS_PER_CYCLE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (other != player && other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) && !localPlayers.contains(other)) {
|
||||
Position local = other.getPosition();
|
||||
|
||||
if (other != player && local.isWithinDistance(position, distance) && !localPlayers.contains(other)) {
|
||||
localPlayers.add(other);
|
||||
count++;
|
||||
added++;
|
||||
|
||||
blockSet = other.getBlockSet();
|
||||
if (!blockSet.contains(AppearanceBlock.class)) {
|
||||
// TODO check if client has cached appearance
|
||||
if (!blockSet.contains(AppearanceBlock.class)) { // TODO check if client has cached appearance
|
||||
blockSet = blockSet.clone();
|
||||
blockSet.add(SynchronizationBlock.createAppearanceBlock(other));
|
||||
}
|
||||
|
||||
segments.add(new AddPlayerSegment(blockSet, other.getIndex(), other.getPosition()));
|
||||
segments.add(new AddPlayerSegment(blockSet, other.getIndex(), local));
|
||||
}
|
||||
}
|
||||
|
||||
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, player.getPosition(),
|
||||
regionChanged, segment, oldLocalPlayers, segments);
|
||||
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, position,
|
||||
regionChanged, segment, oldCount, segments);
|
||||
player.send(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the specified {@link Player} should be removed.
|
||||
*
|
||||
* @param position The {@link Position} of the Player being updated.
|
||||
* @param other The Player being tested.
|
||||
* @return {@code true} iff the specified Player should be removed.
|
||||
*/
|
||||
private boolean removePlayer(Player other) {
|
||||
private boolean removeable(Position position, int distance, Player other) {
|
||||
if (other.isTeleporting() || !other.isActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Position position = player.getPosition();
|
||||
Position otherPosition = other.getPosition();
|
||||
int distance = player.getViewingDistance();
|
||||
|
||||
return otherPosition.getLongestDelta(position) > distance || !otherPosition.isWithinDistance(position, distance);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public final class PostPlayerSynchronizationTask extends SynchronizationTask {
|
||||
player.setTeleporting(false);
|
||||
player.setRegionChanged(false);
|
||||
player.resetBlockSet();
|
||||
|
||||
if (!player.isExcessivePlayersSet()) {
|
||||
player.incrementViewingDistance();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user