diff --git a/src/intents/object/ObjectCreatedIntent.java b/src/intents/object/ObjectCreatedIntent.java new file mode 100644 index 000000000..3357518cd --- /dev/null +++ b/src/intents/object/ObjectCreatedIntent.java @@ -0,0 +1,21 @@ +package intents.object; + +import resources.control.Intent; +import resources.objects.SWGObject; + +public class ObjectCreatedIntent extends Intent { + + public static final String TYPE = "ObjectCreatedIntent"; + + private final SWGObject obj; + + public ObjectCreatedIntent(SWGObject obj) { + super(TYPE); + this.obj = obj; + } + + public SWGObject getObject() { + return obj; + } + +} diff --git a/src/intents/player/PlayerTransformedIntent.java b/src/intents/player/PlayerTransformedIntent.java new file mode 100644 index 000000000..23fe57b77 --- /dev/null +++ b/src/intents/player/PlayerTransformedIntent.java @@ -0,0 +1,64 @@ +package intents.player; + +import resources.Location; +import resources.Terrain; +import resources.control.Intent; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; + +public class PlayerTransformedIntent extends Intent { + + public static final String TYPE = "PlayerTransformedIntent"; + + private final CreatureObject object; + private final SWGObject oldParent; + private final SWGObject newParent; + private final Location oldLocation; + private final Location newLocation; + + public PlayerTransformedIntent(CreatureObject object, SWGObject oldParent, SWGObject newParent, Location oldLocation, Location newLocation) { + super(TYPE); + this.object = object; + this.oldParent = oldParent; + this.newParent = newParent; + this.oldLocation = oldLocation; + this.newLocation = newLocation; + } + + public CreatureObject getPlayer() { + return object; + } + + public SWGObject getOldParent() { + return oldParent; + } + + public SWGObject getNewParent() { + return newParent; + } + + public Location getOldLocation() { + return oldLocation; + } + + public Location getNewLocation() { + return newLocation; + } + + public boolean changedParents() { + return oldParent != newParent; + } + + public boolean enteredParentFromWorld() { + return oldParent == null && newParent != null; + } + + public boolean enteredArea(Location l, double radius) { + return enteredArea(l.getTerrain(), l.getX(), l.getY(), l.getZ(), radius); + } + + public boolean enteredArea(Terrain t, double x, double y, double z, double radius) { + return newLocation.isWithinDistance(t, x, y, z, radius) && !oldLocation.isWithinDistance(t, x, y, z, radius); + } + +} diff --git a/src/resources/Location.java b/src/resources/Location.java index a58b87cfb..70896efc0 100644 --- a/src/resources/Location.java +++ b/src/resources/Location.java @@ -90,12 +90,24 @@ public class Location implements Encodable, Serializable { public Quaternion getOrientation() { return new Quaternion(orientation); } public boolean isWithinDistance(Location l, double x, double y, double z) { + if (getTerrain() != l.getTerrain()) + return false; double xD = Math.abs(getX() - l.getX()); double yD = Math.abs(getY() - l.getY()); double zD = Math.abs(getZ() - l.getZ()); return xD <= x && yD <= y && zD <= z; } + public boolean isWithinDistance(Location l, double radius) { + return isWithinDistance(l.getTerrain(), l.getX(), l.getY(), l.getZ(), radius); + } + + public boolean isWithinDistance(Terrain t, double x, double y, double z, double radius) { + if (getTerrain() != t) + return false; + return square(square(getX()-x) + square(getY()-y) + square(getZ()-z)) <= square(radius); + } + public void translatePosition(double x, double y, double z) { setX(getX() + x); setY(getY() + y); diff --git a/src/resources/control/IntentManager.java b/src/resources/control/IntentManager.java index d07c50125..be7f461be 100644 --- a/src/resources/control/IntentManager.java +++ b/src/resources/control/IntentManager.java @@ -27,18 +27,16 @@ ***********************************************************************************/ package resources.control; -import intents.server.ServerStatusIntent; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; +import resources.server_info.Log; import utilities.ThreadUtilities; @@ -46,21 +44,25 @@ public class IntentManager { private static final IntentManager instance = new IntentManager(); private final Runnable broadcastRunnable; + private final Map > intentRegistrations; + private final Queue intentQueue; private ExecutorService broadcastThreads; - private Map > intentRegistrations; - private Queue intentQueue; private boolean initialized = false; private boolean terminated = false; private IntentManager() { + intentRegistrations = new HashMap>(); + intentQueue = new IntentQueue(); initialize(); broadcastRunnable = new Runnable() { public void run() { - Intent i = intentQueue.poll(); + Intent i; + synchronized (intentQueue) { + i = intentQueue.poll(); + } if (i != null) broadcast(i); - if (i instanceof ServerStatusIntent) - onServerStatusIntent((ServerStatusIntent) i); + Log.d("IntentManager", "Completed - %d [%s]", intentQueue.size(), i); } }; } @@ -68,14 +70,12 @@ public class IntentManager { protected void initialize() { if (!initialized) { broadcastThreads = Executors.newCachedThreadPool(ThreadUtilities.newThreadFactory("intent-processor-%d")); - intentRegistrations = new HashMap>(); - intentQueue = new ConcurrentLinkedQueue(); initialized = true; terminated = false; } } - private void terminate() { + protected void terminate() { if (!terminated) { broadcastThreads.shutdown(); initialized = false; @@ -83,16 +83,12 @@ public class IntentManager { } } - private void onServerStatusIntent(ServerStatusIntent i) { - if (i.getStatus() == ServerStatus.TERMINATING) { - terminate(); - } - } - protected void broadcastIntent(Intent i) { if (i == null) throw new NullPointerException("Intent cannot be null!"); - intentQueue.add(i); + synchronized (intentQueue) { + intentQueue.add(i); + } try { broadcastThreads.submit(broadcastRunnable); } catch (RejectedExecutionException e) { } // This error is thrown when the server is being shut down } @@ -154,7 +150,9 @@ public class IntentManager { } public static int getIntentsQueued() { - return getInstance().intentQueue.size(); + synchronized (getInstance().intentQueue) { + return getInstance().intentQueue.size(); + } } public static IntentManager getInstance() { diff --git a/src/resources/control/IntentQueue.java b/src/resources/control/IntentQueue.java new file mode 100644 index 000000000..d2144f78d --- /dev/null +++ b/src/resources/control/IntentQueue.java @@ -0,0 +1,208 @@ +package resources.control; + +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.Queue; + +class IntentQueue implements Queue { + + private final Node head; + private int size; + private int modificationCount; + + public IntentQueue() { + head = new Node(null, null, null); // Left = Forward, Right = Reverse + head.left = head; + head.right = head; + modificationCount = 0; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean contains(Object o) { + Node n = head; + while (n.left != head) { + if (n.left.intent == o) + return true; + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator(); + } + + @Override + public Object [] toArray() { + return null; + } + + @Override + public T [] toArray(T [] a) { + return null; + } + + @Override + public boolean remove(Object o) { + modificationCount++; + Node n = head; + while (n.left != head) { + if (n.left.intent == o) { + n.left = n.left.left; + n.left.right = n; + } + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) + if (!contains(o)) + return false; + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean added = false; + for (Intent i : c) + added = add(i) || added; + return added; + } + + @Override + public boolean removeAll(Collection c) { + boolean changed = false; + for (Object o : c) + changed = remove(o) || changed; + return changed; + } + + @Override + public boolean retainAll(Collection c) { + boolean changed = false; + Node n = head; + while (n.left != head) { + if (!c.contains(n.left.intent)) { + n.left.left.right = n; + n.left = n.left.left; + } + } + return changed; + } + + @Override + public void clear() { + modificationCount++; + size = 0; + head.left = head; + head.right = head; + } + + @Override + public boolean add(Intent e) { + head.right.left = new Node(e, head, head.right); + head.right = head.right.left; + modificationCount++; + size++; + return true; + } + + @Override + public boolean offer(Intent e) { + return add(e); + } + + @Override + public Intent remove() { + if (isEmpty()) + throw new NoSuchElementException("Queue is empty!"); + modificationCount++; + Intent i = head.left.intent; + head.left = head.left.left; + head.left.right = head; + size--; + return i; + } + + @Override + public Intent poll() { + if (isEmpty()) + return null; + modificationCount++; + Intent i = head.left.intent; + head.left = head.left.left; + head.left.right = head; + size--; + return i; + } + + @Override + public Intent element() { + if (isEmpty()) + throw new NoSuchElementException("Queue is empty!"); + return head.left.intent; + } + + @Override + public Intent peek() { + if (isEmpty()) + return null; + return head.left.intent; + } + + private class Iterator implements java.util.Iterator { + + private final int modificationCount; + private Node currentNode; + + public Iterator() { + this.modificationCount = IntentQueue.this.modificationCount; + this.currentNode = IntentQueue.this.head; + } + + @Override + public boolean hasNext() { + if (this.modificationCount != IntentQueue.this.modificationCount) + throw new ConcurrentModificationException(); + return currentNode.left != IntentQueue.this.head; + } + + @Override + public Intent next() { + if (!hasNext()) + throw new NoSuchElementException("Iterator has reached the end of the queue!"); + Intent i = currentNode.left.intent; + currentNode = currentNode.left; + return i; + } + + } + + private static class Node { + + public final Intent intent; + public Node left; + public Node right; + + public Node(Intent intent, Node left, Node right) { + this.intent = intent; + this.left = left; + this.right = right; + } + + } + +} diff --git a/src/resources/control/Service.java b/src/resources/control/Service.java index 7ea7c0de3..c662fe42d 100644 --- a/src/resources/control/Service.java +++ b/src/resources/control/Service.java @@ -78,6 +78,7 @@ public abstract class Service implements IntentReceiver { * @return TRUE if termination was successful, FALSE otherwise */ public boolean terminate() { + IntentManager.getInstance().terminate(); return true; } diff --git a/src/services/map/CityService.java b/src/services/map/CityService.java index 5e3c50474..5865d9c59 100644 --- a/src/services/map/CityService.java +++ b/src/services/map/CityService.java @@ -7,6 +7,7 @@ import java.util.Locale; import intents.PlayerEventIntent; import intents.network.GalacticPacketIntent; +import network.packets.Packet; import network.packets.swg.zone.object_controller.DataTransform; import resources.Location; import resources.control.Intent; @@ -42,7 +43,8 @@ public class CityService extends Service { public void onIntentReceived(Intent i) { if (i instanceof GalacticPacketIntent) { GalacticPacketIntent gpi = (GalacticPacketIntent) i; - if (gpi.getPacket() instanceof DataTransform) { + Packet p = gpi.getPacket(); + if (p instanceof DataTransform) { Player player = gpi.getPlayerManager().getPlayerFromNetworkId(gpi.getNetworkId()); if (player == null) { Log.e("CityService", "Player is null in GalacticPacketIntent:DataTransform!"); @@ -53,7 +55,7 @@ public class CityService extends Service { Log.e("CityService", "Creature is null in GalacticPacketIntent:DataTransform!"); return; } - DataTransform transform = (DataTransform) gpi.getPacket(); + DataTransform transform = (DataTransform) p; Location loc = transform.getLocation(); performLocationUpdate(creature, loc); } diff --git a/src/services/map/MapManager.java b/src/services/map/MapManager.java index 8b0243e27..917c42712 100644 --- a/src/services/map/MapManager.java +++ b/src/services/map/MapManager.java @@ -28,6 +28,7 @@ package services.map; import intents.network.GalacticPacketIntent; +import intents.object.ObjectCreatedIntent; import network.packets.Packet; import network.packets.swg.SWGPacket; import network.packets.swg.zone.spatial.GetMapLocationsMessage; @@ -83,6 +84,7 @@ public class MapManager extends Manager { @Override public boolean initialize() { registerForIntent(GalacticPacketIntent.TYPE); + registerForIntent(ObjectCreatedIntent.TYPE); loadStaticCityPoints(); return super.initialize(); } @@ -91,7 +93,12 @@ public class MapManager extends Manager { public void onIntentReceived(Intent i) { switch (i.getType()) { case GalacticPacketIntent.TYPE: - processPacket((GalacticPacketIntent) i); + if (i instanceof GalacticPacketIntent) + processPacket((GalacticPacketIntent) i); + break; + case ObjectCreatedIntent.TYPE: + if (i instanceof ObjectCreatedIntent) + addMapLocation(((ObjectCreatedIntent) i).getObject(), MapType.STATIC); break; default: break; @@ -106,11 +113,12 @@ public class MapManager extends Manager { if (p instanceof SWGPacket) processSwgPacket(player, (SWGPacket) p); } - + private void processSwgPacket(Player player, SWGPacket p) { switch (p.getPacketType()) { case GET_MAP_LOCATIONS_MESSAGE: - handleMapLocationsRequest(player, (GetMapLocationsMessage) p); + if (p instanceof GetMapLocationsMessage) + handleMapLocationsRequest(player, (GetMapLocationsMessage) p); break; default: break; @@ -140,7 +148,7 @@ public class MapManager extends Manager { for (int row = 0; row < table.getRowCount(); row++) { MapCategory category = new MapCategory(); category.setName(table.getCell(row, 0).toString()); - category.setIndex(Integer.valueOf(table.getCell(row, 1).toString())); + category.setIndex((Integer) table.getCell(row, 1)); category.setIsCategory(Boolean.valueOf(table.getCell(row, 2).toString())); category.setIsSubCategory(Boolean.valueOf(table.getCell(row, 3).toString())); category.setCanBeActive(Boolean.valueOf(table.getCell(row, 4).toString())); @@ -158,8 +166,8 @@ public class MapManager extends Manager { template.setName(table.getCell(row, 1).toString()); template.setCategory(table.getCell(row, 2).toString()); template.setSubcategory(table.getCell(row, 3).toString()); - template.setType(Integer.valueOf(table.getCell(row, 4).toString())); - template.setFlag(Integer.valueOf(table.getCell(row, 5).toString())); + template.setType((Integer) table.getCell(row, 4)); + template.setFlag((Integer) table.getCell(row, 5)); mappingTemplates.put(template.getTemplate(), template); } diff --git a/src/services/objects/BuildoutAreaService.java b/src/services/objects/BuildoutAreaService.java index 6c2fa512f..6a0dfdad5 100644 --- a/src/services/objects/BuildoutAreaService.java +++ b/src/services/objects/BuildoutAreaService.java @@ -1,5 +1,22 @@ package services.objects; -public class BuildoutAreaService { +import resources.control.Service; +import resources.server_info.RelationalServerData; + +public class BuildoutAreaService extends Service { + + private static final String FILE_PREFIX = "serverdata/buildout/"; + + private final RelationalServerData clientSdb; + + public BuildoutAreaService() { + clientSdb = new RelationalServerData(FILE_PREFIX+"buildouts.db"); + } + + @Override + public boolean initialize() { + boolean success = clientSdb.linkTableWithSdb("areas", FILE_PREFIX+"areas.sdb"); + return super.initialize() && success; + } } diff --git a/src/services/objects/ObjectCreator.java b/src/services/objects/ObjectCreator.java index 2e2a4c07b..d52318df7 100644 --- a/src/services/objects/ObjectCreator.java +++ b/src/services/objects/ObjectCreator.java @@ -39,7 +39,6 @@ import resources.objects.cell.CellObject; import resources.objects.creature.CreatureObject; import resources.objects.installation.InstallationObject; import resources.objects.intangible.IntangibleObject; -import resources.objects.mobile.MobileObject; import resources.objects.player.PlayerObject; import resources.objects.resource.ResourceContainerObject; import resources.objects.ship.ShipObject; diff --git a/src/services/objects/ObjectManager.java b/src/services/objects/ObjectManager.java index c7188b55e..e8c6d57b6 100644 --- a/src/services/objects/ObjectManager.java +++ b/src/services/objects/ObjectManager.java @@ -37,6 +37,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import intents.object.ObjectCreateIntent; +import intents.object.ObjectCreatedIntent; import intents.object.ObjectIdRequestIntent; import intents.object.ObjectIdResponseIntent; import intents.object.ObjectTeleportIntent; @@ -113,11 +114,27 @@ public class ObjectManager extends Manager { registerForIntent(DeleteCharacterIntent.TYPE); objectAwareness.initialize(); loadClientObjects(); - maxObjectId = 1000000000; // Gets over all the buildouts/snapshots loadObjects(); return super.initialize(); } + @Override + public boolean start() { + Log.i("ObjectManager", "Starting object manager..."); + synchronized (objectMap) { + int i = 0; + for (SWGObject obj : objectMap.values()) { + if (obj.isBuildout()) { + new ObjectCreatedIntent(obj).broadcast(); + Log.d("ObjectManager", "%d / %d", i, objectMap.size()); + } + i++; + } + } + Log.i("ObjectManager", "Started object manager."); + return super.start(); + } + private void loadObjects() { long startLoad = System.nanoTime(); Log.i("ObjectManager", "Loading objects from ObjectDatabase..."); @@ -132,9 +149,6 @@ public class ObjectManager extends Manager { } } }); - for (SWGObject obj : new ArrayList<>(objectMap.values())) { - staticService.createSupportingObjects(obj); - } double loadTime = (System.nanoTime() - startLoad) / 1E6; Log.i("ObjectManager", "Finished loading %d objects. Time: %fms", database.size(), loadTime); System.out.printf("ObjectManager: Finished loading %d objects. Time: %fms%n", database.size(), loadTime); @@ -159,7 +173,7 @@ public class ObjectManager extends Manager { loadClientObject(obj); double loadTime = (System.nanoTime() - startLoad) / 1E6; System.out.printf("ClientObjectLoader: Finished loading %d client objects. Time: %fms%n", objects.size(), loadTime); - Log.i("ClientObjectLoader", "Finished loading client objects. Time: %fms", loadTime); + Log.i("ClientObjectLoader", "Finished loading %d client objects. Time: %fms", objects.size(), loadTime); } else { Log.w("ObjectManager", "Did not load client objects. Reason: Disabled."); System.out.println("ObjectManager: Did not load client objects. Reason: Disabled!"); @@ -167,13 +181,16 @@ public class ObjectManager extends Manager { } private void loadClientObject(SWGObject obj) { - if (obj instanceof TangibleObject || obj instanceof BuildingObject) { - objectAwareness.add(obj); + if (obj.getParent() == null) { + if (obj instanceof TangibleObject || obj instanceof BuildingObject) { + objectAwareness.add(obj); + } } - if (obj.getObjectId() >= maxObjectId) { - maxObjectId = obj.getObjectId() + 1; + synchronized (objectMap) { + if (obj.getObjectId() >= maxObjectId) { + maxObjectId = obj.getObjectId() + 1; + } } - mapService.addMapLocation(obj, MapManager.MapType.STATIC); } private void loadObject(SWGObject obj) { @@ -423,6 +440,7 @@ public class ObjectManager extends Manager { database.put(objectId, obj); } Log.i("ObjectManager", "Created object %d [%s]", obj.getObjectId(), obj.getTemplate()); + new ObjectCreatedIntent(obj).broadcast(); return obj; } } diff --git a/src/services/spawn/StaticService.java b/src/services/spawn/StaticService.java index e9c157bbf..3f11855dc 100644 --- a/src/services/spawn/StaticService.java +++ b/src/services/spawn/StaticService.java @@ -27,14 +27,18 @@ ***********************************************************************************/ package services.spawn; +import intents.object.ObjectCreatedIntent; + import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import resources.Location; +import resources.control.Intent; import resources.control.Service; import resources.objects.SWGObject; import resources.objects.building.BuildingObject; +import resources.server_info.Log; import resources.server_info.RelationalServerData; import services.objects.ObjectManager; @@ -60,24 +64,43 @@ public class StaticService extends Service { getSupportingStatement = spawnDatabase.prepareStatement(GET_SUPPORTING_SQL); } - public void createSupportingObjects(SWGObject object) { + @Override + public boolean initialize() { + registerForIntent(ObjectCreatedIntent.TYPE); + return super.initialize(); + } + + @Override + public void onIntentReceived(Intent i) { + switch (i.getType()) { + case ObjectCreatedIntent.TYPE: + if (i instanceof ObjectCreatedIntent) + createSupportingObjects(((ObjectCreatedIntent) i).getObject()); + break; + } + } + + private void createSupportingObjects(SWGObject object) { synchronized (databaseMutex) { try { getSupportingStatement.setString(1, object.getTemplate()); - ResultSet set = getSupportingStatement.executeQuery(); - Location world = object.getWorldLocation(); - while (set.next()) { - String iff = set.getString("child_iff"); - String cell = set.getString("cell"); - double x = set.getDouble("x"); - double y = set.getDouble("y"); - double z = set.getDouble("z"); - double heading = set.getDouble("heading"); - if (cell.isEmpty()) { - createObject(iff, world, x, y, z, heading); - } else { - BuildingObject buio = (BuildingObject) object; - createObject(iff, buio.getCellByName(cell), x, y, z, heading); + try (ResultSet set = getSupportingStatement.executeQuery()) { + Location world = object.getWorldLocation(); + while (set.next()) { + String iff = set.getString("child_iff"); + String cell = set.getString("cell"); + double x = set.getDouble("x"); + double y = set.getDouble("y"); + double z = set.getDouble("z"); + double heading = set.getDouble("heading"); + if (cell.isEmpty()) { + createObject(iff, world, x, y, z, heading); + } else if (object instanceof BuildingObject) { + BuildingObject buio = (BuildingObject) object; + createObject(iff, buio.getCellByName(cell), x, y, z, heading); + } else { + Log.e("StaticService", "Parent object with cell specified is not a BuildingObject!"); + } } } } catch (SQLException e) { diff --git a/test/resources/control/TestIntentQueue.java b/test/resources/control/TestIntentQueue.java new file mode 100644 index 000000000..6571e412a --- /dev/null +++ b/test/resources/control/TestIntentQueue.java @@ -0,0 +1,50 @@ +package resources.control; + +import java.util.Arrays; + +import intents.LoginEventIntent; +import intents.PlayerEventIntent; +import intents.object.ObjectCreateIntent; +import intents.object.ObjectCreatedIntent; +import intents.object.ObjectTeleportIntent; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TestIntentQueue { + + @Test + public void testQueue() { + Intent [] intents = new Intent[5]; + intents[0] = new PlayerEventIntent(null, null); + intents[1] = new ObjectTeleportIntent(null, null); + intents[2] = new ObjectCreateIntent(null); + intents[3] = new ObjectCreatedIntent(null); + intents[4] = new LoginEventIntent(0, null); + IntentQueue queue = new IntentQueue(); + long start = System.nanoTime(); + queue.addAll(Arrays.asList(intents)); + long end = System.nanoTime(); + System.out.println((end-start)/1E6/5 + "ms"); + int iterI = 0; + Assert.assertEquals(queue.size(), 5); + Assert.assertFalse(queue.isEmpty()); + for (Intent i : queue) { + Assert.assertEquals("Failed for intent #"+iterI, intents[iterI], i); + iterI++; + } + Assert.assertEquals(queue.size(), 5); + Assert.assertFalse(queue.isEmpty()); + for (int i = 0; i < intents.length; i++) { + Intent intent = queue.poll(); + Assert.assertEquals(5 - i - 1, queue.size()); + Assert.assertEquals("Failed for intent #"+i, intents[i], intent); + } + Assert.assertEquals(queue.size(), 0); + Assert.assertTrue(queue.isEmpty()); + } + +}