diff --git a/src/as/com/threerings/cast/ActionFrames.as b/src/as/com/threerings/cast/ActionFrames.as
index a25c3ec6..6f11328f 100644
--- a/src/as/com/threerings/cast/ActionFrames.as
+++ b/src/as/com/threerings/cast/ActionFrames.as
@@ -38,7 +38,7 @@ public interface ActionFrames
* Returns the multi-frame image that comprises the frames for the
* specified orientation.
*/
- function getFrames (orient :int) :DisplayObject;
+ function getFrames (orient :int, callback :Function) :void;
/**
* Returns the x offset from the upper left of the image to the
diff --git a/src/as/com/threerings/cast/ActionSequence.as b/src/as/com/threerings/cast/ActionSequence.as
new file mode 100644
index 00000000..427de4b2
--- /dev/null
+++ b/src/as/com/threerings/cast/ActionSequence.as
@@ -0,0 +1,106 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.geom.Point;
+
+import com.threerings.media.tile.SwissArmyTileSet;
+import com.threerings.util.DirectionUtil;
+import com.threerings.util.StringUtil;
+
+/**
+ * The action sequence class describes a particular character animation
+ * sequence. An animation sequence consists of one or more frames of
+ * animation, renders at a particular frame rate, and has an origin point
+ * that specifies the location of the base of the character in relation to
+ * the bounds of the animation images.
+ */
+public class ActionSequence
+{
+ /**
+ * Defines the name of the default action sequence. When component
+ * tilesets are loaded to build a set of composited images for a
+ * particular action sequence, a check is first made for a component
+ * tileset specific to the action sequence and then for the
+ * component's default tileset if the action specific tileset did not
+ * exist.
+ */
+ public static const DEFAULT_SEQUENCE :String = "default";
+
+ /** The action sequence name. */
+ public var name :String;
+
+ /** The number of frames per second to show when animating. */
+ public var framesPerSecond :Number;
+
+ /** The position of the character's base for this sequence. */
+ public var origin :Point = new Point();
+
+ public var tileset :SwissArmyTileSet;
+
+ /** Orientation codes for the orientations available for this
+ * action. */
+ public var orients :Array;
+
+ public static function fromXml (xml :XML) :ActionSequence
+ {
+ var seq :ActionSequence = new ActionSequence();
+ seq.name = xml.@name;
+ seq.framesPerSecond = xml.framesPerSecond;
+ seq.orients = toOrientArray(xml.orients);
+ seq.origin = toPoint(xml.origin);
+ seq.tileset = SwissArmyTileSet.fromXml(xml.tileset[0]);
+
+ return seq;
+ }
+
+ protected static function toOrientArray (str :String) :Array
+ {
+ if (str == null || str.length == 0) {
+ return null;
+ }
+
+ return str.split(",").map(function(element :String, index :int, arr :Array) :int {
+ return DirectionUtil.fromShortString(StringUtil.trim(element));
+ });
+ }
+
+ protected static function toPoint (str :String) :Point
+ {
+ if (str == null || str.length == 0) {
+ return null;
+ }
+
+ var coords :Array =
+ str.split(",").map(function(element :String, index :int, arr :Array) :int {
+ return int(StringUtil.trim(element));
+ });
+
+ return new Point(coords[0], coords[1]);
+ }
+
+ public function toString () :String
+ {
+ return "[name=" + name + ", framesPerSecond=" + framesPerSecond +
+ ", origin=" + origin +
+ ", orients=" + (orients == null ? 0 : orients.length) + "]";
+ }
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/CharacterComponent.as b/src/as/com/threerings/cast/CharacterComponent.as
index 9a413473..7a8b3d2f 100644
--- a/src/as/com/threerings/cast/CharacterComponent.as
+++ b/src/as/com/threerings/cast/CharacterComponent.as
@@ -53,6 +53,17 @@ public class CharacterComponent
_frameProvider = fprov;
}
+ public function setFrameProvider (frameProvider :FrameProvider) :void
+ {
+ _frameProvider = frameProvider;
+
+ // Notify them all and clear our list.
+ for each (var func :Function in _notifyOnLoad) {
+ func(this);
+ }
+ _notifyOnLoad = [];
+ }
+
/**
* Returns the render priority appropriate for this component at the specified action and
* orientation.
@@ -71,7 +82,7 @@ public class CharacterComponent
*/
public function getFrames (action :String, type :String) :ActionFrames
{
- return _frameProvider.getFrames(this, action, type);
+ return isLoaded() ? _frameProvider.getFrames(this, action, type) : null;
}
/**
@@ -104,10 +115,29 @@ public class CharacterComponent
public function toString () :String
{
- return StringUtil.toString(this);
+ return StringUtil.simpleToString(this);
}
+ public function isLoaded () :Boolean
+ {
+ return _frameProvider != null;
+ }
+
+ public function notifyOnLoad (func :Function) :void
+ {
+ if (isLoaded()) {
+ func(this);
+ } else {
+ _notifyOnLoad.push(func);
+ }
+ }
+
+
+
/** The entity from which we obtain our animation frames. */
protected var _frameProvider :FrameProvider;
+
+ /** Everyone who cares when we're loaded. */
+ protected var _notifyOnLoad :Array = [];
}
}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/CharacterManager.as b/src/as/com/threerings/cast/CharacterManager.as
new file mode 100644
index 00000000..1ea84739
--- /dev/null
+++ b/src/as/com/threerings/cast/CharacterManager.as
@@ -0,0 +1,280 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.geom.Point;
+
+import com.threerings.util.Hashable;
+import com.threerings.util.Log;
+import com.threerings.util.Map;
+import com.threerings.util.StringUtil;
+import com.threerings.util.Maps;
+import com.threerings.util.maps.LRMap;
+
+/**
+ * The character manager provides facilities for constructing sprites that
+ * are used to represent characters in a scene. It also handles the
+ * compositing and caching of composited character animations.
+ */
+public class CharacterManager
+{
+ private static var log :Log = Log.getLog(CharacterManager);
+
+ /**
+ * Constructs the character manager.
+ */
+ public function CharacterManager (crepo :ComponentRepository)
+ {
+ // keep this around
+ _crepo = crepo;
+
+ for each (var action :ActionSequence in crepo.getActionSequences()) {
+ _actions.put(action.name, action);
+ }
+ }
+
+ /**
+ * Returns a {@link CharacterSprite} representing the character
+ * described by the given {@link CharacterDescriptor}, or
+ * null if an error occurs.
+ *
+ * @param desc the character descriptor.
+ */
+ public function getCharacter (desc :CharacterDescriptor,
+ charClass :Class = null) :CharacterSprite
+ {
+ if (charClass == null) {
+ charClass = _charClass;
+ }
+
+ try {
+ var sprite :CharacterSprite = new charClass();
+ sprite.init(desc, this);
+ return sprite;
+
+ } catch (e :Error) {
+ log.warning("Failed to instantiate character sprite.", e);
+ return null;
+ }
+ return getCharacter(desc, charClass);
+ }
+
+ public function getActionSequence (action :String) :ActionSequence
+ {
+ return _actions.get(action);
+ }
+
+ public function getActionFrames (descrip :CharacterDescriptor, action :String) :ActionFrames
+ {
+ if (!isLoaded(descrip)) {
+ return null;
+ }
+
+ var key :FrameKey = new FrameKey(descrip, action);
+
+ var frames :ActionFrames = _actionFrames.get(key);
+ if (frames == null) {
+ // this doesn't actually composite the images, but prepares an
+ // object to be able to do so
+ frames = createCompositeFrames(descrip, action);
+ _actionFrames.put(key, frames);
+ }
+
+ return frames;
+ }
+
+ /**
+ * Returns whether all the components are loaded and ready to go.
+ */
+ protected function isLoaded (descrip :CharacterDescriptor) :Boolean
+ {
+ var cids :Array = descrip.getComponentIds();
+ var ccount :int = cids.length;
+
+ for (var ii :int = 0; ii < ccount; ii++) {
+ var ccomp :CharacterComponent = _crepo.getComponent(cids[ii]);
+ if (!ccomp.isLoaded()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function load (descrip :CharacterDescriptor, notify :Function) :void
+ {
+ _crepo.load(descrip.getComponentIds(), notify);
+ }
+
+ protected function createCompositeFrames (descrip :CharacterDescriptor,
+ action :String) :ActionFrames
+ {
+ var cids :Array = descrip.getComponentIds();
+ var ccount :int = cids.length;
+
+ var zations :Array = descrip.getColorizations();
+ var xlations :Array = descrip.getTranslations();
+
+ log.debug("Compositing action [action=" + action +
+ ", descrip=" + descrip + "].");
+
+ // this will be used to construct any shadow layers
+ var shadows :Map = null; /* of String, Array */
+
+ // maps components by class name for masks
+ var ccomps :Map = Maps.newMapOf(String); /* of String, ArrayList */
+
+ // create colorized versions of all of the source action frames
+ var sources :Array = [];
+ for (var ii :int = 0; ii < ccount; ii++) {
+ var cframes :ComponentFrames = new ComponentFrames();
+ sources.push(cframes);
+ var ccomp :CharacterComponent = cframes.ccomp = _crepo.getComponent(cids[ii]);
+
+ // load up the main component images
+ var source :ActionFrames = ccomp.getFrames(action, null);
+ if (source == null) {
+ var errmsg :String = "Cannot composite action frames; no such " +
+ "action for component [action=" + action +
+ ", desc=" + descrip + ", comp=" + ccomp + "]";
+ throw new Error(errmsg);
+ }
+ source = (zations == null || zations[ii] == null) ?
+ source : source.cloneColorized(zations[ii]);
+ var xlation :Point = (xlations == null) ? null : xlations[ii];
+ cframes.frames = (xlation == null) ?
+ source : source.cloneTranslated(xlation.x, xlation.y);
+
+ // store the component with its translation under its class for masking
+ var tcomp :TranslatedComponent = new TranslatedComponent(ccomp, xlation);
+ var tcomps :Array = ccomps.get(ccomp.componentClass.name);
+ if (tcomps == null) {
+ ccomps.put(ccomp.componentClass.name, tcomps = []);
+ }
+ tcomps.push(tcomp);
+
+ // if this component has a shadow, make a note of it
+ if (ccomp.componentClass.isShadowed()) {
+ if (shadows == null) {
+ shadows = Maps.newMapOf(String);
+ }
+ var shadlist :Array = shadows.get(ccomp.componentClass.shadow);
+ if (shadlist == null) {
+ shadows.put(ccomp.componentClass.shadow, shadlist = []);
+ }
+ shadlist.push(tcomp);
+ }
+ }
+
+/* TODO - Implement shadows & masks - I don't actually know if/where we use these, though.
+ // now create any necessary shadow layers
+ if (shadows != null) {
+ for (Map.Entry> entry : shadows.entrySet()) {
+ ComponentFrames scf = compositeShadow(action, entry.getKey(), entry.getValue());
+ if (scf != null) {
+ sources.add(scf);
+ }
+ }
+ }
+
+ // add any necessary masks
+ for (ComponentFrames cframes : sources) {
+ ArrayList mcomps = ccomps.get(cframes.ccomp.componentClass.mask);
+ if (mcomps != null) {
+ cframes.frames = compositeMask(action, cframes.ccomp, cframes.frames, mcomps);
+ }
+ }
+*/
+ // use those to create an entity that will lazily composite things
+ // together as they are needed
+ return new CompositedActionFrames(_frameCache, _actions.get(action), sources);
+ }
+
+ protected var _crepo :ComponentRepository;
+
+ protected var _actions :Map = Maps.newMapOf(String);
+
+ protected var _actionFrames :Map = Maps.newMapOf(FrameKey);
+
+ protected var _frameCache :LRMap = new LRMap(Maps.newMapOf(CompositedFramesKey), MAX_FRAMES);
+
+ protected var _charClass :Class;
+
+ protected static const MAX_FRAMES :int = 1000;
+}
+}
+
+import com.threerings.util.Hashable;
+import com.threerings.util.StringUtil;
+import com.threerings.cast.CharacterDescriptor;
+
+class FrameKey
+ implements Hashable
+{
+ public var desc :CharacterDescriptor;
+
+ public var action :String;
+
+ public function FrameKey (desc :CharacterDescriptor, action :String)
+ {
+ this.desc = desc;
+ this.action = action;
+ }
+
+ public function hashCode () :int
+ {
+ return desc.hashCode() ^ StringUtil.hashCode(action);
+ }
+
+ public function equals (other :Object) :Boolean
+ {
+ if (other is FrameKey) {
+ var okey :FrameKey = FrameKey(other);
+ return okey.desc.equals(desc) && okey.action == action;
+ } else {
+ return false;
+ }
+ }
+}
+
+import flash.geom.Point;
+
+import com.threerings.cast.ActionFrames;
+import com.threerings.cast.CharacterComponent;
+
+class TranslatedComponent
+{
+ public var ccomp :CharacterComponent;
+ public var xlation :Point;
+
+ public function TranslatedComponent (ccomp :CharacterComponent, xlation :Point)
+ {
+ this.ccomp = ccomp;
+ this.xlation = xlation;
+ }
+
+ public function getFrames (action :String, type :String) :ActionFrames
+ {
+ var frames :ActionFrames = ccomp.getFrames(action, type);
+ return (frames == null || xlation == null) ?
+ frames : frames.cloneTranslated(xlation.x, xlation.y);
+ }
+
+}
diff --git a/src/as/com/threerings/cast/CharacterSprite.as b/src/as/com/threerings/cast/CharacterSprite.as
new file mode 100644
index 00000000..0fd20dd1
--- /dev/null
+++ b/src/as/com/threerings/cast/CharacterSprite.as
@@ -0,0 +1,289 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.display.DisplayObject;
+import flash.display.Sprite;
+
+import flash.geom.Point;
+import flash.geom.Rectangle;
+
+import com.threerings.display.DisplayUtil;
+import com.threerings.util.DirectionCodes;
+import com.threerings.util.Log;
+
+/**
+ * A character sprite is a sprite that animates itself while walking
+ * about in a scene.
+ */
+public class CharacterSprite extends Sprite
+{
+ private static var log :Log = Log.getLog(CharacterSprite);
+
+ /**
+ * Initializes this character sprite with the specified character
+ * descriptor and character manager. It will obtain animation data
+ * from the supplied character manager.
+ */
+ public function init (descrip :CharacterDescriptor, charmgr :CharacterManager) :void
+ {
+ // keep track of this stuff
+ _descrip = descrip;
+ _charmgr = charmgr;
+
+ // sanity check our values
+ sanityCheckDescrip();
+
+ // assign an arbitrary starting orientation
+ _orient = DirectionCodes.SOUTHWEST;
+
+ // pass the buck to derived classes
+ didInit();
+
+ halt();
+
+ updateActionFrames();
+ }
+
+ /**
+ * Called after this sprite has been initialized with its character
+ * descriptor and character manager. Derived classes can do post-init
+ * business here.
+ */
+ protected function didInit () :void
+ {
+ _mainSprite = new Sprite();
+ addChild(_mainSprite);
+ }
+
+ /**
+ * Reconfigures this sprite to use the specified character descriptor.
+ */
+ public function setCharacterDescriptor (descrip :CharacterDescriptor) :void
+ {
+ // keep the new descriptor
+ _descrip = descrip;
+
+ // sanity check our values
+ sanityCheckDescrip();
+
+ // update our action frames
+ updateActionFrames();
+ }
+
+ /**
+ * Specifies the action to use when the sprite is at rest. The default
+ * is STANDING.
+ */
+ public function setRestingAction (action :String) :void
+ {
+ _restingAction = action;
+ }
+
+ /**
+ * Returns the action to be used when the sprite is at rest. Derived
+ * classes may wish to override this method and vary the action based
+ * on external parameters (or randomly).
+ */
+ public function getRestingAction () :String
+ {
+ return _restingAction;
+ }
+
+ /**
+ * Specifies the action to use when the sprite is following a path.
+ * The default is WALKING.
+ */
+ public function setFollowingPathAction (action :String) :void
+ {
+ _followingPathAction = action;
+ }
+
+ /**
+ * Returns the action to be used when the sprite is following a path.
+ * Derived classes may wish to override this method and vary the
+ * action based on external parameters (or randomly).
+ */
+ public function getFollowingPathAction () :String
+ {
+ return _followingPathAction;
+ }
+
+ /**
+ * Sets the action sequence used when rendering the character, from
+ * the set of available sequences.
+ */
+ public function setActionSequence (action :String) :void
+ {
+ // sanity check
+ if (action == null) {
+ log.warning("Refusing to set null action sequence " + this + ".", new Error());
+ return;
+ }
+
+ // no need to noop
+ if (action == _action) {
+ return;
+ }
+ _action = action;
+ updateActionFrames();
+ }
+
+ public function setOrientation (orient :int) :void
+ {
+ _orient = orient;
+ if (orient < 0 || orient >= DirectionCodes.FINE_DIRECTION_COUNT) {
+ log.info("Refusing to set invalid orientation [sprite=" + this +
+ ", orient=" + orient + "].", new Error());
+ return;
+ }
+
+ var oorient :int = _orient;
+ if (_orient != oorient) {
+ updateMainSprite();
+ }
+ }
+
+ public function cancelMove () :void
+ {
+ halt();
+ }
+
+ public function pathBeginning () :void
+ {
+ // enable walking animation
+ setActionSequence(getFollowingPathAction());
+ }
+
+ public function pathCompleted () :void
+ {
+ halt();
+ }
+
+ /**
+ * Rebuilds our action frames given our current character descriptor
+ * and action sequence. This is called when either of those two things
+ * changes.
+ */
+ protected function updateActionFrames () :void
+ {
+ // get a reference to the action sequence so that we can obtain
+ // our animation frames and configure our frames per second
+ var actseq :ActionSequence = _charmgr.getActionSequence(_action);
+ if (actseq == null) {
+ var errmsg :String = "No such action '" + _action + "'.";
+ throw new Error(errmsg);
+ }
+
+ try {
+ // obtain our animation frames for this action sequence
+ _aframes = _charmgr.getActionFrames(_descrip, _action);
+
+ if (_aframes == null) {
+ // Once the frames are really ready to go, we'll do this...
+ _charmgr.load(_descrip, updateActionFrames);
+ }
+
+ updateMainSprite();
+
+ } catch (nsce :NoSuchComponentError) {
+ log.warning("Character sprite references non-existent " +
+ "component [sprite=" + this + ", err=" + nsce + "].");
+
+ } catch (e :Error) {
+ log.warning("Failed to obtain action frames [sprite=" + this +
+ ", descrip=" + _descrip + ", action=" + _action + "].", e);
+ }
+ }
+
+ protected function updateMainSprite () :void
+ {
+ DisplayUtil.removeAllChildren(_mainSprite);
+ if (_aframes != null) {
+ _aframes.getFrames(_orient, function(frames :MultiFrameBitmap) :void {
+ _mainSprite.addChild(frames);
+ });
+ } else {
+ updateWithUnloadedSprite();
+ }
+ }
+
+ protected function updateWithUnloadedSprite () :void
+ {
+ // Nothing by default.
+ }
+
+ /**
+ * Makes it easier to track down problems with bogus character descriptors.
+ */
+ protected function sanityCheckDescrip () :void
+ {
+ if (_descrip.getComponentIds() == null ||
+ _descrip.getComponentIds().length == 0) {
+ log.warning("Invalid character descriptor [sprite=" + this +
+ ", descrip=" + _descrip + "].", new Error());
+ }
+ }
+
+ /**
+ * Updates the sprite animation frame to reflect the cessation of
+ * movement and disables any further animation.
+ */
+ protected function halt () :void
+ {
+ var rest :String = getRestingAction();
+ if (rest != null) {
+ setActionSequence(rest);
+ }
+ }
+
+ /** The action to use when at rest. */
+ protected var _restingAction :String = StandardActions.STANDING;
+
+ /** The action to use when following a path. */
+ protected var _followingPathAction :String = StandardActions.WALKING;
+
+ /** A reference to the descriptor for the character that we're
+ * visualizing. */
+ protected var _descrip :CharacterDescriptor;
+
+ /** A reference to the character manager that created us. */
+ protected var _charmgr :CharacterManager;
+
+ /** The action we are currently displaying. */
+ protected var _action :String;
+
+ /** The animation frames for the active action sequence in each
+ * orientation. */
+ protected var _aframes :ActionFrames;
+
+ /** The offset from the upper-left of the total sprite bounds to the
+ * upper-left of the image within those bounds. */
+ protected var _ioff :Point = new Point();
+
+ /** The bounds of the current sprite image. */
+ protected var _ibounds :Rectangle = new Rectangle();
+
+ /** The orientation of this sprite. */
+ protected var _orient :int = DirectionCodes.NONE;
+
+ protected var _mainSprite :Sprite;
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/ComponentClass.as b/src/as/com/threerings/cast/ComponentClass.as
index 064ddd0e..91351e2b 100644
--- a/src/as/com/threerings/cast/ComponentClass.as
+++ b/src/as/com/threerings/cast/ComponentClass.as
@@ -109,9 +109,9 @@ public class ComponentClass
{
// because we expect there to be relatively few priority overrides, we simply search
// linearly through the list for the closest match
- var ocount :int = (_overrides != null) ? _overrides.size() : 0;
+ var ocount :int = (_overrides != null) ? _overrides.length : 0;
for (var ii :int = 0; ii < ocount; ii++) {
- var over :PriorityOverride = _overrides.get(ii);
+ var over :PriorityOverride = _overrides[ii];
// based on the way the overrides are sorted, the first match
// is the most specific and the one we want
if (over.matches(action, component, orientation)) {
diff --git a/src/as/com/threerings/cast/ComponentDataPack.as b/src/as/com/threerings/cast/ComponentDataPack.as
new file mode 100644
index 00000000..6b0db1fe
--- /dev/null
+++ b/src/as/com/threerings/cast/ComponentDataPack.as
@@ -0,0 +1,160 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.display.Bitmap;
+import flash.display.BitmapData;
+import flash.display.DisplayObject;
+
+import flash.geom.Point;
+import flash.geom.Rectangle;
+
+import flash.events.Event;
+
+import flash.utils.ByteArray;
+
+import nochump.util.zip.ZipEntry;
+import nochump.util.zip.ZipError;
+import nochump.util.zip.ZipFile;
+
+import com.threerings.media.image.PngRecolorUtil;
+import com.threerings.media.tile.TileDataPack;
+import com.threerings.media.tile.TileSet;
+import com.threerings.util.DataPack;
+import com.threerings.util.Log;
+import com.threerings.util.Map;
+import com.threerings.util.Maps;
+import com.threerings.util.MultiLoader;
+import com.threerings.util.Set;
+import com.threerings.util.StringUtil;
+
+/**
+ * Like a normal data pack, but we don't deal with any data, and all our filenames are a direct
+ * translation, so we need no metadata file. We also serve as a tile ImageProvider.
+ */
+public class ComponentDataPack extends TileDataPack
+ implements FrameProvider
+{
+ private static var log :Log = Log.getLog(ComponentDataPack);
+
+ public function ComponentDataPack (source :Object, actions :Map,
+ completeListener :Function = null, errorListener :Function = null)
+ {
+ super(source, completeListener, errorListener);
+ _actions = actions;
+ }
+
+ // Documentation inherited from interface
+ public function getFrames (component :CharacterComponent, action :String,
+ type :String) :ActionFrames
+ {
+ // obtain the action sequence definition for this action
+ var actseq :ActionSequence = _actions.get(action);
+ if (actseq == null) {
+ log.warning("Missing action sequence definition [action=" + action +
+ ", component=" + component + "].");
+ return null;
+ }
+
+ // determine our image path name
+ var imgpath :String = action;
+ var dimgpath :String = ActionSequence.DEFAULT_SEQUENCE;
+ if (type != null) {
+ imgpath += "_" + type;
+ dimgpath += "_" + type;
+ }
+
+ var root :String = component.componentClass.name + "/" + component.name + "/";
+ var cpath :String = root + imgpath + ".png";
+ var dpath :String = root + dimgpath + ".png";
+
+ // look to see if this tileset is already cached (as the custom action or the default
+ // action)
+ var aset :TileSet = _setcache.get(cpath);
+ if (aset == null) {
+ aset = _setcache.get(dpath);
+ if (aset != null) {
+ // save ourselves a lookup next time
+ _setcache.put(cpath, aset);
+ }
+ }
+
+ try {
+ // then try loading up a tileset customized for this action
+ if (aset == null) {
+ if (_zip.getEntry(cpath)) {
+ aset = TileSet(actseq.tileset.clone());
+ aset.setImagePath(cpath);
+ } else if (_zip.getEntry(dpath)) {
+ aset = TileSet(TileSet(_actions.get(ActionSequence.DEFAULT_SEQUENCE)).clone());
+ aset.setImagePath(dpath);
+ _setcache.put(dpath, aset);
+ }
+ }
+
+ // if that failed too, we're hosed
+ if (aset == null) {
+ // if this is a shadow or crop image, no need to freak out as they are optional
+ if (!StandardActions.CROP_TYPE == type &&
+ !StandardActions.SHADOW_TYPE == type) {
+ log.warning("Unable to locate tileset for action '" + imgpath + "' " +
+ component + ".");
+ }
+ return null;
+ }
+
+ aset.setImageProvider(this);
+ _setcache.put(cpath, aset);
+ return new TileSetFrameImage(aset, actseq);
+
+ } catch (e :Error) {
+ log.warning("Error loading tset for action '" + imgpath + "' " + component + ".", e);
+ }
+
+ return null;
+ }
+
+ // Documentation inherited from interface
+ public function getFramePath (
+ component :CharacterComponent, action :String, type :String, existentPaths :Set) :String
+ {
+ var actionPath :String = makePath(component, action, type);
+ if (!existentPaths.contains(actionPath)) {
+ return makePath(component, ActionSequence.DEFAULT_SEQUENCE, type);
+ }
+ return actionPath;
+ }
+
+ protected function makePath (component :CharacterComponent, action :String,
+ type :String) :String
+ {
+ var imgpath :String = action;
+ if (type != null) {
+ imgpath += "_" + type;
+ }
+ var root :String = component.componentClass.name + "/" + component.name + "/";
+ return root + imgpath + ".png";
+ }
+
+ protected var _actions :Map;
+
+ protected var _setcache :Map = Maps.newMapOf(String);
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/ComponentFrames.as b/src/as/com/threerings/cast/ComponentFrames.as
new file mode 100644
index 00000000..741dd356
--- /dev/null
+++ b/src/as/com/threerings/cast/ComponentFrames.as
@@ -0,0 +1,40 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+/** Used to associate a {@link CharacterComponent} with its {@link
+ * ActionFrames} for a particular action. */
+public class ComponentFrames
+{
+ public var ccomp :CharacterComponent;
+
+ public var frames :ActionFrames;
+
+ public function ComponentFrames (ccomp :CharacterComponent = null,
+ frames :ActionFrames = null) {
+ this.ccomp = ccomp;
+ this.frames = frames;
+ }
+
+ public function toString () :String {
+ return ccomp + ":" + frames;
+ }
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/ComponentRepository.as b/src/as/com/threerings/cast/ComponentRepository.as
new file mode 100644
index 00000000..eed36a6a
--- /dev/null
+++ b/src/as/com/threerings/cast/ComponentRepository.as
@@ -0,0 +1,67 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+/**
+ * Makes available a collection of character components and associated metadata. Character
+ * components are animated sequences that can be composited together to create a complete
+ * character visualization (imagine interchanging pairs of boots, torsos, hats, etc.).
+ */
+public interface ComponentRepository
+{
+ /**
+ * Returns the {@link CharacterComponent} object for the given component identifier.
+ */
+ function getComponent (componentId :int) :CharacterComponent;
+
+ /**
+ * Returns the {@link CharacterComponent} object with the given component class and name.
+ */
+ function getComponentByName (className :String, compName :String) :CharacterComponent;
+
+ /**
+ * Returns the {@link ComponentClass} with the specified name or null if none exists with that
+ * name.
+ */
+ function getComponentClass (className :String) :ComponentClass;
+
+ /**
+ * Iterates over the {@link ComponentClass} instances representing all available character
+ * component classes.
+ */
+ function getComponentClasses () :Array;
+
+ /**
+ * Iterates over the {@link ActionSequence} instances representing every available action
+ * sequence.
+ */
+ function getActionSequences () :Array;
+
+ /**
+ * Iterates over the component ids of all components in the specified class.
+ */
+ function getComponentIds (compClass :ComponentClass) :Array;
+
+ /**
+ * Loads up all the specified components and calls notify() when done.
+ */
+ function load (compIds :Array, notify :Function) :void;
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/CompositedActionFrames.as b/src/as/com/threerings/cast/CompositedActionFrames.as
new file mode 100644
index 00000000..6b2e992a
--- /dev/null
+++ b/src/as/com/threerings/cast/CompositedActionFrames.as
@@ -0,0 +1,197 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.display.Bitmap;
+import flash.display.BitmapData;
+import flash.display.DisplayObject;
+
+import flash.geom.Point;
+import flash.geom.Rectangle;
+
+import com.threerings.util.ArrayUtil;
+import com.threerings.util.Hashable;
+import com.threerings.util.Integer;
+import com.threerings.util.Map;
+import com.threerings.util.Maps;
+import com.threerings.util.RandomUtil;
+import com.threerings.util.StringUtil;
+
+/**
+ * An implementation of the {@link ActionFrames} interface that is used
+ * to lazily create composited character frames when they are requested.
+ */
+public class CompositedActionFrames
+ implements ActionFrames, Hashable
+{
+ /**
+ * Constructs a set of composited action frames with the supplied
+ * source frames and colorization configuration. The actual component
+ * frame images will not be composited until they are requested.
+ */
+ public function CompositedActionFrames (frameCache :Map, action :ActionSequence, sources :Array)
+ {
+ // sanity check
+ if (sources == null || sources.length == 0) {
+ var errmsg :String = "Requested to composite invalid set of source " +
+ "frames! [action=" + action +
+ ", sources=" + StringUtil.toString(sources) + "].";
+ throw new Error(errmsg);
+ }
+
+ _frameCache = frameCache;
+ _sources = sources;
+ _action = action;
+
+ // the sources must all have the same orientation count, so we
+ // just use the first
+
+ _orientCount = _sources[0].frames.getOrientationCount();
+ }
+
+ // documentation inherited from interface
+ public function getOrientationCount () :int
+ {
+ return _orientCount;
+ }
+
+ // documentation inherited from interface
+ public function getFrames (orient :int, callback :Function) :void
+ {
+ var key :CompositedFramesKey = new CompositedFramesKey(this, orient);
+ var disp :MultiFrameBitmap = _frameCache.get(key);
+ if (disp == null) {
+ var listeners :Array = _pending.get(key);
+ if (listeners != null) {
+ listeners.push(callback);
+ } else {
+ listeners = [callback];
+ _pending.put(key, listeners);
+
+ createFrames(orient, function(disp :MultiFrameBitmap) :void {
+ _frameCache.put(key, disp);
+ for each (var func :Function in listeners) {
+ func(disp);
+ }
+ });
+ }
+ } else {
+ callback(disp);
+ }
+ }
+
+ // documentation inherited from interface
+ public function getXOrigin (orient :int, frameIdx :int) :int
+ {
+ return 0;
+ }
+
+ // documentation inherited from interface
+ public function getYOrigin (orient :int, frameIdx :int) :int
+ {
+ return 0;
+ }
+
+ // documentation inherited from interface
+ public function cloneColorized (zations :Array) :ActionFrames
+ {
+ throw new Error("Unsupported: CompositedActionFrames.cloneColorized()");
+ }
+
+ // documentation inherited from interface
+ public function cloneTranslated (dx :int, dy :int) :ActionFrames
+ {
+ var tsources :Array = new Array(_sources.length);
+ for (var ii :int = 0; ii < _sources.length; ii++) {
+ tsources[ii] = new ComponentFrames(
+ _sources[ii].ccomp, _sources[ii].frames.cloneTranslated(dx, dy));
+ }
+ return new CompositedActionFrames(_frameCache, _action, tsources);
+ }
+
+ /**
+ * Creates our underlying multi-frame image for a particular orientation.
+ */
+ protected function createFrames (orient :int, callback :Function) :void
+ {
+ ArrayUtil.stableSort(_sources, function(cf1 :ComponentFrames, cf2 :ComponentFrames) :int {
+ return (cf1.ccomp.getRenderPriority(_action.name, orient) -
+ cf2.ccomp.getRenderPriority(_action.name, orient));
+ });
+
+ var idx :int = ArrayUtil.indexOf(_action.orients, orient);
+
+ var frameCt :int = _action.tileset.getTileCounts()[idx];
+ var width :int = _action.tileset.getWidths()[idx];
+ var height :int = _action.tileset.getHeights()[idx];
+ var frameBitmaps :Array = new Array(frameCt);
+ for (var ff :int = 0; ff < frameCt; ff++) {
+ frameBitmaps[ff] = new Bitmap(new BitmapData(width, height, true, 0x00000000));
+ }
+
+ var region :Rectangle = new Rectangle(0, 0, width, height);
+ var pt :Point = new Point(0, 0);
+ compositeFrames(frameBitmaps, _sources, 0, orient, region, pt, frameCt, callback);
+ }
+
+ protected function compositeFrames (frameBitmaps :Array, sources :Array, idx :int, orient :int,
+ region :Rectangle, pt :Point, frameCt :int, callback :Function) :void
+ {
+ if (idx >= sources.length) {
+ callback(new MultiFrameBitmap(frameBitmaps, _action.framesPerSecond));
+ return;
+ }
+
+ _sources[idx].frames.getFrames(orient, function (compBitmaps :MultiFrameBitmap) :void {
+ for (var ii :int = 0; ii < frameCt; ii++) {
+ frameBitmaps[ii].bitmapData.draw(compBitmaps.getFrame(ii).bitmapData);
+ }
+ compositeFrames(frameBitmaps, sources, idx+1, orient, region, pt, frameCt, callback);
+ });
+ }
+
+ public function equals (other :Object) :Boolean
+ {
+ return this === other;
+ }
+
+ public function hashCode () :int
+ {
+ return _randHashCode;
+ }
+
+ /** Used to cache our composited action frame images. */
+ protected var _frameCache :Map;
+
+ protected static var _pending :Map = Maps.newMapOf(CompositedFramesKey);
+
+ /** The action for which we're compositing frames. */
+ protected var _action :ActionSequence;
+
+ /** The number of orientations. */
+ protected var _orientCount :int;
+
+ /** Our source components and action frames. */
+ protected var _sources :Array;
+
+ /** We keep a hash code to use to identify us nearly-uniquely from the CompositedFramesKey. */
+ protected var _randHashCode :int = int(Math.random() * Integer.MAX_VALUE);
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/CompositedFramesKey.as b/src/as/com/threerings/cast/CompositedFramesKey.as
new file mode 100644
index 00000000..b070837d
--- /dev/null
+++ b/src/as/com/threerings/cast/CompositedFramesKey.as
@@ -0,0 +1,56 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import com.threerings.util.Hashable;
+
+/** Used to cache composited frames for a particular action and
+ * orientation. */
+public class CompositedFramesKey
+ implements Hashable
+{
+ public function CompositedFramesKey (owner :CompositedActionFrames, orient :int) {
+ _orient = orient;
+ _owner = owner;
+ }
+
+ public function setOrient (orient :int) :void {
+ _orient = orient;
+ }
+
+ public function getOwner () :CompositedActionFrames {
+ return _owner;
+ }
+
+ public function equals (other :Object) :Boolean {
+ var okey :CompositedFramesKey = CompositedFramesKey(other);
+ return ((getOwner() == okey.getOwner()) &&
+ (_orient == okey._orient));
+ }
+
+ public function hashCode () :int {
+ return _owner.hashCode() ^ _orient;
+ }
+
+ protected var _orient :int;
+
+ protected var _owner :CompositedActionFrames;
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/DataPackComponentRepository.as b/src/as/com/threerings/cast/DataPackComponentRepository.as
new file mode 100644
index 00000000..9aca9c24
--- /dev/null
+++ b/src/as/com/threerings/cast/DataPackComponentRepository.as
@@ -0,0 +1,309 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.events.Event;
+
+import com.threerings.cast.ComponentDataPack;
+import com.threerings.media.tile.TileDataPack;
+import com.threerings.util.ArrayUtil;
+import com.threerings.util.DataPack;
+import com.threerings.util.Log;
+import com.threerings.util.Map;
+import com.threerings.util.Maps;
+import com.threerings.util.StringUtil;
+
+public class DataPackComponentRepository
+ implements ComponentRepository
+{
+ private static var log :Log = Log.getLog(DataPackComponentRepository);
+
+ public function DataPackComponentRepository (compUrl :String)
+ {
+ _rootUrl = compUrl;
+ loadComponentMeta(compUrl + "metadata.jar");
+ }
+
+ protected function loadComponentMeta (compMetaUrl :String) :void
+ {
+ _metaPack = new TileDataPack(compMetaUrl, metaLoadingComplete);
+ }
+
+ /**
+ * Handle the successful completion of datapack loading.
+ *
+ * @private
+ */
+ protected function metaLoadingComplete (event :Event) :void
+ {
+ var actionXml :XML = _metaPack.getFileAsXML("actions.xml");
+ var classesXml :XML = _metaPack.getFileAsXML("classes.xml");
+
+ for each (var aXml :XML in actionXml.action) {
+ var action :ActionSequence = ActionSequence.fromXml(aXml);
+ _actions.put(action.name, action);
+ }
+
+ for each (var cXml :XML in classesXml.elements("class")) {
+ var compClass :ComponentClass = ComponentClass.fromXml(cXml);
+ _classes.put(compClass.name, compClass);
+ }
+
+ parseComponentMap(_metaPack.getFileAsString("compmap.txt"));
+
+ // Notify them all and clear our list.
+ for each (var func :Function in _notifyOnLoad) {
+ func();
+ }
+ _notifyOnLoad = [];
+ }
+
+ protected function parseComponentMap (map :String) :void
+ {
+ var lines :Array = map.split("\n");
+ var tileCount :int = int(lines[0]);
+ for (var ii :int = 1; ii < lines.length; ii++) {
+ var line :String = lines[ii];
+ var toks :Array = line.split(" := ");
+ if (toks.length >= 3) {
+ var id :int = int(toks[2]);
+ var cclass :String = String(toks[0]);
+ var cname :String = String(toks[1]);
+ createComponent(id, cclass, cname);
+ }
+ }
+ }
+
+ /**
+ * Creates a component and inserts it into the component table.
+ */
+ protected function createComponent (componentId :int, cclass :String, cname :String) :void
+ {
+ // look up the component class information
+ var clazz :ComponentClass = _classes.get(cclass);
+ if (clazz == null) {
+ log.warning("Non-existent component class",
+ "class", cclass, "name", cname, "id", componentId);
+ return;
+ }
+
+ // create the component - we'll set the frame provider as soon as we can.
+ var component :CharacterComponent = new CharacterComponent(componentId, cname, clazz, null);
+
+ // stick it into the appropriate tables
+ _components.put(componentId, component);
+
+ // we have a hash of lists for mapping components by class/name
+ var comps :Array = _classComps.get(cclass);
+ if (comps == null) {
+ comps = [];
+ _classComps.put(cclass, comps);
+ }
+ if (!ArrayUtil.contains(comps, component)) {
+ comps.push(component);
+ } else {
+ log.info("Requested to register the same component twice?", "comp", component);
+ }
+ }
+
+ public function notifyOnLoad (func :Function) :void
+ {
+ _notifyOnLoad.push(func);
+ }
+
+ /**
+ * Returns the {@link CharacterComponent} object for the given component identifier.
+ */
+ public function getComponent (componentId :int) :CharacterComponent
+ {
+ var component :CharacterComponent = _components.get(componentId);
+ if (component == null) {
+ throw new NoSuchComponentError(componentId);
+ }
+ return component;
+ }
+
+ /**
+ * Returns the {@link CharacterComponent} object with the given component class and name.
+ */
+ public function getComponentByName (className :String, compName :String) :CharacterComponent
+ {
+ // look up the list for that class
+ var comps :Array = _classComps.get(className);
+ if (comps != null) {
+ for each (var comp :CharacterComponent in comps) {
+ if (comp.name == compName) {
+ return comp;
+ }
+ }
+ }
+ throw new NoSuchComponentError(className + "/" + compName);
+ }
+
+ public function load (compIds :Array, notify :Function) :void
+ {
+ var complete :Boolean = true;
+
+ var toLoad :Array = [];
+
+ for each (var cid :int in compIds) {
+ var comp :CharacterComponent = getComponent(cid);
+ if (!comp.isLoaded()) {
+ toLoad.push(comp);
+ }
+ }
+
+ var waiting :int = toLoad.length;
+
+ if (waiting == 0) {
+ notify();
+ } else {
+ for each (var load :CharacterComponent in toLoad) {
+ load.notifyOnLoad(function() :void {
+ waiting--;
+ if (waiting == 0) {
+ notify();
+ }
+ });
+ loadComponent(load);
+ }
+ }
+ }
+
+ protected function loadComponent (comp :CharacterComponent) :void
+ {
+ var packName :String = comp.componentClass.name + "/" + comp.name;
+ var pack :ComponentDataPack = _packs.get(packName);
+ if (pack == null || !pack.isComplete()) {
+ loadPack(packName, comp);
+ } else {
+ comp.setFrameProvider(pack);
+ }
+ }
+
+ protected function loadPack (packName :String, comp :CharacterComponent) :void
+ {
+ var pack :ComponentDataPack = _packs.get(packName);
+
+ if (pack != null && pack.isComplete()) {
+ // Already loaded
+ return;
+ }
+
+ var completeListener :Function = function () :void {
+ comp.setFrameProvider(_packs.get(packName));
+ }
+
+ var listeners :Array = _packListeners.get(packName);
+ if (listeners == null) {
+ listeners = [];
+ _packListeners.put(packName, listeners);
+
+ var packCompleteListener :Function = function () :void {
+ for each (var listener :Function in listeners) {
+ listener();
+ }
+ _packListeners.remove(packName);
+ }
+
+ pack = new ComponentDataPack(getPackUrl(packName), _actions, packCompleteListener);
+ _packs.put(packName, pack);
+ }
+
+ listeners.push(completeListener);
+ }
+
+ protected function getPackUrl (packName :String) :String
+ {
+ return _rootUrl + packName + "/components.jar";
+ }
+
+ /**
+ * Returns the {@link ComponentClass} with the specified name or null if none exists with that
+ * name.
+ */
+ public function getComponentClass (className :String) :ComponentClass
+ {
+ return _classes.get(className);
+ }
+
+ /**
+ * Iterates over the {@link ComponentClass} instances representing all available character
+ * component classes.
+ */
+ public function getComponentClasses () :Array
+ {
+ return _classes.values();
+ }
+
+ /**
+ * Iterates over the {@link ActionSequence} instances representing every available action
+ * sequence.
+ */
+ public function getActionSequences () :Array
+ {
+ return _actions.values();
+ }
+
+ /**
+ * Iterates over the component ids of all components in the specified class.
+ */
+ public function getComponentIds (compClass :ComponentClass) :Array
+ {
+ var result :Array = [];
+
+ _classes.forEach(function (key :int, value :CharacterComponent) :Boolean {
+ if (value.componentClass.equals(compClass)) {
+ result.push(key);
+ }
+ return false;
+ });
+
+ return result;
+ }
+
+ /** Everyone who cares when we're loaded. */
+ protected var _notifyOnLoad :Array = [];
+
+ /** Contains the component meta-data for all components. */
+ protected var _metaPack : DataPack;
+
+ /** The URL for our components. */
+ protected var _rootUrl :String;
+
+ /** Map of pack name to listeners waiting for that pack to resolve. */
+ protected var _packListeners :Map = Maps.newMapOf(String);
+
+ /** The map of packs we've already loaded up and resolved. */
+ protected var _packs :Map = Maps.newMapOf(String);
+
+ /** All our component classes by name. */
+ protected var _classes :Map = Maps.newMapOf(String);
+
+ /** All our action sequences by name. */
+ protected var _actions :Map = Maps.newMapOf(String);
+
+ /** The map of componentId to CharacterComponent */
+ protected var _components :Map = Maps.newMapOf(int);
+
+ /** A table of component lists indexed on classname. */
+ protected var _classComps :Map = Maps.newMapOf(String);
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/MultiFrameBitmap.as b/src/as/com/threerings/cast/MultiFrameBitmap.as
new file mode 100644
index 00000000..bb021fa5
--- /dev/null
+++ b/src/as/com/threerings/cast/MultiFrameBitmap.as
@@ -0,0 +1,85 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.display.Bitmap;
+import flash.display.Sprite;
+
+import flash.events.Event;
+
+import com.threerings.util.StringUtil;
+
+public class MultiFrameBitmap extends Sprite
+{
+ public function MultiFrameBitmap (frames :Array, fps :Number)
+ {
+ _frames = frames;
+ _bitmap = new Bitmap();
+ _frameRate = fps / 1000.0;
+ addChild(_bitmap);
+ setFrame(0);
+ addEventListener(Event.ENTER_FRAME, enterFrame);
+ }
+
+ public function enterFrame (event :Event) :void
+ {
+ if (_start == 0) {
+ _start = (new Date().time);
+ }
+
+ var elapsedTime :int = (new Date().time - _start);
+ var frameIndex :int = Math.floor(elapsedTime * _frameRate);
+ var totalFrames :int = _frames.length;
+ if (frameIndex >= totalFrames) {
+ frameIndex = frameIndex % totalFrames;
+ }
+
+ setFrame(frameIndex);
+ }
+
+ protected function setFrame (index :int) :void
+ {
+ if (_curFrameIndex != index) {
+ _bitmap.bitmapData = Bitmap(_frames[index]).bitmapData;
+ _curFrameIndex = index;
+ }
+ }
+
+ public function getFrame (index :int) :Bitmap
+ {
+ return _frames[index];
+ }
+
+ public function getFrameCount () :int
+ {
+ return _frames.length;
+ }
+
+ protected var _frames :Array;
+
+ protected var _bitmap :Bitmap;
+
+ protected var _start :int;
+
+ protected var _frameRate :Number;
+
+ protected var _curFrameIndex :int = -1;
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/NoSuchComponentError.as b/src/as/com/threerings/cast/NoSuchComponentError.as
new file mode 100644
index 00000000..8eb2966f
--- /dev/null
+++ b/src/as/com/threerings/cast/NoSuchComponentError.as
@@ -0,0 +1,34 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+/**
+ * Thrown when an attempt is made to retrieve a non-existent character
+ * component from the component repository.
+ */
+public class NoSuchComponentError extends Error
+{
+ public function NoSuchComponentError (comp :*)
+ {
+ super((comp is int) ? ("No component with id '" + int(comp) + "'") :
+ ("No component named'" + String(comp) + "'"));
+ }
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/StandardActions.as b/src/as/com/threerings/cast/StandardActions.as
new file mode 100644
index 00000000..b0a3fe8d
--- /dev/null
+++ b/src/as/com/threerings/cast/StandardActions.as
@@ -0,0 +1,41 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+/**
+ * Actions are referenced by name and this interface defines constants for
+ * standard actions and action suffixes used by the shadow and cropping
+ * support.
+ */
+public class StandardActions
+{
+ /** The name of the standard standing action. */
+ public static const STANDING :String = "standing";
+
+ /** The name of the standard walking action. */
+ public static const WALKING :String = "walking";
+
+ /** A special action sub-type for shadow imagery. */
+ public static const SHADOW_TYPE :String = "shadow";
+
+ /** A special action sub-type for crop imagery. */
+ public static const CROP_TYPE :String = "crop";
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/cast/TileSetFrameImage.as b/src/as/com/threerings/cast/TileSetFrameImage.as
new file mode 100644
index 00000000..b4c11e96
--- /dev/null
+++ b/src/as/com/threerings/cast/TileSetFrameImage.as
@@ -0,0 +1,129 @@
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// 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.cast {
+
+import flash.display.Bitmap;
+
+import flash.geom.Rectangle;
+
+import com.threerings.media.tile.Tile;
+import com.threerings.media.tile.TileSet;
+import com.threerings.util.Map;
+import com.threerings.util.Maps;
+
+public class TileSetFrameImage
+ implements ActionFrames
+{
+ public function TileSetFrameImage (set :TileSet, actseq :ActionSequence,
+ dx :int = 0, dy :int = 0)
+ {
+ _set = set;
+ _actseq = actseq;
+ _dx = dx;
+ _dy = dy;
+
+ // compute these now to avoid pointless recomputation later
+ _ocount = actseq.orients.length;
+ _fcount = set.getTileCount() / _ocount;
+
+ // create our mapping from orientation to animation sequence index
+ for (var ii :int = 0; ii < _ocount; ii++) {
+ _orients.put(actseq.orients[ii], ii);
+ }
+ }
+
+ public function getFrames (orient :int, callback :Function) :void
+ {
+ var frames :Array = new Array(_fcount);
+ getFramesFromTiles(frames, 0, orient, callback);
+ }
+
+ protected function getFramesFromTiles (frames :Array, idx :int, orient :int,
+ callback :Function) :void
+ {
+ if (idx >= frames.length) {
+ callback(new MultiFrameBitmap(frames, _actseq.framesPerSecond));
+ } else {
+ var tile :Tile = getTile(orient, idx);
+ tile.notifyOnLoad(function() :void {
+ frames[idx] = Bitmap(tile.getImage());
+ getFramesFromTiles(frames, idx + 1, orient, callback);
+ });
+ }
+ }
+
+ // documentation inherited from interface
+ public function getOrientationCount () :int
+ {
+ return _ocount;
+ }
+
+ protected function getTileIndex (orient :int, index :int) :int
+ {
+ return _orients.get(orient) * _fcount + index;
+ }
+
+ protected function getTile (orient :int, index :int) :Tile
+ {
+ return _set.getTile(getTileIndex(orient, index));
+ }
+
+ // documentation inherited from interface
+ public function getXOrigin (orient :int, index :int) :int
+ {
+ return _actseq.origin.x;
+ }
+
+ // documentation inherited from interface
+ public function getYOrigin (orient :int, index :int) :int
+ {
+ return _actseq.origin.y;
+ }
+
+ // documentation inherited from interface
+ public function cloneColorized (zations :Array) :ActionFrames
+ {
+ return new TileSetFrameImage(_set.cloneWithZations(zations), _actseq);
+ }
+
+ // documentation inherited from interface
+ public function cloneTranslated (dx :int, dy :int) :ActionFrames
+ {
+ return new TileSetFrameImage(_set, _actseq, dx, dy);
+ }
+
+ /** The tileset from which we obtain our frame images. */
+ protected var _set :TileSet;
+
+ /** The action sequence for which we're providing frame images. */
+ protected var _actseq :ActionSequence;
+
+ /** A translation to apply to the images. */
+ protected var _dx :int;
+ protected var _dy :int;
+
+ /** Frame and orientation counts. */
+ protected var _fcount :int;
+ protected var _ocount :int;
+
+ /** A mapping from orientation code to animation sequence index. */
+ protected var _orients :Map = Maps.newMapOf(int);
+}
+}
\ No newline at end of file
diff --git a/src/as/com/threerings/util/DirectionUtil.as b/src/as/com/threerings/util/DirectionUtil.as
index c0dbd018..7d3c3977 100644
--- a/src/as/com/threerings/util/DirectionUtil.as
+++ b/src/as/com/threerings/util/DirectionUtil.as
@@ -74,7 +74,7 @@ public class DirectionUtil extends DirectionCodes
public static function fromShortString (dirstr :String) :int
{
for (var ii :int = 0; ii < FINE_DIRECTION_COUNT; ii++) {
- if (SHORT_DIR_STRINGS[ii].equals(dirstr)) {
+ if (SHORT_DIR_STRINGS[ii] == dirstr) {
return ii;
}
}