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:
@@ -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.
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user