diff --git a/src/network/packets/swg/zone/object_controller/DataTransformWithParent.java b/src/network/packets/swg/zone/object_controller/DataTransformWithParent.java index 0e11db38..6dacd981 100644 --- a/src/network/packets/swg/zone/object_controller/DataTransformWithParent.java +++ b/src/network/packets/swg/zone/object_controller/DataTransformWithParent.java @@ -99,7 +99,7 @@ public class DataTransformWithParent extends ObjectController { this.useLookAtYaw = useLookAtYaw; } - public int getCounter() { + public int getUpdateCounter() { return counter; } diff --git a/src/resources/Location.java b/src/resources/Location.java index b08120bb..011712d8 100644 --- a/src/resources/Location.java +++ b/src/resources/Location.java @@ -107,6 +107,10 @@ public class Location implements Encodable, Persistable { return square(getX()-x) + square(getY()-y) + square(getZ()-z) <= square(radius); } + public boolean isWithinFlatDistance(Location l, double radius) { + return isWithinFlatDistance(l.point, radius); + } + public boolean isWithinFlatDistance(Point3D target, double radius){ return square(getX() - target.getX()) + square(getZ() - target.getZ()) <= square(radius); } diff --git a/src/resources/Point3D.java b/src/resources/Point3D.java index 6455446e..ba886aac 100644 --- a/src/resources/Point3D.java +++ b/src/resources/Point3D.java @@ -87,9 +87,13 @@ public class Point3D implements Encodable, Persistable { double oY = rot.getY(); double oZ = rot.getZ(); double oW = rot.getW(); - double nX = x + oW*oW*getX() + 2*oY*oW*getZ() - 2*oZ*oW*getY() + oX*oX*getX() + 2*oY*oX*getY() + 2*oZ*oX*getZ() - oZ*oZ*getX() - oY*oY*getX(); - double nY = y + 2*oX*oY*getX() + oY*oY*getY() + 2*oZ*oY*getZ() + 2*oW*oZ*getX() - oZ*oZ*getY() + oW*oW*getY() - 2*oX*oW*getZ() - oX*oX*getY(); - double nZ = z + 2*oX*oZ*getX() + 2*oY*oZ*getY() + oZ*oZ*getZ() - 2*oW*oY*getX() - oY*oY*getZ() + 2*oW*oX*getY() - oX*oX*getZ() + oW*oW*getZ(); + double oX2 = oX * oX; + double oY2 = oY * oY; + double oZ2 = oZ * oZ; + double oW2 = oW * oW; + double nX = x + oW2*getX() + 2*oY*oW*getZ() - 2*oZ*oW*getY() + oX2*getX() + 2*oY*oX*getY() + 2*oZ*oX*getZ() - oZ2*getX() - oY2*getX(); + double nY = y + 2*oX*oY*getX() + oY2*getY() + 2*oZ*oY*getZ() + 2*oW*oZ*getX() - oZ2*getY() + oW2*getY() - 2*oX*oW*getZ() - oX2*getY(); + double nZ = z + 2*oX*oZ*getX() + 2*oY*oZ*getY() + oZ2*getZ() - 2*oW*oY*getX() - oY2*getZ() + 2*oW*oX*getY() - oX2*getZ() + oW2*getZ(); set(nX, nY, nZ); } diff --git a/src/resources/Terrain.java b/src/resources/Terrain.java index 9dbdefd1..82637d7d 100644 --- a/src/resources/Terrain.java +++ b/src/resources/Terrain.java @@ -148,6 +148,10 @@ public enum Terrain { return l; } + public static int getTerrainCount() { + return CRC_TO_TERRAIN.size(); + } + public static String getNameFromCrc(int crc) { String name = CRC_TO_NAME.get(crc); if (name == null) diff --git a/src/resources/buildout/BuildoutAreaGrid.java b/src/resources/buildout/BuildoutAreaGrid.java index 5501d237..60c9f097 100644 --- a/src/resources/buildout/BuildoutAreaGrid.java +++ b/src/resources/buildout/BuildoutAreaGrid.java @@ -78,7 +78,11 @@ public class BuildoutAreaGrid { public BuildoutArea getBuildoutArea(Terrain t, double x, double z) { BuildoutAreaNode node; synchronized (grid) { - node = grid[getNodeIndex(z)][getNodeIndex(x)]; + int zInd = getNodeIndex(z); + int xInd = getNodeIndex(x); + if (xInd < 0 || xInd >= grid.length || zInd < 0 || zInd >= grid.length) + return null; + node = grid[zInd][xInd]; if (node == null) return null; } diff --git a/src/resources/callback/BaseCallbackManager.java b/src/resources/callback/BaseCallbackManager.java new file mode 100644 index 00000000..66303187 --- /dev/null +++ b/src/resources/callback/BaseCallbackManager.java @@ -0,0 +1,106 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package resources.callback; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import utilities.ThreadUtilities; + +class BaseCallbackManager { + + private final Object executorMutex; + private final String name; + private final int threadCount; + private final AtomicBoolean shutdown; + private ExecutorService executor; + + public BaseCallbackManager(String name) { + this(name, 1); + } + + public BaseCallbackManager(String name, int threadCount) { + this.executorMutex = new Object(); + this.name = name; + this.threadCount = threadCount; + this.shutdown = new AtomicBoolean(false); + this.executor = null; + } + + public void start() { + synchronized (executorMutex) { + if (executor != null && !shutdown.get()) + executor.shutdown(); + shutdown.set(false); + if (threadCount <= 1) + executor = Executors.newSingleThreadExecutor(ThreadUtilities.newThreadFactory(name+"-callback-manager")); + else + executor = Executors.newFixedThreadPool(threadCount, ThreadUtilities.newThreadFactory(name+"-callback-manager-%d")); + } + } + + public void stop() { + synchronized (executorMutex) { + executor.shutdown(); + shutdown.set(true); + } + } + + public boolean awaitTermination(long timeout, TimeUnit unit) { + synchronized (executorMutex) { + if (executor == null) + return true; + if (!shutdown.get()) + throw new IllegalStateException("Cannot wait for termination when never stopped!"); + try { + return executor.awaitTermination(timeout, unit); + } catch (InterruptedException e) { + return false; + } + } + } + + protected boolean call(Runnable r) { + synchronized (executorMutex) { + if (executor == null) + throw new IllegalStateException("Manager has not been started!"); + if (shutdown.get()) + return false; + try { + executor.execute(r); + return true; + } catch (RejectedExecutionException e) { + return false; + } + } + } + +} diff --git a/src/resources/callback/CallbackManager.java b/src/resources/callback/CallbackManager.java new file mode 100644 index 00000000..b8fc9926 --- /dev/null +++ b/src/resources/callback/CallbackManager.java @@ -0,0 +1,89 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package resources.callback; + +import java.util.ArrayList; +import java.util.List; + +public class CallbackManager extends BaseCallbackManager { + + private final List callbacks; + + public CallbackManager(String name) { + this(name, 1); + } + + public CallbackManager(String name, int threadCount) { + super(name, threadCount); + this.callbacks = new ArrayList<>(); + } + + public void addCallback(T callback) { + synchronized (callbacks) { + callbacks.add(callback); + } + } + + public void removeCallback(T callback) { + synchronized (callbacks) { + callbacks.remove(callback); + } + } + + public void setCallback(T callback) { + synchronized (callbacks) { + callbacks.clear(); + callbacks.add(callback); + } + } + + public void clearCallbacks() { + synchronized (callbacks) { + callbacks.clear(); + } + } + + public boolean callOnEach(CallCallback call) { + synchronized (callbacks) { + return call(() -> { + for (T callback : callbacks) { + try { + call.run(callback); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + + + public interface CallCallback { + void run(T callback); + } +} diff --git a/src/resources/commands/callbacks/SocialInternalCmdCallback.java b/src/resources/commands/callbacks/SocialInternalCmdCallback.java index 67308aa1..f52657c1 100644 --- a/src/resources/commands/callbacks/SocialInternalCmdCallback.java +++ b/src/resources/commands/callbacks/SocialInternalCmdCallback.java @@ -47,9 +47,8 @@ public class SocialInternalCmdCallback implements ICmdCallback { PlayerEmote emote = new PlayerEmote(objectId, objectId, ((target == null) ? 0 : target.getObjectId()), Short.valueOf(cmd[1])); player.sendPacket(emote); - for (SWGObject aware : player.getCreatureObject().getObservers()) { - if (aware.getOwner() != null) - aware.getOwner().sendPacket(new PlayerEmote(aware.getObjectId(), emote)); + for (Player aware : player.getCreatureObject().getObservers()) { + aware.sendPacket(new PlayerEmote(aware.getCreatureObject().getObjectId(), emote)); } } } diff --git a/src/resources/network/DeltaBuilder.java b/src/resources/network/DeltaBuilder.java index 6d02fe19..9c17ec31 100644 --- a/src/resources/network/DeltaBuilder.java +++ b/src/resources/network/DeltaBuilder.java @@ -30,7 +30,6 @@ package resources.network; import network.packets.swg.zone.baselines.Baseline.BaselineType; import network.packets.swg.zone.deltas.DeltasMessage; import resources.objects.SWGObject; -import resources.objects.creature.CreatureObject; import resources.player.Player; import resources.player.PlayerState; import utilities.Encoder; @@ -65,8 +64,9 @@ public class DeltaBuilder { public void send() { DeltasMessage message = getBuiltMessage(); - boolean sendSelf = true; - if (object instanceof CreatureObject && ((CreatureObject) object).isLoggedInPlayer() && object.getOwner().getPlayerState() != PlayerState.ZONED_IN) + Player owner = object.getOwner(); + boolean sendSelf = owner != null; + if (owner == null || owner.getPlayerState() != PlayerState.ZONED_IN) sendSelf = false; switch(num) { case 3: diff --git a/src/resources/objects/SWGObject.java b/src/resources/objects/SWGObject.java index 93439d5e..6f39ac4c 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -32,11 +32,7 @@ import network.packets.swg.zone.SceneCreateObjectByCrc; import network.packets.swg.zone.SceneDestroyObject; import network.packets.swg.zone.SceneEndBaselines; import network.packets.swg.zone.UpdateContainmentMessage; -import network.packets.swg.zone.UpdateTransformMessage; -import network.packets.swg.zone.UpdateTransformWithParentMessage; import network.packets.swg.zone.baselines.Baseline.BaselineType; -import network.packets.swg.zone.object_controller.DataTransform; -import network.packets.swg.zone.object_controller.DataTransformWithParent; import resources.Location; import resources.Terrain; import resources.buildout.BuildoutArea; @@ -49,12 +45,12 @@ import resources.network.BaselineBuilder; import resources.network.BaselineObject; import resources.network.NetBuffer; import resources.network.NetBufferStream; +import resources.objects.awareness.ObjectAware; import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureObject; import resources.persistable.Persistable; import resources.persistable.SWGObjectFactory; import resources.player.Player; -import resources.player.PlayerState; import resources.server_info.Log; import services.CoreManager; import services.objects.ObjectCreator; @@ -74,12 +70,11 @@ public abstract class SWGObject extends BaselineObject implements Comparable containedObjects= new HashSet<>(); - private final Map attributes = new LinkedHashMap<>(); - private final Set objectsAware = new HashSet<>(); - private final Set customAware = new HashSet<>(); private final HashMap slots = new HashMap<>(); // HashMap used for null value support - private final Map dataAttributes = new HashMap<>(); - private final AtomicInteger updateCounter = new AtomicInteger(1); + private final transient Map attributes = new LinkedHashMap<>(); + private final transient ObjectAware awareness = new ObjectAware(this); + private final transient Map dataAttributes = new HashMap<>(); + private final transient AtomicInteger updateCounter = new AtomicInteger(1); private ObjectClassification classification = ObjectClassification.GENERATED; private GameObjectType gameObjectType = GameObjectType.GOT_NONE; @@ -97,7 +92,8 @@ public abstract class SWGObject extends BaselineObject implements Comparable oldAware = object.getObjectsAware(); + for (SWGObject obj : oldAware) + object.getAwareness().addObjectAware(obj.getAwareness()); + object.getAwareness().addObjectAware(object.getSuperParent().getAwareness()); + // Remove as parent object.parent = null; + object.getAwareness().setParent(null); object.slotArrangement = -1; + updateChildRadius(null); } /** @@ -164,7 +170,7 @@ public abstract class SWGObject extends BaselineObject implements Comparable oldObservers = getObserversAndParent(); + Set oldObservers = getObserversAndParent(); SWGObject parent = this.parent; if (parent != null) parent.removeObject(this); @@ -174,18 +180,12 @@ public abstract class SWGObject extends BaselineObject implements Comparable Parent - updateObjectAwareness(container.getObjectsAware(), false, true); // Create/Destroy - updateObjectAwareness(new HashSet<>(), true, false); // Clear Aware - } - } else if (parent != null) { // Parent -> World - updateObjectAwareness(parent.getObjectsAware(), true, true); // Create/Destroy & Aware } oldObservers.retainAll(getObserversAndParent()); long newId = (container != null) ? container.getObjectId() : 0; - for (SWGObject update : oldObservers) - update.getOwner().sendPacket(new UpdateContainmentMessage(objectId, newId, slotArrangement)); + for (Player update : oldObservers) + update.sendPacket(new UpdateContainmentMessage(objectId, newId, slotArrangement)); return ContainerResult.SUCCESS; } @@ -299,10 +299,16 @@ public abstract class SWGObject extends BaselineObject implements Comparable> getArrangement() { return arrangement; } @@ -492,6 +502,10 @@ public abstract class SWGObject extends BaselineObject implements Comparable bubble) + bubble = dist; + } + } + return bubble; } - public void setLoadRange(double range) { - this.loadRange = range; + public double getChildRadius() { + return childRadius; + } + + public double getPrefLoadRange() { + return prefLoadRange; + } + + public void setPrefLoadRange(double range) { + this.prefLoadRange = range; } /** @@ -570,7 +602,27 @@ public abstract class SWGObject extends BaselineObject implements Comparable maxDistance) + maxDistance = dist; + } + } + childRadius = maxDistance; + } else { + double maxDistance = childRadius; + double dist = location.distanceTo(updated.location); + if (dist > maxDistance) + maxDistance = dist; + childRadius = maxDistance; + } + } + private final void sendSceneCreateObject(Player target) { if (target == null) return; @@ -591,13 +643,15 @@ public abstract class SWGObject extends BaselineObject implements Comparable()); + public void addObjectAware(SWGObject aware) { + if (awareness.addObjectAware(aware.getAwareness())) { + createObject(aware.getOwner()); + aware.createObject(getOwner()); + } + } + + public void removeObjectAware(SWGObject aware) { + if (awareness.removeObjectAware(aware.getAwareness())) { + destroyObject(aware.getOwner()); + aware.destroyObject(getOwner()); + } + } + + public boolean isObjectAware(SWGObject aware) { + return awareness.isObjectAware(aware); + } + + public void clearObjectsAware() { + Set observers = awareness.getObjectObservers(); + awareness.clearObjectsAware(); + Player owner = getOwner(); + for (Player p : observers) { + destroyObject(p); + if (owner != null) + ((SWGObject) p.getCreatureObject()).destroyObject(owner); + } } public void resetAwareness() { - synchronized (objectsAware) { - objectsAware.clear(); - } + awareness.clearObjectsAware(); } - + public Set getObjectsAware() { - Set aware; - synchronized (objectsAware) { - aware = new HashSet<>(objectsAware); - } - synchronized (customAware) { - aware.addAll(customAware); - } - if (parent != null) { - aware.addAll(parent.getObjectsAware()); - aware.add(getSuperParent()); - } - return aware; + return awareness.getObjectsAware(); } public void addCustomAware(SWGObject aware) { - internalAddCustomAware(aware, true); - aware.internalAddCustomAware(this, true); + if (awareness.addCustomAware(aware.getAwareness())) { + createObject(aware.getOwner()); + aware.createObject(getOwner()); + } } public void removeCustomAware(SWGObject aware) { - internalRemoveCustomAware(aware, true); - aware.internalRemoveCustomAware(this, true); - } - - private void internalAddCustomAware(SWGObject aware, boolean sendUpdates) { - boolean changed = false; - synchronized (customAware) { - changed = customAware.add(aware); - } - if (changed && sendUpdates && aware.getOwner() != null) - createObject(aware.getOwner()); - } - - private void internalRemoveCustomAware(SWGObject aware, boolean sendUpdates) { - boolean changed = false; - synchronized (customAware) { - changed = customAware.remove(aware); - } - if (changed && sendUpdates && aware.getOwner() != null) + if (awareness.removeCustomAware(aware.getAwareness())) { destroyObject(aware.getOwner()); + aware.destroyObject(getOwner()); + } + } + + public boolean isCustomAware(SWGObject aware) { + return awareness.isCustomAware(aware); } public void clearCustomAware(boolean sendUpdates) { - Set copy; - synchronized (customAware) { - copy = new HashSet<>(customAware); - } - for (SWGObject aware : copy) { - internalRemoveCustomAware(aware, sendUpdates); - aware.internalRemoveCustomAware(this, sendUpdates); + Set observers = sendUpdates ? awareness.getCustomObservers() : null; + awareness.clearCustomAware(); + if (!sendUpdates) + return; + Player owner = getOwner(); + for (Player p : observers) { + destroyObject(p); + ((SWGObject) p.getCreatureObject()).destroyObject(owner); } } - public Set getObserversAndParent() { - Set observers = getObservers(); + public Set getObserversAndParent() { + Set observers = getObservers(); Player parentOwner = (getParent() != null) ? getParent().getOwner() : null; if (parentOwner != null) - observers.add(parentOwner.getCreatureObject()); + observers.add(parentOwner); return observers; } - public Set getObservers() { - Player owner = getOwner(); - SWGObject parent = getParent(); - if (parent == null) - return getObservers(owner, this, true); - while (parent.getParent() != null) - parent = parent.getParent(); - return parent.getObservers(owner, this, true); + public Set getObservers() { + return getObservers(true); } - private Set getObservers(boolean useAware) { - return getObservers(getOwner(), this, useAware); - } - - /** - * Gets all observers within the tree. Called from the head node of the tree to search. In - * addition to the contained objects, it also searches aware objects if useAware is true - * - * @param owner the original owner that should be ignored from the search - * @param original the original object that should be ignored from the search - * @param useAware TRUE if aware objects should be searched - * @return a set with unique SWGObjects that have unique player owners - */ - private Set getObservers(Player owner, SWGObject original, boolean useAware) { - Set observers = new HashSet<>(); - synchronized (containedObjects) { - addObserversToSet(containedObjects, observers, owner, original); - } - if (useAware) { - addObserversToSet(getObjectsAware(), observers, owner, original); - } - return observers; - } - - private void addObserversToSet(Collection nearby, Set observers, Player owner, SWGObject original) { - for (SWGObject aware : nearby) { - if (!aware.isVisible(original)) - continue; - if (checkAwareIsObserver(aware, owner, original)) - observers.add(aware); - else - observers.addAll(aware.getObservers(owner, original, false)); - } - } - - private boolean checkAwareIsObserver(SWGObject aware, Player owner, SWGObject original) { - if (!(aware instanceof CreatureObject)) - return false; - Player awareOwner = aware.getOwner(); - if (awareOwner == null || awareOwner.equals(owner)) - return false; - if (awareOwner.getPlayerState() != PlayerState.ZONED_IN) - return false; - return ((CreatureObject) aware).isLoggedInPlayer(); + private Set getObservers(boolean useAware) { + if (!useAware) + return awareness.getChildObservers(); + return awareness.getObservers(); } public void sendObserversAndSelf(Packet ... packets) { @@ -750,9 +761,8 @@ public abstract class SWGObject extends BaselineObject implements Comparable observers = getObservers(); - for (SWGObject observer : observers) { - observer.getOwner().sendPacket(packets); + for (Player observer : getObservers()) { + observer.sendPacket(packets); } } @@ -772,97 +782,6 @@ public abstract class SWGObject extends BaselineObject implements Comparable withinRange) { - updateObjectAwareness(withinRange, true, true); - } - - public void updateObjectAwareness(Set withinRange, boolean updateAware, boolean sendUpdate) { - Set oldAware; - synchronized (objectsAware) { - oldAware = new HashSet<>(objectsAware); - } - for (SWGObject obj : withinRange) { - if (!oldAware.contains(obj)) - obj.awarenessCreate(this, updateAware, sendUpdate); - } - for (SWGObject obj : oldAware) { - if (!withinRange.contains(obj)) - obj.awarenessDestroy(this, updateAware, sendUpdate); - } - } - - private void awarenessCreate(SWGObject obj, boolean updateAware, boolean sendUpdate) { - internalAwarenessCreate(obj, updateAware, sendUpdate); - obj.internalAwarenessCreate(this, updateAware, sendUpdate); - } - - private void awarenessDestroy(SWGObject obj, boolean updateAware, boolean sendUpdate) { - internalAwarenessDestroy(obj, updateAware, sendUpdate); - obj.internalAwarenessDestroy(this, updateAware, sendUpdate); - } - - private void internalAwarenessCreate(SWGObject obj, boolean updateAware, boolean sendUpdate) { - if (updateAware) { - synchronized (objectsAware) { - if (!objectsAware.add(obj)) - return; - } - } - if (!sendUpdate) - return; - Player owner = getOwner(); - if (owner != null && !owner.equals(obj.getOwner())) { - // Don't resend character baselines to the player every time they reenter awareness! - obj.createObject(owner); - } - - for (SWGObject create : getObservers(false)) - obj.createObject(create.getOwner()); - } - - private void internalAwarenessDestroy(SWGObject obj, boolean updateAware, boolean sendUpdate) { - if (updateAware) { - synchronized (objectsAware) { - if (!objectsAware.remove(obj)) - return; - } - } - if (!sendUpdate) - return; - Player owner = getOwner(); - if (owner != null && !owner.equals(obj.getOwner())) { - // Don't destroy the character of a player that's just left a building - obj.destroyObject(getOwner()); - } - for (SWGObject destroy : getObservers(false)) - obj.destroyObject(destroy.getOwner()); - } - - public void sendDataTransforms(DataTransform dt) { - UpdateTransformMessage transform = new UpdateTransformMessage(); - transform.setObjectId(getObjectId()); - transform.setX((short) (dt.getLocation().getX() * 4 + 0.5)); - transform.setY((short) (dt.getLocation().getY() * 4 + 0.5)); - transform.setZ((short) (dt.getLocation().getZ() * 4 + 0.5)); - transform.setUpdateCounter(updateCounter.incrementAndGet()); - transform.setDirection(dt.getMovementAngle()); - transform.setSpeed((byte) (dt.getSpeed()+0.5)); - transform.setLookAtYaw((byte) (dt.getLookAtYaw() * 16)); - transform.setUseLookAtYaw(dt.isUseLookAtYaw()); - sendObservers(transform); - } - - public void sendParentDataTransforms(DataTransformWithParent ptm) { - UpdateTransformWithParentMessage transform = new UpdateTransformWithParentMessage(ptm.getCellId(), getObjectId()); - transform.setLocation(ptm.getLocation()); - transform.setUpdateCounter(updateCounter.incrementAndGet()); - transform.setDirection(ptm.getMovementAngle()); - transform.setSpeed((byte) ptm.getSpeed()); - transform.setLookDirection((byte) (ptm.getLookAtYaw() * 16)); - transform.setUseLookDirection(ptm.isUseLookAtYaw()); - sendObservers(transform); - } - protected void createChildrenObjects(Player target) { createChildrenObjects(target, false); } @@ -873,14 +792,11 @@ public abstract class SWGObject extends BaselineObject implements Comparable sentObjects = new ArrayList<>(); - // First create the objects in the slots synchronized (slots) { for (SWGObject slotObject : slots.values()) { - if (slotObject != null && !sentObjects.contains(slotObject)) { + if (slotObject != null) { slotObject.createObject(target, ignoreSnapshotChecks); - sentObjects.add(slotObject); } } } @@ -888,7 +804,7 @@ public abstract class SWGObject extends BaselineObject implements Comparable { stream.addAscii(e.getKey()); @@ -1027,7 +943,7 @@ public abstract class SWGObject extends BaselineObject implements Comparable attributes.put(stream.getAscii(), stream.getAscii())); stream.getList((i) -> SWGObjectFactory.create(stream).moveToContainer(this)); } @@ -1042,7 +958,7 @@ public abstract class SWGObject extends BaselineObject implements Comparable attributes.put(stream.getAscii(), stream.getAscii())); stream.getList((i) -> SWGObjectFactory.create(stream).moveToContainer(this)); } diff --git a/src/resources/objects/awareness/Aware.java b/src/resources/objects/awareness/Aware.java new file mode 100644 index 00000000..6ec9ea31 --- /dev/null +++ b/src/resources/objects/awareness/Aware.java @@ -0,0 +1,179 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; +import resources.player.Player; +import resources.player.PlayerState; + +class Aware { + + private final SWGObject object; + private final List awareness; + private final Object parentMutex; + private Aware parent; + + public Aware(SWGObject obj) { + if (obj == null) + throw new NullPointerException("Object cannot be null!"); + this.object = obj; + this.awareness = new ArrayList<>(); + this.parentMutex = new Object(); + } + + public SWGObject getObject() { + return object; + } + + public void setParent(Aware parent) { + this.parent = parent; + } + + public boolean add(Aware a) { + boolean success = internalAdd(a); + return a.internalAdd(this) && success; + } + + public boolean remove(Aware a) { + boolean success = internalRemove(a); + return a.internalRemove(this) && success; + } + + public boolean contains(SWGObject obj) { + synchronized (awareness) { + for (Aware a : awareness) { + if (a.getObject().equals(obj)) + return true; + } + return false; + } + } + + public void clear() { + List aware; + synchronized (awareness) { + aware = new ArrayList<>(awareness); + } + for (Aware a : aware) + remove(a); + } + + public Set getAware() { +// Set aware = new AwarenessSet(awareness.size()); + Set aware = new HashSet<>(awareness.size()); + synchronized (awareness) { + for (Aware a : awareness) { + aware.add(a.getObject()); + } + } + Aware parent; + synchronized (parentMutex) { + parent = this.parent; + } + if (parent != null) + aware.addAll(parent.getAware()); + return aware; + } + + public Set getObservers() { + Player owner = object.getOwner(); + synchronized (parentMutex) { + if (parent == null) + return getObservers(owner, getObject(), true); + return getSuperParent().getObservers(owner, getObject(), true); + } + } + + private boolean internalAdd(Aware binding) { + synchronized (awareness) { + return awareness.add(binding); + } + } + + private boolean internalRemove(Aware binding) { + synchronized (awareness) { + return awareness.remove(binding); + } + } + + private Aware getParent() { + synchronized (parentMutex) { + return parent; + } + } + + private Aware getSuperParent() { + Aware tmpParent = getParent(); + Aware ret = tmpParent; + if (tmpParent == null) + return ret; + while ((tmpParent = tmpParent.getParent()) != null) { + ret = tmpParent; + } + return ret; + } + + private Set getObservers(Player owner, SWGObject original, boolean useAware) { + Set observers = new HashSet<>(); + addObserversToSet(getObject().getContainedObjects(), observers, owner, original); + if (useAware) { + addObserversToSet(getAware(), observers, owner, original); + } + return observers; + } + + public static void addObserversToSet(Collection nearby, Collection observers, Player owner, SWGObject original) { + for (SWGObject aware : nearby) { + if (!aware.isVisible(original)) + continue; + if (isObserver(aware, owner, original)) + observers.add(aware.getOwner()); + else + addObserversToSet(aware.getContainedObjects(), observers, owner, original); + } + } + + public static boolean isObserver(SWGObject aware, Player owner, SWGObject original) { + if (!(aware instanceof CreatureObject)) + return false; + Player awareOwner = aware.getOwner(); + if (awareOwner == null || awareOwner.equals(owner)) + return false; + if (awareOwner.getPlayerState() != PlayerState.ZONED_IN) + return false; + return ((CreatureObject) aware).isLoggedInPlayer(); + } + +} diff --git a/src/resources/objects/awareness/AwarenessHandler.java b/src/resources/objects/awareness/AwarenessHandler.java new file mode 100644 index 00000000..625c5f6c --- /dev/null +++ b/src/resources/objects/awareness/AwarenessHandler.java @@ -0,0 +1,156 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import resources.Location; +import resources.Terrain; +import resources.buildout.BuildoutArea; +import resources.objects.SWGObject; +import resources.objects.awareness.TerrainMap.TerrainMapCallback; +import resources.player.Player; +import resources.server_info.Log; + +public class AwarenessHandler { + + private static final Location GONE_LOCATION = new Location(0, 0, 0, null); + + private final Map terrains; + + public AwarenessHandler(TerrainMapCallback callback) { + terrains = new HashMap<>(Terrain.getTerrainCount()); + loadTerrainMaps(callback); + } + + public void close() { + for (TerrainMap map : terrains.values()) { + map.stop(); + } + } + + private void loadTerrainMaps(TerrainMapCallback callback) { + for (Terrain t : Terrain.values()) { + TerrainMap map = new TerrainMap(t); + map.start(); + map.setCallback(callback); + terrains.put(t, map); + } + } + + public void moveObject(SWGObject obj, Location requestedLocation) { + if (obj.getParent() != null) + obj.moveToContainer(null); + // Adjust to server coordinates + BuildoutArea area = obj.getBuildoutArea(); + if (area != null) + requestedLocation = area.adjustLocation(requestedLocation); + // Remove from previous awareness + Terrain oldTerrain = obj.getTerrain(); + Terrain newTerrain = requestedLocation.getTerrain(); + if (oldTerrain != newTerrain && oldTerrain != null) { + obj.clearObjectsAware(); // Moving to GONE + TerrainMap oldTerrainMap = getTerrainMap(oldTerrain); + if (oldTerrainMap != null) + oldTerrainMap.removeFromMap(obj); + } + // Add to new awareness + TerrainMap map = getTerrainMap(newTerrain); + if (map != null) { + map.moveWithinMap(obj, requestedLocation); + } else if (!requestedLocation.equals(GONE_LOCATION)) { + Log.e(this, "Unknown terrain: %s", newTerrain); + } + } + + public void moveObject(SWGObject obj, SWGObject parent, Location requestedLocation) { + Set oldAware = obj.getObjectsAware(); + Set oldObservers = obj.getObservers(); + if (obj.getParent() != parent) + obj.moveToContainer(parent); + // Remove from previous awareness + TerrainMap oldTerrain = getTerrainMap(obj); + if (oldTerrain != null) + oldTerrain.removeWithoutUpdate(obj); + // Update location + obj.setLocation(requestedLocation); + // Update awareness + TerrainMap map = getTerrainMap(parent); + if (map != null) { + map.moveToParent(obj, parent); + handleUpdateAwarenessManual(obj, oldAware, oldObservers, obj.getObjectsAware(), obj.getObservers()); + } else if (!requestedLocation.equals(GONE_LOCATION)) { + Log.e(this, "Unknown terrain: %s", requestedLocation.getTerrain()); + } + } + + public void disappearObject(SWGObject obj, boolean disappearObjects, boolean disappearCustom) { + moveObject(obj, GONE_LOCATION); + if (disappearObjects) + obj.clearObjectsAware(); + if (disappearCustom) + obj.clearCustomAware(true); + } + + private void handleUpdateAwarenessManual(SWGObject obj, Set oldAware, Set oldObservers, Set newAware, Set newObservers) { + // Create obj for all new observers + for (Player nowObserver : newObservers) { + if (!oldObservers.remove(nowObserver)) + obj.createObject(nowObserver); + } + // Destroy obj for all old observers + for (Player oldObserver : oldObservers) + obj.destroyObject(oldObserver); + + Player owner = obj.getOwner(); + if (owner == null) + return; + // Create new aware objects for obj + for (SWGObject nowAware : newAware) { + if (!oldAware.remove(nowAware)) + nowAware.createObject(owner); + } + // Destroy old aware objects for obj + for (SWGObject old : oldAware) + old.destroyObject(owner); + } + + private TerrainMap getTerrainMap(SWGObject object) { + Terrain t = object.getTerrain(); + if (t == null) + return null; + return getTerrainMap(t); + } + + private TerrainMap getTerrainMap(Terrain t) { + return terrains.get(t); + } + +} diff --git a/src/resources/objects/awareness/DataTransformHandler.java b/src/resources/objects/awareness/DataTransformHandler.java new file mode 100644 index 00000000..76664fe9 --- /dev/null +++ b/src/resources/objects/awareness/DataTransformHandler.java @@ -0,0 +1,104 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import resources.Location; +import resources.buildout.BuildoutArea; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; +import network.packets.swg.zone.UpdateTransformMessage; +import network.packets.swg.zone.UpdateTransformWithParentMessage; + +public class DataTransformHandler { + + private final SpeedCheckHandler speedCheckHandler; + + public DataTransformHandler() { + speedCheckHandler = new SpeedCheckHandler(); + } + + public boolean handleMove(SWGObject obj, Location requestedLocation, double speed, int update) { + if (obj instanceof CreatureObject && ((CreatureObject) obj).isLoggedInPlayer()) + speedCheckHandler.moveObjectSpeedChecks((CreatureObject) obj, requestedLocation); + BuildoutArea area = obj.getBuildoutArea(); + if (area != null) + requestedLocation = area.adjustLocation(requestedLocation); + obj.sendObservers(createTransform(obj, requestedLocation, speed, update)); + return true; + } + + public boolean handleMove(SWGObject obj, SWGObject parent, Location requestedLocation, double speed, int update) { + if (obj instanceof CreatureObject && ((CreatureObject) obj).isLoggedInPlayer()) + speedCheckHandler.moveObjectSpeedChecks((CreatureObject) obj, parent, requestedLocation); + obj.sendObservers(createTransform(obj, parent.getObjectId(), requestedLocation, speed, update)); + return true; + } + + private UpdateTransformMessage createTransform(SWGObject obj, Location requestedLocation, double speed, int update) { + UpdateTransformMessage transform = new UpdateTransformMessage(); + transform.setObjectId(obj.getObjectId()); + transform.setX((short) (requestedLocation.getX() * 4 + 0.5)); + transform.setY((short) (requestedLocation.getY() * 4 + 0.5)); + transform.setZ((short) (requestedLocation.getZ() * 4 + 0.5)); + transform.setUpdateCounter(obj.getNextUpdateCount()); + transform.setDirection(getMovementAngle(requestedLocation)); + transform.setSpeed((byte) (speed+0.5)); + transform.setLookAtYaw((byte) 0); + transform.setUseLookAtYaw(false); + return transform; + } + + private UpdateTransformWithParentMessage createTransform(SWGObject obj, long cellId, Location requestedLocation, double speed, int update) { + UpdateTransformWithParentMessage transform = new UpdateTransformWithParentMessage(cellId, obj.getObjectId()); + transform.setLocation(requestedLocation); + transform.setUpdateCounter(obj.getNextUpdateCount()); + transform.setDirection(getMovementAngle(requestedLocation)); + transform.setSpeed((byte) (speed + 0.5)); + transform.setLookDirection((byte) 0); // lookAtYaw * 16 + transform.setUseLookDirection(false); + return transform; + } + + private byte getMovementAngle(Location requestedLocation) { + byte movementAngle = (byte) 0.0f; + double wOrient = requestedLocation.getOrientationW(); + double yOrient = requestedLocation.getOrientationY(); + double sq = Math.sqrt(1 - (wOrient*wOrient)); + + if (sq != 0) { + if (requestedLocation.getOrientationW() > 0 && requestedLocation.getOrientationY() < 0) { + wOrient *= -1; + yOrient *= -1; + } + movementAngle = (byte) ((yOrient / sq) * (2 * Math.acos(wOrient) / 0.06283f)); + } + + return movementAngle; + } + +} diff --git a/src/resources/objects/awareness/ObjectAware.java b/src/resources/objects/awareness/ObjectAware.java new file mode 100644 index 00000000..64fc9d6e --- /dev/null +++ b/src/resources/objects/awareness/ObjectAware.java @@ -0,0 +1,132 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.HashSet; +import java.util.Set; + +import resources.objects.SWGObject; +import resources.player.Player; + +public class ObjectAware { + + private final SWGObject object; + private final Aware objectsAware; + private final Aware customAware; + + public ObjectAware(SWGObject obj) { + this.object = obj; + this.objectsAware = new Aware(obj); + this.customAware = new Aware(obj); + } + + public void setParent(ObjectAware parent) { + if (parent == null) { + objectsAware.setParent(null); + customAware.setParent(null); + } else { + objectsAware.setParent(parent.getRawObjectsAware()); + customAware.setParent(parent.getRawCustomAware()); + } + } + + public boolean addObjectAware(ObjectAware aware) { + return objectsAware.add(aware.getRawObjectsAware()); + } + + public boolean removeObjectAware(ObjectAware aware) { + return objectsAware.remove(aware.getRawObjectsAware()); + } + + public boolean addCustomAware(ObjectAware aware) { + return customAware.add(aware.getRawCustomAware()); + } + + public boolean removeCustomAware(ObjectAware aware) { + return customAware.remove(aware.getRawCustomAware()); + } + + public boolean isObjectAware(SWGObject aware) { + return objectsAware.contains(aware); + } + + public boolean isCustomAware(SWGObject aware) { + return customAware.contains(aware); + } + + public void clearObjectsAware() { + objectsAware.clear(); + } + + public void clearCustomAware() { + customAware.clear(); + } + + public Set getAware() { + Set aware = objectsAware.getAware(); + aware.addAll(customAware.getAware()); + return aware; + } + + public Set getObservers() { + Set observers = objectsAware.getObservers(); + observers.addAll(customAware.getObservers()); + return observers; + } + + public Set getChildObservers() { + Set observers = new HashSet<>(); + Aware.addObserversToSet(object.getContainedObjects(), observers, object.getOwner(), object); + return observers; + } + + public Set getObjectsAware() { + return objectsAware.getAware(); + } + + public Set getCustomAware() { + return customAware.getAware(); + } + + public Set getObjectObservers() { + return objectsAware.getObservers(); + } + + public Set getCustomObservers() { + return customAware.getObservers(); + } + + private Aware getRawObjectsAware() { + return objectsAware; + } + + private Aware getRawCustomAware() { + return customAware; + } + +} diff --git a/src/resources/objects/awareness/SpeedCheckHandler.java b/src/resources/objects/awareness/SpeedCheckHandler.java new file mode 100644 index 00000000..d25b9e53 --- /dev/null +++ b/src/resources/objects/awareness/SpeedCheckHandler.java @@ -0,0 +1,84 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import resources.Location; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; + +class SpeedCheckHandler { + + public SpeedCheckHandler() { + + } + + public void moveObjectSpeedChecks(CreatureObject obj, Location requestedLocation) { + double time = ((CreatureObject) obj).getTimeSinceLastTransform() / 1000; + obj.updateLastTransformTime(); + Location l = obj.getWorldLocation(); + if (isSpeeding(obj, l, requestedLocation, time)) { + double angle = getMovementAngle(l, requestedLocation); + requestedLocation.setX(l.getX()+obj.getMovementScale()*7.3*time*Math.cos(angle)); + requestedLocation.setZ(l.getZ()+obj.getMovementScale()*7.3*time*Math.sin(angle)); + } + } + + public void moveObjectSpeedChecks(CreatureObject obj, SWGObject parent, Location requestedLocation) { + double time = ((CreatureObject) obj).getTimeSinceLastTransform() / 1000; + obj.updateLastTransformTime(); + Location l = obj.getWorldLocation(); + Location requestedWorld = new Location(requestedLocation.getX(), 0, requestedLocation.getZ(), parent.getTerrain()); + requestedWorld.translateLocation(parent.getWorldLocation()); + if (isSpeeding(obj, l, requestedWorld, time)) { + double angle = getMovementAngle(l, requestedWorld); + requestedLocation.setX(requestedLocation.getX()+obj.getMovementScale()*7.3*time*invertNormalizedValue(Math.cos(angle))); + requestedLocation.setZ(requestedLocation.getZ()+obj.getMovementScale()*7.3*time*invertNormalizedValue(Math.sin(angle))); + } + } + + private boolean isSpeeding(CreatureObject obj, Location nWorld, Location newLocation, double time) { + return Math.sqrt(square(nWorld.getX()-nWorld.getX()) + square(nWorld.getZ()-newLocation.getZ())) / time > obj.getMovementScale()*7.3; + } + + private double getMovementAngle(Location oldLocation, Location newLocation) { + if (newLocation.getX() == oldLocation.getX()) + return Math.PI; + return Math.atan2(newLocation.getZ()-oldLocation.getZ(), newLocation.getX()-oldLocation.getX()) + Math.PI; + } + + private double invertNormalizedValue(double x) { + if (x < 0) + return -1 - x; + return 1-x; + } + + private double square(double x) { + return x * x; + } + +} diff --git a/src/resources/objects/awareness/TerrainMap.java b/src/resources/objects/awareness/TerrainMap.java new file mode 100644 index 00000000..8a935e9d --- /dev/null +++ b/src/resources/objects/awareness/TerrainMap.java @@ -0,0 +1,179 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import resources.Location; +import resources.Terrain; +import resources.callback.CallbackManager; +import resources.objects.SWGObject; +import resources.server_info.Log; + +public class TerrainMap { + + private static final int CHUNK_COUNT_ACROSS = 16; + private static final double MIN_X = -8192; + private static final double MIN_Z = -8192; + private static final double MAP_WIDTH = 16384; + private static final double CHUNK_WIDTH = MAP_WIDTH / CHUNK_COUNT_ACROSS; + + private final CallbackManager callbackManager; + private final TerrainMapChunk [][] chunks; + private final Map objectChunk; + + public TerrainMap(Terrain t) { + callbackManager = new CallbackManager<>("terrain-map-"+t.name(), 1); + chunks = new TerrainMapChunk[CHUNK_COUNT_ACROSS][CHUNK_COUNT_ACROSS]; + objectChunk = new HashMap<>(); + for (int z = 0; z < chunks.length; z++) { + for (int x = 0; x < chunks[z].length; x++) { + double chunkStartX = MIN_X+x*CHUNK_WIDTH; + double chunkStartZ = MIN_Z+z*CHUNK_WIDTH; + chunks[z][x] = new TerrainMapChunk(chunkStartX, chunkStartZ, chunkStartX+CHUNK_WIDTH, chunkStartZ+CHUNK_WIDTH); + } + } + } + + public void start() { + callbackManager.start(); + } + + public void stop() { + callbackManager.stop(); + } + + public void setCallback(TerrainMapCallback callback) { + callbackManager.setCallback(callback); + } + + public void moveWithinMap(SWGObject obj, Location loc) { + obj.setLocation(loc); + if (isInAwareness(obj)) { + move(obj); + update(obj); + callbackManager.callOnEach((call) -> call.onMoveSuccess(obj)); + } else { + callbackManager.callOnEach((call) -> call.onMoveFailure(obj)); + } + } + + public void moveToParent(SWGObject obj, SWGObject parent) { + SWGObject superParent = obj.getSuperParent(); + if (superParent != null) { + superParent.removeObjectAware(obj); // Just in case + update(superParent); + } + } + + public void removeWithoutUpdate(SWGObject obj) { + remove(obj); + } + + public void removeFromMap(SWGObject obj) { + remove(obj); + update(obj); + } + + private void move(SWGObject obj) { + TerrainMapChunk chunk = objectChunk.get(obj.getObjectId()); + if (chunk != null) { + if (!chunk.isWithinBounds(obj)) + chunk.removeObject(obj); + else + return; + } + chunk = getChunk(obj.getX(), obj.getZ()); + if (chunk == null) { + Log.e("TerrainMap", "Null Chunk! Location: (%.3f, %.3f) Object: %s", obj.getX(), obj.getZ(), obj); + return; + } + chunk.addObject(obj); + objectChunk.put(obj.getObjectId(), chunk); + } + + private void remove(SWGObject obj) { + TerrainMapChunk chunk = objectChunk.remove(obj.getObjectId()); + if (chunk != null) + chunk.removeObject(obj); + } + + private void update(SWGObject obj) { + Set aware = getNearbyAware(obj); + Set prevAware = obj.getObjectsAware(); + for (SWGObject inRange : aware) { + if (!prevAware.remove(inRange)) { + callbackManager.callOnEach((call) -> call.onWithinRange(obj, inRange)); + } + } + for (SWGObject outRange : prevAware) { + callbackManager.callOnEach((call) -> call.onOutOfRange(obj, outRange)); + } + } + + private Set getNearbyAware(SWGObject obj) { + Set aware = new HashSet<>(); + int startX = Math.max(calculateIndex(obj.getX()) - 1, 0); + int startZ = Math.max(calculateIndex(obj.getZ()) - 1, 0); + int endX = Math.min(startX+3, CHUNK_COUNT_ACROSS); + int endZ = Math.min(startZ+3, CHUNK_COUNT_ACROSS); + for (int z = startZ; z < endZ; z++) { + for (int x = startX; x < endX; x++) { + aware.addAll(chunks[z][x].getWithinAwareness(obj)); + } + } + return aware; + } + + private boolean isInAwareness(SWGObject obj) { + return obj.getParent() == null; + } + + private TerrainMapChunk getChunk(double x, double z) { + int xInd = calculateIndex(x); + int zInd = calculateIndex(z); + if (xInd < 0 || zInd < 0 || xInd >= CHUNK_COUNT_ACROSS || zInd >= CHUNK_COUNT_ACROSS) + return null; + return chunks[zInd][xInd]; + } + + private int calculateIndex(double x) { + return (int) ((x+8192)/16384*CHUNK_COUNT_ACROSS); + } + + public interface TerrainMapCallback { + void onWithinRange(SWGObject obj, SWGObject inRange); + void onOutOfRange(SWGObject obj, SWGObject outRange); + void onMoveSuccess(SWGObject obj); + void onMoveFailure(SWGObject obj); + } + +} diff --git a/src/resources/objects/awareness/TerrainMapChunk.java b/src/resources/objects/awareness/TerrainMapChunk.java new file mode 100644 index 00000000..494b9361 --- /dev/null +++ b/src/resources/objects/awareness/TerrainMapChunk.java @@ -0,0 +1,102 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import resources.Location; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; + +class TerrainMapChunk { + + private final Set objects; + private final double minX; + private final double minZ; + private final double maxX; + private final double maxZ; + + public TerrainMapChunk(double minX, double minZ, double maxX, double maxZ) { + objects = new HashSet<>(); // There will be some expanding and shrinking + this.minX = minX; + this.minZ = minZ; + this.maxX = maxX; + this.maxZ = maxZ; + } + + public void addObject(SWGObject obj) { + synchronized (objects) { + objects.add(obj); + } + } + + public void removeObject(SWGObject obj) { + synchronized (objects) { + objects.remove(obj); + } + } + + public boolean containsObject(SWGObject obj) { + synchronized (objects) { + return objects.contains(obj); + } + } + + public boolean isWithinBounds(SWGObject obj) { + double x = obj.getX(); + double z = obj.getZ(); + return minX <= x && minZ <= z && maxX >= x && maxZ >= z; + } + + public List getWithinAwareness(SWGObject obj) { + double loadRange = obj.getLoadRange(); + synchronized (objects) { + List withinRange = new LinkedList<>(); + Location objLocation = obj.getWorldLocation(); + for (SWGObject test : objects) { + if (isValidWithinRange(obj, test, objLocation, loadRange)) + withinRange.add(test); + } + return withinRange; + } + } + + private boolean isValidWithinRange(SWGObject obj, SWGObject inRange, Location objLocation, double range) { + if (obj.equals(inRange)) + return false; + if (inRange instanceof CreatureObject && ((CreatureObject) inRange).isLoggedOutPlayer()) + return false; + if (!inRange.getWorldLocation().isWithinFlatDistance(objLocation, Math.max(range, inRange.getLoadRange()))) + return false; + return true; + } + +} diff --git a/src/resources/objects/buildouts/TerrainBuildoutLoader.java b/src/resources/objects/buildouts/TerrainBuildoutLoader.java index a2ba07e3..383d06bb 100644 --- a/src/resources/objects/buildouts/TerrainBuildoutLoader.java +++ b/src/resources/objects/buildouts/TerrainBuildoutLoader.java @@ -91,7 +91,7 @@ class TerrainBuildoutLoader { buildoutRow.load(areaTable.getRow(row), crcTable); SWGObject object = createObject(buildoutRow); object.setClassification(ObjectClassification.BUILDOUT); - object.setLoadRange(buildoutRow.getRadius()); + object.setPrefLoadRange(buildoutRow.getRadius()); object.setBuildoutAreaId(area.getIndex()); setCellInformation(object, buildoutRow.getCellIndex()); addObject(area.getName(), object, buildoutRow.getContainerId()); diff --git a/src/resources/objects/buildouts/TerrainSnapshotLoader.java b/src/resources/objects/buildouts/TerrainSnapshotLoader.java index d02252f6..ddb920ed 100644 --- a/src/resources/objects/buildouts/TerrainSnapshotLoader.java +++ b/src/resources/objects/buildouts/TerrainSnapshotLoader.java @@ -79,7 +79,7 @@ public class TerrainSnapshotLoader { SWGObject object = createObject(templates, node); object.setClassification(ObjectClassification.SNAPSHOT); object.setBuildoutAreaId(-1); - object.setLoadRange(node.getRadius()); + object.setPrefLoadRange(node.getRadius()); setCellInformation(object, node.getCellIndex()); addObject(object, node.getContainerId()); diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 8d5b5fee..cafff7e6 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -29,6 +29,7 @@ package resources.objects.creature; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Set; import network.packets.swg.zone.UpdatePostureMessage; @@ -81,6 +82,7 @@ public class CreatureObject extends TangibleObject { public CreatureObject(long objectId) { super(objectId, BaselineType.CREO); initBaseAttributes(); + setPrefLoadRange(200); } @Override @@ -125,7 +127,7 @@ public class CreatureObject extends TangibleObject { public void addSkill(String skillName) { synchronized(skills) { - if(skills.add(skillName)) { + if (skills.add(skillName)) { skills.sendDeltaMessage(this); } } @@ -135,8 +137,8 @@ public class CreatureObject extends TangibleObject { return skills.contains(skillName); } - public SWGSet getSkills() { - return skills; + public Set getSkills() { + return Collections.unmodifiableSet(skills); } public int getCashBalance() { diff --git a/src/resources/objects/tangible/TangibleObject.java b/src/resources/objects/tangible/TangibleObject.java index 54896bf5..95cd2550 100644 --- a/src/resources/objects/tangible/TangibleObject.java +++ b/src/resources/objects/tangible/TangibleObject.java @@ -341,7 +341,7 @@ public class TangibleObject extends SWGObject { protected void sendBaselines(Player target) { super.sendBaselines(target); - new FactionIntent(this, FactionIntentType.FLAGUPDATE).broadcast(); +// new FactionIntent(this, FactionIntentType.FLAGUPDATE).broadcast(); } @Override diff --git a/src/services/chat/ChatManager.java b/src/services/chat/ChatManager.java index 2432e996..1ac5c2cb 100644 --- a/src/services/chat/ChatManager.java +++ b/src/services/chat/ChatManager.java @@ -321,14 +321,10 @@ public class ChatManager extends Manager { String senderName = ChatAvatar.getFromPlayer(sender).getName(); // Notify observers of the chat message - for (SWGObject aware : actor.getObservers()) { - Player owner = aware.getOwner(); - if (owner == null) - continue; - + for (Player owner : actor.getObservers()) { PlayerObject awareGhost = owner.getPlayerObject(); if (!awareGhost.isIgnored(senderName)) - aware.getOwner().sendPacket(new SpatialChat(aware.getObjectId(), message)); + owner.sendPacket(new SpatialChat(owner.getCreatureObject().getObjectId(), message)); } } diff --git a/src/services/experience/SkillService.java b/src/services/experience/SkillService.java index c89b95e2..cd8c1bc8 100644 --- a/src/services/experience/SkillService.java +++ b/src/services/experience/SkillService.java @@ -29,10 +29,12 @@ package services.experience; import intents.experience.SkillBoxGrantedIntent; import intents.network.GalacticPacketIntent; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; + import network.packets.Packet; import network.packets.swg.zone.object_controller.ChangeRoleIconChoice; import resources.client_info.ClientFactory; diff --git a/src/services/faction/FactionService.java b/src/services/faction/FactionService.java index 49addcaf..90bbe1a1 100644 --- a/src/services/faction/FactionService.java +++ b/src/services/faction/FactionService.java @@ -36,13 +36,11 @@ import network.packets.swg.zone.UpdatePvpStatusMessage; import network.packets.swg.zone.chat.ChatSystemMessage; import network.packets.swg.zone.chat.ChatSystemMessage.SystemChatType; import intents.FactionIntent; -import intents.PlayerEventIntent; import resources.PvpFaction; import resources.PvpFlag; import resources.PvpStatus; import resources.control.Intent; import resources.control.Service; -import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; import resources.player.Player; @@ -195,21 +193,13 @@ public final class FactionService extends Service { private void handleFlagChange(TangibleObject object) { Player objOwner = object.getOwner(); - - for (SWGObject o : object.getObservers()) { - if (!(o instanceof TangibleObject)) - continue; - TangibleObject observer = (TangibleObject) o; - Player obsOwner = observer.getOwner(); - + for (Player obsOwner : object.getObservers()) { + TangibleObject observer = obsOwner.getCreatureObject(); int pvpBitmask = getPvpBitmask(object, observer); - if (objOwner != null) - // Send the PvP information about this observer to the owner + if (objOwner != null) // Send the PvP information about this observer to the owner objOwner.sendPacket(createPvpStatusMessage(observer, observer.getPvpFlags() | pvpBitmask)); - if (obsOwner != null) - // Send the pvp information about the owner to this observer - obsOwner.sendPacket(createPvpStatusMessage(object, object.getPvpFlags() | pvpBitmask)); + obsOwner.sendPacket(createPvpStatusMessage(object, object.getPvpFlags() | pvpBitmask)); } } diff --git a/src/services/galaxy/travel/TravelService.java b/src/services/galaxy/travel/TravelService.java index 8aeea47c..8d3f2184 100644 --- a/src/services/galaxy/travel/TravelService.java +++ b/src/services/galaxy/travel/TravelService.java @@ -377,7 +377,6 @@ public class TravelService extends Service { grantTickets(purchaser, nearestPoint, destinationPoint, roundTrip); handlePurchaseFinish(purchaserOwner, purchaser, ticketPrice, newCashBalance, newBankBalance); - } private void handlePurchaseFinish(Player purchaserOwner, CreatureObject purchaser, int ticketPrice, long newCashBalance, long newBankBalance) { diff --git a/src/services/objects/ClientBuildoutService.java b/src/services/objects/ClientBuildoutService.java index ba9fff32..e4ef47d4 100644 --- a/src/services/objects/ClientBuildoutService.java +++ b/src/services/objects/ClientBuildoutService.java @@ -65,6 +65,12 @@ public class ClientBuildoutService extends Service { + "objects.radius, objects.cell_index " + "FROM objects " + "ORDER BY buildout_id ASC"; + private static final String GET_CLIENT_OBJECTS_BY_AREA_SQL = "SELECT objects.id, objects.snapshot, objects.area_id, objects.template_crc, objects.container_id, " + + "objects.x, objects.y, objects.z, objects.orientation_x, objects.orientation_y, objects.orientation_z, objects.orientation_w, " + + "objects.radius, objects.cell_index " + + "FROM objects " + + "WHERE objects.area_id = ? " + + "ORDER BY buildout_id ASC"; private static final String GET_ADDITIONAL_OBJECTS_SQL = "SELECT terrain, template, x, y, z, heading, cell_id, radius, building_name " + "FROM additional_buildouts WHERE active = 1"; private static final String GET_BUILDING_INFO_SQL = "SELECT object_id FROM buildings WHERE building_id = ?"; @@ -108,7 +114,6 @@ public class ClientBuildoutService extends Service { objects = new HashSet<>(); } catch (SQLException e) { objects = new HashSet<>(); - e.printStackTrace(); Log.e(this, e); } double loadTime = (System.nanoTime() - startLoad) / 1E6; @@ -116,27 +121,23 @@ public class ClientBuildoutService extends Service { return objects; } + public Collection loadClientObjectsByArea(int areaId) { + try { + if (areasById.isEmpty()) + loadAreas(new ArrayList<>()); + return loadObjects(areaId); + } catch (SQLException e) { + Log.e(this, e); + return new ArrayList<>(); + } + } + private Collection loadObjects() throws SQLException { Map objects = new Hashtable<>(); try (CrcDatabase strings = new CrcDatabase()) { try (RelationalServerData data = RelationalServerFactory.getServerData("buildout/buildouts.db", "objects")) { try (ResultSet set = data.executeQuery(GET_CLIENT_OBJECTS_SQL)) { - set.setFetchSize(4*1024); - BuildoutArea area = null; - ObjectInformation info = new ObjectInformation(set); - int prevAreaId = Integer.MAX_VALUE; - int areaId = 0; - while (set.next()) { - areaId = info.getAreaIdNoLoad(); - if (prevAreaId != areaId) { - area = areasById.get(areaId); - prevAreaId = areaId; - } - if (area == null) - continue; - info.load(strings); - createObject(objects, area, info); - } + readAllObjects(objects, strings, set); } } } @@ -145,6 +146,39 @@ public class ClientBuildoutService extends Service { return ret; } + private Collection loadObjects(int areaId) throws SQLException { + Map objects = new Hashtable<>(); + try (CrcDatabase strings = new CrcDatabase()) { + try (RelationalServerData data = RelationalServerFactory.getServerData("buildout/buildouts.db", "objects")) { + PreparedStatement ps = data.prepareStatement(GET_CLIENT_OBJECTS_BY_AREA_SQL); + ps.setInt(1, areaId); + try (ResultSet set = ps.executeQuery()) { + readAllObjects(objects, strings, set); + } + } + } + return new ArrayList<>(objects.values()); + } + + private void readAllObjects(Map objects, CrcDatabase strings, ResultSet set) throws SQLException { + set.setFetchSize(4*1024); + BuildoutArea area = null; + ObjectInformation info = new ObjectInformation(set); + int prevAreaId = Integer.MAX_VALUE; + int areaId = 0; + while (set.next()) { + areaId = info.getAreaIdNoLoad(); + if (prevAreaId != areaId) { + area = areasById.get(areaId); + prevAreaId = areaId; + } + if (area == null) + continue; + info.load(strings); + createObject(objects, area, info); + } + } + private Collection getAdditionalObjects(Map buildouts) throws SQLException { Map objects = new Hashtable<>(); try (CrcDatabase strings = new CrcDatabase()) { @@ -166,7 +200,7 @@ public class ClientBuildoutService extends Service { l.setTerrain(area.getTerrain()); obj.setLocation(l); obj.setClassification(info.isSnapshot() ? ObjectClassification.SNAPSHOT : ObjectClassification.BUILDOUT); - obj.setLoadRange(info.getRadius()); + obj.setPrefLoadRange(info.getRadius()); checkCell(obj, info.getCell()); checkChild(objects, obj, info.getContainer()); objects.put(obj.getObjectId(), obj); @@ -183,7 +217,7 @@ public class ClientBuildoutService extends Service { l.setHeading(set.getFloat("heading")); obj.setLocation(l); obj.setClassification(ObjectClassification.BUILDOUT); - obj.setLoadRange(set.getFloat("radius")); + obj.setPrefLoadRange(set.getFloat("radius")); checkParent(buildouts, obj, set.getString("building_name"), set.getInt("cell_id")); objects.put(obj.getObjectId(), obj); } catch (NullPointerException e) { @@ -200,9 +234,12 @@ public class ClientBuildoutService extends Service { Log.e(this, "Unknown building name: %s", buildingName); return; } - SWGObject buildingUncasted = objects.get(set.getLong("object_id")); + long buildingId = set.getLong("object_id"); + if (buildingId == 0) + return; + SWGObject buildingUncasted = objects.get(buildingId); if (buildingUncasted == null) { - Log.e(this, "Building not found in map: %s / %d", buildingName, set.getLong("object_id")); + Log.e(this, "Building not found in map: %s / %d", buildingName, buildingId); return; } if (!(buildingUncasted instanceof BuildingObject)) { diff --git a/src/services/objects/ObjectAwareness.java b/src/services/objects/ObjectAwareness.java index 06174744..adb09bea 100644 --- a/src/services/objects/ObjectAwareness.java +++ b/src/services/objects/ObjectAwareness.java @@ -36,49 +36,42 @@ import intents.object.ObjectCreatedIntent; import intents.object.ObjectTeleportIntent; import intents.player.PlayerTransformedIntent; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import main.ProjectSWG; import network.packets.Packet; import network.packets.swg.zone.CmdSceneReady; import network.packets.swg.zone.object_controller.DataTransform; import network.packets.swg.zone.object_controller.DataTransformWithParent; import resources.Location; -import resources.Terrain; -import resources.buildout.BuildoutArea; import resources.control.Intent; import resources.control.Service; import resources.objects.SWGObject; -import resources.objects.SWGObject.ObjectClassification; +import resources.objects.awareness.AwarenessHandler; +import resources.objects.awareness.DataTransformHandler; +import resources.objects.awareness.TerrainMap.TerrainMapCallback; import resources.objects.creature.CreatureObject; -import resources.objects.quadtree.QuadTree; -import resources.objects.tangible.TangibleObject; import resources.player.Player; import resources.server_info.Log; -public class ObjectAwareness extends Service { +public class ObjectAwareness extends Service implements TerrainMapCallback { - private static final double AWARE_RANGE = 1024; - private static final int DEFAULT_LOAD_RANGE = (int) square(200); // Squared for speed - private static final Location GONE_LOCATION = new Location(0, 0, 0, null); - - private final Map > quadTree; - private final Map locations; + private final AwarenessHandler awarenessHandler; + private final DataTransformHandler dataTransformHandler; public ObjectAwareness() { - quadTree = new HashMap<>(); - locations = new HashMap<>(); + awarenessHandler = new AwarenessHandler(this); + dataTransformHandler = new DataTransformHandler(); + registerForIntent(PlayerEventIntent.TYPE); registerForIntent(ObjectCreatedIntent.TYPE); registerForIntent(DestroyObjectIntent.TYPE); registerForIntent(ObjectTeleportIntent.TYPE); registerForIntent(GalacticPacketIntent.TYPE); registerForIntent(MoveObjectIntent.TYPE); - loadQuadTree(); + } + + @Override + public boolean terminate() { + awarenessHandler.close(); + return super.terminate(); } @Override @@ -113,6 +106,27 @@ public class ObjectAwareness extends Service { } } + @Override + public void onWithinRange(SWGObject obj, SWGObject inRange) { + obj.addObjectAware(inRange); + } + + @Override + public void onOutOfRange(SWGObject obj, SWGObject outRange) { + obj.removeObjectAware(outRange); + } + + @Override + public void onMoveSuccess(SWGObject obj) { + + } + + @Override + public void onMoveFailure(SWGObject obj) { + Log.e(this, "Move failure! %s", obj); + obj.clearObjectsAware(); + } + private void handlePlayerEventIntent(PlayerEventIntent pei) { Player p = pei.getPlayer(); CreatureObject creature = p.getCreatureObject(); @@ -120,14 +134,17 @@ public class ObjectAwareness extends Service { return; switch (pei.getEvent()) { case PE_DISAPPEAR: - moveToLocation(creature, creature.getParent(), null); + disappearObject(creature, true, true); break; case PE_DESTROYED: creature.setOwner(null); break; case PE_ZONE_IN_SERVER: creature.resetAwareness(); - moveToLocation(creature, creature.getParent(), creature.getLocation()); + if (creature.getParent() == null) + moveObject(creature, creature.getLocation()); + else + moveObject(creature, creature.getParent(), creature.getLocation()); p.sendPacket(new CmdSceneReady()); break; default: @@ -135,24 +152,16 @@ public class ObjectAwareness extends Service { } } - private void loadQuadTree() { - for (Terrain t : Terrain.values()) { - quadTree.put(t, new QuadTree(16, -8192, -8192, 8192, 8192)); - } - } - private void handleObjectCreatedIntent(ObjectCreatedIntent oci) { SWGObject object = oci.getObject(); - if (isInAwareness(object)) - moveToLocation(object, object.getLocation()); + if (object.getParent() == null) + moveObject(object, object.getLocation()); } private void handleDestroyObjectIntent(DestroyObjectIntent doi) { SWGObject obj = doi.getObject(); - obj.setLocation(GONE_LOCATION); + disappearObject(obj, true, true); obj.moveToContainer(null); - obj.clearAware(); - obj.clearCustomAware(true); } private void processObjectTeleportIntent(ObjectTeleportIntent oti) { @@ -160,9 +169,9 @@ public class ObjectAwareness extends Service { Player owner = object.getOwner(); object.setLocation(oti.getNewLocation()); if (oti.getParent() != null) { - moveToLocation(object, oti.getParent(), null); + moveObject(object, oti.getParent(), null); } else { - moveToLocation(object, null); + moveObject(object, null); } if (object instanceof CreatureObject && ((CreatureObject) object).isLoggedInPlayer()) new RequestZoneInIntent(owner, (CreatureObject) object, false).broadcast(); @@ -173,228 +182,56 @@ public class ObjectAwareness extends Service { if (packet instanceof DataTransform) { DataTransform trans = (DataTransform) packet; SWGObject obj = i.getObjectManager().getObjectById(trans.getObjectId()); - if (obj instanceof CreatureObject) - moveObject((CreatureObject) obj, trans); + if (obj instanceof CreatureObject) { + Location requestedLocation = new Location(trans.getLocation()); + requestedLocation.setTerrain(obj.getTerrain()); + moveObjectWithTransform(obj, requestedLocation, trans.getSpeed(), trans.getUpdateCounter()); + } } else if (packet instanceof DataTransformWithParent) { - DataTransformWithParent transformWithParent = (DataTransformWithParent) packet; - SWGObject object = i.getObjectManager().getObjectById(transformWithParent.getObjectId()); - SWGObject parent = i.getObjectManager().getObjectById(transformWithParent.getCellId()); - if (object instanceof CreatureObject) - moveObject((CreatureObject) object, parent, transformWithParent); + DataTransformWithParent trans = (DataTransformWithParent) packet; + SWGObject obj = i.getObjectManager().getObjectById(trans.getObjectId()); + SWGObject parent = i.getObjectManager().getObjectById(trans.getCellId()); + if (obj instanceof CreatureObject) { + Location requestedLocation = new Location(trans.getLocation()); + requestedLocation.setTerrain(obj.getTerrain()); + moveObjectWithTransform(obj, parent, requestedLocation, trans.getSpeed(), trans.getUpdateCounter()); + } } } private void processMoveObjectIntent(MoveObjectIntent i) { if (i.getParent() != null) - processMoveObjectIntentParent(i); + moveObjectWithTransform(i.getObject(), i.getNewLocation(), i.getSpeed(), i.getUpdateCounter()); else - processMoveObjectIntentNoParent(i); + moveObjectWithTransform(i.getObject(), i.getParent(), i.getNewLocation(), i.getSpeed(), i.getUpdateCounter()); } - private void processMoveObjectIntentNoParent(MoveObjectIntent i) { - SWGObject obj = i.getObject(); - Location newLocation = i.getNewLocation(); - BuildoutArea area = obj.getBuildoutArea(); - if (area != null) - newLocation = area.adjustLocation(newLocation); - moveToLocation(obj, newLocation); - if (area != null) - newLocation = area.readjustLocation(newLocation); - DataTransform transform = new DataTransform(obj.getObjectId()); - transform.setTimestamp((int) ProjectSWG.getGalacticTime()); - transform.setLocation(newLocation); - transform.setLookAtYaw(0); - transform.setUseLookAtYaw(false); - transform.setSpeed((float) i.getSpeed()); - transform.setUpdateCounter(i.getUpdateCounter()); - obj.sendDataTransforms(transform); + private void moveObject(SWGObject obj, Location requestedLocation) { + awarenessHandler.moveObject(obj, requestedLocation); } - private void processMoveObjectIntentParent(MoveObjectIntent i) { - SWGObject obj = i.getObject(); - moveToLocation(obj, i.getParent(), i.getNewLocation()); - DataTransformWithParent transform = new DataTransformWithParent(obj.getObjectId()); - transform.setTimestamp((int) ProjectSWG.getGalacticTime()); - transform.setLocation(i.getNewLocation()); - transform.setLookAtYaw(0); - transform.setUseLookAtYaw(false); - transform.setSpeed((float) i.getSpeed()); - transform.setUpdateCounter(i.getUpdateCounter()); - obj.sendParentDataTransforms(transform); + private void moveObject(SWGObject obj, SWGObject parent, Location requestedLocation) { + awarenessHandler.moveObject(obj, parent, requestedLocation); } - private void moveObject(CreatureObject obj, DataTransform transform) { - transform = new DataTransform(transform); - Location newLocation = transform.getLocation(); - newLocation.setTerrain(obj.getTerrain()); - moveObjectSpeedChecks(obj, newLocation); - BuildoutArea area = obj.getBuildoutArea(); - if (area != null) - newLocation = area.adjustLocation(newLocation); - new PlayerTransformedIntent(obj, obj.getParent(), null, obj.getLocation(), newLocation).broadcast(); - moveToLocation(obj, newLocation); - if (area != null) - newLocation = area.readjustLocation(newLocation); - obj.sendDataTransforms(transform); - } - - private void moveObject(CreatureObject obj, SWGObject parent, DataTransformWithParent transformWithParent) { - Location newLocation = transformWithParent.getLocation(); - newLocation.setTerrain(obj.getTerrain()); - if (parent == null) { - Log.e("ObjectManager", "Could not find parent for transform! Cell: %d Object: %s", transformWithParent.getCellId(), obj); + private void moveObjectWithTransform(SWGObject obj, Location requestedLocation, double speed, int update) { + if (!dataTransformHandler.handleMove(obj, requestedLocation, speed, update)) return; - } - moveObjectSpeedChecks(obj, parent, newLocation); - new PlayerTransformedIntent((CreatureObject) obj, obj.getParent(), parent, obj.getLocation(), newLocation).broadcast(); - moveToLocation(obj, parent, newLocation); - obj.sendParentDataTransforms(transformWithParent); + if (obj instanceof CreatureObject && ((CreatureObject) obj).isLoggedInPlayer()) + new PlayerTransformedIntent((CreatureObject) obj, obj.getParent(), null, obj.getLocation(), requestedLocation).broadcast(); + moveObject(obj, requestedLocation); } - private void moveObjectSpeedChecks(CreatureObject obj, Location newLocation) { - double time = ((CreatureObject) obj).getTimeSinceLastTransform() / 1000; - obj.updateLastTransformTime(); - Location l = obj.getWorldLocation(); - double speed = Math.sqrt(square(l.getX()-newLocation.getX()) + square(l.getZ()-newLocation.getZ())) / time; - if (speed > obj.getMovementScale()*7.3) { - double angle = (newLocation.getX() == l.getX() ? 0 : Math.atan2(newLocation.getZ()-l.getZ(), newLocation.getX()-l.getX())); - newLocation.setX(l.getX()+obj.getMovementScale()*7.3*time*Math.cos(angle)); - newLocation.setZ(l.getZ()+obj.getMovementScale()*7.3*time*Math.sin(angle)); - } - } - - private void moveObjectSpeedChecks(CreatureObject obj, SWGObject parent, Location newLocation) { - double time = ((CreatureObject) obj).getTimeSinceLastTransform() / 1000; - obj.updateLastTransformTime(); - Location l = obj.getWorldLocation(); - Location nWorld = new Location(newLocation.getX(), 0, newLocation.getZ(), parent.getTerrain()); - nWorld.translateLocation(parent.getWorldLocation()); - double speed = Math.sqrt(square(l.getX()-nWorld.getX()) + square(l.getZ()-nWorld.getZ())) / time; - if (speed > obj.getMovementScale()*7.3) { - double angle = (nWorld.getX() == l.getX() ? 0 : Math.atan2(nWorld.getZ()-l.getZ(), nWorld.getX()-l.getX())) + Math.PI; - newLocation.setX(newLocation.getX()+obj.getMovementScale()*7.3*time*invertNormalizedValue(Math.cos(angle))); - newLocation.setZ(newLocation.getZ()+obj.getMovementScale()*7.3*time*invertNormalizedValue(Math.sin(angle))); - } - } - - private void moveToLocation(SWGObject object, Location l) { - moveToLocation(object, null, l); - } - - private void moveToLocation(SWGObject object, SWGObject parent, Location l) { - remove(object); - if (parent != object.getParent()) - object.moveToContainer(parent); - - if (l != null) { - object.setLocation(l); - if (isInAwareness(object)) { - add(object); - update(object); - } - } else { - object.clearAware(); - } - } - - /** - * Adds the specified object to the awareness quadtree - * @param object the object to add - */ - private void add(SWGObject object) { - Location l = object.getLocation(); - if (isInMap(object)) { - synchronized (locations) { - locations.put(object.getObjectId(), l); - } - } - QuadTree tree = getTree(l); - synchronized (tree) { - tree.put(l.getX(), l.getZ(), object); - } - } - - /** - * Removes the specified object from the awareness quadtree - * @param object the object to remove - */ - private boolean remove(SWGObject object) { - Location l; - if (isInMap(object)) { - synchronized (locations) { - l = locations.remove(object.getObjectId()); - } - } else { - l = object.getLocation(); - } - if (l == null) - return false; - QuadTree tree = getTree(l); - synchronized (tree) { - return tree.remove(l.getX(), l.getZ(), object); - } - } - - /** - * Updates the specified object after it has been moved, or to verify that - * the awareness is up to date - * @param obj the object to update - */ - private void update(SWGObject obj) { - if (!obj.isGenerated()) + private void moveObjectWithTransform(SWGObject obj, SWGObject parent, Location requestedLocation, double speed, int update) { + if (!dataTransformHandler.handleMove(obj, parent, requestedLocation, speed, update)) return; - Location l = obj.getWorldLocation(); - Set objectAware = new HashSet(); - QuadTree tree = getTree(l); - List range; - synchronized (tree) { - range = tree.getWithinRange(l.getX(), l.getZ(), AWARE_RANGE); - } - for (SWGObject inRange : range) { - if (isValidInRange(obj, inRange, l)) - objectAware.add(inRange); - } - obj.updateObjectAwareness(objectAware); + if (obj instanceof CreatureObject && ((CreatureObject) obj).isLoggedInPlayer()) + new PlayerTransformedIntent((CreatureObject) obj, obj.getParent(), parent, obj.getLocation(), requestedLocation).broadcast(); + moveObject(obj, parent, requestedLocation); } - private QuadTree getTree(Location l) { - return quadTree.get(l.getTerrain()); - } - - private boolean isValidInRange(SWGObject obj, SWGObject inRange, Location objLoc) { - if (inRange.getObjectId() == obj.getObjectId()) - return false; - if (inRange instanceof CreatureObject && ((CreatureObject) inRange).isLoggedOutPlayer()) - return false; - int distSquared = distanceSquared(objLoc, inRange.getWorldLocation()); - int loadSquared = (int) (square(inRange.getLoadRange()) + 0.5); - return (loadSquared != 0 || distSquared <= DEFAULT_LOAD_RANGE) && (loadSquared == 0 || distSquared <= loadSquared); - } - - private boolean isInAwareness(SWGObject object) { - if (object instanceof CreatureObject && ((CreatureObject) object).isLoggedOutPlayer()) - return false; - if (object.getTerrain() == null) - return false; - return object.getParent() == null && object instanceof TangibleObject; - } - - private boolean isInMap(SWGObject object) { - return object.getClassification() == ObjectClassification.GENERATED; - } - - private double invertNormalizedValue(double x) { - if (x < 0) - return -1 - x; - return 1-x; - } - - private int distanceSquared(Location l1, Location l2) { - return (int) (square(l1.getX()-l2.getX()) + square(l1.getY()-l2.getY()) + square(l1.getZ()-l2.getZ()) + 0.5); - } - - private static double square(double x) { - return x * x; + private void disappearObject(SWGObject obj, boolean disappearObjects, boolean disappearCustom) { + awarenessHandler.disappearObject(obj, disappearObjects, disappearCustom); } } \ No newline at end of file diff --git a/src/services/objects/ObjectManager.java b/src/services/objects/ObjectManager.java index d2e70781..b8e65426 100644 --- a/src/services/objects/ObjectManager.java +++ b/src/services/objects/ObjectManager.java @@ -183,7 +183,6 @@ public class ObjectManager extends Manager { @Override public boolean terminate() { synchronized (database) { - database.traverse((obj) -> obj.setOwner(null)); database.close(); } return super.terminate(); diff --git a/src/services/objects/StaticItemService.java b/src/services/objects/StaticItemService.java index 8737e847..a378bb4c 100644 --- a/src/services/objects/StaticItemService.java +++ b/src/services/objects/StaticItemService.java @@ -235,7 +235,6 @@ public final class StaticItemService extends Service { private boolean unique; private String conditionString; private int volume; - private String requiredLevel; // TODO bio-link private final String itemName; private final String iffTemplate; @@ -262,7 +261,6 @@ public final class StaticItemService extends Service { int hitPoints = resultSet.getInt("hit_points"); conditionString = String.format("%d/%d", hitPoints, hitPoints); volume = resultSet.getInt("volume"); - requiredLevel = String.valueOf(resultSet.getShort("required_level")); // load type-specific attributes return loadTypeAttributes(resultSet); @@ -293,8 +291,7 @@ public final class StaticItemService extends Service { object.addAttribute("unique", "1"); object.addAttribute("condition", conditionString); object.addAttribute("volume", String.valueOf(volume)); - object.addAttribute("required_combat_level", requiredLevel); - + // apply type-specific attributes applyTypeAttributes(object); } @@ -319,6 +316,7 @@ public final class StaticItemService extends Service { private final Map mods; // skillmods/statmods private String requiredProfession; + private String requiredLevel; private String requiredFaction; private String buffName; // TODO species restriction @@ -331,6 +329,7 @@ public final class StaticItemService extends Service { @Override protected boolean loadTypeAttributes(ResultSet resultSet) throws SQLException { + requiredLevel = String.valueOf(resultSet.getShort("required_level")); requiredProfession = resultSet.getString("required_profession"); if (requiredProfession.equals("-")) { // Ziggy: This value is not defined in any String Table File. @@ -338,7 +337,6 @@ public final class StaticItemService extends Service { } else { requiredProfession = "@ui_roadmap:title_" + requiredProfession; } - requiredFaction = resultSet.getString("required_faction"); if (requiredFaction.equals("-")) { @@ -383,6 +381,7 @@ public final class StaticItemService extends Service { @Override protected void applyTypeAttributes(SWGObject object) { object.addAttribute("class_required", requiredProfession); + object.addAttribute("required_combat_level", requiredLevel); object.addAttribute("faction_restriction", requiredFaction); // Apply the mods! @@ -397,6 +396,7 @@ public final class StaticItemService extends Service { private static final class ArmorAttributes extends WearableAttributes { + private String requiredLevel; private String armorCategory; private String kinetic, energy, elementals; private float protectionWeight; @@ -412,7 +412,7 @@ public final class StaticItemService extends Service { if (!wearableAttributesLoaded) { return false; } - + requiredLevel = String.valueOf(resultSet.getShort("required_level")); String armorType = resultSet.getString("armor_category"); protectionWeight = resultSet.getFloat("protection"); @@ -444,6 +444,7 @@ public final class StaticItemService extends Service { @Override protected void applyTypeAttributes(SWGObject object) { super.applyTypeAttributes(object); + object.addAttribute("required_combat_level", requiredLevel); object.addAttribute("armor_category", armorCategory); object.addAttribute("cat_armor_standard_protection.kinetic", kinetic); object.addAttribute("cat_armor_standard_protection.energy", energy); @@ -461,6 +462,7 @@ public final class StaticItemService extends Service { private static final class WeaponAttributes extends WearableAttributes { + private String requiredLevel; private WeaponType category; private DamageType damageTypeEnum; private DamageType elementalTypeEnum; @@ -525,6 +527,7 @@ public final class StaticItemService extends Service { return false; } + requiredLevel = String.valueOf(resultSet.getShort("required_level")); weaponCategory = "@obj_attr_n:wpn_category_" + String.valueOf(category.getNum()); damageType = resultSet.getString("damage_type"); damageTypeEnum = getDamageTypeForName(damageType); diff --git a/src/services/player/CharacterCreationService.java b/src/services/player/CharacterCreationService.java index a500d2c3..22829124 100644 --- a/src/services/player/CharacterCreationService.java +++ b/src/services/player/CharacterCreationService.java @@ -319,7 +319,6 @@ public class CharacterCreationService extends Service { playerObj.setAdminTag(player.getAccessLevel()); new ObjectCreatedIntent(creatureObj).broadcast(); - new SkillBoxGrantedIntent(create.getStartingPhase(), creatureObj).broadcast(); return creatureObj; } @@ -399,8 +398,9 @@ public class CharacterCreationService extends Service { creatureObj.setHeight(create.getHeight()); creatureObj.setName(create.getName()); creatureObj.setPvpFlags(PvpFlag.PLAYER); - creatureObj.getSkills().add("species_" + creatureObj.getRace().getSpecies()); creatureObj.setVolume(0x000F4240); + new SkillBoxGrantedIntent(create.getStartingPhase(), creatureObj).broadcast(); + new SkillBoxGrantedIntent("species_" + creatureObj.getRace().getSpecies(), creatureObj).broadcast(); WeaponObject defWeapon = (WeaponObject) createInventoryObject(objManager, creatureObj, "object/weapon/melee/unarmed/shared_unarmed_default_player.iff"); defWeapon.setMaxRange(5); @@ -413,9 +413,6 @@ public class CharacterCreationService extends Service { createInventoryObject(objManager, creatureObj, "object/tangible/inventory/shared_appearance_inventory.iff"); createInventoryObject(objManager, creatureObj, "object/tangible/bank/shared_character_bank.iff"); createInventoryObject(objManager, creatureObj, "object/tangible/mission_bag/shared_mission_bag.iff"); - - // Any character can perform the basic dance. - creatureObj.addAbility("startDance+basic"); } private void setPlayerObjectValues(PlayerObject playerObj, ClientCreateCharacter create) { diff --git a/test/resources/TestResources.java b/test/resources/TestResources.java index 8037a580..d4f5de53 100644 --- a/test/resources/TestResources.java +++ b/test/resources/TestResources.java @@ -35,6 +35,7 @@ import resources.collections.SWGListTest; import resources.collections.TestSWGBitSet; import resources.collections.TestSWGFlag; import resources.network.TestLittleEndianDataOutputStream; +import resources.objects.awareness.TestAwareness; import resources.objects.quadtree.TestQuadTree; import resources.persistable.TestPersistable; import resources.services.TestConfig; @@ -49,7 +50,8 @@ import resources.services.TestConfig; TestPersistable.class, TestConfig.class, TestQuaternion.class, - TestLittleEndianDataOutputStream.class + TestLittleEndianDataOutputStream.class, + TestAwareness.class }) public class TestResources { diff --git a/test/resources/objects/awareness/GenericCreatureObject.java b/test/resources/objects/awareness/GenericCreatureObject.java new file mode 100644 index 00000000..96973b46 --- /dev/null +++ b/test/resources/objects/awareness/GenericCreatureObject.java @@ -0,0 +1,60 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import network.packets.Packet; +import resources.objects.creature.CreatureObject; +import resources.objects.player.PlayerObject; +import resources.player.Player; +import resources.player.PlayerState; + +class GenericCreatureObject extends CreatureObject { + + private Player player; + + public GenericCreatureObject(long objectId) { + super(objectId); + player = new Player() { + public void sendPacket(Packet ... packets) { + // Nah + } + }; + player.setPlayerState(PlayerState.ZONED_IN); + setHasOwner(true); + setSlot("ghost", new PlayerObject(-objectId)); + } + + public void setHasOwner(boolean hasOwner) { + if (hasOwner) { + player.setCreatureObject(this); + } else { + player.setCreatureObject(null); + } + } + +} diff --git a/test/resources/objects/awareness/MapCallback.java b/test/resources/objects/awareness/MapCallback.java new file mode 100644 index 00000000..623ac9e2 --- /dev/null +++ b/test/resources/objects/awareness/MapCallback.java @@ -0,0 +1,160 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Assert; + +import resources.objects.SWGObject; +import resources.objects.awareness.TerrainMap.TerrainMapCallback; + +class MapCallback implements TerrainMapCallback { + + private final BlockingCounter withinRange = new BlockingCounter(); + private final BlockingCounter outOfRange = new BlockingCounter(); + private final BlockingCounter successfulMove = new BlockingCounter(); + private final BlockingCounter failedMove = new BlockingCounter(); + + @Override + public void onWithinRange(SWGObject obj, SWGObject inRange) { + withinRange.increment(); + } + + @Override + public void onOutOfRange(SWGObject obj, SWGObject outRange) { + outOfRange.increment(); + } + + @Override + public void onMoveSuccess(SWGObject obj) { + successfulMove.increment(); + } + + @Override + public void onMoveFailure(SWGObject obj) { + failedMove.increment(); + } + + public void set(int withinRange, int outOfRange, int successful, int failed) { + this.withinRange.set(withinRange); + this.outOfRange.set(outOfRange); + this.successfulMove.set(successful); + this.failedMove.set(failed); + } + + public int getWithinRange() { + return withinRange.get(); + } + + public int getOutOfRange() { + return outOfRange.get(); + } + + public int getSuccess() { + return successfulMove.get(); + } + + public int getFailed() { + return failedMove.get(); + } + + public void waitAndTest(int withinRange, int outOfRange, int successful, int failed, long timeout) { + waitFor(withinRange, outOfRange, successful, failed, timeout); + testAssert(withinRange, outOfRange, successful, failed); + } + + public void waitFor(int withinRange, int outOfRange, int successful, int failed, long timeout) { + long start = System.nanoTime(); + this.withinRange.waitUntil(withinRange, timeout); + this.outOfRange.waitUntil(outOfRange, timeout-(long)((System.nanoTime()-start)/1E6)); + this.successfulMove.waitUntil(successful, timeout-(long)((System.nanoTime()-start)/1E6)); + this.failedMove.waitUntil(failed, timeout-(long)((System.nanoTime()-start)/1E6)); + } + + public void testAssert(int withinRange, int outOfRange, int successful, int failed) { + Assert.assertEquals("TEST-WITHIN-RANGE", withinRange, this.withinRange.get()); + Assert.assertEquals("TEST-OUT-OF-RANGE", outOfRange, this.outOfRange.get()); + Assert.assertEquals("TEST-SUCCESSFUL-MOVE", successful, this.successfulMove.get()); + Assert.assertEquals("TEST-FAILED-MOVE", failed, this.failedMove.get()); + } + + private static class BlockingCounter { + + private final Object mutex; + private final AtomicInteger integer; + + public BlockingCounter() { + mutex = new Object(); + integer = new AtomicInteger(0); + } + + public boolean waitUntil(int value, long timeout) { + long start = System.nanoTime(); + while (timeout > 0 && integer.get() < value && waitForChange(timeout)) { + timeout -= (long) ((System.nanoTime()-start)/1E6); + start = System.nanoTime(); + if (integer.get() >= value) + return true; + } + return false; + } + + public boolean waitForChange(long timeout) { + synchronized (mutex) { + int initial = integer.get(); + try { + mutex.wait(timeout); + } catch (InterruptedException e) { + return false; + } + return initial != integer.get(); + } + } + + public void increment() { + integer.incrementAndGet(); + synchronized (mutex) { + mutex.notifyAll(); + } + } + + public int get() { + return integer.get(); + } + + public void set(int i) { + integer.set(i); + synchronized (mutex) { + mutex.notifyAll(); + } + } + + } + +} diff --git a/test/resources/objects/awareness/TestAware.java b/test/resources/objects/awareness/TestAware.java new file mode 100644 index 00000000..f4dacab7 --- /dev/null +++ b/test/resources/objects/awareness/TestAware.java @@ -0,0 +1,131 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import resources.objects.SWGObject; +import resources.objects.cell.CellObject; +import resources.player.Player; + +@RunWith(JUnit4.class) +public class TestAware { + + @Test + public void testAdd() { + GenericCreatureObject creatureA = new GenericCreatureObject(1); + GenericCreatureObject creatureB = new GenericCreatureObject(2); + Aware a = new Aware(creatureA); + Aware b = new Aware(creatureB); + Assert.assertTrue(a.add(b)); + Assert.assertTrue(a.contains(creatureB)); + Assert.assertTrue(b.contains(creatureA)); + } + + @Test + public void testRemove() { + GenericCreatureObject creatureA = new GenericCreatureObject(1); + GenericCreatureObject creatureB = new GenericCreatureObject(2); + Aware a = new Aware(creatureA); + Aware b = new Aware(creatureB); + Assert.assertTrue(a.add(b)); + Assert.assertTrue(a.contains(creatureB)); + Assert.assertTrue(b.contains(creatureA)); + Assert.assertTrue(a.remove(b)); + Assert.assertFalse(a.contains(creatureB)); + Assert.assertFalse(b.contains(creatureA)); + } + + @Test + public void testAwareNoParent() { + GenericCreatureObject creatureA = new GenericCreatureObject(1); + GenericCreatureObject creatureB = new GenericCreatureObject(2); + Aware a = new Aware(creatureA); + Aware b = new Aware(creatureB); + Assert.assertTrue(a.add(b)); + Set aware = a.getAware(); + Assert.assertEquals(1, aware.size()); + Assert.assertTrue(aware.contains(creatureB)); + } + + @Test + public void testObserversNoParent() { + GenericCreatureObject creatureA = new GenericCreatureObject(1); + GenericCreatureObject creatureB = new GenericCreatureObject(2); + Aware a = new Aware(creatureA); + Aware b = new Aware(creatureB); + Assert.assertTrue(a.add(b)); + Assert.assertTrue(a.contains(creatureB)); + Assert.assertTrue(b.contains(creatureA)); + Set observers = a.getObservers(); + Assert.assertEquals(1, observers.size()); + Assert.assertTrue(observers.contains(creatureB.getOwner())); + } + + @Test + public void testAwareParent() { + GenericCreatureObject creatureA = new GenericCreatureObject(1); + GenericCreatureObject creatureB = new GenericCreatureObject(2); + GenericCreatureObject creatureC = new GenericCreatureObject(3); + Aware a = new Aware(creatureA); + Aware b = new Aware(creatureB); + Aware c = new Aware(creatureC); + c.setParent(a); + Assert.assertTrue(a.add(b)); + Set aware = c.getAware(); + Assert.assertEquals(1, aware.size()); + Assert.assertTrue(aware.contains(creatureB)); + } + + @Test + public void testObserversParent() { + CellObject cellA = new CellObject(1); + GenericCreatureObject creatureB = new GenericCreatureObject(2); + GenericCreatureObject creatureC = new GenericCreatureObject(3); + Aware a = new Aware(cellA); + Aware b = new Aware(creatureB); + Aware c = new Aware(creatureC); + cellA.addObject(creatureC); + c.setParent(a); + Assert.assertTrue(a.add(b)); + Assert.assertTrue(a.contains(creatureB)); + Assert.assertTrue(b.contains(cellA)); + Set observers = c.getObservers(); + Assert.assertEquals(1, observers.size()); + Assert.assertTrue(observers.contains(creatureB.getOwner())); + observers = b.getObservers(); + Assert.assertEquals(1, observers.size()); + Assert.assertTrue(observers.contains(creatureC.getOwner())); + } + +} diff --git a/test/resources/objects/awareness/TestAwareness.java b/test/resources/objects/awareness/TestAwareness.java new file mode 100644 index 00000000..d23be568 --- /dev/null +++ b/test/resources/objects/awareness/TestAwareness.java @@ -0,0 +1,43 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ + TestAware.class, + TestTerrainMapChunk.class, + TestTerrainMap.class, + TestAwarenessHandler.class +}) +public class TestAwareness { + +} diff --git a/test/resources/objects/awareness/TestAwarenessHandler.java b/test/resources/objects/awareness/TestAwarenessHandler.java new file mode 100644 index 00000000..1f131a59 --- /dev/null +++ b/test/resources/objects/awareness/TestAwarenessHandler.java @@ -0,0 +1,159 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import resources.Location; +import resources.Terrain; +import resources.objects.SWGObject; +import resources.objects.building.BuildingObject; +import resources.objects.creature.CreatureObject; +import services.objects.ClientBuildoutService; + +@RunWith(JUnit4.class) +public class TestAwarenessHandler { + + private static final Location CREATURE_LOCATION = new Location(3618, 5, -4801, Terrain.TATOOINE); + + private List eisleyObjects = new ArrayList<>(); + + @Before + public void initTatooine() { + ClientBuildoutService buildoutService = new ClientBuildoutService(); + Collection allObjects = buildoutService.loadClientObjectsByArea(843); // mos eisley's area id + eisleyObjects = allObjects.stream().filter((obj) -> { + return obj.getTerrain() == Terrain.TATOOINE && obj.getParent() == null; + }).collect(Collectors.toList()); + allObjects = buildoutService.loadClientObjectsByArea(-59); // general tatooine's area id + eisleyObjects.addAll(allObjects.stream().filter((obj) -> { + return obj.getTerrain() == Terrain.TATOOINE && obj.getParent() == null && CREATURE_LOCATION.isWithinFlatDistance(obj.getLocation(), 1024*Math.sqrt(2)); + }).collect(Collectors.toList())); + } + + @Test + public void testMoveSameAwareness() throws InterruptedException { + MapCallbackRealistic callback = new MapCallbackRealistic(); + AwarenessHandler awareness = new AwarenessHandler(callback); + GenericCreatureObject creature = new GenericCreatureObject(1); + List withinRange = new ArrayList<>(); + for (SWGObject obj : eisleyObjects) { + awareness.moveObject(obj, obj.getLocation()); + if (isValidWithinRange(creature, obj, CREATURE_LOCATION, Math.max(obj.getLoadRange(), creature.getLoadRange()))) + withinRange.add(obj); + } + callback.waitFor(0, 0, eisleyObjects.size(), 0, 1000); + callback.set(0, 0, 0, 0); + awareness.moveObject(creature, CREATURE_LOCATION); + callback.waitAndTest(withinRange.size(), 0, 1, 0, 1000); + callback.set(0, 0, 0, 0); + awareness.moveObject(creature, CREATURE_LOCATION); + callback.waitAndTest(0, 0, 1, 0, 1000); + } + + @Test + public void testMoveAway() throws InterruptedException { + MapCallbackRealistic callback = new MapCallbackRealistic(); + AwarenessHandler awareness = new AwarenessHandler(callback); + GenericCreatureObject creature = new GenericCreatureObject(1); + List withinRange = new ArrayList<>(); + for (SWGObject obj : eisleyObjects) { + awareness.moveObject(obj, obj.getLocation()); + if (isValidWithinRange(creature, obj, CREATURE_LOCATION, Math.max(obj.getLoadRange(), creature.getLoadRange()))) + withinRange.add(obj); + } + callback.waitFor(0, 0, eisleyObjects.size(), 0, 1000); + callback.set(0, 0, 0, 0); + awareness.moveObject(creature, CREATURE_LOCATION); + callback.waitAndTest(withinRange.size(), 0, 1, 0, 1000); + callback.set(0, 0, 0, 0); + awareness.moveObject(creature, new Location(0, 0, 0, Terrain.TATOOINE)); + callback.waitAndTest(0, withinRange.size(), 1, 0, 1000); + } + + @Test + public void testMoveIntoStarport() throws InterruptedException { + MapCallbackRealistic callback = new MapCallbackRealistic(); + AwarenessHandler awareness = new AwarenessHandler(callback); + GenericCreatureObject creature = new GenericCreatureObject(1); + List withinRange = new ArrayList<>(); + BuildingObject starport = null; + for (SWGObject obj : eisleyObjects) { + awareness.moveObject(obj, obj.getLocation()); + if (isValidWithinRange(creature, obj, CREATURE_LOCATION, Math.max(obj.getLoadRange(), creature.getLoadRange()))) { + withinRange.add(obj); + if (obj instanceof BuildingObject && obj.getTemplate().contains("starport")) + starport = (BuildingObject) obj; + } + } + Assert.assertNotNull("Starport is null!", starport); + callback.waitFor(0, 0, eisleyObjects.size(), 0, 1000); + callback.set(0, 0, 0, 0); + awareness.moveObject(creature, CREATURE_LOCATION); + callback.waitAndTest(withinRange.size(), 0, 1, 0, 1000); + callback.set(0, 0, 0, 0); + awareness.moveObject(creature, starport.getCellByNumber(1), new Location(0, 0, 0, Terrain.TATOOINE)); + callback.waitAndTest(0, 0, 0, 0, 1000); + } + + private boolean isValidWithinRange(SWGObject obj, SWGObject inRange, Location objLocation, double range) { + if (obj.equals(inRange)) + return false; + if (inRange instanceof CreatureObject && ((CreatureObject) inRange).isLoggedOutPlayer()) + return false; + if (!inRange.getWorldLocation().isWithinFlatDistance(objLocation, Math.max(range, inRange.getLoadRange()))) + return false; + return true; + } + + private static class MapCallbackRealistic extends MapCallback { + + @Override + public void onWithinRange(SWGObject obj, SWGObject inRange) { + super.onWithinRange(obj, inRange); + obj.addObjectAware(inRange); + } + + @Override + public void onOutOfRange(SWGObject obj, SWGObject outOfRange) { + super.onOutOfRange(obj, outOfRange); + obj.removeObjectAware(outOfRange); + } + + } + +} diff --git a/test/resources/objects/awareness/TestTerrainMap.java b/test/resources/objects/awareness/TestTerrainMap.java new file mode 100644 index 00000000..3ada82cc --- /dev/null +++ b/test/resources/objects/awareness/TestTerrainMap.java @@ -0,0 +1,156 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.Collection; +import java.util.List; +import java.util.Vector; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import resources.Location; +import resources.Terrain; +import resources.objects.SWGObject; +import resources.objects.cell.CellObject; +import resources.objects.creature.CreatureObject; +import resources.server_info.Log; +import services.objects.ClientBuildoutService; + +@RunWith(JUnit4.class) +public class TestTerrainMap { + + @Test + public void testWithoutParent() { + TerrainMap map = new TerrainMap(Terrain.TATOOINE); + MapCallback callback = new MapCallback(); + map.setCallback(callback); + try { + map.start(); + GenericCreatureObject objA = new GenericCreatureObject(1); + GenericCreatureObject objB = new GenericCreatureObject(2); + map.moveWithinMap(objA, new Location(0, 0, 0, Terrain.TATOOINE)); // 0 - Within Range, 1 - Successful Move + map.moveWithinMap(objB, new Location(5, 0, 5, Terrain.TATOOINE)); // 1 - Within Range, 1 - Successful Move + callback.waitAndTest(1, 0, 2, 0, 1000); + } finally { + map.stop(); + } + } + + @Test + public void testWithParent() { + TerrainMap map = new TerrainMap(Terrain.TATOOINE); + MapCallback callback = new MapCallback(); + map.setCallback(callback); + try { + map.start(); + GenericCreatureObject objA = new GenericCreatureObject(1); + GenericCreatureObject objB = new GenericCreatureObject(2); + CellObject cell = new CellObject(3); + map.moveWithinMap(cell, new Location(5, 0, 5, Terrain.TATOOINE)); // 0 - Within Range, 1 - Successful Move + map.moveWithinMap(objA, new Location(0, 0, 0, Terrain.TATOOINE)); // 1 - Within Range, 1 - Successful Move + objB.setLocation(new Location(5, 0, 5, Terrain.TATOOINE)); + objB.moveToContainer(cell); + map.moveToParent(objB, cell); // 0 - Within Range, 0 - Successful Move + callback.waitAndTest(2, 0, 2, 0, 1000); + } finally { + map.stop(); + } + } + + @Test + public void testMultiChunk() { + TerrainMap map = new TerrainMap(Terrain.TATOOINE); + MapCallback callback = new MapCallback(); + map.setCallback(callback); + try { + map.start(); + GenericCreatureObject objA = new GenericCreatureObject(1); + GenericCreatureObject objB = new GenericCreatureObject(2); + map.moveWithinMap(objA, new Location(5, 0, 5, Terrain.TATOOINE)); // 0 - Within Range, 1 - Successful Move + map.moveWithinMap(objB, new Location(-5, 0, -5, Terrain.TATOOINE)); // 1 - Within Range, 1 - Successful Move + callback.waitAndTest(1, 0, 2, 0, 1000); + } finally { + map.stop(); + } + } + + @Test + public void testTatooine() { + ClientBuildoutService buildoutService = new ClientBuildoutService(); + Collection allObjects = buildoutService.loadClientObjectsByArea(843); // mos eisley's area id + List tatObjects = allObjects.stream().filter((obj) -> { + return obj.getTerrain() == Terrain.TATOOINE && obj.getParent() == null; + }).collect(Collectors.toList()); + TerrainMap map = new TerrainMap(Terrain.TATOOINE); + List withinRange = new Vector<>(); + AtomicBoolean onlyWithinRange = new AtomicBoolean(true); + GenericCreatureObject creature = new GenericCreatureObject(1); + MapCallback callback = new MapCallback() { + public void onWithinRange(SWGObject obj, SWGObject inRange) { + if (obj.equals(creature) && !withinRange.remove(inRange)) { + Log.e(this, "Not in list: %s", inRange); + onlyWithinRange.set(false); + } + } + }; + map.setCallback(callback); + try { + map.start(); + creature.setName("testTatooine"); + Location creatureLocation = new Location(3500, 5, -4800, Terrain.TATOOINE); + for (SWGObject obj : tatObjects) { + double range = Math.min(1024*Math.sqrt(2), Math.max(obj.getLoadRange(), creature.getLoadRange())); + if (isValidWithinRange(creature, obj, creatureLocation, range)) + withinRange.add(obj); + map.moveWithinMap(obj, obj.getLocation()); + } + map.moveWithinMap(creature, creatureLocation); + callback.waitFor(0, 0, tatObjects.size()+1, 0, 1000); + Assert.assertEquals(0, withinRange.size()); + Assert.assertTrue("TEST-ONLY-WITHIN-RANGE", onlyWithinRange.get()); + } finally { + map.stop(); + } + } + + private boolean isValidWithinRange(SWGObject obj, SWGObject inRange, Location objLocation, double range) { + if (obj.equals(inRange)) + return false; + if (inRange instanceof CreatureObject && ((CreatureObject) inRange).isLoggedOutPlayer()) + return false; + if (!inRange.getWorldLocation().isWithinFlatDistance(objLocation, Math.max(range, inRange.getLoadRange()))) + return false; + return true; + } + +} diff --git a/test/resources/objects/awareness/TestTerrainMapChunk.java b/test/resources/objects/awareness/TestTerrainMapChunk.java new file mode 100644 index 00000000..12ec8a2f --- /dev/null +++ b/test/resources/objects/awareness/TestTerrainMapChunk.java @@ -0,0 +1,286 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package resources.objects.awareness; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runners.JUnit4; +import org.junit.runner.RunWith; + +import resources.Location; +import resources.Terrain; +import resources.objects.SWGObject; +import resources.objects.cell.CellObject; + +@RunWith(JUnit4.class) +public class TestTerrainMapChunk { + + /* + * Test Cases: + * A: Within A and B's load range in same chunk + * B: Within A's load range but not B's in same chunk + * C: Within A and B's load range in different chunks + * D: Within A's load range but not B's in different chunks + * E: Not within A's load range but within B's in same chunk + * F: Not within A's load range and not within B's in same chunk + * G: Not within A's load range but within B's in different chunks + * H: Not within A's load range and not within B's in different chunks + */ + + @Test + public void testContains() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = new GenericCreatureObject(1); + GenericCreatureObject objB = new GenericCreatureObject(2); + objA.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + objB.setLocation(new Location(100, 0, 0, Terrain.TATOOINE)); // 100m away + chunk.addObject(objA); + chunk.addObject(objB); + Assert.assertTrue("CONTAINS-OBJA", chunk.containsObject(objA)); + Assert.assertTrue("CONTAINS-OBJB", chunk.containsObject(objB)); + } + + @Test + public void testRemove() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = new GenericCreatureObject(1); + GenericCreatureObject objB = new GenericCreatureObject(2); + objA.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + objB.setLocation(new Location(100, 0, 0, Terrain.TATOOINE)); // 100m away + chunk.addObject(objA); + Assert.assertTrue("CONTAINS-OBJA", chunk.containsObject(objA)); + chunk.addObject(objB); + Assert.assertTrue("CONTAINS-OBJB", chunk.containsObject(objB)); + chunk.removeObject(objA); + Assert.assertFalse("CONTAINS-OBJA", chunk.containsObject(objA)); + chunk.removeObject(objB); + Assert.assertFalse("CONTAINS-OBJB", chunk.containsObject(objB)); + } + + @Test + public void testCaseA() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunk, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunk, 2, 50, 0, 0, 50); + testContains(chunk, objA, objB); + } + + @Test + public void testCaseB() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunk, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunk, 2, 50, 0, 0, 25); + testContains(chunk, objA, objB); + } + + @Test + public void testCaseC() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunkA, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunkB, 2, 50, 0, 0, 50); + testContains(chunkB, objA, objB); + } + + @Test + public void testCaseD() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunkA, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunkB, 2, 50, 0, 0, 25); + testContains(chunkB, objA, objB); + } + + @Test + public void testCaseE() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunk, 1, 0, 0, 0, 25); + GenericCreatureObject objB = createCreature(chunk, 2, 50, 0, 0, 50); + testContains(chunk, objA, objB); + } + + @Test + public void testCaseF() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunk, 1, 0, 0, 0, 25); + createCreature(chunk, 2, 50, 0, 0, 25); + testContains(chunk, objA); + } + + @Test + public void testCaseG() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunkA, 1, 0, 0, 0, 25); + GenericCreatureObject objB = createCreature(chunkB, 2, 50, 0, 0, 50); + testContains(chunkB, objA, objB); + } + + @Test + public void testCaseH() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + GenericCreatureObject objA = createCreature(chunkA, 1, 0, 0, 0, 25); + createCreature(chunkB, 2, 50, 0, 0, 25); + testContains(chunkB, objA); + } + + @Test + public void testCaseCellA() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + chunk.addObject(cell); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunk, 2, 50, 0, 0, 50); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 100, cell.getLoadRange(), 1E-7); + testContains(chunk, cell, objB); + } + + @Test + public void testCaseCellB() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + chunk.addObject(cell); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunk, 2, 50, 0, 0, 25); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 100, cell.getLoadRange(), 1E-7); + testContains(chunk, cell, objB); + } + + @Test + public void testCaseCellC() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + chunkA.addObject(cell); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunkB, 2, 50, 0, 0, 50); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 100, cell.getLoadRange(), 1E-7); + testContains(chunkB, cell, objB); + } + + @Test + public void testCaseCellD() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + chunkA.addObject(cell); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 100); + GenericCreatureObject objB = createCreature(chunkB, 2, 50, 0, 0, 25); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 100, cell.getLoadRange(), 1E-7); + testContains(chunkB, cell, objB); + } + + @Test + public void testCaseCellE() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 25); + GenericCreatureObject objB = createCreature(chunk, 2, 50, 0, 0, 50); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 25, cell.getLoadRange(), 1E-7); + testContains(chunk, cell, objB); + } + + @Test + public void testCaseCellF() { + TerrainMapChunk chunk = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 25); + createCreature(chunk, 2, 50, 0, 0, 25); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 25, cell.getLoadRange(), 1E-7); + testContains(chunk, cell); + } + + @Test + public void testCaseCellG() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + chunkA.addObject(cell); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 25); + GenericCreatureObject objB = createCreature(chunkB, 2, 50, 0, 0, 50); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 25, cell.getLoadRange(), 1E-7); + testContains(chunkB, cell, objB); + } + + @Test + public void testCaseCellH() { + TerrainMapChunk chunkA = new TerrainMapChunk(0, 0, 0, 0); + TerrainMapChunk chunkB = new TerrainMapChunk(0, 0, 0, 0); + CellObject cell = new CellObject(3); + cell.setPrefLoadRange(0); + chunkA.addObject(cell); + cell.setLocation(new Location(0, 0, 0, Terrain.TATOOINE)); + GenericCreatureObject objA = createCreature(null, 1, 0, 0, 0, 25); + createCreature(chunkB, 2, 50, 0, 0, 25); + cell.addObject(objA); + Assert.assertEquals("CELL-LOAD-RANGE", 25, cell.getLoadRange(), 1E-7); + testContains(chunkB, cell); + } + + private GenericCreatureObject createCreature(TerrainMapChunk chunk, long id, double x, double y, double z, double loadRange) { + GenericCreatureObject creature = new GenericCreatureObject(id); + creature.setLocation(new Location(x, y, z, Terrain.TATOOINE)); + creature.setPrefLoadRange(loadRange); + if (chunk != null) + chunk.addObject(creature); + return creature; + } + + private void testContains(TerrainMapChunk chunk, SWGObject src, SWGObject ... expected) { + List aware = chunk.getWithinAwareness(src); + Assert.assertEquals("TEST-SIZE", expected.length, aware.size()); + for (SWGObject e : expected) { + Assert.assertTrue("TEST-CONTAIN-"+e, aware.contains(e)); + } + } + +}