diff --git a/src/java/com/threerings/whirled/spot/Log.java b/src/java/com/threerings/whirled/spot/Log.java
index 698d14142..0656888d8 100644
--- a/src/java/com/threerings/whirled/spot/Log.java
+++ b/src/java/com/threerings/whirled/spot/Log.java
@@ -1,11 +1,11 @@
//
-// $Id: Log.java,v 1.1 2001/11/13 00:12:20 mdb Exp $
+// $Id: Log.java,v 1.2 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot;
/**
* A placeholder class that contains a reference to the log object used by
- * the services provided by this package.
+ * the Whirled Spot services.
*/
public class Log
{
diff --git a/src/java/com/threerings/whirled/spot/client/SpotCodes.java b/src/java/com/threerings/whirled/spot/client/SpotCodes.java
new file mode 100644
index 000000000..f3835ab64
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/client/SpotCodes.java
@@ -0,0 +1,37 @@
+//
+// $Id: SpotCodes.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.client;
+
+import com.threerings.crowd.chat.ChatCodes;
+import com.threerings.crowd.client.LocationCodes;
+
+/**
+ * Contains codes used by the Spot invocation services.
+ */
+public interface SpotCodes extends ChatCodes, LocationCodes
+{
+ /** The module name for the Spot services. */
+ public static final String MODULE_NAME = "whirled!spot";
+
+ /** The message identifier for a cluster speak request message. */
+ public static final String CLUSTER_SPEAK_REQUEST = "cspkreq";
+
+ /** The message identifier for a changeLoc request. */
+ public static final String CHANGE_LOC_REQUEST = "ChangeLoc";
+
+ /** The response identifier for a successful changeLoc request. This
+ * is mapped by the invocation services to a call to {@link
+ * SpotSceneDirector#handleChangeLocSucceeded}. */
+ public static final String CHANGE_LOC_SUCCEEDED_RESPONSE =
+ "ChangeLocSucceeded";
+
+ /** The response identifier for a failed changeLoc request. This is
+ * mapped by the invocation services to a call to {@link
+ * SpotSceneDirector#handleChangeLocFailed}. */
+ public static final String CHANGE_LOC_FAILED_RESPONSE = "ChangeLocFailed";
+
+ /** An error code indicating that a location is occupied. Usually
+ * generated by a failed changeLoc request. */
+ public static final String LOCATION_OCCUPIED = "m.location_occupied";
+}
diff --git a/src/java/com/threerings/whirled/spot/client/SpotSceneDirector.java b/src/java/com/threerings/whirled/spot/client/SpotSceneDirector.java
new file mode 100644
index 000000000..1f2fa9ff3
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/client/SpotSceneDirector.java
@@ -0,0 +1,100 @@
+//
+// $Id: SpotSceneDirector.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.client;
+
+import java.util.Iterator;
+import com.samskivert.util.StringUtil;
+
+import com.threerings.whirled.client.DisplayScene;
+import com.threerings.whirled.client.DisplaySceneFactory;
+import com.threerings.whirled.client.SceneDirector;
+import com.threerings.whirled.client.persist.SceneRepository;
+import com.threerings.whirled.util.WhirledContext;
+
+import com.threerings.whirled.spot.Log;
+import com.threerings.whirled.spot.data.Location;
+
+/**
+ * Extends the standard scene director with facilities to move between
+ * locations within a scene.
+ */
+public class SpotSceneDirector extends SceneDirector
+ implements SpotCodes
+{
+ /**
+ * Creates a new spot scene director with the specified context.
+ *
+ * @param ctx the active client context.
+ * @param screp the entity from which the scene director will load
+ * scene data from the local client scene storage.
+ * @param dsfact the factory that knows which derivation of {@link
+ * DisplayScene} to create for the current system.
+ */
+ public SpotSceneDirector (
+ WhirledContext ctx, SceneRepository screp, DisplaySceneFactory dsfact)
+ {
+ super(ctx, screp, dsfact);
+ }
+
+ /**
+ * Issues a request to change our location within the scene to the
+ * location identified by the specified id.
+ */
+ public void changeLocation (int locationId)
+ {
+ // refuse if there's a pending location change
+ if (_pendingLocId != -1) {
+ return;
+ }
+
+ // make sure we're currently in a scene
+ if (_sceneId == -1) {
+ Log.warning("Requested to change locations, but we're not " +
+ "currently in any scene [locId=" + locationId + "].");
+ return;
+ }
+
+ // make sure the specified location is in the current scene
+ int locidx = -1;
+ DisplaySpotScene sscene = (DisplaySpotScene)_scene;
+ Iterator locs = sscene.getLocations().iterator();
+ for (int i = 0; locs.hasNext(); i++) {
+ Location loc = (Location)locs.next();
+ if (loc.locationId == locationId) {
+ locidx = i;
+ break;
+ }
+ }
+ if (locidx == -1) {
+ Log.warning("Requested to change to a location that's not " +
+ "in the current scene [locs=" + StringUtil.toString(
+ sscene.getLocations().iterator()) +
+ ", locId=" + locationId + "].");
+ return;
+ }
+
+ // make a note that we're changing to this location
+ _pendingLocId = locationId;
+ // and send the location change request
+ SpotService.changeLoc(_ctx.getClient(), _sceneId, locationId, this);
+ }
+
+ /**
+ * Called in response to a successful changeLoc request.
+ */
+ public void handleChangeLocSucceeded (int invid)
+ {
+ }
+
+ /**
+ * Called in response to a failed changeLoc request.
+ */
+ public void handleChangeLocFailed (int invid, String reason)
+ {
+ }
+
+ /** The location id on which we have an outstanding change location
+ * request. */
+ protected int _pendingLocId = -1;
+}
diff --git a/src/java/com/threerings/whirled/spot/client/SpotService.java b/src/java/com/threerings/whirled/spot/client/SpotService.java
new file mode 100644
index 000000000..19a56ff65
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/client/SpotService.java
@@ -0,0 +1,32 @@
+//
+// $Id: SpotService.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.client;
+
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationDirector;
+
+import com.threerings.whirled.spot.Log;
+
+/**
+ * Provides a mechanism by which the client can request to move between
+ * locations within a scene. These services should not be used directly,
+ * but instead should be accessed via the {@link SpotSceneDirector}.
+ */
+public class SpotService implements SpotCodes
+{
+ /**
+ * Requests that that this client's body be made to occupy the
+ * specified location.
+ */
+ public static void changeLoc (Client client, int sceneId, int locationId,
+ SpotSceneDirector rsptarget)
+ {
+ InvocationDirector invdir = client.getInvocationDirector();
+ Object[] args = new Object[] { new Integer(sceneId),
+ new Integer(locationId) };
+ invdir.invoke(MODULE_NAME, CHANGE_LOC_REQUEST, args, rsptarget);
+ Log.info("Sent changeLoc request [sceneId=" + sceneId +
+ ", locId=" + locationId + "].");
+ }
+}
diff --git a/src/java/com/threerings/whirled/spot/data/SpotOccupantInfo.java b/src/java/com/threerings/whirled/spot/data/SpotOccupantInfo.java
new file mode 100644
index 000000000..8a19213ae
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/data/SpotOccupantInfo.java
@@ -0,0 +1,44 @@
+//
+// $Id: SpotOccupantInfo.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.data;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+import com.threerings.crowd.data.OccupantInfo;
+
+/**
+ * The spot services extend the basic occupant info with information on
+ * which location within the scene a user occupies.
+ */
+public class SpotOccupantInfo extends OccupantInfo
+{
+ /** The id of the location occupied by this user or -1 if they occupy
+ * no location. */
+ public int locationId;
+
+ // documentation inherited
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeInt(locationId);
+ }
+
+ // documentation inherited
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ locationId = in.readInt();
+ }
+
+ // documentation inherited
+ protected void toString (StringBuffer buf)
+ {
+ super.toString(buf);
+ buf.append(", locId=").append(locationId);
+ }
+}
diff --git a/src/java/com/threerings/whirled/spot/server/ClusterChatMessageHandler.java b/src/java/com/threerings/whirled/spot/server/ClusterChatMessageHandler.java
new file mode 100644
index 000000000..b5fd88440
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/server/ClusterChatMessageHandler.java
@@ -0,0 +1,51 @@
+//
+// $Id: ClusterChatMessageHandler.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.server;
+
+import com.threerings.presents.dobj.MessageEvent;
+
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.crowd.data.PlaceObject;
+import com.threerings.crowd.server.PlaceManager;
+
+import com.threerings.whirled.server.WhirledServer;
+import com.threerings.whirled.spot.Log;
+
+/**
+ * The cluster chat message handler handles chat messages that are issued
+ * to the cluster associated with a particular location, with the
+ * intention of speaking only to bodies in that cluster.
+ */
+public class ClusterChatMessageHandler
+ implements PlaceManager.MessageHandler
+{
+ /**
+ * Handles {@link SpotCodes#CLUSTER_SPEAK_REQUEST} messages.
+ */
+ public void handleEvent (MessageEvent event, PlaceManager pmgr)
+ {
+ // presently we do no ratification of chat messages, so we just
+ // generate a chat notification with the message and name of the
+ // speaker
+ int soid = event.getSourceOid();
+ BodyObject source = (BodyObject)WhirledServer.omgr.getObject(soid);
+ if (source == null) {
+ Log.info("Chatter went away. Dropping cluster chat request " +
+ "[req=" + event + "].");
+ return;
+ }
+
+ // parse our incoming arguments
+ Object[] inargs = event.getArgs();
+ int reqid = ((Integer)inargs[0]).intValue();
+ int locid = ((Integer)inargs[1]).intValue();
+ String message = (String)inargs[2];
+
+ // pass this request on to the spot scene manager as it will need
+ // to check that the location exists and that the requester
+ // occupies it and so on
+ ((SpotSceneManager)pmgr).handleClusterChatRequest(
+ source, locid, message);
+ }
+}
diff --git a/src/java/com/threerings/whirled/spot/server/RuntimeSpotScene.java b/src/java/com/threerings/whirled/spot/server/RuntimeSpotScene.java
index 18b41eca3..c0369e701 100644
--- a/src/java/com/threerings/whirled/spot/server/RuntimeSpotScene.java
+++ b/src/java/com/threerings/whirled/spot/server/RuntimeSpotScene.java
@@ -1,5 +1,5 @@
//
-// $Id: RuntimeSpotScene.java,v 1.1 2001/11/13 02:25:35 mdb Exp $
+// $Id: RuntimeSpotScene.java,v 1.2 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
@@ -13,16 +13,32 @@ import com.threerings.whirled.server.RuntimeScene;
*/
public interface RuntimeSpotScene extends RuntimeScene
{
+ /**
+ * Returns the number of locations in this scene.
+ */
+ public int getLocationCount ();
+
+ /**
+ * Returns the location id of the location at the specified index.
+ */
+ public int getLocationId (int locidx);
+
+ /**
+ * Returns the index of the specified location id.
+ */
+ public int getLocationIndex (int locationId);
+
/**
* Returns the total number of clusters in this scene.
*/
public int getClusterCount ();
/**
- * Returns the cluster index associated with the specified location or
- * -1 if the location is not associated with a cluster.
+ * Returns the cluster index associated with the specified location
+ * index or -1 if the location at that index is not associated with a
+ * cluster.
*/
- public int getClusterIndex (int locationId);
+ public int getClusterIndex (int locationIdx);
/**
* Returns the location id of the default entrance to this scene. If a
diff --git a/src/java/com/threerings/whirled/spot/server/RuntimeSpotSceneImpl.java b/src/java/com/threerings/whirled/spot/server/RuntimeSpotSceneImpl.java
index 4116aacbc..8d8da518f 100644
--- a/src/java/com/threerings/whirled/spot/server/RuntimeSpotSceneImpl.java
+++ b/src/java/com/threerings/whirled/spot/server/RuntimeSpotSceneImpl.java
@@ -1,5 +1,5 @@
//
-// $Id: RuntimeSpotSceneImpl.java,v 1.1 2001/11/13 02:25:35 mdb Exp $
+// $Id: RuntimeSpotSceneImpl.java,v 1.2 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
@@ -38,6 +38,30 @@ public class RuntimeSpotSceneImpl extends RuntimeSceneImpl
_clusterCount++;
}
+ // documentation inherited
+ public int getLocationCount ()
+ {
+ return _model.locationIds.length;
+ }
+
+ // documentation inherited
+ public int getLocationId (int locidx)
+ {
+ return _model.locationIds[locidx];
+ }
+
+ // documentation inherited
+ public int getLocationIndex (int locationId)
+ {
+ int lcount = _model.locationIds.length;
+ for (int i = 0; i < lcount; i++) {
+ if (_model.locationIds[i] == locationId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
// documentation inherited
public int getClusterCount ()
{
@@ -45,10 +69,9 @@ public class RuntimeSpotSceneImpl extends RuntimeSceneImpl
}
// documentation inherited
- public int getClusterIndex (int locationId)
+ public int getClusterIndex (int locationIdx)
{
- int lidx = getLocationIndex(locationId);
- return (lidx == -1) ? -1 : _model.locationClusters[lidx];
+ return _model.locationClusters[locationIdx];
}
// documentation inherited
@@ -71,21 +94,6 @@ public class RuntimeSpotSceneImpl extends RuntimeSceneImpl
return (pidx == -1) ? -1 : _model.targetLocIds[pidx];
}
- /**
- * Returns the index of the specified location in the model's internal
- * location arrays.
- */
- protected int getLocationIndex (int locationId)
- {
- int lcount = _model.locationIds.length;
- for (int i = 0; i < lcount; i++) {
- if (_model.locationIds[i] == locationId) {
- return i;
- }
- }
- return -1;
- }
-
/**
* Returns the index of the specified portal in the model's internal
* portal arrays.
diff --git a/src/java/com/threerings/whirled/spot/server/SpotProvider.java b/src/java/com/threerings/whirled/spot/server/SpotProvider.java
new file mode 100644
index 000000000..f109fcbc7
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/server/SpotProvider.java
@@ -0,0 +1,66 @@
+//
+// $Id: SpotProvider.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.server;
+
+import com.threerings.presents.server.InvocationManager;
+import com.threerings.presents.server.InvocationProvider;
+import com.threerings.presents.server.ServiceFailedException;
+
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.whirled.server.SceneRegistry;
+
+import com.threerings.whirled.spot.Log;
+import com.threerings.whirled.spot.client.SpotCodes;
+
+/**
+ * This class provides the server side of the spot services.
+ */
+public class SpotProvider extends InvocationProvider
+ implements SpotCodes
+{
+ /**
+ * Constructs a spot provider and registers it with the invocation
+ * manager to handle spot services. This need be done by a server that
+ * wishes to make use of the Spot services.
+ */
+ public static void init (
+ InvocationManager invmgr, SceneRegistry screg)
+ {
+ // we'll need this later
+ _screg = screg;
+
+ // register a spot provider instance
+ invmgr.registerProvider(MODULE_NAME, new SpotProvider());
+ }
+
+ /**
+ * Processes a request from a client to move to a new location.
+ */
+ public void handleChangeLocRequest (
+ BodyObject source, int invid, int sceneId, int locationId)
+ {
+ try {
+ // look up the scene manager for the specified scene
+ SpotSceneManager smgr = (SpotSceneManager)
+ _screg.getSceneManager(sceneId);
+ if (smgr == null) {
+ Log.warning("User requested to change location in " +
+ "non-existent scene [user=" + source.username +
+ ", sceneId=" + sceneId +
+ ", locId=" + locationId + "].");
+ throw new ServiceFailedException(INTERNAL_ERROR);
+ }
+
+ smgr.handleChangeLocRequest(source, locationId);
+ sendResponse(source, invid, CHANGE_LOC_SUCCEEDED_RESPONSE);
+
+ } catch (ServiceFailedException sfe) {
+ sendResponse(source, invid, CHANGE_LOC_FAILED_RESPONSE,
+ sfe.getMessage());
+ }
+ }
+
+ /** The scene registry with which we interoperate. */
+ protected static SceneRegistry _screg;
+}
diff --git a/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java b/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java
new file mode 100644
index 000000000..3b67f88f0
--- /dev/null
+++ b/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java
@@ -0,0 +1,172 @@
+//
+// $Id: SpotSceneManager.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
+
+package com.threerings.whirled.spot.server;
+
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.dobj.Subscriber;
+import com.threerings.presents.dobj.ObjectAccessException;
+import com.threerings.presents.server.ServiceFailedException;
+
+import com.threerings.crowd.chat.ChatProvider;
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.whirled.server.SceneManager;
+
+import com.threerings.whirled.spot.Log;
+import com.threerings.whirled.spot.client.SpotCodes;
+import com.threerings.whirled.spot.data.SpotOccupantInfo;
+
+/**
+ * Handles the movement of bodies between locations in the scene and
+ * creates the necessary distributed objects to allow bodies in clusters
+ * to chat with one another.
+ */
+public class SpotSceneManager extends SceneManager
+ implements SpotCodes
+{
+ // documentation inherited
+ protected void gotSceneData ()
+ {
+ // keep a casted reference around to our scene
+ _sscene = (RuntimeSpotScene)_scene;
+
+ // now that we have our scene, we create chat objects for each of
+ // the clusters in the scene
+ _clusterOids = new int[_sscene.getClusterCount()];
+
+ // create a subscriber that will grab the oids when we hear back
+ // about object creation
+ Subscriber sub = new Subscriber() {
+ public void objectAvailable (DObject object) {
+ _clusterOids[_index++] = object.getOid();
+ }
+
+ public void requestFailed (int oid, ObjectAccessException cause) {
+ Log.warning("Failed to create cluster object " +
+ "[cluster=" + _index + ", oid=" + oid +
+ ", cause=" + cause + "].");
+ // skip to the next cluster in case others didn't fail
+ _index++;
+ }
+
+ protected int _index = 0;
+ };
+
+ // now issue the object creation requests
+ for (int i = 0; i < _clusterOids.length; i++) {
+ _omgr.createObject(DObject.class, sub);
+ }
+
+ // create an array in which to track the occupants of each
+ // location
+ _locationOccs = new int[_sscene.getLocationCount()];
+ }
+
+ /**
+ * Called by the {@link SpotProvider} when we receive a request by a
+ * user to occupy a particular location.
+ *
+ * @exception ServiceFailedException thrown with a reason code
+ * explaining the location change failure if there is a problem
+ * processing the location change request.
+ */
+ protected void handleChangeLocRequest (BodyObject source, int locationId)
+ throws ServiceFailedException
+ {
+ // make sure no one is already in the requested location
+ int locidx = _sscene.getLocationIndex(locationId);
+ if (locidx == -1 || _locationOccs[locidx] != 0) {
+ Log.info("Ignoring request to change to occupied or " +
+ "non-existent location [user=" + source.username +
+ ", locId=" + locationId + ", locidx=" + locidx + "].");
+ throw new ServiceFailedException(LOCATION_OCCUPIED);
+ }
+
+ // make sure they have an occupant info object in the place
+ int bodyOid = source.getOid();
+ SpotOccupantInfo soi = (SpotOccupantInfo)
+ _plobj.occupantInfo.get(new Integer(bodyOid));
+ if (soi == null) {
+ Log.warning("Aiya! Can't update non-existent occupant info " +
+ "with new location [body=" + source + "].");
+ throw new ServiceFailedException(INTERNAL_ERROR);
+ }
+
+ // stick our new friend into that location
+ _locationOccs[locidx] = bodyOid;
+ // update their occupant info
+ soi.locationId = locationId;
+ // and broadcast the update to the place
+ _plobj.updateOccupantInfo(soi);
+ }
+
+ /**
+ * Called by the {@link ClusterChatMessageHandler} when we receive a
+ * cluster chat request.
+ */
+ protected void handleClusterChatRequest (
+ BodyObject source, int locationId, String message)
+ {
+ // make sure this user occupies the specified location
+ int locidx = _sscene.getLocationIndex(locationId);
+ if (locidx == -1 || _locationOccs[locidx] != source.getOid()) {
+ Log.warning("User not in specified location for CCREQ " +
+ "[body=" + source + ", locId=" + locationId +
+ ", message=" + message + "].");
+ return;
+ }
+
+ // make sure there's a cluster associated with this location
+ int clusterIndex = _sscene.getClusterIndex(locidx);
+ if (clusterIndex == -1) {
+ Log.warning("User in clusterless location sent CCREQ " +
+ "[body=" + source + ", locId=" + locationId +
+ ", message=" + message + "].");
+ return;
+ }
+
+ // all is well, generate a chat notification
+ int clusterOid = _clusterOids[clusterIndex];
+ if (clusterOid > 0) {
+ ChatProvider.sendChatMessage(clusterOid, source.username, message);
+
+ } else {
+ Log.warning("Have no cluster object for CCREQ " +
+ "[cidx=" + clusterIndex +
+ ", chatter=" + source.username +
+ ", message=" + message + "].");
+ }
+ }
+
+ /**
+ * Returns the location index of the location occupied by the
+ * specified body oid or -1 if they occupy no location.
+ */
+ protected int getLocationIndex (int bodyOid)
+ {
+ for (int i = 0; i < _locationOccs.length; i++) {
+ if (_locationOccs[i] == bodyOid) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * We need our own extended occupant info to keep track of what
+ * location each occupant occupies.
+ */
+ protected Class getOccupantInfoClass ()
+ {
+ return SpotOccupantInfo.class;
+ }
+
+ /** A casted reference to our runtime scene instance. */
+ protected RuntimeSpotScene _sscene;
+
+ /** Oids of the cluster chat objects. */
+ protected int[] _clusterOids;
+
+ /** Oids of the bodies that occupy each of our locations. */
+ protected int[] _locationOccs;
+}