diff --git a/src/as/com/threerings/crowd/client/LocationAdapter.as b/src/as/com/threerings/crowd/client/LocationAdapter.as new file mode 100644 index 000000000..c1fe747a0 --- /dev/null +++ b/src/as/com/threerings/crowd/client/LocationAdapter.as @@ -0,0 +1,43 @@ +package com.threerings.crowd.client { + +import com.threerings.crowd.data.PlaceObject; + +public class LocationAdapter + implements LocationObserver +{ + public function LocationAdapter ( + mayChange :Function = null, didChange :Function = null, + changeFailed :Function = null) + { + _mayChange = mayChange; + _didChange = didChange; + _changeFailed = changeFailed; + } + + // documentation inherited from interface LocationObserver + public function locationMayChange (placeId :int) :Boolean + { + return (_mayChange == null) || _mayChange(placeId); + } + + // documentation inherited from interface LocationObserver + public function locationDidChange (place :PlaceObject) :void + { + if (_didChange != null) { + _didChange(place); + } + } + + // documentation inherited from interface LocationObserver + public function locationChangeFailed (placeId :int, reason :String) :void + { + if (_changeFailed != null) { + _changeFailed(placeId, reason); + } + } + + protected var _mayChange :Function; + protected var _didChange :Function; + protected var _changeFailed :Function; +} +} diff --git a/src/as/com/threerings/io/TypedArray.as b/src/as/com/threerings/io/TypedArray.as index 60761b657..e64636c0e 100644 --- a/src/as/com/threerings/io/TypedArray.as +++ b/src/as/com/threerings/io/TypedArray.as @@ -1,12 +1,32 @@ package com.threerings.io { +import com.threerings.util.ClassUtil; + public dynamic class TypedArray extends Array { + /** + * Create a TypedArray + * + * @param jtype The java classname of this array, for example "[I" to + * represent an int[], or "[Ljava.lang.Object;" for Object[]. + */ public function TypedArray (jtype :String) { _jtype = jtype; } + /** + * Convenience method to get the java type of an array containing + * objects of the specified class. + */ + public static function getJavaType (of :Class) :String + { + var cname :String = Translations.getToServer( + ClassUtil.getClassName(of)); + // TODO: primitive types + return "[L" + cname + ";"; + } + public function getJavaType () :String { return _jtype; diff --git a/src/as/com/threerings/presents/client/ConfirmAdapter.as b/src/as/com/threerings/presents/client/ConfirmAdapter.as new file mode 100644 index 000000000..bcc09d44f --- /dev/null +++ b/src/as/com/threerings/presents/client/ConfirmAdapter.as @@ -0,0 +1,27 @@ +package com.threerings.presents.client { + +public class ConfirmAdapter + implements ConfirmListener +{ + public function ConfirmAdapter (processed :Function, failed :Function) + { + _processed = processed; + _failed = failed; + } + + // documentation inherited from interface ConfirmListener + public function requestProcessed () :void + { + _processed(); + } + + // documentation inherited from interface ConfirmListener + public function requestFailed (cause :String) :void + { + _failed(cause); + } + + protected var _processed :Function; + protected var _failed :Function; +} +} diff --git a/src/as/com/threerings/presents/client/ConfirmListener.as b/src/as/com/threerings/presents/client/ConfirmListener.as index f28e9ddec..d4474d76a 100644 --- a/src/as/com/threerings/presents/client/ConfirmListener.as +++ b/src/as/com/threerings/presents/client/ConfirmListener.as @@ -1,7 +1,14 @@ package com.threerings.presents.client { +/** + * Extends the {@link InvocationListener} with a basic success + * callback. + */ public interface ConfirmListener extends InvocationListener { + /** + * Indicates that the request was successfully processed. + */ function requestProcessed () :void; } } diff --git a/src/as/com/threerings/presents/client/InvocationListener.as b/src/as/com/threerings/presents/client/InvocationListener.as index 9ef7e0f67..582161fc0 100644 --- a/src/as/com/threerings/presents/client/InvocationListener.as +++ b/src/as/com/threerings/presents/client/InvocationListener.as @@ -1,7 +1,30 @@ package com.threerings.presents.client { +/** + * Invocation service methods that require a response should take a + * listener argument that can be notified of request success or + * failure. The listener argument should extend this interface so that + * generic failure can be reported in all cases. For example: + * + *
+ * // Used to communicate responses to+ */ public interface InvocationListener { + /** + * Called to report request failure. If the invocation services + * system detects failure of any kind, it will report it via this + * callback. Particular services may also make use of this + * callback to report failures of their own, or they may opt to + * define more specific failure callbacks. + */ function requestFailed (cause :String) :void; } } diff --git a/src/as/com/threerings/presents/client/ResultListener.as b/src/as/com/threerings/presents/client/ResultListener.as index a0be0a892..044c80517 100644 --- a/src/as/com/threerings/presents/client/ResultListener.as +++ b/src/as/com/threerings/presents/client/ResultListener.as @@ -1,7 +1,14 @@ package com.threerings.presents.client { +/** + * Extends the {@link InvocationListener} with a basic success + * callback that delivers a result object. + */ public interface ResultListener extends InvocationListener { + /** + * Indicates that the request was successfully processed. + */ function requestProcessed (result :Object) :void; } } diff --git a/src/as/com/threerings/presents/data/ConfirmMarshaller.as b/src/as/com/threerings/presents/data/ConfirmMarshaller.as new file mode 100644 index 000000000..c3f0c2953 --- /dev/null +++ b/src/as/com/threerings/presents/data/ConfirmMarshaller.as @@ -0,0 +1,29 @@ +package com.threerings.presents.data { + +import com.threerings.presents.client.ConfirmListener; + +public class ConfirmMarshaller extends ListenerMarshaller + implements ConfirmListener +{ + public static const REQUEST_PROCESSED :int = 1; + + // documetnation inherited from interfacc + public function requestProcessed () + { + // TODO: server only? + } + + // documetnation inherited + public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { + case REQUEST_PROCESSED: + (listener as ConfirmListener).requestProcessed(); + return; + + default: + super.dispatchResponse(methodId, args); + } + } +} +} diff --git a/src/as/com/threerings/util/ResultAdapter.as b/src/as/com/threerings/util/ResultAdapter.as new file mode 100644 index 000000000..d02e12dc4 --- /dev/null +++ b/src/as/com/threerings/util/ResultAdapter.as @@ -0,0 +1,31 @@ +package com.threerings.util { + +public class ResultAdapter + implements ResultListener +{ + public function ResultAdapter (completed :Function, failed :Function) + { + _completed = completed; + _failed = failed; + } + + // documentation inherited from interface ResultListener + public function requestCompleted (obj :Object) :void + { + if (_completed != null) { + _completed(obj); + } + } + + // documentation inherited from interface ResultListener + public function requestFailed (cause :Error) :void + { + if (_failed != null) { + _failed(cause); + } + } + + protected var _completed :Function; + protected var _failed :Function; +} +} diff --git a/src/as/com/threerings/whirled/spot/client/SpotSceneController.as b/src/as/com/threerings/whirled/spot/client/SpotSceneController.as new file mode 100644 index 000000000..39c969e46 --- /dev/null +++ b/src/as/com/threerings/whirled/spot/client/SpotSceneController.as @@ -0,0 +1,37 @@ +// +// $Id: SpotSceneController.java 3099 2004-08-27 02:21:06Z 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.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 +{ +} +} diff --git a/src/as/com/threerings/whirled/spot/client/SpotSceneDirector.as b/src/as/com/threerings/whirled/spot/client/SpotSceneDirector.as new file mode 100644 index 000000000..b91c2ec3e --- /dev/null +++ b/src/as/com/threerings/whirled/spot/client/SpotSceneDirector.as @@ -0,0 +1,472 @@ +// +// $Id: SpotSceneDirector.java 3890 2006-02-24 19:51:11Z mthomas $ +// +// 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.util.ResultListener; + +import com.threerings.presents.client.BasicDirector; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.ClientEvent; +import com.threerings.presents.client.ConfirmAdapter; +import com.threerings.presents.client.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.ObjectAccessError; +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.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 Subscriber, AttributeChangeListener +{ + private static const log :Log = Log.getLog(SpotSceneDirector); + + /** + * 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 function SpotSceneDirector ( + ctx :WhirledContext, locdir :LocationDirector, scdir :SceneDirector) + { + super(ctx); + + _wctx = ctx; + _scdir = scdir; + + // wire ourselves up to hear about leave place notifications + locdir.addLocationObserver(new LocationAdapter(null, + function (place :PlaceObject) :void { + handleDeparture(); + })); + } + + /** + * Configures this spot scene director with a chat director, with + * which it will coordinate to implement cluster chatting. + */ + public function setChatDirector (chatdir :ChatDirector) :void + { + _chatdir = chatdir; + } + + /** + * Returns our current location unless we have a location change + * pending, in which case our pending location is returned. + */ + public function getIntendedLocation () :Location + { + 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 function traversePortal ( + portalId :int, + rl :com.threerings.util.ResultListener = null) :Boolean + { + // look up the destination scene and location + var scene :SpotScene = (_scdir.getScene() as SpotScene); + 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 + var sceneId :int = _scdir.getScene().getId(); + var sbobj :ScenedBodyObject = + (_wctx.getClient().getClientObject() as ScenedBodyObject); + 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 + var dest :Portal = scene.getPortal(portalId); + if (dest == null) { + log.warning("Requested to traverse non-existent portal " + + "[portalId=" + portalId + ", portals=" + 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 + var sceneVer :int = 0; + var pendingModel :SceneModel = _scdir.getPendingModel(); + if (pendingModel != null) { + sceneVer = pendingModel.version; + } + + // issue a traversePortal request + log.info("Issuing traversePortal(" + + sceneId + ", " + dest + ", " + sceneVer + ")."); + _sservice.traversePortal( + _wctx.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 function changeLocation ( + loc :Location, listener :com.threerings.util.ResultListener) :void + { + // 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; + } + + var scene :SpotScene = (_scdir.getScene() as SpotScene); + 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 Error("m.cant_get_there")); + } + return; + } + + var sceneId :int = _scdir.getScene().getId(); + log.info("Sending changeLocation request [scid=" + sceneId + + ", loc=" + loc + "]."); + + _pendingLoc = (loc.clone() as Location); + var clist :ConfirmAdapter = new ConfirmAdapter( + function () :void { + _location = _pendingLoc; + _pendingLoc = null; + if (listener != null) { + listener.requestCompleted(_location); + } + }, + function (reason :String) :void { + _pendingLoc = null; + if (listener != null) { + listener.requestFailed(new Error(reason)); + } + }); + _sservice.changeLocation(_wctx.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 function joinCluster ( + froid :int, listener :com.threerings.util.ResultListener) :void + { + var scene :SpotScene = (_scdir.getScene() as SpotScene); + 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 Error("m.cant_get_there")); + } + return; + } + + log.info("Joining cluster [friend=" + froid + "]."); + + _sservice.joinCluster(_wctx.getClient(), froid, new ConfirmAdapter( + function () :void { + if (listener != null) { + listener.requestCompleted(null); + } + }, + function (reason :String) :void { + if (listener != null) { + listener.requestFailed(new Error(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 function requestClusterSpeak ( + message :String, mode :int = ChatCodes.DEFAULT_MODE) :Boolean + { + // make sure we're currently in a scene + var scene :SpotScene = (_scdir.getScene() as SpotScene); + 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(_wctx.getClient(), message, mode); + } + return true; + } + + // documentation inherited from interface + public function objectAvailable (object :DObject) :void + { + clearCluster(false); + var oid :int = object.getOid(); + if (oid != _self.getClusterOid()) { + // we got it too late, just unsubscribe + var omgr :DObjectManager = _wctx.getDObjectManager(); + omgr.unsubscribeFromObject(oid, this); + } else { + // it's our new cluster! + _clobj = object; + if (_chatdir != null) { + _chatdir.addAuxiliarySource(object, + SpotCodes.CLUSTER_CHAT_TYPE); + } + } + } + + // documentation inherited from interface + public function requestFailed (oid :int, cause :ObjectAccessError) :void + { + log.warning("Unable to subscribe to cluster chat object " + + "[oid=" + oid + ", cause=" + cause + "]."); + } + + // documentation inherited from interface + public function attributeChanged (event :AttributeChangedEvent) :void + { + if (event.getName() == _self.getClusterField() && + event.getValue() != event.getOldValue()) { + maybeUpdateCluster(); + } + } + + // documentation inherited + public override function clientDidLogon (event :ClientEvent) :void + { + super.clientDidLogon(event); + + var clientObj :ClientObject = event.getClient().getClientObject(); + if (clientObj is ClusteredBodyObject) { + // listen to the client object + clientObj.addListener(this); + _self = (clientObj as ClusteredBodyObject); + + // we may need to subscribe to a cluster due to session resumption + maybeUpdateCluster(); + } + } + + // documentation inherited + public override function clientObjectDidChange (event :ClientEvent) :void + { + super.clientObjectDidChange(event); + + // listen to the client object + var clientObj :ClientObject = event.getClient().getClientObject(); + clientObj.addListener(this); + _self = (clientObj as ClusteredBodyObject); + } + + // documentation inherited + public override function clientDidLogoff (event :ClientEvent) :void + { + super.clientDidLogoff(event); + + // clear out our business + _location = null; + _pendingLoc = null; + _sservice = null; + clearCluster(true); + + // stop listening to the client object + event.getClient().getClientObject().removeListener(this); + _self = null; + } + + // documentation inherited + protected override function fetchServices (client :Client) :void + { + _sservice = (client.requireService(SpotService) as SpotService); + } + + /** + * Clean up after a few things when we depart from a scene. + */ + protected function handleDeparture () :void + { + // 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 function maybeUpdateCluster () :void + { + var cloid :int = _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) { + var omgr :DObjectManager = _wctx.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 function clearCluster (force :Boolean) :void + { + if (_clobj != null && + (force || (_clobj.getOid() != _self.getClusterOid()))) { + if (_chatdir != null) { + _chatdir.removeAuxiliarySource(_clobj); + } + var omgr :DObjectManager = _wctx.getDObjectManager(); + omgr.unsubscribeFromObject(_clobj.getOid(), this); + _clobj = null; + } + } + + /** The active client context. */ + protected var _wctx :WhirledContext; + + /** Access to spot scene services. */ + protected var _sservice :SpotService; + + /** The scene director with which we are cooperating. */ + protected var _scdir :SceneDirector; + + /** A casted reference to our clustered body object. */ + protected var _self :ClusteredBodyObject; + + /** A reference to the chat director with which we coordinate. */ + protected var _chatdir :ChatDirector; + + /** The location we currently occupy. */ + protected var _location :Location; + + /** The location to which we have an outstanding change location + * request. */ + protected var _pendingLoc :Location; + + /** The cluster chat object for the cluster we currently occupy. */ + protected var _clobj :DObject; +} +} diff --git a/src/as/com/threerings/whirled/spot/client/SpotService.as b/src/as/com/threerings/whirled/spot/client/SpotService.as new file mode 100644 index 000000000..7308f81a9 --- /dev/null +++ b/src/as/com/threerings/whirled/spot/client/SpotService.as @@ -0,0 +1,85 @@ +// +// $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. + */ + function traversePortal ( + client :Client, sceneId :int, portalId :int, destSceneVer :int, + listener :SceneService_SceneMoveListener) :void; + + /** + * 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. + */ + function changeLocation ( + client :Client, sceneId :int, loc :Location, + listener :ConfirmListener) :void; + + /** + * 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. + */ + function joinCluster ( + client :Client, friendOid :int, listener :ConfirmListener) :void; + + /** + * 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.). + */ + function clusterSpeak (client :Client, message :String, mode :int) :void; +} +} diff --git a/src/as/com/threerings/whirled/spot/data/Cluster.as b/src/as/com/threerings/whirled/spot/data/Cluster.as new file mode 100644 index 000000000..795476d1c --- /dev/null +++ b/src/as/com/threerings/whirled/spot/data/Cluster.as @@ -0,0 +1,76 @@ +// +// $Id: Cluster.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 flash.geom.Rectangle; + +import com.threerings.io.Streamable; +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +import com.threerings.presents.dobj.DSet_Entry; + +/** + * 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 var clusterOid :int; + + // documentation inherited from interface DSet_Entry + public function getKey () :Object + { + return clusterOid; + } + + // documentation inherited from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeInt(x); + out.writeInt(y); + out.writeInt(width); + out.writeInt(height); + out.writeInt(clusterOid); + } + + // documentation inherited from interface Streamable + public function readObject (ins :ObjectInputStream) :void + { + x = ins.readInt(); + y = ins.readInt(); + width = ins.readInt(); + height = ins.readInt(); + clusterOid = ins.readInt(); + } + + /** + * Generates a string representation of this instance. + */ + public function toString () :String + { + return super.toString() + ", clusterOid=" + clusterOid; + } +} +} diff --git a/src/as/com/threerings/whirled/spot/data/ClusterObject.as b/src/as/com/threerings/whirled/spot/data/ClusterObject.as new file mode 100644 index 000000000..b99b30fe5 --- /dev/null +++ b/src/as/com/threerings/whirled/spot/data/ClusterObject.as @@ -0,0 +1,66 @@ +// +// $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 +{ + // AUTO-GENERATED: FIELDS START + /** The field name of themoveTorequests. + * public interface MoveListener extends InvocationListener + * { + * // Called in response to a successfulmoveTo+ * // request. + * public void moveSucceeded (PlaceConfig config); + * } + *
occupants field. */
+ public static const OCCUPANTS :String = "occupants";
+ // AUTO-GENERATED: FIELDS END
+
+ /**
+ * Tracks the oid of the body objects that occupy this cluster.
+ */
+ public var occupants :OidList = new OidList();
+
+ // AUTO-GENERATED: METHODS START
+ /**
+ * Requests that oid be added to the occupants
+ * oid list. The list will not change until the event is actually
+ * propagated through the system.
+ */
+ public function addToOccupants (oid :int) :void
+ {
+ requestOidAdd(OCCUPANTS, oid);
+ }
+
+ /**
+ * Requests that oid be removed from the
+ * occupants oid list. The list will not change until the
+ * event is actually propagated through the system.
+ */
+ public function removeFromOccupants (oid :int) :void
+ {
+ requestOidRemove(OCCUPANTS, oid);
+ }
+ // AUTO-GENERATED: METHODS END
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/ClusteredBodyObject.as b/src/as/com/threerings/whirled/spot/data/ClusteredBodyObject.as
new file mode 100644
index 000000000..2a1958c42
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/ClusteredBodyObject.as
@@ -0,0 +1,48 @@
+//
+// $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.
+ */
+ function getClusterField () :String;
+
+ /**
+ * Returns the oid of the cluster to which this user currently
+ * belongs.
+ */
+ function getClusterOid () :int;
+
+ /**
+ * Sets the oid of the cluster to which this user currently belongs.
+ */
+ function setClusterOid (clusterOid :int) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/Location.as b/src/as/com/threerings/whirled/spot/data/Location.as
new file mode 100644
index 000000000..79390a316
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/Location.as
@@ -0,0 +1,50 @@
+//
+// $Id: Location.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.threerings.io.Streamable;
+
+import com.threerings.util.Hashable;
+
+/**
+ * Contains information on a scene occupant's position and orientation.
+ */
+public interface Location extends Streamable, Hashable
+{
+ /**
+ * Get a new Location instance that is equals() to this one but that
+ * has an orientation facing the opposite direction.
+ */
+ function getOpposite () :Location;
+
+ /**
+ * Two locations are equivalent if they specify the same location
+ * and orientation.
+ */
+ function equivalent (other :Location) :Boolean;
+
+ /**
+ * Locations are cloneable.
+ */
+ function clone () :Object;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/Portal.as b/src/as/com/threerings/whirled/spot/data/Portal.as
new file mode 100644
index 000000000..7df8f22c0
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/Portal.as
@@ -0,0 +1,133 @@
+//
+// $Id: Portal.java 4072 2006-04-28 01:34:02Z 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.Streamable;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.util.Hashable;
+
+/**
+ * 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
+ implements Streamable, Hashable
+{
+ /** This portal's unique identifier. */
+ public var portalId :int;
+
+ /** The location of the portal. Typically this is a base Location (2d)
+ * class, but different games could use a different subclass of
+ * Location. */
+ public var loc :Location;
+
+ /** The scene identifier of the scene to which a body will exit when
+ * they "use" this portal. */
+ public var targetSceneId :int;
+
+ /** The portal identifier of the portal at which a body will enter
+ * the target scene when they "use" this portal. */
+ public var targetPortalId :int;
+
+ /**
+ * Returns a location instance configured with the location and
+ * orientation of this portal.
+ */
+ public function getLocation () :Location
+ {
+ return (loc.clone() as Location);
+ }
+
+ /**
+ * 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 function getOppLocation () :Location
+ {
+ 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 function isValid () :Boolean
+ {
+ return (targetSceneId > 0) && (targetPortalId > 0);
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeShort(portalId);
+ out.writeObject(loc);
+ out.writeInt(targetSceneId);
+ out.writeShort(targetPortalId);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ portalId = ins.readShort();
+ loc = (ins.readObject() as Location);
+ targetSceneId = ins.readInt();
+ targetPortalId = ins.readShort();
+ }
+
+ /**
+ * Creates a clone of this instance.
+ */
+ public function clone () :Object
+ {
+ var p :Portal = new Portal();
+ p.portalId = portalId;
+ p.loc = loc;
+ p.targetSceneId = targetSceneId;
+ p.targetPortalId = targetPortalId;
+ return p;
+ }
+
+ // documentation inherited from interface Hashable
+ public function equals (other :Object) :Boolean
+ {
+ return (other is Portal) &&
+ ((other as Portal).portalId == portalId);
+ }
+
+ // documentation inherited from interface Hashable
+ public function hashCode () :int
+ {
+ return portalId;
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SceneLocation.as b/src/as/com/threerings/whirled/spot/data/SceneLocation.as
new file mode 100644
index 000000000..d2de3003f
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SceneLocation.as
@@ -0,0 +1,69 @@
+//
+// $Id: SceneLocation.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.util.Hashable;
+
+import com.threerings.presents.dobj.DSet_Entry;
+
+/**
+ * Extends {@link Location} with the data and functionality needed to
+ * represent a particular user's location in a scene.
+ */
+public class SceneLocation
+ implements DSet_Entry, Hashable
+{
+ /** The oid of the body that occupies this location. */
+ public var bodyOid :int;
+
+ /** The actual location, which is interpreted by the display system. */
+ public var loc :Location;
+
+ /**
+ * Creates a scene location with the specified information.
+ */
+ public function SceneLocation (loc :Location = null, bodyOid :int = 0)
+ {
+ this.loc = loc;
+ this.bodyOid = bodyOid;
+ }
+
+ // documentation inherited from interface DSet_Entry
+ public function getKey () :Object
+ {
+ return bodyOid;
+ }
+
+ // documentation inherited from interface Hashable
+ public function equals (other :Object) :Boolean
+ {
+ return (other is SceneLocation) &&
+ this.loc.equals((other as SceneLocation).loc);
+ }
+
+ // documentation inherited from interface Hashable
+ public function hashCode () :int
+ {
+ return loc.hashCode();
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotCodes.as b/src/as/com/threerings/whirled/spot/data/SpotCodes.as
new file mode 100644
index 000000000..c5ad11f23
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotCodes.as
@@ -0,0 +1,58 @@
+//
+// $Id: SpotCodes.java 3099 2004-08-27 02:21:06Z 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.crowd.chat.data.ChatCodes;
+
+import com.threerings.whirled.data.SceneCodes;
+
+/**
+ * Contains codes used by the Spot invocation services.
+ */
+public class SpotCodes extends SceneCodes /*, ChatCodes */
+{
+ /** An error code indicating that the portal specified in a
+ * traversePortal request does not exist. */
+ public static const NO_SUCH_PORTAL :String = "m.no_such_portal";
+
+ /** An error code indicating that a location is occupied. Usually
+ * generated by a failed changeLoc request. */
+ public static const LOCATION_OCCUPIED :String = "m.location_occupied";
+
+ /** An error code indicating that a location is not valid. Usually
+ * generated by a failed changeLoc request. */
+ public static const INVALID_LOCATION :String = "m.invalid_location";
+
+ /** An error code indicating that a cluster is not valid. Usually
+ * generated by a failed joinCluster request. */
+ public static const NO_SUCH_CLUSTER :String = "m.no_such_cluster";
+
+ /** An error code indicating that a cluster is full. Usually generated
+ * by a failed joinCluster request. */
+ public static const CLUSTER_FULL :String = "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 const CLUSTER_CHAT_TYPE :String = "clusterChat";
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotMarshaller.as b/src/as/com/threerings/whirled/spot/data/SpotMarshaller.as
new file mode 100644
index 000000000..41e4b70b2
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotMarshaller.as
@@ -0,0 +1,97 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2005 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.client.ConfirmListener;
+import com.threerings.presents.data.InvocationMarshaller;
+import com.threerings.presents.data.ConfirmMarshaller;
+import com.threerings.presents.dobj.InvocationResponseEvent;
+import com.threerings.whirled.client.SceneService;
+import com.threerings.whirled.client.SceneService_SceneMoveListener;
+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 const CHANGE_LOCATION :int = 1;
+
+ // documentation inherited from interface
+ public function changeLocation (arg1 :Client, arg2 :int, arg3 :Location, arg4 :ConfirmListener) :void
+ {
+ var listener4 :ConfirmMarshaller = new ConfirmMarshaller();
+ listener4.listener = arg4;
+ sendRequest(arg1, CHANGE_LOCATION, [
+ new Integer(arg2), arg3, listener4
+ ]);
+ }
+
+ /** The method id used to dispatch {@link #clusterSpeak} requests. */
+ public static const CLUSTER_SPEAK :int = 2;
+
+ // documentation inherited from interface
+ public function clusterSpeak (arg1 :Client, arg2 :String, arg3 :int) :void
+ {
+ sendRequest(arg1, CLUSTER_SPEAK, [
+ arg2, new Byte(arg3)
+ ]);
+ }
+
+ /** The method id used to dispatch {@link #joinCluster} requests. */
+ public static const JOIN_CLUSTER :int = 3;
+
+ // documentation inherited from interface
+ public function joinCluster (arg1 :Client, arg2 :int, arg3 :ConfirmListener) :void
+ {
+ var listener3 :ConfirmMarshaller listener3 = new ConfirmMarshaller();
+ listener3.listener = arg3;
+ sendRequest(arg1, JOIN_CLUSTER, [
+ new Integer(arg2), listener3
+ ]);
+ }
+
+ /** The method id used to dispatch {@link #traversePortal} requests. */
+ public static const TRAVERSE_PORTAL :int = 4;
+
+ // documentation inherited from interface
+ public function traversePortal (arg1 :Client, arg2 :int, arg3 :int, arg4 :int, arg5 :SceneService_SceneMoveListener) :void
+ {
+ var listener5 :SceneMarshaller_SceneMoveMarshaller = new SceneMarshaller_SceneMoveMarshaller();
+ listener5.listener = arg5;
+ sendRequest(arg1, TRAVERSE_PORTAL, [
+ new Integer(arg2), new Integer(arg3), new Integer(arg4), listener5
+ ]);
+ }
+
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotScene.as b/src/as/com/threerings/whirled/spot/data/SpotScene.as
new file mode 100644
index 000000000..33feb162e
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotScene.as
@@ -0,0 +1,81 @@
+//
+// $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 com.threerings.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.
+ */
+ function getPortal (portalId :int) :Portal;
+
+ /**
+ * Returns the number of portals in this scene.
+ */
+ function getPortalCount () :int;
+
+ /**
+ * Returns an iterator over the portals in this scene.
+ */
+ function getPortals () :Iterator;
+
+ /**
+ * Returns the portal id that should be assigned to the next portal
+ * added to this scene.
+ */
+ function getNextPortalId () :int;
+
+ /**
+ * 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.
+ */
+ function getDefaultEntrance () :Portal;
+
+ /**
+ * 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}.
+ */
+ function addPortal (portal :Portal) :void;
+
+ /**
+ * Removes the specified portal from the scene.
+ */
+ function removePortal (portal :Portal) :void;
+
+ /**
+ * Sets the default entrance in this scene, immediately making the
+ * requisite modifications to the underlying scene model.
+ */
+ function setDefaultEntrance (portal :Portal) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotSceneImpl.as b/src/as/com/threerings/whirled/spot/data/SpotSceneImpl.as
new file mode 100644
index 000000000..542f1a58a
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotSceneImpl.as
@@ -0,0 +1,155 @@
+//
+// $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 com.threerings.util.Iterator;
+import com.threerings.util.HashMap;
+
+/**
+ * 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 function SpotSceneImpl (model :SpotSceneModel)
+ {
+ _smodel = smodel;
+ readPortals();
+ }
+
+ protected function readPortals () :void
+ {
+ _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 var _smodel :SpotSceneModel;
+
+ /** A mapping from portal id to portal. */
+ protected var _portals :HashMap = new HashMap();
+
+ /** We don't allow more than ~32k portals in a scene. Things would
+ * slow down *way* before we got there. */
+ protected static const MAX_PORTAL_ID :int = int(Math.pow(2, 15) - 1);
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotSceneModel.as b/src/as/com/threerings/whirled/spot/data/SpotSceneModel.as
new file mode 100644
index 000000000..3d3aacfd2
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotSceneModel.as
@@ -0,0 +1,113 @@
+//
+// $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.threerings.io.Streamable;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+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
+ implements Streamable, AuxModel
+{
+ /** An array containing all portals in this scene. */
+ public var portals :TypedArray = new TypedArray(Portal);
+
+ /** 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 var defaultEntranceId :int = -1;
+
+ /**
+ * Adds a portal to this scene model.
+ */
+ public function addPortal (portal :Portal) :void
+ {
+ portals.push(portal);
+ }
+
+ /**
+ * Removes a portal from this model.
+ */
+ public function removePortal (portal :Portal) :void
+ {
+ for (var ii :int = 0; ii < portals.length; ii++) {
+ if (portal.equals(portals[ii])) {
+ portals.splice(ii, 1);
+ return;
+ }
+ }
+ }
+
+ // documentation inherited
+ public Object clone ()
+ throws CloneNotSupportedException
+ {
+ // TODO
+ 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;
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeField(portals);
+ out.writeInt(defaultEntranceId);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ portals = (ins.readField(TypedArray.getJavaType(Portal)) as TypedArray);
+ defaultEntranceId = ins.readInt();
+ }
+
+ /**
+ * Locates and returns the {@link SpotSceneModel} among the auxiliary
+ * scene models associated with the supplied scene
+ * model. null is returned if no spot scene model could
+ * be found.
+ */
+ public static function getSceneModel (model :SceneModel) :SpotSceneModel
+ {
+ for (var ii :int = 0; ii < model.auxModels.length; ii++) {
+ if (model.auxModels[ii] is SpotSceneModel) {
+ return (model.auxModels[ii] as SpotSceneModel);
+ }
+ }
+ return null;
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotSceneObject.as b/src/as/com/threerings/whirled/spot/data/SpotSceneObject.as
new file mode 100644
index 000000000..b5d848f05
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotSceneObject.as
@@ -0,0 +1,141 @@
+//
+// $Id: SpotSceneObject.java 3300 2005-01-08 22:05:00Z 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.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 occupantLocs field. */
+ public static const OCCUPANT_LOCS :String = "occupantLocs";
+
+ /** The field name of the clusters field. */
+ public static const CLUSTERS :String = "clusters";
+ // AUTO-GENERATED: FIELDS END
+
+ /** A distributed set containing {@link SceneLocation} records for all
+ * occupants of this scene. */
+ public var occupantLocs :DSet = new DSet();
+
+ /** Contains information on all {@link Cluster}s in this scene. */
+ public var clusters :DSet = new DSet();
+
+ // AUTO-GENERATED: METHODS START
+ /**
+ * Requests that the specified entry be added to the
+ * occupantLocs set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function addToOccupantLocs (elem :DSet_Entry) :void
+ {
+ requestEntryAdd(OCCUPANT_LOCS, occupantLocs, elem);
+ }
+
+ /**
+ * Requests that the entry matching the supplied key be removed from
+ * the occupantLocs set. The set will not change until the
+ * event is actually propagated through the system.
+ */
+ public function removeFromOccupantLocs (key :Object) :void
+ {
+ requestEntryRemove(OCCUPANT_LOCS, occupantLocs, key);
+ }
+
+ /**
+ * Requests that the specified entry be updated in the
+ * occupantLocs set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function updateOccupantLocs (elem :DSet_Entry) :void
+ {
+ requestEntryUpdate(OCCUPANT_LOCS, occupantLocs, elem);
+ }
+
+ /**
+ * Requests that the occupantLocs 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 function setOccupantLocs (value :DSet) :void
+ {
+ requestAttributeChange(OCCUPANT_LOCS, value, this.occupantLocs);
+ this.occupantLocs = value;
+ }
+
+ /**
+ * Requests that the specified entry be added to the
+ * clusters set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function addToClusters (elem :DSet_Entry) :void
+ {
+ requestEntryAdd(CLUSTERS, clusters, elem);
+ }
+
+ /**
+ * Requests that the entry matching the supplied key be removed from
+ * the clusters set. The set will not change until the
+ * event is actually propagated through the system.
+ */
+ public function removeFromClusters (key :Object) :void
+ {
+ requestEntryRemove(CLUSTERS, clusters, key);
+ }
+
+ /**
+ * Requests that the specified entry be updated in the
+ * clusters set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function updateClusters (elem :DSet_Entry) :void
+ {
+ requestEntryUpdate(CLUSTERS, clusters, elem);
+ }
+
+ /**
+ * Requests that the clusters 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 function setClusters (value :DSet) :void
+ {
+ requestAttributeChange(CLUSTERS, value, this.clusters);
+ this.clusters = value;
+ }
+ // AUTO-GENERATED: METHODS END
+}
+}