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));
+ }
+ }
+
+}