Behold Vilya, Ring of Air and repository for our game and virtual worldly

extensions to the distributed environment provided by Narya.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@1 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Michael Bayne
2006-06-23 17:58:11 +00:00
commit a4df87e52f
317 changed files with 45818 additions and 0 deletions
@@ -0,0 +1,56 @@
//
// $Id: Log.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot;
/**
* A placeholder class that contains a reference to the log object used by
* the Whirled Spot services.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("whirled.spot");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,36 @@
//
// $Id: SpotSceneController.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.client;
import com.threerings.whirled.client.SceneController;
/**
* The base spot scene controller class. It is expected that users of the
* Whirled Spot services will extend this controller class when creating
* specialized controllers for their scenes. Presently there are no basic
* scene services provided by this controller, but its existence affords
* the addition of such services should they become necessary in the
* future.
*/
public abstract class SpotSceneController extends SceneController
{
}
@@ -0,0 +1,492 @@
//
// $Id: SpotSceneDirector.java 4142 2006-05-23 23:16:14Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.client;
import com.samskivert.util.ResultListener;
import com.samskivert.util.StringUtil;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService.ConfirmListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.client.LocationAdapter;
import com.threerings.crowd.client.LocationDirector;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.whirled.client.SceneDirector;
import com.threerings.whirled.data.SceneModel;
import com.threerings.whirled.data.ScenedBodyObject;
import com.threerings.whirled.util.WhirledContext;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.data.ClusteredBodyObject;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
import com.threerings.whirled.spot.data.SpotCodes;
import com.threerings.whirled.spot.data.SpotScene;
/**
* Extends the standard scene director with facilities to move between
* locations within a scene.
*/
public class SpotSceneDirector extends BasicDirector
implements SpotCodes, Subscriber, AttributeChangeListener
{
/**
* Creates a new spot scene director with the specified context and
* which will cooperate with the supplied scene director.
*
* @param ctx the active client context.
* @param locdir the location director with which we will be
* cooperating.
* @param scdir the scene director with which we will be cooperating.
*/
public SpotSceneDirector (WhirledContext ctx, LocationDirector locdir,
SceneDirector scdir)
{
super(ctx);
_ctx = ctx;
_scdir = scdir;
// wire ourselves up to hear about leave place notifications
locdir.addLocationObserver(new LocationAdapter() {
public void locationDidChange (PlaceObject place) {
// we need to clear some things out when we leave a place
handleDeparture();
}
});
}
/**
* Configures this spot scene director with a chat director, with
* which it will coordinate to implement cluster chatting.
*/
public void setChatDirector (ChatDirector chatdir)
{
_chatdir = chatdir;
}
/**
* Returns our current location unless we have a location change
* pending, in which case our pending location is returned.
*/
public Location getIntendedLocation ()
{
return (_pendingLoc != null) ? _pendingLoc : _location;
}
/**
* Requests that this client move to the location specified by the
* supplied portal id. A request will be made and when the response is
* received, the location observers will be notified of success or
* failure.
*
* @return true if the request was issued, false if it was rejected by
* a location observer or because we have another request outstanding.
*/
public boolean traversePortal (int portalId)
{
return traversePortal(portalId, null);
}
/**
* Requests that this client move to the location specified by the
* supplied portal id. A request will be made and when the response is
* received, the location observers will be notified of success or
* failure.
*/
public boolean traversePortal (int portalId, ResultListener rl)
{
// look up the destination scene and location
SpotScene scene = (SpotScene)_scdir.getScene();
if (scene == null) {
Log.warning("Requested to traverse portal when we have " +
"no scene [portalId=" + portalId + "].");
return false;
}
// sanity check the server's notion of what scene we're in with
// our notion of it
int sceneId = _scdir.getScene().getId();
ScenedBodyObject sbobj = (ScenedBodyObject)
_ctx.getClient().getClientObject();
if (sceneId != sbobj.getSceneId()) {
Log.warning("Client and server differ in opinion of what scene " +
"we're in [sSceneId=" + sbobj.getSceneId() +
", cSceneId=" + sceneId + "].");
return false;
}
// find the portal they're talking about
Portal dest = scene.getPortal(portalId);
if (dest == null) {
Log.warning("Requested to traverse non-existent portal " +
"[portalId=" + portalId + ", portals=" +
StringUtil.toString(scene.getPortals()) + "].");
return false;
}
// prepare to move to this scene (sets up pending data)
if (!_scdir.prepareMoveTo(dest.targetSceneId, rl)) {
Log.info("Portal traversal vetoed by scene director " +
"[portalId=" + portalId + "].");
return false;
}
// check the version of our cached copy of the scene to which
// we're requesting to move; if we were unable to load it, assume
// a cached version of zero
int sceneVer = 0;
SceneModel pendingModel = _scdir.getPendingModel();
if (pendingModel != null) {
sceneVer = pendingModel.version;
}
// issue a traversePortal request
Log.info("Issuing traversePortal(" +
sceneId + ", " + dest + ", " + sceneVer + ").");
_sservice.traversePortal(
_ctx.getClient(), sceneId, portalId, sceneVer, _scdir);
return true;
}
/**
* Issues a request to change our location within the scene to the
* specified location.
*
* @param loc the new location to which to move.
* @param listener will be notified of success or failure. Most client
* entities find out about location changes via changes to the
* occupant info data, but the initiator of a location change request
* can be notified of its success or failure, primarily so that it can
* act in anticipation of a successful location change (like by
* starting a sprite moving toward the new location), but backtrack if
* it finds out that the location change failed.
*/
public void changeLocation (Location loc, final ResultListener listener)
{
// refuse if there's a pending location change or if we're already
// at the specified location
if (loc.equivalent(_location)) {
Log.info("Not going to " + loc + "; we're at " + _location +
" and we're headed to " + _pendingLoc + ".");
if (listener != null) {
// This isn't really a failure, it's just a no-op.
listener.requestCompleted(_location);
}
return;
}
if (_pendingLoc != null) {
Log.info("Not going to " + loc + "; we're at " + _location +
" and we're headed to " + _pendingLoc + ".");
if (listener != null) {
// Already moving, best thing to do is ignore it.
listener.requestCompleted(_pendingLoc);
}
return;
}
SpotScene scene = (SpotScene)_scdir.getScene();
if (scene == null) {
Log.warning("Requested to change locations, but we're not " +
"currently in any scene [loc=" + loc + "].");
if (listener != null) {
listener.requestFailed(new Exception("m.cant_get_there"));
}
return;
}
int sceneId = _scdir.getScene().getId();
Log.info("Sending changeLocation request [scid=" + sceneId +
", loc=" + loc + "].");
_pendingLoc = (Location)loc.clone();
ConfirmListener clist = new ConfirmListener() {
public void requestProcessed () {
_location = _pendingLoc;
_pendingLoc = null;
if (listener != null) {
listener.requestCompleted(_location);
}
}
public void requestFailed (String reason) {
_pendingLoc = null;
if (listener != null) {
listener.requestFailed(new Exception(reason));
}
}
};
_sservice.changeLocation(_ctx.getClient(), sceneId, loc, clist);
}
/**
* Issues a request to join the cluster associated with the specified
* user (starting one if necessary).
*
* @param froid the bodyOid of another user; the calling user will
* be made to join the target user's cluster.
* @param listener will be notified of success or failure.
*/
public void joinCluster (int froid, final ResultListener listener)
{
SpotScene scene = (SpotScene)_scdir.getScene();
if (scene == null) {
Log.warning("Requested to join cluster, but we're not " +
"currently in any scene [froid=" + froid + "].");
if (listener != null) {
listener.requestFailed(new Exception("m.cant_get_there"));
}
return;
}
Log.info("Joining cluster [friend=" + froid + "].");
_sservice.joinCluster(_ctx.getClient(), froid, new ConfirmListener() {
public void requestProcessed () {
if (listener != null) {
listener.requestCompleted(null);
}
}
public void requestFailed (String reason) {
if (listener != null) {
listener.requestFailed(new Exception(reason));
}
}
});
}
/**
* Sends a chat message to the other users in the cluster to which the
* location that we currently occupy belongs.
*
* @return true if a cluster speak message was delivered, false if we
* are not in a valid cluster and refused to deliver the request.
*/
public boolean requestClusterSpeak (String message)
{
return requestClusterSpeak(message, ChatCodes.DEFAULT_MODE);
}
/**
* Sends a chat message to the other users in the cluster to which the
* location that we currently occupy belongs.
*
* @return true if a cluster speak message was delivered, false if we
* are not in a valid cluster and refused to deliver the request.
*/
public boolean requestClusterSpeak (String message, byte mode)
{
// make sure we're currently in a scene
SpotScene scene = (SpotScene)_scdir.getScene();
if (scene == null) {
Log.warning("Requested to speak to cluster, but we're not " +
"currently in any scene [message=" + message + "].");
return false;
}
// make sure we're part of a cluster
if (_self.getClusterOid() <= 0) {
Log.info("Ignoring cluster speak as we're not in a cluster " +
"[cloid=" + _self.getClusterOid() + "].");
return false;
}
message = _chatdir.filter(message, null, true);
if (message != null) {
_sservice.clusterSpeak(_ctx.getClient(), message, mode);
}
return true;
}
// documentation inherited from interface
public void objectAvailable (DObject object)
{
clearCluster(false);
int oid = object.getOid();
if (oid != _self.getClusterOid()) {
// we got it too late, just unsubscribe
DObjectManager omgr = _ctx.getDObjectManager();
omgr.unsubscribeFromObject(oid, this);
} else {
// it's our new cluster!
_clobj = object;
if (_chatdir != null) {
_chatdir.addAuxiliarySource(object, CLUSTER_CHAT_TYPE);
}
}
}
// documentation inherited from interface
public void requestFailed (int oid, ObjectAccessException cause)
{
Log.warning("Unable to subscribe to cluster chat object " +
"[oid=" + oid + ", cause=" + cause + "].");
}
// documentation inherited from interface
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(_self.getClusterField()) &&
!event.getValue().equals(event.getOldValue())) {
maybeUpdateCluster();
}
}
// documentation inherited
public void clientDidLogon (Client client)
{
super.clientDidLogon(client);
ClientObject clientObj = client.getClientObject();
if (clientObj instanceof ClusteredBodyObject) {
// listen to the client object
clientObj.addListener(this);
_self = (ClusteredBodyObject) clientObj;
// we may need to subscribe to a cluster due to session resumption
maybeUpdateCluster();
}
}
// documentation inherited
public void clientObjectDidChange (Client client)
{
super.clientObjectDidChange(client);
// listen to the client object
ClientObject clientObj = client.getClientObject();
clientObj.addListener(this);
_self = (ClusteredBodyObject) clientObj;
}
// documentation inherited
public void clientDidLogoff (Client client)
{
super.clientDidLogoff(client);
// clear out our business
_location = null;
_pendingLoc = null;
_sservice = null;
clearCluster(true);
// stop listening to the client object
client.getClientObject().removeListener(this);
_self = null;
}
// documentation inherited
protected void fetchServices (Client client)
{
_sservice = (SpotService)client.requireService(SpotService.class);
}
/**
* Clean up after a few things when we depart from a scene.
*/
protected void handleDeparture ()
{
// clear out our last known location id
_location = null;
}
/**
* Checks to see if our cluster has changed and does the necessary
* subscription machinations if necessary.
*/
protected void maybeUpdateCluster ()
{
int cloid = _self.getClusterOid();
if ((_clobj == null && cloid <= 0) ||
(_clobj != null && cloid == _clobj.getOid())) {
// our cluster didn't change, we can stop now
return;
}
// clear out any old cluster object
clearCluster(false);
// if there's a new cluster object, subscribe to it
if (_chatdir != null && cloid > 0) {
DObjectManager omgr = _ctx.getDObjectManager();
// we'll wire up to the chat director when this completes
omgr.subscribeToObject(cloid, this);
}
}
/**
* Convenience routine to unwire chat for and unsubscribe from our
* current cluster, if any.
*
* @param force clear the cluster even if we're still apparently in it.
*/
protected void clearCluster (boolean force)
{
if (_clobj != null &&
(force || (_clobj.getOid() != _self.getClusterOid()))) {
if (_chatdir != null) {
_chatdir.removeAuxiliarySource(_clobj);
}
DObjectManager omgr = _ctx.getDObjectManager();
omgr.unsubscribeFromObject(_clobj.getOid(), this);
_clobj = null;
}
}
/** The active client context. */
protected WhirledContext _ctx;
/** Access to spot scene services. */
protected SpotService _sservice;
/** The scene director with which we are cooperating. */
protected SceneDirector _scdir;
/** A casted reference to our clustered body object. */
protected ClusteredBodyObject _self;
/** A reference to the chat director with which we coordinate. */
protected ChatDirector _chatdir;
/** The location we currently occupy. */
protected Location _location;
/** The location to which we have an outstanding change location
* request. */
protected Location _pendingLoc;
/** The cluster chat object for the cluster we currently occupy. */
protected DObject _clobj;
}
@@ -0,0 +1,83 @@
//
// $Id: SpotService.java 3363 2005-02-22 18:54:48Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.whirled.client.SceneService.SceneMoveListener;
import com.threerings.whirled.spot.data.Location;
/**
* Defines the mechanism by which the client can request to move around
* within a scene and between scenes (taking exit and entry locations into
* account). These services should not be used directly, but instead
* should be accessed via the {@link SpotSceneDirector}.
*/
public interface SpotService extends InvocationService
{
/**
* Requests to traverse the specified portal.
*
* @param sceneId the player's current scene which is used to sanity
* check things when the request actually arrives.
* @param portalId the portal to be traversed.
* @param destSceneVer the version of the destination scene data that
* the client has in its local repository.
*/
public void traversePortal (
Client client, int sceneId, int portalId, int destSceneVer,
SceneMoveListener listener);
/**
* Requests that this client's body be made to move to the specified
* location. The user will be removed from any cluster from which they
* are an occupant.
*
* @param sceneId the id of the scene in which to change location.
* @param loc the location to which to move.
*/
public void changeLocation (Client client, int sceneId, Location loc,
ConfirmListener listener);
/**
* Requests that this client start or join the specified cluster. They
* will be relocated appropriately by the scene manager.
*
* @param friendOid the bodyOid of another user or the oid of an
* existing cluster; the calling user will be made to join the cluster
* or target user's cluster, or create a cluster with the target user
* if they are not already in one.
*/
public void joinCluster (Client client, int friendOid,
ConfirmListener listener);
/**
* Requests that the supplied message be delivered to listeners in the
* cluster to which the specified location belongs.
*
* @param message the text of the message to be spoken.
* @param mode an associated mode constant that can be used to
* identify different kinds of "speech" (emote, thought bubble, etc.).
*/
public void clusterSpeak (Client client, String message, byte mode);
}
@@ -0,0 +1,77 @@
//
// $Id: Cluster.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import java.awt.Rectangle;
import com.samskivert.util.StringUtil;
import com.threerings.io.Streamable;
import com.threerings.presents.dobj.DSet;
/**
* Contains information on clusters.
*/
public class Cluster extends Rectangle
implements DSet.Entry, Streamable
{
/** A unique identifier for this cluster (also the distributed object
* id of the cluster chat object). */
public int clusterOid;
// documentation inherited
public Comparable getKey ()
{
if (_key == null) {
_key = Integer.valueOf(clusterOid);
}
return _key;
}
// documentation inherited
public boolean equals (Object other)
{
if (other instanceof Cluster) {
return ((Cluster)other).clusterOid == clusterOid;
} else {
return false;
}
}
// documentation inherited
public int hashCode ()
{
return clusterOid;
}
/**
* Generates a string representation of this instance.
*/
public String toString ()
{
return StringUtil.fieldsToString(this);
}
/** Used for {@link #getKey}. */
protected transient Integer _key;
}
@@ -0,0 +1,74 @@
//
// $Id: ClusterObject.java 3288 2004-12-28 03:51:29Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.OidList;
import com.threerings.crowd.chat.data.SpeakObject;
/**
* Used to dispatch chat in clusters.
*/
public class ClusterObject extends DObject
implements SpeakObject
{
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>occupants</code> field. */
public static final String OCCUPANTS = "occupants";
// AUTO-GENERATED: FIELDS END
/**
* Tracks the oid of the body objects that occupy this cluster.
*/
public OidList occupants = new OidList();
// documentation inherited
public void applyToListeners (ListenerOp op)
{
for (int ii = 0, ll = occupants.size(); ii < ll; ii++) {
op.apply(occupants.get(ii));
}
}
// AUTO-GENERATED: METHODS START
/**
* Requests that <code>oid</code> be added to the <code>occupants</code>
* oid list. The list will not change until the event is actually
* propagated through the system.
*/
public void addToOccupants (int oid)
{
requestOidAdd(OCCUPANTS, oid);
}
/**
* Requests that <code>oid</code> be removed from the
* <code>occupants</code> oid list. The list will not change until the
* event is actually propagated through the system.
*/
public void removeFromOccupants (int oid)
{
requestOidRemove(OCCUPANTS, oid);
}
// AUTO-GENERATED: METHODS END
}
@@ -0,0 +1,47 @@
//
// $Id: ClusteredBodyObject.java 3310 2005-01-24 23:08:21Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.whirled.data.ScenedBodyObject;
/**
* Defines some required methods for a {@link ScenedBodyObject} that is to
* participate in the Whirled Spot system.
*/
public interface ClusteredBodyObject extends ScenedBodyObject
{
/**
* Returns the field name of the cluster oid distributed object field.
*/
public String getClusterField ();
/**
* Returns the oid of the cluster to which this user currently
* belongs.
*/
public int getClusterOid ();
/**
* Sets the oid of the cluster to which this user currently belongs.
*/
public void setClusterOid (int clusterOid);
}
@@ -0,0 +1,34 @@
package com.threerings.whirled.spot.data;
import com.threerings.io.Streamable;
public interface Location extends Streamable, Cloneable
{
/**
* Get a new Location instance that is equals() to this one but that
* has an orientation facing the opposite direction.
*/
public Location getOpposite ();
/**
* Two locations are equivalent if they specify the same location
* and orientation.
*/
public boolean equivalent (Location other);
/**
* Two locations are equals if they specify the same coordinates, but
* the orientation may be different.
*/
public boolean equals (Object other);
/**
* The hashcode of a Location should be based only on its coordinates.
*/
public int hashCode ();
/**
* Locations are cloneable.
*/
public Object clone ();
}
@@ -0,0 +1,112 @@
//
// $Id: Portal.java 4140 2006-05-23 21:44:05Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.io.SimpleStreamableObject;
/**
* Represents an exit to another scene. A body sprite would walk over to a
* portal's coordinates and then either proceed off of the edge of the
* display, or open a door and walk through it, or fizzle away in a Star
* Trekkian transporter style or whatever is appropriate for the game in
* question. It contains information on the scene to which the body exits
* when using this portal and the location at which the body sprite should
* appear in that target scene.
*/
public class Portal extends SimpleStreamableObject
implements Cloneable
{
/** This portal's unique identifier. */
public short portalId;
/** The location of the portal.
* This field is present on client and server, it is streamed specially. */
public Location loc;
/** The scene identifier of the scene to which a body will exit when
* they "use" this portal. */
public int targetSceneId;
/** The portal identifier of the portal at which a body will enter
* the target scene when they "use" this portal. */
public short targetPortalId;
/**
* Returns a location instance configured with the location and
* orientation of this portal.
*/
public Location getLocation ()
{
return (Location) loc.clone();
}
/**
* Returns a location instance configured with the location and
* opposite orientation of this portal. This is useful for when a body
* is entering a scene at a portal and we want them to face the
* opposite direction (as they are entering via the portal rather than
* leaving, which is the natural "orientation" of a portal).
*/
public Location getOppLocation ()
{
return loc.getOpposite();
}
/**
* Returns true if the portal has a potentially valid target scene and
* portal id (they are not guaranteed to exist, but they are at least
* potentially valid values rather than -1 or 0).
*/
public boolean isValid ()
{
return (targetSceneId > 0) && (targetPortalId > 0);
}
/**
* Creates a clone of this instance.
*/
public Object clone ()
{
try {
return (Portal)super.clone();
} catch (CloneNotSupportedException cnse) {
throw new RuntimeException("Portal.clone() failed " + cnse);
}
}
/**
* Portal equality is determined by portal id.
*/
public boolean equals (Object other)
{
return (other instanceof Portal) &&
((Portal) other).portalId == portalId;
}
/**
* Computes a reasonable hashcode for portal instances.
*/
public int hashCode ()
{
return portalId;
}
}
@@ -0,0 +1,90 @@
//
// $Id: SceneLocation.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.presents.dobj.DSet;
/**
* Extends {@link Location} with the data and functionality needed to
* represent a particular user's location in a scene.
*/
public class SceneLocation extends SimpleStreamableObject
implements DSet.Entry
{
/** The oid of the body that occupies this location. */
public int bodyOid;
/** The actual location, which is interpreted by the display system. */
public Location loc;
/**
* Creates a scene location with the specified information.
*/
public SceneLocation (Location loc, int bodyOid)
{
this.loc = loc;
this.bodyOid = bodyOid;
}
/**
* Creates a blank instance suitable for unserialization.
*/
public SceneLocation ()
{
}
// documentation inherited
public Comparable getKey ()
{
if (_key == null) {
_key = Integer.valueOf(bodyOid);
}
return _key;
}
// documentation inherited
public boolean equals (Object other)
{
// TEMP
if (other instanceof Location) {
com.threerings.whirled.spot.Log.warning(
"Illegal compare of SceneLocation and Location!!!");
Thread.dumpStack(); // this will help us find logic errors,
// as a SceneLocation and a Location shouldn't be compared
}
// END: temp
return (other instanceof SceneLocation) &&
this.loc.equals(((SceneLocation) other).loc);
}
// documentation inherited
public int hashCode ()
{
return loc.hashCode();
}
/** Used for {@link #getKey}. */
protected transient Integer _key;
}
@@ -0,0 +1,57 @@
//
// $Id: SpotCodes.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.whirled.data.SceneCodes;
/**
* Contains codes used by the Spot invocation services.
*/
public interface SpotCodes extends ChatCodes, SceneCodes
{
/** An error code indicating that the portal specified in a
* traversePortal request does not exist. */
public static final String NO_SUCH_PORTAL = "m.no_such_portal";
/** 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";
/** An error code indicating that a location is not valid. Usually
* generated by a failed changeLoc request. */
public static final String INVALID_LOCATION = "m.invalid_location";
/** An error code indicating that a cluster is not valid. Usually
* generated by a failed joinCluster request. */
public static final String NO_SUCH_CLUSTER = "m.no_such_cluster";
/** An error code indicating that a cluster is full. Usually generated
* by a failed joinCluster request. */
public static final String CLUSTER_FULL = "m.cluster_full";
/** The chat type code with which we register our cluster auxiliary
* chat objects. Chat display implementations should interpret chat
* messages with this type accordingly. */
public static final String CLUSTER_CHAT_TYPE = "clusterChat";
}
@@ -0,0 +1,93 @@
//
// $Id: SpotMarshaller.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
import com.threerings.whirled.client.SceneService;
import com.threerings.whirled.data.SceneMarshaller;
import com.threerings.whirled.spot.client.SpotService;
import com.threerings.whirled.spot.data.Location;
/**
* Provides the implementation of the {@link SpotService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
public class SpotMarshaller extends InvocationMarshaller
implements SpotService
{
/** The method id used to dispatch {@link #changeLocation} requests. */
public static final int CHANGE_LOCATION = 1;
// documentation inherited from interface
public void changeLocation (Client arg1, int arg2, Location arg3, InvocationService.ConfirmListener arg4)
{
InvocationMarshaller.ConfirmMarshaller listener4 = new InvocationMarshaller.ConfirmMarshaller();
listener4.listener = arg4;
sendRequest(arg1, CHANGE_LOCATION, new Object[] {
Integer.valueOf(arg2), arg3, listener4
});
}
/** The method id used to dispatch {@link #clusterSpeak} requests. */
public static final int CLUSTER_SPEAK = 2;
// documentation inherited from interface
public void clusterSpeak (Client arg1, String arg2, byte arg3)
{
sendRequest(arg1, CLUSTER_SPEAK, new Object[] {
arg2, Byte.valueOf(arg3)
});
}
/** The method id used to dispatch {@link #joinCluster} requests. */
public static final int JOIN_CLUSTER = 3;
// documentation inherited from interface
public void joinCluster (Client arg1, int arg2, InvocationService.ConfirmListener arg3)
{
InvocationMarshaller.ConfirmMarshaller listener3 = new InvocationMarshaller.ConfirmMarshaller();
listener3.listener = arg3;
sendRequest(arg1, JOIN_CLUSTER, new Object[] {
Integer.valueOf(arg2), listener3
});
}
/** The method id used to dispatch {@link #traversePortal} requests. */
public static final int TRAVERSE_PORTAL = 4;
// documentation inherited from interface
public void traversePortal (Client arg1, int arg2, int arg3, int arg4, SceneService.SceneMoveListener arg5)
{
SceneMarshaller.SceneMoveMarshaller listener5 = new SceneMarshaller.SceneMoveMarshaller();
listener5.listener = arg5;
sendRequest(arg1, TRAVERSE_PORTAL, new Object[] {
Integer.valueOf(arg2), Integer.valueOf(arg3), Integer.valueOf(arg4), listener5
});
}
}
@@ -0,0 +1,80 @@
//
// $Id: SpotScene.java 3451 2005-03-31 19:40:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import java.util.Iterator;
/**
* Makes available the spot scene information that the server needs to do
* its business.
*/
public interface SpotScene
{
/**
* Returns a {@link Portal} object for the portal with the specified
* id or null if no portal exists with that id.
*/
public Portal getPortal (int portalId);
/**
* Returns the number of portals in this scene.
*/
public int getPortalCount ();
/**
* Returns an iterator over the portals in this scene.
*/
public Iterator getPortals ();
/**
* Returns the portal id that should be assigned to the next portal
* added to this scene.
*/
public short getNextPortalId ();
/**
* Returns the portal that represents the default entrance to this
* scene. If a body enters the scene at logon time rather than
* entering from some other scene, this is the portal at which they
* would appear.
*/
public Portal getDefaultEntrance ();
/**
* Adds a portal to this scene, immediately making the requisite
* modifications to the underlying scene model. The portal id should
* have already been assigned using the value obtained from {@link
* #getNextPortalId}.
*/
public void addPortal (Portal portal);
/**
* Removes the specified portal from the scene.
*/
public void removePortal (Portal portal);
/**
* Sets the default entrance in this scene, immediately making the
* requisite modifications to the underlying scene model.
*/
public void setDefaultEntrance (Portal portal);
}
@@ -0,0 +1,157 @@
//
// $Id: SpotSceneImpl.java 3451 2005-03-31 19:40:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
import com.threerings.whirled.spot.Log;
/**
* An implementation of the {@link SpotScene} interface.
*/
public class SpotSceneImpl
implements SpotScene
{
/**
* Creates an instance that will obtain data from the supplied spot
* scene model.
*/
public SpotSceneImpl (SpotSceneModel smodel)
{
_smodel = smodel;
readPortals();
}
protected void readPortals ()
{
_portals.clear();
for (int ii = 0, ll = _smodel.portals.length; ii < ll; ii++) {
Portal port = _smodel.portals[ii];
_portals.put(port.portalId, port);
}
}
/**
* Instantiates a blank scene implementation.
*/
public SpotSceneImpl ()
{
_smodel = new SpotSceneModel();
}
// documentation inherited from interface
public Portal getPortal (int portalId)
{
return (Portal)_portals.get(portalId);
}
// documentation inherited from interface
public int getPortalCount ()
{
return _portals.size();
}
// documentation inherited from interface
public Iterator getPortals ()
{
return _portals.values().iterator();
}
// documentation inherited from interface
public short getNextPortalId ()
{
// compute a new portal id for our friend the portal
for (short ii = 1; ii < MAX_PORTAL_ID; ii++) {
if (!_portals.containsKey(ii)) {
return ii;
}
}
return (short)-1;
}
// documentation inherited from interface
public Portal getDefaultEntrance ()
{
return getPortal(_smodel.defaultEntranceId);
}
// documentation inherited from interface
public void addPortal (Portal portal)
{
if (portal.portalId <= 0) {
Log.warning("Refusing to add zero-id portal " +
"[scene=" + this + ", portal=" + portal + "].");
return;
}
// add it to our model
_smodel.addPortal(portal);
// and slap it into our table
_portals.put(portal.portalId, portal);
}
// documentation inherited from interface
public void removePortal (Portal portal)
{
// remove the portal from our mapping
_portals.remove(portal.portalId);
// remove it from the model
_smodel.removePortal(portal);
}
/**
* Used when we're being parsed from an XML scene model.
*/
public void setDefaultEntranceId (int defaultEntranceId)
{
_smodel.defaultEntranceId = defaultEntranceId;
}
// documentation inherited from interface
public void setDefaultEntrance (Portal portal)
{
_smodel.defaultEntranceId = (portal == null) ? -1 : portal.portalId;
}
/**
* This should be called if a scene update was received that caused
* our underlying scene model to change.
*/
public void updateReceived ()
{
readPortals();
}
/** A casted reference to our scene model. */
protected SpotSceneModel _smodel;
/** A mapping from portal id to portal. */
protected HashIntMap _portals = new HashIntMap();
/** We don't allow more than ~32k portals in a scene. Things would
* slow down *way* before we got there. */
protected static final int MAX_PORTAL_ID = Short.MAX_VALUE;
}
@@ -0,0 +1,96 @@
//
// $Id: SpotSceneModel.java 3726 2005-10-11 19:17:43Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.whirled.data.AuxModel;
import com.threerings.whirled.data.SceneModel;
/**
* The spot scene model extends the standard scene model with information
* on portals. Portals are referenced by an identifier, unique within the
* scene and unchanging, so that portals can stably reference the target
* portal in the scene to which they connect.
*/
public class SpotSceneModel extends SimpleStreamableObject
implements AuxModel
{
/** An array containing all portals in this scene. */
public Portal[] portals = new Portal[0];
/** The portal id of the default entrance to this scene. If a body
* enters the scene without coming from another scene, this is the
* portal at which they would appear. */
public int defaultEntranceId = -1;
/**
* Adds a portal to this scene model.
*/
public void addPortal (Portal portal)
{
portals = (Portal[])ArrayUtil.append(portals, portal);
}
/**
* Removes a portal from this model.
*/
public void removePortal (Portal portal)
{
int pidx = ListUtil.indexOf(portals, portal);
if (pidx != -1) {
portals = (Portal[])ArrayUtil.splice(portals, pidx, 1);
}
}
// documentation inherited
public Object clone ()
throws CloneNotSupportedException
{
SpotSceneModel model = (SpotSceneModel)super.clone();
// clone our portals individually
model.portals = new Portal[portals.length];
for (int ii = 0, ll = portals.length; ii < ll; ii++) {
model.portals[ii] = (Portal)portals[ii].clone();
}
return model;
}
/**
* Locates and returns the {@link SpotSceneModel} among the auxiliary
* scene models associated with the supplied scene
* model. <code>null</code> is returned if no spot scene model could
* be found.
*/
public static SpotSceneModel getSceneModel (SceneModel model)
{
for (int ii = 0; ii < model.auxModels.length; ii++) {
if (model.auxModels[ii] instanceof SpotSceneModel) {
return (SpotSceneModel)model.auxModels[ii];
}
}
return null;
}
}
@@ -0,0 +1,140 @@
//
// $Id: SpotSceneObject.java 4166 2006-05-31 04:16:57Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.data;
import com.threerings.presents.dobj.DSet;
import com.threerings.whirled.data.SceneObject;
/**
* Extends the {@link SceneObject} with information specific to spots.
*/
public class SpotSceneObject extends SceneObject
{
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>occupantLocs</code> field. */
public static final String OCCUPANT_LOCS = "occupantLocs";
/** The field name of the <code>clusters</code> field. */
public static final String CLUSTERS = "clusters";
// AUTO-GENERATED: FIELDS END
/** A distributed set containing {@link SceneLocation} records for all
* occupants of this scene. */
public DSet occupantLocs = new DSet();
/** Contains information on all {@link Cluster}s in this scene. */
public DSet clusters = new DSet();
// AUTO-GENERATED: METHODS START
/**
* Requests that the specified entry be added to the
* <code>occupantLocs</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void addToOccupantLocs (DSet.Entry elem)
{
requestEntryAdd(OCCUPANT_LOCS, occupantLocs, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>occupantLocs</code> set. The set will not change until the
* event is actually propagated through the system.
*/
public void removeFromOccupantLocs (Comparable key)
{
requestEntryRemove(OCCUPANT_LOCS, occupantLocs, key);
}
/**
* Requests that the specified entry be updated in the
* <code>occupantLocs</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void updateOccupantLocs (DSet.Entry elem)
{
requestEntryUpdate(OCCUPANT_LOCS, occupantLocs, elem);
}
/**
* Requests that the <code>occupantLocs</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setOccupantLocs (DSet value)
{
requestAttributeChange(OCCUPANT_LOCS, value, this.occupantLocs);
this.occupantLocs = (value == null) ? null : value.typedClone();
}
/**
* Requests that the specified entry be added to the
* <code>clusters</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void addToClusters (DSet.Entry elem)
{
requestEntryAdd(CLUSTERS, clusters, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>clusters</code> set. The set will not change until the
* event is actually propagated through the system.
*/
public void removeFromClusters (Comparable key)
{
requestEntryRemove(CLUSTERS, clusters, key);
}
/**
* Requests that the specified entry be updated in the
* <code>clusters</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void updateClusters (DSet.Entry elem)
{
requestEntryUpdate(CLUSTERS, clusters, elem);
}
/**
* Requests that the <code>clusters</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setClusters (DSet value)
{
requestAttributeChange(CLUSTERS, value, this.clusters);
this.clusters = (value == null) ? null : value.typedClone();
}
// AUTO-GENERATED: METHODS END
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,108 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<!-- $Id: package.html 617 2001-11-13 00:12:20Z mdb $ -->
</head>
<body bgcolor="white">
Extends the Whirled services with the notion of locations, clusters,
coordinates and orientation.
<p> The basic Whirled services facilitate a game that provides a
connected set of scenes and the ability for the bodies to move from
a scene to its neighboring scenes. The Spot package extends those
services by providing the notion of locations within scenes which a
body can occupy and which, while occupied, cannot be occupied by
other bodies.
</p>
<p> The philosophy is that a game that uses these services wants to
create a constrained universe where a body cannot simply stand
anywhere on the screen, but can only stand in a set of predefined
locations. This is a simplified mechanism for providing finer
grained locality management, such that expensive distance monitoring
algorithms need not be used to determine who can interact (speak)
with others in a particular scene. For many games, the overhead of
tracking each users' position in near-realtime coupled with a
complex message broadcasting system that dispatches messages to
users in geometric proximity to one another is serious overkill and
does not add sufficient value to the game to merit inclusion.
</p>
<p><b>Locations and clusters</b><br>
The Spot package provides a functional approximation in the form of
clusters of locations, with predefined coordinates, that are
sufficiently proximal that bodies in that cluster of locations can
all speak to one another. The locations for a particular scene are
specified in advance which serves the additional purpose of putting
a limit on the number of bodies that can be standing (and
participating) in a particular scene. This puts a natural upper
bound on the amount of network traffic that will be generated by the
need to broadcast scene updates to all occupants of a scene.
</p>
<p> Locations also serve as entry and exit points for a scene,
providing a means both for the graphical user interface to identify
the location at which bodies enter and exit a scene as well as
providing a spatial context for the connections between scenes
(i.e. the exit to the scene to the north can be located in the
northward part of a scene display).
</p>
<p><b>Orientation</b><br>
Locations define a notion of orientation which is assigned a priori,
along with the coordinates of the location and which can be
interpreted by the graphical display as the direction that body
sprites should face when standing on that location. Clusters of
locations can be constructed such that all of the bodies standing in
that cluster appear to be facing one another which provides visible
reinforcement that members of the cluster can "hear" the
conversation, while members of other clusters cannot. The
orientation of entry and exit locations are also useful in
establishing an initial orientation for a body sprite that enters a
scene based on the visualization of the entrance mechanism (i.e. if
they appear to enter through the door, the body sprite should be
oriented as if it just walked in the proper direction through the
door).
</p>
<p> Because the Spot services redefine the scope at which speak
messages are normally distributed (the Crowd services would, by
default, have speak messages dispatched to everyone in the entire
scene because the scene is a place and the default chat mechanisms
dispatch speak messages to all occupants of a place), it defines an
additional communication mode known as shouting, which conveys the
message to everyone in the entire scene.
</p>
<p><b>Portals</b><br>
Portals are a combination of locations and the standard Whirled
notion of neighboring scenes. They contain the coordinates to which
a body sprite would traverse prior to exiting to a neighboring scene
(generally so that they appear to walk to whatever visual
representation of the portal is provided by the scene, like a door
or perhaps simply the edge of the scene). Conversely, they represent
the coordinates at which a body arriving from same neighboring scene
would appear in the displayed scene.
</p>
<p><b>Example display</b><br>
This is an example of how these concepts might look in a game
providing top-down 2D display of its scenes. <br>
</p>
<center>
<img align="center" src="images/scene.png" alt="Example scene image">
</center>
<p> The arrows would, of course, not be rendered but are shown to
indicate the presence of locations (and portals) and their
associated orientation information.
</p>
</body>
</html>
@@ -0,0 +1,34 @@
//
// $Id: SpotClient.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.server;
import com.threerings.whirled.server.WhirledClient;
/**
* Extends the Whirled client and handles the necessary notifications that
* take place when a user logs off to let the clients displaying the scene
* they were occupying know that they didn't exit via a portal, but
* instead just disappeared.
*/
public class SpotClient extends WhirledClient
{
}
@@ -0,0 +1,95 @@
//
// $Id: SpotDispatcher.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.server;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
import com.threerings.whirled.client.SceneService;
import com.threerings.whirled.data.SceneMarshaller;
import com.threerings.whirled.spot.client.SpotService;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.SpotMarshaller;
/**
* Dispatches requests to the {@link SpotProvider}.
*/
public class SpotDispatcher extends InvocationDispatcher
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public SpotDispatcher (SpotProvider provider)
{
this.provider = provider;
}
// documentation inherited
public InvocationMarshaller createMarshaller ()
{
return new SpotMarshaller();
}
// documentation inherited
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case SpotMarshaller.CHANGE_LOCATION:
((SpotProvider)provider).changeLocation(
source,
((Integer)args[0]).intValue(), (Location)args[1], (InvocationService.ConfirmListener)args[2]
);
return;
case SpotMarshaller.CLUSTER_SPEAK:
((SpotProvider)provider).clusterSpeak(
source,
(String)args[0], ((Byte)args[1]).byteValue()
);
return;
case SpotMarshaller.JOIN_CLUSTER:
((SpotProvider)provider).joinCluster(
source,
((Integer)args[0]).intValue(), (InvocationService.ConfirmListener)args[1]
);
return;
case SpotMarshaller.TRAVERSE_PORTAL:
((SpotProvider)provider).traversePortal(
source,
((Integer)args[0]).intValue(), ((Integer)args[1]).intValue(), ((Integer)args[2]).intValue(), (SceneService.SceneMoveListener)args[3]
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,324 @@
//
// $Id: SpotProvider.java 3832 2006-02-04 03:49:53Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.server;
import com.samskivert.util.StringUtil;
import com.threerings.util.MessageManager;
import com.threerings.util.Name;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.server.SpeakProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.server.PlaceRegistry;
import com.threerings.whirled.client.SceneService.SceneMoveListener;
import com.threerings.whirled.data.ScenedBodyObject;
import com.threerings.whirled.server.SceneManager;
import com.threerings.whirled.server.SceneRegistry;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.client.SpotService;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
import com.threerings.whirled.spot.data.SpotCodes;
import com.threerings.whirled.spot.data.SpotScene;
/**
* Provides the server-side implementation of the spot services.
*/
public class SpotProvider
implements SpotCodes, InvocationProvider
{
/**
* Creates a spot provider that can be registered with the invocation
* manager to handle spot services.
*/
public SpotProvider (RootDObjectManager omgr, PlaceRegistry plreg,
SceneRegistry screg)
{
// we'll need these later
_plreg = plreg;
_screg = screg;
_omgr = omgr;
}
/**
* Processes a {@link SpotService#traversePortal} request.
*/
public void traversePortal (ClientObject caller, int sceneId, int portalId,
int destSceneVer, SceneMoveListener listener)
throws InvocationException
{
// le sanity check
int cSceneId = getCallerSceneId(caller);
if (cSceneId != sceneId) {
Log.info("Ignoring stale traverse portal request " +
"[caller=" + caller.who() + ", oSceneId=" + sceneId +
", portalId=" + portalId + ", cSceneId=" + cSceneId + "].");
InvocationMarshaller.setNoResponse(listener);
return;
}
// avoid cluttering up the method declaration with final keywords
final BodyObject fsource = (BodyObject)caller;
final int fportalId = portalId;
final int fsceneVer = destSceneVer;
final SceneMoveListener flistener = listener;
// obtain the source scene
final SpotSceneManager srcmgr = (SpotSceneManager)
_screg.getSceneManager(sceneId);
if (srcmgr == null) {
Log.warning("Traverse portal missing source scene " +
"[user=" + fsource.who() + ", sceneId=" + sceneId +
", portalId=" + portalId + "].");
throw new InvocationException(INTERNAL_ERROR);
}
// obtain the destination scene and location id
SpotScene rss = (SpotScene)srcmgr.getScene();
final Portal fdest = rss.getPortal(portalId);
// give the source scene manager a chance to do access control
String errmsg = srcmgr.mayTraversePortal(fsource, fdest);
if (errmsg != null) {
throw new InvocationException(errmsg);
}
// make sure this portal has valid info
if (fdest == null || !fdest.isValid()) {
Log.warning("Traverse portal with invalid portal " +
"[user=" + fsource.who() + ", scene=" + srcmgr.where() +
", pid=" + portalId + ", portal=" + fdest +
", portals=" + StringUtil.toString(rss.getPortals()) +
"].");
throw new InvocationException(NO_SUCH_PORTAL);
}
// resolve their destination scene
SceneRegistry.ResolutionListener rl =
new SceneRegistry.ResolutionListener() {
public void sceneWasResolved (SceneManager scmgr) {
// make sure our caller is still around; under heavy
// load, clients might end their session while the
// scene is resolving
if (!fsource.isActive()) {
Log.info("Abandoning portal traversal, client gone " +
"[who=" + fsource.who() +
", dest=" + scmgr.where() + "].");
InvocationMarshaller.setNoResponse(flistener);
return;
}
// let the source manager know that this guy is
// departing via the specified portal
srcmgr.willTraversePortal(fsource, fdest);
SpotSceneManager sscmgr = (SpotSceneManager)scmgr;
finishTraversePortalRequest(
fsource, sscmgr, fsceneVer, fdest, flistener);
}
public void sceneFailedToResolve (
int rsceneId, Exception reason) {
Log.warning("Unable to resolve target scene " +
"[sceneId=" + rsceneId +
", reason=" + reason + "].");
// pretend like the scene doesn't exist to the client
flistener.requestFailed(NO_SUCH_PLACE);
}
};
_screg.resolveScene(fdest.targetSceneId, rl);
}
/**
* This is called after the scene to which we are moving is guaranteed
* to have been loaded into the server.
*/
protected void finishTraversePortalRequest (
BodyObject source, SpotSceneManager destmgr, int sceneVer,
Portal dest, SceneMoveListener listener)
{
// let the destination scene manager know that we're coming in
destmgr.mapEnteringBody(source, dest.targetPortalId);
try {
// move to the place object associated with this scene
_screg.sceneprov.effectSceneMove(
source, destmgr, sceneVer, listener);
} catch (InvocationException sfe) {
listener.requestFailed(sfe.getMessage());
// and let the destination scene manager know that we're no
// longer coming in
destmgr.clearEnteringBody(source);
}
}
/**
* Processes a {@link SpotService#changeLocation} request.
*/
public void changeLocation (ClientObject caller, int sceneId, Location loc,
SpotService.ConfirmListener listener)
throws InvocationException
{
BodyObject source = (BodyObject)caller;
int cSceneId = getCallerSceneId(caller);
if (cSceneId != sceneId) {
Log.info("Rejecting changeLocation for invalid scene " +
"[user=" + source.who() + ", insid=" + cSceneId +
", wantsid=" + sceneId + ", loc=" + loc + "].");
throw new InvocationException(INVALID_LOCATION);
}
// 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 from " +
"non-existent scene [user=" + source.who() +
", sceneId=" + sceneId + ", loc=" + loc +"].");
throw new InvocationException(INTERNAL_ERROR);
}
// pass the buck to yon scene manager
smgr.handleChangeLoc(source, loc);
// if that method finished, we're good to go
listener.requestProcessed();
}
/**
* Processes a {@link SpotService#joinCluster} request.
*/
public void joinCluster (ClientObject caller, int friendOid,
SpotService.ConfirmListener listener)
throws InvocationException
{
int sceneId = getCallerSceneId(caller);
BodyObject source = (BodyObject)caller;
// look up the scene manager for the specified scene
SpotSceneManager smgr = (SpotSceneManager)
_screg.getSceneManager(sceneId);
if (smgr == null) {
Log.warning("User requested to join cluster from " +
"non-existent scene [user=" + source.who() +
", sceneId=" + sceneId + ", foid=" + friendOid +"].");
throw new InvocationException(INTERNAL_ERROR);
}
// pass the buck to yon scene manager
smgr.handleJoinCluster(source, friendOid);
// if that method finished, we're good to go
listener.requestProcessed();
}
/**
* Handles request to generate a speak message in the specified cluster.
*/
public void clusterSpeak (ClientObject caller, String message, byte mode)
throws InvocationException
{
// ensure the caller has normal chat access
BodyObject source = (BodyObject)caller;
String errmsg = source.checkAccess(ChatCodes.CHAT_ACCESS, null);
if (errmsg != null) {
SpeakProvider.sendFeedback(source,
MessageManager.GLOBAL_BUNDLE, errmsg);
return;
}
sendClusterChatMessage(getCallerSceneId(caller), source.getOid(),
source.getVisibleName(), null, message, mode);
}
/**
* Sends a cluster chat notification to the specified location in the
* specified place object originating with the specified speaker (the
* speaker can be a server entity that wishes to fake a "speak"
* message, in which case the bundle argument should be non-null and
* should contain the id of the bundle to be used to translate the
* message text) and with the supplied message content.
*
* @param sceneId the scene id in which to deliver the chat message.
* @param speakerOid the body object id of the speaker (used to verify
* that they are in the cluster in question).
* @param speaker the username of the user that generated the message
* (or some special speaker name for server messages).
* @param bundle the bundle identifier that will be used by the client
* to translate the message text (or null if the message originated
* from a real live human who wrote it in their native tongue).
* @param message the text of the chat message.
*/
public void sendClusterChatMessage (
int sceneId, int speakerOid, Name speaker,
String bundle, String message, byte mode)
{
// look up the scene manager for the specified scene
SpotSceneManager smgr = (SpotSceneManager)
_screg.getSceneManager(sceneId);
if (smgr == null) {
Log.warning("User requested cluster chat in non-existent scene " +
"[user=" + speaker + ", sceneId=" + sceneId +
", message=" + message + "].");
return;
}
// pass this request on to the spot scene manager
smgr.handleClusterSpeakRequest(
speakerOid, speaker, bundle, message, mode);
}
/**
* Obtains the scene id occupied by the supplied caller.
*
* @exception InvocationException thrown if the caller does not
* implement {@link ScenedBodyObject}.
*/
protected static int getCallerSceneId (ClientObject caller)
throws InvocationException
{
if (caller instanceof ScenedBodyObject) {
return ((ScenedBodyObject)caller).getSceneId();
} else {
Log.warning("Can't get scene from non-scened caller " +
caller.who() + ".");
throw new InvocationException(INTERNAL_ERROR);
}
}
/** The place registry with which we interoperate. */
protected PlaceRegistry _plreg;
/** The scene registry with which we interoperate. */
protected SceneRegistry _screg;
/** The object manager we use to do dobject stuff. */
protected RootDObjectManager _omgr;
}
@@ -0,0 +1,626 @@
//
// $Id: SpotSceneManager.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.server;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.IntIntMap;
import com.threerings.util.Name;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.server.InvocationException;
import com.threerings.crowd.chat.server.SpeakProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.server.CrowdServer;
import com.threerings.whirled.server.SceneManager;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.data.Cluster;
import com.threerings.whirled.spot.data.ClusterObject;
import com.threerings.whirled.spot.data.ClusteredBodyObject;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
import com.threerings.whirled.spot.data.SceneLocation;
import com.threerings.whirled.spot.data.SpotCodes;
import com.threerings.whirled.spot.data.SpotScene;
import com.threerings.whirled.spot.data.SpotSceneObject;
/**
* 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
{
/**
* Move the specified body to the default portal, if possible.
*/
public static void moveBodyToDefaultPortal (BodyObject body)
{
SpotSceneManager mgr = (SpotSceneManager)
CrowdServer.plreg.getPlaceManager(body.location);
if (mgr != null) {
SpotScene scene = (SpotScene)mgr.getScene();
if (scene == null) {
Log.warning("No scene in moveBodyToDefaultPortal()? " +
"[who=" + body.who() +
", where=" + mgr.where() + "].");
return;
}
try {
Location eloc = scene.getDefaultEntrance().getLocation();
mgr.handleChangeLoc(body, eloc);
} catch (InvocationException ie) {
Log.warning("Could not move user to default portal " +
"[where=" + mgr.where() + ", who=" + body.who() +
", error=" + ie + "].");
}
}
}
/**
* Assigns a starting location for an entering body. This will happen
* before the body is made to "occupy" the scene (defined by their
* having an occupant info record). So when they do finally occupy the
* scene, the client will know where to render them.
*/
public void mapEnteringBody (BodyObject body, int portalId)
{
_enterers.put(body.getOid(), portalId);
}
/**
* Called if a body failed to enter our scene after we assigned them
* an entering position.
*/
public void clearEnteringBody (BodyObject body)
{
_enterers.remove(body.getOid());
}
/**
* This is called when a user requests to traverse a portal from this
* scene to another scene. The manager may return an error code string
* that will be reported back to the caller explaining the failure or
* <code>null</code> indicating that it is OK for the caller to
* traverse the portal.
*/
public String mayTraversePortal (BodyObject body, Portal portal)
{
return null;
}
/**
* This is called to let this scene manager know that the user is
* about to traverse the specified portal. The default implementation
* relocates the user to the location associated with the portal. It
* is still possible that the traversal will fail, so don't do
* anything too crazy.
*/
public void willTraversePortal (BodyObject body, Portal portal)
{
updateLocation(body, portal.getLocation());
}
// documentation inherited
protected void didStartup ()
{
// get a casted reference to our place object (we need to do this
// before calling super.didStartup() because that will call
// sceneManagerDidResolve() which may start letting people into
// the scene)
_ssobj = (SpotSceneObject)_plobj;
super.didStartup();
}
// documentation inherited
protected void gotSceneData ()
{
super.gotSceneData();
// keep a casted reference around to our scene
_sscene = (SpotScene)_scene;
}
// documentation inherited
protected void bodyLeft (int bodyOid)
{
super.bodyLeft(bodyOid);
// clear out their location information
_ssobj.removeFromOccupantLocs(Integer.valueOf(bodyOid));
// clear any cluster they may occupy
removeFromCluster(bodyOid);
// let's make damned sure they're not in any cluster
Iterator cliter = _clusters.values().iterator();
while (cliter.hasNext()) {
ClusterRecord clrec = (ClusterRecord)cliter.next();
if (clrec.containsKey(bodyOid)) {
Log.info("Pruning departed body from cluster [boid=" + bodyOid +
", cluster=" + clrec + "].");
clrec.removeBody(bodyOid);
if (clrec.size() == 0) {
// If we just removed the last body, destroy the cluster,
// need to use the iterator's removal so we don't
// hose ourselves.
clrec.destroy(false);
cliter.remove();
}
}
}
}
// documentation inherited
protected void insertOccupantInfo (OccupantInfo info, BodyObject body)
{
super.insertOccupantInfo(info, body);
// we don't actually populate their occupant info, but instead assign
// them their starting location in the scene
int portalId = _enterers.remove(body.getOid());
Portal entry;
if (portalId != -1) {
entry = _sscene.getPortal(portalId);
if (entry == null) {
Log.warning("Body mapped at invalid portal [where=" + where() +
", who=" + body.who() +
", portalId=" + portalId + "].");
entry = _sscene.getDefaultEntrance();
}
} else {
entry = _sscene.getDefaultEntrance();
}
// Log.debug("Positioning entering body [who=" + body.who() +
// ", where=" + entry.getOppLocation() + "].");
// create a scene location for them located on the entrance portal
// but facing the opposite direction
_ssobj.addToOccupantLocs(computeEnteringLocation(body, entry));
}
/**
* Called when the supplied body is entering our scene via the
* specified portal. The default location is the one associated with
* the portal, but derived classes may wish to adjust this.
*/
protected SceneLocation computeEnteringLocation (
BodyObject body, Portal entry)
{
return new SceneLocation(entry.getOppLocation(), body.getOid());
}
/**
* Called by the {@link SpotProvider} when we receive a request by a
* user to occupy a particular location.
*
* @param source the body to be moved.
* @param loc the location to which to move the body.
*
* @exception InvocationException thrown with a reason code explaining
* the failure if there is a problem processing the request.
*/
protected void handleChangeLoc (BodyObject source, Location loc)
throws InvocationException
{
// make sure they are in our scene
if (!_ssobj.occupants.contains(source.getOid())) {
Log.warning("Refusing change loc from non-scene occupant " +
"[where=" + where() + ", who=" + source.who() +
", loc=" + loc + "].");
throw new InvocationException(INTERNAL_ERROR);
}
// let our derived classes decide if this is an OK place to stand
if (!validateLocation(source, loc)) {
throw new InvocationException(INVALID_LOCATION);
}
// update the user's location information in the scene which will
// indicate to the client that their avatar should be moved from
// its current position to their new position
updateLocation(source, loc);
// remove them from any cluster as they've departed
removeFromCluster(source.getOid());
}
/**
* Derived classes can override this method and validate that the
* specified body can stand in the requested location. The default
* implementation returns <code>true</code> in all circumstances;
* stand where ye may!
*/
protected boolean validateLocation (BodyObject source, Location loc)
{
return true;
}
/**
* Updates the location of the specified body.
*/
protected void updateLocation (BodyObject source, Location loc)
{
SceneLocation sloc = new SceneLocation(loc, source.getOid());
if (!_ssobj.occupantLocs.contains(sloc)) {
// complain if they don't already have a location configured
Log.warning("Changing loc for occupant without previous loc " +
"[where=" + where() + ", who=" + source.who() +
", nloc=" + loc + "].");
Thread.dumpStack();
_ssobj.addToOccupantLocs(sloc);
} else {
_ssobj.updateOccupantLocs(sloc);
}
}
/**
* Called by the {@link SpotProvider} when we receive a request by a
* user to join a particular cluster.
*
* @param joiner the body to be moved.
* @param targetOid the bodyOid of another user or the oid of an
* existing cluster; the moving user will be made to join the other
* user's cluster.
*
* @exception InvocationException thrown with a reason code explaining
* the failure if there is a problem processing the request.
*/
protected void handleJoinCluster (BodyObject joiner, int targetOid)
throws InvocationException
{
// if the cluster already exists, add this user and be done
ClusterRecord clrec = (ClusterRecord)_clusters.get(targetOid);
if (clrec != null) {
clrec.addBody(joiner);
return;
}
// otherwise see if they sent us the user's oid
DObject tobj = CrowdServer.omgr.getObject(targetOid);
if (!(tobj instanceof BodyObject)) {
Log.info("Can't join cluster, missing target " +
"[creator=" + joiner.who() +
", targetOid=" + targetOid + "].");
throw new InvocationException(NO_SUCH_CLUSTER);
}
// make sure we're in the same scene as said user
BodyObject friend = (BodyObject)tobj;
if (friend.location != joiner.location) {
Log.info("Refusing cluster join from non-proximate user " +
"[joiner=" + joiner.who() + ", jloc=" + joiner.location +
", target=" + friend.who() +
", tloc=" + friend.location + "].");
throw new InvocationException(NO_SUCH_CLUSTER);
}
// see if the friend is already in a cluster
clrec = getCluster(friend.getOid());
if (clrec != null) {
clrec.addBody(joiner);
return;
}
// confirm that they can start a cluster with this unsuspecting
// other person
checkCanCluster(joiner, friend);
// otherwise we create a new cluster and add our charter members!
// Log.debug("Creating cluster [starter=" + joiner.who() +
// ", target=" + friend.who() + "].");
clrec = createClusterRecord();
clrec.addBody(friend);
clrec.addBody(joiner);
}
/**
* Creates the cluster record instance that we'll use to manage our
* cluster.
*/
protected ClusterRecord createClusterRecord ()
{
return new ClusterRecord();
}
/**
* Gives derived classes an opportunity to veto a user's attempt to
* start a cluster with another user. If the attempt should be vetoed,
* this method should throw an {@link InvocationException} indicating
* the reason for veto.
*/
protected void checkCanCluster (BodyObject initiator, BodyObject target)
throws InvocationException
{
// nothing to do by default
}
/**
* Removes the specified user from any cluster they occupy.
*/
protected void removeFromCluster (int bodyOid)
{
ClusterRecord clrec = getCluster(bodyOid);
if (clrec != null) {
clrec.removeBody(bodyOid);
// If that was the last person, destroy the cluster
if (clrec.size() == 0) {
clrec.destroy(true);
}
}
}
/**
* Fetches the cluster record for the specified body.
*/
protected ClusterRecord getCluster (int bodyOid)
{
ClusteredBodyObject bobj = (ClusteredBodyObject)
CrowdServer.omgr.getObject(bodyOid);
return (bobj == null) ? null :
(ClusterRecord)_clusters.get(bobj.getClusterOid());
}
/**
* Called by the {@link SpotProvider} when we receive a cluster speak
* request.
*/
protected void handleClusterSpeakRequest (
int sourceOid, Name source, String bundle, String message, byte mode)
{
ClusterRecord clrec = getCluster(sourceOid);
if (clrec == null) {
Log.warning("Non-clustered user requested cluster speak " +
"[where=" + where() + ", chatter=" + source +
" (" + sourceOid + "), msg=" + message + "].");
} else {
SpeakProvider.sendSpeak(clrec.getClusterObject(),
source, bundle, message, mode);
}
}
/**
* Returns the location of the specified body or null if they have no
* location in this scene.
*/
protected SceneLocation locationForBody (int bodyOid)
{
return (SceneLocation)_ssobj.occupantLocs.get(Integer.valueOf(bodyOid));
}
/**
* Verifies that the specified cluster can be expanded to include
* another body.
*/
protected boolean canAddBody (ClusterRecord clrec, BodyObject body)
{
return true;
}
/**
* Called when a user is added to a cluster. The scene manager
* implementation should take this opportunity to rearrange everyone
* in the cluster appropriately for the new size.
*/
protected void bodyAdded (ClusterRecord clrec, BodyObject body)
{
}
/**
* Called when a user is removed from a cluster. The scene manager
* implementation should take this opportunity to rearrange everyone
* in the cluster appropriately for the new size.
*/
protected void bodyRemoved (ClusterRecord clrec, BodyObject body)
{
}
/**
* Used to manage clusters which are groups of users that can chat to
* one another.
*/
protected class ClusterRecord extends HashIntMap
implements Subscriber
{
public ClusterRecord ()
{
CrowdServer.omgr.createObject(ClusterObject.class, this);
}
public boolean addBody (BodyObject body)
throws InvocationException
{
if (!(body instanceof ClusteredBodyObject)) {
Log.warning("Refusing to add non-clustered body to cluster " +
"[cloid=" + _clobj.getOid() +
", size=" + size() + ", who=" + body.who() + "].");
throw new InvocationException(INTERNAL_ERROR);
}
// if they're already in the cluster, do nothing
if (containsKey(body.getOid())) {
return false;
}
// make sure we can add this body
if (!canAddBody(this, body)) {
// Log.debug("Cluster full, refusing growth " + this + ".");
throw new InvocationException(CLUSTER_FULL);
}
// make sure our intrepid joiner is not in any another cluster
removeFromCluster(body.getOid());
put(body.getOid(), body);
_ssobj.startTransaction();
try {
body.startTransaction();
try {
bodyAdded(this, body); // do the hokey pokey
if (_clobj != null) {
((ClusteredBodyObject)body).setClusterOid(
_clobj.getOid());
_clobj.addToOccupants(body.getOid());
_ssobj.updateClusters(_cluster);
}
} finally {
body.commitTransaction();
}
} finally {
_ssobj.commitTransaction();
}
// Log.debug("Added " + body.who() + " to "+ this + ".");
return true;
}
public void removeBody (int bodyOid)
{
BodyObject body = (BodyObject)remove(bodyOid);
if (body == null) {
Log.warning("Requested to remove unknown body from cluster " +
"[cloid=" + _clobj.getOid() +
", size=" + size() + ", who=" + bodyOid + "].");
return;
}
if (body.isActive()) {
body.startTransaction();
}
try {
_ssobj.startTransaction();
try {
((ClusteredBodyObject)body).setClusterOid(-1);
bodyRemoved(this, body); // do the hokey pokey
if (_clobj != null) {
_clobj.removeFromOccupants(bodyOid);
_ssobj.updateClusters(_cluster);
}
} finally {
_ssobj.commitTransaction();
}
} finally {
if (body.isActive()) {
body.commitTransaction();
}
}
// Log.debug("Removed " + bodyOid + " from "+ this + ".");
}
public ClusterObject getClusterObject ()
{
return _clobj;
}
public Cluster getCluster ()
{
return _cluster;
}
public void objectAvailable (DObject object)
{
// keep this feller around
_clobj = (ClusterObject)object;
_clusters.put(_clobj.getOid(), this);
// let any mapped users know about our cluster
Iterator iter = values().iterator();
while (iter.hasNext()) {
ClusteredBodyObject body = (ClusteredBodyObject)iter.next();
body.setClusterOid(_clobj.getOid());
_clobj.addToOccupants(((BodyObject)body).getOid());
}
// configure our cluster record and publish it
_cluster.clusterOid = _clobj.getOid();
_ssobj.addToClusters(_cluster);
// if we didn't manage to add our creating user when we first
// started up, there's no point in our sticking around
if (size() == 0) {
destroy(true);
}
}
public void requestFailed (int oid, ObjectAccessException cause)
{
Log.warning("Aiya! Failed to create cluster object " +
"[cause=" + cause + ", penders=" + size() + "].");
// let any mapped users know that we're hosed
Iterator iter = values().iterator();
while (iter.hasNext()) {
ClusteredBodyObject body = (ClusteredBodyObject)iter.next();
body.setClusterOid(-1);
}
}
public String toString ()
{
return "[cluster=" + _cluster + ", size=" + size() + "]";
}
protected void destroy (boolean doRemoval)
{
// Log.debug("Cluster empty, going away " +
// "[cloid=" + _clobj.getOid() + "].");
_ssobj.removeFromClusters(_cluster.getKey());
// If we've also been requested to remove ourself from the clusters
// list, do that.
if (doRemoval) {
_clusters.remove(_clobj.getOid());
}
CrowdServer.omgr.destroyObject(_clobj.getOid());
}
protected ClusterObject _clobj;
protected Cluster _cluster = new Cluster();
}
/** A casted reference to our place object. */
protected SpotSceneObject _ssobj;
/** A casted reference to our scene instance. */
protected SpotScene _sscene;
/** Records with information on all clusters in this scene. */
protected HashIntMap _clusters = new HashIntMap();
/** A mapping of entering bodies to portal ids. */
protected IntIntMap _enterers = new IntIntMap();
}
@@ -0,0 +1,42 @@
//
// $Id: EditablePortal.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.tools;
import com.threerings.whirled.spot.data.Portal;
/**
* An editable portal extends the standard portal with information needed
* by the loader and editor.
*/
public class EditablePortal extends Portal
{
/** The human-readable name of this portal. */
public String name;
/** The human-readable name of the scene to which this portal
* links. */
public String targetSceneName;
/** The human-readable name of the portal to which this portal links
* in its target scene. */
public String targetPortalName;
}
@@ -0,0 +1,164 @@
//
// $Id: SpotSceneRuleSet.java 4140 2006-05-23 21:44:05Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.tools.xml;
import java.lang.reflect.Field;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.samskivert.util.StringUtil;
import com.samskivert.util.ValueMarshaller;
import com.threerings.tools.xml.NestableRuleSet;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.ObjectCreateRule;
import org.apache.commons.digester.Rule;
import org.xml.sax.Attributes;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
import com.threerings.whirled.spot.data.SpotSceneModel;
import com.threerings.whirled.spot.tools.EditablePortal;
/**
* Used to parse a {@link SpotSceneModel} from XML.
*/
public abstract class SpotSceneRuleSet implements NestableRuleSet
{
// documentation inherited from interface
public String getOuterElement ()
{
return SpotSceneWriter.OUTER_ELEMENT;
}
// documentation inherited from interface
public void addRuleInstances (String prefix, Digester digester)
{
digester.addObjectCreate(prefix, SpotSceneModel.class.getName());
// set up rules to parse and set our fields
digester.addRule(prefix, new SetPropertyFieldsRule());
// create EditablePortal instances when we see <portal>
digester.addRule(prefix + "/portal", new PortalCreateRule(this));
digester.addRule(prefix + "/portal", new PortalFieldsRule());
digester.addSetNext(prefix + "/portal", "addPortal",
Portal.class.getName());
}
/**
* Create a new instance of the Location class that should be used
* with Portals.
*/
protected abstract Location createLocation ();
/**
* A rule used to create the portal but also initialize the Location
* property within it.
*/
protected static class PortalCreateRule extends ObjectCreateRule
{
public PortalCreateRule (SpotSceneRuleSet ruleset)
{
super(EditablePortal.class.getName());
_ruleset = ruleset;
}
// documentation inherited
public void begin (String namespace, String name, Attributes attributes)
throws Exception
{
super.begin(namespace, name, attributes);
// create the empty Location in the Portal
Portal p = (Portal) digester.peek();
p.loc = _ruleset.createLocation();
}
protected SpotSceneRuleSet _ruleset;
}
/**
* Set fields in the Portal, or in the Location object
* contained therein. If there are ambiguous attribute names then..
* well. yeah.
*/
protected static class PortalFieldsRule extends Rule
{
// documentation inherited
public void begin (String namespace, String name, Attributes attrs)
throws Exception
{
Portal portal = (Portal) digester.peek();
Class portalClass = portal.getClass();
Location loc = portal.loc;
Class locClass = loc.getClass();
// iterate over the attributes, setting public fields where
// applicable
for (int i = 0; i < attrs.getLength(); i++) {
String lname = attrs.getLocalName(i);
if (StringUtil.isBlank(lname)) {
lname = attrs.getQName(i);
}
// look for a public field with this lname
Field field;
Object container;
try {
field = portalClass.getField(lname);
container = portal;
} catch (NoSuchFieldException nsfe) {
// if we didn't find the field in the Portal, maybe it's
// in the Location
try {
field = locClass.getField(lname);
container = loc;
} catch (NoSuchFieldException nsfe2) {
digester.getLogger().warn(
"Skipping property '" + lname +
"' for which there is no field.");
continue;
}
}
// convert the value into the appropriate object type
String valstr = attrs.getValue(i);
// use the value marshaller to parse the
// property based on the type of the target object field
Object value = ValueMarshaller.unmarshal(
field.getType(), valstr);
if (digester.getLogger().isDebugEnabled()) {
digester.getLogger().debug(" Setting property '" + lname +
"' to '" + valstr + "'");
}
// and finally set the field
field.set(container, value);
}
}
}
}
@@ -0,0 +1,113 @@
//
// $Id: SpotSceneWriter.java 4077 2006-04-28 21:27:11Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.whirled.spot.tools.xml;
import java.lang.reflect.Field;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.megginson.sax.DataWriter;
import com.threerings.tools.xml.NestableWriter;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.SpotSceneModel;
import com.threerings.whirled.spot.tools.EditablePortal;
/**
* Generates an XML representation of a {@link SpotSceneModel}.
*/
public class SpotSceneWriter
implements NestableWriter
{
/** The outer element used to enclose our spot scene definition. */
public static final String OUTER_ELEMENT = "spot";
// documentation inherited from interface
public void write (Object object, DataWriter writer)
throws SAXException
{
SpotSceneModel model = (SpotSceneModel)object;
AttributesImpl attrs = new AttributesImpl();
addSceneAttributes(model, attrs);
writer.startElement("", OUTER_ELEMENT, "", attrs);
writeSceneData(model, writer);
writer.endElement(OUTER_ELEMENT);
}
protected void addSceneAttributes (SpotSceneModel model,
AttributesImpl attrs)
{
if (model.defaultEntranceId != -1) {
attrs.addAttribute("", "defaultEntranceId", "", "",
String.valueOf(model.defaultEntranceId));
}
}
protected void writeSceneData (SpotSceneModel model, DataWriter writer)
throws SAXException
{
// write out the portal info
for (int ii = 0; ii < model.portals.length; ii++) {
EditablePortal port = (EditablePortal)model.portals[ii];
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "portalId", "", "",
String.valueOf(port.portalId));
addPortalLocationAttributes(port.loc, attrs);
maybeAddAttr(attrs, "name", port.name);
maybeAddAttr(attrs, "targetSceneName", port.targetSceneName);
maybeAddAttr(attrs, "targetPortalName", port.targetPortalName);
writer.emptyElement("", "portal", "", attrs);
}
}
protected void addPortalLocationAttributes (
Location portalLoc, AttributesImpl attrs)
{
// we just add all the visible fields of the location, but something
// more sophisticated could be done
Class clazz = portalLoc.getClass();
Field[] fields = clazz.getFields();
for (int ii=0; ii < fields.length; ii++) {
try {
attrs.addAttribute("", fields[ii].getName(), "", "",
String.valueOf(fields[ii].get(portalLoc)));
} catch (IllegalAccessException iae) {
Log.warning("Unable to write portal field, skipping " +
"[field=" + fields[ii].getName() + ", e=" + iae + "].");
}
}
}
/**
* Adds the supplied attribute to the attributes object iff the value
* is non-null.
*/
protected void maybeAddAttr (
AttributesImpl attrs, String name, String value)
{
if (value != null) {
attrs.addAttribute("", name, "", "", value);
}
}
}