diff --git a/build.xml b/build.xml index 49c8bb997..deebc208c 100644 --- a/build.xml +++ b/build.xml @@ -192,6 +192,9 @@ + + + diff --git a/src/java/com/threerings/stage/Log.java b/src/java/com/threerings/stage/Log.java new file mode 100644 index 000000000..81a155a03 --- /dev/null +++ b/src/java/com/threerings/stage/Log.java @@ -0,0 +1,38 @@ +// +// $Id: Log.java 49 2001-08-09 00:32:53Z mdb $ + +package com.threerings.stage; + +/** + * A placeholder class that contains a reference to the log object used by + * this package. + */ +public class Log +{ + public static com.samskivert.util.Log log = + new com.samskivert.util.Log("stage"); + + /** Convenience function. */ + public static void debug (String message) + { + log.debug(message); + } + + /** Convenience function. */ + public static void info (String message) + { + log.info(message); + } + + /** Convenience function. */ + public static void warning (String message) + { + log.warning(message); + } + + /** Convenience function. */ + public static void logStackTrace (Throwable t) + { + log.logStackTrace(com.samskivert.util.Log.WARNING, t); + } +} diff --git a/src/java/com/threerings/stage/client/SceneColorizer.java b/src/java/com/threerings/stage/client/SceneColorizer.java new file mode 100644 index 000000000..c52233867 --- /dev/null +++ b/src/java/com/threerings/stage/client/SceneColorizer.java @@ -0,0 +1,130 @@ +// +// $Id: SceneColorizer.java 17027 2004-09-10 00:10:46Z ray $ + +package com.threerings.stage.client; + +import java.util.HashMap; +import java.util.Iterator; + +import com.threerings.media.image.ColorPository; +import com.threerings.media.image.Colorization; +import com.threerings.media.tile.TileSet; + +import com.threerings.miso.data.ObjectInfo; + +import com.threerings.stage.Log; +import com.threerings.stage.data.StageScene; + +/** + * Handles colorization of object tiles in a scene. + */ +public class SceneColorizer implements TileSet.Colorizer +{ + /** + * Creates a scene colorizer for the supplied scene. + */ + public SceneColorizer (ColorPository cpos, StageScene scene) + { + _cpos = cpos; + _scene = scene; + + // enumerate the color ids for all possible colorization classes + for (Iterator iter = _cpos.enumerateClasses(); iter.hasNext(); ) { + String cname = ((ColorPository.ClassRecord)iter.next()).name; + _cids.put(cname, _cpos.enumerateColorIds(cname)); + } + } + + /** + * Set an auxiliary colorizer that overrides our colorizations. + */ + public void setAuxiliary (TileSet.Colorizer aux) + { + _aux = aux; + } + + /** + * Obtains a colorizer for the supplied scene object. + */ + public TileSet.Colorizer getColorizer (final ObjectInfo oinfo) + { + // if the object has no custom colorizations, return the default + // colorizer + if (oinfo.zations == 0) { + return this; + } + + // otherwise create a custom colorizer that returns this object's + // custom colorization assignments + return new TileSet.Colorizer() { + public Colorization getColorization (int index, String zation) { + int colorId = 0; + switch (index) { + case 0: colorId = oinfo.getPrimaryZation(); break; + case 1: colorId = oinfo.getSecondaryZation(); break; + } + if (colorId == 0) { + return SceneColorizer.this.getColorization(index, zation); + } else { + return _cpos.getColorization(zation, colorId); + } + } + }; + } + + // documentation inherited from interface TileSet.Colorizer + public Colorization getColorization (int index, String zation) + { + // This method is called when an object in the scene has no colorization + // of its own defined for a particular color class. + if (_aux != null) { + Colorization c = _aux.getColorization(index, zation); + if (c != null) { + return c; + } + } + return _cpos.getColorization(zation, getColorId(zation)); + } + + /** + * Get the colorId to use for the specified colorization. + */ + public int getColorId (String zation) + { + // 1. We see if the scene contains a default color we should use. + ColorPository.ClassRecord rec = _cpos.getClassRecord(zation); + int colorId = _scene.getDefaultColor(rec.classId); + if (colorId == -1) { + // 2. If the scene does not contain a color, see if a default + // is defined for that color class. + ColorPository.ColorRecord def = rec.getDefault(); + if (def != null) { + return def.colorId; + } + + // 3. If there are no defaults whatsoever, just hash on the sceneId. + int[] cids = (int[])_cids.get(zation); + if (cids == null) { + Log.warning("Zoiks, have no colorizations for '" + + zation + "'."); + return -1; + } else { + colorId = cids[_scene.getZoneId() % cids.length]; + } + } + return colorId; + } + + /** An auxiliary colorizer which may temporarily return + * non-standard colorizations. */ + protected TileSet.Colorizer _aux; + + /** The entity from which we obtain colorization info. */ + protected ColorPository _cpos; + + /** The scene for which we're providing zations. */ + protected StageScene _scene; + + /** Contains our colorization class information. */ + protected HashMap _cids = new HashMap(); +} diff --git a/src/java/com/threerings/stage/client/StageSceneController.java b/src/java/com/threerings/stage/client/StageSceneController.java new file mode 100644 index 000000000..06e459507 --- /dev/null +++ b/src/java/com/threerings/stage/client/StageSceneController.java @@ -0,0 +1,33 @@ +// +// $Id: WorldSceneController.java 9625 2003-06-11 04:17:18Z mdb $ + +package com.threerings.stage.client; + +import com.threerings.crowd.client.PlaceView; + +import com.threerings.whirled.data.SceneUpdate; +import com.threerings.whirled.spot.client.SpotSceneController; + +import com.threerings.stage.util.StageContext; + +/** + * Extends the {@link SpotSceneController} with functionality specific to + * displaying Stage scenes. + */ +public class StageSceneController extends SpotSceneController +{ + // documentation inherited + protected PlaceView createPlaceView () + { + return new StageScenePanel((StageContext)_ctx, this); + } + + // documentation inherited + protected void sceneUpdated (SceneUpdate update) + { + super.sceneUpdated(update); + + // let the scene panel know to rethink everything + ((StageScenePanel)_view).sceneUpdated(update); + } +} diff --git a/src/java/com/threerings/stage/client/StageScenePanel.java b/src/java/com/threerings/stage/client/StageScenePanel.java new file mode 100644 index 000000000..62760469b --- /dev/null +++ b/src/java/com/threerings/stage/client/StageScenePanel.java @@ -0,0 +1,659 @@ +// +// $Id: WorldScenePanel.java 18366 2004-12-15 22:56:58Z ray $ + +package com.threerings.stage.client; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; + +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import com.samskivert.swing.Controller; +import com.samskivert.swing.ControllerProvider; +import com.samskivert.swing.util.SwingUtil; +import com.samskivert.util.RuntimeAdjust; + +import com.samskivert.util.Tuple; +import com.threerings.util.StreamableArrayList; + +import com.threerings.media.tile.ObjectTile; +import com.threerings.media.tile.TileSet; +import com.threerings.media.tile.UniformTileSet; + +import com.threerings.miso.client.MisoScenePanel; +import com.threerings.miso.client.SceneObject; +import com.threerings.miso.client.SceneObjectTip; +import com.threerings.miso.data.ObjectInfo; +import com.threerings.miso.util.MisoUtil; + +import com.threerings.crowd.client.PlaceView; +import com.threerings.crowd.data.PlaceObject; + +import com.threerings.whirled.data.SceneUpdate; +import com.threerings.whirled.spot.data.Cluster; +import com.threerings.whirled.spot.data.Location; +import com.threerings.whirled.spot.data.Portal; + +import com.threerings.stage.Log; +import com.threerings.stage.data.StageMisoSceneModel; +import com.threerings.stage.data.StageScene; +import com.threerings.stage.data.StageSceneModel; +import com.threerings.stage.util.StageContext; +import com.threerings.stage.util.StageSceneUtil; + +/** + * Extends the basic Miso scene panel with Stage fun stuff like portals, + * clusters and locations. + */ +public class StageScenePanel extends MisoScenePanel + implements ControllerProvider, KeyListener, PlaceView +{ + /** An action command generated when the user clicks on a location + * within the scene. */ + public static final String LOCATION_CLICKED = "LocationClicked"; + + /** An action command generated when something besides the user wants + * us to move to a location. */ + public static final String LOCATION_REQUESTED = "LocationRequested"; + + /** An action command generated when a cluster is clicked. */ + public static final String CLUSTER_CLICKED = "ClusterClicked"; + + /** Show flag that indicates we should show all clusters. */ + public static final int SHOW_CLUSTERS = (1 << 1); + + /** Show flag that indicates we should render known land plots + * (expensive, don't turn this on willy nilly). */ + public static final int SHOW_PLOTS = (1 << 2); + + /** + * Constructs a stage scene view panel. + */ + public StageScenePanel (StageContext ctx, StageSceneController ctrl) + { + super(ctx, StageSceneUtil.getMetrics()); + + // keep these around for later + _ctx = ctx; + _ctrl = ctrl; + _ctrl.setControlledPanel(this); + + // no layout manager + setLayout(null); + } + + /** + * Get the tileset colorizer in use in this scene. + */ + public SceneColorizer getColorizer () + { + return _rizer; + } + + // documentation inherited + protected TileSet.Colorizer getColorizer (ObjectInfo oinfo) + { + return _rizer.getColorizer(oinfo); + } + + /** + * Returns the scene being displayed by this panel. Do not modify it. + */ + public StageScene getScene () + { + return _scene; + } + + /** + * Sets the scene managed by the panel. + */ + public void setScene (StageScene scene) + { + _scene = scene; + if (_scene != null) { + recomputePortals(); + setSceneModel(StageMisoSceneModel.getSceneModel( + scene.getSceneModel())); + _rizer = new SceneColorizer(_ctx.getColorPository(), scene); + } else { + Log.warning("Zoiks! We can't display a null scene!"); + // TODO: display something to the user letting them know that + // we're so hosed that we don't even know what time it is + } + } + + /** + * Called when we have received a scene update from the server. + */ + public void sceneUpdated (SceneUpdate update) + { + // recompute the portals as those may well have changed + recomputePortals(); + + // we go aheand and completely replace our scene model which will + // reload the whole good goddamned business; it is a little + // shocking to the user, but it's guaranteed to work + refreshScene(); + } + + /** + * Computes a set of display objects for the portals in this scene. + */ + protected void recomputePortals () + { + // create scene objects for our portals + UniformTileSet ots = loadPortalTileSet(); + + _portobjs.clear(); + for (Iterator iter = _scene.getPortals(); iter.hasNext(); ) { + Portal portal = (Portal) iter.next(); + Point p = getScreenCoords(portal.x, portal.y); + int tx = MisoUtil.fullToTile(portal.x); + int ty = MisoUtil.fullToTile(portal.y); + Point ts = MisoUtil.tileToScreen(_metrics, tx, ty, new Point()); + +// Log.info("Added portal " + portal + +// " [screen=" + StringUtil.toString(p) + +// ", tile=" + StringUtil.coordsToString(tx, ty) + +// ", tscreen=" + StringUtil.toString(ts) + "]."); + + ObjectInfo info = new ObjectInfo(0, tx, ty); + info.action = "portal:" + portal.portalId; + + // TODO: cache me + ObjectTile tile = new PortalObjectTile( + ts.x + _metrics.tilehwid - p.x + (PORTAL_ICON_WIDTH / 2), + ts.y + _metrics.tilehei - p.y + (PORTAL_ICON_HEIGHT / 2)); + tile.setImage(ots.getTileMirage(portal.orient)); + + _portobjs.add(new SceneObject(this, info, tile) { + public boolean setHovered (boolean hovered) { + ((PortalObjectTile)this.tile).hovered = hovered; + return isResponsive(); + } + }); + } + } + + // documentation inherited + protected void recomputeVisible () + { + super.recomputeVisible(); + + // add our visible portal objects to the list of visible objects + for (int ii = 0, ll = _portobjs.size(); ii < ll; ii++) { + SceneObject pobj = (SceneObject)_portobjs.get(ii); + if (pobj.bounds != null && _vbounds.intersects(pobj.bounds)) { + _vizobjs.add(pobj); + } + } + } + + // documentation inherited from interface ControllerProvider + public Controller getController () + { + return _ctrl; + } + + // documentation inherited from interface KeyListener + public void keyPressed (KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_ALT) { + // display all tooltips + setShowFlags(SHOW_TIPS, true); + } + } + + // documentation inherited from interface KeyListener + public void keyReleased (KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_ALT) { + // stop displaying all tooltips + setShowFlags(SHOW_TIPS, defaultShowTips()); + } + } + + // documentation inherited from interface PlaceView + public void willEnterPlace (PlaceObject plobj) + { + } + + // documentation inherited from interface PlaceView + public void didLeavePlace (PlaceObject plobj) + { + } + + /** + * Returns true if we should always show the object tooltips by + * default, false if they should only be shown while the 'Alt' key is + * depressed. + */ + protected boolean defaultShowTips () + { + return false; + } + + // documentation inherited + public void keyTyped (KeyEvent e) + { + // nothing + } + + // documentation inherited + protected boolean handleMousePressed (Object hobject, MouseEvent event) + { + // let our parent have a crack at the old mouse press + if (super.handleMousePressed(hobject, event)) { + return true; + } + + // if the hover object is a cluster, we clicked it! + if (event.getButton() == MouseEvent.BUTTON1) { + if (hobject instanceof Cluster) { + int mx = event.getX(), my = event.getY(); + Object actarg = new Tuple(hobject, new Point(mx, my)); + Controller.postAction(this, CLUSTER_CLICKED, actarg); + } else { + // post an action indicating that we've clicked on a location + Point lc = MisoUtil.screenToFull( + _metrics, event.getX(), event.getY(), new Point()); + Controller.postAction(this, LOCATION_CLICKED, + new Location(lc.x, lc.y, (byte)0)); + } + return true; + } + return false; + } + + /** + * Called when our show flags have changed. + */ + protected void showFlagsDidChange (int oldflags) + { + super.showFlagsDidChange(oldflags); + + if ((oldflags & SHOW_CLUSTERS) != (_showFlags & SHOW_CLUSTERS)) { + // dirty every cluster rectangle + Iterator iter = _clusters.values().iterator(); + while (iter.hasNext()) { + dirtyCluster((Shape)iter.next()); + } + } + } + + /** + * Called when a real cluster is created or updated in the scene. + */ + protected void clusterUpdated (Cluster cluster) + { + // compute a screen rectangle that contains all possible "spots" + // in this cluster + ArrayList spots = StageSceneUtil.getClusterLocs(cluster); + Rectangle cbounds = null; + for (int ii = 0, ll = spots.size(); ii < ll; ii++) { + Location loc = (Location)spots.get(ii); + Point sp = getScreenCoords(loc.x, loc.y); + if (cbounds == null) { + cbounds = new Rectangle(sp.x, sp.y, 0, 0); + } else { + cbounds.add(sp.x, sp.y); + } + } + + if (cbounds == null) { + // if we found no one actually in this cluster, nix it + removeCluster(cluster.clusterOid); + } else { + // otherwise have the view update the cluster + updateCluster(cluster, cbounds); + } + } + + /** + * Adds or updates the specified cluster in the view. Metrics will be + * created that allow the cluster to be rendered and hovered over + * (which would make it the active cluster as indicated by {@link + * #getActiveCluster}). + * + * @param cluster the cluster record to be added. + * @param bounds the screen coordinates that bound the occupants of + * the cluster. + */ + public void updateCluster (Cluster cluster, Rectangle bounds) + { + // dirty any old bounds + dirtyCluster(cluster); + + // compute the screen coordinate bounds of this cluster + Shape shape = new Ellipse2D.Float( + bounds.x, bounds.y, bounds.width, bounds.height); + _clusters.put(cluster, shape); + + // if the mouse is inside these bounds, we highlight this cluster + Shape mshape = new Ellipse2D.Float( + bounds.x-CLUSTER_SLOP, bounds.y-CLUSTER_SLOP, + bounds.width+2*CLUSTER_SLOP, bounds.height+2*CLUSTER_SLOP); + _clusterWells.put(cluster, mshape); + + // dirty our new bounds + dirtyCluster(shape); + } + + /** + * Removes the specified cluster from the view. + * + * @return true if such a cluster existed and was removed. + */ + public boolean removeCluster (int clusterOid) + { + Cluster key = new Cluster(); + key.clusterOid = clusterOid; + _clusterWells.remove(key); + Shape shape = (Shape)_clusters.remove(key); + if (shape == null) { + return false; + } + + dirtyCluster(shape); + // clear out the hover object if this cluster was it + if (_hobject instanceof Cluster && + ((Cluster)_hobject).clusterOid == clusterOid) { + _hobject = null; + } + return true; + } + + /** + * A place for subclasses to react to the hover object changing. + * One of the supplied arguments may be null. + */ + protected void hoverObjectChanged (Object oldHover, Object newHover) + { + super.hoverObjectChanged(oldHover, newHover); + + if (oldHover instanceof Cluster) { + dirtyCluster((Cluster)oldHover); + } + if (newHover instanceof Cluster) { + dirtyCluster((Cluster)newHover); + } + } + + /** + * Gives derived classes a chance to compute a hover object that takes + * precedence over sprites and actionable objects. If this method + * returns non-null, no sprite or object hover calculations will be + * performed and the object returned will become the new hover object. + */ + protected Object computeOverHover (int mx, int my) + { + return null; + } + + /** + * Gives derived classes a chance to compute a hover object that is + * used if the mouse is not hovering over a sprite or actionable + * object. If this method is called, it means that there are no + * sprites or objects under the mouse. Thus if it returns non-null, + * the object returned will become the new hover object. + */ + protected Object computeUnderHover (int mx, int my) + { + if (!isResponsive()) { + return null; + } + + // if the current hover object is a cluster, see if we're still in + // that cluster + if (_hobject instanceof Cluster) { + Cluster cluster = (Cluster)_hobject; + if (containsPoint(cluster, mx, my)) { + return cluster; + } + } + + // otherwise, check to see if the mouse is in some new cluster + Iterator iter = _clusters.keySet().iterator(); + while (iter.hasNext()) { + Cluster cclust = (Cluster)iter.next(); + if (containsPoint(cclust, mx, my)) { + return cclust; + } + } + + return null; + } + + /** + * Returns true if the specified cluster contains the supplied screen + * coordinate. + */ + protected boolean containsPoint (Cluster cluster, int mx, int my) + { + Shape shape = (Shape)_clusterWells.get(cluster); + return (shape == null) ? false : shape.contains(mx, my); + } + + /** + * Dirties the supplied cluster. + */ + protected void dirtyCluster (Cluster cluster) + { + if (cluster != null) { + dirtyCluster((Shape)_clusters.get(cluster)); + } + } + + /** + * Dirties the supplied cluster rectangle. + */ + protected void dirtyCluster (Shape shape) + { + if (shape != null) { + Rectangle r = shape.getBounds(); + _remgr.invalidateRegion( + r.x - (CLUSTER_PAD / 2), + r.y - (CLUSTER_PAD / 2), + r.width + (CLUSTER_PAD * 3 / 2), + r.height + (CLUSTER_PAD * 3 / 2)); + } + } + + /** + * Returns the portal at the specified full coordinates or null if no + * portal exists at said coordinates. + */ + public Portal getPortal (int fullX, int fullY) + { + Iterator iter = _scene.getPortals(); + while (iter.hasNext()) { + Portal portal = (Portal)iter.next(); + if (portal.x == fullX && portal.y == fullY) { + return portal; + } + } + return null; + } + + // documentation inherited + protected void paintBaseDecorations (Graphics2D gfx, Rectangle clip) + { + super.paintBaseDecorations(gfx, clip); + + paintClusters(gfx, clip); + } + + /** + * Paints any visible clusters. + */ + protected void paintClusters (Graphics2D gfx, Rectangle clip) + { + // remember how daddy's things were arranged + Object oalias = SwingUtil.activateAntiAliasing(gfx); + Composite ocomp = gfx.getComposite(); + Stroke ostroke = gfx.getStroke(); + + // get ready to draw clusters + gfx.setStroke(CLUSTER_STROKE); + gfx.setColor(CLUSTER_COLOR); + + if (checkShowFlag(SHOW_CLUSTERS) + /* || // _alwaysShowClusters.getValue() */) { + // draw all clusters + Iterator iter = _clusters.keySet().iterator(); + while (iter.hasNext()) { + drawCluster(gfx, clip, (Cluster)iter.next()); + } + + } else if (_hobject instanceof Cluster) { + // or just draw the active cluster + drawCluster(gfx, clip, (Cluster)_hobject); + } + + // put back daddy's things + gfx.setComposite(ocomp); + gfx.setStroke(ostroke); + SwingUtil.restoreAntiAliasing(gfx, oalias); + } + + /** + * Draw the cluster specified by the rectangle. + */ + protected void drawCluster (Graphics2D gfx, Rectangle clip, Cluster cluster) + { + Shape shape = (Shape)_clusters.get(cluster); + if ((shape != null) && shape.intersects(clip)) { + if (_hobject == cluster) { + gfx.setComposite(HIGHLIGHT_ALPHA); + } else { + gfx.setComposite(SHOWN_ALPHA); + } + gfx.draw(shape); + } + } + + /** + * Returns true if the specified location is associated with a portal. + */ + protected boolean isPortal (Location loc) + { + Iterator iter = _scene.getPortals(); + while (iter.hasNext()) { + Portal p = (Portal)iter.next(); + if (p.x == loc.x && p.y == loc.y) { + return true; + } + } + return false; + } + + /** + * Loads up the tileset used to render the portal arrows. + */ + protected UniformTileSet loadPortalTileSet () + { +// return YoUI.client.loadTileSet( +// "media/yohoho/icons/portal_arrows.png", +// PORTAL_ICON_WIDTH, PORTAL_ICON_HEIGHT); + return null; + } + + /** Used to render portals as objects in a scene. */ + protected class PortalObjectTile extends ObjectTile + { + public boolean hovered = false; + + public PortalObjectTile (int ox, int oy) + { + setOrigin(ox, oy); + } + + public void paint (Graphics2D gfx, int x, int y) + { + Composite ocomp = gfx.getComposite(); + if (!isResponsive() || !hovered) { + gfx.setComposite(INACTIVE_PORTAL_ALPHA); + } + super.paint(gfx, x, y); + gfx.setComposite(ocomp); + } + } + + /** A reference to our client context. */ + protected StageContext _ctx; + + /** The controller with which we work in tandem. */ + protected StageSceneController _ctrl; + + /** Our currently displayed scene. */ + protected StageScene _scene; + + /** Contains scene objects for our portals. */ + protected ArrayList _portobjs = new ArrayList(); + + /** Shapes describing the clusters, indexed by cluster. */ + protected HashMap _clusters = new HashMap(); + + /** Shapes describing the clusters, indexed by cluster. */ + protected HashMap _clusterWells = new HashMap(); + + /** Handles scene object colorization. */ + protected SceneColorizer _rizer; + +// /** A debug hook that toggles always-on rendering of clusters. */ +// protected static RuntimeAdjust.BooleanAdjust _alwaysShowClusters = +// new RuntimeAdjust.BooleanAdjust( +// "Causes all clusters to always be rendered.", +// "yohoho.miso.always_show_clusters", +// ClientPrefs.config, false); + + /** The width of the portal icons. */ + protected static final int PORTAL_ICON_WIDTH = 48; + + /** The height of the portal icons. */ + protected static final int PORTAL_ICON_HEIGHT = 48; + + /** The distance within which the mouse must be from a location + * in order to highlight it. */ + protected static final int MAX_LOCATION_DIST = 25; + + /** The amount the stroke a cluster. */ + protected static final int CLUSTER_PAD = 4; + + /** The width with which to draw the cluster. */ + protected static final Stroke CLUSTER_STROKE = new BasicStroke(CLUSTER_PAD); + + /** The color used to render clusters. */ + protected static final Color CLUSTER_COLOR = Color.ORANGE; + + /** Alpha level used to hightlight locations or clusters. */ + protected static final Composite HIGHLIGHT_ALPHA = + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); + + /** Alpha level used to render clusters when they're not selected. */ + protected static final Composite SHOWN_ALPHA = + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.15f); + + /** The alpha with which to render inactive portals. */ + protected static final Composite INACTIVE_PORTAL_ALPHA = HIGHLIGHT_ALPHA; + + /** The number of pixels outside a cluster when we assume the mouse is + * "over" that cluster. */ + protected static final int CLUSTER_SLOP = 25; +} diff --git a/src/java/com/threerings/stage/client/StageSceneService.java b/src/java/com/threerings/stage/client/StageSceneService.java new file mode 100644 index 000000000..683ba4472 --- /dev/null +++ b/src/java/com/threerings/stage/client/StageSceneService.java @@ -0,0 +1,21 @@ +// +// $Id: WorldSceneService.java 14426 2004-03-12 12:12:32Z mdb $ + +package com.threerings.stage.client; + +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; + +import com.threerings.miso.data.ObjectInfo; + +/** + * Provides services relating to Stage scenes. + */ +public interface StageSceneService extends InvocationService +{ + /** + * Requests to add the supplied object to the current scene. + */ + public void addObject (Client client, ObjectInfo info, + ConfirmListener listener); +} diff --git a/src/java/com/threerings/stage/data/AddObjectUpdate.java b/src/java/com/threerings/stage/data/AddObjectUpdate.java new file mode 100644 index 000000000..8e7178ac2 --- /dev/null +++ b/src/java/com/threerings/stage/data/AddObjectUpdate.java @@ -0,0 +1,53 @@ +// +// $Id: AddObjectUpdate.java 15953 2004-06-11 23:40:47Z ray $ + +package com.threerings.stage.data; + +import com.threerings.miso.data.ObjectInfo; + +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.data.SceneUpdate; + +/** + * A scene update that is broadcast when an object has been added to a + * scene. + */ +public class AddObjectUpdate extends SceneUpdate +{ + /** The info on the object. */ + public ObjectInfo info; + + /** If non-null, a list of objects to remove from the scene. */ + public ObjectInfo[] casualties; + + /** + * Initializes this update with all necessary data. + * + * @param casualties optional, a list of objects to remove. + */ + public void init (int targetId, int targetVersion, ObjectInfo info, + ObjectInfo[] casualties) + { + init(targetId, targetVersion); + this.info = info; + this.casualties = casualties; + } + + // documentation inherited + public void apply (SceneModel model) + { + super.apply(model); + + StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(model); + + // wipe out the objects that need to go + if (casualties != null) { + for (int ii = 0; ii < casualties.length; ii++) { + mmodel.removeObject(casualties[ii]); + } + } + + // add the new object + mmodel.addObject(info); + } +} diff --git a/src/java/com/threerings/stage/data/DefaultColorUpdate.java b/src/java/com/threerings/stage/data/DefaultColorUpdate.java new file mode 100644 index 000000000..81bd91b9c --- /dev/null +++ b/src/java/com/threerings/stage/data/DefaultColorUpdate.java @@ -0,0 +1,39 @@ +// +// $Id: DefaultColorUpdate.java 16551 2004-07-27 20:53:28Z ray $ + +package com.threerings.stage.data; + +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.data.SceneUpdate; + +/** + * Update to change the default colorization for objects in a scene which + * do not define their own colorization. + */ +public class DefaultColorUpdate extends SceneUpdate +{ + /** The class id of the colorization we're changing. */ + public int classId; + + /** The color id to set as the new default, or -1 to remove the default. */ + public int colorId; + + /** + * Initializes this update. + */ + public void init (int targetId, int targetVersion, int classId, int colorId) + { + init(targetId, targetVersion); + this.classId = classId; + this.colorId = colorId; + } + + // documentation inherited + public void apply (SceneModel model) + { + super.apply(model); + + StageSceneModel smodel = (StageSceneModel)model; + smodel.setDefaultColor(classId, colorId); + } +} diff --git a/src/java/com/threerings/stage/data/StageMisoSceneModel.java b/src/java/com/threerings/stage/data/StageMisoSceneModel.java new file mode 100644 index 000000000..f6ea348e5 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageMisoSceneModel.java @@ -0,0 +1,64 @@ +// +// $Id: YoMisoSceneModel.java 9680 2003-06-13 02:16:00Z mdb $ + +package com.threerings.stage.data; + +import com.threerings.miso.data.SparseMisoSceneModel; + +import com.threerings.whirled.data.AuxModel; +import com.threerings.whirled.data.SceneModel; + +/** + * Extends the {@link SparseMisoSceneModel} with the necessary interface + * to wire it up to the Whirled auxiliary model system. + */ +public class StageMisoSceneModel extends SparseMisoSceneModel + implements AuxModel +{ + /** The width (in tiles) of a scene section. */ + public static final int SECTION_WIDTH = 9; + + /** The height (in tiles) of a scene section. */ + public static final int SECTION_HEIGHT = 9; + + /** + * Creates a completely uninitialized scene model. + */ + public StageMisoSceneModel () + { + super(SECTION_WIDTH, SECTION_HEIGHT); + } + + /** + * Locates and returns the {@link StageMisoSceneModel} among the + * auxiliary scene models associated with the supplied scene model. + * null is returned if no miso scene model could be + * found. + */ + public static StageMisoSceneModel getSceneModel (SceneModel model) + { + for (int ii = 0; ii < model.auxModels.length; ii++) { + if (model.auxModels[ii] instanceof StageMisoSceneModel) { + return (StageMisoSceneModel)model.auxModels[ii]; + } + } + return null; + } + + /** + * Returns the section key for the specified tile coordinate. + */ + public int getSectionKey (int x, int y) + { + return key(x, y); + } + + /** + * Returns the section identified by the specified key, or null if no + * section exists for that key. + */ + public Section getSection (int key) + { + return (Section)_sections.get(key); + } +} diff --git a/src/java/com/threerings/stage/data/StageScene.java b/src/java/com/threerings/stage/data/StageScene.java new file mode 100644 index 000000000..38a81f328 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageScene.java @@ -0,0 +1,235 @@ +// +// $Id: YoScene.java 17013 2004-09-08 02:39:09Z ray $ + +package com.threerings.stage.data; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.threerings.miso.data.ObjectInfo; +import com.threerings.miso.util.MisoUtil; + +import com.threerings.crowd.data.PlaceConfig; + +import com.threerings.whirled.data.Scene; +import com.threerings.whirled.data.SceneImpl; +import com.threerings.whirled.data.SceneUpdate; + +import com.threerings.whirled.spot.data.Portal; +import com.threerings.whirled.spot.data.SpotScene; +import com.threerings.whirled.spot.data.SpotSceneImpl; +import com.threerings.whirled.spot.data.SpotSceneModel; + +import com.threerings.stage.Log; + +/** + * The implementation of the Stage scene interface. + */ +public class StageScene extends SceneImpl + implements Scene, SpotScene, Cloneable +{ + /** + * Creates an instance that will obtain data from the supplied scene + * model and place config. + */ + public StageScene (StageSceneModel model, PlaceConfig config) + { + super(model, config); + _model = model; + _sdelegate = new SpotSceneImpl(SpotSceneModel.getSceneModel(_model)); + 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.). + */ + public String getType () + { + return _model.type; + } + + /** + * Returns the zone id to which this scene belongs. + */ + public int getZoneId () + { + return _model.zoneId; + } + + /** + * Sets the type of this scene. + */ + public void setType (String type) + { + _model.type = type; + } + + /** + * Get the default color id to use for the specified colorization class, + * or -1 if no default is set. + */ + public int getDefaultColor (int classId) + { + return _model.getDefaultColor(classId); + } + + /** + * Set the default color to use for the specified colorization class id. + * Setting the colorId to -1 disables the default. + */ + public void setDefaultColor (int classId, int colorId) + { + _model.setDefaultColor(classId, colorId); + } + + /** + * Adds a new object to this scene. + */ + public void addObject (ObjectInfo info) + { + _objects.add(info); + + // add it to the underlying scene model + StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(_model); + if (mmodel != null) { + if (!mmodel.addObject(info)) { + Log.warning("Scene model rejected object add " + + "[scene=" + this + ", object=" + info + "]."); + } + } + } + + /** + * Removes an object from this scene. + */ + public boolean removeObject (ObjectInfo info) + { + boolean removed = _objects.remove(info); + + // remove it from the underlying scene model + StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(_model); + if (mmodel != null) { + removed = mmodel.removeObject(info) || removed; + } + + return removed; + } + + /** + * Iterates over all of the interesting objects in this scene. + */ + public Iterator enumerateObjects () + { + return _objects.iterator(); + } + + /** + * Applies the supplied scene update to this scene. + */ + public void applyUpdate (SceneUpdate update) + { + if (update instanceof AddObjectUpdate) { + AddObjectUpdate aou = (AddObjectUpdate) update; + + // 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); + } + + // documentation inherited + public Object clone () + throws CloneNotSupportedException + { + // create a new scene with a clone of our model + return new StageScene((StageSceneModel)_model.clone(), _config); + } + + // documentation inherited from interface + public Portal getPortal (int portalId) + { + return _sdelegate.getPortal(portalId); + } + + // documentation inherited from interface + public int getPortalCount () + { + return _sdelegate.getPortalCount(); + } + + // documentation inherited from interface + public Iterator getPortals () + { + return _sdelegate.getPortals(); + } + + // documentation inherited from interface + public Portal getDefaultEntrance () + { + return _sdelegate.getDefaultEntrance(); + } + + // documentation inherited from interface + public void addPortal (Portal portal) + { + _sdelegate.addPortal(portal); + } + + // documentation inherited from interface + public void removePortal (Portal portal) + { + _sdelegate.removePortal(portal); + } + + // documentation inherited from interface + public void setDefaultEntrance (Portal portal) + { + _sdelegate.setDefaultEntrance(portal); + } + + /** A reference to our scene model. */ + protected StageSceneModel _model; + + /** Our spot scene delegate. */ + protected SpotSceneImpl _sdelegate; + + /** A list of all interesting scene objects. */ + protected ArrayList _objects = new ArrayList(); +} diff --git a/src/java/com/threerings/stage/data/StageSceneModel.java b/src/java/com/threerings/stage/data/StageSceneModel.java new file mode 100644 index 000000000..12f3c6b8a --- /dev/null +++ b/src/java/com/threerings/stage/data/StageSceneModel.java @@ -0,0 +1,85 @@ +// +// $Id: YoSceneModel.java 17643 2004-10-28 22:58:30Z mdb $ + +package com.threerings.stage.data; + +import com.threerings.util.StreamableArrayList; +import com.threerings.util.StreamableIntIntMap; + +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.spot.data.SpotSceneModel; + +/** + * Extends the basic scene model with the notion of a scene type and + * incorporates the necessary auxiliary models used by the Stage system. + */ +public class StageSceneModel extends SceneModel +{ + /** A scene type code. */ + public static final String WORLD = "world"; + + /** This scene's type which is a string identifier used to later + * construct a specific controller to handle this scene. */ + public String type; + + /** The zone id to which this scene belongs. */ + public int zoneId; + + /** If non-null, contains default colorizations to use for objects + * that do not have colorizations defined. */ + public StreamableIntIntMap defaultColors; + + /** + * Get the default color to use for the specified colorization + * classId, or -1 if no default is set for that colorization. + */ + public int getDefaultColor (int classId) + { + if (defaultColors != null) { + return defaultColors.get(classId); + } + return -1; + } + + /** + * Set the default colorId to use for a specified colorization + * classId, or -1 to clear the default for that colorization. + */ + public void setDefaultColor (int classId, int colorId) + { + if (colorId == -1) { + if (defaultColors != null) { + defaultColors.remove(classId); + if (defaultColors.size() == 0) { + defaultColors = null; + } + } + + } else { + if (defaultColors == null) { + defaultColors = new StreamableIntIntMap(); + } + defaultColors.put(classId, colorId); + } + } + + /** + * Creates and returns a blank scene model. + */ + public static StageSceneModel blankStageSceneModel () + { + StageSceneModel model = new StageSceneModel(); + populateBlankStageSceneModel(model); + return model; + } + + /** + * Populates a blank scene model with blank values. + */ + protected static void populateBlankStageSceneModel (StageSceneModel model) + { + populateBlankSceneModel(model); + model.addAuxModel(new SpotSceneModel()); + model.addAuxModel(new StageMisoSceneModel()); + } +} diff --git a/src/java/com/threerings/stage/util/StageContext.java b/src/java/com/threerings/stage/util/StageContext.java new file mode 100644 index 000000000..39cc6c077 --- /dev/null +++ b/src/java/com/threerings/stage/util/StageContext.java @@ -0,0 +1,88 @@ +// +// $Id: BasicYoContext.java 19661 2005-03-09 02:40:29Z andrzej $ + +package com.threerings.stage.util; + +import com.threerings.resource.ResourceManager; +import com.threerings.util.KeyDispatcher; +import com.threerings.util.KeyboardManager; +import com.threerings.util.MessageManager; + +import com.threerings.media.FrameManager; +import com.threerings.media.IconManager; +import com.threerings.media.image.ColorPository; +import com.threerings.media.image.ImageManager; +import com.threerings.media.sound.SoundManager; + +import com.threerings.cast.CharacterManager; +import com.threerings.cast.ComponentRepository; +import com.threerings.miso.util.MisoContext; + +/** + * A context that provides for the myriad requirements of the Stage + * system. + */ +public interface StageContext + extends MisoContext +{ + /** + * Returns the frame manager driving our interface. + */ + public FrameManager getFrameManager (); + + /** + * Returns the resource manager via which all client resources are + * loaded. + */ + public ResourceManager getResourceManager (); + + /** + * Access to the image manager. + */ + public ImageManager getImageManager (); + + /** + * Provides access to the key dispatcher. + */ + public KeyDispatcher getKeyDispatcher (); + + /** + * Returns a reference to the message manager used by the client. + */ + public MessageManager getMessageManager (); + + /** + * Returns a reference to the icon manager used by the client. + */ + public IconManager getIconManager (); + + /** + * Returns a reference to the sound manager used by the client. + */ + public SoundManager getSoundManager(); + + /** + * Returns a reference to the keyboard manager. + */ + public KeyboardManager getKeyboardManager(); + + /** + * Returns the component repository in use by this client. + */ + public ComponentRepository getComponentRepository (); + + /** + * Returns a reference to the colorization repository. + */ + public ColorPository getColorPository (); + + /** + * Translates the specified message using the default bundle. + */ + public String xlate (String message); + + /** + * Translates the specified message using the specified bundle. + */ + public String xlate (String bundle, String message); +} diff --git a/src/java/com/threerings/stage/util/StageSceneUtil.java b/src/java/com/threerings/stage/util/StageSceneUtil.java new file mode 100644 index 000000000..3b4db7e28 --- /dev/null +++ b/src/java/com/threerings/stage/util/StageSceneUtil.java @@ -0,0 +1,401 @@ +// +// $Id: YoSceneUtil.java 19769 2005-03-17 07:38:31Z mdb $ + +package com.threerings.stage.util; + +import java.awt.Point; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Comparator; + +import com.samskivert.util.SortableArrayList; +import com.samskivert.util.StringUtil; +import com.threerings.util.DirectionCodes; +import com.threerings.util.DirectionUtil; + +import com.threerings.media.tile.TileManager; +import com.threerings.media.tile.TileUtil; +import com.threerings.media.tile.TrimmedObjectTileSet; +import com.threerings.media.util.AStarPathUtil; +import com.threerings.media.util.MathUtil; + +import com.threerings.miso.MisoConfig; +import com.threerings.miso.data.ObjectInfo; +import com.threerings.miso.tile.BaseTileSet; +import com.threerings.miso.util.MisoSceneMetrics; +import com.threerings.miso.util.MisoUtil; +import com.threerings.miso.util.ObjectSet; + +import com.threerings.whirled.spot.data.Cluster; +import com.threerings.whirled.spot.data.Location; +import com.threerings.whirled.spot.data.SceneLocation; + +import com.threerings.stage.Log; +import com.threerings.stage.data.StageMisoSceneModel; +import com.threerings.stage.data.StageSceneModel; + +/** + * Provides scene related utility functions. + */ +public class StageSceneUtil +{ + /** + * Returns the scene metrics we use to do our calculations. + */ + public static MisoSceneMetrics getMetrics () + { + return _metrics; + } + + /** + * Does the necessary jiggery pokery to figure out where the specified + * object's associated location is. + */ + public static Location locationForObject ( + TileManager tilemgr, ObjectInfo info) + { + return locationForObject(tilemgr, info.tileId, info.x, info.y); + } + + /** + * Does the necessary jiggery pokery to figure out where the specified + * object's associated location is. + * + * @param tilemgr a tile manager that can be used to look up the tile + * information. + * @param tileId the fully qualified tile id of the object tile. + * @param tx the object's x tile coordinate. + * @param ty the object's y tile coordinate. + */ + public static Location locationForObject ( + TileManager tilemgr, int tileId, int tx, int ty) + { + try { + int tsid = TileUtil.getTileSetId(tileId); + int tidx = TileUtil.getTileIndex(tileId); + TrimmedObjectTileSet tset = (TrimmedObjectTileSet) + tilemgr.getTileSet(tsid); + if (tset == null || tset.getSpotOrient(tidx) < 0) { + return null; + } + + Point opos = MisoUtil.tilePlusFineToFull( + _metrics, tx, ty, tset.getXSpot(tidx), tset.getYSpot(tidx), + new Point()); + +// Log.info("Computed location [set=" + tset.getName() + +// ", tidx=" + tidx + ", tx=" + tx + ", ty=" + ty + +// ", sx=" + tset.getXSpot(tidx) + +// ", sy=" + tset.getYSpot(tidx) + +// ", lx=" + opos.x + ", ly=" + opos.y + +// ", fg=" + _metrics.finegran + "]."); + return new Location(opos.x, opos.y, (byte)tset.getSpotOrient(tidx)); + + } catch (Exception e) { + Log.warning("Unable to look up object tile for scene object " + + "[tileId=" + tileId + ", error=" + e + "]."); + } + return null; + } + + /** + * Converts full coordinates to Cartesian coordinates. + */ + public static void locationToCoords (int lx, int ly, Point coords) + { + int tx = MisoUtil.fullToTile(lx), fx = MisoUtil.fullToFine(lx); + int ty = MisoUtil.fullToTile(ly), fy = MisoUtil.fullToFine(ly); + coords.x = tx*_metrics.finegran+fx; + coords.y = ty*_metrics.finegran+fy; + } + + /** + * Converts Cartesian coordinates back to full coordinates. + */ + public static void coordsToLocation (int cx, int cy, Point loc) + { + loc.x = MisoUtil.toFull(cx/_metrics.finegran, cx%_metrics.finegran); + loc.y = MisoUtil.toFull(cy/_metrics.finegran, cy%_metrics.finegran); + } + + /** + * Returns the footprint, in absolute tile coordinates, for the + * specified object with origin as specified. + */ + public static Rectangle getObjectFootprint ( + TileManager tilemgr, int tileId, int ox, int oy) + { + Rectangle foot = new Rectangle(); + getObjectFootprint(tilemgr, tileId, ox, oy, foot); + return foot; + } + + /** + * Fills in the footprint, in absolute tile coordinates, for the + * specified object with origin as specified. + * + * @return true if the object was successfully looked up and the + * footprint filled in, false if an error occurred trying to look up + * the associated object tile. + */ + public static boolean getObjectFootprint ( + TileManager tilemgr, int tileId, int ox, int oy, Rectangle foot) + { + try { + int tsid = TileUtil.getTileSetId(tileId); + int tidx = TileUtil.getTileIndex(tileId); + TrimmedObjectTileSet tset = (TrimmedObjectTileSet) + tilemgr.getTileSet(tsid); + if (tset == null) { + return false; + } + + int bwidth = tset.getBaseWidth(tidx); + int bheight = tset.getBaseHeight(tidx); + foot.setBounds(ox - bwidth + 1, oy - bheight + 1, bwidth, bheight); + return true; + + } catch (Exception e) { + Log.warning("Unable to look up object tile for scene object " + + "[tileId=" + tileId + ", error=" + e + "]."); + return false; + } + } + + /** + * Looks up the base tile set for the specified fully qualified tile + * identifier and returns true if the associated tile is passable. + */ + public static boolean isPassable (TileManager tilemgr, int tileId) + { + // non-existent tiles are not passable + if (tileId <= 0) { + return false; + } + + try { + int tsid = TileUtil.getTileSetId(tileId); + int tidx = TileUtil.getTileIndex(tileId); + BaseTileSet tset = (BaseTileSet)tilemgr.getTileSet(tsid); + return tset.getPassability()[tidx]; + + } catch (Exception e) { + Log.warning("Unable to look up base tile [tileId=" + tileId + + ", error=" + e + "]."); + return true; + } + } + + /** + * Computes a list of the valid locations in this cluster. + */ + public static ArrayList getClusterLocs (Cluster cluster) + { + ArrayList list = new ArrayList(); + + // convert our tile coordinates into a cartesian coordinate system + // with units equal to one fine coordinate in size + int fx = cluster.x*_metrics.finegran+1, + fy = cluster.y*_metrics.finegran+1; + int fwid = cluster.width*_metrics.finegran-2, + fhei = cluster.height*_metrics.finegran-2; + int cx = fx + fwid/2, cy = fy + fhei/2; + + // if it's a 1x1 cluster, return one location in the center of the + // cluster + if (cluster.width == 1) { + list.add(new SceneLocation(MisoUtil.toFull(cluster.x, 2), + MisoUtil.toFull(cluster.y, 2), + (byte)DirectionCodes.SOUTHWEST, 0)); + return list; + } + + double radius = (double)fwid/2; + int clidx = cluster.width-2; + if (clidx >= CLUSTER_METRICS.length/2 || clidx < 0) { + Log.warning("Requested locs from invalid cluster " + cluster + "."); + Thread.dumpStack(); + return list; + } + + for (double angle = CLUSTER_METRICS[clidx*2]; angle < Math.PI*2; + angle += CLUSTER_METRICS[clidx*2+1]) { + int sx = cx + (int)Math.round(Math.cos(angle) * radius); + int sy = cy + (int)Math.round(Math.sin(angle) * radius); + + // obtain the orientation facing toward the center + int orient = 2*(int)(Math.round(angle/(Math.PI/4))%8); + orient = DirectionUtil.rotateCW(DirectionCodes.SOUTH, orient); + orient = DirectionUtil.getOpposite(orient); + + // convert them back to full coordinates for the location + int tx = MathUtil.floorDiv(sx, _metrics.finegran); + sx = MisoUtil.toFull(tx, sx-(tx*_metrics.finegran)); + int ty = MathUtil.floorDiv(sy, _metrics.finegran); + sy = MisoUtil.toFull(ty, sy-(ty*_metrics.finegran)); + list.add(new SceneLocation(sx, sy, (byte)orient, 0)); + } + + return list; + } + +// /** +// * Returns true if this user is available to be clustered with. +// */ +// public static boolean isClusterable (YoOccupantInfo info) +// { +// switch (info.activity) { +// case ActivityCodes.NONE: +// case ActivityCodes.READING: +// case ActivityCodes.IDLE: +// case ActivityCodes.DISCONNECTED: +// return true; +// default: +// return false; +// } +// } + + /** + * Locates a spot to stand near the supplied rectangular footprint. + * First a spot will be sought in a tile immediately next to the + * footprint, then one tile removed, then two, up to the maximum + * distance specified by dist. + * + * @param foot the tile coordinate footprint around which we are + * attempting to stand. + * @param dist the maximum number of tiles away from the footprint to + * search before giving up. + * @param pred a predicate that will be used to determine whether a + * particular spot can be stood upon (we're hijacking the meaning of + * "traverse" in this case, but the interface is otherwise so nice). + * @param traverser the object that will be passed to the traversal + * predicate. + * @param nearto a point (in tile coordinates) which will be used to + * select from among the valid standing spots, the one nearest to the + * supplied point will be returned. + * @param orient if not {@link DirectionCodes#NONE} this orientation + * will be used to override the "natural" orientation of the spot + * which is facing toward the footprint. + * + * @return the closest spot to the + */ + public static Location findStandingSpot ( + Rectangle foot, int dist, AStarPathUtil.TraversalPred pred, + Object traverser, final Point nearto, int orient) + { + // generate a list of the tile coordinates of all squares around + // this footprint + SortableArrayList spots = new SortableArrayList(); + + for (int dd = 1; dd <= dist; dd++) { + int yy1 = foot.y-dd, yy2 = foot.y+foot.height+dd-1; + int xx1 = foot.x-dd, xx2 = foot.x+foot.width+dd-1; + + // get the corners + spots.add(new Location(xx1, yy1, (byte)DirectionCodes.SOUTHWEST)); + spots.add(new Location(xx1, yy2, (byte)DirectionCodes.SOUTHEAST)); + spots.add(new Location(xx2, yy1, (byte)DirectionCodes.NORTHWEST)); + spots.add(new Location(xx2, yy2, (byte)DirectionCodes.NORTHEAST)); + + // then the sides + for (int xx = xx1+1; xx < xx2; xx++) { + spots.add(new Location(xx, yy1, (byte)DirectionCodes.WEST)); + spots.add(new Location(xx, yy2, (byte)DirectionCodes.EAST)); + } + for (int yy = yy1+1; yy < yy2; yy++) { + spots.add(new Location(xx1, yy, (byte)DirectionCodes.SOUTH)); + spots.add(new Location(xx2, yy, (byte)DirectionCodes.NORTH)); + } + + // sort them in order of closeness to the players current + // coordinate + spots.sort(new Comparator() { + public int compare (Object o1, Object o2) { + return dist((Location)o1) - dist((Location)o2); + } + private final int dist (Location l) { + return Math.round(100*MathUtil.distance( + l.x, l.y, nearto.x, nearto.y)); + } + }); + + // return the first spot that can be "traversed" which we're + // taking to mean "stood upon" + for (int ii = 0, ll = spots.size(); ii < ll; ii++) { + Location loc = (Location)spots.get(ii); + if (pred.canTraverse(traverser, loc.x, loc.y)) { + // convert to full coordinates + loc.x = MisoUtil.toFull(loc.x, 2); + loc.y = MisoUtil.toFull(loc.y, 2); + + // see if we need to override the orientation + if (DirectionCodes.NONE != orient) { + loc.orient = (byte) orient; + } + return loc; + } + } + + // clear this list and try one further out + spots.clear(); + } + + return null; + } + + /** + * Returns an array of the objects intersected by the supplied tile + * coordinate rectangle. + */ + public static ObjectInfo[] getIntersectedObjects ( + TileManager tmgr, StageSceneModel model, Rectangle rect) + { + // first get all objects whose origin is in an expanded version of + // our intersection rect, any object that is *so* large that its + // origin falls outside of this rectangle but that still + // intersects this rectangle can go to hell; it's either this or + // we iterate over every object in the whole goddamned scene which + // is so hairily inefficient i can't even bear to contemplate it + ObjectSet objs = new ObjectSet(); + Rectangle orect = new Rectangle(rect); + orect.grow(MAX_OBJECT_SIZE, MAX_OBJECT_SIZE); + StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(model); + mmodel.getObjects(orect, objs); + + // now prune from this set any and all objects that don't actually + // overlap the specified rectangle + Rectangle foot = new Rectangle(); + for (int ii = 0; ii < objs.size(); ii++) { + ObjectInfo info = objs.get(ii); + if (getObjectFootprint(tmgr, info.tileId, info.x, info.y, foot)) { + if (!foot.intersects(rect)) { + objs.remove(ii--); + } + } else { + Log.warning("Unknown potentially intersecting object?! " + + "[scene=" + model.name + " (" + model.sceneId + + "), info=" + info + "]."); + } + } + + return objs.toArray(); + } + + /** Our default scene metrics. */ + protected static MisoSceneMetrics _metrics = MisoConfig.getSceneMetrics(); + + /** Contains the starting offset from zero radians for the first + * occupant and the radial distance between occupants. */ + protected static final double[] CLUSTER_METRICS = { + Math.PI/4, Math.PI/2, // 2x + Math.PI/4, Math.PI/2, // 3x + 0, Math.PI/4, // 4x + Math.PI/12, Math.PI/6, // 5x + 0, Math.PI/8, // 6x + Math.PI/24, Math.PI/12, // 7x + }; + + /** The maximum footprint width or height for which we will account in + * {@link #getIntersectedObjects}. */ + protected static final int MAX_OBJECT_SIZE = 15; +}