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; } }