diff --git a/src/resources/commands/callbacks/SocialInternalCmdCallback.java b/src/resources/commands/callbacks/SocialInternalCmdCallback.java index d0a1332a..67308aa1 100644 --- a/src/resources/commands/callbacks/SocialInternalCmdCallback.java +++ b/src/resources/commands/callbacks/SocialInternalCmdCallback.java @@ -47,7 +47,7 @@ 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().getObjectsAware()) { + for (SWGObject aware : player.getCreatureObject().getObservers()) { if (aware.getOwner() != null) aware.getOwner().sendPacket(new PlayerEmote(aware.getObjectId(), emote)); } diff --git a/src/resources/objects/SWGObject.java b/src/resources/objects/SWGObject.java index 96fe94d2..fa05aef2 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -67,7 +67,7 @@ public abstract class SWGObject implements Serializable, Comparable { private final Map templateAttributes; private final BaselineType objectType; private ContainerPermissions containerPermissions; - private transient List objectsAware; + private transient Set objectsAware; private List > arrangement; private Player owner = null; @@ -92,7 +92,7 @@ public abstract class SWGObject implements Serializable, Comparable { public SWGObject(long objectId, BaselineType objectType) { this.objectId = objectId; this.location = new Location(); - this.objectsAware = new Vector(); + this.objectsAware = new HashSet(); this.slots = new HashMap<>(); this.containedObjects = Collections.synchronizedMap(new HashMap()); this.attributes = new LinkedHashMap(); @@ -103,7 +103,7 @@ public abstract class SWGObject implements Serializable, Comparable { private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); - objectsAware = new LinkedList(); + objectsAware = new HashSet(); } /** @@ -174,7 +174,7 @@ public abstract class SWGObject implements Serializable, Comparable { // TODO Slot occupation check, old version was not working properly, always returning SLOT_OCCUPIED // Get a pre-parent-removal list of the observers so we can send create/destroy/update messages - List oldObservers = getChildrenAwareness(); + Set oldObservers = getObservers(); // Remove this object from the old parent if one exists SWGObject oldParent = null; @@ -187,7 +187,7 @@ public abstract class SWGObject implements Serializable, Comparable { System.err.println("Failed adding " + this + " to " + container); // Observer notification - sendUpdatedContainment(oldObservers, new ArrayList<>(container.getChildrenAwareness())); + sendUpdatedContainment(oldObservers, container.getObservers()); Log.i("Container", "Moved %s from %s to %s", this, oldParent, container); return ContainerResult.SUCCESS; @@ -425,6 +425,17 @@ public abstract class SWGObject implements Serializable, Comparable { return location; } + public Location getWorldLocation() { + Location loc = new Location(location); + SWGObject parent = getParent(); + while (parent != null) { + Location l = parent.location; + loc.translatePosition(l.getX(), l.getY(), l.getZ()); // Have to access privately to avoid copies + parent = parent.getParent(); + } + return loc; + } + public String getName() { return objectName; } @@ -478,7 +489,14 @@ public abstract class SWGObject implements Serializable, Comparable { } public int getMaxContainerSize() { - return Integer.valueOf(templateAttributes.get("containerVolumeLimit").toString()); + Object volume = templateAttributes.get("containerVolumeLimit"); + if (volume == null) + return 0; + try { + return Integer.valueOf(volume.toString()); + } catch (NumberFormatException e) { + return 0; + } } public void setBuildout(boolean buildout) { @@ -572,63 +590,55 @@ public abstract class SWGObject implements Serializable, Comparable { } } - public List getObjectsAware() { + public Set getObjectsAware() { synchronized (objectsAware) { - return Collections.unmodifiableList(objectsAware); + return Collections.unmodifiableSet(objectsAware); } } - - public List getChildrenAwareness() { - List awareness = new ArrayList<>(objectsAware); - - if (getParent() != null && !(awareness.contains(getParent()))) - awareness.addAll(getParent().getObjectsAware()); - - if (getOwner() != null && getOwner().getCreatureObject() != null - && !(awareness.contains(getOwner().getCreatureObject()))) - awareness.add(getOwner().getCreatureObject()); - - return awareness; + + public Set getObservers() { + return getObservers(this); + } + + private Set getObservers(SWGObject childObject) { + if (getParent() == null) { + Set observers = new HashSet<>(); + synchronized (objectsAware) { + for (SWGObject obj : objectsAware) { + Player p = obj.getOwner(); + if (p != null && !p.equals(childObject.getOwner())) + observers.add(obj); + else + childObject.getChildrenObservers(observers, obj); + } + } + childObject.getChildrenObservers(observers, this); + return observers; + } else { + return getParent().getObservers(childObject); // Search for top level parent + } + } + + private void getChildrenObservers(Set observers, SWGObject obj) { + for (SWGObject child : obj.getContainedObjects()) { + Player p = child.getOwner(); + if (p != null && !p.equals(getOwner())) { + observers.add(child); + } else { + getChildrenObservers(observers, child); + } + } } public void sendObserversAndSelf(Packet ... packets) { sendSelf(packets); sendObservers(packets); } - + public void sendObservers(Packet ... packets) { - synchronized (objectsAware) { - for (SWGObject obj : objectsAware) { - Player p = obj.getOwner(); - if (p == null || p.getPlayerState() != PlayerState.ZONED_IN) - continue; - p.sendPacket(packets); - - //System.out.println("Sent " + packets + " for " + this + " to objAware " + obj); - } - - List childrenAwareness = getChildrenAwareness(); - childrenAwareness.removeAll(objectsAware); - childrenAwareness.remove(this); // Remove self since only observers being notified - - for (SWGObject childObserver : childrenAwareness) { - if (childObserver == null || childObserver == this) - continue; - - Player p = childObserver.getOwner(); - if (p == null || getOwner() == p || p.getPlayerState() != PlayerState.ZONED_IN) - continue; - p.sendPacket(packets); - - //System.out.println("Sent " + packets + " of " + this + " to child " + childObserver); - } - - SWGObject parent = getParent(); - - if(parent != null) { - parent.sendObservers(packets); - //System.out.println("Sent " + packets + " to observers of " + this); - } + Set observers = getObservers(); + for (SWGObject observer : observers) { + observer.getOwner().sendPacket(packets); } } @@ -660,16 +670,16 @@ public abstract class SWGObject implements Serializable, Comparable { } } - private void sendUpdatedContainment(List oldObservers, List newObservers) { + private void sendUpdatedContainment(Set oldObservers, Set newObservers) { if (parent == null) return; - List same = new ArrayList<>(oldObservers); + Set same = new HashSet<>(oldObservers); same.retainAll(newObservers); - List added = new ArrayList<>(newObservers); + Set added = new HashSet<>(newObservers); added.removeAll(oldObservers); - List removed = new ArrayList<>(oldObservers); + Set removed = new HashSet<>(oldObservers); removed.removeAll(newObservers); for (SWGObject swgObject : same) { @@ -688,42 +698,61 @@ public abstract class SWGObject implements Serializable, Comparable { } } } - + public void updateObjectAwareness(List withinRange) { synchronized (objectsAware) { - List outOfRange = new ArrayList(objectsAware); + Set outOfRange = new HashSet<>(objectsAware); outOfRange.removeAll(withinRange); - for (SWGObject o : outOfRange) + for (SWGObject o : outOfRange) { awarenessOutOfRange(o); - for (SWGObject o : withinRange) + o.awarenessOutOfRange(this); + } + for (SWGObject o : withinRange) { awarenessInRange(o); + o.awarenessInRange(this); + } } } private void awarenessOutOfRange(SWGObject o) { synchronized (objectsAware) { if (objectsAware.remove(o)) { - if (o.getOwner() != null) { + Player owner = o.getOwner(); + if (owner != null) sendSceneDestroyObject(o.getOwner()); - } - if (getOwner() != null) - o.awarenessOutOfRange(this); + else + destroyObjectObservers(o); } } } private void awarenessInRange(SWGObject o) { synchronized (objectsAware) { - if (!objectsAware.contains(o)) { - objectsAware.add(o); - if (o.getOwner() != null) { + if (objectsAware.add(o)) { + Player owner = o.getOwner(); + if (owner != null) createObject(o.getOwner()); - } - if (getOwner() != null) - o.awarenessInRange(this); + else + createObjectObservers(o); } } } + + private void createObjectObservers(SWGObject obj) { + Set observers = new HashSet<>(); + getChildrenObservers(observers, obj); + for (SWGObject observer : observers) { + createObject(observer.getOwner()); + } + } + + private void destroyObjectObservers(SWGObject obj) { + Set observers = new HashSet<>(); + getChildrenObservers(observers, obj); + for (SWGObject observer : observers) { + sendSceneDestroyObject(observer.getOwner()); + } + } public void sendDataTransforms(DataTransform dTransform) { Location loc = dTransform.getLocation(); diff --git a/src/services/chat/ChatService.java b/src/services/chat/ChatService.java index 3b326414..5983d25f 100644 --- a/src/services/chat/ChatService.java +++ b/src/services/chat/ChatService.java @@ -227,7 +227,7 @@ public class ChatService extends Service { sender.sendPacket(message); // Notify observers of the chat message - for (SWGObject aware : actor.getObjectsAware()) { + for (SWGObject aware : actor.getObservers()) { if (aware.getOwner() != null) aware.getOwner().sendPacket(new SpatialChat(aware.getObjectId(), message)); } diff --git a/src/services/objects/ObjectAwareness.java b/src/services/objects/ObjectAwareness.java index f70ab2c5..1f5b92ab 100644 --- a/src/services/objects/ObjectAwareness.java +++ b/src/services/objects/ObjectAwareness.java @@ -5,10 +5,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import network.packets.swg.zone.UpdateContainmentMessage; import resources.Location; import resources.Terrain; import resources.objects.SWGObject; -import resources.objects.creature.CreatureObject; import resources.objects.quadtree.QuadTree; public class ObjectAwareness { @@ -32,6 +32,10 @@ public class ObjectAwareness { } } + /** + * Adds the specified object to the awareness quadtree + * @param object the object to add + */ public void add(SWGObject object) { update(object); Location l = object.getLocation(); @@ -43,6 +47,10 @@ public class ObjectAwareness { } } + /** + * Removes the specified object from the awareness quadtree + * @param object the object to remove + */ public void remove(SWGObject object) { Location l = object.getLocation(); if (invalidLocation(l)) @@ -53,16 +61,54 @@ public class ObjectAwareness { } } + /** + * This function is used for moving objects within the world, which + * includes moving from a cell to the world. + * @param object the object to move + * @param nLocation the new location + */ public void move(SWGObject object, Location nLocation) { - remove(object); + if (object.getParent() != null) { + object.getParent().removeObject(object); // Moving from cell to world + object.sendObserversAndSelf(new UpdateContainmentMessage(object.getObjectId(), 0, object.getSlotArrangement())); + } else { + remove(object); // World to World + } object.setLocation(nLocation); add(object); } + /** + * This function is used for moving objects to or within containers, + * probably a cell. This handles the logic for removing the object from the + * previous cell and adding it to the new one, if necessary. + * @param object the object to move + * @param nParent the new parent the object will be in + * @param nLocation the new location relative to the parent + */ + public void move(SWGObject object, SWGObject nParent, Location nLocation) { + SWGObject parent = object.getParent(); + if (parent != null && nParent != parent) { + parent.removeObject(object); // Moving from cell to cell, for instance + } else if (parent == null) { + remove(object); // Moving from world to cell + } + if (object.getParent() == null) { // Should have been updated in removeObject() + nParent.addObject(object); // If necessary, add to new cell + object.sendObserversAndSelf(new UpdateContainmentMessage(object.getObjectId(), nParent.getObjectId(), object.getSlotArrangement())); + } + object.setLocation(nLocation); + } + + /** + * 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 + */ public void update(SWGObject obj) { if (obj.isBuildout()) return; - Location l = obj.getLocation(); + Location l = obj.getWorldLocation(); if (invalidLocation(l)) return; List objectAware = new LinkedList(); @@ -88,9 +134,7 @@ public class ObjectAwareness { private boolean isValidInRange(SWGObject obj, SWGObject inRange, Location objLoc) { if (inRange.getObjectId() == obj.getObjectId()) return false; - if (inRange instanceof CreatureObject && inRange.getOwner() == null) - return false; - Location inRangeLoc = inRange.getLocation(); + Location inRangeLoc = inRange.getWorldLocation(); double distSquared = distanceSquared(objLoc, inRangeLoc); if (inRange.getLoadRange() != 0 && distSquared > square(inRange.getLoadRange())) return false; diff --git a/src/services/objects/ObjectManager.java b/src/services/objects/ObjectManager.java index 3b23b52c..a8890dad 100644 --- a/src/services/objects/ObjectManager.java +++ b/src/services/objects/ObjectManager.java @@ -27,13 +27,15 @@ ***********************************************************************************/ package services.objects; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import intents.ObjectTeleportIntent; import intents.PlayerEventIntent; import intents.RequestZoneInIntent; import intents.network.GalacticPacketIntent; - -import java.util.*; - import main.ProjectSWG; import network.packets.Packet; import network.packets.swg.zone.SceneDestroyObject; @@ -50,6 +52,7 @@ import resources.control.Manager; import resources.objects.SWGObject; import resources.objects.buildouts.BuildoutLoader; import resources.objects.buildouts.SnapshotLoader; +import resources.objects.cell.CellObject; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; import resources.player.Player; @@ -59,7 +62,6 @@ import resources.server_info.Config; import resources.server_info.Log; import resources.server_info.ObjectDatabase; import resources.server_info.ObjectDatabase.Traverser; -import resources.server_info.UncachedObjectDatabase; import services.map.MapService; import services.player.PlayerManager; @@ -74,7 +76,7 @@ public class ObjectManager extends Manager { public ObjectManager() { mapService = new MapService(); - database = new UncachedObjectDatabase("odb/objects.db"); + database = new CachedObjectDatabase("odb/objects.db"); objectAwareness = new ObjectAwareness(); objectMap = new HashMap<>(); maxObjectId = 1; @@ -87,9 +89,9 @@ public class ObjectManager extends Manager { registerForIntent(PlayerEventIntent.TYPE); registerForIntent(ObjectTeleportIntent.TYPE); objectAwareness.initialize(); - loadObjects(); loadBuildouts(); loadSnapshots(); + loadObjects(); return super.initialize(); } @@ -97,6 +99,7 @@ public class ObjectManager extends Manager { long startLoad = System.nanoTime(); Log.i("ObjectManager", "Loading objects from ObjectDatabase..."); System.out.println("ObjectManager: Loading objects from ObjectDatabase..."); + database.load(); database.traverse(new Traverser() { @Override public void process(SWGObject obj) { @@ -170,25 +173,47 @@ public class ObjectManager extends Manager { } private void loadBuildout(SWGObject obj) { - if (obj instanceof TangibleObject) { + if (obj instanceof TangibleObject || obj instanceof CellObject) { objectAwareness.add(obj); } objectMap.put(obj.getObjectId(), obj); + addChildrenObjects(obj); mapService.addMapLocation(obj, MapService.MapType.STATIC); } private void loadSnapshot(SWGObject obj) { - if (obj instanceof TangibleObject) { + if (obj instanceof TangibleObject || obj instanceof CellObject) { objectAwareness.add(obj); } objectMap.put(obj.getObjectId(), obj); + addChildrenObjects(obj); mapService.addMapLocation(obj, MapService.MapType.STATIC); } private void loadObject(SWGObject obj) { obj.setOwner(null); - objectAwareness.add(obj); + if (!(obj instanceof CreatureObject) || ((CreatureObject) obj).getPlayerObject() == null) + objectAwareness.add(obj); objectMap.put(obj.getObjectId(), obj); + if (obj.getParent() != null && obj.getParent().isBuildout()) { + long id = obj.getParent().getObjectId(); + obj.getParent().removeObject(obj); + SWGObject parent = objectMap.get(id); + if (parent != null) + parent.addObject(obj); + else { + System.err.println("Parent for " + obj + " is null! ParentID: " + id); + Log.e("ObjectManager", "Parent for %s is null! ParentID: %d", obj, id); + } + } + addChildrenObjects(obj); + } + + private void addChildrenObjects(SWGObject obj) { + for (SWGObject child : obj.getContainedObjects()) { + objectMap.put(child.getObjectId(), child); + addChildrenObjects(child); + } } @Override @@ -250,10 +275,9 @@ public class ObjectManager extends Manager { SWGObject obj = getObjectById(trans.getObjectId()); moveObject(obj, trans); } else if (packet instanceof DataTransformWithParent) { - // TODO: Change this when World Snapshot loading is to update player's position in awareness DataTransformWithParent transformWithParent = (DataTransformWithParent) packet; SWGObject object = getObjectById(transformWithParent.getObjectId()); - object.sendParentDataTransforms(transformWithParent); + moveObject(object, transformWithParent); } } } @@ -361,6 +385,14 @@ public class ObjectManager extends Manager { // on the active state (mainly for CreatureObject, override sendDataTransforms in the class?) } + private void moveObject(SWGObject obj, DataTransformWithParent transformWithParent) { + Location newLocation = transformWithParent.getLocation(); + newLocation.setTerrain(obj.getLocation().getTerrain()); + SWGObject parent = objectMap.get(transformWithParent.getCellId()); + objectAwareness.move(obj, parent, newLocation); + obj.sendParentDataTransforms(transformWithParent); + } + private void zoneInCharacter(PlayerManager playerManager, String galaxy, long netId, long characterId) { Player player = playerManager.getPlayerFromNetworkId(netId); if (player == null) { @@ -383,7 +415,12 @@ public class ObjectManager extends Manager { Log.e("ObjectManager", "Failed to start zone - CreatureObject doesn't have a ghost [Character: %d User: %s", characterId, player.getUsername()); return; } - objectAwareness.add(creatureObj); + if (creatureObj.getParent() != null) + objectAwareness.update(creatureObj); + else { + objectAwareness.remove(creatureObj); + objectAwareness.add(creatureObj); + } new RequestZoneInIntent(player, (CreatureObject) creatureObj, galaxy).broadcast(); } diff --git a/src/services/player/ZoneManager.java b/src/services/player/ZoneManager.java index b069cbdb..da9a4430 100644 --- a/src/services/player/ZoneManager.java +++ b/src/services/player/ZoneManager.java @@ -137,7 +137,8 @@ public class ZoneManager extends Manager { sendZonePackets(player, creature); initPlayerBeforeZoneIn(player, creature, playerObj); - creature.createObject(player); + if (creature.getParent() == null) + creature.createObject(player); System.out.printf("[%s] %s is zoning in%n", player.getUsername(), player.getCharacterName()); Log.i("ObjectManager", "Zoning in %s with character %s", player.getUsername(), player.getCharacterName()); sendCommitHistory(player);