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