diff --git a/src/java/com/threerings/parlor/game/server/GameDispatcher.java b/src/java/com/threerings/parlor/game/server/GameDispatcher.java
index 48b590190..d635244ea 100644
--- a/src/java/com/threerings/parlor/game/server/GameDispatcher.java
+++ b/src/java/com/threerings/parlor/game/server/GameDispatcher.java
@@ -21,8 +21,8 @@
package com.threerings.parlor.game.server;
-import com.threerings.parlor.game.data.GameMarshaller;
import com.threerings.parlor.game.client.GameService;
+import com.threerings.parlor.game.data.GameMarshaller;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
diff --git a/src/java/com/threerings/stage/data/StageCodes.java b/src/java/com/threerings/stage/data/StageCodes.java
new file mode 100644
index 000000000..065b87812
--- /dev/null
+++ b/src/java/com/threerings/stage/data/StageCodes.java
@@ -0,0 +1,54 @@
+//
+// $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.stage.data;
+
+import com.threerings.util.MessageBundle;
+
+import com.threerings.crowd.data.BodyObject;
+
+/**
+ * Codes and constants relating to the Stage system.
+ */
+public interface StageCodes
+{
+ /** The i18n bundle identifier for the Stage system. */
+ public static final String STAGE_MESSAGE_BUNDLE = "stage.general";
+
+ /** The resource set that contains our tileset bundles. */
+ public static final String TILESET_RSRC_SET = "tilesets";
+
+ /** The access control identifier for scene modification privileges.
+ * See {@link BodyObject#checkAccess}. */
+ public static final String MODIFY_SCENE_ACCESS = "stage.modify_scene";
+
+ /** The access control identifier for potentially damaging scene
+ * modification privileges. See {@link BodyObject#checkAccess}. */
+ public static final String MUTILATE_SCENE_ACCESS = "stage.mutilate_scene";
+
+ /** An error delivered when adding objects to scenes. */
+ public static final String ERR_NO_OVERLAP = MessageBundle.qualify(
+ STAGE_MESSAGE_BUNDLE, "m.addobj_no_overlap");
+
+ /** An error delivered when adding objects to scenes. */
+ public static final String ERR_CANNOT_CLUSTER = MessageBundle.qualify(
+ STAGE_MESSAGE_BUNDLE, "m.cannot_cluster");
+}
diff --git a/src/java/com/threerings/stage/data/StageOccupantInfo.java b/src/java/com/threerings/stage/data/StageOccupantInfo.java
new file mode 100644
index 000000000..25be04f78
--- /dev/null
+++ b/src/java/com/threerings/stage/data/StageOccupantInfo.java
@@ -0,0 +1,40 @@
+//
+// $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.stage.data;
+
+import com.threerings.crowd.data.OccupantInfo;
+
+/**
+ * Extends the standard occupant info with stage specific business.
+ */
+public class StageOccupantInfo extends OccupantInfo
+{
+ /**
+ * Should return true if the occupant in question is available to be
+ * clustered with, false if they are "busy". This means that a user
+ * can click on them and start a chat circle.
+ */
+ public boolean isClusterable ()
+ {
+ return true;
+ }
+}
diff --git a/src/java/com/threerings/stage/data/StageScene.java b/src/java/com/threerings/stage/data/StageScene.java
index 38a81f328..899144977 100644
--- a/src/java/com/threerings/stage/data/StageScene.java
+++ b/src/java/com/threerings/stage/data/StageScene.java
@@ -40,27 +40,6 @@ public class StageScene extends SceneImpl
readInterestingObjects();
}
- protected void readInterestingObjects ()
- {
- _objects.clear();
- StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(_model);
- if (mmodel != null) {
- mmodel.getInterestingObjects(_objects);
- }
- }
-
- // documentation inherited
- public void updateReceived (SceneUpdate update)
- {
- super.updateReceived(update);
-
- // update our spot scene delegate
- _sdelegate.updateReceived();
-
- // re-read our interesting objects
- readInterestingObjects();
- }
-
/**
* Returns the scene type (e.g. "world", "port", "bank", etc.).
*/
@@ -103,6 +82,14 @@ public class StageScene extends SceneImpl
_model.setDefaultColor(classId, colorId);
}
+ /**
+ * Iterates over all of the interesting objects in this scene.
+ */
+ public Iterator enumerateObjects ()
+ {
+ return _objects.iterator();
+ }
+
/**
* Adds a new object to this scene.
*/
@@ -136,42 +123,16 @@ public class StageScene extends SceneImpl
return removed;
}
- /**
- * Iterates over all of the interesting objects in this scene.
- */
- public Iterator enumerateObjects ()
+ // documentation inherited
+ public void updateReceived (SceneUpdate update)
{
- return _objects.iterator();
- }
+ super.updateReceived(update);
- /**
- * Applies the supplied scene update to this scene.
- */
- public void applyUpdate (SceneUpdate update)
- {
- if (update instanceof AddObjectUpdate) {
- AddObjectUpdate aou = (AddObjectUpdate) update;
+ // update our spot scene delegate
+ _sdelegate.updateReceived();
- // remove any occluded objects
- if (aou.casualties != null) {
- for (int ii = 0; ii < aou.casualties.length; ii++) {
- removeObject(aou.casualties[ii]);
- }
- }
-
- // add the object
- addObject(aou.info);
-
- } else if (update instanceof DefaultColorUpdate) {
- DefaultColorUpdate dcu = (DefaultColorUpdate) update;
- setDefaultColor(dcu.classId, dcu.colorId);
-
- } else {
- Log.warning("Unknown scene update applied to StageScene: " + update);
- }
-
- // increment our scene version
- setVersion(getVersion() + 1);
+ // re-read our interesting objects
+ readInterestingObjects();
}
// documentation inherited
@@ -224,6 +185,15 @@ public class StageScene extends SceneImpl
_sdelegate.setDefaultEntrance(portal);
}
+ protected void readInterestingObjects ()
+ {
+ _objects.clear();
+ StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(_model);
+ if (mmodel != null) {
+ mmodel.getInterestingObjects(_objects);
+ }
+ }
+
/** A reference to our scene model. */
protected StageSceneModel _model;
diff --git a/src/java/com/threerings/stage/data/StageSceneConfig.java b/src/java/com/threerings/stage/data/StageSceneConfig.java
new file mode 100644
index 000000000..ef7f4dff1
--- /dev/null
+++ b/src/java/com/threerings/stage/data/StageSceneConfig.java
@@ -0,0 +1,26 @@
+//
+// $Id: StageSceneConfig.java 8478 2003-05-07 05:12:26Z mdb $
+
+package com.threerings.stage.data;
+
+import com.threerings.crowd.data.PlaceConfig;
+
+import com.threerings.stage.client.StageSceneController;
+
+/**
+ * Place configuration for the main isometric scenes in Stage.
+ */
+public class StageSceneConfig extends PlaceConfig
+{
+ // documentation inherited
+ public Class getControllerClass ()
+ {
+ return StageSceneController.class;
+ }
+
+ // documentation inherited
+ public String getManagerClassName ()
+ {
+ return "com.threerings.stage.server.StageSceneManager";
+ }
+}
diff --git a/src/java/com/threerings/stage/data/StageSceneMarshaller.java b/src/java/com/threerings/stage/data/StageSceneMarshaller.java
new file mode 100644
index 000000000..5db275fb2
--- /dev/null
+++ b/src/java/com/threerings/stage/data/StageSceneMarshaller.java
@@ -0,0 +1,54 @@
+//
+// $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.stage.data;
+
+import com.threerings.miso.data.ObjectInfo;
+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.stage.client.StageSceneService;
+
+/**
+ * Provides the implementation of the {@link StageSceneService} 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 StageSceneMarshaller extends InvocationMarshaller
+ implements StageSceneService
+{
+ /** The method id used to dispatch {@link #addObject} requests. */
+ public static final int ADD_OBJECT = 1;
+
+ // documentation inherited from interface
+ public void addObject (Client arg1, ObjectInfo arg2, InvocationService.ConfirmListener arg3)
+ {
+ InvocationMarshaller.ConfirmMarshaller listener3 = new InvocationMarshaller.ConfirmMarshaller();
+ listener3.listener = arg3;
+ sendRequest(arg1, ADD_OBJECT, new Object[] {
+ arg2, listener3
+ });
+ }
+
+}
diff --git a/src/java/com/threerings/stage/data/StageSceneObject.java b/src/java/com/threerings/stage/data/StageSceneObject.java
new file mode 100644
index 000000000..511bcf3a9
--- /dev/null
+++ b/src/java/com/threerings/stage/data/StageSceneObject.java
@@ -0,0 +1,83 @@
+//
+// $Id: StageSceneObject.java 18473 2004-12-28 03:52:57Z mdb $
+
+package com.threerings.stage.data;
+
+import com.threerings.whirled.spot.data.SpotSceneObject;
+
+/**
+ * Extends the basic {@link SpotSceneObject} with data and services
+ * specific to isometric stage scenes.
+ */
+public class StageSceneObject extends SpotSceneObject
+{
+ // AUTO-GENERATED: FIELDS START
+ /** The field name of the stageSceneService field. */
+ public static final String STAGE_SCENE_SERVICE = "stageSceneService";
+
+ /** The field name of the lightLevel field. */
+ public static final String LIGHT_LEVEL = "lightLevel";
+
+ /** The field name of the lightShade field. */
+ public static final String LIGHT_SHADE = "lightShade";
+ // AUTO-GENERATED: FIELDS END
+
+ /** Provides stage scene services. */
+ public StageSceneMarshaller stageSceneService;
+
+ /** The light level in this scene. 0f being fully on, 1f fully shaded. */
+ public float lightLevel = 0f;
+
+ /** The color of the light, if the light level is not 0f. */
+ public int lightShade = 0;
+
+ // AUTO-GENERATED: METHODS START
+ /**
+ * Requests that the stageSceneService field be set to the
+ * specified 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 setStageSceneService (StageSceneMarshaller value)
+ {
+ StageSceneMarshaller ovalue = this.stageSceneService;
+ requestAttributeChange(
+ STAGE_SCENE_SERVICE, value, ovalue);
+ this.stageSceneService = value;
+ }
+
+ /**
+ * Requests that the lightLevel field be set to the
+ * specified 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 setLightLevel (float value)
+ {
+ float ovalue = this.lightLevel;
+ requestAttributeChange(
+ LIGHT_LEVEL, new Float(value), new Float(ovalue));
+ this.lightLevel = value;
+ }
+
+ /**
+ * Requests that the lightShade field be set to the
+ * specified 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 setLightShade (int value)
+ {
+ int ovalue = this.lightShade;
+ requestAttributeChange(
+ LIGHT_SHADE, new Integer(value), new Integer(ovalue));
+ this.lightShade = value;
+ }
+ // AUTO-GENERATED: METHODS END
+}
diff --git a/src/java/com/threerings/stage/server/StageSceneDispatcher.java b/src/java/com/threerings/stage/server/StageSceneDispatcher.java
new file mode 100644
index 000000000..7efe7de38
--- /dev/null
+++ b/src/java/com/threerings/stage/server/StageSceneDispatcher.java
@@ -0,0 +1,71 @@
+//
+// $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.stage.server;
+
+import com.threerings.miso.data.ObjectInfo;
+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.stage.client.StageSceneService;
+import com.threerings.stage.data.StageSceneMarshaller;
+
+/**
+ * Dispatches requests to the {@link StageSceneProvider}.
+ */
+public class StageSceneDispatcher extends InvocationDispatcher
+{
+ /**
+ * Creates a dispatcher that may be registered to dispatch invocation
+ * service requests for the specified provider.
+ */
+ public StageSceneDispatcher (StageSceneProvider provider)
+ {
+ this.provider = provider;
+ }
+
+ // documentation inherited
+ public InvocationMarshaller createMarshaller ()
+ {
+ return new StageSceneMarshaller();
+ }
+
+ // documentation inherited
+ public void dispatchRequest (
+ ClientObject source, int methodId, Object[] args)
+ throws InvocationException
+ {
+ switch (methodId) {
+ case StageSceneMarshaller.ADD_OBJECT:
+ ((StageSceneProvider)provider).addObject(
+ source,
+ (ObjectInfo)args[0], (InvocationService.ConfirmListener)args[1]
+ );
+ return;
+
+ default:
+ super.dispatchRequest(source, methodId, args);
+ }
+ }
+}
diff --git a/src/java/com/threerings/stage/server/StageSceneManager.java b/src/java/com/threerings/stage/server/StageSceneManager.java
new file mode 100644
index 000000000..443ee7758
--- /dev/null
+++ b/src/java/com/threerings/stage/server/StageSceneManager.java
@@ -0,0 +1,817 @@
+//
+// $Id: WorldSceneManager.java 19769 2005-03-17 07:38:31Z mdb $
+
+package com.threerings.stage.server;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import com.samskivert.io.PersistenceException;
+import com.samskivert.util.HashIntMap;
+import com.samskivert.util.Invoker;
+import com.samskivert.util.StringUtil;
+
+import com.threerings.media.util.AStarPathUtil;
+import com.threerings.media.util.MathUtil;
+import com.threerings.miso.data.ObjectInfo;
+import com.threerings.miso.util.MisoSceneMetrics;
+import com.threerings.miso.util.MisoUtil;
+
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.server.InvocationException;
+
+import com.threerings.whirled.data.SceneUpdate;
+import com.threerings.whirled.spot.data.Cluster;
+import com.threerings.whirled.spot.data.ClusterObject;
+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.server.SpotSceneManager;
+
+import com.threerings.stage.Log;
+import com.threerings.stage.client.StageSceneService;
+import com.threerings.stage.data.AddObjectUpdate;
+import com.threerings.stage.data.DefaultColorUpdate;
+import com.threerings.stage.data.StageCodes;
+import com.threerings.stage.data.StageMisoSceneModel;
+import com.threerings.stage.data.StageOccupantInfo;
+import com.threerings.stage.data.StageScene;
+import com.threerings.stage.data.StageSceneMarshaller;
+import com.threerings.stage.data.StageSceneModel;
+import com.threerings.stage.data.StageSceneObject;
+import com.threerings.stage.util.StageSceneUtil;
+
+/**
+ * Defines extensions to the basic Stage scene manager specific to
+ * displaying isometric "stage" scenes (these may be indoor, outdoor or
+ * aboard a vessel).
+ */
+public class StageSceneManager extends SpotSceneManager
+ implements StageSceneProvider
+{
+ /**
+ * Returns a traversal predicate for use with {@link
+ * StageSceneUtil#findStandingSpot} that validates whether a player can
+ * stand in the searched spots.
+ */
+ public AStarPathUtil.TraversalPred getCanStandPred ()
+ {
+ // this checks to see if we can stand at a particular tile coord
+ return new AStarPathUtil.TraversalPred() {
+ public boolean canTraverse (Object traverser, int x, int y) {
+ _tloc.x = MisoUtil.toFull(x, 2);
+ _tloc.y = MisoUtil.toFull(y, 2);
+ return mayStandAtLocation((BodyObject)traverser, _tloc);
+ }
+ protected Location _tloc = new Location();
+ };
+ }
+
+ /**
+ * Adds the supplied object to this scene. A persistent update is
+ * generated and broadcast to all scene occupants. The update is
+ * stored in the repository for communication to future occupants of
+ * the scene and then entire complex process of changing our virtual
+ * game world is effected at the simple call of this single method.
+ *
+ * @param killOverlap if true, overlapping object will be removed, and
+ * the allowOverlap argument will be ignored.
+ * @param allowOverlap if true, overlapping objects will be allowed
+ * but one must be *very* careful to ensure that they know what they
+ * are doing (ie. the objects have render priorities that correctly
+ * handle the overlap).
+ *
+ * @return true if the object was added, false if the add was rejected
+ * because the object overlaps an existing scene object.
+ */
+ public boolean addObject (ObjectInfo info, boolean killOverlap,
+ boolean allowOverlap)
+ {
+ // determine whether or not any object overlap our footprint
+ Rectangle foot = StageSceneUtil.getObjectFootprint(
+ StageServer.tilemgr, info.tileId, info.x, info.y);
+ if (foot == null) {
+ Log.warning("Aiya! Unable to compute object footprint! " +
+ "[where=" + where() + ", info=" + info + "].");
+ return false;
+ }
+
+ ObjectInfo[] lappers = StageSceneUtil.getIntersectedObjects(
+ StageServer.tilemgr, (StageSceneModel)_sscene.getSceneModel(),
+ foot);
+ if (!killOverlap && lappers.length > 0 && !allowOverlap) {
+ // no overlapping allowed
+ return false;
+ }
+
+ // create our scene update which will be stored in the database
+ // and used to efficiently update clients
+ final AddObjectUpdate update = new AddObjectUpdate();
+ update.init(_sscene.getId(), _sscene.getVersion(), info,
+ killOverlap ? lappers : null);
+
+ Log.info("Adding object '" + update + ".");
+ recordUpdate(update, true);
+
+ return true;
+ }
+
+ /**
+ * Changes the default colorization for the specified color class.
+ * A persistent update is generated and broadcast to all scene
+ * occupants. The update is stored in the repository for communication
+ * to future occupants of the scene and then entire complex process
+ * of changing our virtual game world is effected at the simple call
+ * of this single method.
+ */
+ public void setColor (int classId, int colorId)
+ {
+ DefaultColorUpdate update = new DefaultColorUpdate();
+ update.init(_sscene.getId(), _sscene.getVersion(), classId, colorId);
+ recordUpdate(update, false);
+ }
+
+ /**
+ * Returns true if the specified tile coordinate is passable (the base
+ * tile is passable and it is not in the footprint of an object).
+ */
+ public boolean isPassable (int tx, int ty)
+ {
+ return (StageSceneUtil.isPassable(
+ StageServer.tilemgr, _mmodel.getBaseTileId(tx, ty)) &&
+ !checkContains(_footprints.iterator(), tx, ty));
+ }
+
+ /**
+ * Called by NPPs to determine whether or not they can stand at the
+ * specified location.
+ */
+ public boolean mayStandAtLocation (BodyObject source, Location loc)
+ {
+ return validateLocation(source, loc, false);
+ }
+
+ // documentation inherited from interface
+ public void addObject (ClientObject caller, ObjectInfo info,
+ StageSceneService.ConfirmListener listener)
+ throws InvocationException
+ {
+ BodyObject user = (BodyObject)caller;
+ String err = user.checkAccess(StageCodes.MODIFY_SCENE_ACCESS, _sscene);
+ if (err != null) {
+ throw new InvocationException(err);
+ }
+
+ boolean allowOverlap =
+ (user.checkAccess(StageCodes.MODIFY_SCENE_ACCESS, _sscene) == null);
+ if (addObject(info, false, allowOverlap)) {
+ listener.requestProcessed();
+ } else {
+ listener.requestFailed(StageCodes.ERR_NO_OVERLAP);
+ }
+ }
+
+ // documentation inherited
+ protected void gotSceneData ()
+ {
+ super.gotSceneData();
+
+ // cast some scene related bits
+ _sscene = (StageScene)_scene;
+ _mmodel = StageMisoSceneModel.getSceneModel(_scene.getSceneModel());
+
+ // note the footprints of all objects and portals in this scene
+ computeFootprints();
+ }
+
+ /**
+ * Applies the supplied scene update to our runtime scene and then
+ * fires off an invocation unit to apply the update to the scene
+ * database. Finally the update is "recorded" which broadcasts the
+ * update to all occupants of the scene and stores the update so that
+ * it can be provided as a patch to future scene visitors.
+ */
+ protected void recordUpdate (SceneUpdate update, boolean tilesModified)
+ {
+ // do the standard update processing
+ recordUpdate(update);
+
+ // then recompute some internal structures if need be
+ if (tilesModified) {
+ sceneTilesModified();
+ }
+ }
+
+ /**
+ * Called when any change is made to a scene's base or object tiles
+ * (resulting in a change in the way the scene looks). If any sneaky
+ * business need be done to deal with said addition, it can be handled
+ * here.
+ */
+ protected void sceneTilesModified ()
+ {
+ // compute the object footprints of all objects in this scene
+ computeFootprints();
+ }
+
+ // documentation inherited
+ protected void didStartup ()
+ {
+ super.didStartup();
+ _ssobj = (StageSceneObject)_plobj;
+
+ // register and fill in our stage scene service
+ StageSceneMarshaller service = (StageSceneMarshaller)
+ StageServer.invmgr.registerDispatcher(
+ new StageSceneDispatcher(this), false);
+ _ssobj.setStageSceneService(service);
+ }
+
+ // documentation inherited
+ protected void didShutdown ()
+ {
+ super.didShutdown();
+ StageServer.invmgr.clearDispatcher(_ssobj.stageSceneService);
+ }
+
+ // documentation inherited
+ protected Class getPlaceObjectClass ()
+ {
+ return StageSceneObject.class;
+ }
+
+ // documentation inherited
+ protected void bodyLeft (int bodyOid)
+ {
+ super.bodyLeft(bodyOid);
+
+ // out ye go!
+ _loners.remove(bodyOid);
+ }
+
+ // documentation inherited
+ protected void updateLocation (BodyObject source, Location loc)
+ {
+ super.updateLocation(source, loc);
+
+ // keep a rectangle around for each un-clustered occupant
+ int tx = MisoUtil.fullToTile(loc.x), ty = MisoUtil.fullToTile(loc.y);
+ _loners.put(source.getOid(), new Rectangle(tx, ty, 1, 1));
+ }
+
+ /**
+ * Computes the footprints of all objects and portals in this scene.
+ * This is done when we are first resolved and following any
+ * modifications to the scene's tile data.
+ */
+ protected void computeFootprints ()
+ {
+ _footprints.clear();
+ _mmodel.visitObjects(new StageMisoSceneModel.ObjectVisitor() {
+ public void visit (ObjectInfo info) {
+ Rectangle foot = StageSceneUtil.getObjectFootprint(
+ StageServer.tilemgr, info.tileId, info.x, info.y);
+ _footprints.add(foot);
+ }
+ });
+
+// Log.info("Computed footprints [where=" + where () +
+// ", rects=" + StringUtil.toString(_footprints) + "].");
+
+ // place the tile coordinates of our portals into a set for
+ // efficient comparison with location coordinates
+ _plocs.clear();
+ for (Iterator iter = _sscene.getPortals(); iter.hasNext(); ) {
+ Portal port = (Portal)iter.next();
+ _plocs.add(new Point(MisoUtil.fullToTile(port.x),
+ MisoUtil.fullToTile(port.y)));
+ }
+ }
+
+ /**
+ * Helper function for {@link #mayStandAtLocation} and {@link
+ * #validateLocation(BodyObject,Location)}.
+ */
+ protected boolean validateLocation (BodyObject source, Location loc,
+ boolean allowPortals)
+ {
+ int tx = MisoUtil.fullToTile(loc.x), ty = MisoUtil.fullToTile(loc.y);
+
+ // make sure the tile at that location is passable
+ if (!StageSceneUtil.isPassable(StageServer.tilemgr,
+ _mmodel.getBaseTileId(tx, ty))) {
+// Log.info("Rejecting non-passable loc [who=" + source.who() +
+// ", loc=" + loc + "].");
+ return false;
+ }
+
+ // if they're moving to stand on a portal, let them do it
+ if (allowPortals && _plocs.contains(new Point(tx, ty))) {
+ return true;
+ }
+
+ // if they are already standing on this tile, allow it
+ Location cloc = (Location)
+ _ssobj.occupantLocs.get(new Integer(source.getOid()));
+ if (cloc != null &&
+ MisoUtil.fullToTile(cloc.x) == tx &&
+ MisoUtil.fullToTile(cloc.y) == ty) {
+// Log.info("Allowing adjust [who=" + source.who () +
+// ", cloc=" + cloc + ", nloc=" + loc + "].");
+ return true;
+ }
+
+ // make sure they're not standing in a cluster footprint, an
+ // object footprint, or in the same tile as another scene occupant
+ if (checkContains(_ssobj.clusters.iterator(), tx, ty) ||
+ checkContains(_footprints.iterator(), tx, ty) ||
+ checkContains(_loners.values().iterator(), tx, ty)) {
+// Log.info("Rejecting loc [who=" + source.who() +
+// ", loc=" + loc + ", inCluster=" +
+// checkContains(_ssobj.clusters.iterator(), tx, ty) +
+// ", inFootprint=" +
+// checkContains(_footprints.iterator(), tx, ty) +
+// ", onLoner=" +
+// checkContains(_loners.values().iterator(), tx, ty) +
+// "].");
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Helper function for {@link #validateLocation}. */
+ protected boolean checkContains (Iterator iter, int tx, int ty)
+ {
+ while (iter.hasNext()) {
+ Rectangle rect = (Rectangle)iter.next();
+ if (rect.contains(tx, ty)) {
+// Log.info(StringUtil.toString(rect) + " contains " +
+// StringUtil.coordsToString(tx, ty) + ".");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // documentation inherited
+ protected SceneLocation computeEnteringLocation (
+ BodyObject body, Portal entry)
+ {
+ // sanity check
+ if (entry == null) {
+ Log.warning("Requested to compute entering location for " +
+ "non-existent portal [where=" + where() +
+ ", who=" + body.who() + "].");
+ entry = _sscene.getDefaultEntrance();
+ }
+
+ MisoSceneMetrics metrics = StageSceneUtil.getMetrics();
+ SceneLocation loc = new SceneLocation(
+ entry.getOppLocation(), body.getOid());
+ int tx = MisoUtil.fullToTile(loc.x),
+ ty = MisoUtil.fullToTile(loc.y);
+ int oidx = loc.orient/2;
+ int lidx = (oidx+3)%4; // rotate to the left
+ int ridx = (oidx+1)%4; // rotate to the right
+
+ // start in the row in front of the portal and search forward,
+ // checking the center, then the spot(s) to the left, then the
+ // spot(s) to the right (fanning out by one tile each time)
+ final int MAX_FAN = 4;
+ LOC_SEARCH:
+ for (int fan = 1; fan < MAX_FAN; fan++) {
+ tx += PORTAL_DX[oidx];
+ ty += PORTAL_DY[oidx];
+
+ // look in the center column
+ if (checkEntry(metrics, body, tx, ty, loc)) {
+ break LOC_SEARCH;
+ }
+
+ // look to the left
+ for (int lx = tx, ly = ty, ff = 0; ff < fan; ff++) {
+ lx += PORTAL_DX[lidx];
+ ly += PORTAL_DY[lidx];
+ if (checkEntry(metrics, body, lx, ly, loc)) {
+ break LOC_SEARCH;
+ }
+ }
+
+ // look to the right
+ for (int rx = tx, ry = ty, ff = 0; ff < fan; ff++) {
+ rx += PORTAL_DX[ridx];
+ ry += PORTAL_DY[ridx];
+ if (checkEntry(metrics, body, rx, ry, loc)) {
+ break LOC_SEARCH;
+ }
+ }
+
+ // if this is our last pass and we didn't find anything,
+ // revert back to the portal location
+ if (fan == MAX_FAN-1) {
+ loc = new SceneLocation(
+ entry.getOppLocation(), body.getOid());
+ }
+ }
+
+ tx = MisoUtil.fullToTile(loc.x);
+ ty = MisoUtil.fullToTile(loc.y);
+ _loners.put(body.getOid(), new Rectangle(tx, ty, 1, 1));
+ return loc;
+ }
+
+ /** Helper function for {@link #computeEnteringLocation}. */
+ protected boolean checkEntry (MisoSceneMetrics metrics, BodyObject body,
+ int tx, int ty, SceneLocation sloc)
+ {
+ sloc.x = MisoUtil.toFull(tx, metrics.finegran/2);
+ sloc.y = MisoUtil.toFull(ty, metrics.finegran/2);
+ return validateLocation(body, sloc, false);
+ }
+
+ // documentation inherited
+ protected boolean validateLocation (BodyObject source, Location loc)
+ {
+ // TODO: make sure the user isn't warping to hell and gone (and if
+ // they are, make sure they're an admin)
+ return validateLocation(source, loc, true);
+ }
+
+ // documentation inherited
+ protected boolean canAddBody (ClusterRecord clrec, BodyObject body)
+ {
+ // make sure we have a setting for clusters of this size
+ if (clrec.size() >= TARGET_SIZE.length-2) {
+ return false;
+ }
+
+ // if this is a brand new cluster, just make it 2x2 and be done
+ // with it
+ Cluster cl = clrec.getCluster();
+ if (cl.width == 0) {
+ cl.width = 2;
+ cl.height = 2;
+// Log.info("Created new cluster for " + body.who() +
+// " (" + cl + ").");
+ return true;
+ }
+
+// Log.info("Maybe adding " + body.who() + " to " + cl + ".");
+
+ // if the cluster is already big enough, we're groovy
+ int target = TARGET_SIZE[clrec.size()+1];
+ if (cl.width >= target) {
+ return true;
+ }
+
+ // make an expanded footprint and try to fit it somewhere
+ int expand = target-cl.width;
+ Rectangle rect = null;
+ for (int ii = 0; ii < X_OFF.length; ii++) {
+ rect = new Rectangle(cl.x + expand*X_OFF[ii],
+ cl.y + expand*Y_OFF[ii],
+ cl.width+expand, cl.height+expand);
+
+ // if this rect overlaps objects, other clusters, portals or
+ // impassable tiles, it's no good
+ if (checkIntersects(_ssobj.clusters.iterator(), rect, cl) ||
+ checkIntersects(_footprints.iterator(), rect, cl) ||
+ checkPortals(rect) || checkViolatesPassability(rect)) {
+ rect = null;
+ } else {
+ break;
+ }
+ }
+
+ // if we couldn't expand in any direction, it's no go
+ if (rect == null) {
+ Log.info("Couldn't expand cluster " + cl + ".");
+ return false;
+ }
+
+ // now look to see if we just expanded our cluster over top of any
+ // unsuspecting standers by and if so, attempt to subsume them
+ for (Iterator iter = _loners.keySet().iterator();
+ iter.hasNext(); ) {
+ int bodyOid = ((Integer)iter.next()).intValue();
+ // skip ourselves
+ if (bodyOid == body.getOid()) {
+ continue;
+ }
+
+ // do the right thing with a person standing right on the
+ // cluster (they're the person we're clustering with)
+ Rectangle trect = (Rectangle)_loners.get(bodyOid);
+ if (trect.equals(cl)) {
+ continue;
+ }
+
+ if (trect.intersects(rect)) {
+ // make sure this person isn't already in our cluster
+ ClusterObject clobj = clrec.getClusterObject();
+ if (clobj != null && clobj.occupants.contains(bodyOid)) {
+ Log.warning("Ignoring stale occupant [where=" + where() +
+ ", cluster=" + cl + ", occ=" + bodyOid + "].");
+ continue;
+ }
+
+ // make sure the subsumee exists
+ final BodyObject bobj = (BodyObject)
+ StageServer.omgr.getObject(bodyOid);
+ if (bobj == null) {
+ Log.warning("Can't subsume disappeared body " +
+ "[where=" + where() + ", cluster=" + cl +
+ ", boid=" + bodyOid + "].");
+ continue;
+ }
+
+ // we subsume nearby pirates by queueing up their addition
+ // to our cluster to be processed after we finish adding
+ // ourselves to this cluster
+ final ClusterRecord fclrec = clrec;
+ StageServer.omgr.postRunnable(new Runnable() {
+ public void run () {
+ try {
+ Log.info("Subsuming " + bobj.who() +
+ " into " + fclrec.getCluster() + ".");
+ fclrec.addBody(bobj);
+
+ } catch (InvocationException ie) {
+ Log.info("Unable to subsume neighbor " +
+ "[cluster=" + fclrec.getCluster() +
+ ", neighbor=" + bobj.who() +
+ ", cause=" + ie.getMessage() + "].");
+ }
+ }
+ });
+ }
+ }
+
+// Log.info("Expanding cluster [cluster=" + cl +
+// ", rect=" + rect + "].");
+ cl.setBounds(rect);
+
+ return true;
+ }
+
+ /** Helper function for {@link #canAddBody}. */
+ protected boolean checkIntersects (Iterator iter, Rectangle rect,
+ Rectangle ignore)
+ {
+ while (iter.hasNext()) {
+ Rectangle trect = (Rectangle)iter.next();
+ if (ignore != null && trect.equals(ignore)) {
+ continue;
+ }
+ if (trect.intersects(rect)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Helper function for {@link #canAddBody}. */
+ protected boolean checkPortals (Rectangle rect)
+ {
+ for (Iterator iter = _plocs.iterator(); iter.hasNext(); ) {
+ Point ppoint = (Point)iter.next();
+ if (rect.contains(ppoint)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Helper function for {@link #canAddBody}. */
+ protected boolean checkViolatesPassability (Rectangle rect)
+ {
+ // we just check the whole damned thing, but it's not really that
+ // expensive, so we're not going to worry about it
+ // Check the bounds + 1 in each direction, so we can see if a fringer
+ // blocks us.
+ for (int xx = rect.x-1, ex = rect.x+rect.width+1; xx < ex; xx++) {
+ for (int yy = rect.y-1, ey = rect.y+rect.height+1; yy < ey; yy++) {
+ int btid = _mmodel.getBaseTileId(xx, yy);
+ if ((btid == 0) &&
+ ((xx == rect.x-1) || (xx == rect.x + rect.width) ||
+ (yy == rect.y-1) || (yy == rect.y + rect.height))) {
+ // it's ok to have an unspecified base tile in the outer
+ // border
+ continue;
+
+ } else if (!StageSceneUtil.isPassable(
+ StageServer.tilemgr, btid)) {
+// Log.info("Cluster impassable " +
+// "[rect=" + StringUtil.toString(rect) +
+// ", spot=" + StringUtil.coordsToString(xx, yy) +
+// "].");
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // documentation inherited
+ protected void bodyAdded (ClusterRecord clrec, BodyObject body)
+ {
+ super.bodyAdded(clrec, body);
+
+// Log.info(body.who() + " added to " + clrec + ".");
+
+ // remove them from the loners map if they were in it
+ int bodyOid = body.getOid();
+ _loners.remove(bodyOid);
+
+ Cluster cl = clrec.getCluster();
+ if (clrec.size() == 1) {
+ // if we're adding our first player, assign initial dimensions
+ // to the cluster
+ cl.width = 1; cl.height = 1;
+ Location loc = locationForBody(bodyOid);
+ if (loc == null) {
+ Log.warning("Foreign body added to cluster [clrec=" + clrec +
+ ", body=" + body.who() + "].");
+ cl.x = 10; cl.y = 10;
+ } else {
+ cl.x = MisoUtil.fullToTile(loc.x);
+ cl.y = MisoUtil.fullToTile(loc.y);
+ }
+ // we'll do everything else when occupant two shows up
+ return;
+ }
+
+ // generate a list of all valid locations for this cluster
+ ArrayList locs = StageSceneUtil.getClusterLocs(cl);
+
+// Log.info("Positioning " + clrec.size() + " bodies in " +
+// StringUtil.toString(locs) + " for " + cl + ".");
+
+ // make sure everyone is in their proper position
+ for (Iterator iter = clrec.keySet().iterator(); iter.hasNext(); ) {
+ int tbodyOid = ((Integer)iter.next()).intValue();
+ // leave the newly added player to last
+ if (tbodyOid != bodyOid) {
+ positionBody(cl, tbodyOid, locs);
+ }
+ }
+
+ // finally position the new guy
+ positionBody(cl, bodyOid, locs);
+ }
+
+ /** Helper function for {@link #bodyAdded}. */
+ protected void positionBody (Cluster cl, int bodyOid, ArrayList locs)
+ {
+ SceneLocation sloc = (SceneLocation)
+ _ssobj.occupantLocs.get(new Integer(bodyOid));
+ if (sloc == null) {
+ BodyObject user = (BodyObject)StageServer.omgr.getObject(bodyOid);
+ String who = (user == null) ? ("" + bodyOid) : user.who();
+ Log.warning("Can't position locationless user " +
+ "[where=" + where() + ", cluster=" + cl +
+ ", boid=" + who + "].");
+ return;
+ }
+
+ SceneLocation cloc = getClosestLoc(locs, sloc);
+// Log.info("Maybe moving " + bodyOid + " from " + sloc +
+// " to " + cloc +
+// " (avail=" + StringUtil.toString(locs) + ").");
+ if (cloc != null && !cloc.equivalent(sloc)) {
+ cloc.bodyOid = bodyOid;
+// Log.info("Moving " + bodyOid + " to " + cloc +
+// " for " + cl + ".");
+ _ssobj.updateOccupantLocs(cloc);
+ }
+ }
+
+ // documentation inherited
+ protected void bodyRemoved (ClusterRecord clrec, BodyObject body)
+ {
+ super.bodyRemoved(clrec, body);
+
+ // if we've been reduced to a zero person cluster, we can skip all
+ // this because the cluster will be destroyed when we return
+ if (clrec.size() < 1) {
+ return;
+ }
+
+ // reduce the bounds of the cluster if necessary
+ int target = TARGET_SIZE[clrec.size()];
+ Cluster cl = clrec.getCluster();
+ // allow a cluster to remain one size larger than it needs to be
+ // as long as there's more than one person in it
+ if (cl.width <= (target + ((clrec.size() > 1) ? 1 : 0))) {
+ return;
+ }
+
+ // otherwise shrink the cluster
+ cl.width = target;
+ cl.height = target;
+
+ // generate a list of all valid locations for this cluster
+ ArrayList locs = StageSceneUtil.getClusterLocs(cl);
+
+ // make sure everyone is in their proper position
+ for (Iterator iter = clrec.keySet().iterator(); iter.hasNext(); ) {
+ int bodyOid = ((Integer)iter.next()).intValue();
+ // leave the newly added player to last
+ if (bodyOid != body.getOid()) {
+ positionBody(cl, bodyOid, locs);
+ }
+ }
+ }
+
+ // documentation inherited
+ protected void checkCanCluster (BodyObject initiator, BodyObject target)
+ throws InvocationException
+ {
+ StageOccupantInfo info = (StageOccupantInfo)_ssobj.occupantInfo.get(
+ new Integer(target.getOid()));
+ if (info == null) {
+ Log.warning("Have no occinfo for cluster target " +
+ "[where=" + where() + ", init=" + initiator.who() +
+ ", target=" + target.who() + "].");
+ throw new InvocationException(INTERNAL_ERROR);
+ }
+
+ if (!info.isClusterable()) {
+ throw new InvocationException(StageCodes.ERR_CANNOT_CLUSTER);
+ }
+ }
+
+ /**
+ * Locates and removes the location in the list closest to the
+ * supplied location.
+ */
+ protected static SceneLocation getClosestLoc (
+ ArrayList locs, SceneLocation loc)
+ {
+ SceneLocation cloc = null;
+ float cdist = Integer.MAX_VALUE;
+ int cidx = -1;
+ for (int ii = 0, ll = locs.size(); ii < ll; ii++) {
+ SceneLocation tloc = (SceneLocation)locs.get(ii);
+ float tdist = MathUtil.distance(loc.x, loc.y, tloc.x, tloc.y);
+ if (tdist < cdist) {
+ cloc = tloc;
+ cdist = tdist;
+ cidx = ii;
+ }
+ }
+ if (cidx != -1) {
+ locs.remove(cidx);
+ }
+ return cloc;
+ }
+
+ /** A casted reference to our scene object. */
+ protected StageSceneObject _ssobj;
+
+ /** A casted reference to our scene data. */
+ protected StageScene _sscene;
+
+ /** Our miso scene data extracted for convenience and efficiency. */
+ protected StageMisoSceneModel _mmodel;
+
+ /** Rectangles describing the footprints (in tile coordinates) of all
+ * of our scene objects. */
+ protected ArrayList _footprints = new ArrayList();
+
+ /** Rectangles containing a "footprint" for the users that aren't in
+ * any clusters. */
+ protected HashIntMap _loners = new HashIntMap();
+
+ /** Contains the (tile) coordinates of all of our portals. */
+ protected HashSet _plocs = new HashSet();
+
+ /** The dimensions of a cluster with the specified number of
+ * occupants. */
+ protected static final int[] TARGET_SIZE = {
+ 1, 2, // one person cluster
+ 3, 3, 3, // two to four person cluster
+ 4, 4, 4, 4, // five to eight person cluster
+ 5, 5, 5, 5, // nine to twelve person cluster
+ 6, 6, 6, 6, // thirteen to sixteen person cluster
+ 7, 7, 7, 7, 7, 7, 7, 7, // seventeen to twenty four person cluster
+ 8 // needed though we'll never expand to this size
+ };
+
+ /** Used by {@link #canAddBody}. */
+ protected static final int[] X_OFF = { 0, -1, 0, -1 };
+
+ /** Used by {@link #canAddBody}. */
+ protected static final int[] Y_OFF = { 0, 0, -1, -1 };
+
+ /** Used by {@link #computeEnteringLocation}. */
+ protected static final int[] PORTAL_DX = { 0, -1, 0, 1 }; // W N E S
+
+ /** Used by {@link #computeEnteringLocation}. */
+ protected static final int[] PORTAL_DY = { 1, 0, -1, 0 }; // W N E S
+}
diff --git a/src/java/com/threerings/stage/server/StageSceneProvider.java b/src/java/com/threerings/stage/server/StageSceneProvider.java
new file mode 100644
index 000000000..962735fc8
--- /dev/null
+++ b/src/java/com/threerings/stage/server/StageSceneProvider.java
@@ -0,0 +1,25 @@
+//
+// $Id: StageSceneProvider.java 14426 2004-03-12 12:12:32Z mdb $
+
+package com.threerings.stage.server;
+
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.server.InvocationException;
+import com.threerings.presents.server.InvocationProvider;
+
+import com.threerings.miso.data.ObjectInfo;
+
+import com.threerings.stage.client.StageSceneService;
+
+/**
+ * Defines the server side of the {@link StageSceneService}.
+ */
+public interface StageSceneProvider extends InvocationProvider
+{
+ /**
+ * Handles a {@link StageSceneService#addObject} request.
+ */
+ public void addObject (ClientObject caller, ObjectInfo info,
+ StageSceneService.ConfirmListener listener)
+ throws InvocationException;
+}
diff --git a/src/java/com/threerings/stage/server/StageServer.java b/src/java/com/threerings/stage/server/StageServer.java
new file mode 100644
index 000000000..f7596d0b4
--- /dev/null
+++ b/src/java/com/threerings/stage/server/StageServer.java
@@ -0,0 +1,67 @@
+//
+// $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.stage.server;
+
+import com.threerings.resource.ResourceManager;
+
+import com.threerings.media.tile.TileManager;
+import com.threerings.media.tile.bundle.BundledTileSetRepository;
+
+import com.threerings.whirled.server.WhirledServer;
+
+import com.threerings.stage.Log;
+import com.threerings.stage.data.StageCodes;
+
+/**
+ * Extends the Whirled server to provide services needed by the Stage
+ * system.
+ */
+public abstract class StageServer extends WhirledServer
+{
+ /** A resource manager with which we can load resources in the same
+ * manner that the client does (for resources that are used on both
+ * the server and client). */
+ public ResourceManager rsrcmgr;
+
+ /** Provides access to our tile repository. */
+ public static TileManager tilemgr;
+
+ // documentation inherited
+ public void init ()
+ throws Exception
+ {
+ // do the base server initialization
+ super.init();
+
+ // create the resource manager
+ rsrcmgr = new ResourceManager("rsrc");
+ rsrcmgr.initBundles(null, "config/resource/manager.properties", null);
+
+ // create our tile manager and repository
+ tilemgr = new TileManager(null);
+ tilemgr.setTileSetRepository(
+ new BundledTileSetRepository(rsrcmgr, null,
+ StageCodes.TILESET_RSRC_SET));
+
+ Log.info("Stage server initialized.");
+ }
+}
diff --git a/src/java/com/threerings/whirled/server/SceneManager.java b/src/java/com/threerings/whirled/server/SceneManager.java
index 080bd4cd7..ed4b40087 100644
--- a/src/java/com/threerings/whirled/server/SceneManager.java
+++ b/src/java/com/threerings/whirled/server/SceneManager.java
@@ -127,32 +127,35 @@ public class SceneManager extends PlaceManager
/**
* When a modification is made to a scene, the scene manager should
- * update its internal structures, update the {@link Scene} object,
- * update the repository (either by storing the updated scene
- * wholesale or more efficiently updating only what has changed), and
- * then it should create a {@link SceneUpdate} record that can be
- * delivered to clients to effect the update to the clients's cached
- * copy of the scene model. This update will be stored persistently
- * and provided (along with any other accumulated updates) to clients
- * that later request to enter the scene with an old version of the
- * scene data. Updates are not stored forever, but a sizable number of
- * recent updates are stored so that moderately current clients can
- * apply incremental patches to their scenes rather than redownloading
- * the entire scenes when they change.
+ * create a SceneUpdate instance and pass it to this method which will
+ * update the in-memory scene, and apply and record the update in the
+ * scene repository.
+ *
+ *
This update will be stored persistently and provided (along + * with any other accumulated updates) to clients that later request + * to enter the scene with an old version of the scene data. Updates + * are not stored forever, but a sizable number of recent updates are + * stored so that moderately current clients can apply incremental + * patches to their scenes rather than redownloading entire scenes + * when they change. */ protected void recordUpdate (final SceneUpdate update) { + // instruct our in-memory copy of the scene to apply the update + _scene.updateReceived(update); + // add it to our in memory update list _updates.addUpdate(update); - // and store it in the repository + // and apply and store it in the repository WhirledServer.invoker.postUnit(new Invoker.Unit() { public boolean invoke () { try { - _screg.getSceneRepository().addUpdate(update); + _screg.getSceneRepository().applyAndRecordUpdate( + _scene.getSceneModel(), update); } catch (PersistenceException pe) { - Log.warning("Failed to store scene update " + - "[update=" + update + ", error=" + pe + "]."); + Log.warning("Failed to apply scene update " + update + "."); + Log.logStackTrace(pe); } return false; } diff --git a/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java index e30a68c21..a0142d0fb 100644 --- a/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java +++ b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java @@ -1,5 +1,5 @@ // -// $Id: DummySceneRepository.java,v 1.9 2004/08/27 02:20:43 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -52,7 +52,7 @@ public class DummySceneRepository implements SceneRepository } // documentation inherited from interface - public void addUpdate (SceneUpdate update) + public void applyAndRecordUpdate (SceneModel model, SceneUpdate update) throws PersistenceException { // nothing doing diff --git a/src/java/com/threerings/whirled/server/persist/SceneRepository.java b/src/java/com/threerings/whirled/server/persist/SceneRepository.java index 13368fbd1..0695608fd 100644 --- a/src/java/com/threerings/whirled/server/persist/SceneRepository.java +++ b/src/java/com/threerings/whirled/server/persist/SceneRepository.java @@ -1,5 +1,5 @@ // -// $Id: SceneRepository.java,v 1.7 2004/08/27 02:20:43 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -60,12 +60,14 @@ public interface SceneRepository throws PersistenceException, NoSuchSceneException; /** - * Adds the supplied scene update to the list of updates for its - * associated scene. + * Applise the supplied scene update to persistent representation of + * its associated scene, then stores the update persistently for + * future invocations of the server to load. Note: the scene + * update will have already been applied to the supplied scene model. * * @exception PersistenceException thrown if an error occurs - * attempting to store the scene update. + * attempting to apply the scene update. */ - public void addUpdate (SceneUpdate update) + public void applyAndRecordUpdate (SceneModel model, SceneUpdate update) throws PersistenceException; }